| import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; |
| import * as fsPromises from "fs/promises"; |
|
|
| |
| vi.mock("next/server", () => ({ |
| NextResponse: { |
| json: vi.fn((body, init) => ({ |
| status: init?.status || 200, |
| body, |
| json: async () => body, |
| })), |
| }, |
| })); |
|
|
| |
| vi.mock("os", () => ({ |
| default: { homedir: vi.fn(() => "/mock/home") }, |
| homedir: vi.fn(() => "/mock/home"), |
| })); |
|
|
| |
| vi.mock("fs/promises", () => ({ |
| access: vi.fn(), |
| constants: { R_OK: 4 }, |
| })); |
|
|
| |
| const mockDbInstance = { |
| prepare: vi.fn(), |
| close: vi.fn(), |
| __throwOnConstruct: false, |
| }; |
|
|
| |
| vi.mock("better-sqlite3", () => ({ |
| default: class MockDatabase { |
| constructor() { |
| if (mockDbInstance.__throwOnConstruct) { |
| throw new Error("SQLITE_CANTOPEN"); |
| } |
| return mockDbInstance; |
| } |
| }, |
| })); |
|
|
| |
| let GET; |
|
|
| describe("GET /api/oauth/cursor/auto-import", () => { |
| const originalPlatform = process.platform; |
|
|
| beforeEach(async () => { |
| vi.clearAllMocks(); |
| mockDbInstance.__throwOnConstruct = false; |
| |
| Object.defineProperty(process, "platform", { value: "darwin", writable: true }); |
| |
| const mod = await import("../../src/app/api/oauth/cursor/auto-import/route.js"); |
| GET = mod.GET; |
| }); |
|
|
| afterEach(() => { |
| Object.defineProperty(process, "platform", { value: originalPlatform, writable: true }); |
| }); |
|
|
| |
|
|
| it("returns not-found when no macOS cursor db paths are accessible", async () => { |
| vi.mocked(fsPromises.access).mockRejectedValue(new Error("ENOENT")); |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(false); |
| expect(response.body.error).toContain("Cursor database not found in known macOS locations"); |
| }); |
|
|
| it("returns descriptive error if macOS db file exists but cannot be opened", async () => { |
| vi.mocked(fsPromises.access).mockResolvedValue(); |
| mockDbInstance.__throwOnConstruct = true; |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(false); |
| expect(response.body.error).toContain("could not open it"); |
| expect(response.body.error).toContain("SQLITE_CANTOPEN"); |
| }); |
|
|
| |
|
|
| it("extracts tokens using exact keys", async () => { |
| vi.mocked(fsPromises.access).mockResolvedValue(); |
| mockDbInstance.prepare.mockReturnValue({ |
| all: vi.fn().mockReturnValue([ |
| { key: "cursorAuth/accessToken", value: "test-token" }, |
| { key: "storage.serviceMachineId", value: "test-machine-id" }, |
| ]), |
| }); |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(true); |
| expect(response.body.accessToken).toBe("test-token"); |
| expect(response.body.machineId).toBe("test-machine-id"); |
| expect(mockDbInstance.close).toHaveBeenCalled(); |
| }); |
|
|
| it("unwraps JSON-encoded string values", async () => { |
| vi.mocked(fsPromises.access).mockResolvedValue(); |
| mockDbInstance.prepare.mockReturnValue({ |
| all: vi.fn().mockReturnValue([ |
| { key: "cursorAuth/accessToken", value: '"json-token"' }, |
| { key: "storage.serviceMachineId", value: '"json-machine-id"' }, |
| ]), |
| }); |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(true); |
| expect(response.body.accessToken).toBe("json-token"); |
| expect(response.body.machineId).toBe("json-machine-id"); |
| }); |
|
|
| |
|
|
| it("falls back to fuzzy key matching on macOS when exact keys are missing", async () => { |
| vi.mocked(fsPromises.access).mockResolvedValue(); |
| mockDbInstance.prepare.mockImplementation((query) => { |
| if (query.includes("IN (")) { |
| return { all: vi.fn().mockReturnValue([]) }; |
| } |
| |
| return { |
| all: vi.fn().mockReturnValue([ |
| { key: "cursorAuth/someOtherAccessTokenKey", value: "fallback-token" }, |
| { key: "storage.someMachineId", value: "fallback-machine" }, |
| ]), |
| }; |
| }); |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(true); |
| expect(response.body.accessToken).toBe("fallback-token"); |
| expect(response.body.machineId).toBe("fallback-machine"); |
| }); |
|
|
| it("returns login-prompt error when tokens are missing even after fallback", async () => { |
| vi.mocked(fsPromises.access).mockResolvedValue(); |
| mockDbInstance.prepare.mockReturnValue({ |
| all: vi.fn().mockReturnValue([]), |
| }); |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(false); |
| expect(response.body.error).toContain("Please login to Cursor IDE first"); |
| }); |
|
|
| |
|
|
| it("linux uses single hardcoded path and original error message", async () => { |
| Object.defineProperty(process, "platform", { value: "linux", writable: true }); |
| vi.mocked(fsPromises.access).mockRejectedValue(new Error("ENOENT")); |
| mockDbInstance.__throwOnConstruct = true; |
|
|
| const response = await GET(); |
|
|
| expect(response.body.found).toBe(false); |
| expect(response.body.error).toBe( |
| "Cursor database not found. Make sure Cursor IDE is installed and you are logged in." |
| ); |
| |
| expect(fsPromises.access).not.toHaveBeenCalled(); |
| }); |
|
|
| it("unsupported platform returns 400", async () => { |
| Object.defineProperty(process, "platform", { value: "freebsd", writable: true }); |
|
|
| const response = await GET(); |
|
|
| expect(response.status).toBe(400); |
| expect(response.body.error).toBe("Unsupported platform"); |
| }); |
| }); |
|
|