Skip to content

Commit cfb41cf

Browse files
committed
test: cover lib/platform flows
1 parent 5cfc59a commit cfb41cf

19 files changed

Lines changed: 2264 additions & 5 deletions

.github/workflows/pr-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ on:
1414
types: [opened, synchronize, reopened]
1515

1616
env:
17-
NODE_VERSION: "22.9.0"
17+
NODE_VERSION: "22.12.0"
1818
PNPM_VERSION: "10.20.0"
1919

2020
jobs:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ For technical details, see [Architecture Documentation](./docs/architecture.md).
6969

7070
### Prerequisites
7171

72-
- Node.js 22.9.0 or higher
72+
- Node.js 22.12.0 or higher
7373
- PostgreSQL database
7474
- Kubernetes cluster with KubeBlocks installed
7575
- GitHub OAuth application credentials

docs/development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This document provides guidance for local development and code patterns.
1414

1515
## Prerequisites
1616

17-
- **Node.js**: 22.9.0 or higher
17+
- **Node.js**: 22.12.0 or higher
1818
- **pnpm**: 9.x or higher
1919
- **PostgreSQL**: 14.x or higher
2020
- **Kubernetes cluster**: For full integration testing (optional)
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
const {
4+
prisma,
5+
getK8sServiceForUser,
6+
toK8sProjectName,
7+
generateK8sRandomString,
8+
} = vi.hoisted(() => ({
9+
prisma: {
10+
project: {
11+
findUnique: vi.fn(),
12+
},
13+
database: {
14+
create: vi.fn(),
15+
},
16+
},
17+
getK8sServiceForUser: vi.fn(),
18+
toK8sProjectName: vi.fn(),
19+
generateK8sRandomString: vi.fn(),
20+
}))
21+
22+
vi.mock('@/lib/db', () => ({
23+
prisma,
24+
}))
25+
26+
vi.mock('@/lib/k8s/k8s-service-helper', () => ({
27+
getK8sServiceForUser,
28+
}))
29+
30+
vi.mock('@/lib/k8s/kubernetes-utils', () => ({
31+
KubernetesUtils: {
32+
toK8sProjectName,
33+
generateRandomString: generateK8sRandomString,
34+
},
35+
}))
36+
37+
vi.mock('@/lib/k8s/versions', () => ({
38+
VERSIONS: {
39+
STORAGE: {
40+
DATABASE_SIZE: '3Gi',
41+
},
42+
RESOURCES: {
43+
DATABASE: {
44+
requests: {
45+
cpu: '100m',
46+
memory: '102Mi',
47+
},
48+
limits: {
49+
cpu: '1000m',
50+
memory: '1024Mi',
51+
},
52+
},
53+
},
54+
},
55+
}))
56+
57+
import { createDatabaseCommand } from '@/lib/platform/control/commands/database/create-database'
58+
59+
describe('createDatabaseCommand', () => {
60+
beforeEach(() => {
61+
vi.clearAllMocks()
62+
})
63+
64+
it('returns an error when the project does not exist', async () => {
65+
prisma.project.findUnique.mockResolvedValue(null)
66+
67+
const result = await createDatabaseCommand({
68+
userId: 'user-1',
69+
projectId: 'project-1',
70+
})
71+
72+
expect(result).toEqual({
73+
success: false,
74+
error: 'Project not found',
75+
})
76+
expect(prisma.database.create).not.toHaveBeenCalled()
77+
})
78+
79+
it('returns an error when the project belongs to another user', async () => {
80+
prisma.project.findUnique.mockResolvedValue({
81+
id: 'project-1',
82+
userId: 'other-user',
83+
databases: [],
84+
})
85+
86+
const result = await createDatabaseCommand({
87+
userId: 'user-1',
88+
projectId: 'project-1',
89+
})
90+
91+
expect(result).toEqual({
92+
success: false,
93+
error: 'Unauthorized',
94+
})
95+
})
96+
97+
it('returns an error when a database already exists', async () => {
98+
prisma.project.findUnique.mockResolvedValue({
99+
id: 'project-1',
100+
userId: 'user-1',
101+
databases: [{ id: 'database-1' }],
102+
})
103+
104+
const result = await createDatabaseCommand({
105+
userId: 'user-1',
106+
projectId: 'project-1',
107+
})
108+
109+
expect(result).toEqual({
110+
success: false,
111+
error: 'Database already exists for this project',
112+
})
113+
})
114+
115+
it('returns a friendly error when kubeconfig is missing', async () => {
116+
prisma.project.findUnique.mockResolvedValue({
117+
id: 'project-1',
118+
userId: 'user-1',
119+
name: 'Project Alpha',
120+
databases: [],
121+
})
122+
getK8sServiceForUser.mockRejectedValue(
123+
new Error('User [user-1] does not have KUBECONFIG configured')
124+
)
125+
126+
const result = await createDatabaseCommand({
127+
userId: 'user-1',
128+
projectId: 'project-1',
129+
})
130+
131+
expect(result).toEqual({
132+
success: false,
133+
error: 'Please configure your kubeconfig before creating a database',
134+
})
135+
expect(prisma.database.create).not.toHaveBeenCalled()
136+
})
137+
138+
it('creates the database with the generated default name and expected resource config', async () => {
139+
prisma.project.findUnique.mockResolvedValue({
140+
id: 'project-1',
141+
userId: 'user-1',
142+
name: 'Project Alpha',
143+
databases: [],
144+
})
145+
prisma.database.create.mockResolvedValue({
146+
id: 'database-1',
147+
name: 'projectalpha-db-suffix',
148+
})
149+
getK8sServiceForUser.mockResolvedValue({
150+
getDefaultNamespace: vi.fn().mockReturnValue('ns-user-1'),
151+
})
152+
toK8sProjectName.mockReturnValue('projectalpha')
153+
generateK8sRandomString.mockReturnValue('suffix')
154+
155+
const result = await createDatabaseCommand({
156+
userId: 'user-1',
157+
projectId: 'project-1',
158+
})
159+
160+
expect(result).toEqual({
161+
success: true,
162+
data: {
163+
id: 'database-1',
164+
name: 'projectalpha-db-suffix',
165+
},
166+
})
167+
expect(prisma.database.create).toHaveBeenCalledWith({
168+
data: {
169+
projectId: 'project-1',
170+
name: 'projectalpha-db-suffix',
171+
k8sNamespace: 'ns-user-1',
172+
databaseName: 'projectalpha-db-suffix',
173+
status: 'CREATING',
174+
lockedUntil: null,
175+
storageSize: '3Gi',
176+
cpuRequest: '100m',
177+
cpuLimit: '1000m',
178+
memoryRequest: '102Mi',
179+
memoryLimit: '1024Mi',
180+
},
181+
})
182+
})
183+
})
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
const { prisma } = vi.hoisted(() => ({
4+
prisma: {
5+
database: {
6+
findUnique: vi.fn(),
7+
update: vi.fn(),
8+
},
9+
},
10+
}))
11+
12+
vi.mock('@/lib/db', () => ({
13+
prisma,
14+
}))
15+
16+
import { deleteDatabaseCommand } from '@/lib/platform/control/commands/database/delete-database'
17+
18+
describe('deleteDatabaseCommand', () => {
19+
beforeEach(() => {
20+
vi.clearAllMocks()
21+
})
22+
23+
it('returns an error when the database does not exist', async () => {
24+
prisma.database.findUnique.mockResolvedValue(null)
25+
26+
const result = await deleteDatabaseCommand({
27+
userId: 'user-1',
28+
databaseId: 'database-1',
29+
})
30+
31+
expect(result).toEqual({
32+
success: false,
33+
error: 'Database not found',
34+
})
35+
expect(prisma.database.update).not.toHaveBeenCalled()
36+
})
37+
38+
it('returns an error when the database belongs to another user', async () => {
39+
prisma.database.findUnique.mockResolvedValue({
40+
id: 'database-1',
41+
project: {
42+
userId: 'other-user',
43+
},
44+
})
45+
46+
const result = await deleteDatabaseCommand({
47+
userId: 'user-1',
48+
databaseId: 'database-1',
49+
})
50+
51+
expect(result).toEqual({
52+
success: false,
53+
error: 'Unauthorized',
54+
})
55+
expect(prisma.database.update).not.toHaveBeenCalled()
56+
})
57+
58+
it('marks the database as terminating and clears the lock', async () => {
59+
prisma.database.findUnique.mockResolvedValue({
60+
id: 'database-1',
61+
project: {
62+
userId: 'user-1',
63+
},
64+
})
65+
prisma.database.update.mockResolvedValue({
66+
id: 'database-1',
67+
})
68+
69+
const result = await deleteDatabaseCommand({
70+
userId: 'user-1',
71+
databaseId: 'database-1',
72+
})
73+
74+
expect(result).toEqual({
75+
success: true,
76+
data: undefined,
77+
})
78+
expect(prisma.database.update).toHaveBeenCalledWith({
79+
where: { id: 'database-1' },
80+
data: {
81+
status: 'TERMINATING',
82+
lockedUntil: null,
83+
},
84+
})
85+
})
86+
})

0 commit comments

Comments
 (0)