import { describe, expect, it, vi } from "vitest"; import { ColineApiClient } from "./client"; function createJsonResponse(payload: unknown, status = 200): Response { return new Response(JSON.stringify(payload), { status, headers: { "Content-Type": "application/json" }, }); } describe("ColineApiClient", () => { it("installs a workspace app with explicit granted permissions", async () => { const fetchMock = vi.fn().mockResolvedValue( createJsonResponse({ data: { installId: "install_123", appId: "app_123", appKey: "com.coline.notes", version: "0.1.0", grantedPermissions: ["app.home.read"], }, }), ); const client = new ColineApiClient({ baseUrl: "https://api.coline.app", apiKey: "test-key", fetch: fetchMock, }); const result = await client.installWorkspaceApp("acme", { appId: "app_123", grantedPermissions: ["app.home.read"], }); expect(result.installId).toBe("install_123"); expect(fetchMock).toHaveBeenCalledWith( "https://api.coline.app/api/v1/workspaces/acme/apps", expect.objectContaining({ method: "POST", headers: expect.any(Headers), }), ); const headers = fetchMock.mock.calls[0]?.[1]?.headers as Headers; expect(headers.get("Authorization")).toBe("Bearer test-key"); }); it("loads and updates workspace app permissions", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( createJsonResponse({ data: { install: { installId: "install_123", appId: "app_123", appKey: "com.coline.notes", name: "Notes", description: null, iconUrl: null, status: "active", installScope: "workspace", scopeSubjectId: "workspace_123", version: "0.1.0", }, availablePermissions: ["app.home.read", "files.write"], grantedPermissions: ["app.home.read"], permissions: { canManageApps: true, }, }, }), ) .mockResolvedValueOnce( createJsonResponse({ data: { install: { installId: "install_123", appId: "app_123", appKey: "com.coline.notes", name: "Notes", description: null, iconUrl: null, status: "active", installScope: "workspace", scopeSubjectId: "workspace_123", version: "0.1.0", }, availablePermissions: ["app.home.read", "files.write"], grantedPermissions: ["app.home.read", "files.write"], permissions: { canManageApps: true, }, }, }), ); const client = new ColineApiClient({ baseUrl: "https://api.coline.app", fetch: fetchMock, }); const currentPermissions = await client.getWorkspaceAppPermissions( "acme", "com.coline.notes", ); expect(currentPermissions.grantedPermissions).toEqual(["app.home.read"]); const updatedPermissions = await client.updateWorkspaceAppPermissions( "acme", "com.coline.notes", { grantedPermissions: ["app.home.read", "files.write"], }, ); expect(updatedPermissions.grantedPermissions).toEqual([ "app.home.read", "files.write", ]); }); it("loads app detail, app versions, installed app, and uninstalls it", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( createJsonResponse({ data: { app: { appId: "app_123", appKey: "com.coline.notes", name: "Notes", description: null, iconUrl: null, status: "active", ownerUserId: "user_123", latestPublishedVersion: "0.1.0", latestPublishedVersionId: "ver_123", }, }, }), ) .mockResolvedValueOnce( createJsonResponse({ data: { versions: [ { versionId: "ver_123", version: "0.1.0", status: "published", apiBaseUrl: "https://notes.example.com", permissions: ["app.home.read"], fileTypes: [], createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", }, ], }, }), ) .mockResolvedValueOnce( createJsonResponse({ data: { install: { installId: "install_123", appId: "app_123", appKey: "com.coline.notes", name: "Notes", description: null, iconUrl: null, status: "active", installScope: "workspace", scopeSubjectId: "workspace_123", version: "0.1.0", }, permissions: { canManageApps: true, }, }, }), ) .mockResolvedValueOnce(createJsonResponse({ data: { ok: true } })); const client = new ColineApiClient({ baseUrl: "https://api.coline.app", fetch: fetchMock, }); const app = await client.getApp("com.coline.notes"); expect(app.app.appKey).toBe("com.coline.notes"); const versions = await client.listAppVersions("com.coline.notes"); expect(versions.versions[0]?.version).toBe("0.1.0"); const installed = await client.getWorkspaceInstalledApp("acme", "com.coline.notes"); expect(installed.install.installId).toBe("install_123"); const uninstallResult = await client.uninstallWorkspaceApp( "acme", "com.coline.notes", ); expect(uninstallResult.ok).toBe(true); }); it("loads and updates app file documents through the public runtime API", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce( createJsonResponse({ data: { file: { id: "file_123", name: "Customer", fileType: "com.acme.customer", appKey: "acme-crm", appName: "Acme CRM", typeLabel: "Customer", openHref: "/acme/acme-crm/files/file_123", updatedAt: "2026-03-18T08:00:00.000Z", }, document: { status: "active" }, version: 2, capabilities: { canRead: true, canWrite: true, canManage: false, }, }, }), ) .mockResolvedValueOnce( createJsonResponse({ data: { document: { status: "archived" }, version: 3, updatedAt: "2026-03-18T09:00:00.000Z", }, }), ); const client = new ColineApiClient({ baseUrl: "https://api.coline.app", fetch: fetchMock, }); const file = await client.getAppFileDocument("acme", "acme-crm", "file_123"); expect(file.file.id).toBe("file_123"); expect(file.version).toBe(2); const updated = await client.updateAppFileDocument( "acme", "acme-crm", "file_123", { status: "archived" }, ); expect(updated.version).toBe(3); }); });