UNPKG

@embeddable.com/sdk-core

Version:

Core Embeddable SDK module responsible for web-components bundling and publishing.

228 lines (185 loc) 6.14 kB
import { server } from "./../../../mocks/server"; import { vi } from "vitest"; import { mkdir, access, writeFile, readFile } from "fs/promises"; import { CREDENTIALS_DIR, CREDENTIALS_FILE } from "./credentials"; import { http, HttpResponse } from "msw"; import { AxiosError } from "axios"; vi.mock("./logger", () => ({ initLogger: vi.fn(), logError: vi.fn(), })); vi.mock("fs/promises", () => ({ readFile: vi.fn(), writeFile: vi.fn(), access: vi.fn(), mkdir: vi.fn(), })); const startMock = { succeed: vi.fn(), fail: vi.fn(), }; vi.mock("ora", () => ({ default: () => ({ start: vi.fn().mockImplementation(() => startMock), info: vi.fn(), }), })); vi.mock("./rollbar.mjs", () => ({ default: vi.fn(), })); vi.mock("open", () => ({ default: vi.fn(), })); vi.mock("node:fs", () => ({ existsSync: vi.fn(), })); // Mock provideConfig directly to avoid file:// URL mocking issues vi.mock("./provideConfig", () => ({ default: vi.fn().mockResolvedValue({ authDomain: "test-domain.com", authClientId: "test-client-id", audienceUrl: "test-audience-url", }), })); // Import after mocks are set up import login, { resolveFiles, getToken } from "./login"; const makeJwt = (payload: Record<string, unknown>) => { const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })) .toString("base64url"); const body = Buffer.from(JSON.stringify(payload)).toString("base64url"); return `${header}.${body}.signature`; }; describe("login", () => { beforeEach(async () => { vi.resetModules(); // Re-mock provideConfig after resetModules const provideConfigModule = await import("./provideConfig"); vi.mocked(provideConfigModule.default).mockResolvedValue({ authDomain: "test-domain.com", authClientId: "test-client-id", audienceUrl: "test-audience-url", } as any); vi.mocked(readFile).mockImplementation(async () => Buffer.from(`{"access_token":"mocked-token"}`), ); vi.mocked(access).mockImplementation(async () => { throw new Error(); }); vi.mocked(mkdir).mockImplementation(async () => "mocked"); vi.mocked(writeFile).mockImplementation(async () => undefined); }); vi.mock("./reportErrorToRollbar", () => vi.fn()); vi.mock("./sleep", () => vi.fn()); it("should resolve files", async () => { await resolveFiles(); expect(access).toHaveBeenCalledWith(CREDENTIALS_DIR); expect(mkdir).toHaveBeenCalledWith(CREDENTIALS_DIR); expect(writeFile).toHaveBeenCalledWith(CREDENTIALS_FILE, ""); }); it("should get token", async () => { const token = await getToken(); expect(token).toBe("mocked-token"); }); it("should return a non-expired JWT access token without refreshing", async () => { const futureExp = Math.floor(Date.now() / 1000) + 60 * 60; const jwt = makeJwt({ exp: futureExp }); vi.mocked(readFile).mockResolvedValue( Buffer.from( JSON.stringify({ access_token: jwt, refresh_token: "rt-A" }), ), ); const token = await getToken(); expect(token).toBe(jwt); expect(writeFile).not.toHaveBeenCalled(); }); it("should refresh and persist a new access token when the cached one is expired", async () => { const pastExp = Math.floor(Date.now() / 1000) - 60; const expiredJwt = makeJwt({ exp: pastExp }); vi.mocked(readFile).mockResolvedValue( Buffer.from( JSON.stringify({ access_token: expiredJwt, refresh_token: "rt-A", }), ), ); server.use( http.post("**/oauth/token", () => HttpResponse.json({ access_token: "fresh-access-token", refresh_token: "rt-B", expires_in: 1800, }), ), ); const token = await getToken(); expect(token).toBe("fresh-access-token"); expect(writeFile).toHaveBeenCalledWith( CREDENTIALS_FILE, JSON.stringify({ access_token: "fresh-access-token", refresh_token: "rt-B", expires_in: 1800, }), ); }); it("should return the stale access token when refresh fails", async () => { const pastExp = Math.floor(Date.now() / 1000) - 60; const expiredJwt = makeJwt({ exp: pastExp }); vi.mocked(readFile).mockResolvedValue( Buffer.from( JSON.stringify({ access_token: expiredJwt, refresh_token: "rt-A", }), ), ); server.use( http.post("**/oauth/token", () => HttpResponse.json( { error: "invalid_grant" }, { status: 403 }, ), ), ); const token = await getToken(); expect(token).toBe(expiredJwt); expect(writeFile).not.toHaveBeenCalled(); }); it("should return the stale access token when no refresh_token is stored", async () => { const pastExp = Math.floor(Date.now() / 1000) - 60; const expiredJwt = makeJwt({ exp: pastExp }); vi.mocked(readFile).mockResolvedValue( Buffer.from(JSON.stringify({ access_token: expiredJwt })), ); const token = await getToken(); expect(token).toBe(expiredJwt); expect(writeFile).not.toHaveBeenCalled(); }); it("should login by saving token in the credentials file", async () => { await login(); expect(startMock.succeed).toHaveBeenCalledWith( "You are successfully authenticated now!", ); expect(writeFile).toHaveBeenCalledWith( CREDENTIALS_FILE, JSON.stringify({ access_token: "mocked-token " }), ); }); it("should fail to login when the response returns 500 error", async () => { vi.spyOn(console, "log").mockImplementation(() => undefined); vi.spyOn(process, "exit").mockImplementation(() => undefined as never); server.use( http.post("**/oauth/device/code", () => { return new HttpResponse(null, { status: 500 }); }), ); await login(); expect(startMock.fail).toHaveBeenCalledWith( "Authentication failed. Please try again.", ); expect((console.log as any).mock.calls[0][0]).toMatchInlineSnapshot( `[AxiosError: Request failed with status code 500]`, ); }); });