@selvajs/platform 0.14.2 → 0.15.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/auth.d.ts +60 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +12 -0
- package/dist/auth.js.map +1 -0
- package/dist/compute.d.ts +47 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +11 -0
- package/dist/compute.js.map +1 -0
- package/dist/computeServer/index.d.ts +2 -0
- package/dist/computeServer/index.d.ts.map +1 -1
- package/dist/computeServer/index.js +6 -0
- package/dist/computeServer/index.js.map +1 -1
- package/dist/computeServer/interface.d.ts +10 -0
- package/dist/computeServer/interface.d.ts.map +1 -1
- package/dist/computeServer/secrets.d.ts +53 -0
- package/dist/computeServer/secrets.d.ts.map +1 -0
- package/dist/computeServer/secrets.js +67 -0
- package/dist/computeServer/secrets.js.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +13 -0
- package/dist/context.js.map +1 -1
- package/dist/data/interface.d.ts +33 -0
- package/dist/data/interface.d.ts.map +1 -1
- package/dist/definitions/interface.d.ts +18 -0
- package/dist/definitions/interface.d.ts.map +1 -1
- package/dist/definitions/schemas.d.ts +1 -0
- package/dist/definitions/schemas.d.ts.map +1 -1
- package/dist/definitions/schemas.js +3 -0
- package/dist/definitions/schemas.js.map +1 -1
- package/dist/definitions/service.d.ts +62 -0
- package/dist/definitions/service.d.ts.map +1 -0
- package/dist/definitions/service.js +149 -0
- package/dist/definitions/service.js.map +1 -0
- package/dist/definitions/types.d.ts +24 -1
- package/dist/definitions/types.d.ts.map +1 -1
- package/dist/definitions.d.ts +146 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +23 -0
- package/dist/definitions.js.map +1 -0
- package/dist/errors/interface.d.ts +44 -0
- package/dist/errors/interface.d.ts.map +1 -0
- package/dist/errors/interface.js +9 -0
- package/dist/errors/interface.js.map +1 -0
- package/dist/events/interface.d.ts +23 -0
- package/dist/events/interface.d.ts.map +1 -1
- package/dist/events/interface.js.map +1 -1
- package/dist/index.d.ts +14 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/invites/schemas.d.ts +24 -0
- package/dist/invites/schemas.d.ts.map +1 -0
- package/dist/invites/schemas.js +17 -0
- package/dist/invites/schemas.js.map +1 -0
- package/dist/metrics/interface.d.ts +6 -0
- package/dist/metrics/interface.d.ts.map +1 -1
- package/dist/metrics/interface.js.map +1 -1
- package/dist/organizations/index.d.ts +4 -3
- package/dist/organizations/index.d.ts.map +1 -1
- package/dist/organizations/index.js +2 -1
- package/dist/organizations/index.js.map +1 -1
- package/dist/organizations/interface.d.ts +1 -1
- package/dist/organizations/interface.d.ts.map +1 -1
- package/dist/organizations/paths.d.ts +22 -0
- package/dist/organizations/paths.d.ts.map +1 -0
- package/dist/organizations/paths.js +43 -0
- package/dist/organizations/paths.js.map +1 -0
- package/dist/organizations/schemas.d.ts +10 -0
- package/dist/organizations/schemas.d.ts.map +1 -1
- package/dist/organizations/schemas.js +6 -0
- package/dist/organizations/schemas.js.map +1 -1
- package/dist/organizations/types.d.ts +10 -1
- package/dist/organizations/types.d.ts.map +1 -1
- package/dist/organizations.d.ts +93 -0
- package/dist/organizations.d.ts.map +1 -0
- package/dist/organizations.js +14 -0
- package/dist/organizations.js.map +1 -0
- package/dist/solveCache/interface.d.ts +86 -0
- package/dist/solveCache/interface.d.ts.map +1 -0
- package/dist/solveCache/interface.js +12 -0
- package/dist/solveCache/interface.js.map +1 -0
- package/dist/storage/assetClasses.d.ts +59 -0
- package/dist/storage/assetClasses.d.ts.map +1 -0
- package/dist/storage/assetClasses.js +86 -0
- package/dist/storage/assetClasses.js.map +1 -0
- package/dist/storage/cacheBust.d.ts +21 -0
- package/dist/storage/cacheBust.d.ts.map +1 -0
- package/dist/storage/cacheBust.js +29 -0
- package/dist/storage/cacheBust.js.map +1 -0
- package/dist/storage/image.d.ts +6 -1
- package/dist/storage/image.d.ts.map +1 -1
- package/dist/storage/image.js +8 -3
- package/dist/storage/image.js.map +1 -1
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/testing/adapterSuite.d.ts +33 -0
- package/dist/testing/adapterSuite.d.ts.map +1 -0
- package/dist/testing/adapterSuite.js +166 -0
- package/dist/testing/adapterSuite.js.map +1 -0
- package/dist/testing/suites/definitionStoreSuite.d.ts.map +1 -1
- package/dist/testing/suites/definitionStoreSuite.js +87 -0
- package/dist/testing/suites/definitionStoreSuite.js.map +1 -1
- package/dist/testing/suites/eventSinkSuite.js +1 -0
- package/dist/testing/suites/eventSinkSuite.js.map +1 -1
- package/dist/testing/suites/orgStoreSuite.d.ts.map +1 -1
- package/dist/testing/suites/orgStoreSuite.js +12 -0
- package/dist/testing/suites/orgStoreSuite.js.map +1 -1
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts +9 -0
- package/dist/testing/suites/platformPermissionStoreSuite.d.ts.map +1 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js +34 -1
- package/dist/testing/suites/platformPermissionStoreSuite.js.map +1 -1
- package/dist/testing/suites/runner.d.ts +21 -0
- package/dist/testing/suites/runner.d.ts.map +1 -0
- package/dist/testing/suites/runner.js +7 -0
- package/dist/testing/suites/runner.js.map +1 -0
- package/dist/testing/suites/storageProviderSuite.d.ts.map +1 -1
- package/dist/testing/suites/storageProviderSuite.js +18 -0
- package/dist/testing/suites/storageProviderSuite.js.map +1 -1
- package/package.json +9 -24
- package/src/access/index.ts +0 -23
- package/src/access/rules.ts +0 -276
- package/src/auth/index.ts +0 -8
- package/src/auth/interface.ts +0 -279
- package/src/auth/types.ts +0 -32
- package/src/bindings/index.ts +0 -2
- package/src/bindings/interface.ts +0 -61
- package/src/computeServer/index.ts +0 -17
- package/src/computeServer/interface.ts +0 -62
- package/src/computeServer/types.ts +0 -74
- package/src/computeServer/utils.ts +0 -112
- package/src/config.ts +0 -99
- package/src/context.ts +0 -66
- package/src/data/index.ts +0 -1
- package/src/data/interface.ts +0 -70
- package/src/definitions/index.ts +0 -26
- package/src/definitions/interface.ts +0 -78
- package/src/definitions/paths.ts +0 -31
- package/src/definitions/schemas.ts +0 -64
- package/src/definitions/types.ts +0 -111
- package/src/errors.ts +0 -16
- package/src/events/audit.ts +0 -67
- package/src/events/index.ts +0 -9
- package/src/events/interface.ts +0 -102
- package/src/index.ts +0 -219
- package/src/invites/index.ts +0 -2
- package/src/invites/interface.ts +0 -38
- package/src/invites/types.ts +0 -39
- package/src/metrics/interface.ts +0 -86
- package/src/organizations/index.ts +0 -14
- package/src/organizations/interface.ts +0 -68
- package/src/organizations/schemas.ts +0 -52
- package/src/organizations/types.ts +0 -31
- package/src/pagination.ts +0 -45
- package/src/permissions/index.ts +0 -3
- package/src/permissions/interface.ts +0 -60
- package/src/permissions/types.ts +0 -21
- package/src/platformProjects/index.ts +0 -3
- package/src/platformProjects/interface.ts +0 -27
- package/src/platformProjects/schemas.ts +0 -13
- package/src/platformProjects/types.ts +0 -22
- package/src/projects/index.ts +0 -4
- package/src/projects/interface.ts +0 -58
- package/src/projects/schemas.ts +0 -28
- package/src/projects/types.ts +0 -36
- package/src/shareLinks/index.ts +0 -5
- package/src/shareLinks/interface.ts +0 -35
- package/src/shareLinks/schemas.ts +0 -21
- package/src/shareLinks/types.ts +0 -34
- package/src/storage/image.ts +0 -77
- package/src/storage/index.ts +0 -9
- package/src/storage/interface.ts +0 -27
- package/src/testing/index.ts +0 -47
- package/src/testing/suites/authProviderSuite.ts +0 -207
- package/src/testing/suites/computeServerStoreSuite.ts +0 -195
- package/src/testing/suites/definitionStoreSuite.ts +0 -585
- package/src/testing/suites/emailLinkAuthSuite.ts +0 -83
- package/src/testing/suites/eventSinkSuite.ts +0 -379
- package/src/testing/suites/helpers.ts +0 -87
- package/src/testing/suites/inviteStoreSuite.ts +0 -143
- package/src/testing/suites/orgStoreSuite.ts +0 -412
- package/src/testing/suites/platformPermissionStoreSuite.ts +0 -196
- package/src/testing/suites/platformProjectGrantStoreSuite.ts +0 -166
- package/src/testing/suites/projectStoreSuite.ts +0 -381
- package/src/testing/suites/shareLinkStoreSuite.ts +0 -203
- package/src/testing/suites/solveMetricSinkSuite.ts +0 -135
- package/src/testing/suites/storageProviderSuite.ts +0 -175
- package/src/testing/suites/userProfileStoreSuite.ts +0 -134
- package/src/userProfile/index.ts +0 -3
- package/src/userProfile/interface.ts +0 -48
- package/src/userProfile/types.ts +0 -24
- package/src/utils/audit.ts +0 -46
- package/src/utils/index.ts +0 -2
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
// Conformance suite for IAuthProvider: token round-trips, identity, password auth, admin management.
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect } from 'vitest';
|
|
4
|
-
import type { IAuthProvider } from '../../auth/index.js';
|
|
5
|
-
import { makeUuid } from './helpers.js';
|
|
6
|
-
|
|
7
|
-
export interface AuthProviderConformanceOptions {
|
|
8
|
-
/** Name to show in test output (e.g. "LocalAuthProvider"). */
|
|
9
|
-
name: string;
|
|
10
|
-
/**
|
|
11
|
-
* Factory returning a fresh provider configured with a known password.
|
|
12
|
-
* `adminPassword` must work with `passwordAuth.verifyLogin(adminEmail, adminPassword)`
|
|
13
|
-
* for password-capable providers. The factory is responsible for seeding
|
|
14
|
-
* the admin user (e.g. via `createUserWithPassword`) before returning.
|
|
15
|
-
*
|
|
16
|
-
* `adminEmail` defaults to `''` for legacy reasons; new providers should
|
|
17
|
-
* always return the seeded admin's email.
|
|
18
|
-
*/
|
|
19
|
-
createProvider: () =>
|
|
20
|
-
| Promise<{ provider: IAuthProvider; adminPassword: string; adminEmail?: string }>
|
|
21
|
-
| { provider: IAuthProvider; adminPassword: string; adminEmail?: string };
|
|
22
|
-
/**
|
|
23
|
-
* Set to true when the provider is wired with a backend so that
|
|
24
|
-
* createUser / listUsers / updateUserPlatformPermissions / deleteUser tests run.
|
|
25
|
-
* When false those methods are expected to return null / 'not_supported'.
|
|
26
|
-
*/
|
|
27
|
-
userManagement?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function runAuthProviderConformance(opts: AuthProviderConformanceOptions): void {
|
|
31
|
-
const { name, createProvider, userManagement = false } = opts;
|
|
32
|
-
|
|
33
|
-
describe(`IAuthProvider conformance: ${name}`, () => {
|
|
34
|
-
// Token round-trips
|
|
35
|
-
it('verifyLogin returns a session token that verifyToken accepts', async () => {
|
|
36
|
-
const { provider, adminPassword, adminEmail = '' } = await createProvider();
|
|
37
|
-
const result = await provider.passwordAuth!.verifyLogin(adminEmail, adminPassword);
|
|
38
|
-
expect(result.kind).toBe('success');
|
|
39
|
-
if (result.kind !== 'success') return;
|
|
40
|
-
const verified = await provider.verifyToken(result.sessionToken);
|
|
41
|
-
expect(verified?.id).toBe(result.user.id);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('verifyToken returns null for a garbage token', async () => {
|
|
45
|
-
const { provider } = await createProvider();
|
|
46
|
-
const result = await provider.verifyToken('not-a-valid-token');
|
|
47
|
-
expect(result).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('verifyToken returns null for an empty string', async () => {
|
|
51
|
-
const { provider } = await createProvider();
|
|
52
|
-
const result = await provider.verifyToken('');
|
|
53
|
-
expect(result).toBeNull();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Password auth (only runs when passwordAuth is present)
|
|
57
|
-
it('verifyLogin returns success with correct password', async () => {
|
|
58
|
-
const { provider, adminPassword, adminEmail = '' } = await createProvider();
|
|
59
|
-
if (!provider.passwordAuth) return;
|
|
60
|
-
const result = await provider.passwordAuth.verifyLogin(adminEmail, adminPassword);
|
|
61
|
-
expect(result.kind).toBe('success');
|
|
62
|
-
if (result.kind !== 'success') return;
|
|
63
|
-
// Identity-only — platform permissions live on IPlatformPermissionStore now.
|
|
64
|
-
expect(result.user.id).toBeTruthy();
|
|
65
|
-
expect(typeof result.sessionToken).toBe('string');
|
|
66
|
-
expect(result.sessionToken.length).toBeGreaterThan(0);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('verifyLogin returns failed for wrong password', async () => {
|
|
70
|
-
const { provider, adminEmail = '' } = await createProvider();
|
|
71
|
-
if (!provider.passwordAuth) return;
|
|
72
|
-
const result = await provider.passwordAuth.verifyLogin(adminEmail, 'wrong-password-xyz');
|
|
73
|
-
expect(result.kind).toBe('failed');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// getUser
|
|
77
|
-
it('getUser returns null for unknown id', async () => {
|
|
78
|
-
const { provider } = await createProvider();
|
|
79
|
-
// Valid UUID shape but not an actual user — stricter backends (Postgres
|
|
80
|
-
// uuid columns) reject non-UUID ids outright, so we never send those.
|
|
81
|
-
const result = await provider.getUser(makeUuid());
|
|
82
|
-
expect(result).toBeNull();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// ============================================================================
|
|
86
|
-
// User management (opt-in)
|
|
87
|
-
// ============================================================================
|
|
88
|
-
|
|
89
|
-
if (userManagement) {
|
|
90
|
-
it('passwordAuth.createUserWithPassword + getUser returns the created user', async () => {
|
|
91
|
-
const { provider } = await createProvider();
|
|
92
|
-
const email = `test-${makeUuid()}@example.com`;
|
|
93
|
-
const created = await provider.passwordAuth!.createUserWithPassword(email, 'password1234');
|
|
94
|
-
expect(created).toBeTruthy();
|
|
95
|
-
const fetched = await provider.getUser(created.id);
|
|
96
|
-
expect(fetched?.id).toBe(created.id);
|
|
97
|
-
expect(fetched?.email).toBe(email);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('createUserWithPassword allows login with the new credentials', async () => {
|
|
101
|
-
const { provider } = await createProvider();
|
|
102
|
-
const email = `login-${makeUuid()}@example.com`;
|
|
103
|
-
await provider.passwordAuth!.createUserWithPassword(email, 'secret9999');
|
|
104
|
-
const result = await provider.passwordAuth!.verifyLogin(email, 'secret9999');
|
|
105
|
-
expect(result.kind).toBe('success');
|
|
106
|
-
if (result.kind !== 'success') return;
|
|
107
|
-
expect(result.user.email).toBe(email);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('listUsers returns created users', async () => {
|
|
111
|
-
const { provider } = await createProvider();
|
|
112
|
-
const a = `a-${makeUuid()}@example.com`;
|
|
113
|
-
const b = `b-${makeUuid()}@example.com`;
|
|
114
|
-
await provider.passwordAuth!.createUserWithPassword(a, 'pass12345678');
|
|
115
|
-
await provider.passwordAuth!.createUserWithPassword(b, 'pass12345678');
|
|
116
|
-
const page = await provider.listUsers({ limit: 200 });
|
|
117
|
-
expect(page).toBeTruthy();
|
|
118
|
-
const emails = page!.items.map((u) => u.email);
|
|
119
|
-
expect(emails).toContain(a);
|
|
120
|
-
expect(emails).toContain(b);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('deleteUser removes the user', async () => {
|
|
124
|
-
const { provider } = await createProvider();
|
|
125
|
-
const email = `del-${makeUuid()}@example.com`;
|
|
126
|
-
const created = await provider.passwordAuth!.createUserWithPassword(email, 'pass12345678');
|
|
127
|
-
const result = await provider.deleteUser(created.id);
|
|
128
|
-
expect(result).toBe('ok');
|
|
129
|
-
const fetched = await provider.getUser(created.id);
|
|
130
|
-
expect(fetched).toBeNull();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('deleteUser returns not_found for unknown user', async () => {
|
|
134
|
-
const { provider } = await createProvider();
|
|
135
|
-
const result = await provider.deleteUser(makeUuid());
|
|
136
|
-
expect(result).toBe('not_found');
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Note: the §2 sole-`instance_admin` invariant is no longer enforced
|
|
140
|
-
// by IAuthProvider — it lives on IPlatformPermissionStore now and
|
|
141
|
-
// callers consult `countInstanceAdminsExcluding` before destructive
|
|
142
|
-
// ops. Auth-side delete/disable just deletes the identity and
|
|
143
|
-
// trusts the caller. See `runPlatformPermissionStoreConformance`
|
|
144
|
-
// for the invariant tests.
|
|
145
|
-
|
|
146
|
-
it('createUserWithPassword returns an identity record without profile fields', async () => {
|
|
147
|
-
const { provider } = await createProvider();
|
|
148
|
-
const user = await provider.passwordAuth!.createUserWithPassword(
|
|
149
|
-
`new-${makeUuid()}@example.com`,
|
|
150
|
-
'pass12345678'
|
|
151
|
-
);
|
|
152
|
-
// Profile state (starred, recentRuns, displayName) lives on
|
|
153
|
-
// IUserProfileStore. Platform permissions live on
|
|
154
|
-
// IPlatformPermissionStore. AuthUser is identity-only.
|
|
155
|
-
expect(
|
|
156
|
-
(user as unknown as { starredDefinitions?: unknown }).starredDefinitions
|
|
157
|
-
).toBeUndefined();
|
|
158
|
-
expect((user as unknown as { recentRuns?: unknown }).recentRuns).toBeUndefined();
|
|
159
|
-
expect((user as unknown as { displayName?: unknown }).displayName).toBeUndefined();
|
|
160
|
-
expect(
|
|
161
|
-
(user as unknown as { platformPermissions?: unknown }).platformPermissions
|
|
162
|
-
).toBeUndefined();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('disableUser disables a user', async () => {
|
|
166
|
-
const { provider } = await createProvider();
|
|
167
|
-
const created = await provider.passwordAuth!.createUserWithPassword(
|
|
168
|
-
`disable-${makeUuid()}@example.com`,
|
|
169
|
-
'pass12345678'
|
|
170
|
-
);
|
|
171
|
-
const result = await provider.disableUser(created.id);
|
|
172
|
-
expect(result).toBe('ok');
|
|
173
|
-
const fetched = await provider.getUser(created.id);
|
|
174
|
-
expect(fetched?.disabled).toBe(true);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('disableUser returns not_found for unknown user', async () => {
|
|
178
|
-
const { provider } = await createProvider();
|
|
179
|
-
const result = await provider.disableUser(makeUuid());
|
|
180
|
-
expect(result).toBe('not_found');
|
|
181
|
-
});
|
|
182
|
-
} else {
|
|
183
|
-
it('listUsers returns null when user management is not supported', async () => {
|
|
184
|
-
const { provider } = await createProvider();
|
|
185
|
-
const result = await provider.listUsers();
|
|
186
|
-
expect(result).toBeNull();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('createUser (allowlist) is undefined when user management is not supported', async () => {
|
|
190
|
-
const { provider } = await createProvider();
|
|
191
|
-
expect(provider.createUser).toBeUndefined();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('deleteUser returns not_supported', async () => {
|
|
195
|
-
const { provider } = await createProvider();
|
|
196
|
-
const result = await provider.deleteUser(makeUuid());
|
|
197
|
-
expect(result).toBe('not_supported');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('disableUser returns not_supported', async () => {
|
|
201
|
-
const { provider } = await createProvider();
|
|
202
|
-
const result = await provider.disableUser(makeUuid());
|
|
203
|
-
expect(result).toBe('not_supported');
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapter conformance suite for IComputeServerStore.
|
|
3
|
-
*
|
|
4
|
-
* Architecture spec §4.8 — servers are scoped (platform vs. org-private), with a global
|
|
5
|
-
* `defaultServerId` and per-org `orgDefaults`. The store surface exposes
|
|
6
|
-
* scope-targeted mutations; visibility filtering is done by callers via
|
|
7
|
-
* the pure helpers in `@selvajs/platform`.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect } from 'vitest';
|
|
11
|
-
import type { IComputeServerStore } from '../../computeServer/interface.js';
|
|
12
|
-
import type {
|
|
13
|
-
ComputeServerConfig,
|
|
14
|
-
OrgComputeServer,
|
|
15
|
-
PlatformComputeServer
|
|
16
|
-
} from '../../computeServer/types.js';
|
|
17
|
-
import { isOrgServer, isPlatformServer } from '../../computeServer/types.js';
|
|
18
|
-
import { SYSTEM_CONTEXT } from '../../context.js';
|
|
19
|
-
import { makeUuid } from './helpers.js';
|
|
20
|
-
|
|
21
|
-
export interface ComputeServerStoreConformanceOptions {
|
|
22
|
-
name: string;
|
|
23
|
-
createStore: () => Promise<IComputeServerStore> | IComputeServerStore;
|
|
24
|
-
/**
|
|
25
|
-
* Optional hook adapters with FK constraints use to seed an `orgs` row
|
|
26
|
-
* before the suite writes an org-scoped compute config that FK-references
|
|
27
|
-
* it. Adapters without org FKs (local JSON) can omit this.
|
|
28
|
-
*/
|
|
29
|
-
seedOrg?: (orgId: string) => Promise<void>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function platformServer(
|
|
33
|
-
overrides: Partial<Omit<PlatformComputeServer, 'scope'>> = {}
|
|
34
|
-
): PlatformComputeServer {
|
|
35
|
-
return {
|
|
36
|
-
id: overrides.id ?? makeUuid(),
|
|
37
|
-
scope: 'platform',
|
|
38
|
-
sharedWith: overrides.sharedWith ?? 'all',
|
|
39
|
-
label: overrides.label ?? 'Test Server',
|
|
40
|
-
serverUrl: overrides.serverUrl ?? 'http://localhost:5000',
|
|
41
|
-
apiKey: overrides.apiKey,
|
|
42
|
-
timeoutMs: overrides.timeoutMs,
|
|
43
|
-
retryCount: overrides.retryCount
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function orgServer(
|
|
48
|
-
ownerOrgId: string,
|
|
49
|
-
overrides: Partial<Omit<OrgComputeServer, 'scope' | 'ownerOrgId'>> = {}
|
|
50
|
-
): OrgComputeServer {
|
|
51
|
-
return {
|
|
52
|
-
id: overrides.id ?? makeUuid(),
|
|
53
|
-
scope: 'org',
|
|
54
|
-
ownerOrgId,
|
|
55
|
-
label: overrides.label ?? 'Org Server',
|
|
56
|
-
serverUrl: overrides.serverUrl ?? 'http://localhost:5001',
|
|
57
|
-
apiKey: overrides.apiKey,
|
|
58
|
-
timeoutMs: overrides.timeoutMs,
|
|
59
|
-
retryCount: overrides.retryCount
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function runComputeServerStoreConformance(opts: ComputeServerStoreConformanceOptions): void {
|
|
64
|
-
const { name, createStore, seedOrg } = opts;
|
|
65
|
-
const seed = async (): Promise<string> => {
|
|
66
|
-
const id = makeUuid();
|
|
67
|
-
if (seedOrg) await seedOrg(id);
|
|
68
|
-
return id;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
describe(`IComputeServerStore conformance: ${name}`, () => {
|
|
72
|
-
it('savePlatformServers + getConfig round-trips servers and defaultServerId', async () => {
|
|
73
|
-
const store = await createStore();
|
|
74
|
-
const s1 = platformServer({ label: 'Primary' });
|
|
75
|
-
const s2 = platformServer({ label: 'Secondary', apiKey: 'secret' });
|
|
76
|
-
await store.savePlatformServers(SYSTEM_CONTEXT, [s1, s2], s1.id);
|
|
77
|
-
|
|
78
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
79
|
-
const platformIds = got.servers.filter(isPlatformServer).map((s) => s.id);
|
|
80
|
-
expect(platformIds.sort()).toEqual([s1.id, s2.id].sort());
|
|
81
|
-
expect(got.defaultServerId).toBe(s1.id);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('savePlatformServers replaces the previous platform set', async () => {
|
|
85
|
-
const store = await createStore();
|
|
86
|
-
await store.savePlatformServers(
|
|
87
|
-
SYSTEM_CONTEXT,
|
|
88
|
-
[platformServer({ label: 'Old' })],
|
|
89
|
-
undefined
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const newS = platformServer({ label: 'New' });
|
|
93
|
-
await store.savePlatformServers(SYSTEM_CONTEXT, [newS], newS.id);
|
|
94
|
-
|
|
95
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
96
|
-
const platformIds = got.servers.filter(isPlatformServer).map((s) => s.id);
|
|
97
|
-
expect(platformIds).toEqual([newS.id]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('getConfig on empty returns an empty servers list', async () => {
|
|
101
|
-
const store = await createStore();
|
|
102
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
103
|
-
expect(Array.isArray(got.servers)).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// Org-private servers + orgDefaults (Architecture spec §4.8)
|
|
108
|
-
// ============================================================================
|
|
109
|
-
|
|
110
|
-
it('saveOrgServers stores rows owned by the org', async () => {
|
|
111
|
-
const store = await createStore();
|
|
112
|
-
const orgId = await seed();
|
|
113
|
-
const s = orgServer(orgId, { label: "Org's BYO" });
|
|
114
|
-
await store.saveOrgServers(SYSTEM_CONTEXT, orgId, [s], s.id);
|
|
115
|
-
|
|
116
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
117
|
-
const ownRows = got.servers.filter(
|
|
118
|
-
(row): row is OrgComputeServer => isOrgServer(row) && row.ownerOrgId === orgId
|
|
119
|
-
);
|
|
120
|
-
expect(ownRows.map((r) => r.id)).toEqual([s.id]);
|
|
121
|
-
expect(got.orgDefaults?.[orgId]).toBe(s.id);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('saveOrgServers does not clobber platform rows (and vice versa)', async () => {
|
|
125
|
-
const store = await createStore();
|
|
126
|
-
const orgId = await seed();
|
|
127
|
-
const platS = platformServer({ label: 'Platform' });
|
|
128
|
-
const orgS = orgServer(orgId, { label: 'Org' });
|
|
129
|
-
|
|
130
|
-
await store.savePlatformServers(SYSTEM_CONTEXT, [platS], platS.id);
|
|
131
|
-
await store.saveOrgServers(SYSTEM_CONTEXT, orgId, [orgS], orgS.id);
|
|
132
|
-
|
|
133
|
-
// Replacing the platform set should leave org rows untouched.
|
|
134
|
-
const replacement = platformServer({ label: 'Replacement' });
|
|
135
|
-
await store.savePlatformServers(SYSTEM_CONTEXT, [replacement], replacement.id);
|
|
136
|
-
|
|
137
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
138
|
-
const platformIds = got.servers.filter(isPlatformServer).map((s) => s.id);
|
|
139
|
-
expect(platformIds).toEqual([replacement.id]);
|
|
140
|
-
const orgIds = got.servers
|
|
141
|
-
.filter((s): s is OrgComputeServer => isOrgServer(s) && s.ownerOrgId === orgId)
|
|
142
|
-
.map((s) => s.id);
|
|
143
|
-
expect(orgIds).toEqual([orgS.id]);
|
|
144
|
-
expect(got.defaultServerId).toBe(replacement.id);
|
|
145
|
-
expect(got.orgDefaults?.[orgId]).toBe(orgS.id);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('setOrgDefault updates and clears the per-org default', async () => {
|
|
149
|
-
const store = await createStore();
|
|
150
|
-
const orgId = await seed();
|
|
151
|
-
const s = orgServer(orgId);
|
|
152
|
-
await store.saveOrgServers(SYSTEM_CONTEXT, orgId, [s]);
|
|
153
|
-
|
|
154
|
-
await store.setOrgDefault(SYSTEM_CONTEXT, orgId, s.id);
|
|
155
|
-
let got = await store.getConfig(SYSTEM_CONTEXT);
|
|
156
|
-
expect(got.orgDefaults?.[orgId]).toBe(s.id);
|
|
157
|
-
|
|
158
|
-
await store.setOrgDefault(SYSTEM_CONTEXT, orgId, null);
|
|
159
|
-
got = await store.getConfig(SYSTEM_CONTEXT);
|
|
160
|
-
expect(got.orgDefaults?.[orgId]).toBeUndefined();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('deleteByOrg removes org-private rows and the orgDefaults entry', async () => {
|
|
164
|
-
const store = await createStore();
|
|
165
|
-
const orgId = await seed();
|
|
166
|
-
const orgS = orgServer(orgId);
|
|
167
|
-
await store.saveOrgServers(SYSTEM_CONTEXT, orgId, [orgS], orgS.id);
|
|
168
|
-
|
|
169
|
-
await store.deleteByOrg(SYSTEM_CONTEXT, orgId);
|
|
170
|
-
|
|
171
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
172
|
-
const remaining = got.servers.filter(
|
|
173
|
-
(s): s is ComputeServerConfig => isOrgServer(s) && s.ownerOrgId === orgId
|
|
174
|
-
);
|
|
175
|
-
expect(remaining).toEqual([]);
|
|
176
|
-
expect(got.orgDefaults?.[orgId]).toBeUndefined();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('deleteByOrg strips this org from platform sharedWith allowlists', async () => {
|
|
180
|
-
const store = await createStore();
|
|
181
|
-
const orgId = await seed();
|
|
182
|
-
const otherOrgId = await seed();
|
|
183
|
-
const platS = platformServer({ sharedWith: [orgId, otherOrgId] });
|
|
184
|
-
await store.savePlatformServers(SYSTEM_CONTEXT, [platS], platS.id);
|
|
185
|
-
|
|
186
|
-
await store.deleteByOrg(SYSTEM_CONTEXT, orgId);
|
|
187
|
-
|
|
188
|
-
const got = await store.getConfig(SYSTEM_CONTEXT);
|
|
189
|
-
const found = got.servers.find(
|
|
190
|
-
(s): s is PlatformComputeServer => isPlatformServer(s) && s.id === platS.id
|
|
191
|
-
);
|
|
192
|
-
expect(found?.sharedWith).toEqual([otherOrgId]);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
}
|