From 99134d8556d155047dd1182e0ac438a6b0a4a414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aerilyn=20Dziedzel=C4=97?= Date: Tue, 19 May 2026 11:06:03 +0900 Subject: [PATCH] Tests refactor --- ANTIGRAVITY.md | 21 +- docs/instructions/conventions.md | 17 +- docs/instructions/tdd.md | 167 ++++++++++++++++ docs/tdd-transition-plan.md | 181 ++++++++++++++++++ package.json | 1 + .../modules/meal-plans/meal-plans.routes.ts | 27 ++- .../modules/meal-plans/meal-plans.service.ts | 27 +-- .../meal-plans/shopping-gap.service.ts | 11 +- .../meal-plans/suggestion-engine.service.ts | 14 +- .../nutrition-target.repository.ts | 20 +- .../nutrition-target.routes.ts | 25 ++- .../nutrition-target.service.ts | 20 +- .../src/modules/prices/prices.repository.ts | 4 +- .../api/src/modules/prices/prices.routes.ts | 10 +- .../api/src/modules/prices/prices.service.ts | 21 +- .../shopping-lists.repository.ts | 35 ++-- .../shopping-lists/shopping-lists.routes.ts | 42 ++-- .../shopping-lists/shopping-lists.service.ts | 27 ++- .../api/src/schemas/price-record.schema.ts | 2 +- .../api/src/schemas/shopping-list.schema.ts | 6 +- .../api/{src => tests}/common/errors.test.ts | 2 +- .../config/configuration.test.ts | 2 +- packages/api/tests/helpers/mock-repository.ts | 34 ++++ packages/api/{src => tests}/main.test.ts | 4 +- .../cabinet-events.repository.test.ts | 4 +- .../cabinet-events.routes.test.ts | 14 +- .../cabinet-events.service.test.ts | 2 +- .../cabinet/cabinet.repository.test.ts | 4 +- .../modules/cabinet/cabinet.routes.test.ts | 30 +-- .../modules/cabinet/cabinet.service.test.ts | 2 +- .../freshness-rules.repository.test.ts | 4 +- .../freshness-rules.routes.test.ts | 12 +- .../freshness-rules.service.test.ts | 4 +- .../modules/health/health.routes.test.ts | 2 +- .../households/households.repository.test.ts | 4 +- .../households/households.routes.test.ts | 12 +- .../households/households.service.test.ts | 4 +- .../modules/llm/no-op-llm.provider.test.ts | 4 +- .../meal-plans/meal-plans.repository.test.ts | 6 +- .../meal-plans/meal-plans.routes.test.ts | 20 +- .../meal-plans/meal-plans.service.test.ts | 6 +- .../meal-plans/shopping-gap.service.test.ts | 12 +- .../suggestion-engine.service.test.ts | 10 +- .../medicine-prices.repository.test.ts | 4 +- .../medicine-prices.routes.test.ts | 18 +- .../medicine-prices.service.test.ts | 2 +- .../medicine-products.repository.test.ts | 4 +- .../medicine-products.routes.test.ts | 16 +- .../medicine-products.service.test.ts | 4 +- .../medicines/medicines.repository.test.ts | 4 +- .../medicines/medicines.routes.test.ts | 18 +- .../medicines/medicines.service.test.ts | 4 +- .../nutrition-target.repository.test.ts | 6 +- .../nutrition-target.routes.test.ts | 12 +- .../nutrition-target.service.test.ts | 4 +- .../organizer/organizer.repository.test.ts | 4 +- .../organizer/organizer.routes.test.ts | 14 +- .../organizer/organizer.service.test.ts | 2 +- .../freshness-calculator.service.test.ts | 2 +- .../modules/pantry/pantry.repository.test.ts | 4 +- .../modules/pantry/pantry.routes.test.ts | 16 +- .../modules/pantry/pantry.service.test.ts | 4 +- .../modules/prices/prices.repository.test.ts | 6 +- .../modules/prices/prices.routes.test.ts | 16 +- .../modules/prices/prices.service.test.ts | 4 +- .../modules/products/barcode.service.test.ts | 2 +- .../modules/products/csv-parser.test.ts | 2 +- .../products/products.repository.test.ts | 6 +- .../modules/products/products.routes.test.ts | 14 +- .../modules/products/products.service.test.ts | 4 +- .../purchases/purchases.repository.test.ts | 16 +- .../purchases/purchases.routes.test.ts | 14 +- .../purchases/purchases.service.test.ts | 2 +- .../nutrition-calculator.service.test.ts | 2 +- .../recipes/recipes.repository.test.ts | 4 +- .../modules/recipes/recipes.routes.test.ts | 14 +- .../modules/recipes/recipes.service.test.ts | 4 +- .../recipes/unit-conversion.service.test.ts | 2 +- .../refills/refills.repository.test.ts | 4 +- .../modules/refills/refills.routes.test.ts | 14 +- .../modules/refills/refills.service.test.ts | 2 +- .../regimens/regimens.repository.test.ts | 4 +- .../modules/regimens/regimens.routes.test.ts | 14 +- .../modules/regimens/regimens.service.test.ts | 2 +- .../shopping-lists.repository.test.ts | 6 +- .../shopping-lists.routes.test.ts | 22 +-- .../shopping-lists.service.test.ts | 4 +- .../modules/stores/stores.repository.test.ts | 4 +- .../modules/stores/stores.routes.test.ts | 14 +- .../modules/stores/stores.service.test.ts | 2 +- .../modules/users/users.repository.test.ts | 4 +- .../modules/users/users.routes.test.ts | 6 +- .../modules/users/users.service.test.ts | 4 +- .../plugins/auth.plugin.test.ts | 2 +- .../plugins/household.plugin.test.ts | 4 +- .../plugins/mongoose.plugin.test.ts | 2 +- .../schemas/household.schema.test.ts | 2 +- .../schemas/medicine-product.schema.test.ts | 2 +- .../schemas/medicine.schema.test.ts | 2 +- .../schemas/product.schema.test.ts | 2 +- .../schemas/user.schema.test.ts | 2 +- packages/api/vitest.config.ts | 3 +- packages/web/package.json | 1 + .../app/(dashboard)/dashboard}/page.test.tsx | 2 +- .../app/(dashboard)}/layout.test.tsx | 2 +- .../app/(dashboard)}/loading.test.tsx | 2 +- .../medicine-prices}/page.test.tsx | 2 +- .../medicines}/ActivityTab.test.tsx | 2 +- .../medicines}/CabinetTab.test.tsx | 2 +- .../medicines}/LibraryTab.test.tsx | 2 +- .../medicines}/OrganizerTab.test.tsx | 2 +- .../medicines}/RegimensTab.test.tsx | 2 +- .../(dashboard)/medicines/[id]}/page.test.tsx | 2 +- .../medicines/activity}/page.test.tsx | 2 +- .../medicines/cabinet}/page.test.tsx | 2 +- .../medicines/library}/page.test.tsx | 2 +- .../medicines/organizer}/page.test.tsx | 2 +- .../app/(dashboard)/medicines}/page.test.tsx | 2 +- .../medicines/regimens}/page.test.tsx | 2 +- .../medicines/schedule}/page.test.tsx | 2 +- .../app/(dashboard)/pantry}/page.test.tsx | 2 +- .../products}/ImportDialog.test.tsx | 2 +- .../products}/ProductModal.test.tsx | 2 +- .../app/(dashboard)/products}/page.test.tsx | 2 +- .../app/(dashboard)/purchases}/page.test.tsx | 2 +- .../recipes}/RecipeEditor.test.tsx | 2 +- .../recipes/[id]/edit}/page.test.tsx | 2 +- .../(dashboard)/recipes/[id]}/page.test.tsx | 2 +- .../(dashboard)/recipes/new}/page.test.tsx | 2 +- .../app/(dashboard)/recipes}/page.test.tsx | 2 +- .../app/(dashboard)/refills}/page.test.tsx | 2 +- .../app/(dashboard)/settings}/page.test.tsx | 2 +- .../shopping-lists/[id]}/page.test.tsx | 2 +- .../(dashboard)/shopping-lists}/page.test.tsx | 2 +- .../shopping-lists/prices}/page.test.tsx | 2 +- .../app/(dashboard)/stores}/page.test.tsx | 2 +- .../app/__tests__ => tests/app}/page.test.tsx | 2 +- .../components}/Sidebar.test.tsx | 2 +- .../components}/ThemeProvider.test.tsx | 4 +- .../components}/TopBar.test.tsx | 2 +- .../layout}/PageHeaderContext.test.tsx | 2 +- .../components}/ui.test.tsx | 12 +- .../components/ui}/index.test.ts | 2 +- .../__tests__ => tests/lib}/useApi.test.ts | 2 +- .../lib/useShoppingListSync.test.ts | 2 +- .../services}/api-client.test.ts | 4 +- .../services}/cabinet-events.test.ts | 4 +- .../services}/cabinet.test.ts | 4 +- .../services}/households.test.ts | 4 +- .../services/meal-plans.test.ts | 6 +- .../services}/medicine-prices.test.ts | 4 +- .../services}/medicines.test.ts | 4 +- .../services/nutrition-targets.test.ts | 6 +- .../services}/organizer.test.ts | 4 +- .../services}/pantry.test.ts | 4 +- .../{src => tests}/services/prices.test.ts | 6 +- .../services}/products.test.ts | 4 +- .../services}/purchases.test.ts | 4 +- .../services}/recipes.test.ts | 4 +- .../services}/refills.test.ts | 4 +- .../services}/regimens.test.ts | 4 +- .../services/shopping-lists.test.ts | 6 +- .../services}/stores.test.ts | 4 +- packages/web/vitest.config.ts | 3 +- turbo.json | 5 + 165 files changed, 911 insertions(+), 531 deletions(-) create mode 100644 docs/instructions/tdd.md create mode 100644 docs/tdd-transition-plan.md rename packages/api/{src => tests}/common/errors.test.ts (98%) rename packages/api/{src => tests}/config/configuration.test.ts (90%) create mode 100644 packages/api/tests/helpers/mock-repository.ts rename packages/api/{src => tests}/main.test.ts (98%) rename packages/api/{src => tests}/modules/cabinet-events/cabinet-events.repository.test.ts (98%) rename packages/api/{src => tests}/modules/cabinet-events/cabinet-events.routes.test.ts (95%) rename packages/api/{src => tests}/modules/cabinet-events/cabinet-events.service.test.ts (97%) rename packages/api/{src => tests}/modules/cabinet/cabinet.repository.test.ts (98%) rename packages/api/{src => tests}/modules/cabinet/cabinet.routes.test.ts (91%) rename packages/api/{src => tests}/modules/cabinet/cabinet.service.test.ts (99%) rename packages/api/{src => tests}/modules/freshness-rules/freshness-rules.repository.test.ts (96%) rename packages/api/{src => tests}/modules/freshness-rules/freshness-rules.routes.test.ts (92%) rename packages/api/{src => tests}/modules/freshness-rules/freshness-rules.service.test.ts (95%) rename packages/api/{src => tests}/modules/health/health.routes.test.ts (94%) rename packages/api/{src => tests}/modules/households/households.repository.test.ts (96%) rename packages/api/{src => tests}/modules/households/households.routes.test.ts (94%) rename packages/api/{src => tests}/modules/households/households.service.test.ts (98%) rename packages/api/{src => tests}/modules/llm/no-op-llm.provider.test.ts (93%) rename packages/api/{src => tests}/modules/meal-plans/meal-plans.repository.test.ts (95%) rename packages/api/{src => tests}/modules/meal-plans/meal-plans.routes.test.ts (92%) rename packages/api/{src => tests}/modules/meal-plans/meal-plans.service.test.ts (96%) rename packages/api/{src => tests}/modules/meal-plans/shopping-gap.service.test.ts (93%) rename packages/api/{src => tests}/modules/meal-plans/suggestion-engine.service.test.ts (95%) rename packages/api/{src => tests}/modules/medicine-prices/medicine-prices.repository.test.ts (97%) rename packages/api/{src => tests}/modules/medicine-prices/medicine-prices.routes.test.ts (92%) rename packages/api/{src => tests}/modules/medicine-prices/medicine-prices.service.test.ts (98%) rename packages/api/{src => tests}/modules/medicine-products/medicine-products.repository.test.ts (96%) rename packages/api/{src => tests}/modules/medicine-products/medicine-products.routes.test.ts (92%) rename packages/api/{src => tests}/modules/medicine-products/medicine-products.service.test.ts (96%) rename packages/api/{src => tests}/modules/medicines/medicines.repository.test.ts (97%) rename packages/api/{src => tests}/modules/medicines/medicines.routes.test.ts (90%) rename packages/api/{src => tests}/modules/medicines/medicines.service.test.ts (97%) rename packages/api/{src => tests}/modules/nutrition-targets/nutrition-target.repository.test.ts (92%) rename packages/api/{src => tests}/modules/nutrition-targets/nutrition-target.routes.test.ts (92%) rename packages/api/{src => tests}/modules/nutrition-targets/nutrition-target.service.test.ts (94%) rename packages/api/{src => tests}/modules/organizer/organizer.repository.test.ts (97%) rename packages/api/{src => tests}/modules/organizer/organizer.routes.test.ts (96%) rename packages/api/{src => tests}/modules/organizer/organizer.service.test.ts (99%) rename packages/api/{src => tests}/modules/pantry/freshness-calculator.service.test.ts (98%) rename packages/api/{src => tests}/modules/pantry/pantry.repository.test.ts (98%) rename packages/api/{src => tests}/modules/pantry/pantry.routes.test.ts (94%) rename packages/api/{src => tests}/modules/pantry/pantry.service.test.ts (98%) rename packages/api/{src => tests}/modules/prices/prices.repository.test.ts (95%) rename packages/api/{src => tests}/modules/prices/prices.routes.test.ts (91%) rename packages/api/{src => tests}/modules/prices/prices.service.test.ts (97%) rename packages/api/{src => tests}/modules/products/barcode.service.test.ts (99%) rename packages/api/{src => tests}/modules/products/csv-parser.test.ts (98%) rename packages/api/{src => tests}/modules/products/products.repository.test.ts (97%) rename packages/api/{src => tests}/modules/products/products.routes.test.ts (97%) rename packages/api/{src => tests}/modules/products/products.service.test.ts (98%) rename packages/api/{src => tests}/modules/purchases/purchases.repository.test.ts (91%) rename packages/api/{src => tests}/modules/purchases/purchases.routes.test.ts (96%) rename packages/api/{src => tests}/modules/purchases/purchases.service.test.ts (99%) rename packages/api/{src => tests}/modules/recipes/nutrition-calculator.service.test.ts (98%) rename packages/api/{src => tests}/modules/recipes/recipes.repository.test.ts (97%) rename packages/api/{src => tests}/modules/recipes/recipes.routes.test.ts (96%) rename packages/api/{src => tests}/modules/recipes/recipes.service.test.ts (98%) rename packages/api/{src => tests}/modules/recipes/unit-conversion.service.test.ts (98%) rename packages/api/{src => tests}/modules/refills/refills.repository.test.ts (97%) rename packages/api/{src => tests}/modules/refills/refills.routes.test.ts (96%) rename packages/api/{src => tests}/modules/refills/refills.service.test.ts (99%) rename packages/api/{src => tests}/modules/regimens/regimens.repository.test.ts (97%) rename packages/api/{src => tests}/modules/regimens/regimens.routes.test.ts (96%) rename packages/api/{src => tests}/modules/regimens/regimens.service.test.ts (99%) rename packages/api/{src => tests}/modules/shopping-lists/shopping-lists.repository.test.ts (96%) rename packages/api/{src => tests}/modules/shopping-lists/shopping-lists.routes.test.ts (92%) rename packages/api/{src => tests}/modules/shopping-lists/shopping-lists.service.test.ts (99%) rename packages/api/{src => tests}/modules/stores/stores.repository.test.ts (97%) rename packages/api/{src => tests}/modules/stores/stores.routes.test.ts (94%) rename packages/api/{src => tests}/modules/stores/stores.service.test.ts (97%) rename packages/api/{src => tests}/modules/users/users.repository.test.ts (96%) rename packages/api/{src => tests}/modules/users/users.routes.test.ts (95%) rename packages/api/{src => tests}/modules/users/users.service.test.ts (92%) rename packages/api/{src => tests}/plugins/auth.plugin.test.ts (98%) rename packages/api/{src => tests}/plugins/household.plugin.test.ts (96%) rename packages/api/{src => tests}/plugins/mongoose.plugin.test.ts (94%) rename packages/api/{src => tests}/schemas/household.schema.test.ts (89%) rename packages/api/{src => tests}/schemas/medicine-product.schema.test.ts (90%) rename packages/api/{src => tests}/schemas/medicine.schema.test.ts (91%) rename packages/api/{src => tests}/schemas/product.schema.test.ts (94%) rename packages/api/{src => tests}/schemas/user.schema.test.ts (90%) rename packages/web/{src/app/(dashboard)/dashboard/__tests__ => tests/app/(dashboard)/dashboard}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/__tests__ => tests/app/(dashboard)}/layout.test.tsx (91%) rename packages/web/{src/app/(dashboard)/__tests__ => tests/app/(dashboard)}/loading.test.tsx (81%) rename packages/web/{src/app/(dashboard)/medicine-prices/__tests__ => tests/app/(dashboard)/medicine-prices}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/__tests__ => tests/app/(dashboard)/medicines}/ActivityTab.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/__tests__ => tests/app/(dashboard)/medicines}/CabinetTab.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/__tests__ => tests/app/(dashboard)/medicines}/LibraryTab.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/__tests__ => tests/app/(dashboard)/medicines}/OrganizerTab.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/__tests__ => tests/app/(dashboard)/medicines}/RegimensTab.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/[id]/__tests__ => tests/app/(dashboard)/medicines/[id]}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/medicines/activity/__tests__ => tests/app/(dashboard)/medicines/activity}/page.test.tsx (94%) rename packages/web/{src/app/(dashboard)/medicines/cabinet/__tests__ => tests/app/(dashboard)/medicines/cabinet}/page.test.tsx (94%) rename packages/web/{src/app/(dashboard)/medicines/library/__tests__ => tests/app/(dashboard)/medicines/library}/page.test.tsx (94%) rename packages/web/{src/app/(dashboard)/medicines/organizer/__tests__ => tests/app/(dashboard)/medicines/organizer}/page.test.tsx (94%) rename packages/web/{src/app/(dashboard)/medicines/__tests__ => tests/app/(dashboard)/medicines}/page.test.tsx (95%) rename packages/web/{src/app/(dashboard)/medicines/regimens/__tests__ => tests/app/(dashboard)/medicines/regimens}/page.test.tsx (94%) rename packages/web/{src/app/(dashboard)/medicines/schedule/__tests__ => tests/app/(dashboard)/medicines/schedule}/page.test.tsx (98%) rename packages/web/{src/app/(dashboard)/pantry/__tests__ => tests/app/(dashboard)/pantry}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/products/__tests__ => tests/app/(dashboard)/products}/ImportDialog.test.tsx (98%) rename packages/web/{src/app/(dashboard)/products/__tests__ => tests/app/(dashboard)/products}/ProductModal.test.tsx (99%) rename packages/web/{src/app/(dashboard)/products/__tests__ => tests/app/(dashboard)/products}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/purchases/__tests__ => tests/app/(dashboard)/purchases}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/recipes/__tests__ => tests/app/(dashboard)/recipes}/RecipeEditor.test.tsx (98%) rename packages/web/{src/app/(dashboard)/recipes/[id]/edit/__tests__ => tests/app/(dashboard)/recipes/[id]/edit}/page.test.tsx (96%) rename packages/web/{src/app/(dashboard)/recipes/[id]/__tests__ => tests/app/(dashboard)/recipes/[id]}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/recipes/new/__tests__ => tests/app/(dashboard)/recipes/new}/page.test.tsx (95%) rename packages/web/{src/app/(dashboard)/recipes/__tests__ => tests/app/(dashboard)/recipes}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/refills/__tests__ => tests/app/(dashboard)/refills}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/settings/__tests__ => tests/app/(dashboard)/settings}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/shopping-lists/[id]/__tests__ => tests/app/(dashboard)/shopping-lists/[id]}/page.test.tsx (98%) rename packages/web/{src/app/(dashboard)/shopping-lists/__tests__ => tests/app/(dashboard)/shopping-lists}/page.test.tsx (99%) rename packages/web/{src/app/(dashboard)/shopping-lists/prices/__tests__ => tests/app/(dashboard)/shopping-lists/prices}/page.test.tsx (96%) rename packages/web/{src/app/(dashboard)/stores/__tests__ => tests/app/(dashboard)/stores}/page.test.tsx (99%) rename packages/web/{src/app/__tests__ => tests/app}/page.test.tsx (93%) rename packages/web/{src/components/__tests__ => tests/components}/Sidebar.test.tsx (97%) rename packages/web/{src/components/__tests__ => tests/components}/ThemeProvider.test.tsx (96%) rename packages/web/{src/components/__tests__ => tests/components}/TopBar.test.tsx (94%) rename packages/web/{src/components/layout/__tests__ => tests/components/layout}/PageHeaderContext.test.tsx (90%) rename packages/web/{src/components/__tests__ => tests/components}/ui.test.tsx (90%) rename packages/web/{src/components/ui/__tests__ => tests/components/ui}/index.test.ts (89%) rename packages/web/{src/lib/__tests__ => tests/lib}/useApi.test.ts (98%) rename packages/web/{src => tests}/lib/useShoppingListSync.test.ts (97%) rename packages/web/{src/services/__tests__ => tests/services}/api-client.test.ts (97%) rename packages/web/{src/services/__tests__ => tests/services}/cabinet-events.test.ts (96%) rename packages/web/{src/services/__tests__ => tests/services}/cabinet.test.ts (97%) rename packages/web/{src/services/__tests__ => tests/services}/households.test.ts (94%) rename packages/web/{src => tests}/services/meal-plans.test.ts (92%) rename packages/web/{src/services/__tests__ => tests/services}/medicine-prices.test.ts (96%) rename packages/web/{src/services/__tests__ => tests/services}/medicines.test.ts (97%) rename packages/web/{src => tests}/services/nutrition-targets.test.ts (85%) rename packages/web/{src/services/__tests__ => tests/services}/organizer.test.ts (96%) rename packages/web/{src/services/__tests__ => tests/services}/pantry.test.ts (98%) rename packages/web/{src => tests}/services/prices.test.ts (90%) rename packages/web/{src/services/__tests__ => tests/services}/products.test.ts (98%) rename packages/web/{src/services/__tests__ => tests/services}/purchases.test.ts (96%) rename packages/web/{src/services/__tests__ => tests/services}/recipes.test.ts (97%) rename packages/web/{src/services/__tests__ => tests/services}/refills.test.ts (97%) rename packages/web/{src/services/__tests__ => tests/services}/regimens.test.ts (96%) rename packages/web/{src => tests}/services/shopping-lists.test.ts (94%) rename packages/web/{src/services/__tests__ => tests/services}/stores.test.ts (95%) diff --git a/ANTIGRAVITY.md b/ANTIGRAVITY.md index a0ea314..009cb66 100644 --- a/ANTIGRAVITY.md +++ b/ANTIGRAVITY.md @@ -46,21 +46,24 @@ docker compose -f docker/docker-compose.yml up -d # Start all services docker compose -f docker/docker-compose.yml down # Stop all services ``` -## Implementation Workflow +## Implementation Workflow (TDD-First) Every feature or phase implementation MUST start with a rigorous planning and design phase: 0. **Mandatory Planning (Grilling)**: Before starting any feature or bug fix, you MUST invoke the `grill-with-docs` skill. - Run the skill using: `view_file` on `.agents/skills/grill-with-docs/SKILL.md` and follow its instructions. - - This session will stress-test your plan against the existing domain model, terminology, and documentation (`CONTEXT.md`, ADRs). - - Decisions must be crystallized and documentation (glossary/ADRs) updated before moving to implementation. + - Stress-test your plan against the existing domain model, terminology, and documentation (`CONTEXT.md`, ADRs, `docs/tdd-transition-plan.md`). + - Decisions must be crystallized and documentation updated before moving to implementation. -1. **Vertical Slice Implementation**: Follow this approach for the actual build: -2. **Database Layer**: Create the Mongoose schema and Repository in `packages/api`. All reads must use `.lean().exec()`. -3. **Service Layer**: Implement business logic in the Service class, using Awilix for constructor injection. Add unit tests. -4. **Route Layer**: Create the Fastify route plugin and register it. Add route tests (using `app.inject`). -5. **Web API Client**: Implement the frontend service in `packages/web/src/services/`. Add unit tests. -6. **Web UI**: Create the Next.js pages and components. Use Server Components by default. Add component tests (React Testing Library). +1. **Strict TDD Cycle**: All code changes must strictly follow the Red-Green-Refactor loop as detailed in [TDD Guide](docs/instructions/tdd.md). Do not write any production code before writing its failing test. + +2. **Vertical Slice TDD Order**: Build each vertical slice following this exact test-first sequence: + - **Shared Layer**: Write validation schema tests first in `packages/shared/tests/` -> Implement schemas in `packages/shared/src/`. + - **Database Layer**: Write integration tests in `packages/api/tests/` using `mongodb-memory-server` -> Implement Mongoose schemas and Repository in `packages/api/src/`. All reads must use `.lean().exec()`. + - **Service Layer**: Write unit tests in `packages/api/tests/` using `createMockRepository` -> Implement business logic Service class in `packages/api/src/` with Awilix constructor injection. + - **Route Layer**: Write route tests in `packages/api/tests/` using `app.inject` -> Implement Fastify route plugin in `packages/api/src/`. + - **Web API Client**: Write service unit tests in `packages/web/tests/` using MSW mocking -> Implement the service client in `packages/web/src/`. + - **Web UI**: Write component tests in `packages/web/tests/` using React Testing Library -> Implement Next.js pages and components in `packages/web/src/`. Use Server Components by default. **Verification Gate (Mandatory)**: Before considering a task complete, run: diff --git a/docs/instructions/conventions.md b/docs/instructions/conventions.md index e7fcbee..019da5d 100644 --- a/docs/instructions/conventions.md +++ b/docs/instructions/conventions.md @@ -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) diff --git a/docs/instructions/tdd.md b/docs/instructions/tdd.md new file mode 100644 index 0000000..2c64a2c --- /dev/null +++ b/docs/instructions/tdd.md @@ -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//.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//`. 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>; + + 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//.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//.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//.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/.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)//.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 +``` diff --git a/docs/tdd-transition-plan.md b/docs/tdd-transition-plan.md new file mode 100644 index 0000000..6e8dbc0 --- /dev/null +++ b/docs/tdd-transition-plan.md @@ -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(repoClass: new (...args: any[]) => T): Record { + const methods = Object.getOwnPropertyNames(repoClass.prototype).filter( + (name) => name !== 'constructor' && typeof repoClass.prototype[name] === 'function' + ); + + const mock: Record = {}; + for (const method of methods) { + mock[method] = vi.fn(); + } + return mock as Record; +} +``` + +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. diff --git a/package.json b/package.json index 2f1d4e0..b3c95a6 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "turbo run lint", "lint-fix": "turbo run lint-fix", "test": "turbo run test", + "test:watch": "turbo run test:watch", "test:cov": "turbo run test:cov", "typecheck": "turbo run typecheck", "clean": "turbo run clean", diff --git a/packages/api/src/modules/meal-plans/meal-plans.routes.ts b/packages/api/src/modules/meal-plans/meal-plans.routes.ts index ab85735..dc0169f 100644 --- a/packages/api/src/modules/meal-plans/meal-plans.routes.ts +++ b/packages/api/src/modules/meal-plans/meal-plans.routes.ts @@ -129,11 +129,13 @@ export default fp( mealPlanService: asClass(MealPlanService, { lifetime: Lifetime.SINGLETON }), suggestionEngineService: asClass(SuggestionEngineService, { lifetime: Lifetime.SINGLETON }), shoppingGapService: asClass(ShoppingGapService, { lifetime: Lifetime.SINGLETON }), - + // Make sure other module deps are available for classes instantiated by SuggestionEngine/ShoppingGap recipesRepository: asClass(RecipesRepository, { lifetime: Lifetime.SINGLETON }), pantryRepository: asClass(PantryRepository, { lifetime: Lifetime.SINGLETON }), - nutritionTargetRepository: asClass(NutritionTargetRepository, { lifetime: Lifetime.SINGLETON }), + nutritionTargetRepository: asClass(NutritionTargetRepository, { + lifetime: Lifetime.SINGLETON, + }), productsRepository: asClass(ProductsRepository, { lifetime: Lifetime.SINGLETON }), }); @@ -181,7 +183,7 @@ export default fp( variety: z.number(), }), reasoning: z.array(z.string()), - }) + }), ), }, }, @@ -190,7 +192,7 @@ export default fp( const suggestions = await engine.getSuggestions( request.params.householdId, request.user.keycloakId, - { limit: request.query.limit } + { limit: request.query.limit }, ); return reply.send(suggestions); }, @@ -215,7 +217,7 @@ export default fp( const service = fastify.diContainer.resolve('mealPlanService'); const plan = await service.getByWeek( request.params.householdId, - request.params.weekStartDate + request.params.weekStartDate, ); if (!plan) { return reply.status(200).send({ message: 'No meal plan scheduled for this week' }); @@ -253,7 +255,7 @@ export default fp( const plan = await service.create( request.params.householdId, request.user.keycloakId, - request.body + request.body, ); return reply.status(201).send(toPlanResponse(plan as AnyPlanDoc)); }, @@ -273,7 +275,7 @@ export default fp( const plan = await service.update( request.params.id, request.params.householdId, - request.body + request.body, ); return reply.send(toPlanResponse(plan as AnyPlanDoc)); }, @@ -295,7 +297,7 @@ export default fp( const plan = await service.updateStatus( request.params.id, request.params.householdId, - request.body.status + request.body.status, ); return reply.send(toPlanResponse(plan as AnyPlanDoc)); }, @@ -334,17 +336,14 @@ export default fp( pantryQuantity: z.number(), missingQuantity: z.number(), unit: z.string(), - }) + }), ), }), }, }, handler: async (request, reply) => { const service = fastify.diContainer.resolve('shoppingGapService'); - const result = await service.calculateGap( - request.params.householdId, - request.params.id - ); + const result = await service.calculateGap(request.params.householdId, request.params.id); return reply.send(result); }, }); @@ -352,5 +351,5 @@ export default fp( { name: 'meal-plans-routes', dependencies: ['auth-plugin'], - } + }, ); diff --git a/packages/api/src/modules/meal-plans/meal-plans.service.ts b/packages/api/src/modules/meal-plans/meal-plans.service.ts index bc7cf65..6ce996b 100644 --- a/packages/api/src/modules/meal-plans/meal-plans.service.ts +++ b/packages/api/src/modules/meal-plans/meal-plans.service.ts @@ -8,7 +8,7 @@ import type { NutritionInfo, MealPlanDaySchema, } from '@meshitrack/shared'; -import { z } from 'zod/v4'; +import { type z } from 'zod/v4'; type MealPlanDay = z.infer; @@ -39,19 +39,12 @@ export class MealPlanService { return this.mealPlanRepository.findByWeek(householdId, weekStartDate); } - public async create( - householdId: string, - createdBy: string, - input: CreateMealPlanInput - ) { + public async create(householdId: string, createdBy: string, input: CreateMealPlanInput) { // Prevent overlapping meal plans for same household/week - const existing = await this.mealPlanRepository.findByWeek( - householdId, - input.weekStartDate - ); + const existing = await this.mealPlanRepository.findByWeek(householdId, input.weekStartDate); if (existing) { throw new BadRequestError( - `A meal plan already exists for household ${householdId} starting ${input.weekStartDate}` + `A meal plan already exists for household ${householdId} starting ${input.weekStartDate}`, ); } @@ -66,11 +59,7 @@ export class MealPlanService { }); } - public async update( - id: string, - householdId: string, - input: UpdateMealPlanInput - ) { + public async update(id: string, householdId: string, input: UpdateMealPlanInput) { const existing = await this.getById(id, householdId); const data: Record = {}; @@ -90,11 +79,7 @@ export class MealPlanService { return updated; } - public async updateStatus( - id: string, - householdId: string, - status: MealPlanStatus - ) { + public async updateStatus(id: string, householdId: string, status: MealPlanStatus) { await this.getById(id, householdId); const updated = await this.mealPlanRepository.updateStatus(id, householdId, status); if (!updated) { diff --git a/packages/api/src/modules/meal-plans/shopping-gap.service.ts b/packages/api/src/modules/meal-plans/shopping-gap.service.ts index e9e8b98..d8a4565 100644 --- a/packages/api/src/modules/meal-plans/shopping-gap.service.ts +++ b/packages/api/src/modules/meal-plans/shopping-gap.service.ts @@ -44,10 +44,7 @@ export class ShoppingGapService { this.productsRepository = productsRepository; } - public async calculateGap( - householdId: string, - mealPlanId: string - ): Promise { + public async calculateGap(householdId: string, mealPlanId: string): Promise { const plan = await this.mealPlanRepository.findById(mealPlanId, householdId); if (!plan) { throw new NotFoundError('Meal plan not found'); @@ -75,7 +72,7 @@ export class ShoppingGapService { // 2. Fetch all referenced recipes in parallel const recipeIds = Array.from(recipeIdSet); const recipeDocs = await Promise.all( - recipeIds.map((id) => this.recipesRepository.findById(id, householdId)) + recipeIds.map((id) => this.recipesRepository.findById(id, householdId)), ); const recipesMap = new Map(); for (const doc of recipeDocs) { @@ -116,7 +113,7 @@ export class ShoppingGapService { const activePantry = await this.pantryRepository.findActiveByHousehold(householdId); const requiredProductIds = Array.from(requiredMap.keys()); const productDocs = await this.productsRepository.findByIds(householdId, requiredProductIds); - + const productsInfoMap = new Map(); for (const p of productDocs) { productsInfoMap.set(p._id.toString(), p); @@ -136,7 +133,7 @@ export class ShoppingGapService { for (const [productId, req] of requiredMap.entries()) { const pantryQty = pantryMap.get(productId) || 0; - + if (pantryQty < req.qty) { const productDoc = productsInfoMap.get(productId); const productName = productDoc?.name || 'Unknown Ingredient'; diff --git a/packages/api/src/modules/meal-plans/suggestion-engine.service.ts b/packages/api/src/modules/meal-plans/suggestion-engine.service.ts index 2c38772..c162b7b 100644 --- a/packages/api/src/modules/meal-plans/suggestion-engine.service.ts +++ b/packages/api/src/modules/meal-plans/suggestion-engine.service.ts @@ -44,7 +44,7 @@ export class SuggestionEngineService { public async getSuggestions( householdId: string, userId: string, - options: { limit?: number } = {} + options: { limit?: number } = {}, ): Promise { const limit = options.limit ?? 5; @@ -132,7 +132,7 @@ export class SuggestionEngineService { recipeId, pantryInventory, activeTarget as Record | null, - lastEaten + lastEaten, ); const totalScore = @@ -153,9 +153,7 @@ export class SuggestionEngineService { } // 5. Sort descending and limit - return scoredList - .sort((a, b) => b.totalScore - a.totalScore) - .slice(0, limit); + return scoredList.sort((a, b) => b.totalScore - a.totalScore).slice(0, limit); } private scoreRecipe( @@ -164,11 +162,11 @@ export class SuggestionEngineService { recipeId: string, inventory: Map, target: Record | null, - lastEaten: Map + lastEaten: Map, ) { // Filter non-optional ingredients const requiredIngs = ingredients.filter((ing) => !ing.isOptional); - + // -- COVERAGE & URGENCY -- let coverageScore = 1.0; let urgencyScore = 0.0; @@ -262,7 +260,7 @@ export class SuggestionEngineService { private generateReasoning( scores: { coverage: number; urgency: number; nutrition: number; variety: number }, - recipeName: string + recipeName: string, ): string[] { const reasons: string[] = []; diff --git a/packages/api/src/modules/nutrition-targets/nutrition-target.repository.ts b/packages/api/src/modules/nutrition-targets/nutrition-target.repository.ts index 66593db..f72604b 100644 --- a/packages/api/src/modules/nutrition-targets/nutrition-target.repository.ts +++ b/packages/api/src/modules/nutrition-targets/nutrition-target.repository.ts @@ -2,16 +2,11 @@ import { NutritionTargetModel } from '../../schemas/nutrition-target.schema.js'; export class NutritionTargetRepository { public async findByUser(userId: string, householdId: string) { - return NutritionTargetModel.findOne({ userId, householdId, isActive: true }) - .lean() - .exec(); + return NutritionTargetModel.findOne({ userId, householdId, isActive: true }).lean().exec(); } public async findAllByUser(userId: string, householdId: string) { - return NutritionTargetModel.find({ userId, householdId }) - .sort({ createdAt: -1 }) - .lean() - .exec(); + return NutritionTargetModel.find({ userId, householdId }).sort({ createdAt: -1 }).lean().exec(); } public async create(data: Record) { @@ -23,15 +18,20 @@ export class NutritionTargetRepository { public async deactivateAllForUser(userId: string, householdId: string) { return NutritionTargetModel.updateMany( { userId, householdId, isActive: true }, - { $set: { isActive: false } } + { $set: { isActive: false } }, ).exec(); } - public async update(id: string, userId: string, householdId: string, data: Record) { + public async update( + id: string, + userId: string, + householdId: string, + data: Record, + ) { return NutritionTargetModel.findOneAndUpdate( { _id: id, userId, householdId }, { $set: data }, - { new: true, lean: true } + { new: true, lean: true }, ).exec(); } } diff --git a/packages/api/src/modules/nutrition-targets/nutrition-target.routes.ts b/packages/api/src/modules/nutrition-targets/nutrition-target.routes.ts index 0447be1..c11d441 100644 --- a/packages/api/src/modules/nutrition-targets/nutrition-target.routes.ts +++ b/packages/api/src/modules/nutrition-targets/nutrition-target.routes.ts @@ -2,10 +2,7 @@ import fp from 'fastify-plugin'; import { asClass, Lifetime } from 'awilix'; import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import { z } from 'zod/v4'; -import { - NutritionTargetSchema, - NutritionTargetResponseSchema, -} from '@meshitrack/shared'; +import { NutritionTargetSchema, NutritionTargetResponseSchema } from '@meshitrack/shared'; import { NutritionTargetRepository } from './nutrition-target.repository.js'; import { NutritionTargetService } from './nutrition-target.service.js'; @@ -33,7 +30,9 @@ function toIso(v: string | Date): string { return typeof v === 'string' ? v : v.toISOString(); } -function toNutritionTargetResponse(doc: AnyTargetDoc): z.infer { +function toNutritionTargetResponse( + doc: AnyTargetDoc, +): z.infer { return { _id: toStr(doc._id), userId: doc.userId, @@ -61,7 +60,9 @@ declare module '@fastify/awilix' { export default fp( async (fastify) => { fastify.diContainer.register({ - nutritionTargetRepository: asClass(NutritionTargetRepository, { lifetime: Lifetime.SINGLETON }), + nutritionTargetRepository: asClass(NutritionTargetRepository, { + lifetime: Lifetime.SINGLETON, + }), nutritionTargetService: asClass(NutritionTargetService, { lifetime: Lifetime.SINGLETON }), }); @@ -85,11 +86,11 @@ export default fp( const service = fastify.diContainer.resolve('nutritionTargetService'); const userId = request.user.keycloakId; const target = await service.getActiveByUser(userId, request.params.householdId); - + if (!target) { return reply.status(200).send({ message: 'No active targets defined' }); } - + return reply.send(toNutritionTargetResponse(target as AnyTargetDoc)); }, }); @@ -124,11 +125,7 @@ export default fp( handler: async (request, reply) => { const service = fastify.diContainer.resolve('nutritionTargetService'); const userId = request.user.keycloakId; - const target = await service.setTarget( - userId, - request.params.householdId, - request.body - ); + const target = await service.setTarget(userId, request.params.householdId, request.body); return reply.status(201).send(toNutritionTargetResponse(target as AnyTargetDoc)); }, }); @@ -155,5 +152,5 @@ export default fp( { name: 'nutrition-targets-routes', dependencies: ['auth-plugin'], - } + }, ); diff --git a/packages/api/src/modules/nutrition-targets/nutrition-target.service.ts b/packages/api/src/modules/nutrition-targets/nutrition-target.service.ts index 03e5901..a88f3d8 100644 --- a/packages/api/src/modules/nutrition-targets/nutrition-target.service.ts +++ b/packages/api/src/modules/nutrition-targets/nutrition-target.service.ts @@ -22,11 +22,7 @@ export class NutritionTargetService { return this.nutritionTargetRepository.findAllByUser(userId, householdId); } - public async setTarget( - userId: string, - householdId: string, - input: SetNutritionTargetInput - ) { + public async setTarget(userId: string, householdId: string, input: SetNutritionTargetInput) { // Maintain invariant: only one target is active per user per household if (input.isActive !== false) { await this.nutritionTargetRepository.deactivateAllForUser(userId, householdId); @@ -52,20 +48,20 @@ export class NutritionTargetService { switch (preset) { case 'loss': - proteinPct = 0.40; - carbsPct = 0.30; - fatPct = 0.30; + proteinPct = 0.4; + carbsPct = 0.3; + fatPct = 0.3; break; case 'gain': proteinPct = 0.25; - carbsPct = 0.50; + carbsPct = 0.5; fatPct = 0.25; break; case 'maintenance': default: - proteinPct = 0.30; - carbsPct = 0.40; - fatPct = 0.30; + proteinPct = 0.3; + carbsPct = 0.4; + fatPct = 0.3; break; } diff --git a/packages/api/src/modules/prices/prices.repository.ts b/packages/api/src/modules/prices/prices.repository.ts index 0c98f0b..9f5b200 100644 --- a/packages/api/src/modules/prices/prices.repository.ts +++ b/packages/api/src/modules/prices/prices.repository.ts @@ -27,13 +27,13 @@ export class PricesRepository { public async createMany(data: CreatePriceRecordData[]) { const records = await PriceRecordModel.insertMany(data); - return records.map(r => r.toObject()); + return records.map((r) => r.toObject()); } public async findByProduct( householdId: string, productId: string, - query: PriceHistoryQueryInput + query: PriceHistoryQueryInput, ) { const filter: Record = { householdId, productId }; diff --git a/packages/api/src/modules/prices/prices.routes.ts b/packages/api/src/modules/prices/prices.routes.ts index 84026bd..de8ba6a 100644 --- a/packages/api/src/modules/prices/prices.routes.ts +++ b/packages/api/src/modules/prices/prices.routes.ts @@ -95,7 +95,7 @@ export default fp( const record = await service.recordPrice( request.body, request.params.householdId, - request.user.keycloakId + request.user.keycloakId, ); return reply.status(201).send(toPriceRecordResponse(record)); }, @@ -114,7 +114,7 @@ export default fp( const records = await service.recordBulkPrices( request.body, request.params.householdId, - request.user.keycloakId + request.user.keycloakId, ); return reply.status(201).send(records.map(toPriceRecordResponse)); }, @@ -133,7 +133,7 @@ export default fp( const result = await service.getPriceHistory( request.params.productId, request.params.householdId, - request.query + request.query, ); return reply.send({ data: result.data.map(toPriceRecordResponse), @@ -153,7 +153,7 @@ export default fp( const service = fastify.diContainer.resolve('pricesService'); const results = await service.compareStores( request.params.productId, - request.params.householdId + request.params.householdId, ); return reply.send({ data: results.map((r) => ({ @@ -184,5 +184,5 @@ export default fp( { name: 'prices-routes', dependencies: ['auth-plugin'], - } + }, ); diff --git a/packages/api/src/modules/prices/prices.service.ts b/packages/api/src/modules/prices/prices.service.ts index 9ed53b7..0805d8e 100644 --- a/packages/api/src/modules/prices/prices.service.ts +++ b/packages/api/src/modules/prices/prices.service.ts @@ -28,11 +28,7 @@ export class PricesService { /** * Validates entity existence, computes pricePerUnit, and persists record */ - public async recordPrice( - data: CreatePriceRecordInput, - householdId: string, - userId: string - ) { + public async recordPrice(data: CreatePriceRecordInput, householdId: string, userId: string) { const [product, store] = await Promise.all([ this.productsRepository.findById(data.productId, householdId), this.storesRepository.findById(data.storeId, householdId), @@ -64,11 +60,7 @@ export class PricesService { /** * Ingests a list of purchased products in a single transaction */ - public async recordBulkPrices( - data: BulkPriceRecordInput, - householdId: string, - userId: string - ) { + public async recordBulkPrices(data: BulkPriceRecordInput, householdId: string, userId: string) { const store = await this.storesRepository.findById(data.storeId, householdId); if (!store) throw new NotFoundError(`Store not found: ${data.storeId}`); @@ -109,7 +101,7 @@ export class PricesService { public async getPriceHistory( productId: string, householdId: string, - query: PriceHistoryQueryInput + query: PriceHistoryQueryInput, ) { return this.pricesRepository.findByProduct(householdId, productId, query); } @@ -129,13 +121,16 @@ export class PricesService { public async estimatePrice( productId: string, householdId: string, - storeId?: string + storeId?: string, ): Promise { const latest = await this.pricesRepository.getLatestForProduct(householdId, productId, storeId); if (!latest) { // If a specific store was requested but has no history, fall back to the generic latest across all stores if (storeId) { - const genericLatest = await this.pricesRepository.getLatestForProduct(householdId, productId); + const genericLatest = await this.pricesRepository.getLatestForProduct( + householdId, + productId, + ); return genericLatest ? genericLatest.price : null; } return null; diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts b/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts index 23a45b8..47b57dd 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts +++ b/packages/api/src/modules/shopping-lists/shopping-lists.repository.ts @@ -34,7 +34,7 @@ export class ShoppingListsRepository { .sort({ createdAt: -1 }) .lean() .exec(); - return lists.map(l => this.sortItems(l)); + return lists.map((l) => this.sortItems(l)); } public async findById(id: string, householdId: string) { @@ -43,19 +43,24 @@ export class ShoppingListsRepository { } public async findActiveByHousehold(householdId: string) { - const lists = await ShoppingListModel.find({ householdId, status: { $in: ['active', 'shopping'] } }) + const lists = await ShoppingListModel.find({ + householdId, + status: { $in: ['active', 'shopping'] }, + }) .sort({ updatedAt: -1 }) .lean() .exec(); - return lists.map(l => this.sortItems(l)); + return lists.map((l) => this.sortItems(l)); } public async update(id: string, householdId: string, data: UpdateShoppingListInput) { const updated = await ShoppingListModel.findOneAndUpdate( { _id: id, householdId }, { $set: data }, - { new: true } - ).lean().exec(); + { new: true }, + ) + .lean() + .exec(); return this.sortItems(updated); } @@ -69,8 +74,10 @@ export class ShoppingListsRepository { const updated = await ShoppingListModel.findOneAndUpdate( { _id: id, householdId }, { $push: { items: item } }, - { new: true } - ).lean().exec(); + { new: true }, + ) + .lean() + .exec(); return this.sortItems(updated); } @@ -78,7 +85,7 @@ export class ShoppingListsRepository { id: string, householdId: string, itemId: string, - updates: Partial + updates: Partial, ) { const setUpdates: Record = {}; for (const [key, val] of Object.entries(updates)) { @@ -88,8 +95,10 @@ export class ShoppingListsRepository { const updated = await ShoppingListModel.findOneAndUpdate( { _id: id, householdId, 'items.id': itemId }, { $set: setUpdates }, - { new: true } - ).lean().exec(); + { new: true }, + ) + .lean() + .exec(); return this.sortItems(updated); } @@ -97,8 +106,10 @@ export class ShoppingListsRepository { const updated = await ShoppingListModel.findOneAndUpdate( { _id: id, householdId }, { $pull: { items: { id: itemId } } }, - { new: true } - ).lean().exec(); + { new: true }, + ) + .lean() + .exec(); return this.sortItems(updated); } } diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts b/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts index 4571087..33dd5e6 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts +++ b/packages/api/src/modules/shopping-lists/shopping-lists.routes.ts @@ -68,7 +68,7 @@ export default fp( fastify.diContainer.register({ shoppingListsRepository: asClass(ShoppingListsRepository, { lifetime: Lifetime.SINGLETON }), shoppingListsService: asClass(ShoppingListsService, { lifetime: Lifetime.SINGLETON }), - + // Cross-domain requirements to service the list workflow orchestrations shoppingGapService: asClass(ShoppingGapService, { lifetime: Lifetime.SINGLETON }), pantryService: asClass(PantryService, { lifetime: Lifetime.SINGLETON }), @@ -114,7 +114,7 @@ export default fp( const list = await service.create( request.body, request.params.householdId, - request.user.keycloakId + request.user.keycloakId, ); return reply.status(201).send(serializeList(list)); }, @@ -147,7 +147,7 @@ export default fp( const list = await service.update( request.params.id, request.params.householdId, - request.body + request.body, ); return reply.send(serializeList(list)); }, @@ -181,9 +181,9 @@ export default fp( const { list, addedItem } = await service.addItem( request.params.id, request.params.householdId, - request.body + request.body, ); - + // Emit real-time update notification to existing connected viewers broadcastToList(request.params.id, null as any, { type: 'ITEM_ADDED', @@ -209,11 +209,13 @@ export default fp( request.params.householdId, request.params.itemId, request.body, - request.user.keycloakId + request.user.keycloakId, ); // Broadcast the precise item differential state update to sibling websocket listeners - const matchedItem = updatedList.items.find((i: ShoppingItem) => i.id === request.params.itemId); + const matchedItem = updatedList.items.find( + (i: ShoppingItem) => i.id === request.params.itemId, + ); if (matchedItem) { broadcastToList(request.params.id, null as any, { type: 'ITEM_UPDATED', @@ -242,7 +244,7 @@ export default fp( const list = await service.removeItem( request.params.id, request.params.householdId, - request.params.itemId + request.params.itemId, ); broadcastToList(request.params.id, null as any, { @@ -268,7 +270,7 @@ export default fp( const list = await service.createFromMealPlan( request.params.mealPlanId, request.params.householdId, - request.user.keycloakId + request.user.keycloakId, ); return reply.status(201).send(serializeList(list)); }, @@ -286,7 +288,7 @@ export default fp( const results = await service.syncCheckedToPantry( request.params.id, request.params.householdId, - request.user.keycloakId + request.user.keycloakId, ); return reply.send(results); }, @@ -303,14 +305,14 @@ export default fp( const service = fastify.diContainer.resolve('shoppingListsService'); const comparison = await service.getStoreComparison( request.params.id, - request.params.householdId + request.params.householdId, ); return reply.send(comparison); }, }); // 4. Persist Collaborative WebSocket Handshakes - + /* v8 ignore start */ app.get( '/api/v1/households/:householdId/shopping-lists/:id/sync', @@ -318,7 +320,7 @@ export default fp( (connection: any, request: any) => { const socket = connection.socket; const listId = request.params.id; - + // Setup connection context if (!activeListSockets.has(listId)) { activeListSockets.set(listId, new Set()); @@ -330,7 +332,7 @@ export default fp( socket.on('message', async (messageBuffer: any) => { try { const payload = JSON.parse(messageBuffer.toString()); - + // Handlers for inbound events e.g. real-time toggle checks from frontends if (payload.type === 'TOGGLE_ITEM') { const service = fastify.diContainer.resolve('shoppingListsService'); @@ -339,10 +341,12 @@ export default fp( request.params.householdId, payload.itemId, { checked: payload.checked }, - request.user.keycloakId + request.user.keycloakId, ); - const matched = updatedList.items.find((it: ShoppingItem) => it.id === payload.itemId); + const matched = updatedList.items.find( + (it: ShoppingItem) => it.id === payload.itemId, + ); // Echo back differential confirmation to everyone else on the floor broadcastToList(listId, socket, { @@ -352,7 +356,7 @@ export default fp( checked: payload.checked, checkedAt: matched?.checkedAt?.toISOString(), checkedBy: matched?.checkedBy, - } + }, }); } } catch (err) { @@ -370,12 +374,12 @@ export default fp( } request.log.info({ listId }, 'Client severed sync handshake connection'); }); - } + }, ); /* v8 ignore stop */ }, { name: 'shopping-lists-routes', dependencies: ['auth-plugin'], - } + }, ); diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.service.ts b/packages/api/src/modules/shopping-lists/shopping-lists.service.ts index 3414d54..a028fd0 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.service.ts +++ b/packages/api/src/modules/shopping-lists/shopping-lists.service.ts @@ -13,7 +13,7 @@ import type { ShoppingItem, } from '@meshitrack/shared'; import { ShoppingListSourceType } from '@meshitrack/shared'; -import { StorageLocation, ServingUnit } from '@meshitrack/shared'; +import { StorageLocation, type ServingUnit } from '@meshitrack/shared'; import { NotFoundError } from '../../common/errors.js'; import { v4 as uuidv4 } from 'uuid'; @@ -158,10 +158,10 @@ export class ShoppingListsService { householdId: string, itemId: string, data: UpdateShoppingItemInput, - userId: string + userId: string, ) { const updates: Partial = { ...data }; - + if (data.checked !== undefined) { updates.checkedAt = data.checked ? new Date() : undefined; updates.checkedBy = data.checked ? userId : undefined; @@ -188,7 +188,7 @@ export class ShoppingListsService { if (!plan) throw new NotFoundError('Meal plan not found'); const report = await this.shoppingGapService.calculateGap(householdId, mealPlanId); - + const listItems: ShoppingItem[] = []; let runningTotal = 0; @@ -208,7 +208,10 @@ export class ShoppingListsService { }); } - const dateStr = new Date((plan as any).weekStartDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + const dateStr = new Date((plan as any).weekStartDate).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + }); const name = `Groceries for Week of ${dateStr}`; const newList = await this.shoppingListsRepository.create({ @@ -238,11 +241,13 @@ export class ShoppingListsService { */ public async syncCheckedToPantry(id: string, householdId: string, userId: string) { const list = await this.getById(id, householdId); - + let addedCount = 0; let pricesLogged = 0; - const pendingItems = list.items.filter((it: ShoppingItem) => it.checked && !it.addedToPantry && it.productId); + const pendingItems = list.items.filter( + (it: ShoppingItem) => it.checked && !it.addedToPantry && it.productId, + ); for (const item of pendingItems) { // 1. Promote item to active pantry @@ -257,7 +262,7 @@ export class ShoppingListsService { notes: item.notes || undefined, }, householdId, - userId + userId, ); addedCount++; @@ -275,7 +280,7 @@ export class ShoppingListsService { currency: 'USD', }, householdId, - userId + userId, ); pricesLogged++; } @@ -347,7 +352,9 @@ export class ShoppingListsService { } // Sort to surface the cheapest/fullest single store options first - singleStoreOptions.sort((a, b) => b.itemsCovered - a.itemsCovered || a.estimatedTotal - b.estimatedTotal); + singleStoreOptions.sort( + (a, b) => b.itemsCovered - a.itemsCovered || a.estimatedTotal - b.estimatedTotal, + ); return { singleStoreOptions }; } diff --git a/packages/api/src/schemas/price-record.schema.ts b/packages/api/src/schemas/price-record.schema.ts index 700fd25..25009cb 100644 --- a/packages/api/src/schemas/price-record.schema.ts +++ b/packages/api/src/schemas/price-record.schema.ts @@ -17,7 +17,7 @@ const priceRecordSchema = new mongoose.Schema( notes: { type: String }, createdBy: { type: String, required: true }, }, - { timestamps: { createdAt: true, updatedAt: false } } + { timestamps: { createdAt: true, updatedAt: false } }, ); // Performance Indexes for Lookup Speed and Aggregations diff --git a/packages/api/src/schemas/shopping-list.schema.ts b/packages/api/src/schemas/shopping-list.schema.ts index f31f64a..98337cc 100644 --- a/packages/api/src/schemas/shopping-list.schema.ts +++ b/packages/api/src/schemas/shopping-list.schema.ts @@ -17,7 +17,7 @@ const shoppingItemSchema = new mongoose.Schema( category: { type: String }, addedToPantry: { type: Boolean, required: true, default: false }, }, - { _id: false } + { _id: false }, ); const shoppingListSourceSchema = new mongoose.Schema( @@ -25,7 +25,7 @@ const shoppingListSourceSchema = new mongoose.Schema( type: { type: String, required: true }, // values from ShoppingListSourceType referenceId: { type: String }, }, - { _id: false } + { _id: false }, ); const shoppingListSchema = new mongoose.Schema( @@ -44,7 +44,7 @@ const shoppingListSchema = new mongoose.Schema( completedAt: { type: Date }, createdBy: { type: String, required: true }, }, - { timestamps: true } + { timestamps: true }, ); shoppingListSchema.index({ householdId: 1, status: 1 }); diff --git a/packages/api/src/common/errors.test.ts b/packages/api/tests/common/errors.test.ts similarity index 98% rename from packages/api/src/common/errors.test.ts rename to packages/api/tests/common/errors.test.ts index 580e3cc..987503d 100644 --- a/packages/api/src/common/errors.test.ts +++ b/packages/api/tests/common/errors.test.ts @@ -6,7 +6,7 @@ import { ForbiddenError, ConflictError, BadRequestError, -} from './errors.js'; +} from '../../src/common/errors.js'; describe(AppError.name, () => { it('sets statusCode, error, message, and details', () => { diff --git a/packages/api/src/config/configuration.test.ts b/packages/api/tests/config/configuration.test.ts similarity index 90% rename from packages/api/src/config/configuration.test.ts rename to packages/api/tests/config/configuration.test.ts index 3a9758f..60d5c28 100644 --- a/packages/api/src/config/configuration.test.ts +++ b/packages/api/tests/config/configuration.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import config from './configuration.js'; +import config from '../../src/config/configuration.js'; describe('configuration', () => { it('exports default config values', () => { diff --git a/packages/api/tests/helpers/mock-repository.ts b/packages/api/tests/helpers/mock-repository.ts new file mode 100644 index 0000000..61756b7 --- /dev/null +++ b/packages/api/tests/helpers/mock-repository.ts @@ -0,0 +1,34 @@ +import { vi } from 'vitest'; + +/** + * Dynamically creates a fully mocked repository from a repository class. + * Recursively walks the prototype chain (inheritance-aware) to gather all methods, + * and assigns them a Vitest mock function (vi.fn()). + * + * @param repoClass The repository class constructor to mock + * @returns An object with all methods mocked as vi.fn() + * + * @example + * const mockRepo = createMockRepository(ProductsRepository); + * mockRepo.findById.mockResolvedValue(mockProduct); + */ +export function createMockRepository( + repoClass: new (...args: any[]) => T +): Record { + const mock: Record = {}; + let proto = repoClass.prototype; + + while (proto && proto !== Object.prototype) { + const methods = Object.getOwnPropertyNames(proto).filter( + (name) => name !== 'constructor' && typeof (proto as any)[name] === 'function' + ); + for (const method of methods) { + if (!(method in mock)) { + mock[method] = vi.fn(); + } + } + proto = Object.getPrototypeOf(proto); + } + + return mock as Record; +} diff --git a/packages/api/src/main.test.ts b/packages/api/tests/main.test.ts similarity index 98% rename from packages/api/src/main.test.ts rename to packages/api/tests/main.test.ts index 1fd98ad..76a56f8 100644 --- a/packages/api/src/main.test.ts +++ b/packages/api/tests/main.test.ts @@ -63,9 +63,9 @@ vi.mock('jose', () => ({ jwtVerify: vi.fn(), })); -import { buildApp } from './main.js'; +import { buildApp } from '../src/main.js'; import * as jose from 'jose'; -import { NotFoundError } from './common/errors.js'; +import { NotFoundError } from '../src/common/errors.js'; describe('buildApp', () => { beforeEach(() => { diff --git a/packages/api/src/modules/cabinet-events/cabinet-events.repository.test.ts b/packages/api/tests/modules/cabinet-events/cabinet-events.repository.test.ts similarity index 98% rename from packages/api/src/modules/cabinet-events/cabinet-events.repository.test.ts rename to packages/api/tests/modules/cabinet-events/cabinet-events.repository.test.ts index cfe5eb1..fee5b15 100644 --- a/packages/api/src/modules/cabinet-events/cabinet-events.repository.test.ts +++ b/packages/api/tests/modules/cabinet-events/cabinet-events.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockSave, mockInsertMany, mockAggregate } = vi.hoisted(() => ( mockAggregate: vi.fn(), })); -vi.mock('../../schemas/cabinet-event.schema.js', () => { +vi.mock('../../../src/schemas/cabinet-event.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -36,7 +36,7 @@ vi.mock('../../schemas/cabinet-event.schema.js', () => { return { CabinetEventModel: FakeModel }; }); -import { CabinetEventsRepository } from './cabinet-events.repository.js'; +import { CabinetEventsRepository } from '../../../src/modules/cabinet-events/cabinet-events.repository.js'; describe(CabinetEventsRepository.name, () => { let repo: CabinetEventsRepository; diff --git a/packages/api/src/modules/cabinet-events/cabinet-events.routes.test.ts b/packages/api/tests/modules/cabinet-events/cabinet-events.routes.test.ts similarity index 95% rename from packages/api/src/modules/cabinet-events/cabinet-events.routes.test.ts rename to packages/api/tests/modules/cabinet-events/cabinet-events.routes.test.ts index 9b7bef2..6d4ecd4 100644 --- a/packages/api/src/modules/cabinet-events/cabinet-events.routes.test.ts +++ b/packages/api/tests/modules/cabinet-events/cabinet-events.routes.test.ts @@ -24,7 +24,7 @@ const { mockListEvents, mockGetEventsByItem, mockGetSpendingSummary } = vi.hoist mockGetSpendingSummary: vi.fn(), })); -vi.mock('./cabinet-events.repository.js', () => ({ +vi.mock('../../../src/modules/cabinet-events/cabinet-events.repository.js', () => ({ CabinetEventsRepository: class { create = vi.fn(); createMany = vi.fn(); @@ -35,7 +35,7 @@ vi.mock('./cabinet-events.repository.js', () => ({ }, })); -vi.mock('./cabinet-events.service.js', () => ({ +vi.mock('../../../src/modules/cabinet-events/cabinet-events.service.js', () => ({ CabinetEventsService: class { logEvent = vi.fn(); logEvents = vi.fn(); @@ -46,17 +46,17 @@ vi.mock('./cabinet-events.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import cabinetEventsRoutes from './cabinet-events.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import cabinetEventsRoutes from '../../../src/modules/cabinet-events/cabinet-events.routes.js'; function makeFakeEvent(overrides = {}) { return { diff --git a/packages/api/src/modules/cabinet-events/cabinet-events.service.test.ts b/packages/api/tests/modules/cabinet-events/cabinet-events.service.test.ts similarity index 97% rename from packages/api/src/modules/cabinet-events/cabinet-events.service.test.ts rename to packages/api/tests/modules/cabinet-events/cabinet-events.service.test.ts index 6b52eb2..8829df9 100644 --- a/packages/api/src/modules/cabinet-events/cabinet-events.service.test.ts +++ b/packages/api/tests/modules/cabinet-events/cabinet-events.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { CabinetEventsService } from './cabinet-events.service.js'; +import { CabinetEventsService } from '../../../src/modules/cabinet-events/cabinet-events.service.js'; describe(CabinetEventsService.name, () => { const mockCabinetEventsRepo = { diff --git a/packages/api/src/modules/cabinet/cabinet.repository.test.ts b/packages/api/tests/modules/cabinet/cabinet.repository.test.ts similarity index 98% rename from packages/api/src/modules/cabinet/cabinet.repository.test.ts rename to packages/api/tests/modules/cabinet/cabinet.repository.test.ts index 75ee231..aeaf3cc 100644 --- a/packages/api/src/modules/cabinet/cabinet.repository.test.ts +++ b/packages/api/tests/modules/cabinet/cabinet.repository.test.ts @@ -10,7 +10,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave, mockCountDocument mockAggregate: vi.fn(), })); -vi.mock('../../schemas/cabinet-item.schema.js', () => { +vi.mock('../../../src/schemas/cabinet-item.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -54,7 +54,7 @@ vi.mock('../../schemas/cabinet-item.schema.js', () => { return { CabinetItemModel: FakeModel }; }); -import { CabinetRepository } from './cabinet.repository.js'; +import { CabinetRepository } from '../../../src/modules/cabinet/cabinet.repository.js'; describe(CabinetRepository.name, () => { let repo: CabinetRepository; diff --git a/packages/api/src/modules/cabinet/cabinet.routes.test.ts b/packages/api/tests/modules/cabinet/cabinet.routes.test.ts similarity index 91% rename from packages/api/src/modules/cabinet/cabinet.routes.test.ts rename to packages/api/tests/modules/cabinet/cabinet.routes.test.ts index 5eadebb..58a639a 100644 --- a/packages/api/src/modules/cabinet/cabinet.routes.test.ts +++ b/packages/api/tests/modules/cabinet/cabinet.routes.test.ts @@ -45,7 +45,7 @@ const { mockFindActiveByMedicineForFEFO: vi.fn(), })); -vi.mock('./cabinet.repository.js', () => ({ +vi.mock('../../../src/modules/cabinet/cabinet.repository.js', () => ({ CabinetRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -65,7 +65,7 @@ const { mockMedicineFindById } = vi.hoisted(() => ({ mockMedicineFindById: vi.fn(), })); -vi.mock('../medicines/medicines.repository.js', () => ({ +vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({ MedicinesRepository: class { findById = mockMedicineFindById; findByHousehold = vi.fn(); @@ -80,7 +80,7 @@ const { mockProductFindById } = vi.hoisted(() => ({ mockProductFindById: vi.fn(), })); -vi.mock('../medicine-products/medicine-products.repository.js', () => ({ +vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({ MedicineProductsRepository: class { findById = mockProductFindById; findByMedicine = vi.fn(); @@ -91,7 +91,7 @@ vi.mock('../medicine-products/medicine-products.repository.js', () => ({ }, })); -vi.mock('../medicine-products/medicine-products.service.js', () => ({ +vi.mock('../../../src/modules/medicine-products/medicine-products.service.js', () => ({ MedicineProductsService: class { listByMedicine = vi.fn(); getById = vi.fn(); @@ -101,7 +101,7 @@ vi.mock('../medicine-products/medicine-products.service.js', () => ({ }, })); -vi.mock('../medicines/medicines.service.js', () => ({ +vi.mock('../../../src/modules/medicines/medicines.service.js', () => ({ MedicinesService: class { list = vi.fn(); getById = vi.fn(); @@ -111,7 +111,7 @@ vi.mock('../medicines/medicines.service.js', () => ({ }, })); -vi.mock('../cabinet-events/cabinet-events.repository.js', () => ({ +vi.mock('../../../src/modules/cabinet-events/cabinet-events.repository.js', () => ({ CabinetEventsRepository: class { create = vi.fn(); createMany = vi.fn(); @@ -122,7 +122,7 @@ vi.mock('../cabinet-events/cabinet-events.repository.js', () => ({ }, })); -vi.mock('../cabinet-events/cabinet-events.service.js', () => ({ +vi.mock('../../../src/modules/cabinet-events/cabinet-events.service.js', () => ({ CabinetEventsService: class { logEvent = vi.fn(); logEvents = vi.fn(); @@ -133,20 +133,20 @@ vi.mock('../cabinet-events/cabinet-events.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import medicinesRoutes from '../medicines/medicines.routes.js'; -import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js'; -import cabinetEventsRoutes from '../cabinet-events/cabinet-events.routes.js'; -import cabinetRoutes from './cabinet.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js'; +import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js'; +import cabinetEventsRoutes from '../../../src/modules/cabinet-events/cabinet-events.routes.js'; +import cabinetRoutes from '../../../src/modules/cabinet/cabinet.routes.js'; function makeFakeCabinetItem(overrides = {}) { return { diff --git a/packages/api/src/modules/cabinet/cabinet.service.test.ts b/packages/api/tests/modules/cabinet/cabinet.service.test.ts similarity index 99% rename from packages/api/src/modules/cabinet/cabinet.service.test.ts rename to packages/api/tests/modules/cabinet/cabinet.service.test.ts index 93d881b..da0ea2e 100644 --- a/packages/api/src/modules/cabinet/cabinet.service.test.ts +++ b/packages/api/tests/modules/cabinet/cabinet.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { CabinetService } from './cabinet.service.js'; +import { CabinetService } from '../../../src/modules/cabinet/cabinet.service.js'; import { CabinetEventType, CabinetEventSourceType } from '@meshitrack/shared'; describe(CabinetService.name, () => { diff --git a/packages/api/src/modules/freshness-rules/freshness-rules.repository.test.ts b/packages/api/tests/modules/freshness-rules/freshness-rules.repository.test.ts similarity index 96% rename from packages/api/src/modules/freshness-rules/freshness-rules.repository.test.ts rename to packages/api/tests/modules/freshness-rules/freshness-rules.repository.test.ts index b9b9c19..27b1e4c 100644 --- a/packages/api/src/modules/freshness-rules/freshness-rules.repository.test.ts +++ b/packages/api/tests/modules/freshness-rules/freshness-rules.repository.test.ts @@ -16,7 +16,7 @@ const { mockFindById: vi.fn(), })); -vi.mock('../../schemas/freshness-rule.schema.js', () => { +vi.mock('../../../src/schemas/freshness-rule.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -62,7 +62,7 @@ vi.mock('../../schemas/freshness-rule.schema.js', () => { return { FreshnessRuleModel: FakeModel }; }); -import { FreshnessRulesRepository } from './freshness-rules.repository.js'; +import { FreshnessRulesRepository } from '../../../src/modules/freshness-rules/freshness-rules.repository.js'; describe(FreshnessRulesRepository.name, () => { let repo: FreshnessRulesRepository; diff --git a/packages/api/src/modules/freshness-rules/freshness-rules.routes.test.ts b/packages/api/tests/modules/freshness-rules/freshness-rules.routes.test.ts similarity index 92% rename from packages/api/src/modules/freshness-rules/freshness-rules.routes.test.ts rename to packages/api/tests/modules/freshness-rules/freshness-rules.routes.test.ts index fcf35b3..4c0e0cd 100644 --- a/packages/api/src/modules/freshness-rules/freshness-rules.routes.test.ts +++ b/packages/api/tests/modules/freshness-rules/freshness-rules.routes.test.ts @@ -28,7 +28,7 @@ const { mockFindByHousehold, mockFindById, mockCreate, mockUpdate, mockDelete } }), ); -vi.mock('./freshness-rules.repository.js', () => ({ +vi.mock('../../../src/modules/freshness-rules/freshness-rules.repository.js', () => ({ FreshnessRulesRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -39,17 +39,17 @@ vi.mock('./freshness-rules.repository.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import freshnessRulesRoutes from './freshness-rules.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import freshnessRulesRoutes from '../../../src/modules/freshness-rules/freshness-rules.routes.js'; function makeRule(overrides: Record = {}) { return { diff --git a/packages/api/src/modules/freshness-rules/freshness-rules.service.test.ts b/packages/api/tests/modules/freshness-rules/freshness-rules.service.test.ts similarity index 95% rename from packages/api/src/modules/freshness-rules/freshness-rules.service.test.ts rename to packages/api/tests/modules/freshness-rules/freshness-rules.service.test.ts index 77f041e..8e8f1c3 100644 --- a/packages/api/src/modules/freshness-rules/freshness-rules.service.test.ts +++ b/packages/api/tests/modules/freshness-rules/freshness-rules.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { FreshnessRulesService } from './freshness-rules.service.js'; -import { NotFoundError, BadRequestError } from '../../common/errors.js'; +import { FreshnessRulesService } from '../../../src/modules/freshness-rules/freshness-rules.service.js'; +import { NotFoundError, BadRequestError } from '../../../src/common/errors.js'; import { FreshnessRuleSource } from '@meshitrack/shared'; const mockRepo = { diff --git a/packages/api/src/modules/health/health.routes.test.ts b/packages/api/tests/modules/health/health.routes.test.ts similarity index 94% rename from packages/api/src/modules/health/health.routes.test.ts rename to packages/api/tests/modules/health/health.routes.test.ts index db0dc81..1335524 100644 --- a/packages/api/src/modules/health/health.routes.test.ts +++ b/packages/api/tests/modules/health/health.routes.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import Fastify from 'fastify'; import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod'; -import healthRoutes from './health.routes.js'; +import healthRoutes from '../../../src/modules/health/health.routes.js'; describe('Health Routes', () => { async function buildTestApp() { diff --git a/packages/api/src/modules/households/households.repository.test.ts b/packages/api/tests/modules/households/households.repository.test.ts similarity index 96% rename from packages/api/src/modules/households/households.repository.test.ts rename to packages/api/tests/modules/households/households.repository.test.ts index 2f5474a..807fff2 100644 --- a/packages/api/src/modules/households/households.repository.test.ts +++ b/packages/api/tests/modules/households/households.repository.test.ts @@ -15,7 +15,7 @@ const { _mockLean, mockExec, mockFindById, mockFindOne, mockFindByIdAndUpdate, m }; }); -vi.mock('../../schemas/household.schema.js', () => { +vi.mock('../../../src/schemas/household.schema.js', () => { class MockHouseholdModel { _data: Record; constructor(data: Record) { @@ -36,7 +36,7 @@ vi.mock('../../schemas/household.schema.js', () => { return { HouseholdModel: MockHouseholdModel }; }); -import { HouseholdsRepository } from './households.repository.js'; +import { HouseholdsRepository } from '../../../src/modules/households/households.repository.js'; describe('HouseholdsRepository', () => { let repo: HouseholdsRepository; diff --git a/packages/api/src/modules/households/households.routes.test.ts b/packages/api/tests/modules/households/households.routes.test.ts similarity index 94% rename from packages/api/src/modules/households/households.routes.test.ts rename to packages/api/tests/modules/households/households.routes.test.ts index ad7e643..9381b0a 100644 --- a/packages/api/src/modules/households/households.routes.test.ts +++ b/packages/api/tests/modules/households/households.routes.test.ts @@ -41,7 +41,7 @@ const { mockUserUpdate: vi.fn(), })); -vi.mock('./households.repository.js', () => ({ +vi.mock('../../../src/modules/households/households.repository.js', () => ({ HouseholdsRepository: class { create = mockCreate; findById = mockFindById; @@ -52,7 +52,7 @@ vi.mock('./households.repository.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = mockFindByKeycloakId; update = mockUserUpdate; @@ -75,10 +75,10 @@ vi.mock('mongoose', () => ({ }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import householdsRoutes from './households.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import householdsRoutes from '../../../src/modules/households/households.routes.js'; function makeFakeHousehold(overrides = {}) { return { diff --git a/packages/api/src/modules/households/households.service.test.ts b/packages/api/tests/modules/households/households.service.test.ts similarity index 98% rename from packages/api/src/modules/households/households.service.test.ts rename to packages/api/tests/modules/households/households.service.test.ts index 9989e3b..5729617 100644 --- a/packages/api/src/modules/households/households.service.test.ts +++ b/packages/api/tests/modules/households/households.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { HouseholdsService } from './households.service.js'; -import { NotFoundError, ForbiddenError, ConflictError } from '../../common/errors.js'; +import { HouseholdsService } from '../../../src/modules/households/households.service.js'; +import { NotFoundError, ForbiddenError, ConflictError } from '../../../src/common/errors.js'; import { HouseholdRole } from '@meshitrack/shared'; // Mock uuid to return deterministic values diff --git a/packages/api/src/modules/llm/no-op-llm.provider.test.ts b/packages/api/tests/modules/llm/no-op-llm.provider.test.ts similarity index 93% rename from packages/api/src/modules/llm/no-op-llm.provider.test.ts rename to packages/api/tests/modules/llm/no-op-llm.provider.test.ts index 7ac5158..72d1adf 100644 --- a/packages/api/src/modules/llm/no-op-llm.provider.test.ts +++ b/packages/api/tests/modules/llm/no-op-llm.provider.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi } from 'vitest'; -import { NoOpLlmProvider } from './no-op-llm.provider.js'; -import { LLM_PROVIDER } from './llm-provider.interface.js'; +import { NoOpLlmProvider } from '../../../src/modules/llm/no-op-llm.provider.js'; +import { LLM_PROVIDER } from '../../../src/modules/llm/llm-provider.interface.js'; describe(NoOpLlmProvider.name, () => { const provider = new NoOpLlmProvider(); diff --git a/packages/api/src/modules/meal-plans/meal-plans.repository.test.ts b/packages/api/tests/modules/meal-plans/meal-plans.repository.test.ts similarity index 95% rename from packages/api/src/modules/meal-plans/meal-plans.repository.test.ts rename to packages/api/tests/modules/meal-plans/meal-plans.repository.test.ts index 6160a06..ab83131 100644 --- a/packages/api/src/modules/meal-plans/meal-plans.repository.test.ts +++ b/packages/api/tests/modules/meal-plans/meal-plans.repository.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MealPlanRepository } from './meal-plans.repository.js'; +import { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js'; import { MealPlanStatus } from '@meshitrack/shared'; const { mockSave, MockMealPlanModel } = vi.hoisted(() => { @@ -17,11 +17,11 @@ const { mockSave, MockMealPlanModel } = vi.hoisted(() => { return { mockSave, MockMealPlanModel: MockModel }; }); -vi.mock('../../schemas/meal-plan.schema.js', () => ({ +vi.mock('../../../src/schemas/meal-plan.schema.js', () => ({ MealPlanModel: MockMealPlanModel, })); -const { MealPlanModel } = await import('../../schemas/meal-plan.schema.js'); +const { MealPlanModel } = await import('../../../src/schemas/meal-plan.schema.js'); function makeChain(result: unknown = null) { return { diff --git a/packages/api/src/modules/meal-plans/meal-plans.routes.test.ts b/packages/api/tests/modules/meal-plans/meal-plans.routes.test.ts similarity index 92% rename from packages/api/src/modules/meal-plans/meal-plans.routes.test.ts rename to packages/api/tests/modules/meal-plans/meal-plans.routes.test.ts index a135d75..6d62813 100644 --- a/packages/api/src/modules/meal-plans/meal-plans.routes.test.ts +++ b/packages/api/tests/modules/meal-plans/meal-plans.routes.test.ts @@ -37,7 +37,7 @@ const { mockDelete: vi.fn(), })); -vi.mock('./meal-plans.repository.js', () => ({ +vi.mock('../../../src/modules/meal-plans/meal-plans.repository.js', () => ({ MealPlanRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -50,42 +50,42 @@ vi.mock('./meal-plans.repository.js', () => ({ })); // Mock prerequisite repositories to allow SuggestionEngine/Gap to resolve -vi.mock('../recipes/recipes.repository.js', () => ({ +vi.mock('../../../src/modules/recipes/recipes.repository.js', () => ({ RecipesRepository: class { findByHousehold = vi.fn().mockResolvedValue({ data: [] }); findById = vi.fn(); }, })); -vi.mock('../pantry/pantry.repository.js', () => ({ +vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({ PantryRepository: class { findActiveByHousehold = vi.fn().mockResolvedValue([]); }, })); -vi.mock('../nutrition-targets/nutrition-target.repository.js', () => ({ +vi.mock('../../../src/modules/nutrition-targets/nutrition-target.repository.js', () => ({ NutritionTargetRepository: class { findByUser = vi.fn().mockResolvedValue(null); }, })); -vi.mock('../products/products.repository.js', () => ({ +vi.mock('../../../src/modules/products/products.repository.js', () => ({ ProductsRepository: class { findByIds = vi.fn().mockResolvedValue([]); }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import mealPlanRoutes from './meal-plans.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import mealPlanRoutes from '../../../src/modules/meal-plans/meal-plans.routes.js'; const emptyNutrition = { calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0, sugar: 0, sodium: 0, saturatedFat: 0, cholesterol: 0 }; diff --git a/packages/api/src/modules/meal-plans/meal-plans.service.test.ts b/packages/api/tests/modules/meal-plans/meal-plans.service.test.ts similarity index 96% rename from packages/api/src/modules/meal-plans/meal-plans.service.test.ts rename to packages/api/tests/modules/meal-plans/meal-plans.service.test.ts index 9e97265..384aed0 100644 --- a/packages/api/src/modules/meal-plans/meal-plans.service.test.ts +++ b/packages/api/tests/modules/meal-plans/meal-plans.service.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MealPlanService } from './meal-plans.service.js'; -import type { MealPlanRepository } from './meal-plans.repository.js'; +import { MealPlanService } from '../../../src/modules/meal-plans/meal-plans.service.js'; +import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js'; import { MealPlanStatus, MealType } from '@meshitrack/shared'; -import { BadRequestError, NotFoundError } from '../../common/errors.js'; +import { BadRequestError, NotFoundError } from '../../../src/common/errors.js'; describe(MealPlanService.name, () => { let service: MealPlanService; diff --git a/packages/api/src/modules/meal-plans/shopping-gap.service.test.ts b/packages/api/tests/modules/meal-plans/shopping-gap.service.test.ts similarity index 93% rename from packages/api/src/modules/meal-plans/shopping-gap.service.test.ts rename to packages/api/tests/modules/meal-plans/shopping-gap.service.test.ts index e66e93e..07375ae 100644 --- a/packages/api/src/modules/meal-plans/shopping-gap.service.test.ts +++ b/packages/api/tests/modules/meal-plans/shopping-gap.service.test.ts @@ -1,10 +1,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { ShoppingGapService } from './shopping-gap.service.js'; -import type { MealPlanRepository } from './meal-plans.repository.js'; -import type { RecipesRepository } from '../recipes/recipes.repository.js'; -import type { PantryRepository } from '../pantry/pantry.repository.js'; -import type { ProductsRepository } from '../products/products.repository.js'; -import { NotFoundError } from '../../common/errors.js'; +import { ShoppingGapService } from '../../../src/modules/meal-plans/shopping-gap.service.js'; +import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js'; +import type { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js'; +import type { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js'; +import type { ProductsRepository } from '../../../src/modules/products/products.repository.js'; +import { NotFoundError } from '../../../src/common/errors.js'; describe(ShoppingGapService.name, () => { let service: ShoppingGapService; diff --git a/packages/api/src/modules/meal-plans/suggestion-engine.service.test.ts b/packages/api/tests/modules/meal-plans/suggestion-engine.service.test.ts similarity index 95% rename from packages/api/src/modules/meal-plans/suggestion-engine.service.test.ts rename to packages/api/tests/modules/meal-plans/suggestion-engine.service.test.ts index 09c743e..999cc2a 100644 --- a/packages/api/src/modules/meal-plans/suggestion-engine.service.test.ts +++ b/packages/api/tests/modules/meal-plans/suggestion-engine.service.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { SuggestionEngineService } from './suggestion-engine.service.js'; -import type { RecipesRepository } from '../recipes/recipes.repository.js'; -import type { PantryRepository } from '../pantry/pantry.repository.js'; -import type { MealPlanRepository } from './meal-plans.repository.js'; -import type { NutritionTargetRepository } from '../nutrition-targets/nutrition-target.repository.js'; +import { SuggestionEngineService } from '../../../src/modules/meal-plans/suggestion-engine.service.js'; +import type { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js'; +import type { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js'; +import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js'; +import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js'; describe(SuggestionEngineService.name, () => { let service: SuggestionEngineService; diff --git a/packages/api/src/modules/medicine-prices/medicine-prices.repository.test.ts b/packages/api/tests/modules/medicine-prices/medicine-prices.repository.test.ts similarity index 97% rename from packages/api/src/modules/medicine-prices/medicine-prices.repository.test.ts rename to packages/api/tests/modules/medicine-prices/medicine-prices.repository.test.ts index 694efe6..13e523d 100644 --- a/packages/api/src/modules/medicine-prices/medicine-prices.repository.test.ts +++ b/packages/api/tests/modules/medicine-prices/medicine-prices.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockAggregate, mockSave } = vi.hoisted(() => ({ mockSave: vi.fn(), })); -vi.mock('../../schemas/medicine-price.schema.js', () => { +vi.mock('../../../src/schemas/medicine-price.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -37,7 +37,7 @@ vi.mock('../../schemas/medicine-price.schema.js', () => { return { MedicinePriceModel: FakeModel }; }); -import { MedicinePricesRepository } from './medicine-prices.repository.js'; +import { MedicinePricesRepository } from '../../../src/modules/medicine-prices/medicine-prices.repository.js'; describe(MedicinePricesRepository.name, () => { let repo: MedicinePricesRepository; diff --git a/packages/api/src/modules/medicine-prices/medicine-prices.routes.test.ts b/packages/api/tests/modules/medicine-prices/medicine-prices.routes.test.ts similarity index 92% rename from packages/api/src/modules/medicine-prices/medicine-prices.routes.test.ts rename to packages/api/tests/modules/medicine-prices/medicine-prices.routes.test.ts index 9e3e1a7..00257a3 100644 --- a/packages/api/src/modules/medicine-prices/medicine-prices.routes.test.ts +++ b/packages/api/tests/modules/medicine-prices/medicine-prices.routes.test.ts @@ -27,7 +27,7 @@ const { mockRecordPrice, mockGetPriceHistory, mockCompareStores, mockGetAnalytic }), ); -vi.mock('./medicine-prices.repository.js', () => ({ +vi.mock('../../../src/modules/medicine-prices/medicine-prices.repository.js', () => ({ MedicinePricesRepository: class { create = vi.fn(); findByMedicine = vi.fn(); @@ -37,7 +37,7 @@ vi.mock('./medicine-prices.repository.js', () => ({ }, })); -vi.mock('./medicine-prices.service.js', () => ({ +vi.mock('../../../src/modules/medicine-prices/medicine-prices.service.js', () => ({ MedicinePricesService: class { recordPrice = mockRecordPrice; getPriceHistory = mockGetPriceHistory; @@ -46,29 +46,29 @@ vi.mock('./medicine-prices.service.js', () => ({ }, })); -vi.mock('../medicine-products/medicine-products.repository.js', () => ({ +vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({ MedicineProductsRepository: class { findById = vi.fn(); }, })); -vi.mock('../stores/stores.repository.js', () => ({ +vi.mock('../../../src/modules/stores/stores.repository.js', () => ({ StoresRepository: class { findById = vi.fn(); }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import medicinePricesRoutes from './medicine-prices.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import medicinePricesRoutes from '../../../src/modules/medicine-prices/medicine-prices.routes.js'; function makeFakePriceRecord(overrides = {}) { return { diff --git a/packages/api/src/modules/medicine-prices/medicine-prices.service.test.ts b/packages/api/tests/modules/medicine-prices/medicine-prices.service.test.ts similarity index 98% rename from packages/api/src/modules/medicine-prices/medicine-prices.service.test.ts rename to packages/api/tests/modules/medicine-prices/medicine-prices.service.test.ts index d0cfba0..674db66 100644 --- a/packages/api/src/modules/medicine-prices/medicine-prices.service.test.ts +++ b/packages/api/tests/modules/medicine-prices/medicine-prices.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MedicinePricesService } from './medicine-prices.service.js'; +import { MedicinePricesService } from '../../../src/modules/medicine-prices/medicine-prices.service.js'; describe(MedicinePricesService.name, () => { const mockPricesRepo = { diff --git a/packages/api/src/modules/medicine-products/medicine-products.repository.test.ts b/packages/api/tests/modules/medicine-products/medicine-products.repository.test.ts similarity index 96% rename from packages/api/src/modules/medicine-products/medicine-products.repository.test.ts rename to packages/api/tests/modules/medicine-products/medicine-products.repository.test.ts index 0bc24a1..c1e6518 100644 --- a/packages/api/src/modules/medicine-products/medicine-products.repository.test.ts +++ b/packages/api/tests/modules/medicine-products/medicine-products.repository.test.ts @@ -30,7 +30,7 @@ const { }; }); -vi.mock('../../schemas/medicine-product.schema.js', () => { +vi.mock('../../../src/schemas/medicine-product.schema.js', () => { class MockMedicineProductModel { _data: Record; constructor(data: Record) { @@ -52,7 +52,7 @@ vi.mock('../../schemas/medicine-product.schema.js', () => { return { MedicineProductModel: MockMedicineProductModel }; }); -import { MedicineProductsRepository } from './medicine-products.repository.js'; +import { MedicineProductsRepository } from '../../../src/modules/medicine-products/medicine-products.repository.js'; describe(MedicineProductsRepository.name, () => { let repo: MedicineProductsRepository; diff --git a/packages/api/src/modules/medicine-products/medicine-products.routes.test.ts b/packages/api/tests/modules/medicine-products/medicine-products.routes.test.ts similarity index 92% rename from packages/api/src/modules/medicine-products/medicine-products.routes.test.ts rename to packages/api/tests/modules/medicine-products/medicine-products.routes.test.ts index 272aca6..189a051 100644 --- a/packages/api/src/modules/medicine-products/medicine-products.routes.test.ts +++ b/packages/api/tests/modules/medicine-products/medicine-products.routes.test.ts @@ -35,7 +35,7 @@ const { mockMedicineFindById: vi.fn(), })); -vi.mock('./medicine-products.repository.js', () => ({ +vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({ MedicineProductsRepository: class { findByMedicine = mockFindByMedicine; findById = mockFindById; @@ -45,24 +45,24 @@ vi.mock('./medicine-products.repository.js', () => ({ }, })); -vi.mock('../medicines/medicines.repository.js', () => ({ +vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({ MedicinesRepository: class { findById = mockMedicineFindById; }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import medicinesRoutes from '../medicines/medicines.routes.js'; -import medicineProductsRoutes from './medicine-products.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js'; +import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js'; function makeFakeProduct(overrides = {}) { return { diff --git a/packages/api/src/modules/medicine-products/medicine-products.service.test.ts b/packages/api/tests/modules/medicine-products/medicine-products.service.test.ts similarity index 96% rename from packages/api/src/modules/medicine-products/medicine-products.service.test.ts rename to packages/api/tests/modules/medicine-products/medicine-products.service.test.ts index 04901d1..7ee97ca 100644 --- a/packages/api/src/modules/medicine-products/medicine-products.service.test.ts +++ b/packages/api/tests/modules/medicine-products/medicine-products.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MedicineProductsService } from './medicine-products.service.js'; -import { NotFoundError } from '../../common/errors.js'; +import { MedicineProductsService } from '../../../src/modules/medicine-products/medicine-products.service.js'; +import { NotFoundError } from '../../../src/common/errors.js'; import { DosageUnit, MedicineProductSource } from '@meshitrack/shared'; describe(MedicineProductsService.name, () => { diff --git a/packages/api/src/modules/medicines/medicines.repository.test.ts b/packages/api/tests/modules/medicines/medicines.repository.test.ts similarity index 97% rename from packages/api/src/modules/medicines/medicines.repository.test.ts rename to packages/api/tests/modules/medicines/medicines.repository.test.ts index 9c2e51e..990a1fa 100644 --- a/packages/api/src/modules/medicines/medicines.repository.test.ts +++ b/packages/api/tests/modules/medicines/medicines.repository.test.ts @@ -27,7 +27,7 @@ const { }; }); -vi.mock('../../schemas/medicine.schema.js', () => { +vi.mock('../../../src/schemas/medicine.schema.js', () => { class MockMedicineModel { _data: Record; constructor(data: Record) { @@ -48,7 +48,7 @@ vi.mock('../../schemas/medicine.schema.js', () => { return { MedicineModel: MockMedicineModel }; }); -import { MedicinesRepository } from './medicines.repository.js'; +import { MedicinesRepository } from '../../../src/modules/medicines/medicines.repository.js'; describe(MedicinesRepository.name, () => { let repo: MedicinesRepository; diff --git a/packages/api/src/modules/medicines/medicines.routes.test.ts b/packages/api/tests/modules/medicines/medicines.routes.test.ts similarity index 90% rename from packages/api/src/modules/medicines/medicines.routes.test.ts rename to packages/api/tests/modules/medicines/medicines.routes.test.ts index b6503f2..6ec4cf2 100644 --- a/packages/api/src/modules/medicines/medicines.routes.test.ts +++ b/packages/api/tests/modules/medicines/medicines.routes.test.ts @@ -37,7 +37,7 @@ const { mockCountByMedicineId: vi.fn(), })); -vi.mock('./medicines.repository.js', () => ({ +vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({ MedicinesRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -48,13 +48,13 @@ vi.mock('./medicines.repository.js', () => ({ }, })); -vi.mock('../medicine-products/medicine-products.repository.js', () => ({ +vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({ MedicineProductsRepository: class { countByMedicineId = mockCountByMedicineId; }, })); -vi.mock('../medicine-products/medicine-products.service.js', () => ({ +vi.mock('../../../src/modules/medicine-products/medicine-products.service.js', () => ({ MedicineProductsService: class { listByMedicine = vi.fn(); getById = vi.fn(); @@ -64,18 +64,18 @@ vi.mock('../medicine-products/medicine-products.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import medicinesRoutes from './medicines.routes.js'; -import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js'; +import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js'; function makeFakeMedicine(overrides = {}) { return { diff --git a/packages/api/src/modules/medicines/medicines.service.test.ts b/packages/api/tests/modules/medicines/medicines.service.test.ts similarity index 97% rename from packages/api/src/modules/medicines/medicines.service.test.ts rename to packages/api/tests/modules/medicines/medicines.service.test.ts index e1ad191..667baf7 100644 --- a/packages/api/src/modules/medicines/medicines.service.test.ts +++ b/packages/api/tests/modules/medicines/medicines.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MedicinesService } from './medicines.service.js'; -import { NotFoundError, ConflictError } from '../../common/errors.js'; +import { MedicinesService } from '../../../src/modules/medicines/medicines.service.js'; +import { NotFoundError, ConflictError } from '../../../src/common/errors.js'; import { MedicineForm, StrengthUnit, MedicineCategory } from '@meshitrack/shared'; describe(MedicinesService.name, () => { diff --git a/packages/api/src/modules/nutrition-targets/nutrition-target.repository.test.ts b/packages/api/tests/modules/nutrition-targets/nutrition-target.repository.test.ts similarity index 92% rename from packages/api/src/modules/nutrition-targets/nutrition-target.repository.test.ts rename to packages/api/tests/modules/nutrition-targets/nutrition-target.repository.test.ts index a30943b..6aa3e3f 100644 --- a/packages/api/src/modules/nutrition-targets/nutrition-target.repository.test.ts +++ b/packages/api/tests/modules/nutrition-targets/nutrition-target.repository.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { NutritionTargetRepository } from './nutrition-target.repository.js'; +import { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js'; const { mockSave, MockTargetModel } = vi.hoisted(() => { const mockSave = vi.fn(); @@ -16,11 +16,11 @@ const { mockSave, MockTargetModel } = vi.hoisted(() => { return { mockSave, MockTargetModel: MockModel }; }); -vi.mock('../../schemas/nutrition-target.schema.js', () => ({ +vi.mock('../../../src/schemas/nutrition-target.schema.js', () => ({ NutritionTargetModel: MockTargetModel, })); -const { NutritionTargetModel } = await import('../../schemas/nutrition-target.schema.js'); +const { NutritionTargetModel } = await import('../../../src/schemas/nutrition-target.schema.js'); function makeChain(result: unknown = null) { return { diff --git a/packages/api/src/modules/nutrition-targets/nutrition-target.routes.test.ts b/packages/api/tests/modules/nutrition-targets/nutrition-target.routes.test.ts similarity index 92% rename from packages/api/src/modules/nutrition-targets/nutrition-target.routes.test.ts rename to packages/api/tests/modules/nutrition-targets/nutrition-target.routes.test.ts index 826f22e..fbc2cc6 100644 --- a/packages/api/src/modules/nutrition-targets/nutrition-target.routes.test.ts +++ b/packages/api/tests/modules/nutrition-targets/nutrition-target.routes.test.ts @@ -30,7 +30,7 @@ const { mockCreate: vi.fn(), })); -vi.mock('./nutrition-target.repository.js', () => ({ +vi.mock('../../../src/modules/nutrition-targets/nutrition-target.repository.js', () => ({ NutritionTargetRepository: class { findByUser = mockFindByUser; findAllByUser = mockFindAllByUser; @@ -39,17 +39,17 @@ vi.mock('./nutrition-target.repository.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import nutritionTargetRoutes from './nutrition-target.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import nutritionTargetRoutes from '../../../src/modules/nutrition-targets/nutrition-target.routes.js'; function makeTarget(overrides = {}) { return { diff --git a/packages/api/src/modules/nutrition-targets/nutrition-target.service.test.ts b/packages/api/tests/modules/nutrition-targets/nutrition-target.service.test.ts similarity index 94% rename from packages/api/src/modules/nutrition-targets/nutrition-target.service.test.ts rename to packages/api/tests/modules/nutrition-targets/nutrition-target.service.test.ts index c9c9552..3be1375 100644 --- a/packages/api/src/modules/nutrition-targets/nutrition-target.service.test.ts +++ b/packages/api/tests/modules/nutrition-targets/nutrition-target.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { NutritionTargetService } from './nutrition-target.service.js'; -import type { NutritionTargetRepository } from './nutrition-target.repository.js'; +import { NutritionTargetService } from '../../../src/modules/nutrition-targets/nutrition-target.service.js'; +import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js'; describe(NutritionTargetService.name, () => { let service: NutritionTargetService; diff --git a/packages/api/src/modules/organizer/organizer.repository.test.ts b/packages/api/tests/modules/organizer/organizer.repository.test.ts similarity index 97% rename from packages/api/src/modules/organizer/organizer.repository.test.ts rename to packages/api/tests/modules/organizer/organizer.repository.test.ts index d6a9aac..f588b88 100644 --- a/packages/api/src/modules/organizer/organizer.repository.test.ts +++ b/packages/api/tests/modules/organizer/organizer.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() mockSave: vi.fn(), })); -vi.mock('../../schemas/organizer-fill.schema.js', () => { +vi.mock('../../../src/schemas/organizer-fill.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -41,7 +41,7 @@ vi.mock('../../schemas/organizer-fill.schema.js', () => { return { OrganizerFillModel: FakeModel }; }); -import { OrganizerRepository } from './organizer.repository.js'; +import { OrganizerRepository } from '../../../src/modules/organizer/organizer.repository.js'; describe(OrganizerRepository.name, () => { let repo: OrganizerRepository; diff --git a/packages/api/src/modules/organizer/organizer.routes.test.ts b/packages/api/tests/modules/organizer/organizer.routes.test.ts similarity index 96% rename from packages/api/src/modules/organizer/organizer.routes.test.ts rename to packages/api/tests/modules/organizer/organizer.routes.test.ts index e0ba192..a9b14bc 100644 --- a/packages/api/src/modules/organizer/organizer.routes.test.ts +++ b/packages/api/tests/modules/organizer/organizer.routes.test.ts @@ -27,7 +27,7 @@ const { mockListFills, mockGetFillById, mockPreview, mockFill, mockUndoFill } = mockUndoFill: vi.fn(), })); -vi.mock('./organizer.repository.js', () => ({ +vi.mock('../../../src/modules/organizer/organizer.repository.js', () => ({ OrganizerRepository: class { create = vi.fn(); update = vi.fn(); @@ -37,7 +37,7 @@ vi.mock('./organizer.repository.js', () => ({ }, })); -vi.mock('./organizer.service.js', () => ({ +vi.mock('../../../src/modules/organizer/organizer.service.js', () => ({ OrganizerService: class { listFills = mockListFills; getFillById = mockGetFillById; @@ -47,17 +47,17 @@ vi.mock('./organizer.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import organizerRoutes from './organizer.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import organizerRoutes from '../../../src/modules/organizer/organizer.routes.js'; function makeFakeFill(overrides = {}) { return { diff --git a/packages/api/src/modules/organizer/organizer.service.test.ts b/packages/api/tests/modules/organizer/organizer.service.test.ts similarity index 99% rename from packages/api/src/modules/organizer/organizer.service.test.ts rename to packages/api/tests/modules/organizer/organizer.service.test.ts index 39a1b00..85142ae 100644 --- a/packages/api/src/modules/organizer/organizer.service.test.ts +++ b/packages/api/tests/modules/organizer/organizer.service.test.ts @@ -19,7 +19,7 @@ vi.mock('mongoose', () => { return { default: { startSession: vi.fn().mockResolvedValue(mockSession) } }; }); -import { OrganizerService } from './organizer.service.js'; +import { OrganizerService } from '../../../src/modules/organizer/organizer.service.js'; describe(OrganizerService.name, () => { const mockOrganizerRepo = { diff --git a/packages/api/src/modules/pantry/freshness-calculator.service.test.ts b/packages/api/tests/modules/pantry/freshness-calculator.service.test.ts similarity index 98% rename from packages/api/src/modules/pantry/freshness-calculator.service.test.ts rename to packages/api/tests/modules/pantry/freshness-calculator.service.test.ts index e5ad115..c78b649 100644 --- a/packages/api/src/modules/pantry/freshness-calculator.service.test.ts +++ b/packages/api/tests/modules/pantry/freshness-calculator.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { FreshnessCalculatorService } from './freshness-calculator.service.js'; +import { FreshnessCalculatorService } from '../../../src/modules/pantry/freshness-calculator.service.js'; import { ItemStatus, FreshnessUrgency, FreshnessSource, StorageLocation } from '@meshitrack/shared'; describe(FreshnessCalculatorService.name, () => { diff --git a/packages/api/src/modules/pantry/pantry.repository.test.ts b/packages/api/tests/modules/pantry/pantry.repository.test.ts similarity index 98% rename from packages/api/src/modules/pantry/pantry.repository.test.ts rename to packages/api/tests/modules/pantry/pantry.repository.test.ts index e7af3dc..a3a03b0 100644 --- a/packages/api/src/modules/pantry/pantry.repository.test.ts +++ b/packages/api/tests/modules/pantry/pantry.repository.test.ts @@ -20,7 +20,7 @@ const { mockFindByIdAndUpdate: vi.fn(), })); -vi.mock('../../schemas/pantry-item.schema.js', () => { +vi.mock('../../../src/schemas/pantry-item.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -75,7 +75,7 @@ vi.mock('../../schemas/pantry-item.schema.js', () => { return { PantryItemModel: FakeModel }; }); -import { PantryRepository } from './pantry.repository.js'; +import { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js'; describe(PantryRepository.name, () => { let repo: PantryRepository; diff --git a/packages/api/src/modules/pantry/pantry.routes.test.ts b/packages/api/tests/modules/pantry/pantry.routes.test.ts similarity index 94% rename from packages/api/src/modules/pantry/pantry.routes.test.ts rename to packages/api/tests/modules/pantry/pantry.routes.test.ts index 855d09d..baf5547 100644 --- a/packages/api/src/modules/pantry/pantry.routes.test.ts +++ b/packages/api/tests/modules/pantry/pantry.routes.test.ts @@ -54,7 +54,7 @@ const { mockFindApplicableRule } = vi.hoisted(() => ({ mockFindApplicableRule: vi.fn(), })); -vi.mock('./pantry.repository.js', () => ({ +vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({ PantryRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -71,14 +71,14 @@ vi.mock('./pantry.repository.js', () => ({ }, })); -vi.mock('../products/products.repository.js', () => ({ +vi.mock('../../../src/modules/products/products.repository.js', () => ({ ProductsRepository: class { findById = mockProductFindById; findByIds = vi.fn(); }, })); -vi.mock('../freshness-rules/freshness-rules.repository.js', () => ({ +vi.mock('../../../src/modules/freshness-rules/freshness-rules.repository.js', () => ({ FreshnessRulesRepository: class { findApplicableRule = mockFindApplicableRule; findByHousehold = vi.fn(); @@ -89,17 +89,17 @@ vi.mock('../freshness-rules/freshness-rules.repository.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import pantryRoutes from './pantry.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import pantryRoutes from '../../../src/modules/pantry/pantry.routes.js'; const freshness = { estimatedExpiryDate: new Date('2024-02-01').toISOString(), diff --git a/packages/api/src/modules/pantry/pantry.service.test.ts b/packages/api/tests/modules/pantry/pantry.service.test.ts similarity index 98% rename from packages/api/src/modules/pantry/pantry.service.test.ts rename to packages/api/tests/modules/pantry/pantry.service.test.ts index f8d2051..aaac1ce 100644 --- a/packages/api/src/modules/pantry/pantry.service.test.ts +++ b/packages/api/tests/modules/pantry/pantry.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { PantryService } from './pantry.service.js'; -import { NotFoundError, BadRequestError } from '../../common/errors.js'; +import { PantryService } from '../../../src/modules/pantry/pantry.service.js'; +import { NotFoundError, BadRequestError } from '../../../src/common/errors.js'; import { ItemStatus } from '@meshitrack/shared'; const mockPantryRepo = { diff --git a/packages/api/src/modules/prices/prices.repository.test.ts b/packages/api/tests/modules/prices/prices.repository.test.ts similarity index 95% rename from packages/api/src/modules/prices/prices.repository.test.ts rename to packages/api/tests/modules/prices/prices.repository.test.ts index be115e1..abdeeb5 100644 --- a/packages/api/src/modules/prices/prices.repository.test.ts +++ b/packages/api/tests/modules/prices/prices.repository.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { PricesRepository } from './prices.repository.js'; +import { PricesRepository } from '../../../src/modules/prices/prices.repository.js'; const { mockSave, MockPriceRecordModel } = vi.hoisted(() => { const mockSave = vi.fn(); @@ -17,11 +17,11 @@ const { mockSave, MockPriceRecordModel } = vi.hoisted(() => { return { mockSave, MockPriceRecordModel: MockModel }; }); -vi.mock('../../schemas/price-record.schema.js', () => ({ +vi.mock('../../../src/schemas/price-record.schema.js', () => ({ PriceRecordModel: MockPriceRecordModel, })); -const { PriceRecordModel } = await import('../../schemas/price-record.schema.js'); +const { PriceRecordModel } = await import('../../../src/schemas/price-record.schema.js'); function makeChain(result: unknown = null) { return { diff --git a/packages/api/src/modules/prices/prices.routes.test.ts b/packages/api/tests/modules/prices/prices.routes.test.ts similarity index 91% rename from packages/api/src/modules/prices/prices.routes.test.ts rename to packages/api/tests/modules/prices/prices.routes.test.ts index 4a87931..bd4a459 100644 --- a/packages/api/src/modules/prices/prices.routes.test.ts +++ b/packages/api/tests/modules/prices/prices.routes.test.ts @@ -23,7 +23,7 @@ const mockFindByProduct = vi.fn(); const mockCompareStores = vi.fn(); const mockGetAnalytics = vi.fn(); -vi.mock('./prices.repository.js', () => ({ +vi.mock('../../../src/modules/prices/prices.repository.js', () => ({ PricesRepository: class { create = mockCreate; createMany = mockCreateMany; @@ -33,30 +33,30 @@ vi.mock('./prices.repository.js', () => ({ }, })); -vi.mock('../products/products.repository.js', () => ({ +vi.mock('../../../src/modules/products/products.repository.js', () => ({ ProductsRepository: class { findById = vi.fn().mockResolvedValue({ name: 'Mock Product' }); findByIds = vi.fn().mockResolvedValue([{ _id: 'p1', name: 'Mock Product' }]); }, })); -vi.mock('../stores/stores.repository.js', () => ({ +vi.mock('../../../src/modules/stores/stores.repository.js', () => ({ StoresRepository: class { findById = vi.fn().mockResolvedValue({ name: 'Mock Store' }); }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import pricesRoutes from './prices.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import pricesRoutes from '../../../src/modules/prices/prices.routes.js'; describe('prices.routes', () => { let app: any; diff --git a/packages/api/src/modules/prices/prices.service.test.ts b/packages/api/tests/modules/prices/prices.service.test.ts similarity index 97% rename from packages/api/src/modules/prices/prices.service.test.ts rename to packages/api/tests/modules/prices/prices.service.test.ts index e3d566a..8b05eac 100644 --- a/packages/api/src/modules/prices/prices.service.test.ts +++ b/packages/api/tests/modules/prices/prices.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { PricesService } from './prices.service.js'; -import { NotFoundError } from '../../common/errors.js'; +import { PricesService } from '../../../src/modules/prices/prices.service.js'; +import { NotFoundError } from '../../../src/common/errors.js'; describe('PricesService', () => { let service: PricesService; diff --git a/packages/api/src/modules/products/barcode.service.test.ts b/packages/api/tests/modules/products/barcode.service.test.ts similarity index 99% rename from packages/api/src/modules/products/barcode.service.test.ts rename to packages/api/tests/modules/products/barcode.service.test.ts index 5137884..ed4df80 100644 --- a/packages/api/src/modules/products/barcode.service.test.ts +++ b/packages/api/tests/modules/products/barcode.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { BarcodeService } from './barcode.service.js'; +import { BarcodeService } from '../../../src/modules/products/barcode.service.js'; vi.mock('undici', () => ({ request: vi.fn(), diff --git a/packages/api/src/modules/products/csv-parser.test.ts b/packages/api/tests/modules/products/csv-parser.test.ts similarity index 98% rename from packages/api/src/modules/products/csv-parser.test.ts rename to packages/api/tests/modules/products/csv-parser.test.ts index 7622441..65c7cc5 100644 --- a/packages/api/src/modules/products/csv-parser.test.ts +++ b/packages/api/tests/modules/products/csv-parser.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { parseCsv, MAX_FILE_SIZE, MAX_ROWS } from './csv-parser.js'; +import { parseCsv, MAX_FILE_SIZE, MAX_ROWS } from '../../../src/modules/products/csv-parser.js'; import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared'; describe('parseCsv', () => { diff --git a/packages/api/src/modules/products/products.repository.test.ts b/packages/api/tests/modules/products/products.repository.test.ts similarity index 97% rename from packages/api/src/modules/products/products.repository.test.ts rename to packages/api/tests/modules/products/products.repository.test.ts index 6c37ce8..1c394a3 100644 --- a/packages/api/src/modules/products/products.repository.test.ts +++ b/packages/api/tests/modules/products/products.repository.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { ProductsRepository } from './products.repository.js'; +import { ProductsRepository } from '../../../src/modules/products/products.repository.js'; const { mockSave, MockProductModel } = vi.hoisted(() => { const mockSave = vi.fn(); @@ -16,11 +16,11 @@ const { mockSave, MockProductModel } = vi.hoisted(() => { return { mockSave, MockProductModel }; }); -vi.mock('../../schemas/product.schema.js', () => ({ +vi.mock('../../../src/schemas/product.schema.js', () => ({ ProductModel: MockProductModel, })); -const { ProductModel } = await import('../../schemas/product.schema.js'); +const { ProductModel } = await import('../../../src/schemas/product.schema.js'); function makeChain(result: unknown = null) { return { diff --git a/packages/api/src/modules/products/products.routes.test.ts b/packages/api/tests/modules/products/products.routes.test.ts similarity index 97% rename from packages/api/src/modules/products/products.routes.test.ts rename to packages/api/tests/modules/products/products.routes.test.ts index d6bcad1..e87a3b9 100644 --- a/packages/api/src/modules/products/products.routes.test.ts +++ b/packages/api/tests/modules/products/products.routes.test.ts @@ -41,7 +41,7 @@ const { mockBarcodeLookup: vi.fn(), })); -vi.mock('./products.repository.js', () => ({ +vi.mock('../../../src/modules/products/products.repository.js', () => ({ ProductsRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -55,23 +55,23 @@ vi.mock('./products.repository.js', () => ({ }, })); -vi.mock('./barcode.service.js', () => ({ +vi.mock('../../../src/modules/products/barcode.service.js', () => ({ BarcodeService: class { lookup = mockBarcodeLookup; }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import productsRoutes from './products.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import productsRoutes from '../../../src/modules/products/products.routes.js'; function makeFakeProduct(overrides: Record = {}) { return { diff --git a/packages/api/src/modules/products/products.service.test.ts b/packages/api/tests/modules/products/products.service.test.ts similarity index 98% rename from packages/api/src/modules/products/products.service.test.ts rename to packages/api/tests/modules/products/products.service.test.ts index 2bb3893..1276c1e 100644 --- a/packages/api/src/modules/products/products.service.test.ts +++ b/packages/api/tests/modules/products/products.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { ProductsService } from './products.service.js'; -import { NotFoundError, ConflictError } from '../../common/errors.js'; +import { ProductsService } from '../../../src/modules/products/products.service.js'; +import { NotFoundError, ConflictError } from '../../../src/common/errors.js'; import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared'; const mockRepo = { diff --git a/packages/api/src/modules/purchases/purchases.repository.test.ts b/packages/api/tests/modules/purchases/purchases.repository.test.ts similarity index 91% rename from packages/api/src/modules/purchases/purchases.repository.test.ts rename to packages/api/tests/modules/purchases/purchases.repository.test.ts index c3cdf44..adda1ad 100644 --- a/packages/api/src/modules/purchases/purchases.repository.test.ts +++ b/packages/api/tests/modules/purchases/purchases.repository.test.ts @@ -8,7 +8,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockAggregate, mockSave } = mockSave: vi.fn(), })); -vi.mock('../../schemas/purchase.schema.js', () => { +vi.mock('../../../src/schemas/purchase.schema.js', () => { const findChain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -36,7 +36,7 @@ vi.mock('../../schemas/purchase.schema.js', () => { return { PurchaseModel: FakeModel }; }); -import { PurchasesRepository } from './purchases.repository.js'; +import { PurchasesRepository } from '../../../src/modules/purchases/purchases.repository.js'; const makeItem = (overrides = {}) => ({ medicineProductId: 'mp-1', @@ -101,7 +101,7 @@ describe(PurchasesRepository.name, () => { it('filters by status when provided', async () => { mockFind.mockResolvedValue([]); - const { PurchaseModel } = await import('../../schemas/purchase.schema.js'); + const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js'); await repo.findByHousehold('hh1', { limit: 20, status: 'ordered' }); @@ -112,7 +112,7 @@ describe(PurchasesRepository.name, () => { it('filters by storeId when provided', async () => { mockFind.mockResolvedValue([]); - const { PurchaseModel } = await import('../../schemas/purchase.schema.js'); + const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js'); await repo.findByHousehold('hh1', { limit: 20, storeId: 'st-1' }); @@ -122,7 +122,7 @@ describe(PurchasesRepository.name, () => { it('applies cursor filter when provided', async () => { mockFind.mockResolvedValue([]); const cursor = Buffer.from('p-1').toString('base64'); - const { PurchaseModel } = await import('../../schemas/purchase.schema.js'); + const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js'); await repo.findByHousehold('hh1', { limit: 20, cursor }); @@ -172,7 +172,7 @@ describe(PurchasesRepository.name, () => { it('includes items in update set when provided', async () => { const updated = { _id: 'p-1' }; mockFindOneAndUpdate.mockResolvedValue(updated); - const { PurchaseModel } = await import('../../schemas/purchase.schema.js'); + const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js'); const items = [{ name: 'X', quantity: 1, unit: 'tablet', addedToCabinet: false }]; await repo.update('p-1', 'hh1', { items } as never); @@ -189,7 +189,7 @@ describe(PurchasesRepository.name, () => { it('sets status to in_cabinet and all items addedToCabinet', async () => { const updated = { _id: 'p-1', status: 'in_cabinet' }; mockFindOneAndUpdate.mockResolvedValue(updated); - const { PurchaseModel } = await import('../../schemas/purchase.schema.js'); + const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js'); const result = await repo.receiveAll('p-1', 'hh1'); @@ -208,7 +208,7 @@ describe(PurchasesRepository.name, () => { it('builds per-index update set and calls findOneAndUpdate', async () => { const updated = { _id: 'p-1', items: [] }; mockFindOneAndUpdate.mockResolvedValue(updated); - const { PurchaseModel } = await import('../../schemas/purchase.schema.js'); + const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js'); const result = await repo.markItemsAddedToCabinet('p-1', 'hh1', [0, 2]); diff --git a/packages/api/src/modules/purchases/purchases.routes.test.ts b/packages/api/tests/modules/purchases/purchases.routes.test.ts similarity index 96% rename from packages/api/src/modules/purchases/purchases.routes.test.ts rename to packages/api/tests/modules/purchases/purchases.routes.test.ts index 3e974d9..dc660b4 100644 --- a/packages/api/src/modules/purchases/purchases.routes.test.ts +++ b/packages/api/tests/modules/purchases/purchases.routes.test.ts @@ -29,7 +29,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockReceive, mockDelete } }), ); -vi.mock('./purchases.repository.js', () => ({ +vi.mock('../../../src/modules/purchases/purchases.repository.js', () => ({ PurchasesRepository: class { create = vi.fn(); findByHousehold = vi.fn(); @@ -41,7 +41,7 @@ vi.mock('./purchases.repository.js', () => ({ }, })); -vi.mock('./purchases.service.js', () => ({ +vi.mock('../../../src/modules/purchases/purchases.service.js', () => ({ PurchasesService: class { list = mockList; getById = mockGetById; @@ -52,17 +52,17 @@ vi.mock('./purchases.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import purchasesRoutes from './purchases.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import purchasesRoutes from '../../../src/modules/purchases/purchases.routes.js'; function makeFakePurchase(overrides = {}) { return { diff --git a/packages/api/src/modules/purchases/purchases.service.test.ts b/packages/api/tests/modules/purchases/purchases.service.test.ts similarity index 99% rename from packages/api/src/modules/purchases/purchases.service.test.ts rename to packages/api/tests/modules/purchases/purchases.service.test.ts index 72c751d..3f445a9 100644 --- a/packages/api/src/modules/purchases/purchases.service.test.ts +++ b/packages/api/tests/modules/purchases/purchases.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { PurchasesService } from './purchases.service.js'; +import { PurchasesService } from '../../../src/modules/purchases/purchases.service.js'; describe(PurchasesService.name, () => { const mockPurchasesRepo = { diff --git a/packages/api/src/modules/recipes/nutrition-calculator.service.test.ts b/packages/api/tests/modules/recipes/nutrition-calculator.service.test.ts similarity index 98% rename from packages/api/src/modules/recipes/nutrition-calculator.service.test.ts rename to packages/api/tests/modules/recipes/nutrition-calculator.service.test.ts index 1ccfc66..329516c 100644 --- a/packages/api/src/modules/recipes/nutrition-calculator.service.test.ts +++ b/packages/api/tests/modules/recipes/nutrition-calculator.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { NutritionCalculatorService } from './nutrition-calculator.service.js'; +import { NutritionCalculatorService } from '../../../src/modules/recipes/nutrition-calculator.service.js'; import { NutritionWarning } from '@meshitrack/shared'; const service = new NutritionCalculatorService(); diff --git a/packages/api/src/modules/recipes/recipes.repository.test.ts b/packages/api/tests/modules/recipes/recipes.repository.test.ts similarity index 97% rename from packages/api/src/modules/recipes/recipes.repository.test.ts rename to packages/api/tests/modules/recipes/recipes.repository.test.ts index b4e6e8a..e65d59e 100644 --- a/packages/api/src/modules/recipes/recipes.repository.test.ts +++ b/packages/api/tests/modules/recipes/recipes.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() mockSave: vi.fn(), })); -vi.mock('../../schemas/recipe.schema.js', () => { +vi.mock('../../../src/schemas/recipe.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -41,7 +41,7 @@ vi.mock('../../schemas/recipe.schema.js', () => { return { RecipeModel: FakeModel }; }); -import { RecipesRepository } from './recipes.repository.js'; +import { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js'; describe(RecipesRepository.name, () => { let repo: RecipesRepository; diff --git a/packages/api/src/modules/recipes/recipes.routes.test.ts b/packages/api/tests/modules/recipes/recipes.routes.test.ts similarity index 96% rename from packages/api/src/modules/recipes/recipes.routes.test.ts rename to packages/api/tests/modules/recipes/recipes.routes.test.ts index 4821d24..955e406 100644 --- a/packages/api/src/modules/recipes/recipes.routes.test.ts +++ b/packages/api/tests/modules/recipes/recipes.routes.test.ts @@ -40,7 +40,7 @@ const { mockFindByIds } = vi.hoisted(() => ({ mockFindByIds: vi.fn(), })); -vi.mock('./recipes.repository.js', () => ({ +vi.mock('../../../src/modules/recipes/recipes.repository.js', () => ({ RecipesRepository: class { findByHousehold = mockFindByHousehold; findById = mockFindById; @@ -52,24 +52,24 @@ vi.mock('./recipes.repository.js', () => ({ }, })); -vi.mock('../products/products.repository.js', () => ({ +vi.mock('../../../src/modules/products/products.repository.js', () => ({ ProductsRepository: class { findByIds = mockFindByIds; findById = vi.fn(); }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import recipesRoutes from './recipes.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import recipesRoutes from '../../../src/modules/recipes/recipes.routes.js'; const nutrition = { calories: 200, protein: 20, carbs: 10, fat: 8 }; diff --git a/packages/api/src/modules/recipes/recipes.service.test.ts b/packages/api/tests/modules/recipes/recipes.service.test.ts similarity index 98% rename from packages/api/src/modules/recipes/recipes.service.test.ts rename to packages/api/tests/modules/recipes/recipes.service.test.ts index 7f4f7ce..986e2ef 100644 --- a/packages/api/src/modules/recipes/recipes.service.test.ts +++ b/packages/api/tests/modules/recipes/recipes.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { RecipesService } from './recipes.service.js'; -import { NotFoundError, BadRequestError } from '../../common/errors.js'; +import { RecipesService } from '../../../src/modules/recipes/recipes.service.js'; +import { NotFoundError, BadRequestError } from '../../../src/common/errors.js'; const makeProduct = (id: string, servingUnit = 'g', servingSize = 100) => ({ _id: { toString: () => id }, diff --git a/packages/api/src/modules/recipes/unit-conversion.service.test.ts b/packages/api/tests/modules/recipes/unit-conversion.service.test.ts similarity index 98% rename from packages/api/src/modules/recipes/unit-conversion.service.test.ts rename to packages/api/tests/modules/recipes/unit-conversion.service.test.ts index 4c769e8..c81aabc 100644 --- a/packages/api/src/modules/recipes/unit-conversion.service.test.ts +++ b/packages/api/tests/modules/recipes/unit-conversion.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { toMetric } from './unit-conversion.service.js'; +import { toMetric } from '../../../src/modules/recipes/unit-conversion.service.js'; describe('toMetric', () => { describe('metric pass-through', () => { diff --git a/packages/api/src/modules/refills/refills.repository.test.ts b/packages/api/tests/modules/refills/refills.repository.test.ts similarity index 97% rename from packages/api/src/modules/refills/refills.repository.test.ts rename to packages/api/tests/modules/refills/refills.repository.test.ts index 48bd73e..c144a87 100644 --- a/packages/api/src/modules/refills/refills.repository.test.ts +++ b/packages/api/tests/modules/refills/refills.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() mockSave: vi.fn(), })); -vi.mock('../../schemas/refill-list.schema.js', () => { +vi.mock('../../../src/schemas/refill-list.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -33,7 +33,7 @@ vi.mock('../../schemas/refill-list.schema.js', () => { return { RefillListModel: FakeModel }; }); -import { RefillsRepository } from './refills.repository.js'; +import { RefillsRepository } from '../../../src/modules/refills/refills.repository.js'; describe(RefillsRepository.name, () => { let repo: RefillsRepository; diff --git a/packages/api/src/modules/refills/refills.routes.test.ts b/packages/api/tests/modules/refills/refills.routes.test.ts similarity index 96% rename from packages/api/src/modules/refills/refills.routes.test.ts rename to packages/api/tests/modules/refills/refills.routes.test.ts index 538299a..579ed9a 100644 --- a/packages/api/src/modules/refills/refills.routes.test.ts +++ b/packages/api/tests/modules/refills/refills.routes.test.ts @@ -38,7 +38,7 @@ const { mockGetStoreComparison: vi.fn(), })); -vi.mock('./refills.repository.js', () => ({ +vi.mock('../../../src/modules/refills/refills.repository.js', () => ({ RefillsRepository: class { create = vi.fn(); findByHousehold = vi.fn(); @@ -49,7 +49,7 @@ vi.mock('./refills.repository.js', () => ({ }, })); -vi.mock('./refills.service.js', () => ({ +vi.mock('../../../src/modules/refills/refills.service.js', () => ({ RefillsService: class { getAlerts = mockGetAlerts; createList = mockCreateList; @@ -62,17 +62,17 @@ vi.mock('./refills.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import refillsRoutes from './refills.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import refillsRoutes from '../../../src/modules/refills/refills.routes.js'; function makeFakeRefillList(overrides = {}) { return { diff --git a/packages/api/src/modules/refills/refills.service.test.ts b/packages/api/tests/modules/refills/refills.service.test.ts similarity index 99% rename from packages/api/src/modules/refills/refills.service.test.ts rename to packages/api/tests/modules/refills/refills.service.test.ts index f3b75fb..9ee6b36 100644 --- a/packages/api/src/modules/refills/refills.service.test.ts +++ b/packages/api/tests/modules/refills/refills.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { RefillsService } from './refills.service.js'; +import { RefillsService } from '../../../src/modules/refills/refills.service.js'; describe(RefillsService.name, () => { const mockRepo = { diff --git a/packages/api/src/modules/regimens/regimens.repository.test.ts b/packages/api/tests/modules/regimens/regimens.repository.test.ts similarity index 97% rename from packages/api/src/modules/regimens/regimens.repository.test.ts rename to packages/api/tests/modules/regimens/regimens.repository.test.ts index 6e050d7..90d11d9 100644 --- a/packages/api/src/modules/regimens/regimens.repository.test.ts +++ b/packages/api/tests/modules/regimens/regimens.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() mockSave: vi.fn(), })); -vi.mock('../../schemas/regimen.schema.js', () => { +vi.mock('../../../src/schemas/regimen.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -41,7 +41,7 @@ vi.mock('../../schemas/regimen.schema.js', () => { return { RegimenModel: FakeModel }; }); -import { RegimensRepository } from './regimens.repository.js'; +import { RegimensRepository } from '../../../src/modules/regimens/regimens.repository.js'; describe(RegimensRepository.name, () => { let repo: RegimensRepository; diff --git a/packages/api/src/modules/regimens/regimens.routes.test.ts b/packages/api/tests/modules/regimens/regimens.routes.test.ts similarity index 96% rename from packages/api/src/modules/regimens/regimens.routes.test.ts rename to packages/api/tests/modules/regimens/regimens.routes.test.ts index f6a4c37..b3bf11f 100644 --- a/packages/api/src/modules/regimens/regimens.routes.test.ts +++ b/packages/api/tests/modules/regimens/regimens.routes.test.ts @@ -29,7 +29,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockDelete, mockCalculate mockCalculateBurnRates: vi.fn(), })); -vi.mock('./regimens.repository.js', () => ({ +vi.mock('../../../src/modules/regimens/regimens.repository.js', () => ({ RegimensRepository: class { findByHousehold = vi.fn(); findById = vi.fn(); @@ -41,7 +41,7 @@ vi.mock('./regimens.repository.js', () => ({ }, })); -vi.mock('./regimens.service.js', () => ({ +vi.mock('../../../src/modules/regimens/regimens.service.js', () => ({ RegimensService: class { list = mockList; getById = mockGetById; @@ -53,17 +53,17 @@ vi.mock('./regimens.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import regimensRoutes from './regimens.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import regimensRoutes from '../../../src/modules/regimens/regimens.routes.js'; function makeFakeRegimen(overrides = {}) { return { diff --git a/packages/api/src/modules/regimens/regimens.service.test.ts b/packages/api/tests/modules/regimens/regimens.service.test.ts similarity index 99% rename from packages/api/src/modules/regimens/regimens.service.test.ts rename to packages/api/tests/modules/regimens/regimens.service.test.ts index a2d5fbc..8979adf 100644 --- a/packages/api/src/modules/regimens/regimens.service.test.ts +++ b/packages/api/tests/modules/regimens/regimens.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { RegimensService } from './regimens.service.js'; +import { RegimensService } from '../../../src/modules/regimens/regimens.service.js'; import { DosageFrequency } from '@meshitrack/shared'; describe(RegimensService.name, () => { diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.repository.test.ts b/packages/api/tests/modules/shopping-lists/shopping-lists.repository.test.ts similarity index 96% rename from packages/api/src/modules/shopping-lists/shopping-lists.repository.test.ts rename to packages/api/tests/modules/shopping-lists/shopping-lists.repository.test.ts index 279ad32..c09cb15 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.repository.test.ts +++ b/packages/api/tests/modules/shopping-lists/shopping-lists.repository.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { ShoppingListsRepository } from './shopping-lists.repository.js'; +import { ShoppingListsRepository } from '../../../src/modules/shopping-lists/shopping-lists.repository.js'; const { mockSave, MockShoppingListModel } = vi.hoisted(() => { const mockSave = vi.fn(); @@ -16,11 +16,11 @@ const { mockSave, MockShoppingListModel } = vi.hoisted(() => { return { mockSave, MockShoppingListModel: MockModel }; }); -vi.mock('../../schemas/shopping-list.schema.js', () => ({ +vi.mock('../../../src/schemas/shopping-list.schema.js', () => ({ ShoppingListModel: MockShoppingListModel, })); -const { ShoppingListModel } = await import('../../schemas/shopping-list.schema.js'); +const { ShoppingListModel } = await import('../../../src/schemas/shopping-list.schema.js'); function makeChain(result: unknown = null) { return { diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.routes.test.ts b/packages/api/tests/modules/shopping-lists/shopping-lists.routes.test.ts similarity index 92% rename from packages/api/src/modules/shopping-lists/shopping-lists.routes.test.ts rename to packages/api/tests/modules/shopping-lists/shopping-lists.routes.test.ts index 5b67fd4..fa4fcd5 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.routes.test.ts +++ b/packages/api/tests/modules/shopping-lists/shopping-lists.routes.test.ts @@ -26,7 +26,7 @@ const mockAddItem = vi.fn(); const mockUpdateItem = vi.fn(); const mockRemoveItem = vi.fn(); -vi.mock('./shopping-lists.repository.js', () => ({ +vi.mock('../../../src/modules/shopping-lists/shopping-lists.repository.js', () => ({ ShoppingListsRepository: class { list = mockList; findById = mockFindById; @@ -39,25 +39,25 @@ vi.mock('./shopping-lists.repository.js', () => ({ }, })); -vi.mock('../meal-plans/shopping-gap.service.js', () => ({ +vi.mock('../../../src/modules/meal-plans/shopping-gap.service.js', () => ({ ShoppingGapService: class { calculateGap = vi.fn().mockResolvedValue({ missingItems: [] }); }, })); -vi.mock('../pantry/pantry.service.js', () => ({ +vi.mock('../../../src/modules/pantry/pantry.service.js', () => ({ PantryService: class { create = vi.fn().mockResolvedValue({ _id: 'pant1' }); }, })); -vi.mock('../products/products.repository.js', () => ({ +vi.mock('../../../src/modules/products/products.repository.js', () => ({ ProductsRepository: class { findById = vi.fn().mockResolvedValue({ category: 'dairy' }); }, })); -vi.mock('../prices/prices.service.js', () => ({ +vi.mock('../../../src/modules/prices/prices.service.js', () => ({ PricesService: class { estimatePrice = vi.fn().mockResolvedValue(5.0); recordPrice = vi.fn().mockResolvedValue({}); @@ -65,24 +65,24 @@ vi.mock('../prices/prices.service.js', () => ({ }, })); -vi.mock('../meal-plans/meal-plans.repository.js', () => ({ +vi.mock('../../../src/modules/meal-plans/meal-plans.repository.js', () => ({ MealPlanRepository: class { findById = vi.fn().mockResolvedValue({ _id: 'mp1', weekStartDate: new Date() }); update = vi.fn().mockResolvedValue({}); }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import shoppingListsRoutes from './shopping-lists.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import shoppingListsRoutes from '../../../src/modules/shopping-lists/shopping-lists.routes.js'; describe('shopping-lists.routes', () => { let app: any; diff --git a/packages/api/src/modules/shopping-lists/shopping-lists.service.test.ts b/packages/api/tests/modules/shopping-lists/shopping-lists.service.test.ts similarity index 99% rename from packages/api/src/modules/shopping-lists/shopping-lists.service.test.ts rename to packages/api/tests/modules/shopping-lists/shopping-lists.service.test.ts index dbed730..b7fcdae 100644 --- a/packages/api/src/modules/shopping-lists/shopping-lists.service.test.ts +++ b/packages/api/tests/modules/shopping-lists/shopping-lists.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { ShoppingListsService } from './shopping-lists.service.js'; -import { NotFoundError } from '../../common/errors.js'; +import { ShoppingListsService } from '../../../src/modules/shopping-lists/shopping-lists.service.js'; +import { NotFoundError } from '../../../src/common/errors.js'; describe('ShoppingListsService', () => { let service: ShoppingListsService; diff --git a/packages/api/src/modules/stores/stores.repository.test.ts b/packages/api/tests/modules/stores/stores.repository.test.ts similarity index 97% rename from packages/api/src/modules/stores/stores.repository.test.ts rename to packages/api/tests/modules/stores/stores.repository.test.ts index 650adf8..bf04fbc 100644 --- a/packages/api/src/modules/stores/stores.repository.test.ts +++ b/packages/api/tests/modules/stores/stores.repository.test.ts @@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() mockSave: vi.fn(), })); -vi.mock('../../schemas/store.schema.js', () => { +vi.mock('../../../src/schemas/store.schema.js', () => { const chain = () => ({ sort: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), @@ -33,7 +33,7 @@ vi.mock('../../schemas/store.schema.js', () => { return { StoreModel: FakeModel }; }); -import { StoresRepository } from './stores.repository.js'; +import { StoresRepository } from '../../../src/modules/stores/stores.repository.js'; describe(StoresRepository.name, () => { let repo: StoresRepository; diff --git a/packages/api/src/modules/stores/stores.routes.test.ts b/packages/api/tests/modules/stores/stores.routes.test.ts similarity index 94% rename from packages/api/src/modules/stores/stores.routes.test.ts rename to packages/api/tests/modules/stores/stores.routes.test.ts index 5a8baac..7dcaad4 100644 --- a/packages/api/src/modules/stores/stores.routes.test.ts +++ b/packages/api/tests/modules/stores/stores.routes.test.ts @@ -26,7 +26,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockDeactivate } = vi.hoi mockDeactivate: vi.fn(), })); -vi.mock('./stores.repository.js', () => ({ +vi.mock('../../../src/modules/stores/stores.repository.js', () => ({ StoresRepository: class { findByHousehold = vi.fn(); findById = vi.fn(); @@ -36,7 +36,7 @@ vi.mock('./stores.repository.js', () => ({ }, })); -vi.mock('./stores.service.js', () => ({ +vi.mock('../../../src/modules/stores/stores.service.js', () => ({ StoresService: class { list = mockList; getById = mockGetById; @@ -46,17 +46,17 @@ vi.mock('./stores.service.js', () => ({ }, })); -vi.mock('../users/users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class { findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import householdPlugin from '../../plugins/household.plugin.js'; -import usersRoutes from '../users/users.routes.js'; -import storesRoutes from './stores.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../../src/plugins/household.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; +import storesRoutes from '../../../src/modules/stores/stores.routes.js'; function makeFakeStore(overrides = {}) { return { diff --git a/packages/api/src/modules/stores/stores.service.test.ts b/packages/api/tests/modules/stores/stores.service.test.ts similarity index 97% rename from packages/api/src/modules/stores/stores.service.test.ts rename to packages/api/tests/modules/stores/stores.service.test.ts index 59474d8..5bcc559 100644 --- a/packages/api/src/modules/stores/stores.service.test.ts +++ b/packages/api/tests/modules/stores/stores.service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { StoresService } from './stores.service.js'; +import { StoresService } from '../../../src/modules/stores/stores.service.js'; describe(StoresService.name, () => { const mockRepo = { diff --git a/packages/api/src/modules/users/users.repository.test.ts b/packages/api/tests/modules/users/users.repository.test.ts similarity index 96% rename from packages/api/src/modules/users/users.repository.test.ts rename to packages/api/tests/modules/users/users.repository.test.ts index 160ca23..219e9ca 100644 --- a/packages/api/src/modules/users/users.repository.test.ts +++ b/packages/api/tests/modules/users/users.repository.test.ts @@ -15,7 +15,7 @@ const { mockLean, mockExec, mockFindOne, mockFindById, mockFindOneAndUpdate, moc }; }); -vi.mock('../../schemas/user.schema.js', () => { +vi.mock('../../../src/schemas/user.schema.js', () => { class MockUserModel { _data: Record; constructor(data: Record) { @@ -36,7 +36,7 @@ vi.mock('../../schemas/user.schema.js', () => { return { UserModel: MockUserModel }; }); -import { UsersRepository } from './users.repository.js'; +import { UsersRepository } from '../../../src/modules/users/users.repository.js'; describe('UsersRepository', () => { let repo: UsersRepository; diff --git a/packages/api/src/modules/users/users.routes.test.ts b/packages/api/tests/modules/users/users.routes.test.ts similarity index 95% rename from packages/api/src/modules/users/users.routes.test.ts rename to packages/api/tests/modules/users/users.routes.test.ts index 62e46f9..852d7fe 100644 --- a/packages/api/src/modules/users/users.routes.test.ts +++ b/packages/api/tests/modules/users/users.routes.test.ts @@ -24,15 +24,15 @@ const { mockUpsertFromToken, mockFindByKeycloakId } = vi.hoisted(() => ({ mockUpsertFromToken: vi.fn(), mockFindByKeycloakId: vi.fn(), })); -vi.mock('./users.repository.js', () => ({ +vi.mock('../../../src/modules/users/users.repository.js', () => ({ UsersRepository: class MockUsersRepository { upsertFromToken = mockUpsertFromToken; findByKeycloakId = mockFindByKeycloakId; }, })); -import authPlugin from '../../plugins/auth.plugin.js'; -import usersRoutes from './users.routes.js'; +import authPlugin from '../../../src/plugins/auth.plugin.js'; +import usersRoutes from '../../../src/modules/users/users.routes.js'; describe('users.routes', () => { async function buildTestApp() { diff --git a/packages/api/src/modules/users/users.service.test.ts b/packages/api/tests/modules/users/users.service.test.ts similarity index 92% rename from packages/api/src/modules/users/users.service.test.ts rename to packages/api/tests/modules/users/users.service.test.ts index 43e496b..df83a52 100644 --- a/packages/api/src/modules/users/users.service.test.ts +++ b/packages/api/tests/modules/users/users.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { UsersService } from './users.service.js'; -import { NotFoundError } from '../../common/errors.js'; +import { UsersService } from '../../../src/modules/users/users.service.js'; +import { NotFoundError } from '../../../src/common/errors.js'; describe('UsersService', () => { const mockRepo = { diff --git a/packages/api/src/plugins/auth.plugin.test.ts b/packages/api/tests/plugins/auth.plugin.test.ts similarity index 98% rename from packages/api/src/plugins/auth.plugin.test.ts rename to packages/api/tests/plugins/auth.plugin.test.ts index 73ba23b..64ed961 100644 --- a/packages/api/src/plugins/auth.plugin.test.ts +++ b/packages/api/tests/plugins/auth.plugin.test.ts @@ -19,7 +19,7 @@ const { mockFindByKeycloakId, mockUpsertFromToken } = vi.hoisted(() => ({ mockUpsertFromToken: vi.fn(), })); -import authPlugin from './auth.plugin.js'; +import authPlugin from '../../src/plugins/auth.plugin.js'; import * as jose from 'jose'; describe('auth.plugin', () => { diff --git a/packages/api/src/plugins/household.plugin.test.ts b/packages/api/tests/plugins/household.plugin.test.ts similarity index 96% rename from packages/api/src/plugins/household.plugin.test.ts rename to packages/api/tests/plugins/household.plugin.test.ts index 29d2d2c..d52bc10 100644 --- a/packages/api/src/plugins/household.plugin.test.ts +++ b/packages/api/tests/plugins/household.plugin.test.ts @@ -22,8 +22,8 @@ const { mockFindByKeycloakId } = vi.hoisted(() => ({ mockFindByKeycloakId: vi.fn(), })); -import authPlugin from './auth.plugin.js'; -import householdPlugin from './household.plugin.js'; +import authPlugin from '../../src/plugins/auth.plugin.js'; +import householdPlugin from '../../src/plugins/household.plugin.js'; describe('household.plugin', () => { async function buildApp() { diff --git a/packages/api/src/plugins/mongoose.plugin.test.ts b/packages/api/tests/plugins/mongoose.plugin.test.ts similarity index 94% rename from packages/api/src/plugins/mongoose.plugin.test.ts rename to packages/api/tests/plugins/mongoose.plugin.test.ts index 20261cd..b4e5e78 100644 --- a/packages/api/src/plugins/mongoose.plugin.test.ts +++ b/packages/api/tests/plugins/mongoose.plugin.test.ts @@ -15,7 +15,7 @@ vi.mock('@fastify/awilix', () => ({ }, })); -import mongoosePlugin from './mongoose.plugin.js'; +import mongoosePlugin from '../../src/plugins/mongoose.plugin.js'; import mongoose from 'mongoose'; import { diContainer } from '@fastify/awilix'; diff --git a/packages/api/src/schemas/household.schema.test.ts b/packages/api/tests/schemas/household.schema.test.ts similarity index 89% rename from packages/api/src/schemas/household.schema.test.ts rename to packages/api/tests/schemas/household.schema.test.ts index bb7eb48..538df45 100644 --- a/packages/api/src/schemas/household.schema.test.ts +++ b/packages/api/tests/schemas/household.schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { HouseholdModel } from './household.schema.js'; +import { HouseholdModel } from '../../src/schemas/household.schema.js'; describe('HouseholdModel', () => { it('is a valid mongoose model', () => { diff --git a/packages/api/src/schemas/medicine-product.schema.test.ts b/packages/api/tests/schemas/medicine-product.schema.test.ts similarity index 90% rename from packages/api/src/schemas/medicine-product.schema.test.ts rename to packages/api/tests/schemas/medicine-product.schema.test.ts index ece1de8..5dc8f37 100644 --- a/packages/api/src/schemas/medicine-product.schema.test.ts +++ b/packages/api/tests/schemas/medicine-product.schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { MedicineProductModel } from './medicine-product.schema.js'; +import { MedicineProductModel } from '../../src/schemas/medicine-product.schema.js'; describe(MedicineProductModel.name, () => { it('is a valid mongoose model', () => { diff --git a/packages/api/src/schemas/medicine.schema.test.ts b/packages/api/tests/schemas/medicine.schema.test.ts similarity index 91% rename from packages/api/src/schemas/medicine.schema.test.ts rename to packages/api/tests/schemas/medicine.schema.test.ts index 80c87b5..6c5d388 100644 --- a/packages/api/src/schemas/medicine.schema.test.ts +++ b/packages/api/tests/schemas/medicine.schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { MedicineModel } from './medicine.schema.js'; +import { MedicineModel } from '../../src/schemas/medicine.schema.js'; describe(MedicineModel.name, () => { it('is a valid mongoose model', () => { diff --git a/packages/api/src/schemas/product.schema.test.ts b/packages/api/tests/schemas/product.schema.test.ts similarity index 94% rename from packages/api/src/schemas/product.schema.test.ts rename to packages/api/tests/schemas/product.schema.test.ts index 03b9b84..9a02713 100644 --- a/packages/api/src/schemas/product.schema.test.ts +++ b/packages/api/tests/schemas/product.schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { ProductModel } from './product.schema.js'; +import { ProductModel } from '../../src/schemas/product.schema.js'; describe(ProductModel.name, () => { it('is a valid mongoose model', () => { diff --git a/packages/api/src/schemas/user.schema.test.ts b/packages/api/tests/schemas/user.schema.test.ts similarity index 90% rename from packages/api/src/schemas/user.schema.test.ts rename to packages/api/tests/schemas/user.schema.test.ts index 7af4b94..3dcf3d8 100644 --- a/packages/api/src/schemas/user.schema.test.ts +++ b/packages/api/tests/schemas/user.schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { UserModel } from './user.schema.js'; +import { UserModel } from '../../src/schemas/user.schema.js'; describe('UserModel', () => { it('is a valid mongoose model', () => { diff --git a/packages/api/vitest.config.ts b/packages/api/vitest.config.ts index 663f4aa..00173a5 100644 --- a/packages/api/vitest.config.ts +++ b/packages/api/vitest.config.ts @@ -4,14 +4,13 @@ export default defineConfig({ test: { globals: true, environment: 'node', - include: ['src/**/*.test.ts'], + include: ['tests/**/*.test.ts'], coverage: { provider: 'v8', enabled: false, // enable via --coverage flag or test:cov script all: true, include: ['src/**/*.ts'], exclude: [ - 'src/**/*.test.ts', 'src/scripts/**', 'src/common/types.ts', // declaration merging only — no runtime logic ], diff --git a/packages/web/package.json b/packages/web/package.json index 080d27d..01c5898 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -11,6 +11,7 @@ "lint-fix": "eslint src --fix", "typecheck": "tsc --noEmit", "test": "vitest run", + "test:watch": "vitest", "test:cov": "vitest run --coverage" }, "dependencies": { diff --git a/packages/web/src/app/(dashboard)/dashboard/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/dashboard/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/dashboard/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/dashboard/page.test.tsx index 313342a..107fea8 100644 --- a/packages/web/src/app/(dashboard)/dashboard/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/dashboard/page.test.tsx @@ -32,7 +32,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import DashboardPage from '../page'; +import DashboardPage from '../../../../src/app/(dashboard)/dashboard/page'; beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/web/src/app/(dashboard)/__tests__/layout.test.tsx b/packages/web/tests/app/(dashboard)/layout.test.tsx similarity index 91% rename from packages/web/src/app/(dashboard)/__tests__/layout.test.tsx rename to packages/web/tests/app/(dashboard)/layout.test.tsx index b8afa9c..fd33bc7 100644 --- a/packages/web/src/app/(dashboard)/__tests__/layout.test.tsx +++ b/packages/web/tests/app/(dashboard)/layout.test.tsx @@ -9,7 +9,7 @@ vi.mock('@/components/layout/TopBar', () => ({ TopBar: () =>
, })); -import DashboardLayout from '../layout'; +import DashboardLayout from '../../../src/app/(dashboard)/layout'; describe('DashboardLayout', () => { it('renders sidebar, topbar and children', () => { diff --git a/packages/web/src/app/(dashboard)/__tests__/loading.test.tsx b/packages/web/tests/app/(dashboard)/loading.test.tsx similarity index 81% rename from packages/web/src/app/(dashboard)/__tests__/loading.test.tsx rename to packages/web/tests/app/(dashboard)/loading.test.tsx index c23731b..483a977 100644 --- a/packages/web/src/app/(dashboard)/__tests__/loading.test.tsx +++ b/packages/web/tests/app/(dashboard)/loading.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/react'; -import DashboardLoading from '../loading'; +import DashboardLoading from '../../../src/app/(dashboard)/loading'; describe('DashboardLoading', () => { it('renders a loading spinner', () => { diff --git a/packages/web/src/app/(dashboard)/medicine-prices/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicine-prices/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicine-prices/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicine-prices/page.test.tsx index 1c86984..d3afc9b 100644 --- a/packages/web/src/app/(dashboard)/medicine-prices/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicine-prices/page.test.tsx @@ -33,7 +33,7 @@ vi.mock('next/link', () => ({ default: (props: { href?: string; children: React.ReactNode }) => props.children, })); -import MedicinePricesPage from '../page'; +import MedicinePricesPage from '../../../../src/app/(dashboard)/medicine-prices/page'; const emptyResponse = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/medicines/__tests__/ActivityTab.test.tsx b/packages/web/tests/app/(dashboard)/medicines/ActivityTab.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicines/__tests__/ActivityTab.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/ActivityTab.test.tsx index 6274368..ca4f169 100644 --- a/packages/web/src/app/(dashboard)/medicines/__tests__/ActivityTab.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/ActivityTab.test.tsx @@ -16,7 +16,7 @@ vi.mock('@/services/cabinet-events', () => ({ vi.mock('@/services/medicines', () => ({ listMedicines: mockListMedicines })); -import { ActivityTab } from '../ActivityTab'; +import { ActivityTab } from '../../../../src/app/(dashboard)/medicines/ActivityTab'; const emptyMeds = { data: [], pagination: { cursor: null, hasMore: false } }; const emptyEvents = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/medicines/__tests__/CabinetTab.test.tsx b/packages/web/tests/app/(dashboard)/medicines/CabinetTab.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicines/__tests__/CabinetTab.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/CabinetTab.test.tsx index 6668989..16bf24e 100644 --- a/packages/web/src/app/(dashboard)/medicines/__tests__/CabinetTab.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/CabinetTab.test.tsx @@ -36,7 +36,7 @@ vi.mock('next/link', () => ({ ), })); -import { CabinetTab } from '../CabinetTab'; +import { CabinetTab } from '../../../../src/app/(dashboard)/medicines/CabinetTab'; const emptySummary = { data: [] }; const emptyItems = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/medicines/__tests__/LibraryTab.test.tsx b/packages/web/tests/app/(dashboard)/medicines/LibraryTab.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicines/__tests__/LibraryTab.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/LibraryTab.test.tsx index d61904f..1daa4e1 100644 --- a/packages/web/src/app/(dashboard)/medicines/__tests__/LibraryTab.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/LibraryTab.test.tsx @@ -21,7 +21,7 @@ vi.mock('next/link', () => ({ ), })); -import { LibraryTab } from '../LibraryTab'; +import { LibraryTab } from '../../../../src/app/(dashboard)/medicines/LibraryTab'; const emptyResponse = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/medicines/__tests__/OrganizerTab.test.tsx b/packages/web/tests/app/(dashboard)/medicines/OrganizerTab.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicines/__tests__/OrganizerTab.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/OrganizerTab.test.tsx index 3c5639f..b71429e 100644 --- a/packages/web/src/app/(dashboard)/medicines/__tests__/OrganizerTab.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/OrganizerTab.test.tsx @@ -20,7 +20,7 @@ vi.mock('@/services/organizer', () => ({ vi.mock('@/services/regimens', () => ({ listRegimens: mockListRegimens })); -import { OrganizerTab } from '../OrganizerTab'; +import { OrganizerTab } from '../../../../src/app/(dashboard)/medicines/OrganizerTab'; const emptyRegimens = { data: [], pagination: { cursor: null, hasMore: false } }; const emptyFills = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/medicines/__tests__/RegimensTab.test.tsx b/packages/web/tests/app/(dashboard)/medicines/RegimensTab.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicines/__tests__/RegimensTab.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/RegimensTab.test.tsx index 29b4f8d..50b94a9 100644 --- a/packages/web/src/app/(dashboard)/medicines/__tests__/RegimensTab.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/RegimensTab.test.tsx @@ -29,7 +29,7 @@ vi.mock('@/services/regimens', () => ({ vi.mock('@/services/medicines', () => ({ listMedicines: mockListMedicines })); -import { RegimensTab } from '../RegimensTab'; +import { RegimensTab } from '../../../../src/app/(dashboard)/medicines/RegimensTab'; const emptyMeds = { data: [], pagination: { cursor: null, hasMore: false } }; const emptyRegimens = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/medicines/[id]/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/[id]/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/medicines/[id]/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/[id]/page.test.tsx index 7acd4ae..8fed027 100644 --- a/packages/web/src/app/(dashboard)/medicines/[id]/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/[id]/page.test.tsx @@ -53,7 +53,7 @@ vi.mock('@/services/cabinet', () => ({ deleteCabinetItem: mockDeleteCabinetItem, })); -import MedicineDetailPage from '../page'; +import MedicineDetailPage from '../../../../../src/app/(dashboard)/medicines/[id]/page'; const medicine = { _id: 'med-1', diff --git a/packages/web/src/app/(dashboard)/medicines/activity/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/activity/page.test.tsx similarity index 94% rename from packages/web/src/app/(dashboard)/medicines/activity/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/activity/page.test.tsx index 71bd631..a82d123 100644 --- a/packages/web/src/app/(dashboard)/medicines/activity/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/activity/page.test.tsx @@ -14,7 +14,7 @@ vi.mock('@/app/(dashboard)/medicines/ActivityTab', () => ({ ), })); -import ActivityPage from '../page'; +import ActivityPage from '../../../../../src/app/(dashboard)/medicines/activity/page'; describe('ActivityPage', () => { it('shows skeleton when loading', () => { diff --git a/packages/web/src/app/(dashboard)/medicines/cabinet/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/cabinet/page.test.tsx similarity index 94% rename from packages/web/src/app/(dashboard)/medicines/cabinet/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/cabinet/page.test.tsx index 3bc6e90..d4a11ae 100644 --- a/packages/web/src/app/(dashboard)/medicines/cabinet/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/cabinet/page.test.tsx @@ -14,7 +14,7 @@ vi.mock('@/app/(dashboard)/medicines/CabinetTab', () => ({ ), })); -import CabinetPage from '../page'; +import CabinetPage from '../../../../../src/app/(dashboard)/medicines/cabinet/page'; describe('CabinetPage', () => { it('shows skeleton when loading', () => { diff --git a/packages/web/src/app/(dashboard)/medicines/library/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/library/page.test.tsx similarity index 94% rename from packages/web/src/app/(dashboard)/medicines/library/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/library/page.test.tsx index 4a7a9a4..ac5d60b 100644 --- a/packages/web/src/app/(dashboard)/medicines/library/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/library/page.test.tsx @@ -14,7 +14,7 @@ vi.mock('@/app/(dashboard)/medicines/LibraryTab', () => ({ ), })); -import LibraryPage from '../page'; +import LibraryPage from '../../../../../src/app/(dashboard)/medicines/library/page'; describe('LibraryPage', () => { it('shows skeleton when loading', () => { diff --git a/packages/web/src/app/(dashboard)/medicines/organizer/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/organizer/page.test.tsx similarity index 94% rename from packages/web/src/app/(dashboard)/medicines/organizer/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/organizer/page.test.tsx index aa11791..135bc78 100644 --- a/packages/web/src/app/(dashboard)/medicines/organizer/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/organizer/page.test.tsx @@ -14,7 +14,7 @@ vi.mock('@/app/(dashboard)/medicines/OrganizerTab', () => ({ ), })); -import OrganizerPage from '../page'; +import OrganizerPage from '../../../../../src/app/(dashboard)/medicines/organizer/page'; describe('OrganizerPage', () => { it('shows skeleton when loading', () => { diff --git a/packages/web/src/app/(dashboard)/medicines/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/page.test.tsx similarity index 95% rename from packages/web/src/app/(dashboard)/medicines/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/page.test.tsx index 94d91d3..de20172 100644 --- a/packages/web/src/app/(dashboard)/medicines/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/page.test.tsx @@ -12,7 +12,7 @@ vi.mock('next/link', () => { return { default: (props: any) => props.children }; }); -import MedicinesPage from '../page'; +import MedicinesPage from '../../../../src/app/(dashboard)/medicines/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/medicines/regimens/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/regimens/page.test.tsx similarity index 94% rename from packages/web/src/app/(dashboard)/medicines/regimens/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/regimens/page.test.tsx index 648cd70..f4e853a 100644 --- a/packages/web/src/app/(dashboard)/medicines/regimens/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/regimens/page.test.tsx @@ -14,7 +14,7 @@ vi.mock('@/app/(dashboard)/medicines/RegimensTab', () => ({ ), })); -import RegimensPage from '../page'; +import RegimensPage from '../../../../../src/app/(dashboard)/medicines/regimens/page'; describe('RegimensPage', () => { it('shows skeleton when loading', () => { diff --git a/packages/web/src/app/(dashboard)/medicines/schedule/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/medicines/schedule/page.test.tsx similarity index 98% rename from packages/web/src/app/(dashboard)/medicines/schedule/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/medicines/schedule/page.test.tsx index 43620dd..0f83dab 100644 --- a/packages/web/src/app/(dashboard)/medicines/schedule/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/medicines/schedule/page.test.tsx @@ -20,7 +20,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import SchedulePage from '../page'; +import SchedulePage from '../../../../../src/app/(dashboard)/medicines/schedule/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/pantry/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/pantry/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/pantry/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/pantry/page.test.tsx index 789215b..6305088 100644 --- a/packages/web/src/app/(dashboard)/pantry/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/pantry/page.test.tsx @@ -25,7 +25,7 @@ vi.mock('next/link', () => { return { default: (props: any) => props.children }; }); -import PantryPage from '../page'; +import PantryPage from '../../../../src/app/(dashboard)/pantry/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/products/__tests__/ImportDialog.test.tsx b/packages/web/tests/app/(dashboard)/products/ImportDialog.test.tsx similarity index 98% rename from packages/web/src/app/(dashboard)/products/__tests__/ImportDialog.test.tsx rename to packages/web/tests/app/(dashboard)/products/ImportDialog.test.tsx index da493d9..54cade8 100644 --- a/packages/web/src/app/(dashboard)/products/__tests__/ImportDialog.test.tsx +++ b/packages/web/tests/app/(dashboard)/products/ImportDialog.test.tsx @@ -9,7 +9,7 @@ vi.mock('@/services/products', () => ({ importProducts: mockImportProducts, })); -import { ImportDialog } from '../ImportDialog'; +import { ImportDialog } from '../../../../src/app/(dashboard)/products/ImportDialog'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/products/__tests__/ProductModal.test.tsx b/packages/web/tests/app/(dashboard)/products/ProductModal.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/products/__tests__/ProductModal.test.tsx rename to packages/web/tests/app/(dashboard)/products/ProductModal.test.tsx index a9b0656..811f65e 100644 --- a/packages/web/src/app/(dashboard)/products/__tests__/ProductModal.test.tsx +++ b/packages/web/tests/app/(dashboard)/products/ProductModal.test.tsx @@ -9,7 +9,7 @@ vi.mock('@/services/products', () => ({ lookupBarcode: mockLookupBarcode, })); -import { ProductModal } from '../ProductModal'; +import { ProductModal } from '../../../../src/app/(dashboard)/products/ProductModal'; import { ProductCategory, ServingUnit } from '@meshitrack/shared'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/products/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/products/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/products/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/products/page.test.tsx index 8aee1da..1862001 100644 --- a/packages/web/src/app/(dashboard)/products/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/products/page.test.tsx @@ -26,7 +26,7 @@ vi.mock('next/link', () => { return { default: (props: any) => props.children }; }); -import ProductsPage from '../page'; +import ProductsPage from '../../../../src/app/(dashboard)/products/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/purchases/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/purchases/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/purchases/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/purchases/page.test.tsx index 7b8f309..529703d 100644 --- a/packages/web/src/app/(dashboard)/purchases/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/purchases/page.test.tsx @@ -35,7 +35,7 @@ vi.mock('next/link', () => ({ default: (props: { href?: string; children: React.ReactNode }) => props.children, })); -import PurchasesPage from '../page'; +import PurchasesPage from '../../../../src/app/(dashboard)/purchases/page'; const emptyResponse = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/recipes/__tests__/RecipeEditor.test.tsx b/packages/web/tests/app/(dashboard)/recipes/RecipeEditor.test.tsx similarity index 98% rename from packages/web/src/app/(dashboard)/recipes/__tests__/RecipeEditor.test.tsx rename to packages/web/tests/app/(dashboard)/recipes/RecipeEditor.test.tsx index 9476ad9..a313c4e 100644 --- a/packages/web/src/app/(dashboard)/recipes/__tests__/RecipeEditor.test.tsx +++ b/packages/web/tests/app/(dashboard)/recipes/RecipeEditor.test.tsx @@ -39,7 +39,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import { RecipeEditor } from '../RecipeEditor'; +import { RecipeEditor } from '../../../../src/app/(dashboard)/recipes/RecipeEditor'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/recipes/[id]/edit/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/recipes/[id]/edit/page.test.tsx similarity index 96% rename from packages/web/src/app/(dashboard)/recipes/[id]/edit/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/recipes/[id]/edit/page.test.tsx index b65f4dc..d72e7ec 100644 --- a/packages/web/src/app/(dashboard)/recipes/[id]/edit/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/recipes/[id]/edit/page.test.tsx @@ -33,7 +33,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import EditRecipePage from '../page'; +import EditRecipePage from '../../../../../../src/app/(dashboard)/recipes/[id]/edit/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/recipes/[id]/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/recipes/[id]/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/recipes/[id]/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/recipes/[id]/page.test.tsx index 88ce116..cdc7a1d 100644 --- a/packages/web/src/app/(dashboard)/recipes/[id]/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/recipes/[id]/page.test.tsx @@ -34,7 +34,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import RecipeDetailPage from '../page'; +import RecipeDetailPage from '../../../../../src/app/(dashboard)/recipes/[id]/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/recipes/new/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/recipes/new/page.test.tsx similarity index 95% rename from packages/web/src/app/(dashboard)/recipes/new/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/recipes/new/page.test.tsx index 9dc72b5..6645524 100644 --- a/packages/web/src/app/(dashboard)/recipes/new/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/recipes/new/page.test.tsx @@ -28,7 +28,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import NewRecipePage from '../page'; +import NewRecipePage from '../../../../../src/app/(dashboard)/recipes/new/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/recipes/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/recipes/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/recipes/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/recipes/page.test.tsx index 74cf8a8..2813136 100644 --- a/packages/web/src/app/(dashboard)/recipes/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/recipes/page.test.tsx @@ -29,7 +29,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import RecipesPage from '../page'; +import RecipesPage from '../../../../src/app/(dashboard)/recipes/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/(dashboard)/refills/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/refills/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/refills/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/refills/page.test.tsx index b6bc6f1..e824a8b 100644 --- a/packages/web/src/app/(dashboard)/refills/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/refills/page.test.tsx @@ -34,7 +34,7 @@ vi.mock('next/link', () => ({ default: (props: { href?: string; children: React.ReactNode }) => props.children, })); -import RefillsPage from '../page'; +import RefillsPage from '../../../../src/app/(dashboard)/refills/page'; const emptyAlerts = { data: [], pagination: { cursor: null, hasMore: false } }; const emptyLists = { data: [], pagination: { cursor: null, hasMore: false } }; diff --git a/packages/web/src/app/(dashboard)/settings/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/settings/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/settings/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/settings/page.test.tsx index b03585d..19faa05 100644 --- a/packages/web/src/app/(dashboard)/settings/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/settings/page.test.tsx @@ -29,7 +29,7 @@ vi.mock('@/services/households', () => ({ generateInviteCode: mockGenerateInviteCode, })); -import SettingsPage from '../page'; +import SettingsPage from '../../../../src/app/(dashboard)/settings/page'; const household = { name: 'My House', diff --git a/packages/web/src/app/(dashboard)/shopping-lists/[id]/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/shopping-lists/[id]/page.test.tsx similarity index 98% rename from packages/web/src/app/(dashboard)/shopping-lists/[id]/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/shopping-lists/[id]/page.test.tsx index dc88fac..4a4cb3f 100644 --- a/packages/web/src/app/(dashboard)/shopping-lists/[id]/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/shopping-lists/[id]/page.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SWRConfig } from 'swr'; -import ShoppingListDetailPage from '../page'; +import ShoppingListDetailPage from '../../../../../src/app/(dashboard)/shopping-lists/[id]/page'; import * as useApiModule from '@/lib/useApi'; import * as ShoppingListsService from '@/services/shopping-lists'; import * as ProductsService from '@/services/products'; diff --git a/packages/web/src/app/(dashboard)/shopping-lists/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/shopping-lists/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/shopping-lists/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/shopping-lists/page.test.tsx index ec4bd2b..9d4fa73 100644 --- a/packages/web/src/app/(dashboard)/shopping-lists/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/shopping-lists/page.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import ShoppingListsPage from '../page'; +import ShoppingListsPage from '../../../../src/app/(dashboard)/shopping-lists/page'; import * as useApiModule from '@/lib/useApi'; import * as ShoppingListsService from '@/services/shopping-lists'; import * as MealPlansService from '@/services/meal-plans'; diff --git a/packages/web/src/app/(dashboard)/shopping-lists/prices/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/shopping-lists/prices/page.test.tsx similarity index 96% rename from packages/web/src/app/(dashboard)/shopping-lists/prices/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/shopping-lists/prices/page.test.tsx index e9fa466..352e536 100644 --- a/packages/web/src/app/(dashboard)/shopping-lists/prices/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/shopping-lists/prices/page.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import ShoppingListPricesPage from '../page'; +import ShoppingListPricesPage from '../../../../../src/app/(dashboard)/shopping-lists/prices/page'; import * as useApiModule from '@/lib/useApi'; import * as PricesService from '@/services/prices'; diff --git a/packages/web/src/app/(dashboard)/stores/__tests__/page.test.tsx b/packages/web/tests/app/(dashboard)/stores/page.test.tsx similarity index 99% rename from packages/web/src/app/(dashboard)/stores/__tests__/page.test.tsx rename to packages/web/tests/app/(dashboard)/stores/page.test.tsx index 456fe19..a01f656 100644 --- a/packages/web/src/app/(dashboard)/stores/__tests__/page.test.tsx +++ b/packages/web/tests/app/(dashboard)/stores/page.test.tsx @@ -29,7 +29,7 @@ vi.mock('next/link', () => { return { default: (props: any) => props.children }; }); -import StoresPage from '../page'; +import StoresPage from '../../../../src/app/(dashboard)/stores/page'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/app/__tests__/page.test.tsx b/packages/web/tests/app/page.test.tsx similarity index 93% rename from packages/web/src/app/__tests__/page.test.tsx rename to packages/web/tests/app/page.test.tsx index c1c70c7..7c21ef9 100644 --- a/packages/web/src/app/__tests__/page.test.tsx +++ b/packages/web/tests/app/page.test.tsx @@ -6,7 +6,7 @@ vi.mock('next/link', () => ({ default: (props: any) => {props.children}, })); -import HomePage from '../page'; +import HomePage from '../../src/app/page'; describe('HomePage', () => { it('renders MeshiTrack heading', () => { diff --git a/packages/web/src/components/__tests__/Sidebar.test.tsx b/packages/web/tests/components/Sidebar.test.tsx similarity index 97% rename from packages/web/src/components/__tests__/Sidebar.test.tsx rename to packages/web/tests/components/Sidebar.test.tsx index 576573b..17c07e4 100644 --- a/packages/web/src/components/__tests__/Sidebar.test.tsx +++ b/packages/web/tests/components/Sidebar.test.tsx @@ -20,7 +20,7 @@ vi.mock('next/link', () => ({ vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi })); -import { Sidebar } from '../layout/Sidebar'; +import { Sidebar } from '../../src/components/layout/Sidebar'; describe('Sidebar', () => { beforeEach(() => { diff --git a/packages/web/src/components/__tests__/ThemeProvider.test.tsx b/packages/web/tests/components/ThemeProvider.test.tsx similarity index 96% rename from packages/web/src/components/__tests__/ThemeProvider.test.tsx rename to packages/web/tests/components/ThemeProvider.test.tsx index 99ebd4f..7677167 100644 --- a/packages/web/src/components/__tests__/ThemeProvider.test.tsx +++ b/packages/web/tests/components/ThemeProvider.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, act } from '@testing-library/react'; -import { ThemeProvider, useTheme } from '../ThemeProvider'; -import { Providers } from '../Providers'; +import { ThemeProvider, useTheme } from '../../src/components/ThemeProvider'; +import { Providers } from '../../src/components/Providers'; // Mock next-auth SessionProvider vi.mock('next-auth/react', () => ({ diff --git a/packages/web/src/components/__tests__/TopBar.test.tsx b/packages/web/tests/components/TopBar.test.tsx similarity index 94% rename from packages/web/src/components/__tests__/TopBar.test.tsx rename to packages/web/tests/components/TopBar.test.tsx index 7e2c84d..facefa2 100644 --- a/packages/web/src/components/__tests__/TopBar.test.tsx +++ b/packages/web/tests/components/TopBar.test.tsx @@ -16,7 +16,7 @@ vi.mock('@/components/layout/PageHeaderContext', () => ({ }), })); -import { TopBar } from '../layout/TopBar'; +import { TopBar } from '../../src/components/layout/TopBar'; describe('TopBar', () => { it('renders title and subtitle', () => { diff --git a/packages/web/src/components/layout/__tests__/PageHeaderContext.test.tsx b/packages/web/tests/components/layout/PageHeaderContext.test.tsx similarity index 90% rename from packages/web/src/components/layout/__tests__/PageHeaderContext.test.tsx rename to packages/web/tests/components/layout/PageHeaderContext.test.tsx index 89183ab..ee07119 100644 --- a/packages/web/src/components/layout/__tests__/PageHeaderContext.test.tsx +++ b/packages/web/tests/components/layout/PageHeaderContext.test.tsx @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; -import { PageHeaderProvider, usePageHeader } from '../PageHeaderContext'; +import { PageHeaderProvider, usePageHeader } from '../../../src/components/layout/PageHeaderContext'; function TestComponent() { const { header, setHeader } = usePageHeader(); diff --git a/packages/web/src/components/__tests__/ui.test.tsx b/packages/web/tests/components/ui.test.tsx similarity index 90% rename from packages/web/src/components/__tests__/ui.test.tsx rename to packages/web/tests/components/ui.test.tsx index 6fd4c4d..b4d4a29 100644 --- a/packages/web/src/components/__tests__/ui.test.tsx +++ b/packages/web/tests/components/ui.test.tsx @@ -1,11 +1,11 @@ import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; -import { Avatar } from '../ui/Avatar'; -import { IconButton } from '../ui/IconButton'; -import { Ring } from '../ui/Ring'; -import { SparkBars } from '../ui/SparkBars'; -import { SupplyBar } from '../ui/SupplyBar'; -import { Card, CardHeader, CardBody } from '../ui/Card'; +import { Avatar } from '../../src/components/ui/Avatar'; +import { IconButton } from '../../src/components/ui/IconButton'; +import { Ring } from '../../src/components/ui/Ring'; +import { SparkBars } from '../../src/components/ui/SparkBars'; +import { SupplyBar } from '../../src/components/ui/SupplyBar'; +import { Card, CardHeader, CardBody } from '../../src/components/ui/Card'; describe('Avatar', () => { it('renders initial from name', () => { diff --git a/packages/web/src/components/ui/__tests__/index.test.ts b/packages/web/tests/components/ui/index.test.ts similarity index 89% rename from packages/web/src/components/ui/__tests__/index.test.ts rename to packages/web/tests/components/ui/index.test.ts index ef2d5ea..6d7b105 100644 --- a/packages/web/src/components/ui/__tests__/index.test.ts +++ b/packages/web/tests/components/ui/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import * as UI from '../index'; +import * as UI from '../../../src/components/ui/index'; describe('UI Index Exports', () => { it('should export all defined UI components', () => { diff --git a/packages/web/src/lib/__tests__/useApi.test.ts b/packages/web/tests/lib/useApi.test.ts similarity index 98% rename from packages/web/src/lib/__tests__/useApi.test.ts rename to packages/web/tests/lib/useApi.test.ts index 3443017..2635de0 100644 --- a/packages/web/src/lib/__tests__/useApi.test.ts +++ b/packages/web/tests/lib/useApi.test.ts @@ -27,7 +27,7 @@ vi.mock('@/services/api-client', () => ({ apiClient: mockApiClient, })); -import { useApi } from '../useApi'; +import { useApi } from '../../src/lib/useApi'; beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/web/src/lib/useShoppingListSync.test.ts b/packages/web/tests/lib/useShoppingListSync.test.ts similarity index 97% rename from packages/web/src/lib/useShoppingListSync.test.ts rename to packages/web/tests/lib/useShoppingListSync.test.ts index 02ea188..07b6452 100644 --- a/packages/web/src/lib/useShoppingListSync.test.ts +++ b/packages/web/tests/lib/useShoppingListSync.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, act } from '@testing-library/react'; -import { useShoppingListSync } from './useShoppingListSync'; +import { useShoppingListSync } from '../../src/lib/useShoppingListSync'; vi.mock('@/services/shopping-lists', () => ({ getShoppingListSyncSocketUrl: vi.fn(() => 'ws://localhost/sync'), diff --git a/packages/web/src/services/__tests__/api-client.test.ts b/packages/web/tests/services/api-client.test.ts similarity index 97% rename from packages/web/src/services/__tests__/api-client.test.ts rename to packages/web/tests/services/api-client.test.ts index fb803b3..ce9fbc3 100644 --- a/packages/web/src/services/__tests__/api-client.test.ts +++ b/packages/web/tests/services/api-client.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import type { apiClient as ApiClientType } from '../api-client'; +import type { apiClient as ApiClientType } from '../../src/services/api-client'; // Create a fresh ApiClient for each test by re-importing let apiClient: typeof ApiClientType; @@ -9,7 +9,7 @@ beforeEach(async () => { vi.restoreAllMocks(); // Reset module to get a fresh singleton vi.resetModules(); - const mod = await import('../api-client'); + const mod = await import('../../src/services/api-client'); apiClient = mod.apiClient; }); diff --git a/packages/web/src/services/__tests__/cabinet-events.test.ts b/packages/web/tests/services/cabinet-events.test.ts similarity index 96% rename from packages/web/src/services/__tests__/cabinet-events.test.ts rename to packages/web/tests/services/cabinet-events.test.ts index bd3ac91..1cda925 100644 --- a/packages/web/src/services/__tests__/cabinet-events.test.ts +++ b/packages/web/tests/services/cabinet-events.test.ts @@ -4,11 +4,11 @@ const { mockGet } = vi.hoisted(() => ({ mockGet: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet }, })); -import { listCabinetEvents, getEventsByItem, getSpendingSummary } from '../cabinet-events'; +import { listCabinetEvents, getEventsByItem, getSpendingSummary } from '../../src/services/cabinet-events'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/cabinet.test.ts b/packages/web/tests/services/cabinet.test.ts similarity index 97% rename from packages/web/src/services/__tests__/cabinet.test.ts rename to packages/web/tests/services/cabinet.test.ts index 7322968..3efbfb5 100644 --- a/packages/web/src/services/__tests__/cabinet.test.ts +++ b/packages/web/tests/services/cabinet.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -20,7 +20,7 @@ import { updateCabinetItem, adjustCabinetItemQuantity, deleteCabinetItem, -} from '../cabinet'; +} from '../../src/services/cabinet'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/households.test.ts b/packages/web/tests/services/households.test.ts similarity index 94% rename from packages/web/src/services/__tests__/households.test.ts rename to packages/web/tests/services/households.test.ts index 6cc6b43..8bb11a3 100644 --- a/packages/web/src/services/__tests__/households.test.ts +++ b/packages/web/tests/services/households.test.ts @@ -6,7 +6,7 @@ const { mockGet, mockPost, mockPatch } = vi.hoisted(() => ({ mockPatch: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch }, })); @@ -16,7 +16,7 @@ import { updateHousehold, generateInviteCode, joinHousehold, -} from '../households'; +} from '../../src/services/households'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/meal-plans.test.ts b/packages/web/tests/services/meal-plans.test.ts similarity index 92% rename from packages/web/src/services/meal-plans.test.ts rename to packages/web/tests/services/meal-plans.test.ts index b719f94..9eac67a 100644 --- a/packages/web/src/services/meal-plans.test.ts +++ b/packages/web/tests/services/meal-plans.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { apiClient } from './api-client'; -import * as MealPlansService from './meal-plans'; +import { apiClient } from '../../src/services/api-client'; +import * as MealPlansService from '../../src/services/meal-plans'; -vi.mock('./api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: vi.fn(), post: vi.fn(), diff --git a/packages/web/src/services/__tests__/medicine-prices.test.ts b/packages/web/tests/services/medicine-prices.test.ts similarity index 96% rename from packages/web/src/services/__tests__/medicine-prices.test.ts rename to packages/web/tests/services/medicine-prices.test.ts index a75c42e..c221ce8 100644 --- a/packages/web/src/services/__tests__/medicine-prices.test.ts +++ b/packages/web/tests/services/medicine-prices.test.ts @@ -5,11 +5,11 @@ const { mockGet, mockPost } = vi.hoisted(() => ({ mockPost: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost }, })); -import { recordPrice, getPriceHistory, compareStores, getPriceAnalytics } from '../medicine-prices'; +import { recordPrice, getPriceHistory, compareStores, getPriceAnalytics } from '../../src/services/medicine-prices'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/medicines.test.ts b/packages/web/tests/services/medicines.test.ts similarity index 97% rename from packages/web/src/services/__tests__/medicines.test.ts rename to packages/web/tests/services/medicines.test.ts index 1baef19..d3ecb34 100644 --- a/packages/web/src/services/__tests__/medicines.test.ts +++ b/packages/web/tests/services/medicines.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -21,7 +21,7 @@ import { createMedicineProduct, updateMedicineProduct, deleteMedicineProduct, -} from '../medicines'; +} from '../../src/services/medicines'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/nutrition-targets.test.ts b/packages/web/tests/services/nutrition-targets.test.ts similarity index 85% rename from packages/web/src/services/nutrition-targets.test.ts rename to packages/web/tests/services/nutrition-targets.test.ts index 8f4118c..6ee8057 100644 --- a/packages/web/src/services/nutrition-targets.test.ts +++ b/packages/web/tests/services/nutrition-targets.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { apiClient } from './api-client'; -import * as NutritionTargetsService from './nutrition-targets'; +import { apiClient } from '../../src/services/api-client'; +import * as NutritionTargetsService from '../../src/services/nutrition-targets'; -vi.mock('./api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: vi.fn(), post: vi.fn(), diff --git a/packages/web/src/services/__tests__/organizer.test.ts b/packages/web/tests/services/organizer.test.ts similarity index 96% rename from packages/web/src/services/__tests__/organizer.test.ts rename to packages/web/tests/services/organizer.test.ts index b488593..d371b05 100644 --- a/packages/web/src/services/__tests__/organizer.test.ts +++ b/packages/web/tests/services/organizer.test.ts @@ -5,11 +5,11 @@ const { mockGet, mockPost } = vi.hoisted(() => ({ mockPost: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost }, })); -import { listFills, getFill, previewFill, executeFill, undoFill } from '../organizer'; +import { listFills, getFill, previewFill, executeFill, undoFill } from '../../src/services/organizer'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/pantry.test.ts b/packages/web/tests/services/pantry.test.ts similarity index 98% rename from packages/web/src/services/__tests__/pantry.test.ts rename to packages/web/tests/services/pantry.test.ts index ecd09e6..7afb14d 100644 --- a/packages/web/src/services/__tests__/pantry.test.ts +++ b/packages/web/tests/services/pantry.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -21,7 +21,7 @@ import { getExpiringSoon, getWasteStats, deletePantryItem, -} from '../pantry'; +} from '../../src/services/pantry'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/prices.test.ts b/packages/web/tests/services/prices.test.ts similarity index 90% rename from packages/web/src/services/prices.test.ts rename to packages/web/tests/services/prices.test.ts index e8fc70c..3033bf4 100644 --- a/packages/web/src/services/prices.test.ts +++ b/packages/web/tests/services/prices.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { apiClient } from './api-client'; -import * as PricesService from './prices'; +import { apiClient } from '../../src/services/api-client'; +import * as PricesService from '../../src/services/prices'; -vi.mock('./api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: vi.fn(), post: vi.fn(), diff --git a/packages/web/src/services/__tests__/products.test.ts b/packages/web/tests/services/products.test.ts similarity index 98% rename from packages/web/src/services/__tests__/products.test.ts rename to packages/web/tests/services/products.test.ts index f2a5363..2696f2f 100644 --- a/packages/web/src/services/__tests__/products.test.ts +++ b/packages/web/tests/services/products.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -20,7 +20,7 @@ import { deleteProduct, smartAddProduct, importProducts, -} from '../products'; +} from '../../src/services/products'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/purchases.test.ts b/packages/web/tests/services/purchases.test.ts similarity index 96% rename from packages/web/src/services/__tests__/purchases.test.ts rename to packages/web/tests/services/purchases.test.ts index 3e02987..c71e019 100644 --- a/packages/web/src/services/__tests__/purchases.test.ts +++ b/packages/web/tests/services/purchases.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -18,7 +18,7 @@ import { updatePurchase, receivePurchase, deletePurchase, -} from '../purchases'; +} from '../../src/services/purchases'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/recipes.test.ts b/packages/web/tests/services/recipes.test.ts similarity index 97% rename from packages/web/src/services/__tests__/recipes.test.ts rename to packages/web/tests/services/recipes.test.ts index 8c090da..9d238e4 100644 --- a/packages/web/src/services/__tests__/recipes.test.ts +++ b/packages/web/tests/services/recipes.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -21,7 +21,7 @@ import { importRecipeFromText, importRecipeFromUrl, listRecipesByProduct, -} from '../recipes'; +} from '../../src/services/recipes'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/refills.test.ts b/packages/web/tests/services/refills.test.ts similarity index 97% rename from packages/web/src/services/__tests__/refills.test.ts rename to packages/web/tests/services/refills.test.ts index f2e20f8..0c45456 100644 --- a/packages/web/src/services/__tests__/refills.test.ts +++ b/packages/web/tests/services/refills.test.ts @@ -6,7 +6,7 @@ const { mockGet, mockPost, mockPatch } = vi.hoisted(() => ({ mockPatch: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch }, })); @@ -18,7 +18,7 @@ import { updateRefillList, updateRefillListItem, addToCabinet, -} from '../refills'; +} from '../../src/services/refills'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/__tests__/regimens.test.ts b/packages/web/tests/services/regimens.test.ts similarity index 96% rename from packages/web/src/services/__tests__/regimens.test.ts rename to packages/web/tests/services/regimens.test.ts index 2c7a9bd..2d5c4a4 100644 --- a/packages/web/src/services/__tests__/regimens.test.ts +++ b/packages/web/tests/services/regimens.test.ts @@ -7,7 +7,7 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); @@ -18,7 +18,7 @@ import { createRegimen, updateRegimen, deleteRegimen, -} from '../regimens'; +} from '../../src/services/regimens'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/src/services/shopping-lists.test.ts b/packages/web/tests/services/shopping-lists.test.ts similarity index 94% rename from packages/web/src/services/shopping-lists.test.ts rename to packages/web/tests/services/shopping-lists.test.ts index 7a5dc19..ed61ecb 100644 --- a/packages/web/src/services/shopping-lists.test.ts +++ b/packages/web/tests/services/shopping-lists.test.ts @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { apiClient } from './api-client'; -import * as ShoppingListsService from './shopping-lists'; +import { apiClient } from '../../src/services/api-client'; +import * as ShoppingListsService from '../../src/services/shopping-lists'; -vi.mock('./api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: vi.fn(), post: vi.fn(), diff --git a/packages/web/src/services/__tests__/stores.test.ts b/packages/web/tests/services/stores.test.ts similarity index 95% rename from packages/web/src/services/__tests__/stores.test.ts rename to packages/web/tests/services/stores.test.ts index 2c8315a..5f2f2a3 100644 --- a/packages/web/src/services/__tests__/stores.test.ts +++ b/packages/web/tests/services/stores.test.ts @@ -7,11 +7,11 @@ const { mockGet, mockPost, mockPatch, mockDelete } = vi.hoisted(() => ({ mockDelete: vi.fn(), })); -vi.mock('../api-client', () => ({ +vi.mock('../../src/services/api-client', () => ({ apiClient: { get: mockGet, post: mockPost, patch: mockPatch, delete: mockDelete }, })); -import { listStores, getStore, createStore, updateStore, deactivateStore } from '../stores'; +import { listStores, getStore, createStore, updateStore, deactivateStore } from '../../src/services/stores'; beforeEach(() => vi.clearAllMocks()); diff --git a/packages/web/vitest.config.ts b/packages/web/vitest.config.ts index 0a71352..7835ff2 100644 --- a/packages/web/vitest.config.ts +++ b/packages/web/vitest.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', - include: ['src/**/*.test.{ts,tsx}'], + include: ['tests/**/*.test.{ts,tsx}'], setupFiles: ['./vitest.setup.ts'], css: false, coverage: { @@ -24,7 +24,6 @@ export default defineConfig({ all: true, include: ['src/**/*.{ts,tsx}'], exclude: [ - 'src/**/*.test.{ts,tsx}', 'src/test-utils.tsx', 'src/app/layout.tsx', 'src/app/api/**', diff --git a/turbo.json b/turbo.json index c47fddb..e432624 100644 --- a/turbo.json +++ b/turbo.json @@ -21,6 +21,11 @@ "test": { "dependsOn": ["^build"] }, + "test:watch": { + "dependsOn": ["^build"], + "cache": false, + "persistent": true + }, "test:cov": { "dependsOn": ["^build"], "outputs": ["coverage/**"]