Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { makeTestWithFixture } from '../../test/utils.js';
2import ApiKeyService from './apiKeyService.js';
3import type { Kysely } from 'kysely';
4import { type CombinedPg } from '../combinedDbTypes.js';
5
6// Mock Kysely database
7const mockDb = {
8 insertInto: jest.fn(),
9 selectFrom: jest.fn(),
10 updateTable: jest.fn(),
11 deleteFrom: jest.fn(),
12} as unknown as Kysely<CombinedPg>;
13
14describe('ApiKeyService', () => {
15 const fakeOrg = { id: '1234', name: 'Random Org' };
16
17 const testWithFixtures = makeTestWithFixture(() => {
18 const sut = new ApiKeyService(mockDb);
19 return { sut };
20 });
21
22 beforeEach(() => {
23 jest.clearAllMocks();
24 });
25
26 describe('#createApiKey', () => {
27 testWithFixtures(
28 'should generate a key, store it, return it + the generated key id',
29 async ({ sut }) => {
30 // Mock the database operations
31 const mockInsert = {
32 values: jest.fn().mockReturnThis(),
33 returningAll: jest.fn().mockReturnThis(),
34 executeTakeFirstOrThrow: jest.fn().mockResolvedValue({
35 id: 'key-123',
36 org_id: fakeOrg.id,
37 key_hash: 'hashed-key',
38 name: 'Test Key',
39 description: 'Test Description',
40 is_active: true,
41 created_at: new Date(),
42 updated_at: new Date(),
43 last_used_at: null,
44 created_by: null,
45 }),
46 };
47
48 const mockUpdate = {
49 set: jest.fn().mockReturnThis(),
50 where: jest.fn().mockReturnThis(),
51 execute: jest.fn().mockResolvedValue([]),
52 };
53
54 (mockDb.insertInto as jest.Mock).mockReturnValue(mockInsert);
55 (mockDb.updateTable as jest.Mock).mockReturnValue(mockUpdate);
56
57 const res = await sut.createApiKey(
58 fakeOrg.id,
59 'Test Key',
60 'Test Description',
61 null,
62 );
63
64 // Verify the key was generated and stored
65 expect(res.apiKey).toBeDefined();
66 expect(typeof res.apiKey).toBe('string');
67 expect(res.record.id).toBe('key-123');
68 expect(res.record.orgId).toBe(fakeOrg.id);
69 },
70 );
71 });
72
73 describe('#getActiveApiKeyForOrg', () => {
74 testWithFixtures(
75 'should retrieve the active key for an org',
76 async ({ sut }) => {
77 const mockSelect = {
78 selectAll: jest.fn().mockReturnThis(),
79 where: jest.fn().mockReturnThis(),
80 executeTakeFirst: jest.fn().mockResolvedValue({
81 id: 'key-123',
82 org_id: fakeOrg.id,
83 key_hash: 'hashed-key',
84 name: 'Test Key',
85 description: 'Test Description',
86 is_active: true,
87 created_at: new Date(),
88 updated_at: new Date(),
89 last_used_at: null,
90 created_by: null,
91 }),
92 };
93
94 (mockDb.selectFrom as jest.Mock).mockReturnValue(mockSelect);
95
96 const result = await sut.getActiveApiKeyForOrg(fakeOrg.id);
97
98 expect(result).toBeDefined();
99 expect(result?.id).toBe('key-123');
100 expect(result?.orgId).toBe(fakeOrg.id);
101 },
102 );
103
104 testWithFixtures(
105 'should return null if no active key exists',
106 async ({ sut }) => {
107 const mockSelect = {
108 selectAll: jest.fn().mockReturnThis(),
109 where: jest.fn().mockReturnThis(),
110 executeTakeFirst: jest.fn().mockResolvedValue(undefined),
111 };
112
113 (mockDb.selectFrom as jest.Mock).mockReturnValue(mockSelect);
114
115 const result = await sut.getActiveApiKeyForOrg(fakeOrg.id);
116
117 expect(result).toBeNull();
118 },
119 );
120 });
121
122 describe('#validateApiKey', () => {
123 testWithFixtures(
124 'should validate a key and return org ID',
125 async ({ sut }) => {
126 const mockSelect = {
127 select: jest.fn().mockReturnThis(),
128 where: jest.fn().mockReturnThis(),
129 executeTakeFirst: jest.fn().mockResolvedValue({
130 org_id: fakeOrg.id,
131 last_used_at: new Date(),
132 }),
133 };
134
135 const mockUpdate = {
136 set: jest.fn().mockReturnThis(),
137 where: jest.fn().mockReturnThis(),
138 execute: jest.fn().mockResolvedValue([]),
139 };
140
141 (mockDb.selectFrom as jest.Mock).mockReturnValue(mockSelect);
142 (mockDb.updateTable as jest.Mock).mockReturnValue(mockUpdate);
143
144 const result = await sut.validateApiKey('test-key');
145
146 expect(result).toBe(fakeOrg.id);
147 },
148 );
149
150 testWithFixtures(
151 'should return null for invalid key',
152 async ({ sut }) => {
153 const mockSelect = {
154 select: jest.fn().mockReturnThis(),
155 where: jest.fn().mockReturnThis(),
156 executeTakeFirst: jest.fn().mockResolvedValue(undefined),
157 };
158
159 (mockDb.selectFrom as jest.Mock).mockReturnValue(mockSelect);
160
161 const result = await sut.validateApiKey('invalid-key');
162
163 expect(result).toBeNull();
164 },
165 );
166 });
167});