diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/SchemaBootstrapIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/SchemaBootstrapIT.java index 4ec6fe08..dffce3f1 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/SchemaBootstrapIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/SchemaBootstrapIT.java @@ -115,6 +115,91 @@ class SchemaBootstrapIT extends AbstractPostgresIT { assertThat(isUnique).isTrue(); } + @Test + void licenseTableExists() { + // V5 migration: per-tenant license row, PK on tenant_id (one server = one tenant). + var rows = jdbcTemplate.queryForList(""" + SELECT column_name, data_type, is_nullable + FROM information_schema.columns + WHERE table_name = 'license' + AND table_schema = current_schema() + """); + var byName = new java.util.HashMap>(); + for (var row : rows) { + byName.put((String) row.get("column_name"), row); + } + + assertThat(byName).containsKeys( + "tenant_id", "license_id", "token", "installed_at", + "installed_by", "expires_at", "last_validated_at"); + + assertThat(byName.get("tenant_id").get("data_type")).isEqualTo("text"); + assertThat(byName.get("tenant_id").get("is_nullable")).isEqualTo("NO"); + + assertThat(byName.get("license_id").get("data_type")).isEqualTo("uuid"); + assertThat(byName.get("license_id").get("is_nullable")).isEqualTo("NO"); + + assertThat(byName.get("token").get("data_type")).isEqualTo("text"); + assertThat(byName.get("token").get("is_nullable")).isEqualTo("NO"); + + assertThat(byName.get("installed_at").get("data_type")) + .isEqualTo("timestamp with time zone"); + assertThat(byName.get("installed_at").get("is_nullable")).isEqualTo("NO"); + + assertThat(byName.get("installed_by").get("data_type")).isEqualTo("text"); + assertThat(byName.get("installed_by").get("is_nullable")).isEqualTo("NO"); + + assertThat(byName.get("expires_at").get("data_type")) + .isEqualTo("timestamp with time zone"); + assertThat(byName.get("expires_at").get("is_nullable")).isEqualTo("NO"); + + assertThat(byName.get("last_validated_at").get("data_type")) + .isEqualTo("timestamp with time zone"); + assertThat(byName.get("last_validated_at").get("is_nullable")).isEqualTo("NO"); + + // PK: tenant_id (one row per tenant). + var pkCols = jdbcTemplate.queryForList(""" + SELECT a.attname AS column_name + FROM pg_index i + JOIN pg_class c ON c.oid = i.indrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey) + WHERE c.relname = 'license' + AND n.nspname = current_schema() + AND i.indisprimary + """, String.class); + assertThat(pkCols).containsExactly("tenant_id"); + } + + @Test + void environmentsHasRetentionColumns() { + // V5 migration adds three retention day columns, NOT NULL DEFAULT 1. + var rows = jdbcTemplate.queryForList(""" + SELECT column_name, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_name = 'environments' + AND table_schema = current_schema() + AND column_name IN + ('execution_retention_days','log_retention_days','metric_retention_days') + """); + var byName = new java.util.HashMap>(); + for (var row : rows) { + byName.put((String) row.get("column_name"), row); + } + assertThat(byName).containsKeys( + "execution_retention_days", "log_retention_days", "metric_retention_days"); + + for (var col : java.util.List.of( + "execution_retention_days", "log_retention_days", "metric_retention_days")) { + assertThat(byName.get(col).get("data_type")) + .as("%s data_type", col).isEqualTo("integer"); + assertThat(byName.get(col).get("is_nullable")) + .as("%s is_nullable", col).isEqualTo("NO"); + assertThat((String) byName.get(col).get("column_default")) + .as("%s column_default", col).isEqualTo("1"); + } + } + @Test void deleting_environment_cascades_alerting_rows() { testEnvId = UUID.randomUUID();