Skip to content

Commit 1833ea2

Browse files
test: add comprehensive tests for system project provisioning
- Add unit tests for provisionProject() with isSystem field - Add unit tests for provisionSystemProject() method - Test idempotent behavior of system project creation - Test system project metadata and well-known UUIDs - Fix missing isSystem field in provisionProject() - All 7 tests passing Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent af565a6 commit 1833ea2

2 files changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { describe, it, expect, vi } from 'vitest';
4+
import {
5+
ProjectProvisioningService,
6+
MockProjectDatabaseAdapter,
7+
NoopSecretEncryptor,
8+
type ProjectDatabaseAdapter,
9+
} from './project-provisioning.js';
10+
11+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
12+
const SYSTEM_PROJECT_ID = '00000000-0000-0000-0000-000000000001';
13+
const PLATFORM_ORG_ID = '00000000-0000-0000-0000-000000000000';
14+
15+
describe('ProjectProvisioningService.provisionProject', () => {
16+
it('returns a fully-formed project + credential in detached mode', async () => {
17+
const svc = new ProjectProvisioningService({
18+
defaultRegion: 'eu-west-1',
19+
defaultStorageLimitMb: 2048,
20+
});
21+
22+
const result = await svc.provisionProject({
23+
organizationId: 'org-123',
24+
slug: 'dev',
25+
projectType: 'development',
26+
createdBy: 'user-1',
27+
});
28+
29+
expect(result.project.id).toMatch(UUID_RE);
30+
expect(result.project.organizationId).toBe('org-123');
31+
expect(result.project.slug).toBe('dev');
32+
expect(result.project.projectType).toBe('development');
33+
expect(result.project.region).toBe('eu-west-1');
34+
expect(result.project.status).toBe('active');
35+
expect(result.project.isDefault).toBe(false);
36+
expect(result.project.isSystem).toBe(false);
37+
38+
// Database addressing is on the project row
39+
expect(result.project.storageLimitMb).toBe(2048);
40+
expect(result.project.databaseDriver).toBe('turso');
41+
expect(result.project.databaseUrl).toContain('libsql://');
42+
43+
expect(result.credential.projectId).toBe(result.project.id);
44+
expect(result.credential.status).toBe('active');
45+
expect(result.credential.authorization).toBe('full_access');
46+
expect(result.credential.encryptionKeyId).toBe('noop');
47+
48+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
49+
// Detached mode must warn that control plane was not written.
50+
expect(result.warnings?.some((w) => w.includes('Control-plane driver'))).toBe(true);
51+
});
52+
53+
it('persists control-plane rows when a driver is configured', async () => {
54+
const created: Array<{ object: string; data: Record<string, unknown> }> = [];
55+
const driver = {
56+
create: vi.fn(async (object: string, data: Record<string, unknown>) => {
57+
created.push({ object, data });
58+
return data;
59+
}),
60+
find: vi.fn(async () => []),
61+
findOne: vi.fn(async () => null),
62+
update: vi.fn(async () => ({})),
63+
};
64+
65+
const svc = new ProjectProvisioningService({
66+
controlPlaneDriver: driver as any,
67+
adapters: [new MockProjectDatabaseAdapter('turso')],
68+
});
69+
70+
const result = await svc.provisionProject({
71+
organizationId: 'org-42',
72+
slug: 'prod',
73+
projectType: 'production',
74+
isDefault: true,
75+
createdBy: 'user-1',
76+
});
77+
78+
const objects = created.map((c) => c.object);
79+
expect(objects).toEqual(['project', 'project_credential']);
80+
81+
const projectRow = created.find((c) => c.object === 'project')!.data;
82+
expect(projectRow.organization_id).toBe('org-42');
83+
expect(projectRow.is_default).toBe(true);
84+
expect(projectRow.is_system).toBe(false);
85+
expect(projectRow.slug).toBe('prod');
86+
expect(projectRow.database_url).toBeTruthy();
87+
expect(projectRow.database_driver).toBe('turso');
88+
89+
expect(result.warnings).toBeUndefined();
90+
});
91+
92+
it('rejects a second default project for the same org', async () => {
93+
const driver = {
94+
find: vi.fn(async () => [{ id: 'existing-project-id' }]),
95+
findOne: vi.fn(async () => null),
96+
create: vi.fn(async () => ({})),
97+
update: vi.fn(async () => ({})),
98+
};
99+
100+
const svc = new ProjectProvisioningService({
101+
controlPlaneDriver: driver as any,
102+
});
103+
104+
await expect(
105+
svc.provisionProject({
106+
organizationId: 'org-42',
107+
slug: 'prod-2',
108+
projectType: 'production',
109+
isDefault: true,
110+
createdBy: 'user-1',
111+
}),
112+
).rejects.toThrow(/already has a default project/);
113+
114+
expect(driver.create).not.toHaveBeenCalled();
115+
});
116+
});
117+
118+
describe('ProjectProvisioningService.provisionSystemProject', () => {
119+
it('creates system project with well-known UUID in detached mode', async () => {
120+
const svc = new ProjectProvisioningService({
121+
defaultRegion: 'us-east-1',
122+
});
123+
124+
const result = await svc.provisionSystemProject();
125+
126+
expect(result.project.id).toBe(SYSTEM_PROJECT_ID);
127+
expect(result.project.organizationId).toBe(PLATFORM_ORG_ID);
128+
expect(result.project.slug).toBe('system');
129+
expect(result.project.displayName).toBe('System');
130+
expect(result.project.projectType).toBe('production');
131+
expect(result.project.isDefault).toBe(false);
132+
expect(result.project.isSystem).toBe(true);
133+
expect(result.project.plan).toBe('enterprise');
134+
expect(result.project.status).toBe('active');
135+
expect(result.project.createdBy).toBe('system');
136+
expect(result.project.hostname).toBe('system.objectstack.internal');
137+
138+
// System project uses control plane DB - no separate physical DB
139+
expect(result.project.databaseUrl).toBeUndefined();
140+
expect(result.project.databaseDriver).toBeUndefined();
141+
expect(result.project.storageLimitMb).toBeUndefined();
142+
143+
expect(result.credential.projectId).toBe(SYSTEM_PROJECT_ID);
144+
expect(result.credential.secretCiphertext).toBe('');
145+
146+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
147+
});
148+
149+
it('persists system project to control plane when driver is configured', async () => {
150+
const created: Array<{ object: string; data: Record<string, unknown> }> = [];
151+
const driver = {
152+
create: vi.fn(async (object: string, data: Record<string, unknown>) => {
153+
created.push({ object, data });
154+
return data;
155+
}),
156+
find: vi.fn(async () => []),
157+
findOne: vi.fn(async () => null),
158+
update: vi.fn(async () => ({})),
159+
};
160+
161+
const svc = new ProjectProvisioningService({
162+
controlPlaneDriver: driver as any,
163+
});
164+
165+
const result = await svc.provisionSystemProject();
166+
167+
expect(created).toHaveLength(1);
168+
const projectRow = created[0].data;
169+
170+
expect(created[0].object).toBe('project');
171+
expect(projectRow.id).toBe(SYSTEM_PROJECT_ID);
172+
expect(projectRow.organization_id).toBe(PLATFORM_ORG_ID);
173+
expect(projectRow.slug).toBe('system');
174+
expect(projectRow.is_system).toBe(true);
175+
expect(projectRow.is_default).toBe(false);
176+
expect(projectRow.plan).toBe('enterprise');
177+
expect(projectRow.database_url).toBeUndefined();
178+
179+
expect(result.warnings?.some((w) => w.includes('created successfully'))).toBe(true);
180+
});
181+
182+
it('returns existing system project if already created (idempotent)', async () => {
183+
const findOneCalled: string[] = [];
184+
const driver = {
185+
create: vi.fn(async () => ({})),
186+
find: vi.fn(async () => []),
187+
findOne: vi.fn(async (object: string, query: any) => {
188+
findOneCalled.push(object);
189+
if (object === 'project' && query.where?.id === SYSTEM_PROJECT_ID) {
190+
return {
191+
id: SYSTEM_PROJECT_ID,
192+
organization_id: PLATFORM_ORG_ID,
193+
slug: 'system',
194+
display_name: 'System',
195+
project_type: 'production',
196+
is_default: false,
197+
is_system: true,
198+
region: 'us-east-1',
199+
plan: 'enterprise',
200+
status: 'active',
201+
created_by: 'system',
202+
created_at: new Date().toISOString(),
203+
updated_at: new Date().toISOString(),
204+
provisioned_at: new Date().toISOString(),
205+
hostname: 'system.objectstack.internal',
206+
};
207+
}
208+
return null;
209+
}),
210+
update: vi.fn(async () => ({})),
211+
};
212+
213+
const svc = new ProjectProvisioningService({
214+
controlPlaneDriver: driver as any,
215+
});
216+
217+
const result = await svc.provisionSystemProject();
218+
219+
// Should have queried for existing system project
220+
expect(findOneCalled).toContain('project');
221+
222+
// Should NOT have called create since project exists
223+
expect(driver.create).not.toHaveBeenCalled();
224+
225+
// Should return the existing project
226+
expect(result.project.id).toBe(SYSTEM_PROJECT_ID);
227+
expect(result.project.isSystem).toBe(true);
228+
expect(result.warnings).toContain('System project already exists');
229+
});
230+
231+
it('metadata field contains expected system project metadata', async () => {
232+
const svc = new ProjectProvisioningService();
233+
234+
const result = await svc.provisionSystemProject();
235+
236+
expect(result.project.metadata).toBeDefined();
237+
expect(result.project.metadata?.description).toBe('Built-in system project for platform infrastructure');
238+
expect(result.project.metadata?.protected).toBe(true);
239+
});
240+
});

packages/services/service-tenant/src/project-provisioning.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ export class ProjectProvisioningService {
352352
displayName: parsed.displayName ?? parsed.slug,
353353
projectType: parsed.projectType,
354354
isDefault: parsed.isDefault ?? false,
355+
isSystem: false, // Regular projects are not system projects
355356
region,
356357
plan: parsed.plan ?? 'free',
357358
status: 'active',
@@ -386,6 +387,7 @@ export class ProjectProvisioningService {
386387
display_name: project.displayName,
387388
project_type: project.projectType,
388389
is_default: project.isDefault,
390+
is_system: project.isSystem,
389391
region: project.region,
390392
plan: project.plan,
391393
status: project.status,

0 commit comments

Comments
 (0)