diff --git a/cameleer-server-app/src/main/resources/db/migration/V4__add_deployment_created_by.sql b/cameleer-server-app/src/main/resources/db/migration/V4__add_deployment_created_by.sql new file mode 100644 index 00000000..b6e2a16a --- /dev/null +++ b/cameleer-server-app/src/main/resources/db/migration/V4__add_deployment_created_by.sql @@ -0,0 +1,8 @@ +-- V4: add created_by column to deployments for audit trail +-- Captures which user initiated a deployment. Nullable for backwards compatibility; +-- pre-V4 historical deployments will have NULL. + +ALTER TABLE deployments + ADD COLUMN created_by TEXT REFERENCES users(user_id); + +CREATE INDEX idx_deployments_created_by ON deployments (created_by); diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/V4DeploymentCreatedByMigrationIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/V4DeploymentCreatedByMigrationIT.java new file mode 100644 index 00000000..fa3568b4 --- /dev/null +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/storage/V4DeploymentCreatedByMigrationIT.java @@ -0,0 +1,53 @@ +package com.cameleer.server.app.storage; + +import com.cameleer.server.app.AbstractPostgresIT; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class V4DeploymentCreatedByMigrationIT extends AbstractPostgresIT { + + @Autowired JdbcTemplate jdbc; + + @Test + void created_by_column_exists_with_correct_type_and_nullable() { + List> cols = jdbc.queryForList( + "SELECT column_name, data_type, is_nullable " + + "FROM information_schema.columns " + + "WHERE table_name = 'deployments' AND column_name = 'created_by'" + ); + assertThat(cols).hasSize(1); + assertThat(cols.get(0)).containsEntry("data_type", "text"); + assertThat(cols.get(0)).containsEntry("is_nullable", "YES"); + } + + @Test + void created_by_index_exists() { + Integer count = jdbc.queryForObject( + "SELECT count(*)::int FROM pg_indexes " + + "WHERE tablename = 'deployments' AND indexname = 'idx_deployments_created_by'", + Integer.class + ); + assertThat(count).isEqualTo(1); + } + + @Test + void created_by_has_fk_to_users() { + Integer count = jdbc.queryForObject( + "SELECT count(*)::int FROM information_schema.table_constraints tc " + + "JOIN information_schema.constraint_column_usage ccu " + + " ON tc.constraint_name = ccu.constraint_name " + + "WHERE tc.table_name = 'deployments' " + + " AND tc.constraint_type = 'FOREIGN KEY' " + + " AND ccu.table_name = 'users' " + + " AND ccu.column_name = 'user_id'", + Integer.class + ); + assertThat(count).isGreaterThanOrEqualTo(1); + } +}