@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.
Files changed (62) hide show
  1. package/LICENSE +34 -0
  2. package/dist/esm/aws-s3/aws-s3.infra.module.d.ts +63 -0
  3. package/dist/esm/aws-s3/aws-s3.infra.module.d.ts.map +1 -0
  4. package/dist/esm/aws-s3/aws-s3.infra.module.js +110 -0
  5. package/dist/esm/aws-s3/aws-s3.infra.module.js.map +1 -0
  6. package/dist/esm/clamav-self/clamav-self.infra.module.d.ts +30 -0
  7. package/dist/esm/clamav-self/clamav-self.infra.module.d.ts.map +1 -0
  8. package/dist/esm/clamav-self/clamav-self.infra.module.js +61 -0
  9. package/dist/esm/clamav-self/clamav-self.infra.module.js.map +1 -0
  10. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts +26 -0
  11. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.d.ts.map +1 -0
  12. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js +54 -0
  13. package/dist/esm/gotenberg-self/gotenberg-self.infra.module.js.map +1 -0
  14. package/dist/esm/index.d.ts +88 -0
  15. package/dist/esm/index.d.ts.map +1 -0
  16. package/dist/esm/index.js +125 -0
  17. package/dist/esm/index.js.map +1 -0
  18. package/dist/esm/minio-self/minio-self.infra.module.d.ts +64 -0
  19. package/dist/esm/minio-self/minio-self.infra.module.d.ts.map +1 -0
  20. package/dist/esm/minio-self/minio-self.infra.module.js +105 -0
  21. package/dist/esm/minio-self/minio-self.infra.module.js.map +1 -0
  22. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts +52 -0
  23. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.d.ts.map +1 -0
  24. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js +201 -0
  25. package/dist/esm/mongodb-atlas/mongodb-atlas.infra.module.js.map +1 -0
  26. package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts +81 -0
  27. package/dist/esm/mongodb-self/mongodb-self.infra.module.d.ts.map +1 -0
  28. package/dist/esm/mongodb-self/mongodb-self.infra.module.js +311 -0
  29. package/dist/esm/mongodb-self/mongodb-self.infra.module.js.map +1 -0
  30. package/dist/esm/postgres-self/postgres-self.infra.module.d.ts +70 -0
  31. package/dist/esm/postgres-self/postgres-self.infra.module.d.ts.map +1 -0
  32. package/dist/esm/postgres-self/postgres-self.infra.module.js +328 -0
  33. package/dist/esm/postgres-self/postgres-self.infra.module.js.map +1 -0
  34. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts +88 -0
  35. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.d.ts.map +1 -0
  36. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js +473 -0
  37. package/dist/esm/rabbitmq-self/rabbitmq-self.infra.module.js.map +1 -0
  38. package/dist/esm/redis-self/redis-self.infra.module.d.ts +89 -0
  39. package/dist/esm/redis-self/redis-self.infra.module.d.ts.map +1 -0
  40. package/dist/esm/redis-self/redis-self.infra.module.js +331 -0
  41. package/dist/esm/redis-self/redis-self.infra.module.js.map +1 -0
  42. package/dist/tsconfig.build.tsbuildinfo +1 -0
  43. package/package.json +52 -0
  44. package/src/aws-s3/__tests__/aws-s3.infra.module.test.ts +67 -0
  45. package/src/aws-s3/aws-s3.infra.module.ts +120 -0
  46. package/src/clamav-self/__tests__/clamav-self.infra.module.test.ts +65 -0
  47. package/src/clamav-self/clamav-self.infra.module.ts +72 -0
  48. package/src/gotenberg-self/__tests__/gotenberg-self.infra.module.test.ts +65 -0
  49. package/src/gotenberg-self/gotenberg-self.infra.module.ts +65 -0
  50. package/src/index.ts +133 -0
  51. package/src/minio-self/__tests__/minio-self.infra.module.test.ts +70 -0
  52. package/src/minio-self/minio-self.infra.module.ts +116 -0
  53. package/src/mongodb-atlas/__tests__/mongodb-atlas.infra.module.test.ts +198 -0
  54. package/src/mongodb-atlas/mongodb-atlas.infra.module.ts +236 -0
  55. package/src/mongodb-self/__tests__/mongodb-self.infra.module.test.ts +419 -0
  56. package/src/mongodb-self/mongodb-self.infra.module.ts +384 -0
  57. package/src/postgres-self/__tests__/postgres-self.infra.module.test.ts +344 -0
  58. package/src/postgres-self/postgres-self.infra.module.ts +377 -0
  59. package/src/rabbitmq-self/__tests__/rabbitmq-self.infra.module.test.ts +499 -0
  60. package/src/rabbitmq-self/rabbitmq-self.infra.module.ts +531 -0
  61. package/src/redis-self/__tests__/redis-self.infra.module.test.ts +341 -0
  62. 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
+ });