File size: 6,279 Bytes
88c4c60 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import * as fsPromises from "fs/promises";
// Mock next/server
vi.mock("next/server", () => ({
NextResponse: {
json: vi.fn((body, init) => ({
status: init?.status || 200,
body,
json: async () => body,
})),
},
}));
// Mock os
vi.mock("os", () => ({
default: { homedir: vi.fn(() => "/mock/home") },
homedir: vi.fn(() => "/mock/home"),
}));
// Mock fs/promises
vi.mock("fs/promises", () => ({
access: vi.fn(),
constants: { R_OK: 4 },
}));
// Shared mock db instance
const mockDbInstance = {
prepare: vi.fn(),
close: vi.fn(),
__throwOnConstruct: false,
};
// Mock better-sqlite3 as a class so `new Database(...)` works
vi.mock("better-sqlite3", () => ({
default: class MockDatabase {
constructor() {
if (mockDbInstance.__throwOnConstruct) {
throw new Error("SQLITE_CANTOPEN");
}
return mockDbInstance;
}
},
}));
// We need to dynamically import after mocks are registered
let GET;
describe("GET /api/oauth/cursor/auto-import", () => {
const originalPlatform = process.platform;
beforeEach(async () => {
vi.clearAllMocks();
mockDbInstance.__throwOnConstruct = false;
// Force darwin so macOS-specific logic is exercised
Object.defineProperty(process, "platform", { value: "darwin", writable: true });
// Re-import to pick up fresh mocks each run
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 });
});
// ββ macOS path probing ββββββββββββββββββββββββββββββββββββββββββββββββ
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");
});
// ββ Token extraction ββββββββββββββββββββββββββββββββββββββββββββββββββ
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");
});
// ββ Fuzzy fallback (macOS only) βββββββββββββββββββββββββββββββββββββββ
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([]) };
}
// Fuzzy LIKE query
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");
});
// ββ Backwards-compatible: linux/win32 keep original single-path logic β
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."
);
// fs/promises.access should NOT have been called (linux skips probing)
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");
});
});
|