import { describe, it, expect, vi, afterEach } from 'vitest'; import { createOrgClient } from './client.js'; import type { OrganizationDoc, WorkspaceDoc, MembershipDoc, LicenseDoc } from './types.js'; const baseConfig = { baseUrl: 'http://localhost:4003/api', productId: 'testapp', getAccessToken: () => 'admin-token', }; function mockOrg(overrides?: Partial): OrganizationDoc { return { id: 'org_1', productId: 'testapp', name: 'Test Org', slug: 'test-org', status: 'active', ownerUserId: 'user-1', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', ...overrides, }; } function mockWorkspace(overrides?: Partial): WorkspaceDoc { return { id: 'ws_1', orgId: 'org_1', productId: 'testapp', name: 'Engineering', slug: 'engineering', status: 'active', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', ...overrides, }; } function mockMembership(overrides?: Partial): MembershipDoc { return { id: 'mbr_org1_user1_org', orgId: 'org_1', productId: 'testapp', scope: 'org', userId: 'user-1', role: 'admin', status: 'active', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', ...overrides, }; } function mockLicense(overrides?: Partial): LicenseDoc { return { id: 'lic_1', productId: 'testapp', key: 'LYSNR-XXXX-YYYY-ZZZZ', userId: 'user-1', plan: 'pro', status: 'active', activatedAt: '2026-01-01T00:00:00Z', expiresAt: null, deviceIds: ['device-1'], maxDevices: 3, createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', ...overrides, }; } describe('createOrgClient', () => { afterEach(() => { vi.restoreAllMocks(); }); it('should list orgs', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve([mockOrg()]), }) ); const client = createOrgClient(baseConfig); const result = await client.listOrgs(); expect(result).toHaveLength(1); expect(result[0].name).toBe('Test Org'); }); it('should create an org', async () => { const org = mockOrg(); vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(org), }) ); const client = createOrgClient(baseConfig); const result = await client.createOrg({ name: 'Test Org', slug: 'test-org' }); expect(result.id).toBe('org_1'); }); it('should get an org by id', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockOrg()), }) ); const client = createOrgClient(baseConfig); const result = await client.getOrg('org_1'); expect(result.slug).toBe('test-org'); }); it('should list workspaces for an org', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve([mockWorkspace()]), }) ); const client = createOrgClient(baseConfig); const result = await client.listWorkspaces('org_1'); expect(result).toHaveLength(1); expect(result[0].name).toBe('Engineering'); }); it('should create a workspace', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockWorkspace()), }) ); const client = createOrgClient(baseConfig); const result = await client.createWorkspace('org_1', { name: 'Engineering', slug: 'engineering', }); expect(result.orgId).toBe('org_1'); }); it('should list memberships', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve([mockMembership()]), }) ); const client = createOrgClient(baseConfig); const result = await client.listMemberships('org_1'); expect(result).toHaveLength(1); expect(result[0].role).toBe('admin'); }); it('should add a member', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockMembership()), }) ); const client = createOrgClient(baseConfig); const result = await client.addMember('org_1', { userId: 'user-1', role: 'admin' }); expect(result.userId).toBe('user-1'); }); it('should generate a license', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockLicense()), }) ); const client = createOrgClient(baseConfig); const result = await client.generateLicense({ userId: 'user-1', plan: 'pro' }); expect(result.key).toContain('LYSNR'); }); it('should activate a license', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(mockLicense()), }) ); const client = createOrgClient(baseConfig); const result = await client.activateLicense({ key: 'LYSNR-XXXX', deviceId: 'device-1' }); expect(result.status).toBe('active'); }); it('should update an org', async () => { const org = mockOrg({ name: 'Updated Org' }); vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(org), }) ); const client = createOrgClient(baseConfig); const result = await client.updateOrg('org_1', { name: 'Updated Org' }); expect(result.name).toBe('Updated Org'); const fetchMock = globalThis.fetch as ReturnType; expect(fetchMock).toHaveBeenCalledWith( 'http://localhost:4003/api/orgs/org_1', expect.objectContaining({ method: 'PATCH' }) ); }); it('should update a workspace', async () => { const ws = mockWorkspace({ name: 'Renamed' }); vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve(ws), }) ); const client = createOrgClient(baseConfig); const result = await client.updateWorkspace('org_1', 'ws_1', { name: 'Renamed' }); expect(result.name).toBe('Renamed'); const fetchMock = globalThis.fetch as ReturnType; expect(fetchMock).toHaveBeenCalledWith( 'http://localhost:4003/api/orgs/org_1/workspaces/ws_1', expect.objectContaining({ method: 'PATCH' }) ); }); it('should deactivate a license', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, }) ); const client = createOrgClient(baseConfig); await expect( client.deactivateLicense({ key: 'LYSNR-XXXX', deviceId: 'device-1' }) ).resolves.toBeUndefined(); const fetchMock = globalThis.fetch as ReturnType; expect(fetchMock).toHaveBeenCalledWith( 'http://localhost:4003/api/licenses/deactivate', expect.objectContaining({ method: 'POST' }) ); }); it('should throw 403 for non-admin', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: false, status: 403, }) ); const client = createOrgClient(baseConfig); await expect(client.listOrgs()).rejects.toThrow('listOrgs failed: 403'); }); it('should send correct headers', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve([]), }) ); const client = createOrgClient(baseConfig); await client.listOrgs(); const fetchMock = globalThis.fetch as ReturnType; const callHeaders = fetchMock.mock.calls[0][1].headers as Record; expect(callHeaders['x-product-id']).toBe('testapp'); expect(callHeaders['Authorization']).toBe('Bearer admin-token'); expect(callHeaders['x-request-id']).toBeDefined(); }); });