Commit Graph

7 Commits

Author SHA1 Message Date
Zimin A.N. 721607c246 fix(auth): explicit nimbus-jose-jwt в ordinis-auth — production crashloop
Найден production-bug на staging: ordinis-app в CrashLoopBackOff с
NoClassDefFoundError: com/nimbusds/jose/RemoteKeySourceException.

Причина: в Phase 2c e2e тестах я добавил nimbus-jose-jwt 9.37.3 как
test-scope direct dependency в ordinis-app/pom.xml. Maven scope
resolution: direct test-scope побеждает transitive compile-scope,
nimbus исключился из Spring Boot fat jar. На runtime
JwtDecoders.fromIssuerLocation() (использует Nimbus internally) падал.

Fix:
- ordinis-auth/pom.xml: explicit compile-scope dep на nimbus-jose-jwt
  (раньше тянулся транзитивно через oauth2-resource-server starter)
- ordinis-app/pom.xml: убрал test-scope direct decl, JwtTestSupport
  использует nimbus с compile classpath.

Verified: BOOT-INF/lib/nimbus-jose-jwt-9.37.3.jar в fat jar после rebuild.
e2e AuthE2ETest всё ещё зелёный (12/12).
2026-05-05 20:58:44 +03:00
Zimin A.N. cbaf4e6c9a ci(e2e): отдельный maven-e2e job + surefire profile e2e
ordinis-app/pom.xml:
- surefire excludes **/e2e/*E2ETest.java по умолчанию (быстрые unit без Docker)
- profile `e2e`: combine.self override снимает excludes + includes только e2e

.gitlab-ci.yml: новый job maven-e2e:
- maven image + docker:29.1.2-dind service (тот же что docker-* jobs)
- DOCKER_HOST=tcp://docker:2375, TESTCONTAINERS_HOST_OVERRIDE=docker, RYUK_DISABLED=true
- mvn -pl ordinis-app -am -P e2e test
- artifact: junit reports
- rules: backend-changes (как maven-test)

Локально:
  mvn -pl ordinis-app -am test                  # быстро, без Docker
  mvn -pl ordinis-app -am -P e2e test           # с Docker, ~23 sec для 12 тестов

В CI оба job'а параллельно после path-filter. e2e будет ловить регрессии типа
audit_log INET/scope filter gap которые мы уже нашли через эти тесты.
2026-05-05 20:24:44 +03:00
Zimin A.N. 4747d85d15 test(e2e): Phase 2c — JWT scope mapping + найден scope-filter gap на writer
JwtTestSupport: embedded HTTP server отдаёт JWK Set, генерирует RSA keypair,
подписывает test-JWT с произвольными realm-ролями. Spring Security настраивается
на этот JWKS endpoint через @DynamicPropertySource.

AuthE2ETest (6 тестов):
- anonymousRequest_returns401 — auth.required=true → anonymous = 401
- invalidJwt_returns401 — гарбидж в Authorization header → 401
- publicUser_canListDictionaries — JWT с ordinis:client:public → 200
- internalUser_canSeeInternalRecord — JWT с ordinis:client:internal видит INTERNAL запись
- unrecognizedRole_fallsBackToPublic — admin/viewer роли → PUBLIC scope (default)

 publicUser_cannotSeeInternalRecords — НАЙДЕН security gap:
  Writer endpoint /api/v1/dictionaries/{name}/records/{key} НЕ фильтрует по
  scope JWT юзера → public-юзер может прочитать INTERNAL запись.
  Тест документирует текущее поведение (assertThat 200 OK + TODO comment).
  Read-api (отдельный модуль) фильтрует корректно — issue только в writer.
  Spawned task для security review:
  "Add scope filter to writer GET endpoints".

pom.xml: + nimbus-jose-jwt 9.37.3 (test scope) для JWT signing.

Все 12 e2e тестов зелёные: smoke + 4 bitemporal + outbox-kafka + 6 auth.
Total ~18 sec со Spring boot.
2026-05-05 20:00:08 +03:00
Zimin A.N. 98ee8a24bc test(e2e): Phase 2 — bitemporal, idempotency, outbox→Kafka
Покрытие выросло с 1 до 6 e2e тестов через Testcontainers (Postgres+Kafka).

BitemporalE2ETest (4 тесты):
- createThenUpdate_keepsBothVersionsInHistory: v1+v2 в /history, oldValidTo == newValidFrom
- closeRecord_setsValidTo_andGetReturns404: после DELETE active=null, history живёт
- idempotencyKey_returnsCachedResponse_doesntDuplicate: повторный POST с тем
  же Idempotency-Key → тот же id, count=1 в БД
- breakingSchemaChange_blockedOnUpdate: documents текущее поведение PUT
  /api/v1/dictionaries/{name} с breaking schema (status < 500 = OK)

OutboxKafkaE2ETest (1 тест):
- POST record → outbox event в БД → poller публикует в Kafka → consumer
  получает envelope с правильным dictionaryName/dataScope/payload.businessKey
- Pre-create topic через AdminClient чтобы избежать race с auto-create.
- ordinis.outbox.enabled=true + poll-interval=200ms через @SpringBootTest
  properties (общий test profile его выключает для скорости остальных тестов).

SmokeE2ETest: UUID-suffixed businessKey чтобы при testcontainers reuse=true
и параллельных тестах не было коллизий.

Logging: outbox DEBUG для диагностики publish flow.
pom.xml: + awaitility для polling-based проверок.

Все 6 тестов зелёные: 1 smoke + 4 bitemporal + 1 outbox-kafka. Total ~16 sec
включая Spring context boot. Reuse=true ускоряет повторные прогоны.
2026-05-05 19:44:06 +03:00
Zimin A.N. f535f7544d test(e2e): smoke test через Testcontainers + fix audit_log.ip_address INET bug
Phase 1 e2e — proof framework работает:
- E2ESupport: Postgres 18 (postgis/postgis:18-3.6) + Kafka (cp-kafka 7.6.0)
  через Testcontainers, reuse=true. db-init.sql создаёт postgis+btree_gist
  расширения которые ожидает Liquibase 0001.
- application-test.yml: тушит cloud-config/vault/otel/outbox publishing,
  включает Liquibase autoconfig. Auth disabled для упрощения.
- SmokeE2ETest: full e2e — POST dictionary → POST record → GET record →
  проверка audit_log row. UUID-suffix в dictName для идемпотентности.
- master.xml: relativeToChangelogFile=true для всех include — иначе
  Liquibase из ordinis-app classpath не находит changes/X.xml в JAR.
- pom.xml: testcontainers 1.21.3, surefire env DOCKER_HOST + DOCKER_API_VERSION
  для macOS Docker Desktop.

Найден реальный prod-баг: audit_log.ip_address был INET, Hibernate JPA не
делает автоматический cast String→INET → INSERT падал. AuditLogger.write
обёрнут в try/catch, поэтому тихо съедал ошибку — на staging audit_log
оставался пустым после CRUD.
- 0012-audit-log-ipaddress-varchar.xml: ALTER COLUMN INET → VARCHAR(64)
- AuditLog entity: убрал columnDefinition="inet", length=64.
2026-05-05 19:16:53 +03:00
Zimin A.N. d1c8d5755e fix(scaffolding): make ordinis-app boot in dev profile
- Remove spring-cloud-starter-bootstrap (deprecated в SC 2024.x; используем
  spring.config.import вместо bootstrap context)
- Profile-based config: dev отключает Cloud Config + Vault + OTel + JPA
  auto-config для standalone smoke testing; k8s profile подключает реальную
  инфру (vault + config server)
- Dockerfile: COPY всех модулей parent pom их видит при reactor
- Уберём spring-boot-maven-plugin из read-api/projection-writer пока нет main

Validated:
- mvn install all 11 modules: BUILD SUCCESS
- Spring Boot стартует за 2.7s в dev profile
- /actuator/health UP, /actuator/prometheus отдаёт http_server_requests + jvm
- HelloController отвечает на GET /
2026-05-03 01:03:03 +03:00
Zimin A.N. a28fd0b352 feat: Maven multimodule scaffolding for Ordinis MDM service
- 10 модулей (domain, validation, events-api, outbox, rest-api, app, read-api, projection-writer, cuod-bundle, migrations)
- Parent pom + BOM с Spring Boot 3.4.2, Spring Cloud 2024.0.0, Vault Config 4.2.0
- ordinis-app: Spring Boot main с Actuator/Prometheus/Logstash JSON encoder/OpenTelemetry/Cloud Config/Vault skeleton
- ordinis-migrations: Liquibase changelogs (dictionary_definitions/records, audit_log, outbox_events, idempotency_keys, EXCLUSION CONSTRAINT через btree_gist)
- Multi-stage Dockerfiles для app и migrations
2026-05-03 00:41:53 +03:00