Skip to content

Commit

Permalink
fix(init): Fix failed to create repository error in init (#1179)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrutayisire authored Oct 26, 2023
1 parent 90a2bba commit e132712
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 29 deletions.
77 changes: 53 additions & 24 deletions packages/init/src/SliceMachineInitProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ Continue with next steps in Slice Machine.
if (ctLibrary.ids.length === 0) {
return true;
}
} catch (e) {
} catch (error) {
return true;
}

Expand Down Expand Up @@ -549,25 +549,9 @@ Continue with next steps in Slice Machine.

if (!isLoggedIn) {
parentTask.output = "Press any key to open the browser to login...";
await new Promise((resolve) => {
const initialRawMode = !!process.stdin.isRaw;
process.stdin.setRawMode?.(true);
process.stdin.resume();
process.stdin.once("data", (data: Buffer) => {
process.stdin.setRawMode?.(initialRawMode);
process.stdin.pause();
resolve(data.toString("utf-8"));
});
});

await this.pressKeyToLogin();
parentTask.output = "Browser opened, waiting for you to login...";
const { port, url } = await this.manager.user.getLoginSessionInfo();
await this.manager.user.nodeLoginSession({
port,
onListenCallback() {
open(url);
},
});
await this.waitingForLogin();
}

parentTask.output = "";
Expand Down Expand Up @@ -600,6 +584,29 @@ Continue with next steps in Slice Machine.
]);
}

protected async pressKeyToLogin(): Promise<void> {
await new Promise((resolve) => {
const initialRawMode = !!process.stdin.isRaw;
process.stdin.setRawMode?.(true);
process.stdin.resume();
process.stdin.once("data", (data: Buffer) => {
process.stdin.setRawMode?.(initialRawMode);
process.stdin.pause();
resolve(data.toString("utf-8"));
});
});
}

protected async waitingForLogin(): Promise<void> {
const { port, url } = await this.manager.user.getLoginSessionInfo();
await this.manager.user.nodeLoginSession({
port,
onListenCallback() {
open(url);
},
});
}

protected useRepositoryFlag(): Promise<void> {
return listrRun([
{
Expand Down Expand Up @@ -871,13 +878,35 @@ ${chalk.cyan("?")} Your Prismic repository name`.replace("\n", ""),
"Project framework must be available through context to run `createNewRepository`",
);

await this.manager.prismicRepository.create({
domain: this.context.repository.domain,
framework: this.context.framework.wroomTelemetryID,
starterId: this.context.starterId,
});
try {
await this.manager.prismicRepository.create({
domain: this.context.repository.domain,
framework: this.context.framework.wroomTelemetryID,
starterId: this.context.starterId,
});
} catch (error) {
// When we have an error here, it's most probably because the user has a stale SESSION cookie

// Ensure to logout user to remove SESSION and prismic-auth cookies
await this.manager.user.logout();

// Force a new login to get a brand new cookies value
task.output =
"It seems there is an authentication problem, press any key to open the browser to login again...";
await this.pressKeyToLogin();
task.output = "Browser opened, waiting for you to login...";
await this.waitingForLogin();

// Try to create repository again with the new cookies value
await this.manager.prismicRepository.create({
domain: this.context.repository.domain,
framework: this.context.framework.wroomTelemetryID,
starterId: this.context.starterId,
});
}

this.context.repository.exists = true;
task.output = "";
task.title = `Created new repository ${chalk.cyan(
this.context.repository.domain,
)}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { beforeEach, expect, it, TestContext } from "vitest";
import http from "node:http";
import { beforeEach, expect, it, TestContext, vi } from "vitest";
import { stdin as mockStdin } from "mock-stdin";

import { createSliceMachineInitProcess, SliceMachineInitProcess } from "../src";
import { UNIVERSAL } from "../src/lib/framework";

import { createPrismicAuthLoginResponse } from "./__testutils__/createPrismicAuthLoginResponse";
import {
createPrismicAuthLoginResponse,
PrismicAuthLoginResponse,
} from "./__testutils__/createPrismicAuthLoginResponse";
import { mockPrismicRepositoryAPI } from "./__testutils__/mockPrismicRepositoryAPI";
import { mockPrismicUserAPI } from "./__testutils__/mockPrismicUserAPI";
import { mockPrismicAuthAPI } from "./__testutils__/mockPrismicAuthAPI";
Expand All @@ -16,6 +21,12 @@ import { watchStd } from "./__testutils__/watchStd";
const initProcess = createSliceMachineInitProcess();
const spiedManager = spyManager(initProcess);

vi.mock("open", () => {
return {
default: vi.fn(),
};
});

beforeEach(() => {
setContext(initProcess, {
framework: UNIVERSAL,
Expand All @@ -30,7 +41,9 @@ const mockPrismicAPIs = async (
ctx: TestContext,
initProcess: SliceMachineInitProcess,
domain?: string,
): Promise<void> => {
): Promise<{
prismicAuthLoginResponse: PrismicAuthLoginResponse;
}> => {
const prismicAuthLoginResponse = createPrismicAuthLoginResponse();
mockPrismicUserAPI(ctx);
mockPrismicAuthAPI(ctx);
Expand All @@ -44,9 +57,42 @@ const mockPrismicAPIs = async (
domain: domain ?? initProcess.context.repository?.domain,
},
});

return { prismicAuthLoginResponse };
};

it.skip("creates repository from context", async (ctx) => {
const loginWithStdin = async (
prismicAuthLoginResponse: PrismicAuthLoginResponse,
) => {
const stdin = mockStdin();

await new Promise((res) => setTimeout(res, 50));
expect(spiedManager.prismicRepository.create).toHaveBeenCalledOnce();

stdin.send("o").restore();
await new Promise((res) => setTimeout(res, 50));

const port: number =
spiedManager.user.getLoginSessionInfo.mock.results[0].value.port;
const body = JSON.stringify(prismicAuthLoginResponse);

// We use low-level `http` because node-fetch has some issue with 127.0.0.1 on CIs
const request = http.request({
host: "127.0.0.1",
port: `${port}`,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body),
},
});
request.write(body);
request.end();
await new Promise((res) => setTimeout(res, 50));
};

it("create repository from context", async (ctx) => {
await mockPrismicAPIs(ctx, initProcess);

await watchStd(async () => {
Expand All @@ -57,12 +103,53 @@ it.skip("creates repository from context", async (ctx) => {
expect(spiedManager.prismicRepository.create).toHaveBeenCalledOnce();
expect(spiedManager.prismicRepository.create).toHaveBeenNthCalledWith(1, {
domain: "new-repo",
framework: UNIVERSAL.sliceMachineTelemetryID,
framework: UNIVERSAL.wroomTelemetryID,
starterId: undefined,
});
// @ts-expect-error - Accessing protected property
expect(initProcess.context.repository?.exists).toBe(true);
});

it("creates repository with a retry after a first fail", async (ctx) => {
const { prismicAuthLoginResponse } = await mockPrismicAPIs(ctx, initProcess);

// Mock only the first create call to reject first one and resole the second one
spiedManager.prismicRepository.create.mockImplementationOnce(() => {
return Promise.reject(new Error("Failed to create repository"));
});

await watchStd(async () => {
// @ts-expect-error - Accessing protected method
initProcess.createNewRepository();
await loginWithStdin(prismicAuthLoginResponse);
});

expect(spiedManager.prismicRepository.create).toHaveBeenCalledTimes(2);
// @ts-expect-error - Accessing protected property
expect(initProcess.context.repository?.exists).toBe(true);
});

it("fail to create repository after a second fail", async (ctx) => {
const { prismicAuthLoginResponse } = await mockPrismicAPIs(ctx, initProcess);

// Mock create call to reject first and second calls
spiedManager.prismicRepository.create.mockImplementation(() => {
return Promise.reject(new Error("Failed to create repository"));
});

await watchStd(async () => {
// @ts-expect-error - Accessing protected method
expect(initProcess.createNewRepository()).rejects.toThrow(
"Failed to create repository",
);
await loginWithStdin(prismicAuthLoginResponse);
});

expect(spiedManager.prismicRepository.create).toHaveBeenCalledTimes(2);
// @ts-expect-error - Accessing protected property
expect(initProcess.context.repository?.exists).toBe(false);
});

it("throws if context is missing framework", async () => {
updateContext(initProcess, {
framework: undefined,
Expand Down

0 comments on commit e132712

Please sign in to comment.