Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
7b8bac4
Create db v2 component
mandryllo Dec 2, 2025
396e648
Remove ability to create custom parameter group
mandryllo Dec 5, 2025
b295e28
Cleanup
mandryllo Dec 5, 2025
35bc5cb
Fix kms key id type
mandryllo Dec 5, 2025
19c3ece
Cleanup
mandryllo Dec 5, 2025
ce83def
Cleanup vpc parameters
mandryllo Dec 5, 2025
292b549
Cleanup types
mandryllo Dec 5, 2025
d4120ce
Fix formatting
mandryllo Dec 5, 2025
b09a6c0
Cleanup
mandryllo Dec 8, 2025
53061fe
Create db builder component
mandryllo Dec 2, 2025
c32427b
Fix typo
mandryllo Dec 3, 2025
cb12a13
Remove custom parameter group args
mandryllo Dec 5, 2025
784af7f
Cleanup private props
mandryllo Dec 5, 2025
4df9363
Remove parameter group name from builder
mandryllo Dec 5, 2025
b439523
Cleanup naming
mandryllo Dec 5, 2025
0195797
Fix formatting
mandryllo Dec 5, 2025
2d399cf
Cleanup
mandryllo Dec 8, 2025
b17a54f
Expose db v2 components
mandryllo Dec 3, 2025
def9885
Add type tests
mandryllo Dec 8, 2025
9cc3856
Add default db tests
mandryllo Dec 8, 2025
bb298bc
Cleanup database types
mandryllo Dec 10, 2025
6af2d1b
Cleanup
mandryllo Dec 10, 2025
950fe1a
Revert allocatedStorage type to number
mandryllo Dec 10, 2025
ff389f9
Remove withKms method from builder
mandryllo Dec 10, 2025
817f100
Update type tests
mandryllo Dec 10, 2025
0605888
Update integration tests
mandryllo Dec 10, 2025
15927fe
fix tags
mandryllo Dec 10, 2025
f72366f
Fix snapshot id
mandryllo Dec 10, 2025
3b4d3e5
Remove export
mandryllo Dec 10, 2025
ef9fea4
Fix imports in tests
mandryllo Dec 10, 2025
c826e12
Fix wrong condition
mandryllo Dec 11, 2025
20a59e3
Cleanup final snapshots
mandryllo Dec 15, 2025
3648874
Fix getting snapshots
mandryllo Dec 15, 2025
3bba784
Update database builder
mandryllo Dec 16, 2025
3602506
Cleanup
mandryllo Dec 16, 2025
7822894
Fix type tests
mandryllo Dec 16, 2025
3107de3
Create db v2 component
mandryllo Dec 2, 2025
e0cae9b
Remove ability to create custom parameter group
mandryllo Dec 5, 2025
117e798
Cleanup
mandryllo Dec 5, 2025
7c2b479
Fix kms key id type
mandryllo Dec 5, 2025
06fe2d8
Cleanup
mandryllo Dec 5, 2025
17f5e9e
Cleanup vpc parameters
mandryllo Dec 5, 2025
f491332
Cleanup types
mandryllo Dec 5, 2025
9056cf8
Fix formatting
mandryllo Dec 5, 2025
75308bc
Cleanup
mandryllo Dec 8, 2025
3ba47f5
Create db builder component
mandryllo Dec 2, 2025
6b0648f
Fix typo
mandryllo Dec 3, 2025
65f030a
Remove custom parameter group args
mandryllo Dec 5, 2025
bfec1fc
Cleanup private props
mandryllo Dec 5, 2025
a48d054
Remove parameter group name from builder
mandryllo Dec 5, 2025
379dc30
Cleanup naming
mandryllo Dec 5, 2025
14d208b
Fix formatting
mandryllo Dec 5, 2025
c629546
Cleanup
mandryllo Dec 8, 2025
6906eb4
Cleanup database types
mandryllo Dec 10, 2025
8f5939e
Cleanup
mandryllo Dec 10, 2025
80804be
Revert allocatedStorage type to number
mandryllo Dec 10, 2025
98fb66b
Remove withKms method from builder
mandryllo Dec 10, 2025
5cfb8f9
fix tags
mandryllo Dec 10, 2025
823598e
Fix snapshot id
mandryllo Dec 10, 2025
9d7d045
Fix wrong condition
mandryllo Dec 11, 2025
1937146
Update database builder
mandryllo Dec 16, 2025
311d792
Cleanup
mandryllo Dec 16, 2025
b43859c
Move applyImmediately to instance config
mandryllo Dec 16, 2025
62b03d9
Update builder methods
mandryllo Dec 16, 2025
e8a017b
Update tests
mandryllo Dec 16, 2025
f9fdc02
Move kms key from storage type
mandryllo Dec 16, 2025
6467dff
Update builder
mandryllo Dec 16, 2025
3375e86
Cleanup default tests
mandryllo Dec 16, 2025
dce0103
Fix allocated storage type
mandryllo Dec 16, 2025
a783195
Revert "Fix allocated storage type"
mandryllo Dec 16, 2025
41783a5
Update after merge
mandryllo Dec 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,932 changes: 1,696 additions & 236 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
"@aws-sdk/client-efs": "^3.758.0",
"@aws-sdk/client-elastic-load-balancing-v2": "^3.764.0",
"@aws-sdk/client-elasticache": "^3.901.0",
"@aws-sdk/client-kms": "^3.943.0",
"@aws-sdk/client-rds": "^3.943.0",
"@aws-sdk/client-route-53": "^3.782.0",
"@aws-sdk/client-secrets-manager": "^3.906.0",
"@aws-sdk/client-servicediscovery": "^3.758.0",
Expand Down
85 changes: 85 additions & 0 deletions tests/build/index.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { describe, expect, it } from 'tstyche';
import { next as studion } from '@studion/infra-code-blocks';
import { OtelCollector } from '../../dist/v2/otel';
import { OtelCollectorBuilder } from '../../dist/v2/otel/builder';
import { Database } from '../../dist/v2/components/database';
import { DatabaseBuilder } from '../../dist/v2/components/database/builder';

describe('Build output', () => {
describe('ECS Service', () => {
Expand Down Expand Up @@ -258,4 +260,87 @@ describe('Build output', () => {
});
});
});

describe('Database', () => {
it.skip('should export Database', () => {
expect(studion).type.toHaveProperty('Database');
});

it.skip('should export DatabaseBuilder', () => {
expect(studion).type.toHaveProperty('DatabaseBuilder');
});

describe('Instantiation', () => {
it('should construct Database', () => {
expect(Database).type.toBeConstructableWith('db-test', {
vpc: new awsx.ec2.Vpc('vpcName'),
dbName: 'dbName',
username: 'username',
});
});

it('should construct DatabaseBuilder', () => {
expect(DatabaseBuilder).type.toBeConstructableWith('db-test');
});
});

describe('Builder', () => {
const builder = new DatabaseBuilder('db-test');

it('should have build method', () => {
expect(builder.build).type.toBeCallableWith();
});

it('should have withInstance method', () => {
expect(builder.withInstance).type.toBeCallableWith({
dbName: 'dbName',
});
});

it('should have withCredentials method', () => {
expect(builder.withCredentials).type.toBeCallableWith({
username: 'username',
password: 'password',
});
});

it('should have withStorage method', () => {
expect(builder.withStorage).type.toBeCallableWith({
allocatedStorage: 50,
maxAllocatedStorage: 200,
});
});

it('should have withVpc method', () => {
expect(builder.withVpc).type.toBeCallableWith(
new awsx.ec2.Vpc('vpcName'),
);
});

it('should have withMonitoring method', () => {
expect(builder.withMonitoring).type.toBeCallableWith();
});

it('should have withSnapshot method', () => {
expect(builder.withSnapshot).type.toBeCallableWith('snapshot-id');
});

it('should have withKms method', () => {
expect(builder.withKms).type.toBeCallableWith('kms-key-id');
});

it('should have withParameterGroup method', () => {
expect(builder.withParameterGroup).type.toBeCallableWith(
'parameter-group-name',
);
});

it('should have withTags method', () => {
expect(builder.withTags).type.toBeCallableWith({
Project: 'db-test',
Environment: 'dev',
});
});
});
});
});
198 changes: 198 additions & 0 deletions tests/database/default-db.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import {
DescribeKeyCommand,
GetKeyRotationStatusCommand,
} from '@aws-sdk/client-kms';
import {
DescribeSecurityGroupsCommand,
IpPermission,
} from '@aws-sdk/client-ec2';
import * as assert from 'node:assert';
import { DatabaseTestContext } from './test-context';
import { DescribeDBSubnetGroupsCommand } from '@aws-sdk/client-rds';
import { it } from 'node:test';

export function testDefaultDb(ctx: DatabaseTestContext) {
it('should create a database and all other resources', () => {
const database = ctx.outputs.defaultDb.value;

assert.ok(database, 'Database should be defined');
assert.strictEqual(
database.name,
`${ctx.config.appName}-default`,
'Database should have correct name',
);

assert.ok(database.instance, 'Db instance should be defined');
assert.ok(database.dbSecurityGroup, 'Db security group should be defined');
assert.ok(database.dbSubnetGroup, 'Db subnet group should be defined');
assert.ok(database.kmsKeyId, 'Kms key id should be defined');
assert.ok(database.password, 'Password should be defined');
});

it('should create database instance with correct default configuration', () => {
const instance = ctx.outputs.defaultDb.value.instance;

assert.strictEqual(
instance.dbName,
ctx.config.dbName,
'Database instance should have correct dbName',
);
assert.strictEqual(
instance.masterUsername,
ctx.config.dbUsername,
'Database instance should have correct master username',
);

assert.strictEqual(
instance.multiAz,
false,
'Multi-AZ argument should be set to false',
);
assert.strictEqual(
instance.applyImmediately,
false,
'Apply immediately argument should be set to false',
);
assert.strictEqual(
instance.allocatedStorage,
'20',
'Allocated storage argument should be set to 20',
);
assert.strictEqual(
instance.maxAllocatedStorage,
100,
'Max allocated storage argument should be set to 100',
);
assert.strictEqual(
instance.dbInstanceClass,
'db.t4g.micro',
'DB instance class argument should be set to db.t4g.micro',
);
assert.strictEqual(
instance.enablePerformanceInsights,
false,
'Enable performance insights argument should be set to false',
);
assert.strictEqual(
instance.allowMajorVersionUpgrade,
false,
'Allow major version upgrade argument should be set to false',
);
assert.strictEqual(
instance.autoMinorVersionUpgrade,
true,
'Auto minor version upgrade argument should be set to true',
);
assert.strictEqual(
instance.engineVersion,
'17.2',
'Engine version argument should be set to 17.2',
);
assert.strictEqual(
instance.engine,
'postgres',
'Engine argument should be set to postgres',
);
assert.strictEqual(
instance.storageEncrypted,
true,
'Storage encrypted argument should be set to true',
);
assert.strictEqual(
instance.publiclyAccessible,
false,
'Publicly accessible argument should be set to false',
);
});

it('should create subnet group in the correct VPC', async () => {
const database = ctx.outputs.defaultDb.value;
const vpc = ctx.outputs.vpc.value;
const dbSubnetGroupName = database.dbSubnetGroup.name;

const command = new DescribeDBSubnetGroupsCommand({
DBSubnetGroupName: dbSubnetGroupName,
});

const { DBSubnetGroups } = await ctx.clients.rds.send(command);
assert.ok(
DBSubnetGroups && DBSubnetGroups.length > 0,
'DB subnet groups should exist',
);
const [subnetGroup] = DBSubnetGroups;
assert.strictEqual(
subnetGroup.VpcId,
vpc.vpc.vpcId,
'DB subnet group should be in the correct VPC',
);
assert.ok(
subnetGroup.Subnets && subnetGroup.Subnets.length > 0,
'DB subnet group should have subnets',
);
});

it('should create a security group with correct ingress rules', async () => {
const database = ctx.outputs.defaultDb.value;
const vpc = ctx.outputs.vpc.value;
const dbSecurityGroupId = database.dbSecurityGroup.id;

const command = new DescribeSecurityGroupsCommand({
GroupIds: [dbSecurityGroupId],
});
const { SecurityGroups } = await ctx.clients.ec2.send(command);
assert.ok(
SecurityGroups && SecurityGroups.length > 0,
'DB security groups should exist',
);
const [securityGroup] = SecurityGroups;
assert.strictEqual(
securityGroup.VpcId,
vpc.vpc.vpcId,
'DB security group should be in the correct VPC',
);

const postgresRule = securityGroup.IpPermissions?.find(
(rule: IpPermission) => rule.FromPort === 5432 && rule.ToPort === 5432,
);
assert.ok(postgresRule, 'Should have postgres port 5432 ingress rule');
assert.strictEqual(
postgresRule.IpProtocol,
'tcp',
'Should allow TCP protocol',
);
});

it('should create a correctly configured RDS KMS key', async () => {
const database = ctx.outputs.defaultDb.value;
const kmsKeyId = database.kmsKeyId;

const describeCommand = new DescribeKeyCommand({
KeyId: kmsKeyId,
});
const { KeyMetadata } = await ctx.clients.kms.send(describeCommand);
assert.strictEqual(
KeyMetadata?.KeySpec,
'SYMMETRIC_DEFAULT',
'KMS key should use SYMMETRIC_DEFAULT spec',
);
assert.strictEqual(
KeyMetadata.KeyUsage,
'ENCRYPT_DECRYPT',
'KMS key should be used for encryption/decryption',
);
assert.strictEqual(KeyMetadata.Enabled, true, 'KMS key should be enabled');
assert.strictEqual(
KeyMetadata.MultiRegion,
false,
'KMS key should not be multi-region',
);

const rotationCmd = new GetKeyRotationStatusCommand({ KeyId: kmsKeyId });
const { KeyRotationEnabled } = await ctx.clients.kms.send(rotationCmd);
assert.strictEqual(
KeyRotationEnabled,
true,
'KMS key rotation should be enabled',
);
});
}
39 changes: 39 additions & 0 deletions tests/database/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, before, after } from 'node:test';
import * as automation from '../automation';
import { cleanupSnapshots } from './utils/cleanup-snapshots';
import * as config from './infrastructure/config';
import { DatabaseTestContext } from './test-context';
import { EC2Client } from '@aws-sdk/client-ec2';
import { InlineProgramArgs } from '@pulumi/pulumi/automation';
import { KMSClient } from '@aws-sdk/client-kms';
import { RDSClient } from '@aws-sdk/client-rds';
import { requireEnv } from '../util';
import { testDefaultDb } from './default-db.test';

const programArgs: InlineProgramArgs = {
stackName: 'dev',
projectName: 'icb-test-database',
program: () => import('./infrastructure'),
};

const region = requireEnv('AWS_REGION');
const ctx: DatabaseTestContext = {
outputs: {},
config,
clients: {
rds: new RDSClient({ region }),
ec2: new EC2Client({ region }),
kms: new KMSClient({ region }),
},
};

describe('Database component deployment', () => {
before(async () => {
ctx.outputs = await automation.deploy(programArgs);
});

after(() => automation.destroy(programArgs));

describe('Default database', () => testDefaultDb(ctx));
after(() => cleanupSnapshots(ctx));
});
3 changes: 3 additions & 0 deletions tests/database/infrastructure/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const appName = 'db-test';
export const dbName = 'dbname';
export const dbUsername = 'dbusername';
17 changes: 17 additions & 0 deletions tests/database/infrastructure/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { next as studion } from '@studion/infra-code-blocks';
import { DatabaseBuilder } from '../../../dist/v2/components/database/builder';
import * as config from './config';

const vpc = new studion.Vpc(`${config.appName}-vpc`, {});

const defaultDb = new DatabaseBuilder(`${config.appName}-default`)
.withInstance({
dbName: config.dbName,
})
.withCredentials({
username: config.dbUsername,
})
.withVpc(vpc.vpc)
.build();

export { vpc, defaultDb };
31 changes: 31 additions & 0 deletions tests/database/test-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { EC2Client } from '@aws-sdk/client-ec2';
import { KMSClient } from '@aws-sdk/client-kms';
import { OutputMap } from '@pulumi/pulumi/automation';
import { RDSClient } from '@aws-sdk/client-rds';

interface ConfigContext {
config: DatabaseTestConfig;
}

interface DatabaseTestConfig {
appName: string;
dbName: string;
dbUsername: string;
}

interface PulumiProgramContext {
outputs: OutputMap;
}

interface AwsContext {
clients: {
rds: RDSClient;
ec2: EC2Client;
kms: KMSClient;
};
}

export interface DatabaseTestContext
extends ConfigContext,
PulumiProgramContext,
AwsContext {}
Loading