Context / Problem
The repository layer is split into three parts:
| Layer |
Purpose |
internal/repository |
Declares repository interfaces (ApplicationRepository, EpochRepository, …) and small helpers. |
internal/repository/factory |
Chooses a concrete backend (postgresql, future sqlite, etc.) based on configuration. |
internal/repository/postgresql |
Current production implementation of the interfaces. |
Today we have no dedicated test suite for these layers, so query errors or future back-end additions could break behaviour unnoticed.
Contract-Style Testing
Write one reusable test suite per interface (the contract) that asserts “this is how an ApplicationRepository must behave.”
Each concrete backend (PostgreSQL now, SQLite later) simply feeds its implementation into that same suite.
Result: you author the behavioural tests once, yet they run against every backend, guaranteeing consistent behaviour and making new back-ends inherit the tests automatically.
Suggested Solution
-
Contract package
internal/repository/contract/ exports functions such as
RunApplicationRepoContract(t *testing.T, repo repository.ApplicationRepository).
- Each function is a table-driven suite covering happy path, edge cases, and error paths.
-
PostgreSQL implementation
- Test files like
internal/repository/postgresql/app_test.go call the contract runner with a Postgres-backed repo.
- Each test opens a transaction and rolls it back on cleanup to isolate state.
-
Fixture seeding
- Store deterministic seed data in
internal/repository/testdata/*.sql.
- Load these fixtures (via
go:embed) inside the contract harness immediately after migrations.
- Each test still runs in its own SQL transaction; the fixture rows are visible inside the txn and rolled back on cleanup.
-
Helper & factory tests
internal/repository/helpers_test.go – filter builders, pagination tokens, nil-safety, etc.
internal/repository/factory/factory_test.go – correct backend selection, errors on unknown schemes.
-
Concurrency & race checks
- All tests run under
go test -race; some use t.Parallel() to ensure thread safety.
-
CI integration
- GitHub Actions spins up Postgres, exports
CARTESI_TEST_DATABASE_CONNECTION, then runs go test ./internal/repository/... -race.
Deliverables & File Layout
| Path |
Purpose |
internal/repository/contract/ |
Contract suites (application.go, epoch.go, …). |
internal/repository/helpers_test.go |
Unit tests for shared helper funcs. |
internal/repository/factory/factory_test.go |
Backend-selection tests. |
internal/repository/postgresql/*_test.go |
Runs contract tests against Postgres impl. |
internal/repository/testdata/*.sql |
Seed fixtures (embedded via go:embed). |
internal/repository/testutil/db.go |
Connect, migrate, load fixtures, wrap each test in rollback TX. |
Acceptance Criteria
| # |
Scenario |
Expected outcome |
| 1 |
go test -race ./internal/repository/... |
All tests pass; repository code coverage ≥ 80 % |
| 2 |
Break a JOIN in PostgreSQL repo |
Contract test fails with clear assertion |
| 3 |
Pass unknown DSN (foo://…) to factory |
Factory returns descriptive error |
| 4 |
Parallel run (go test -parallel 8) |
No data races; state isolation via per-test TX |
This contract-driven suite will give us a strong regression net for the current PostgreSQL backend and any future database implementations.
Context / Problem
The repository layer is split into three parts:
internal/repositoryApplicationRepository,EpochRepository, …) and small helpers.internal/repository/factorypostgresql, futuresqlite, etc.) based on configuration.internal/repository/postgresqlToday we have no dedicated test suite for these layers, so query errors or future back-end additions could break behaviour unnoticed.
Contract-Style Testing
Write one reusable test suite per interface (the contract) that asserts “this is how an
ApplicationRepositorymust behave.”Each concrete backend (PostgreSQL now, SQLite later) simply feeds its implementation into that same suite.
Result: you author the behavioural tests once, yet they run against every backend, guaranteeing consistent behaviour and making new back-ends inherit the tests automatically.
Suggested Solution
Contract package
internal/repository/contract/exports functions such asRunApplicationRepoContract(t *testing.T, repo repository.ApplicationRepository).PostgreSQL implementation
internal/repository/postgresql/app_test.gocall the contract runner with a Postgres-backed repo.Fixture seeding
internal/repository/testdata/*.sql.go:embed) inside the contract harness immediately after migrations.Helper & factory tests
internal/repository/helpers_test.go– filter builders, pagination tokens, nil-safety, etc.internal/repository/factory/factory_test.go– correct backend selection, errors on unknown schemes.Concurrency & race checks
go test -race; some uset.Parallel()to ensure thread safety.CI integration
CARTESI_TEST_DATABASE_CONNECTION, then runsgo test ./internal/repository/... -race.Deliverables & File Layout
internal/repository/contract/application.go,epoch.go, …).internal/repository/helpers_test.gointernal/repository/factory/factory_test.gointernal/repository/postgresql/*_test.gointernal/repository/testdata/*.sqlgo:embed).internal/repository/testutil/db.goAcceptance Criteria
go test -race ./internal/repository/...foo://…) to factorygo test -parallel 8)This contract-driven suite will give us a strong regression net for the current PostgreSQL backend and any future database implementations.