From 7d2199a3f3858647f24c48806a0f474454457b65 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Fri, 8 May 2026 11:48:39 +0300 Subject: [PATCH] =?UTF-8?q?fix(lineage):=203=20e2e=20CI=20failures=20?= =?UTF-8?q?=E2=80=94=20Liquibase=20XSD=20+=20JSONB=20=3F-op=20+=20size=20c?= =?UTF-8?q?ap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 e2e suite (skipped в local mvn verify, гонится только на CI с testcontainers) поймал 3 issues. Все исправлены, e2e теперь 20/20 PASS локально (Postgres testcontainer): - AuthE2ETest 6/6 - AutoDeriveGeometryE2ETest 6/6 - BitemporalE2ETest 4/4 - OutboxKafkaE2ETest 1/1 - SmokeE2ETest 1/1 - WebhookE2ETest 2/2 Fix #1 — Liquibase XSD ordering (0016 changeset): - должен идти ПОСЛЕ . Liquibase XSD строгий: preConditions → comment → operations. - "Invalid content was found starting with element preConditions" на startup → ApplicationContext fail → весь webserver не поднимается. Fix #2 — JSONB ?-operator vs JDBC parameter substitution (0016): - Postgres JDBC PreparedStatement обрабатывает любой `?` как $N placeholder. SQL "prop.value ? 'x-references'" → "prop.value $1 'x-references'" → "syntax error at or near $1". Same для regex с $ anchor. - Replaced `?` operator на эквивалент `-> 'key' IS NOT NULL`, drop-or-replace regex anchors на `LIKE '%.%'` (split_part guards malformed values gracefully). - Comment в migration документирует ловушку для будущих changesets. Fix #3 — service size cap mismatch (LineageIndexService): - CascadeCloseService.evaluatePlan вызывает findRecordDependents с size=CASCADE_MAX_PER_REQUEST=500. Но findRecordDependents enforced size 1..200 → IllegalArgumentException → 400 на DELETE record path. - Bumped service cap до 500. Controller отдельно держит public cap=200 (UI не нуждается в больше). - Test updated: expects "size 1..500". Lessons learned: - Local pre-push discipline должна включать `mvn -P e2e` для миграций. Не запустил, потому что: e2e требует Docker, медленнее, не было в routine. Now corrected — будущие migration changes будут идти через e2e check. - Liquibase JDBC рабочий принцип: SQL прогоняется через PreparedStatement → любой `?` или `$N` ловится. Documenting в migration comment. Verify: - mvn -pl ordinis-app -am -P e2e test: BUILD SUCCESS, 20/20 PASS (28s). - mvn test: 106 unit tests + 20 e2e — все green. --- .../changes/0016-record-dependents-index.xml | 16 ++++++++++++---- .../service/reference/LineageIndexService.java | 7 +++++-- .../reference/LineageIndexServiceTest.java | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ordinis-migrations/src/main/resources/db/changelog/changes/0016-record-dependents-index.xml b/ordinis-migrations/src/main/resources/db/changelog/changes/0016-record-dependents-index.xml index 383c0ff..4459b59 100644 --- a/ordinis-migrations/src/main/resources/db/changelog/changes/0016-record-dependents-index.xml +++ b/ordinis-migrations/src/main/resources/db/changelog/changes/0016-record-dependents-index.xml @@ -29,7 +29,6 @@ pure JSONB indexing (jsonb_each + ?-marker) — не ожидаем больше. --> - MV record_dependents_index: precomputed reverse FK @@ -37,6 +36,7 @@ + MV record_dependents_index: precomputed reverse FK 'properties') AS prop(key, value) ON true - WHERE prop.value ? 'x-references' - AND prop.value->>'x-references' ~ '^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$' - AND r.data ? prop.key + -- Note: избегаем JSONB ?-operator — Liquibase прогоняет SQL через + -- PreparedStatement, который любой ? интерпретирует как $N + -- placeholder → "syntax error at $1". Заменяем на эквиваленты: + -- prop.value ? 'x-references' → prop.value -> 'x-references' IS NOT NULL + -- r.data ? prop.key → r.data -> prop.key IS NOT NULL + -- Регекс с $ anchor по той же причине заменён на LIKE — split_part'ы + -- ниже сами справятся с malformed (вернут empty strings, никогда не + -- match-ятся с real records). + WHERE prop.value -> 'x-references' IS NOT NULL + AND prop.value->>'x-references' LIKE '%.%' + AND r.data -> prop.key IS NOT NULL AND jsonb_typeof(r.data -> prop.key) = 'string' WITH NO DATA; ]]> diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexService.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexService.java index fb66efc..97564c4 100644 --- a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexService.java +++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexService.java @@ -209,8 +209,11 @@ public class LineageIndexService { int page, int size) { if (page < 0) throw new IllegalArgumentException("page must be >= 0"); - if (size < 1 || size > 200) { - throw new IllegalArgumentException("size must be 1..200, got " + size); + // Service-level cap = 500 (CascadeCloseService запрашивает up to + // CASCADE_MAX_PER_REQUEST). Controller separately enforces 200 cap для + // public endpoint — UI не нуждается в больше. + if (size < 1 || size > 500) { + throw new IllegalArgumentException("size must be 1..500, got " + size); } Optional targetOpt = definitionRepository.findByName(targetDictName); diff --git a/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexServiceTest.java b/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexServiceTest.java index 03b403d..37b9c02 100644 --- a/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexServiceTest.java +++ b/ordinis-rest-api/src/test/java/cloud/nstart/terravault/ordinis/restapi/service/reference/LineageIndexServiceTest.java @@ -206,7 +206,7 @@ class LineageIndexServiceTest { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("size"); assertThatThrownBy(() -> service.findRecordDependents( - "d", "k", Set.of(DataScope.PUBLIC), 0, 201)) + "d", "k", Set.of(DataScope.PUBLIC), 0, 501)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("size"); }