Tests refactor

This commit is contained in:
Aerilyn Weber 2026-05-19 11:06:03 +09:00
parent 245520fb50
commit 99134d8556
165 changed files with 911 additions and 531 deletions

View file

@ -97,19 +97,20 @@ Separate each group with a blank line.
| React hook | camelCase | `useProducts`, `usePantryItems` |
| CSS class (Tailwind) | kebab-case | via Tailwind utilities |
## Implementation Workflow (Vertical Slice)
## Implementation Workflow (TDD-First)
Every feature or phase implementation must follow this order to ensure consistency and type safety across the monorepo:
Every feature or phase implementation must follow this exact test-first sequence, strictly practicing the Red-Green-Refactor cycle:
1. **Shared Layer (`packages/shared`)**: Define types, enums, and Zod schemas. Add unit tests.
2. **Database Layer (`packages/api`)**: Create Mongoose schema and Repository. Use `.lean().exec()` on all reads. Add integration tests.
3. **Service Layer (`packages/api`)**: Implement business logic using Awilix DI. Add unit tests.
4. **Route Layer (`packages/api`)**: Create Fastify route plugin. Add route tests.
5. **Web API Client (`packages/web`)**: Implement frontend service. Add unit tests.
6. **Web UI (`packages/web`)**: Create Next.js pages/components (Server Components by default). Add component tests.
1. **Shared Layer (`packages/shared`)**: Write validation schema and enum tests first in `tests/`. Implement schemas and types inside `src/`.
2. **Database Layer (`packages/api`)**: Write schema and repository integration tests first in `tests/` (using `mongodb-memory-server`). Create Mongoose schema and Repository in `src/`. All reads must use `.lean().exec()`.
3. **Service Layer (`packages/api`)**: Write service unit tests first in `tests/` using the `createMockRepository` helper to mock repository methods. Implement business logic Service class inside `src/` using Awilix constructor injection.
4. **Route Layer (`packages/api`)**: Write Fastify route plugin tests first in `tests/` using `app.inject` and resolving mocks. Implement the Fastify route plugin inside `src/`.
5. **Web API Client (`packages/web`)**: Write service unit tests first in `tests/` utilizing MSW to mock backend requests. Implement frontend service in `src/`.
6. **Web UI (`packages/web`)**: Write component and page tests first in `tests/` using React Testing Library role queries. Create Next.js pages and components inside `src/` (Server Components by default).
**Mandatory Verification**: Every task must end with `npm run build`, `npm run test:cov`, and `npm run lint` all passing with the defined coverage thresholds.
## Code Organization Rules
### API (NestJS)

167
docs/instructions/tdd.md Normal file
View file

@ -0,0 +1,167 @@
# Test-Driven Development (TDD) Guide — MeshiTrack
This guide serves as the definitive reference for writing, executing, and maintaining tests using a Test-Driven Development (TDD) methodology across all packages in the MeshiTrack monorepo.
---
## 1. Core Principles
TDD is a software development workflow where you **write tests first, before writing implementation code**. This ensures high reliability, enforces strict scoping of features, prevents untested paths, and results in cleaner designs.
### The Red-Green-Refactor Cycle
For every feature, schema, service, or route you build, follow this continuous loop:
1. **RED (Write a Failing Test)**:
- Identify the small increment of behavior you want to add.
- Write a test expressing that behavior.
- Run the test and see it fail. **Compilation failures in TypeScript count as a valid "Red" state** for new types, interfaces, or class signatures. For logic, stub the signature and ensure it fails at runtime.
2. **GREEN (Write Minimal Code)**:
- Write the absolute minimum production code required to satisfy the test (even if it's returning hardcoded values temporarily).
- Run the test and see it pass.
3. **REFACTOR (Clean Up and Optimize)**:
- Review your code (both production and test files). Remove duplication, improve readability, refine types, and optimize structure.
- Run the test suite continuously to guarantee your refactoring did not break existing behavior (remains Green).
---
## 2. Directory Structure (Option B)
To keep production bundles completely clean, all test files and mock utilities live in a root-level `tests/` folder matching the structure of `src/`. **Never place tests inside `src/`.**
```
packages/api/
├── src/ # 100% Pure Production Code
│ ├── modules/
│ │ └── products/
│ │ ├── products.service.ts
│ │ └── products.repository.ts
│ └── main.ts
└── tests/ # 100% Test-First Files
├── helpers/
│ └── mock-repository.ts # Reusable mocking helpers
├── modules/
│ └── products/
│ ├── products.service.test.ts
│ └── products.repository.test.ts
└── schemas/
└── product.schema.test.ts
```
---
## 3. Layer-by-Layer TDD Guide
### Layer 1: Shared Package (`packages/shared`)
The shared package contains domain types, enums, and Zod schemas.
1. **Red**: Write a test in `packages/shared/tests/validation/feature.schemas.test.ts` asserting validation behaviors for correct, incorrect, and edge-case payloads.
2. **Green**: Implement the validation schema and types in `packages/shared/src/validation/feature.schemas.ts`. Run `npm run test -w packages/shared` to verify it passes.
3. **Refactor**: Clean up custom messages, dry up repeated Zod rules, and ensure exports are barrelled correctly in `index.ts`.
---
### Layer 2: Schema & Repository (`packages/api`)
This layer handles persistence via Mongoose schemas and repositories.
1. **Red**: Write an integration test in `packages/api/tests/modules/<module>/<module>.repository.test.ts` using `mongodb-memory-server` asserting query conditions, indexes, and `householdId` filtering.
2. **Green**: Create the Mongoose schema inside `src/schemas/` and the Repository class in `src/modules/<module>/`. All reads must use `.lean().exec()`. Verify tests pass.
3. **Refactor**: Optimize indexes and verify schema options (e.g. `timestamps: true`).
---
### Layer 3: Service Layer (`packages/api`)
Services contain core business logic, transactional bounds, and use constructor-injected dependencies via Awilix.
To eliminate boilerplate, import the `createMockRepository` helper from `tests/helpers/mock-repository.ts` to mock all repository methods automatically:
```typescript
// packages/api/tests/modules/products/products.service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ProductsService } from '../../../src/modules/products/products.service.js';
import { ProductsRepository } from '../../../src/modules/products/products.repository.js';
import { createMockRepository } from '../../helpers/mock-repository.js';
describe(ProductsService.name, () => {
let service: ProductsService;
let mockRepo: ReturnType<typeof createMockRepository<ProductsRepository>>;
beforeEach(() => {
vi.clearAllMocks();
mockRepo = createMockRepository(ProductsRepository);
service = new ProductsService({ productsRepository: mockRepo as any });
});
it('should find a product by household scope', async () => {
// 1. RED: Write the test first, before service.findById exists
mockRepo.findById.mockResolvedValue({ _id: '1', name: 'Apples' });
const result = await service.getById('1', 'hh1');
expect(result.name).toBe('Apples');
});
});
```
1. **Red**: Run the test. Verify it fails due to compilation (if the service/method is missing) or runtime failure (if stubbed).
2. **Green**: Implement constructor injection and the method in `src/modules/<module>/<module>.service.ts`. Run the test to confirm it passes.
3. **Refactor**: Extract logic, reduce cognitive complexity, and ensure proper typing.
---
### Layer 4: Route Layer (`packages/api`)
Fastify routes define endpoints, validate payloads, and resolve services.
1. **Red**: Write a route test in `tests/modules/<module>/<module>.routes.test.ts` using Fastify's `app.inject()`. Mock the resolved service from the request DI scope. Assert status codes and payloads.
2. **Green**: Register the route plugin in `src/modules/<module>/<module>.routes.ts`, wire validations via Zod type providers, and call the service. Verify it passes.
3. **Refactor**: Clean up endpoint structures, route prefixes, and schema schemas.
---
### Layer 5: Web API Client (`packages/web`)
Frontend fetch wrapper utilizing MSW to mock backend requests.
1. **Red**: Write a service test in `packages/web/tests/services/<service>.test.ts`. Configure MSW to mock the HTTP responses. Assert response modeling and mapping.
2. **Green**: Implement the service wrapper using fetch in `packages/web/src/services/`. Verify it passes.
3. **Refactor**: Clean up payload mappings and parameter serialization.
---
### Layer 6: Web UI Components (`packages/web`)
React components and Next.js pages.
1. **Red**: Write a component test in `packages/web/tests/app/(dashboard)/<feature>/<Component>.test.tsx` using React Testing Library. Use accessible role queries (`screen.getByRole`) to assert loading/rendering states and user interactions.
2. **Green**: Write the minimal TSX/JSX inside `packages/web/src/` to satisfy the tests.
3. **Refactor**: Refine tailwind utility classes, modularize components, and check accessibility (ARIA tags).
---
## 4. Tooling & DX Guidelines
To make TDD fast and effective, use interactive watch-mode running continuously in the background. **Never run `npx`.**
### Continuous File-Specific Watch Mode (Fastest DX)
To start watch mode for a specific test file during a Red-Green cycle:
```bash
# Watch API Service tests
npm run test:watch -w packages/api -- tests/modules/products/products.service.test.ts
# Watch Web Component tests
npm run test:watch -w packages/web -- tests/app/dashboard/ProductCard.test.tsx
```
### Continuous Package-Wide Watch Mode
To start watch mode for all tests in a package:
```bash
# Continuous API package tests
npm run test:watch -w packages/api
# Continuous Web package tests
npm run test:watch -w packages/web
```
### Full-Suite Verification
Before concluding any implementation phase, confirm that the entire verification gate is fully operational:
```bash
npm run build # Enforces typescript compile across all packages
npm run test:cov # Verifies all tests pass and meet strict coverage gates
npm run lint # Validates static conventions
```

181
docs/tdd-transition-plan.md Normal file
View file

@ -0,0 +1,181 @@
# TDD Transition Plan — MeshiTrack
This document details the blueprint for transitioning the MeshiTrack monorepo to a strict Test-Driven Development (TDD) workflow. It outlines the specific steps, tooling enhancements, repository guidelines, and architectural patterns required to shift from "test-after" to "test-first" development.
---
## 1. Objectives
1. **Maintain Strict Quality Bars**: Ensure all new code seamlessly meets our high coverage gates (100% lines/functions/statements, 90% branches for `packages/api` & `packages/shared`, and 90% lines, 85% functions, 75% branches, 85% statements for `packages/web`).
2. **Prevent Architecture Regression**: Stop implementing business logic before definitions and type interfaces are established in `packages/shared`.
3. **Elevate DX and Iteration Speed**: Speed up the local feedback loop (Vitest/RTL runtimes) to enable a continuous Red-Green-Refactor cycle.
4. **Standardize Mocks and Factories**: Establish reusable test builders and automated dependency mocks to reduce boilerplate.
---
## 2. The MeshiTrack TDD Loop (Red-Green-Refactor)
TDD requires writing tests *before* writing the corresponding implementation. In MeshiTrack, this cycle is applied iteratively across each vertical slice.
```mermaid
graph TD
A[1. Write Failing Test - RED] --> B[2. Run Test & Verify Failure]
B --> C[3. Write Minimal Code - GREEN]
C --> D[4. Run Test & Verify Pass]
D --> E[5. Refactor Code & Clean Boilerplate]
E --> F[6. Run All Tests to Prevent Regression]
F --> A
```
### The Rules of TDD in MeshiTrack:
1. **Write no production code** unless it is to make a failing unit or integration test pass.
2. **Write only enough of a test** to demonstrate a failure (compilation failure counts as a failure).
3. **Write only enough production code** to make the single failing test pass.
4. **Refactor immediately** after going Green, while both the unit test and existing regression suite are passing.
---
## 3. Layer-by-Layer TDD Guide
### Layer 1: Shared Package (`packages/shared`)
The shared package contains domain types, enums, and Zod schemas. It must remain pure TypeScript.
1. **Red**: Define the interface or enum signature in TypeScript. Write a test in `src/validation/*.test.ts` asserting how the validation schema should handle valid, invalid, and edge-case payloads.
2. **Green**: Implement the Zod v4 validation schema in `src/validation/*.schemas.ts`. Run the test to verify it passes.
3. **Refactor**: Clean up the schema declarations, refine custom error messages, and ensure barrel exports are updated in `index.ts`.
---
### Layer 2: Database Schema & Repository (`packages/api`)
This layer handles persistence via Mongoose schemas and Awilix-registered repositories.
1. **Red**: Write an integration test (`*.repository.test.ts`) using `mongodb-memory-server` that tests data storage, constraints, index behavior, and `householdId` filtering.
2. **Green**: Implement the Mongoose schema, compile the model, and write the Repository class methods using `.lean().exec()` on all reads. Verify the integration test passes.
3. **Refactor**: Optimize database indexes, ensure appropriate Mongoose options are set, and check for memory leaks or unclosed connections.
---
### Layer 3: Service Layer (`packages/api`)
Services contain the core business logic, including validation, authorization checks, and transactional tasks.
1. **Red**: Write unit tests (`*.service.test.ts`) using Vitest. Mock all dependencies (e.g., repositories, event emitters) using `vi.fn()`. Write tests asserting correct handling of successful cases, expected errors (`NotFoundError`, `ConflictError`, etc.), and household isolation boundaries.
2. **Green**: Create the Service class with constructor injection via Awilix. Implement only the minimum logic required to pass the test cases.
3. **Refactor**: Extract duplicate logic into helper functions, improve type safety, and simplify constructor dependencies.
---
### Layer 4: Route Layer (`packages/api`)
Fastify routes define endpoints, validate incoming payloads via Zod, resolve services, and return responses.
1. **Red**: Write a route test (`*.routes.test.ts`) using Fastify's `app.inject()`. Assert status codes, headers, and the response body structure. Mock the service layer resolved from the request DI scope (`request.diScope.resolve`).
2. **Green**: Implement the Fastify route plugin, declare path validation schemas, resolve the service from the DI container, and call it. Register the plugin.
3. **Refactor**: Optimize error mappings, clean up route paths, verify route configuration options (e.g., household scope exclusions).
---
### Layer 5: Web API Client (`packages/web`)
The client wrapper calls the backend API and handles client-side caching or state synchronization (e.g., via SWR).
1. **Red**: Write a service unit test (`*.test.ts`) using Vitest. Configure MSW (Mock Service Worker) to mock the API endpoint response. Write assertions checking payload formatting, query serialization, and error transformations.
2. **Green**: Implement the frontend service in `src/services/` using fetch. Run tests and verify MSW handlers return correctly.
3. **Refactor**: Standardize request configuration, dry up common header settings, and enhance client type-safety.
---
### Layer 6: Web UI (`packages/web`)
React components and Next.js pages display data, capture input, and handle interactions.
1. **Red**: Write component tests (`*.test.tsx`) using React Testing Library. Query elements by accessible roles (e.g., `screen.getByRole('button', { name: /save/i })`). Assert correct rendering of states (loading, empty, success) and verify user interactions fire expected service callbacks.
2. **Green**: Implement the React component (Server/Client split as appropriate). Write the bare minimum JSX/TSX to satisfy the test roles and events.
3. **Refactor**: Refine CSS structures, optimize components for re-renders, clean up accessibility attributes, and verify responsive design parameters.
---
## 4. DX Tooling Enhancements (Bridges)
To enable developers to practice TDD seamlessly, we must upgrade our test execution loop and reduce boilerplate.
### Bridge A: Immediate Watch Mode for Active Files
Running `npm run test` or `npm run test:cov` across the entire workspace takes too long for the continuous TDD loop. We need scripts to instantly watch specific directories or single files.
We will update package-level `package.json` scripts to introduce:
1. **Root package.json**:
- Add `"test:watch": "turbo run test:watch"`
2. **packages/web/package.json**:
- Add `"test:watch": "vitest"` (mirroring packages/api)
This will allow developers to run:
* `npm run test:watch -w packages/api` to start a continuous, interactive Vitest runner for API files.
* `npm run test:watch -w packages/api -- src/modules/products/products.service.test.ts` to focus only on a single service during the Red-Green cycle.
### Bridge B: Automated Mock Helpers
Creating manual mocks for every repository in every service test adds substantial boilerplate. We will introduce a standard mock utility `packages/api/src/common/test/mock-repository.ts` that dynamically mocks any repository interface.
```typescript
import { vi } from 'vitest';
export function createMockRepository<T>(repoClass: new (...args: any[]) => T): Record<keyof T, any> {
const methods = Object.getOwnPropertyNames(repoClass.prototype).filter(
(name) => name !== 'constructor' && typeof repoClass.prototype[name] === 'function'
);
const mock: Record<string, any> = {};
for (const method of methods) {
mock[method] = vi.fn();
}
return mock as Record<keyof T, any>;
}
```
This reduces the boilerplate in service tests from a manual 15-line structure to a single line:
```typescript
const mockRepo = createMockRepository(ProductsRepository);
```
---
## 5. TDD Commit & Git Conventions
To document and encourage step-by-step TDD, we will adopt atomic commit structures matching the Red-Green-Refactor steps:
1. **Red Commit**: When a new test suite is created and fails.
- `test(pantry): add failing test for spoilage calculation (RED)`
2. **Green Commit**: When the production code is completed and tests pass.
- `feat(pantry): implement spoilage calculation logic (GREEN)`
3. **Refactor Commit**: When the code is cleaned up while staying green.
- `refactor(pantry): optimize date difference utility in spoilage`
These atomic commits make peer reviews and rollbacks extremely clean and make it simple to track development momentum.
---
## 6. Guidelines Alignment & Updates
To formalize the transition, we will execute the following file updates:
1. **Update `ANTIGRAVITY.md`**:
- Modify the "Implementation Workflow" section to make TDD mandatory.
- Replace steps with the Red-Green-Refactor workflow per layer.
- Introduce the `test:watch` commands and mock helper directives.
2. **Update `docs/instructions/conventions.md`**:
- Re-orient the "Implementation Workflow" from "test-after" to TDD-first.
3. **Introduce `docs/instructions/tdd.md`**:
- A dedicated developer tutorial on TDD in the MeshiTrack codebase, explaining mocks, databases, and Next.js testing patterns in detail.
---
## 7. Immediate Action Plan
To bridge the TDD gap immediately, we will execute these concrete steps in this task:
1. **Step 1: Codebase Tooling Infrastructure**
- Add `"test:watch": "vitest"` to `packages/web/package.json`.
- Add `"test:watch": "turbo run test:watch"` to root `package.json`.
2. **Step 2: Reusable Mock Helper**
- Create `packages/api/src/common/test/mock-repository.ts` containing the automated mock builder utility.
3. **Step 3: Document TDD & Update Guidelines**
- Create `docs/instructions/tdd.md` as the definitive guide.
- Update `ANTIGRAVITY.md`'s workflow section.
- Update `docs/instructions/conventions.md`'s workflow section.
4. **Step 4: Verification**
- Verify the codebase build and tests are completely operational.