Skip to content

Commit fafd8d6

Browse files
strukalexclaude
andcommitted
merge: resolve conflicts with develop branch
Resolve merge conflicts in 4 test files by combining develop's mock-based test structure with PLG branch's sessionId/clientIp/apiKeyId feature coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 372bdca + 104471b commit fafd8d6

178 files changed

Lines changed: 25145 additions & 8538 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-instance-images.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
required: false
99
type: string
1010

11+
permissions:
12+
contents: read
13+
1114
jobs:
1215
metadata:
1316
name: Prepare Build Metadata

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
- Do not create features that are not explicitly described in specifications, if there is a gap, include it summary notes after implementing the task. If there is a question regarding the implementation, do not make assumptions, stop and clarify from the user.
66
- When creating or modifying features, create/update documentation in /docs-md folder
77
- If you need to run `npx prisma generate`, run `npm run db:generate` from `apps/backend-services` - it's a special script that writes models into apps/temporal/src and apps/backend-services/src. Don't forget to run migrations as normal if necessary.
8-
- Do not include any document-specific implementation, the system is generic and must support arbitrary workloads
8+
- Do not include any document-specific implementation, the system is generic and must support arbitrary workloads
9+
- All backend controllers must have full Swagger/OpenAPI documentation: use specific decorators (`@ApiOkResponse`, `@ApiForbiddenResponse`, `@ApiUnauthorizedResponse`, `@ApiConflictResponse`, etc.) instead of generic `@ApiResponse`, create dedicated DTO classes with `@ApiProperty` decorators for all request/response shapes, and reference those DTOs via the `type` field in response decorators.

apps/backend-services/biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.4.7/schema.json",
33
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
44
"files": {
55
"includes": ["**", "!!**/dist", "!!**/node_modules"]

apps/backend-services/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"uuid": "13.0.0"
6767
},
6868
"devDependencies": {
69+
"@biomejs/biome": "2.4.7",
6970
"@nestjs/cli": "^11.0.13",
7071
"@nestjs/schematics": "^11.0.9",
7172
"@nestjs/testing": "11.1.9",
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import type { ApiKey } from "@generated/client";
2+
import { Test, TestingModule } from "@nestjs/testing";
3+
import { PrismaService } from "@/database/prisma.service";
4+
import { ApiKeyDbService, type CreateApiKeyData } from "./api-key-db.service";
5+
6+
const mockApiKey: ApiKey = {
7+
id: "key-1",
8+
key_hash: "hash",
9+
key_prefix: "abcd1234",
10+
group_id: "grp-1",
11+
generating_user_id: "user-1",
12+
created_at: new Date("2024-01-01"),
13+
last_used: null,
14+
};
15+
16+
const createData: CreateApiKeyData = {
17+
key_hash: "hash",
18+
key_prefix: "abcd1234",
19+
generating_user_id: "user-1",
20+
group_id: "grp-1",
21+
};
22+
23+
describe("ApiKeyDbService", () => {
24+
let service: ApiKeyDbService;
25+
let mockApiKeyPrisma: {
26+
findFirst: jest.Mock;
27+
findUnique: jest.Mock;
28+
findMany: jest.Mock;
29+
create: jest.Mock;
30+
deleteMany: jest.Mock;
31+
delete: jest.Mock;
32+
update: jest.Mock;
33+
};
34+
let mockPrisma: { apiKey: typeof mockApiKeyPrisma };
35+
36+
beforeEach(async () => {
37+
mockApiKeyPrisma = {
38+
findFirst: jest.fn(),
39+
findUnique: jest.fn(),
40+
findMany: jest.fn(),
41+
create: jest.fn(),
42+
deleteMany: jest.fn(),
43+
delete: jest.fn(),
44+
update: jest.fn(),
45+
};
46+
mockPrisma = { apiKey: mockApiKeyPrisma };
47+
48+
const module: TestingModule = await Test.createTestingModule({
49+
providers: [
50+
ApiKeyDbService,
51+
{
52+
provide: PrismaService,
53+
useValue: { prisma: mockPrisma },
54+
},
55+
],
56+
}).compile();
57+
58+
service = module.get<ApiKeyDbService>(ApiKeyDbService);
59+
});
60+
61+
it("should be defined", () => {
62+
expect(service).toBeDefined();
63+
});
64+
65+
// ---------------------------------------------------------------------------
66+
// findApiKeyByGroupId
67+
// ---------------------------------------------------------------------------
68+
69+
describe("findApiKeyByGroupId", () => {
70+
it("should return the api key from this.prisma when no tx", async () => {
71+
mockApiKeyPrisma.findFirst.mockResolvedValue(mockApiKey);
72+
73+
const result = await service.findApiKeyByGroupId("grp-1");
74+
75+
expect(result).toEqual(mockApiKey);
76+
expect(mockApiKeyPrisma.findFirst).toHaveBeenCalledWith({
77+
where: { group_id: "grp-1" },
78+
});
79+
});
80+
81+
it("should use tx when provided", async () => {
82+
const txApiKey = { findFirst: jest.fn().mockResolvedValue(mockApiKey) };
83+
const tx = { apiKey: txApiKey } as unknown as Parameters<
84+
typeof service.findApiKeyByGroupId
85+
>[1];
86+
87+
const result = await service.findApiKeyByGroupId("grp-1", tx);
88+
89+
expect(result).toEqual(mockApiKey);
90+
expect(txApiKey.findFirst).toHaveBeenCalled();
91+
expect(mockApiKeyPrisma.findFirst).not.toHaveBeenCalled();
92+
});
93+
});
94+
95+
// ---------------------------------------------------------------------------
96+
// findApiKeyById
97+
// ---------------------------------------------------------------------------
98+
99+
describe("findApiKeyById", () => {
100+
it("should return the api key from this.prisma when no tx", async () => {
101+
mockApiKeyPrisma.findUnique.mockResolvedValue(mockApiKey);
102+
103+
const result = await service.findApiKeyById("key-1");
104+
105+
expect(result).toEqual(mockApiKey);
106+
expect(mockApiKeyPrisma.findUnique).toHaveBeenCalledWith({
107+
where: { id: "key-1" },
108+
});
109+
});
110+
111+
it("should use tx when provided", async () => {
112+
const txApiKey = {
113+
findUnique: jest.fn().mockResolvedValue(mockApiKey),
114+
};
115+
const tx = { apiKey: txApiKey } as unknown as Parameters<
116+
typeof service.findApiKeyById
117+
>[1];
118+
119+
const result = await service.findApiKeyById("key-1", tx);
120+
121+
expect(result).toEqual(mockApiKey);
122+
expect(txApiKey.findUnique).toHaveBeenCalled();
123+
expect(mockApiKeyPrisma.findUnique).not.toHaveBeenCalled();
124+
});
125+
});
126+
127+
// ---------------------------------------------------------------------------
128+
// findApiKeysByPrefix
129+
// ---------------------------------------------------------------------------
130+
131+
describe("findApiKeysByPrefix", () => {
132+
it("should return matching keys from this.prisma when no tx", async () => {
133+
mockApiKeyPrisma.findMany.mockResolvedValue([mockApiKey]);
134+
135+
const result = await service.findApiKeysByPrefix("abcd1234");
136+
137+
expect(result).toEqual([mockApiKey]);
138+
expect(mockApiKeyPrisma.findMany).toHaveBeenCalledWith({
139+
where: { key_prefix: "abcd1234" },
140+
});
141+
});
142+
143+
it("should use tx when provided", async () => {
144+
const txApiKey = {
145+
findMany: jest.fn().mockResolvedValue([mockApiKey]),
146+
};
147+
const tx = { apiKey: txApiKey } as unknown as Parameters<
148+
typeof service.findApiKeysByPrefix
149+
>[1];
150+
151+
const result = await service.findApiKeysByPrefix("abcd1234", tx);
152+
153+
expect(result).toEqual([mockApiKey]);
154+
expect(txApiKey.findMany).toHaveBeenCalled();
155+
expect(mockApiKeyPrisma.findMany).not.toHaveBeenCalled();
156+
});
157+
});
158+
159+
// ---------------------------------------------------------------------------
160+
// createApiKey
161+
// ---------------------------------------------------------------------------
162+
163+
describe("createApiKey", () => {
164+
it("should create an api key using this.prisma when no tx", async () => {
165+
mockApiKeyPrisma.create.mockResolvedValue(mockApiKey);
166+
167+
const result = await service.createApiKey(createData);
168+
169+
expect(result).toEqual(mockApiKey);
170+
expect(mockApiKeyPrisma.create).toHaveBeenCalledWith({
171+
data: createData,
172+
});
173+
});
174+
175+
it("should use tx when provided", async () => {
176+
const txApiKey = { create: jest.fn().mockResolvedValue(mockApiKey) };
177+
const tx = { apiKey: txApiKey } as unknown as Parameters<
178+
typeof service.createApiKey
179+
>[1];
180+
181+
const result = await service.createApiKey(createData, tx);
182+
183+
expect(result).toEqual(mockApiKey);
184+
expect(txApiKey.create).toHaveBeenCalled();
185+
expect(mockApiKeyPrisma.create).not.toHaveBeenCalled();
186+
});
187+
});
188+
189+
// ---------------------------------------------------------------------------
190+
// deleteApiKeysByGroupId
191+
// ---------------------------------------------------------------------------
192+
193+
describe("deleteApiKeysByGroupId", () => {
194+
it("should delete keys using this.prisma when no tx", async () => {
195+
mockApiKeyPrisma.deleteMany.mockResolvedValue({ count: 1 });
196+
197+
await service.deleteApiKeysByGroupId("grp-1");
198+
199+
expect(mockApiKeyPrisma.deleteMany).toHaveBeenCalledWith({
200+
where: { group_id: "grp-1" },
201+
});
202+
});
203+
204+
it("should use tx when provided", async () => {
205+
const txApiKey = {
206+
deleteMany: jest.fn().mockResolvedValue({ count: 1 }),
207+
};
208+
const tx = { apiKey: txApiKey } as unknown as Parameters<
209+
typeof service.deleteApiKeysByGroupId
210+
>[1];
211+
212+
await service.deleteApiKeysByGroupId("grp-1", tx);
213+
214+
expect(txApiKey.deleteMany).toHaveBeenCalled();
215+
expect(mockApiKeyPrisma.deleteMany).not.toHaveBeenCalled();
216+
});
217+
});
218+
219+
// ---------------------------------------------------------------------------
220+
// deleteApiKeyById
221+
// ---------------------------------------------------------------------------
222+
223+
describe("deleteApiKeyById", () => {
224+
it("should delete the key using this.prisma when no tx", async () => {
225+
mockApiKeyPrisma.delete.mockResolvedValue(mockApiKey);
226+
227+
const result = await service.deleteApiKeyById("key-1");
228+
229+
expect(result).toEqual(mockApiKey);
230+
expect(mockApiKeyPrisma.delete).toHaveBeenCalledWith({
231+
where: { id: "key-1" },
232+
});
233+
});
234+
235+
it("should use tx when provided", async () => {
236+
const txApiKey = { delete: jest.fn().mockResolvedValue(mockApiKey) };
237+
const tx = { apiKey: txApiKey } as unknown as Parameters<
238+
typeof service.deleteApiKeyById
239+
>[1];
240+
241+
const result = await service.deleteApiKeyById("key-1", tx);
242+
243+
expect(result).toEqual(mockApiKey);
244+
expect(txApiKey.delete).toHaveBeenCalled();
245+
expect(mockApiKeyPrisma.delete).not.toHaveBeenCalled();
246+
});
247+
});
248+
249+
// ---------------------------------------------------------------------------
250+
// updateApiKeyLastUsed
251+
// ---------------------------------------------------------------------------
252+
253+
describe("updateApiKeyLastUsed", () => {
254+
it("should update the last_used timestamp using this.prisma when no tx", async () => {
255+
const updated = { ...mockApiKey, last_used: new Date() };
256+
mockApiKeyPrisma.update.mockResolvedValue(updated);
257+
258+
const result = await service.updateApiKeyLastUsed("key-1");
259+
260+
expect(result).toEqual(updated);
261+
expect(mockApiKeyPrisma.update).toHaveBeenCalledWith(
262+
expect.objectContaining({ where: { id: "key-1" } }),
263+
);
264+
});
265+
266+
it("should use tx when provided", async () => {
267+
const updated = { ...mockApiKey, last_used: new Date() };
268+
const txApiKey = { update: jest.fn().mockResolvedValue(updated) };
269+
const tx = { apiKey: txApiKey } as unknown as Parameters<
270+
typeof service.updateApiKeyLastUsed
271+
>[1];
272+
273+
const result = await service.updateApiKeyLastUsed("key-1", tx);
274+
275+
expect(result).toEqual(updated);
276+
expect(txApiKey.update).toHaveBeenCalled();
277+
expect(mockApiKeyPrisma.update).not.toHaveBeenCalled();
278+
});
279+
});
280+
});

0 commit comments

Comments
 (0)