From 581dc1ad13e34039e8ddecb47b74f99c526bbb1b Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 26 Apr 2026 16:13:50 +0200 Subject: [PATCH] =?UTF-8?q?test(license):=20SchemaBootstrapIT=20=E2=80=94?= =?UTF-8?q?=20assert=20V5=20license=20+=20retention=20columns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new assertions: license table has tenant_id/license_id/token/ installed_at/installed_by/expires_at/last_validated_at columns with expected types + NOT NULL constraints, PK on tenant_id; environments has execution_retention_days/log_retention_days/metric_retention_days all integer NOT NULL DEFAULT 1. Note: V5 migration does not include an installed_via column; the plan's spec was aspirational. Test asserts what the migration actually creates (and what PostgresLicenseRepository reads/writes). OpenAPI regen (Step 35.2) deferred to session end — requires running backend + UI dev server. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../alerting/storage/SchemaBootstrapIT.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) 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();