[Refactor/Chore] Roadmap: strengthen backend tests with business contracts and invariants
- Dominant language
- TypeScript
- Stars
- 156k
- Forks
- 24.6k
- Avg merge
- 22h 9m
- Merged PRs (30d)
- 610
Description
### Description
Improve backend tests so they detect violations of business contracts, including observable outcomes, invariants, failure behavior, and behavior across operation sequences.
Some tests primarily assert that an internal method or API was called, or that individual fields changed after one successful operation. Using SQLite makes database interactions executable, but does not by itself establish that the assertions distinguish correct behavior from plausible business bugs.
This roadmap tracks an incremental review and improvement of test specifications. Begin with one high-risk lifecycle, establish a repeatable approach, and expand based on the defects the tests can detect.
### Motivation
A test should answer: **Which plausible business error would make this test fail?**
Examples include a missing tenant filter, an incomplete ownership check, premature retirement of a referenced resource, partial updates after failure, or duplicate effects after task redelivery. Coverage and test counts alone do not show whether these errors are detected.
An assertion about an external call can be valuable when the call is itself part of the contract, such as preventing a charge after authorization fails. Assertions about internal call structure need a clear contractual reason to remain.
### Additional Context
#### Phase 1: Define contracts for one pilot lifecycle
- [ ] Select one high-risk lifecycle. Agent retirement and resource cleanup is a candidate because it involves ownership, related resources, transactions, and asynchronous work.
- [ ] Read the owning module docstrings, comments, and relevant requirements before defining expected behavior. Record ambiguities for domain decisions; do not infer correctness solely from the current implementation.
- [ ] Write 5–10 explicit contracts, covering applicable outcomes, invariants, failure guarantees, and repeated operations.
- [ ] For each contract, record its scope and observation boundary, allowed outcomes, a plausible incorrect implementation, and existing test coverage.
- [ ] Classify current tests by the contract they establish. Identify gaps, redundant assertions, and assertions coupled to implementation details.
**Acceptance:** The pilot has a reviewed contract matrix. Each contract identifies a concrete defect that a test must detect, and unresolved requirements are explicitly recorded.
Candidate questions for Agent retirement, subject to the owning contract:
| Dimension | Contract to establish | Example defect to detect |
| --- | --- | --- |
| Outcome | An eligible orphan reaches the required archived and cleanup states. | One required related resource is left active. |
| Ownership | A valid persisted reference prevents retirement. | One component of the complete owner chain is ignored. |
| Isolation | An operation for tenant A leaves tenant B's related state unchanged. | A read or write omits tenant scope. |
| Repetition | Repeated execution and redelivery preserve the defined final state and permitted effects. | A replay causes duplicate destructive effects. |
| Failure | Failures leave a state permitted by the atomicity or recovery contract. | Partial updates remain with no valid recovery path. |
| Irrelevant changes | Unrelated records and irrelevant insertion order do not change the target outcome. | Processing accidentally depends on unrelated rows or incidental ordering. |
#### Phase 2: Strengthen observations and test data
- [ ] Exercise the public entry point of the layer whose contract is under test, preserving real collaborators inside that boundary where practical.
- [ ] Assert observable results, relevant persisted state, and required boundary effects. Keep expected results independent of the production algorithm.
- [ ] Where committed persistence is promised, verify through a fresh session after the operation's transaction completes. For caller-owned transactions, test the documented flush/commit responsibility explicitly.
- [ ] Seed confusable data: another tenant's similar resources, valid and invalid references, multiple related resources, and both eligible and ineligible targets.
- [ ] Vary each relevant ownership dimension independently and assert that unrelated resources remain unchanged.
- [ ] Retain mocks at appropriate external boundaries, with meaningful behavior and failure injection. Avoid replacing the policy being tested with a configured mock result.
**Acceptance:** Pilot tests distinguish the correct implementation from omitted ownership predicates and incomplete state transitions, rather than merely confirming a call or a single field assignment.
#### Phase 3: Test sequences, failure, and recovery
- [ ] Add deterministic operation sequences, such as create → bind → attempt retirement → unbind → retire → repeat retirement, using the documented preconditions and outcomes.
- [ ] Check invariants at each meaningful observation boundary. Do not require transient internal states to satisfy guarantees that apply only after commit or eventual completion.
- [ ] Inject failures at relevant database and external-effect boundaries. Assert permitted residual state and the specified retry or recovery behavior.
- [ ] Exercise repeated commands and task redelivery. Distinguish idempotent business effects from delivery counts; do not impose exactly-once delivery unless it is a documented guarantee.
- [ ] Add metamorphic cases where appropriate: introduce unrelated records or reorder irrelevant inputs and verify that the specified result is unchanged.
- [ ] Where sequence combinations justify it, add property-based/stateful tests with explicit preconditions, an independent oracle or invariant, and reproducible failure cases. Keep deterministic regression cases for discovered defects.
**Acceptance:** The pilot covers success, rejection, replay, and at least one meaningful failure/recovery sequence. Assertions specify both the required outcomes and the prohibited effects.
#### Phase 4: Validate the tests against plausible defects
- [ ] Start with a small curated set of temporary code mutations: remove a tenant predicate, omit a required related update, weaken an ownership check, or bypass an idempotency guard.
- [ ] Confirm that each applicable mutation is detected by a relevant behavioral assertion. A syntax error, import failure, or unrelated fixture failure does not establish detection of the business defect.
- [ ] Review surviving mutations: add a missing contract/test, resolve an unclear requirement, or document why the mutation is equivalent or outside scope.
- [ ] Consider automated mutation testing for the pilot only after measuring its usefulness and runtime. Do not begin with a repository-wide mutation-score gate.
**Acceptance:** Every selected non-equivalent defect in scope is caught by a relevant test, and the original implementation passes the same tests. Record the defect-to-test mapping.
#### Phase 5: Expand incrementally and make the approach repeatable
- [ ] Add a short testing guide and one representative pilot example covering contract → discriminating data → observation → defect detection.
- [ ] Use a review checklist: What contract is protected? What incorrect implementation fails? Are negative cases and relevant sequences covered? Is the expected result independent of the implementation?
- [ ] Prioritize subsequent work by business risk and incident history, including tenant authorization, resource lifecycles, retried tasks, and accounting where applicable.
- [ ] Create linked follow-up issues for selected domain slices, each with its contract matrix and acceptance criteria.
- [ ] Track protected contracts and detected defect classes. Record test runtime and flakiness so the suite remains usable.
**Acceptance:** The pilot is complete, its guidance is documented, and follow-up domain work has explicit scope and measurable acceptance criteria.
#### Test placement and execution
- Use focused unit tests for pure policy and calculations, and SQLite-backed tests for applicable service/repository behavior.
- Use CI-owned integration tests for contracts that depend on production database locking, concurrency, isolation, or dialect behavior. A SQLite result is not evidence for those guarantees.
- Keep tool adoption proportional to demonstrated gaps. This roadmap does not require a wholesale database migration, a new test framework, or indiscriminate replacement of all call assertions.
- Run backend checks from the repository root. Use `make test TARGET_TESTS=./api/tests/` for affected suites and the applicable lint/type checks. Run direct Python commands through `uv run --project api`; do not start long-running services for routine local validation.
#### Roadmap completion criteria
- [ ] One complete pilot demonstrates contract-based tests, adversarial data, operation sequences, failure/recovery checks, and verified detection of selected business defects.
- [ ] The testing guide and review checklist are available in the repository.
- [ ] Prioritized domain follow-ups are linked from this issue.
Contributor guide
Research direction
Start at the repository root by reviewing the backend test layout and the owning module for one selected high-risk lifecycle; no specific module or test path is chosen yet. Run the affected checks with `make test TARGET_TESTS=./api/tests/` and use `uv run --project api` for direct Python commands. Done means a reviewed contract matrix, strengthened pilot tests, failure and replay coverage, mutation validation, and documented follow-ups.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, sqlite
- Domain
- backend, databases, testing-qa
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100