feat(approval): Approval Workflow v2 Phase 3 — e2e suite + 0020 case fix
Phase 3 e2e coverage (8 tests, ApprovalWorkflowE2ETest):
- happyFlow: maker submit → checker approve → live record + outbox event
- rejectFlow: keeps draft REJECTED, no live record, re-approve blocked
- selfApprove: same JWT subject blocked (self_approve_forbidden / self_reject_forbidden)
- twoPendingDrafts: exclusion constraint → 409 draft_already_pending
- directCRUD on approval_required dict: POST/PUT/DELETE → 409 draft_required (D2=A)
- regression: dict без approval_required — direct CRUD по-прежнему работает
- withdrawByMaker: maker может, reviewer не может (403 not_draft_maker)
- rejectWithoutComment: 400 reject_reason_required
E2e exposed real Phase 0 bug:
Hibernate @Enumerated(EnumType.STRING) пишет UPPERCASE ('PENDING'), а
Liquibase 0019 check constraint + exclusion WHERE использовали lowercase
('pending'). Каждый insert проваливался на check, маппился в
DataIntegrityViolation, и DraftService.submit маскировал его в
draft_already_pending — скрывая реальную причину.
Fixes:
1. Migration 0020: пересоздание record_drafts_status_check + exclusion
с UPPERCASE значениями (matches enum serialization).
2. DraftService.submit: только SQLSTATE 23P01 (exclusion violation)
маппится в draft_already_pending. Остальные DataIntegrity (check, FK,
NOT NULL) пробрасываются как-есть — иначе schema bugs продолжат
маскироваться.
3. DraftServiceTest StubDraftRepo: оборачивает SQLException 23P01 в
DataIntegrityViolation cause chain — отражает прод поведение.
Test counts:
- ordinis-rest-api unit: 119/119 PASS
- ordinis-app e2e: 28/28 PASS (был 20, +8 новых ApprovalWorkflowE2ETest)
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<!--
|
||||
Approval Workflow v2 — Phase 3 fix.
|
||||
|
||||
BUG (exposed by e2e): Hibernate с @Enumerated(EnumType.STRING) пишет значения
|
||||
enum'а в UPPER CASE (PENDING, APPROVED, REJECTED, WITHDRAWN). А Liquibase 0019
|
||||
объявил check constraint + exclusion WHERE с lowercase ('pending'). Любой
|
||||
insert PENDING draft'а проваливал check constraint, маппился в
|
||||
DataIntegrityViolationException, и DraftService.submit ловил это как
|
||||
draft_already_pending — маскируя реальную причину.
|
||||
|
||||
Fix: переписать ограничения с UPPERCASE значениями (matches Java enum norm).
|
||||
Существующих PENDING-row быть не должно (insert никогда не проходил), но на
|
||||
всякий случай конвертируем lowercase → UPPERCASE до пересоздания constraint'ов.
|
||||
-->
|
||||
<changeSet id="0020-1-normalize-existing-status" author="ordinis">
|
||||
<comment>Normalize any prior lowercase status values to UPPERCASE before recreate.</comment>
|
||||
<sql>UPDATE record_drafts SET status = UPPER(status) WHERE status != UPPER(status);</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0020-2-fix-status-check" author="ordinis">
|
||||
<comment>Recreate status check constraint with UPPERCASE values matching enum.</comment>
|
||||
<sql>ALTER TABLE record_drafts DROP CONSTRAINT IF EXISTS record_drafts_status_check;</sql>
|
||||
<sql>
|
||||
ALTER TABLE record_drafts
|
||||
ADD CONSTRAINT record_drafts_status_check
|
||||
CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED', 'WITHDRAWN'));
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0020-3-fix-exclusion-where" author="ordinis">
|
||||
<comment>Recreate exclusion WHERE clause to use 'PENDING' (matches enum serialization).</comment>
|
||||
<sql>ALTER TABLE record_drafts DROP CONSTRAINT IF EXISTS record_drafts_one_pending_per_key;</sql>
|
||||
<sql>
|
||||
ALTER TABLE record_drafts
|
||||
ADD CONSTRAINT record_drafts_one_pending_per_key
|
||||
EXCLUDE USING btree (
|
||||
dictionary_id WITH =,
|
||||
business_key WITH =
|
||||
) WHERE (status = 'PENDING');
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -29,5 +29,6 @@
|
||||
<include file="changes/0017-redis-projection-flag.xml" relativeToChangelogFile="true"/>
|
||||
<include file="changes/0018-trgm-search-index.xml" relativeToChangelogFile="true"/>
|
||||
<include file="changes/0019-approval-workflow-v2.xml" relativeToChangelogFile="true"/>
|
||||
<include file="changes/0020-fix-draft-status-case.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
Reference in New Issue
Block a user