@wildo-ai/platform-infra-providers 1.1.2
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 +34 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.d.ts +63 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.d.ts.map +1 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.js +110 -0
- package/dist/esm/aws-s3/aws-s3.infra.module.js.map +1 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.d.ts +30 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.d.ts.map +1 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.js +61 -0
- package/dist/esm/clamav-self/clamav-self.infra.module.js.map +1 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts +26 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts.map +1 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js +54 -0
- package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js.map +1 -0
- package/dist/esm/index.d.ts +88 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +125 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/minio-self/minio-self.infra.module.d.ts +64 -0
- package/dist/esm/minio-self/minio-self.infra.module.d.ts.map +1 -0
- package/dist/esm/minio-self/minio-self.infra.module.js +105 -0
- package/dist/esm/minio-self/minio-self.infra.module.js.map +1 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts +52 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts.map +1 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js +201 -0
- package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js.map +1 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts +81 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts.map +1 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.js +311 -0
- package/dist/esm/mongodb-self/mongodb-self.infra.module.js.map +1 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.d.ts +70 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.d.ts.map +1 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.js +328 -0
- package/dist/esm/postgres-self/postgres-self.infra.module.js.map +1 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts +88 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts.map +1 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js +473 -0
- package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js.map +1 -0
- package/dist/esm/redis-self/redis-self.infra.module.d.ts +89 -0
- package/dist/esm/redis-self/redis-self.infra.module.d.ts.map +1 -0
- package/dist/esm/redis-self/redis-self.infra.module.js +331 -0
- package/dist/esm/redis-self/redis-self.infra.module.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +52 -0
- package/src/aws-s3/__tests__/aws-s3.infra.module.test.ts +67 -0
- package/src/aws-s3/aws-s3.infra.module.ts +120 -0
- package/src/clamav-self/__tests__/clamav-self.infra.module.test.ts +65 -0
- package/src/clamav-self/clamav-self.infra.module.ts +72 -0
- package/src/gotenberg-self/__tests__/gotenberg-self.infra.module.test.ts +65 -0
- package/src/gotenberg-self/gotenberg-self.infra.module.ts +65 -0
- package/src/index.ts +133 -0
- package/src/minio-self/__tests__/minio-self.infra.module.test.ts +70 -0
- package/src/minio-self/minio-self.infra.module.ts +116 -0
- package/src/mongodb-atlas/__tests__/mongodb-atlas.infra.module.test.ts +198 -0
- package/src/mongodb-atlas/mongodb-atlas.infra.module.ts +236 -0
- package/src/mongodb-self/__tests__/mongodb-self.infra.module.test.ts +419 -0
- package/src/mongodb-self/mongodb-self.infra.module.ts +384 -0
- package/src/postgres-self/__tests__/postgres-self.infra.module.test.ts +344 -0
- package/src/postgres-self/postgres-self.infra.module.ts +377 -0
- package/src/rabbitmq-self/__tests__/rabbitmq-self.infra.module.test.ts +499 -0
- package/src/rabbitmq-self/rabbitmq-self.infra.module.ts +531 -0
- package/src/redis-self/__tests__/redis-self.infra.module.test.ts +341 -0
- package/src/redis-self/redis-self.infra.module.ts +372 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `PostgresSelfInfraProviderModule` (Slice 4.B.2 of
|
|
3
|
+
* `infra-provider-planner.md`).
|
|
4
|
+
*
|
|
5
|
+
* Goals:
|
|
6
|
+
* - Pin the `plan()` shape so a future regression that drops e.g.
|
|
7
|
+
* `PG_ADMIN_PASSWORD` from `secretEnvVars` fails loud.
|
|
8
|
+
* - Pin the orchestrator-pattern contract: `mintAppCredentials`
|
|
9
|
+
* accepts the password via `secretInputs` and returns the
|
|
10
|
+
* `connectionUrl` from the admin client — proves the module is
|
|
11
|
+
* upsert-only, not generator.
|
|
12
|
+
* - Pin the per-app database + role naming convention against the
|
|
13
|
+
* legacy `platform-infrastructure-manager.service.ts:301-302`
|
|
14
|
+
* convention (`wildo_<sanitized>` / `wildo_<sanitized>_user`) so
|
|
15
|
+
* a future code change here surfaces as a name diff in this
|
|
16
|
+
* test, not as a silent identifier drift on existing apps.
|
|
17
|
+
* - Pin the optional `applicationConfigInputs.databaseName` override.
|
|
18
|
+
* - Pin defensive validation of empty `appSlug` and missing
|
|
19
|
+
* `secretInputs` keys.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
23
|
+
import {
|
|
24
|
+
AppCredentialMintingContext,
|
|
25
|
+
IPostgresAdminClient,
|
|
26
|
+
InfraCapability,
|
|
27
|
+
InfraPlatformAdminConsumer,
|
|
28
|
+
InfraProvider,
|
|
29
|
+
} from '@wildo-ai/platform-config-lib';
|
|
30
|
+
import { ApplicationRuntimePrincipalKind } from '@wildo-ai/platform-config-lib';
|
|
31
|
+
import { PostgresSelfInfraProviderModule } from '../postgres-self.infra.module';
|
|
32
|
+
|
|
33
|
+
function makeAdminClientStub(
|
|
34
|
+
connectionUrl: string = 'postgresql://wildo_wonder_todos_user:secret@postgres:5432/wildo_wonder_todos',
|
|
35
|
+
): IPostgresAdminClient & {
|
|
36
|
+
upsertApplicationDatabaseSpy: ReturnType<typeof vi.fn>;
|
|
37
|
+
dropApplicationDatabaseSpy: ReturnType<typeof vi.fn>;
|
|
38
|
+
upsertTableScopedDatabaseRoleSpy: ReturnType<typeof vi.fn>;
|
|
39
|
+
dropTableScopedDatabaseRoleSpy: ReturnType<typeof vi.fn>;
|
|
40
|
+
} {
|
|
41
|
+
const upsertApplicationDatabaseSpy = vi.fn().mockResolvedValue({ connectionUrl });
|
|
42
|
+
const dropApplicationDatabaseSpy = vi.fn().mockResolvedValue(undefined);
|
|
43
|
+
const upsertTableScopedDatabaseRoleSpy = vi.fn().mockResolvedValue({
|
|
44
|
+
connectionUrl: 'postgresql://scoped:secret@postgres:5432/wildo_wonder_todos',
|
|
45
|
+
});
|
|
46
|
+
const dropTableScopedDatabaseRoleSpy = vi.fn().mockResolvedValue(undefined);
|
|
47
|
+
return {
|
|
48
|
+
upsertApplicationDatabase: upsertApplicationDatabaseSpy,
|
|
49
|
+
dropApplicationDatabase: dropApplicationDatabaseSpy,
|
|
50
|
+
upsertTableScopedDatabaseRole: upsertTableScopedDatabaseRoleSpy,
|
|
51
|
+
dropTableScopedDatabaseRole: dropTableScopedDatabaseRoleSpy,
|
|
52
|
+
upsertApplicationDatabaseSpy,
|
|
53
|
+
dropApplicationDatabaseSpy,
|
|
54
|
+
upsertTableScopedDatabaseRoleSpy,
|
|
55
|
+
dropTableScopedDatabaseRoleSpy,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function makeMintContext(
|
|
60
|
+
adminClient: IPostgresAdminClient,
|
|
61
|
+
overrides: Partial<AppCredentialMintingContext> = {},
|
|
62
|
+
): AppCredentialMintingContext {
|
|
63
|
+
return {
|
|
64
|
+
appSlug: 'wonder-todos',
|
|
65
|
+
applicationNamespace: 'wonder-todos-local',
|
|
66
|
+
wildoApplicationId: '60d21bdf1c4ae5cda1b2c3d4',
|
|
67
|
+
adminClient,
|
|
68
|
+
secretInputs: {
|
|
69
|
+
'database.postgresql.password': 'orchestrator-supplied-password',
|
|
70
|
+
},
|
|
71
|
+
...overrides,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('PostgresSelfInfraProviderModule.plan()', () => {
|
|
76
|
+
const contribution = PostgresSelfInfraProviderModule.plan({
|
|
77
|
+
environment: 'local',
|
|
78
|
+
runtime: 'docker-compose',
|
|
79
|
+
backingServiceKey: 'postgresql',
|
|
80
|
+
config: {},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('declares the right provider + capability triple', () => {
|
|
84
|
+
expect(PostgresSelfInfraProviderModule.provider).toBe(InfraProvider.POSTGRES_SELF);
|
|
85
|
+
expect(PostgresSelfInfraProviderModule.supportedCapabilities).toEqual([
|
|
86
|
+
InfraCapability.RELATIONAL_DATABASE,
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('contributes the postgres compose service + K8s manifest', () => {
|
|
91
|
+
expect(contribution.dockerComposeServices).toEqual(['postgres']);
|
|
92
|
+
expect(contribution.k8sResources).toEqual([
|
|
93
|
+
'templates/infra/k8s/internal/postgres/deployment.yaml.hbs',
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('declares platformAdmin (audience-1) for apps-manager + companion only', () => {
|
|
98
|
+
expect(contribution.platformAdmin?.envVars).toEqual({
|
|
99
|
+
PG_HOST: 'postgres',
|
|
100
|
+
PG_PORT: '5432',
|
|
101
|
+
});
|
|
102
|
+
expect(contribution.platformAdmin?.secretEnvVars).toEqual([
|
|
103
|
+
'PG_ADMIN_USER',
|
|
104
|
+
'PG_ADMIN_PASSWORD',
|
|
105
|
+
]);
|
|
106
|
+
expect(contribution.platformAdmin?.consumers).toEqual([
|
|
107
|
+
InfraPlatformAdminConsumer.APPS_MANAGER,
|
|
108
|
+
InfraPlatformAdminConsumer.COMPANION,
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('declares appRuntime blob paths for the customer-app pod', () => {
|
|
113
|
+
expect(contribution.appRuntime?.perAppConfigBlobPaths).toEqual({
|
|
114
|
+
'database.postgresql.host': 'postgres',
|
|
115
|
+
'database.postgresql.port': 5432,
|
|
116
|
+
});
|
|
117
|
+
// Note: NO `database.postgresql.password` path — the password is
|
|
118
|
+
// embedded in `connectUrl` (Postgres convention), not persisted
|
|
119
|
+
// separately.
|
|
120
|
+
expect(contribution.appRuntime?.perAppSecretBlobPaths).toEqual([
|
|
121
|
+
'database.postgresql.databaseName',
|
|
122
|
+
'database.postgresql.username',
|
|
123
|
+
'database.postgresql.connectUrl',
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('PostgresSelfInfraProviderModule.mintAppCredentials', () => {
|
|
129
|
+
it('upserts a per-app database + role using the appSlug-derived names', async () => {
|
|
130
|
+
const adminClient = makeAdminClientStub();
|
|
131
|
+
const result = await PostgresSelfInfraProviderModule.mintAppCredentials!(
|
|
132
|
+
makeMintContext(adminClient),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(adminClient.upsertApplicationDatabaseSpy).toHaveBeenCalledTimes(1);
|
|
136
|
+
expect(adminClient.upsertApplicationDatabaseSpy).toHaveBeenCalledWith({
|
|
137
|
+
databaseName: 'wildo_wonder_todos',
|
|
138
|
+
username: 'wildo_wonder_todos_user',
|
|
139
|
+
password: 'orchestrator-supplied-password',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result).toEqual({
|
|
143
|
+
secretBlobValues: {
|
|
144
|
+
'database.postgresql.databaseName': 'wildo_wonder_todos',
|
|
145
|
+
'database.postgresql.username': 'wildo_wonder_todos_user',
|
|
146
|
+
'database.postgresql.connectUrl':
|
|
147
|
+
'postgresql://wildo_wonder_todos_user:secret@postgres:5432/wildo_wonder_todos',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('honors applicationConfigInputs.databaseName override', async () => {
|
|
153
|
+
const adminClient = makeAdminClientStub();
|
|
154
|
+
await PostgresSelfInfraProviderModule.mintAppCredentials!(
|
|
155
|
+
makeMintContext(adminClient, {
|
|
156
|
+
applicationConfigInputs: { databaseName: 'authored_override' },
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
expect(adminClient.upsertApplicationDatabaseSpy).toHaveBeenCalledWith({
|
|
160
|
+
databaseName: 'authored_override',
|
|
161
|
+
username: 'wildo_wonder_todos_user',
|
|
162
|
+
password: 'orchestrator-supplied-password',
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('sanitizes slugs with hyphens, dots, and uppercase', async () => {
|
|
167
|
+
const adminClient = makeAdminClientStub();
|
|
168
|
+
await PostgresSelfInfraProviderModule.mintAppCredentials!(
|
|
169
|
+
makeMintContext(adminClient, { appSlug: 'My.App-V2' }),
|
|
170
|
+
);
|
|
171
|
+
// lowercase + non-alphanumeric→`_` + collapse + trim — matches
|
|
172
|
+
// legacy `sanitizeForPostgreSQL` in
|
|
173
|
+
// `platform-infrastructure-manager.service.ts:371-378`.
|
|
174
|
+
expect(adminClient.upsertApplicationDatabaseSpy).toHaveBeenCalledWith({
|
|
175
|
+
databaseName: 'wildo_my_app_v2',
|
|
176
|
+
username: 'wildo_my_app_v2_user',
|
|
177
|
+
password: 'orchestrator-supplied-password',
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('fails loud when secretInputs is missing the password', async () => {
|
|
182
|
+
const adminClient = makeAdminClientStub();
|
|
183
|
+
await expect(
|
|
184
|
+
PostgresSelfInfraProviderModule.mintAppCredentials!(
|
|
185
|
+
makeMintContext(adminClient, { secretInputs: {} }),
|
|
186
|
+
),
|
|
187
|
+
).rejects.toThrow(/missing 'database\.postgresql\.password'/);
|
|
188
|
+
expect(adminClient.upsertApplicationDatabaseSpy).not.toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('fails loud when appSlug is empty', async () => {
|
|
192
|
+
const adminClient = makeAdminClientStub();
|
|
193
|
+
await expect(
|
|
194
|
+
PostgresSelfInfraProviderModule.mintAppCredentials!(
|
|
195
|
+
makeMintContext(adminClient, { appSlug: '' }),
|
|
196
|
+
),
|
|
197
|
+
).rejects.toThrow(/appSlug is empty/);
|
|
198
|
+
expect(adminClient.upsertApplicationDatabaseSpy).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('PostgresSelfInfraProviderModule.revokeAppCredentials', () => {
|
|
203
|
+
it('drops the per-app database + role using the slug-derived names', async () => {
|
|
204
|
+
const adminClient = makeAdminClientStub();
|
|
205
|
+
await PostgresSelfInfraProviderModule.revokeAppCredentials!(
|
|
206
|
+
makeMintContext(adminClient),
|
|
207
|
+
);
|
|
208
|
+
expect(adminClient.dropApplicationDatabaseSpy).toHaveBeenCalledTimes(1);
|
|
209
|
+
expect(adminClient.dropApplicationDatabaseSpy).toHaveBeenCalledWith({
|
|
210
|
+
databaseName: 'wildo_wonder_todos',
|
|
211
|
+
username: 'wildo_wonder_todos_user',
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('honors applicationConfigInputs.databaseName override on drop', async () => {
|
|
216
|
+
const adminClient = makeAdminClientStub();
|
|
217
|
+
await PostgresSelfInfraProviderModule.revokeAppCredentials!(
|
|
218
|
+
makeMintContext(adminClient, {
|
|
219
|
+
applicationConfigInputs: { databaseName: 'authored_override' },
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
expect(adminClient.dropApplicationDatabaseSpy).toHaveBeenCalledWith({
|
|
223
|
+
databaseName: 'authored_override',
|
|
224
|
+
username: 'wildo_wonder_todos_user',
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('fails loud when appSlug is empty', async () => {
|
|
229
|
+
const adminClient = makeAdminClientStub();
|
|
230
|
+
await expect(
|
|
231
|
+
PostgresSelfInfraProviderModule.revokeAppCredentials!(
|
|
232
|
+
makeMintContext(adminClient, { appSlug: '' }),
|
|
233
|
+
),
|
|
234
|
+
).rejects.toThrow(/appSlug is empty/);
|
|
235
|
+
expect(adminClient.dropApplicationDatabaseSpy).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('PostgresSelfInfraProviderModule audience-2 absence (D-4)', () => {
|
|
240
|
+
it('does NOT declare mintPlatformServiceCredentials', () => {
|
|
241
|
+
expect(PostgresSelfInfraProviderModule.mintPlatformServiceCredentials).toBeUndefined();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Step 3 / R1 — a NAMED runtime principal gets a table-scoped role, never the
|
|
248
|
+
* database-owning one.
|
|
249
|
+
*
|
|
250
|
+
* These pins are the PostgreSQL half of the confinement already proven at the
|
|
251
|
+
* live Mongo, Redis and RabbitMQ services. They are written so that each one
|
|
252
|
+
* reddens for a DIFFERENT reason: dropping the grant derivation, widening a
|
|
253
|
+
* verb, falling back to the owner path, or destroying the database on revoke.
|
|
254
|
+
*/
|
|
255
|
+
describe('PostgresSelfInfraProviderModule — per-principal table-scoped role', () => {
|
|
256
|
+
const minionPrincipal = {
|
|
257
|
+
kind: ApplicationRuntimePrincipalKind.MINION,
|
|
258
|
+
name: 'marketingScrapper',
|
|
259
|
+
} as AppCredentialMintingContext['principal'];
|
|
260
|
+
|
|
261
|
+
it('grants ONLY the declared verbs on the declared tables', async () => {
|
|
262
|
+
const adminClient = makeAdminClientStub();
|
|
263
|
+
await PostgresSelfInfraProviderModule.mintAppCredentials!(makeMintContext(adminClient, {
|
|
264
|
+
principal: minionPrincipal,
|
|
265
|
+
resourceGrants: { read: ['todos'] as never, write: ['events'] as never },
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
// The owner path must not have been touched at all: reaching it would make
|
|
269
|
+
// the minion the OWNER of the application's database.
|
|
270
|
+
expect(adminClient.upsertApplicationDatabaseSpy).not.toHaveBeenCalled();
|
|
271
|
+
expect(adminClient.upsertTableScopedDatabaseRoleSpy).toHaveBeenCalledTimes(1);
|
|
272
|
+
|
|
273
|
+
const call = adminClient.upsertTableScopedDatabaseRoleSpy.mock.calls[0]![0];
|
|
274
|
+
expect(call.readTables).toEqual(['todos']);
|
|
275
|
+
// `write` does NOT widen into `read` — the axes stay exactly as declared,
|
|
276
|
+
// which is the property that makes the authored declaration describe the
|
|
277
|
+
// outcome. Asserting `readTables` does not contain 'events' is the half
|
|
278
|
+
// that reddens if somebody "helpfully" merges the axes.
|
|
279
|
+
expect(call.readTables).not.toContain('events');
|
|
280
|
+
expect(call.writeTables).toEqual(['events']);
|
|
281
|
+
expect(call.schemaName).toBe('public');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('REFUSES to mint when the principal declared no resourceAccess (D11/R3)', async () => {
|
|
285
|
+
// The dangerous alternative is not an exception — it is silently falling
|
|
286
|
+
// through to `upsertApplicationDatabase`, which would hand the minion
|
|
287
|
+
// ownership. So the pin asserts BOTH that it throws and that nothing was
|
|
288
|
+
// provisioned.
|
|
289
|
+
const adminClient = makeAdminClientStub();
|
|
290
|
+
await expect(
|
|
291
|
+
PostgresSelfInfraProviderModule.mintAppCredentials!(makeMintContext(adminClient, {
|
|
292
|
+
principal: minionPrincipal,
|
|
293
|
+
})),
|
|
294
|
+
).rejects.toThrow(/no resourceGrants/);
|
|
295
|
+
expect(adminClient.upsertApplicationDatabaseSpy).not.toHaveBeenCalled();
|
|
296
|
+
expect(adminClient.upsertTableScopedDatabaseRoleSpy).not.toHaveBeenCalled();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('leaves the APPLICATION BACKEND on the owner path with its legacy name', async () => {
|
|
300
|
+
// The backend must keep `wildo_<slug>_user` VERBATIM: a renamed owner role
|
|
301
|
+
// orphans every existing deployment's database.
|
|
302
|
+
const adminClient = makeAdminClientStub();
|
|
303
|
+
const result = await PostgresSelfInfraProviderModule.mintAppCredentials!(makeMintContext(adminClient, {
|
|
304
|
+
principal: {
|
|
305
|
+
kind: ApplicationRuntimePrincipalKind.APPLICATION_BACKEND,
|
|
306
|
+
} as AppCredentialMintingContext['principal'],
|
|
307
|
+
}));
|
|
308
|
+
expect(adminClient.upsertTableScopedDatabaseRoleSpy).not.toHaveBeenCalled();
|
|
309
|
+
expect(result!.secretBlobValues['database.postgresql.username']).toBe('wildo_wonder_todos_user');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('revokes a named principal WITHOUT dropping the application database', async () => {
|
|
313
|
+
// The single most destructive failure available in this file. Before named
|
|
314
|
+
// principals existed, `revokeAppCredentials` had exactly one path, and it
|
|
315
|
+
// drops the DATABASE — retiring one minion through it would destroy the
|
|
316
|
+
// whole application's data while reporting success.
|
|
317
|
+
const adminClient = makeAdminClientStub();
|
|
318
|
+
await PostgresSelfInfraProviderModule.revokeAppCredentials!(makeMintContext(adminClient, {
|
|
319
|
+
principal: minionPrincipal,
|
|
320
|
+
}));
|
|
321
|
+
expect(adminClient.dropApplicationDatabaseSpy).not.toHaveBeenCalled();
|
|
322
|
+
expect(adminClient.dropTableScopedDatabaseRoleSpy).toHaveBeenCalledTimes(1);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('derives the SAME role name on mint and on revoke', async () => {
|
|
326
|
+
// Fourth service where a divergent derivation would leave an orphaned,
|
|
327
|
+
// still-valid credential behind. Compared rather than asserted against a
|
|
328
|
+
// literal, so the pin survives a deliberate naming change and only fails
|
|
329
|
+
// when the two sides DISAGREE.
|
|
330
|
+
const adminClient = makeAdminClientStub();
|
|
331
|
+
await PostgresSelfInfraProviderModule.mintAppCredentials!(makeMintContext(adminClient, {
|
|
332
|
+
principal: minionPrincipal,
|
|
333
|
+
resourceGrants: { read: ['todos'] as never, write: [] as never },
|
|
334
|
+
}));
|
|
335
|
+
await PostgresSelfInfraProviderModule.revokeAppCredentials!(makeMintContext(adminClient, {
|
|
336
|
+
principal: minionPrincipal,
|
|
337
|
+
}));
|
|
338
|
+
expect(adminClient.dropTableScopedDatabaseRoleSpy.mock.calls[0]![0].username)
|
|
339
|
+
.toBe(adminClient.upsertTableScopedDatabaseRoleSpy.mock.calls[0]![0].username);
|
|
340
|
+
// And it must not collide with the backend's owner role.
|
|
341
|
+
expect(adminClient.upsertTableScopedDatabaseRoleSpy.mock.calls[0]![0].username)
|
|
342
|
+
.not.toBe('wildo_wonder_todos_user');
|
|
343
|
+
});
|
|
344
|
+
});
|