import { describe, expect, test } from 'vitest'; import { ApiTriggerType } from '../types/api/index.js'; import { DATABASE_LIFECYCLE_MANAGED_BY, LIFECYCLE_OPERATIONS, computeBindingKey, computeDesiredSpecHash, computeNativeDbApplicationToken, computeNativeDbProfileToken, computeRequirementKey, containsCredentialMaterial, isLifecycleManagedIntegrationAllowedForApiTrigger, isSecretKey, isTerminalLifecycleState, nativeDbIamSessionPolicy, parseNativeDbIamDescriptor, redactLifecycleSecrets } from './index.js'; describe('database lifecycle contracts', () => { // Postgres `ALTER TYPE ... ADD VALUE` appends. TypeORM's entity-vs-schema // check treats enum label order as part of the contract, so this array must // match the live `lifecycle_operation_enum` order rather than alphabetical // or semantic grouping. test('lists lifecycle operations in Postgres enum order', () => { expect(LIFECYCLE_OPERATIONS).toEqual(['ensure_database', 'migrate_schema', 'retire_database', 'ensure_physical_database_instance']); }); test('allows lifecycle-managed integrations only in application APIs', () => { expect(isLifecycleManagedIntegrationAllowedForApiTrigger(ApiTriggerType.UI, DATABASE_LIFECYCLE_MANAGED_BY)).toBe(true); expect(isLifecycleManagedIntegrationAllowedForApiTrigger(ApiTriggerType.WORKFLOW, DATABASE_LIFECYCLE_MANAGED_BY)).toBe(false); expect(isLifecycleManagedIntegrationAllowedForApiTrigger(ApiTriggerType.SCHEDULE, DATABASE_LIFECYCLE_MANAGED_BY)).toBe(false); expect(isLifecycleManagedIntegrationAllowedForApiTrigger(ApiTriggerType.WORKFLOW, null)).toBe(true); }); test('computes stable requirement, binding, and desired spec identifiers', async () => { const requirement = { logicalName: 'Orders DB', engine: 'postgres', version: '16', sizing: { storageGb: 20, tier: 'shared' }, extensions: ['pgcrypto', 'uuid-ossp'] } as const; expect(computeRequirementKey(requirement)).toBe('orders-db~Orders%20DB:postgres'); expect(computeRequirementKey({ logicalName: 'orders/db', engine: 'postgres' })).toBe('orders-db~orders%2Fdb:postgres'); expect(computeRequirementKey({ logicalName: 'orders-db', engine: 'postgres' })).toBe('orders-db~orders-db:postgres'); // binding_key excludes the logical name on purpose: one database per // (organization, application, profile). Two different logical names under // the same profile resolve to the same binding_key. expect( computeBindingKey({ organizationId: 'org-1', applicationId: 'app-1', profile: 'Staging EU' }) ).toBe('org-1:app-1:staging-eu~Staging%20EU'); expect( computeBindingKey({ organizationId: 'org-1', applicationId: 'app-1', profile: 'staging' }) ).toBe('org-1:app-1:staging~staging'); await expect(computeDesiredSpecHash(requirement)).resolves.toBe( await computeDesiredSpecHash({ ...requirement, extensions: ['uuid-ossp', 'pgcrypto'] }) ); await expect(computeDesiredSpecHash(requirement)).resolves.toBe('QsCl6RVzJVEwf4Nuk0D27rRtycYe2vQZMxcXLt9WUkQ='); }); test('ignores migrationDirectory when hashing the desired spec', async () => { const infrastructureSpec = { logicalName: 'Orders DB', engine: 'postgres', version: '16', sizing: { storageGb: 20, tier: 'shared' }, extensions: ['pgcrypto', 'uuid-ossp'] } as const; const withoutDirectory = await computeDesiredSpecHash(infrastructureSpec); const withDirectory = await computeDesiredSpecHash({ ...infrastructureSpec, migrationDirectory: 'db/migrations' }); const withOtherDirectory = await computeDesiredSpecHash({ ...infrastructureSpec, migrationDirectory: 'db/other-migrations' }); // migrationDirectory only says where the SQL files live, not what // infrastructure to provision, so changing it must not look like drift. expect(withDirectory).toBe(withoutDirectory); expect(withOtherDirectory).toBe(withoutDirectory); }); test('hashes desired specs with locale-invariant ordering', async () => { const first = await computeDesiredSpecHash({ logicalName: 'Éclair DB', engine: 'postgres', extensions: ['ä', 'z', 'a'] }); const second = await computeDesiredSpecHash({ logicalName: 'Éclair DB', engine: 'postgres', extensions: ['z', 'a', 'ä'] }); expect(first).toBe(second); }); test('redacts raw credential material and generic credential refs while preserving physical masterCredentialRef', () => { const redacted = redactLifecycleSecrets({ host: 'db.internal', password: 'raw-password', nested: { token: 'raw-token', credentialRef: { resolver: 'aws_secrets_manager', ref: 'arn:aws:secretsmanager:us-east-1:123:secret:orders' } }, credential_refs: { password: { resolver: 'vault', ref: 'database/orders', field: 'password' } }, masterCredentialRef: { resolver: 'aws_secrets_manager', ref: 'physical-instance/master', field: 'password' } }); expect(redacted).toEqual({ host: 'db.internal', password: '[REDACTED]', nested: { token: '[REDACTED]', credentialRef: { resolver: 'aws_secrets_manager', ref: 'arn:aws:secretsmanager:us-east-1:123:secret:orders' } }, credential_refs: { password: '[REDACTED]' }, masterCredentialRef: { resolver: 'aws_secrets_manager', ref: 'physical-instance/master', field: 'password' } }); }); test('recognizes terminal lifecycle states', () => { expect(isTerminalLifecycleState('ready')).toBe(true); expect(isTerminalLifecycleState('failed')).toBe(true); expect(isTerminalLifecycleState('provisioning')).toBe(false); }); test('flags secret-shaped keys', () => { for (const key of ['password', 'PASSWORD', 'api_token', 'private_key', 'private-key', 'dsn']) { expect(isSecretKey(key)).toBe(true); } for (const key of ['host', 'port', 'database', 'username']) { expect(isSecretKey(key)).toBe(false); } }); test('flags secret-shaped value content', () => { expect(containsCredentialMaterial('postgres://user:pass@db.internal:5432/orders')).toBe(true); expect(containsCredentialMaterial('mongodb+srv://app:secret@cluster0.example.net/db')).toBe(true); expect(containsCredentialMaterial('password=hunter2;host=db.internal')).toBe(true); expect(containsCredentialMaterial('{"password":"hunter2"}')).toBe(true); expect(containsCredentialMaterial('host=db.internal port=5432')).toBe(false); expect(containsCredentialMaterial('arn:aws:secretsmanager:us-east-1:123:secret:orders')).toBe(false); }); test('builds a canonical session policy from a valid managed Postgres IAM descriptor', () => { const descriptor = parseNativeDbIamDescriptor({ application_id: 'application-id', auth_descriptor_version: 3, auth_mode: 'aws_iam_role', aws_account_id: '123456789012', binding_id: 'binding-id', cluster_resource_id: 'cluster-ABC123DEF456EXAMPLE', connector_role_arn: 'arn:aws:iam::123456789012:role/superblocks-native-db-connector-prod', database: 'sbndb_abcdef1234567890_0123456789abcdef01234567', host: 'native-db.cluster-example.us-west-2.rds.amazonaws.com', port: 5432, region: 'us-west-2', username: 'sbndb_abcdef1234567890_0123456789abcdef01234567_runtime' }); expect(descriptor).toBeDefined(); if (!descriptor) { throw new Error('Expected a valid IAM descriptor'); } expect(nativeDbIamSessionPolicy(descriptor)).toBe( '{"Version":"2012-10-17","Statement":[{"Sid":"ConnectToThisNativeDatabaseUser","Effect":"Allow","Action":"rds-db:connect","Resource":"arn:aws:rds-db:us-west-2:123456789012:dbuser:cluster-ABC123DEF456EXAMPLE/sbndb_abcdef1234567890_0123456789abcdef01234567_runtime"}]}' ); }); test('reads managed Postgres IAM descriptors with auth_descriptor_version 3', () => { expect( parseNativeDbIamDescriptor({ application_id: 'application-id', auth_descriptor_version: 3, auth_mode: 'aws_iam_role', aws_account_id: '123456789012', binding_id: 'binding-id', cluster_resource_id: 'cluster-ABC123DEF456EXAMPLE', connector_role_arn: 'arn:aws:iam::123456789012:role/superblocks-native-db-connector-prod', database: 'sbndb_abcdef1234567890_0123456789abcdef01234567', host: 'native-db.cluster-example.us-west-2.rds.amazonaws.com', port: 5432, region: 'us-west-2', username: 'sbndb_abcdef1234567890_0123456789abcdef01234567_runtime' }) ).toEqual({ application_id: 'application-id', auth_descriptor_version: 3, auth_mode: 'aws_iam_role', aws_account_id: '123456789012', binding_id: 'binding-id', cluster_resource_id: 'cluster-ABC123DEF456EXAMPLE', connector_role_arn: 'arn:aws:iam::123456789012:role/superblocks-native-db-connector-prod', database: 'sbndb_abcdef1234567890_0123456789abcdef01234567', host: 'native-db.cluster-example.us-west-2.rds.amazonaws.com', port: 5432, region: 'us-west-2', username: 'sbndb_abcdef1234567890_0123456789abcdef01234567_runtime' }); }); test('rejects descriptors when database and runtime username do not match', () => { expect( parseNativeDbIamDescriptor({ application_id: 'application-id', auth_descriptor_version: 3, auth_mode: 'aws_iam_role', aws_account_id: '123456789012', binding_id: 'binding-id', cluster_resource_id: 'cluster-ABC123DEF456EXAMPLE', connector_role_arn: 'arn:aws:iam::123456789012:role/superblocks-native-db-connector-prod', database: 'sbndb_abcdef1234567890_0123456789abcdef01234567', host: 'native-db.cluster-example.us-west-2.rds.amazonaws.com', port: 5432, region: 'us-west-2', username: 'sbndb_abcdef1234567890_fedcba9876543210fedcba98_runtime' }) ).toBeUndefined(); }); test('rejects malformed or non-commercial managed Postgres IAM descriptors', () => { const descriptor = { application_id: 'application-id', auth_descriptor_version: 3, auth_mode: 'aws_iam_role', aws_account_id: '123456789012', binding_id: 'binding-id', cluster_resource_id: 'cluster-ABC123DEF456EXAMPLE', connector_role_arn: 'arn:aws:iam::123456789012:role/superblocks-native-db-connector-prod', database: 'sbndb_abcdef1234567890_0123456789abcdef01234567', host: 'native-db.cluster-example.us-west-2.rds.amazonaws.com', port: 5432, region: 'us-west-2', username: 'sbndb_abcdef1234567890_0123456789abcdef01234567_runtime' }; expect(parseNativeDbIamDescriptor({ ...descriptor, auth_descriptor_version: 1 })).toBeUndefined(); expect(parseNativeDbIamDescriptor({ ...descriptor, auth_descriptor_version: 2 })).toBeUndefined(); expect( parseNativeDbIamDescriptor({ ...descriptor, cluster_resource_id: 'not-an-rds-resource' }) ).toBeUndefined(); expect( parseNativeDbIamDescriptor({ ...descriptor, connector_role_arn: 'arn:aws:iam::210987654321:role/other' }) ).toBeUndefined(); expect(parseNativeDbIamDescriptor({ ...descriptor, database: 'orders' })).toBeUndefined(); expect( parseNativeDbIamDescriptor({ ...descriptor, host: descriptor.host.toUpperCase() }) ).toBeUndefined(); expect( parseNativeDbIamDescriptor({ ...descriptor, host: 'native-db.cluster-example.us-east-1.rds.amazonaws.com' }) ).toBeUndefined(); expect(parseNativeDbIamDescriptor({ ...descriptor, port: 0 })).toBeUndefined(); expect(parseNativeDbIamDescriptor({ ...descriptor, region: 'cn-north-1' })).toBeUndefined(); expect(parseNativeDbIamDescriptor({ ...descriptor, region: 'us-gov-west-1' })).toBeUndefined(); expect( parseNativeDbIamDescriptor({ ...descriptor, username: 'sbndb_0000000000000000_7bfeae1170db923274f1943f_runtime' }) ).toBeUndefined(); }); test('accepts standalone RDS IAM resource IDs', () => { expect( parseNativeDbIamDescriptor({ application_id: 'application-id', auth_descriptor_version: 3, auth_mode: 'aws_iam_role', aws_account_id: '123456789012', binding_id: 'binding-id', cluster_resource_id: 'db-ABCDEF0123456789-1', connector_role_arn: 'arn:aws:iam::123456789012:role/superblocks-native-db-connector-prod', database: 'sbndb_abcdef1234567890_0123456789abcdef01234567', host: 'native-db.example.us-west-2.rds.amazonaws.com', port: 5432, region: 'us-west-2', username: 'sbndb_abcdef1234567890_0123456789abcdef01234567_runtime' }) ).toBeDefined(); }); test('derives canonical IAM application token', async () => { await expect(computeNativeDbApplicationToken('application-id')).resolves.toBe('7bfeae1170db923274f1943f'); }); test('derives canonical IAM profile token', async () => { await expect(computeNativeDbProfileToken('production')).resolves.toHaveLength(16); await expect(computeNativeDbProfileToken('Production')).resolves.toBe(await computeNativeDbProfileToken('production')); }); });