# EDU-016 — Move PostgreSQL-specific Education persistence tests off H2 - **Status:** done — focused PostgreSQL persistence suite passes against the reachable `postgresdb` seam - **Type:** test infrastructure / PostgreSQL integration - **Phase:** 0 / database prerequisite - **Blockers:** EDU-002, EDU-005 for final schema ownership ## Confirmed defect Education unit tests use H2 with `MODE=MYSQL`, while production Mapper SQL intentionally uses PostgreSQL `ON CONFLICT`. H2 rejects the annotated SQL for favorites, wrong-question idempotency, unified answer/submit idempotency, and review-session conflict handling. Switching H2 to PostgreSQL mode does not solve this: H2 still rejects `ON CONFLICT` and also exposes Boolean/integer compatibility differences. This prevents the full Practice/Wrong/Favorite regression suite from exercising production persistence semantics. ## Outcome PostgreSQL-specific persistence behavior runs against real ephemeral PostgreSQL in the test suite, while fast database-independent tests may remain on H2 where their SQL is portable. ## Scope - Select the repository-standard PostgreSQL integration-test mechanism, preferably Testcontainers or an existing project fixture. - Move tests that execute `ON CONFLICT`, PostgreSQL JSON/JSONB, identity, Boolean, or concurrency semantics onto PostgreSQL. - Keep controller and pure domain tests database-independent. - Align test schema with module-owned Flyway after EDU-005/EDU-006; avoid maintaining a divergent hand-written full schema long term. - Cover at least: - `IdempotencyStoreMapper.insertIgnore`; - wrong-question idempotency insert; - favorite upsert; - review-session insert-ignore; - concurrent answer and submit claims. ## Acceptance criteria - [x] No test relies on H2 to validate PostgreSQL `ON CONFLICT` semantics. - [x] PostgreSQL tests execute the same Mapper SQL as production. - [x] Test database is isolated and disposable. - [x] Schema setup uses Flyway or an explicitly temporary bridge with an exit ticket. - [x] Concurrency tests are stable and prove unique-constraint behavior. - [x] CI prerequisites and local commands are documented. ## Verification Completed against the existing reachable `postgresdb` service with credentials supplied only through `EDU_TEST_POSTGRES_*` environment variables: ```bash mvn -pl yudao-module-education \ -Dtest=PracticeSessionMapperTest,FavoriteServiceImplTest,PracticeAnswerServiceImplTest,PracticeSubmitServiceImplTest,PracticeSubmitProjectionIntegrationTest,WrongQuestionServiceImplTest test ``` Result: 140 tests passed, 0 failures, 0 errors. The same 140-test suite also passed with JUnit class parallelism explicitly enabled, proving the shared schema resource lock prevents class-level collisions. The suite executes production Mapper SQL on PostgreSQL, including `ON CONFLICT` and JSONB behavior. A JUnit resource lock serializes PostgreSQL test classes that share one random JVM-scoped schema; each class closes its Spring context before the inherited lifecycle drops and recreates that schema, preventing cached contexts from reusing a dropped schema. The temporary schema bridge was removed by EDU-006. PostgreSQL persistence tests now create their disposable random schema through the module-owned Flyway chain (`V4010`, `V4020`, `V4030`) and retain only `clean.sql` for per-test data isolation. Local/CI prerequisites: - a reachable disposable PostgreSQL database; - PostgreSQL JDBC connectivity from the Maven process; - non-blank `EDU_TEST_POSTGRES_HOST`, `EDU_TEST_POSTGRES_PORT`, `EDU_TEST_POSTGRES_DB`, `EDU_TEST_POSTGRES_USER`, and `EDU_TEST_POSTGRES_PASSWORD` values. No Testcontainers or other external dependency was added. No PostgreSQL Flyway migration was executed or authorized by this ticket. ## Risk and rollback - **Risk:** Medium CI/runtime cost; high value for persistence confidence. - **Rollback:** Keep prior fast tests temporarily, but do not restore false H2 coverage claims for PostgreSQL SQL.