Tests refactor

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

View file

@ -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 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: 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. 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. - 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). - 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 (glossary/ADRs) updated before moving to implementation. - Decisions must be crystallized and documentation updated before moving to implementation.
1. **Vertical Slice Implementation**: Follow this approach for the actual build: 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. **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. 2. **Vertical Slice TDD Order**: Build each vertical slice following this exact test-first sequence:
4. **Route Layer**: Create the Fastify route plugin and register it. Add route tests (using `app.inject`). - **Shared Layer**: Write validation schema tests first in `packages/shared/tests/` -> Implement schemas in `packages/shared/src/`.
5. **Web API Client**: Implement the frontend service in `packages/web/src/services/`. Add unit tests. - **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()`.
6. **Web UI**: Create the Next.js pages and components. Use Server Components by default. Add component tests (React Testing Library). - **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)**: **Verification Gate (Mandatory)**:
Before considering a task complete, run: Before considering a task complete, run:

View file

@ -97,19 +97,20 @@ Separate each group with a blank line.
| React hook | camelCase | `useProducts`, `usePantryItems` | | React hook | camelCase | `useProducts`, `usePantryItems` |
| CSS class (Tailwind) | kebab-case | via Tailwind utilities | | 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. 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`)**: Create Mongoose schema and Repository. Use `.lean().exec()` on all reads. Add integration tests. 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`)**: Implement business logic using Awilix DI. Add unit tests. 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`)**: Create Fastify route plugin. Add route tests. 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`)**: Implement frontend service. Add unit tests. 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`)**: Create Next.js pages/components (Server Components by default). Add component tests. 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. **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 ## Code Organization Rules
### API (NestJS) ### API (NestJS)

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

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

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

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

View file

@ -12,6 +12,7 @@
"lint": "turbo run lint", "lint": "turbo run lint",
"lint-fix": "turbo run lint-fix", "lint-fix": "turbo run lint-fix",
"test": "turbo run test", "test": "turbo run test",
"test:watch": "turbo run test:watch",
"test:cov": "turbo run test:cov", "test:cov": "turbo run test:cov",
"typecheck": "turbo run typecheck", "typecheck": "turbo run typecheck",
"clean": "turbo run clean", "clean": "turbo run clean",

View file

@ -133,7 +133,9 @@ export default fp(
// Make sure other module deps are available for classes instantiated by SuggestionEngine/ShoppingGap // Make sure other module deps are available for classes instantiated by SuggestionEngine/ShoppingGap
recipesRepository: asClass(RecipesRepository, { lifetime: Lifetime.SINGLETON }), recipesRepository: asClass(RecipesRepository, { lifetime: Lifetime.SINGLETON }),
pantryRepository: asClass(PantryRepository, { 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 }), productsRepository: asClass(ProductsRepository, { lifetime: Lifetime.SINGLETON }),
}); });
@ -181,7 +183,7 @@ export default fp(
variety: z.number(), variety: z.number(),
}), }),
reasoning: z.array(z.string()), reasoning: z.array(z.string()),
}) }),
), ),
}, },
}, },
@ -190,7 +192,7 @@ export default fp(
const suggestions = await engine.getSuggestions( const suggestions = await engine.getSuggestions(
request.params.householdId, request.params.householdId,
request.user.keycloakId, request.user.keycloakId,
{ limit: request.query.limit } { limit: request.query.limit },
); );
return reply.send(suggestions); return reply.send(suggestions);
}, },
@ -215,7 +217,7 @@ export default fp(
const service = fastify.diContainer.resolve('mealPlanService'); const service = fastify.diContainer.resolve('mealPlanService');
const plan = await service.getByWeek( const plan = await service.getByWeek(
request.params.householdId, request.params.householdId,
request.params.weekStartDate request.params.weekStartDate,
); );
if (!plan) { if (!plan) {
return reply.status(200).send({ message: 'No meal plan scheduled for this week' }); 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( const plan = await service.create(
request.params.householdId, request.params.householdId,
request.user.keycloakId, request.user.keycloakId,
request.body request.body,
); );
return reply.status(201).send(toPlanResponse(plan as AnyPlanDoc)); return reply.status(201).send(toPlanResponse(plan as AnyPlanDoc));
}, },
@ -273,7 +275,7 @@ export default fp(
const plan = await service.update( const plan = await service.update(
request.params.id, request.params.id,
request.params.householdId, request.params.householdId,
request.body request.body,
); );
return reply.send(toPlanResponse(plan as AnyPlanDoc)); return reply.send(toPlanResponse(plan as AnyPlanDoc));
}, },
@ -295,7 +297,7 @@ export default fp(
const plan = await service.updateStatus( const plan = await service.updateStatus(
request.params.id, request.params.id,
request.params.householdId, request.params.householdId,
request.body.status request.body.status,
); );
return reply.send(toPlanResponse(plan as AnyPlanDoc)); return reply.send(toPlanResponse(plan as AnyPlanDoc));
}, },
@ -334,17 +336,14 @@ export default fp(
pantryQuantity: z.number(), pantryQuantity: z.number(),
missingQuantity: z.number(), missingQuantity: z.number(),
unit: z.string(), unit: z.string(),
}) }),
), ),
}), }),
}, },
}, },
handler: async (request, reply) => { handler: async (request, reply) => {
const service = fastify.diContainer.resolve('shoppingGapService'); const service = fastify.diContainer.resolve('shoppingGapService');
const result = await service.calculateGap( const result = await service.calculateGap(request.params.householdId, request.params.id);
request.params.householdId,
request.params.id
);
return reply.send(result); return reply.send(result);
}, },
}); });
@ -352,5 +351,5 @@ export default fp(
{ {
name: 'meal-plans-routes', name: 'meal-plans-routes',
dependencies: ['auth-plugin'], dependencies: ['auth-plugin'],
} },
); );

View file

@ -8,7 +8,7 @@ import type {
NutritionInfo, NutritionInfo,
MealPlanDaySchema, MealPlanDaySchema,
} from '@meshitrack/shared'; } from '@meshitrack/shared';
import { z } from 'zod/v4'; import { type z } from 'zod/v4';
type MealPlanDay = z.infer<typeof MealPlanDaySchema>; type MealPlanDay = z.infer<typeof MealPlanDaySchema>;
@ -39,19 +39,12 @@ export class MealPlanService {
return this.mealPlanRepository.findByWeek(householdId, weekStartDate); return this.mealPlanRepository.findByWeek(householdId, weekStartDate);
} }
public async create( public async create(householdId: string, createdBy: string, input: CreateMealPlanInput) {
householdId: string,
createdBy: string,
input: CreateMealPlanInput
) {
// Prevent overlapping meal plans for same household/week // Prevent overlapping meal plans for same household/week
const existing = await this.mealPlanRepository.findByWeek( const existing = await this.mealPlanRepository.findByWeek(householdId, input.weekStartDate);
householdId,
input.weekStartDate
);
if (existing) { if (existing) {
throw new BadRequestError( 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( public async update(id: string, householdId: string, input: UpdateMealPlanInput) {
id: string,
householdId: string,
input: UpdateMealPlanInput
) {
const existing = await this.getById(id, householdId); const existing = await this.getById(id, householdId);
const data: Record<string, unknown> = {}; const data: Record<string, unknown> = {};
@ -90,11 +79,7 @@ export class MealPlanService {
return updated; return updated;
} }
public async updateStatus( public async updateStatus(id: string, householdId: string, status: MealPlanStatus) {
id: string,
householdId: string,
status: MealPlanStatus
) {
await this.getById(id, householdId); await this.getById(id, householdId);
const updated = await this.mealPlanRepository.updateStatus(id, householdId, status); const updated = await this.mealPlanRepository.updateStatus(id, householdId, status);
if (!updated) { if (!updated) {

View file

@ -44,10 +44,7 @@ export class ShoppingGapService {
this.productsRepository = productsRepository; this.productsRepository = productsRepository;
} }
public async calculateGap( public async calculateGap(householdId: string, mealPlanId: string): Promise<ShoppingGapResult> {
householdId: string,
mealPlanId: string
): Promise<ShoppingGapResult> {
const plan = await this.mealPlanRepository.findById(mealPlanId, householdId); const plan = await this.mealPlanRepository.findById(mealPlanId, householdId);
if (!plan) { if (!plan) {
throw new NotFoundError('Meal plan not found'); throw new NotFoundError('Meal plan not found');
@ -75,7 +72,7 @@ export class ShoppingGapService {
// 2. Fetch all referenced recipes in parallel // 2. Fetch all referenced recipes in parallel
const recipeIds = Array.from(recipeIdSet); const recipeIds = Array.from(recipeIdSet);
const recipeDocs = await Promise.all( 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<string, any>(); const recipesMap = new Map<string, any>();
for (const doc of recipeDocs) { for (const doc of recipeDocs) {

View file

@ -44,7 +44,7 @@ export class SuggestionEngineService {
public async getSuggestions( public async getSuggestions(
householdId: string, householdId: string,
userId: string, userId: string,
options: { limit?: number } = {} options: { limit?: number } = {},
): Promise<ScoredRecipe[]> { ): Promise<ScoredRecipe[]> {
const limit = options.limit ?? 5; const limit = options.limit ?? 5;
@ -132,7 +132,7 @@ export class SuggestionEngineService {
recipeId, recipeId,
pantryInventory, pantryInventory,
activeTarget as Record<string, unknown> | null, activeTarget as Record<string, unknown> | null,
lastEaten lastEaten,
); );
const totalScore = const totalScore =
@ -153,9 +153,7 @@ export class SuggestionEngineService {
} }
// 5. Sort descending and limit // 5. Sort descending and limit
return scoredList return scoredList.sort((a, b) => b.totalScore - a.totalScore).slice(0, limit);
.sort((a, b) => b.totalScore - a.totalScore)
.slice(0, limit);
} }
private scoreRecipe( private scoreRecipe(
@ -164,7 +162,7 @@ export class SuggestionEngineService {
recipeId: string, recipeId: string,
inventory: Map<string, { qty: number; minDays: number; maxUrgencyWeight: number }>, inventory: Map<string, { qty: number; minDays: number; maxUrgencyWeight: number }>,
target: Record<string, unknown> | null, target: Record<string, unknown> | null,
lastEaten: Map<string, number> lastEaten: Map<string, number>,
) { ) {
// Filter non-optional ingredients // Filter non-optional ingredients
const requiredIngs = ingredients.filter((ing) => !ing.isOptional); const requiredIngs = ingredients.filter((ing) => !ing.isOptional);
@ -262,7 +260,7 @@ export class SuggestionEngineService {
private generateReasoning( private generateReasoning(
scores: { coverage: number; urgency: number; nutrition: number; variety: number }, scores: { coverage: number; urgency: number; nutrition: number; variety: number },
recipeName: string recipeName: string,
): string[] { ): string[] {
const reasons: string[] = []; const reasons: string[] = [];

View file

@ -2,16 +2,11 @@ import { NutritionTargetModel } from '../../schemas/nutrition-target.schema.js';
export class NutritionTargetRepository { export class NutritionTargetRepository {
public async findByUser(userId: string, householdId: string) { public async findByUser(userId: string, householdId: string) {
return NutritionTargetModel.findOne({ userId, householdId, isActive: true }) return NutritionTargetModel.findOne({ userId, householdId, isActive: true }).lean().exec();
.lean()
.exec();
} }
public async findAllByUser(userId: string, householdId: string) { public async findAllByUser(userId: string, householdId: string) {
return NutritionTargetModel.find({ userId, householdId }) return NutritionTargetModel.find({ userId, householdId }).sort({ createdAt: -1 }).lean().exec();
.sort({ createdAt: -1 })
.lean()
.exec();
} }
public async create(data: Record<string, unknown>) { public async create(data: Record<string, unknown>) {
@ -23,15 +18,20 @@ export class NutritionTargetRepository {
public async deactivateAllForUser(userId: string, householdId: string) { public async deactivateAllForUser(userId: string, householdId: string) {
return NutritionTargetModel.updateMany( return NutritionTargetModel.updateMany(
{ userId, householdId, isActive: true }, { userId, householdId, isActive: true },
{ $set: { isActive: false } } { $set: { isActive: false } },
).exec(); ).exec();
} }
public async update(id: string, userId: string, householdId: string, data: Record<string, unknown>) { public async update(
id: string,
userId: string,
householdId: string,
data: Record<string, unknown>,
) {
return NutritionTargetModel.findOneAndUpdate( return NutritionTargetModel.findOneAndUpdate(
{ _id: id, userId, householdId }, { _id: id, userId, householdId },
{ $set: data }, { $set: data },
{ new: true, lean: true } { new: true, lean: true },
).exec(); ).exec();
} }
} }

View file

@ -2,10 +2,7 @@ import fp from 'fastify-plugin';
import { asClass, Lifetime } from 'awilix'; import { asClass, Lifetime } from 'awilix';
import type { ZodTypeProvider } from 'fastify-type-provider-zod'; import type { ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod/v4'; import { z } from 'zod/v4';
import { import { NutritionTargetSchema, NutritionTargetResponseSchema } from '@meshitrack/shared';
NutritionTargetSchema,
NutritionTargetResponseSchema,
} from '@meshitrack/shared';
import { NutritionTargetRepository } from './nutrition-target.repository.js'; import { NutritionTargetRepository } from './nutrition-target.repository.js';
import { NutritionTargetService } from './nutrition-target.service.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(); return typeof v === 'string' ? v : v.toISOString();
} }
function toNutritionTargetResponse(doc: AnyTargetDoc): z.infer<typeof NutritionTargetResponseSchema> { function toNutritionTargetResponse(
doc: AnyTargetDoc,
): z.infer<typeof NutritionTargetResponseSchema> {
return { return {
_id: toStr(doc._id), _id: toStr(doc._id),
userId: doc.userId, userId: doc.userId,
@ -61,7 +60,9 @@ declare module '@fastify/awilix' {
export default fp( export default fp(
async (fastify) => { async (fastify) => {
fastify.diContainer.register({ fastify.diContainer.register({
nutritionTargetRepository: asClass(NutritionTargetRepository, { lifetime: Lifetime.SINGLETON }), nutritionTargetRepository: asClass(NutritionTargetRepository, {
lifetime: Lifetime.SINGLETON,
}),
nutritionTargetService: asClass(NutritionTargetService, { lifetime: Lifetime.SINGLETON }), nutritionTargetService: asClass(NutritionTargetService, { lifetime: Lifetime.SINGLETON }),
}); });
@ -124,11 +125,7 @@ export default fp(
handler: async (request, reply) => { handler: async (request, reply) => {
const service = fastify.diContainer.resolve('nutritionTargetService'); const service = fastify.diContainer.resolve('nutritionTargetService');
const userId = request.user.keycloakId; const userId = request.user.keycloakId;
const target = await service.setTarget( const target = await service.setTarget(userId, request.params.householdId, request.body);
userId,
request.params.householdId,
request.body
);
return reply.status(201).send(toNutritionTargetResponse(target as AnyTargetDoc)); return reply.status(201).send(toNutritionTargetResponse(target as AnyTargetDoc));
}, },
}); });
@ -155,5 +152,5 @@ export default fp(
{ {
name: 'nutrition-targets-routes', name: 'nutrition-targets-routes',
dependencies: ['auth-plugin'], dependencies: ['auth-plugin'],
} },
); );

View file

@ -22,11 +22,7 @@ export class NutritionTargetService {
return this.nutritionTargetRepository.findAllByUser(userId, householdId); return this.nutritionTargetRepository.findAllByUser(userId, householdId);
} }
public async setTarget( public async setTarget(userId: string, householdId: string, input: SetNutritionTargetInput) {
userId: string,
householdId: string,
input: SetNutritionTargetInput
) {
// Maintain invariant: only one target is active per user per household // Maintain invariant: only one target is active per user per household
if (input.isActive !== false) { if (input.isActive !== false) {
await this.nutritionTargetRepository.deactivateAllForUser(userId, householdId); await this.nutritionTargetRepository.deactivateAllForUser(userId, householdId);
@ -52,20 +48,20 @@ export class NutritionTargetService {
switch (preset) { switch (preset) {
case 'loss': case 'loss':
proteinPct = 0.40; proteinPct = 0.4;
carbsPct = 0.30; carbsPct = 0.3;
fatPct = 0.30; fatPct = 0.3;
break; break;
case 'gain': case 'gain':
proteinPct = 0.25; proteinPct = 0.25;
carbsPct = 0.50; carbsPct = 0.5;
fatPct = 0.25; fatPct = 0.25;
break; break;
case 'maintenance': case 'maintenance':
default: default:
proteinPct = 0.30; proteinPct = 0.3;
carbsPct = 0.40; carbsPct = 0.4;
fatPct = 0.30; fatPct = 0.3;
break; break;
} }

View file

@ -27,13 +27,13 @@ export class PricesRepository {
public async createMany(data: CreatePriceRecordData[]) { public async createMany(data: CreatePriceRecordData[]) {
const records = await PriceRecordModel.insertMany(data); const records = await PriceRecordModel.insertMany(data);
return records.map(r => r.toObject()); return records.map((r) => r.toObject());
} }
public async findByProduct( public async findByProduct(
householdId: string, householdId: string,
productId: string, productId: string,
query: PriceHistoryQueryInput query: PriceHistoryQueryInput,
) { ) {
const filter: Record<string, unknown> = { householdId, productId }; const filter: Record<string, unknown> = { householdId, productId };

View file

@ -95,7 +95,7 @@ export default fp(
const record = await service.recordPrice( const record = await service.recordPrice(
request.body, request.body,
request.params.householdId, request.params.householdId,
request.user.keycloakId request.user.keycloakId,
); );
return reply.status(201).send(toPriceRecordResponse(record)); return reply.status(201).send(toPriceRecordResponse(record));
}, },
@ -114,7 +114,7 @@ export default fp(
const records = await service.recordBulkPrices( const records = await service.recordBulkPrices(
request.body, request.body,
request.params.householdId, request.params.householdId,
request.user.keycloakId request.user.keycloakId,
); );
return reply.status(201).send(records.map(toPriceRecordResponse)); return reply.status(201).send(records.map(toPriceRecordResponse));
}, },
@ -133,7 +133,7 @@ export default fp(
const result = await service.getPriceHistory( const result = await service.getPriceHistory(
request.params.productId, request.params.productId,
request.params.householdId, request.params.householdId,
request.query request.query,
); );
return reply.send({ return reply.send({
data: result.data.map(toPriceRecordResponse), data: result.data.map(toPriceRecordResponse),
@ -153,7 +153,7 @@ export default fp(
const service = fastify.diContainer.resolve('pricesService'); const service = fastify.diContainer.resolve('pricesService');
const results = await service.compareStores( const results = await service.compareStores(
request.params.productId, request.params.productId,
request.params.householdId request.params.householdId,
); );
return reply.send({ return reply.send({
data: results.map((r) => ({ data: results.map((r) => ({
@ -184,5 +184,5 @@ export default fp(
{ {
name: 'prices-routes', name: 'prices-routes',
dependencies: ['auth-plugin'], dependencies: ['auth-plugin'],
} },
); );

View file

@ -28,11 +28,7 @@ export class PricesService {
/** /**
* Validates entity existence, computes pricePerUnit, and persists record * Validates entity existence, computes pricePerUnit, and persists record
*/ */
public async recordPrice( public async recordPrice(data: CreatePriceRecordInput, householdId: string, userId: string) {
data: CreatePriceRecordInput,
householdId: string,
userId: string
) {
const [product, store] = await Promise.all([ const [product, store] = await Promise.all([
this.productsRepository.findById(data.productId, householdId), this.productsRepository.findById(data.productId, householdId),
this.storesRepository.findById(data.storeId, householdId), this.storesRepository.findById(data.storeId, householdId),
@ -64,11 +60,7 @@ export class PricesService {
/** /**
* Ingests a list of purchased products in a single transaction * Ingests a list of purchased products in a single transaction
*/ */
public async recordBulkPrices( public async recordBulkPrices(data: BulkPriceRecordInput, householdId: string, userId: string) {
data: BulkPriceRecordInput,
householdId: string,
userId: string
) {
const store = await this.storesRepository.findById(data.storeId, householdId); const store = await this.storesRepository.findById(data.storeId, householdId);
if (!store) throw new NotFoundError(`Store not found: ${data.storeId}`); if (!store) throw new NotFoundError(`Store not found: ${data.storeId}`);
@ -109,7 +101,7 @@ export class PricesService {
public async getPriceHistory( public async getPriceHistory(
productId: string, productId: string,
householdId: string, householdId: string,
query: PriceHistoryQueryInput query: PriceHistoryQueryInput,
) { ) {
return this.pricesRepository.findByProduct(householdId, productId, query); return this.pricesRepository.findByProduct(householdId, productId, query);
} }
@ -129,13 +121,16 @@ export class PricesService {
public async estimatePrice( public async estimatePrice(
productId: string, productId: string,
householdId: string, householdId: string,
storeId?: string storeId?: string,
): Promise<number | null> { ): Promise<number | null> {
const latest = await this.pricesRepository.getLatestForProduct(householdId, productId, storeId); const latest = await this.pricesRepository.getLatestForProduct(householdId, productId, storeId);
if (!latest) { if (!latest) {
// If a specific store was requested but has no history, fall back to the generic latest across all stores // If a specific store was requested but has no history, fall back to the generic latest across all stores
if (storeId) { if (storeId) {
const genericLatest = await this.pricesRepository.getLatestForProduct(householdId, productId); const genericLatest = await this.pricesRepository.getLatestForProduct(
householdId,
productId,
);
return genericLatest ? genericLatest.price : null; return genericLatest ? genericLatest.price : null;
} }
return null; return null;

View file

@ -34,7 +34,7 @@ export class ShoppingListsRepository {
.sort({ createdAt: -1 }) .sort({ createdAt: -1 })
.lean() .lean()
.exec(); .exec();
return lists.map(l => this.sortItems(l)); return lists.map((l) => this.sortItems(l));
} }
public async findById(id: string, householdId: string) { public async findById(id: string, householdId: string) {
@ -43,19 +43,24 @@ export class ShoppingListsRepository {
} }
public async findActiveByHousehold(householdId: string) { 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 }) .sort({ updatedAt: -1 })
.lean() .lean()
.exec(); .exec();
return lists.map(l => this.sortItems(l)); return lists.map((l) => this.sortItems(l));
} }
public async update(id: string, householdId: string, data: UpdateShoppingListInput) { public async update(id: string, householdId: string, data: UpdateShoppingListInput) {
const updated = await ShoppingListModel.findOneAndUpdate( const updated = await ShoppingListModel.findOneAndUpdate(
{ _id: id, householdId }, { _id: id, householdId },
{ $set: data }, { $set: data },
{ new: true } { new: true },
).lean().exec(); )
.lean()
.exec();
return this.sortItems(updated); return this.sortItems(updated);
} }
@ -69,8 +74,10 @@ export class ShoppingListsRepository {
const updated = await ShoppingListModel.findOneAndUpdate( const updated = await ShoppingListModel.findOneAndUpdate(
{ _id: id, householdId }, { _id: id, householdId },
{ $push: { items: item } }, { $push: { items: item } },
{ new: true } { new: true },
).lean().exec(); )
.lean()
.exec();
return this.sortItems(updated); return this.sortItems(updated);
} }
@ -78,7 +85,7 @@ export class ShoppingListsRepository {
id: string, id: string,
householdId: string, householdId: string,
itemId: string, itemId: string,
updates: Partial<ShoppingItem> updates: Partial<ShoppingItem>,
) { ) {
const setUpdates: Record<string, unknown> = {}; const setUpdates: Record<string, unknown> = {};
for (const [key, val] of Object.entries(updates)) { for (const [key, val] of Object.entries(updates)) {
@ -88,8 +95,10 @@ export class ShoppingListsRepository {
const updated = await ShoppingListModel.findOneAndUpdate( const updated = await ShoppingListModel.findOneAndUpdate(
{ _id: id, householdId, 'items.id': itemId }, { _id: id, householdId, 'items.id': itemId },
{ $set: setUpdates }, { $set: setUpdates },
{ new: true } { new: true },
).lean().exec(); )
.lean()
.exec();
return this.sortItems(updated); return this.sortItems(updated);
} }
@ -97,8 +106,10 @@ export class ShoppingListsRepository {
const updated = await ShoppingListModel.findOneAndUpdate( const updated = await ShoppingListModel.findOneAndUpdate(
{ _id: id, householdId }, { _id: id, householdId },
{ $pull: { items: { id: itemId } } }, { $pull: { items: { id: itemId } } },
{ new: true } { new: true },
).lean().exec(); )
.lean()
.exec();
return this.sortItems(updated); return this.sortItems(updated);
} }
} }

View file

@ -114,7 +114,7 @@ export default fp(
const list = await service.create( const list = await service.create(
request.body, request.body,
request.params.householdId, request.params.householdId,
request.user.keycloakId request.user.keycloakId,
); );
return reply.status(201).send(serializeList(list)); return reply.status(201).send(serializeList(list));
}, },
@ -147,7 +147,7 @@ export default fp(
const list = await service.update( const list = await service.update(
request.params.id, request.params.id,
request.params.householdId, request.params.householdId,
request.body request.body,
); );
return reply.send(serializeList(list)); return reply.send(serializeList(list));
}, },
@ -181,7 +181,7 @@ export default fp(
const { list, addedItem } = await service.addItem( const { list, addedItem } = await service.addItem(
request.params.id, request.params.id,
request.params.householdId, request.params.householdId,
request.body request.body,
); );
// Emit real-time update notification to existing connected viewers // Emit real-time update notification to existing connected viewers
@ -209,11 +209,13 @@ export default fp(
request.params.householdId, request.params.householdId,
request.params.itemId, request.params.itemId,
request.body, request.body,
request.user.keycloakId request.user.keycloakId,
); );
// Broadcast the precise item differential state update to sibling websocket listeners // 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) { if (matchedItem) {
broadcastToList(request.params.id, null as any, { broadcastToList(request.params.id, null as any, {
type: 'ITEM_UPDATED', type: 'ITEM_UPDATED',
@ -242,7 +244,7 @@ export default fp(
const list = await service.removeItem( const list = await service.removeItem(
request.params.id, request.params.id,
request.params.householdId, request.params.householdId,
request.params.itemId request.params.itemId,
); );
broadcastToList(request.params.id, null as any, { broadcastToList(request.params.id, null as any, {
@ -268,7 +270,7 @@ export default fp(
const list = await service.createFromMealPlan( const list = await service.createFromMealPlan(
request.params.mealPlanId, request.params.mealPlanId,
request.params.householdId, request.params.householdId,
request.user.keycloakId request.user.keycloakId,
); );
return reply.status(201).send(serializeList(list)); return reply.status(201).send(serializeList(list));
}, },
@ -286,7 +288,7 @@ export default fp(
const results = await service.syncCheckedToPantry( const results = await service.syncCheckedToPantry(
request.params.id, request.params.id,
request.params.householdId, request.params.householdId,
request.user.keycloakId request.user.keycloakId,
); );
return reply.send(results); return reply.send(results);
}, },
@ -303,7 +305,7 @@ export default fp(
const service = fastify.diContainer.resolve('shoppingListsService'); const service = fastify.diContainer.resolve('shoppingListsService');
const comparison = await service.getStoreComparison( const comparison = await service.getStoreComparison(
request.params.id, request.params.id,
request.params.householdId request.params.householdId,
); );
return reply.send(comparison); return reply.send(comparison);
}, },
@ -339,10 +341,12 @@ export default fp(
request.params.householdId, request.params.householdId,
payload.itemId, payload.itemId,
{ checked: payload.checked }, { 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 // Echo back differential confirmation to everyone else on the floor
broadcastToList(listId, socket, { broadcastToList(listId, socket, {
@ -352,7 +356,7 @@ export default fp(
checked: payload.checked, checked: payload.checked,
checkedAt: matched?.checkedAt?.toISOString(), checkedAt: matched?.checkedAt?.toISOString(),
checkedBy: matched?.checkedBy, checkedBy: matched?.checkedBy,
} },
}); });
} }
} catch (err) { } catch (err) {
@ -370,12 +374,12 @@ export default fp(
} }
request.log.info({ listId }, 'Client severed sync handshake connection'); request.log.info({ listId }, 'Client severed sync handshake connection');
}); });
} },
); );
/* v8 ignore stop */ /* v8 ignore stop */
}, },
{ {
name: 'shopping-lists-routes', name: 'shopping-lists-routes',
dependencies: ['auth-plugin'], dependencies: ['auth-plugin'],
} },
); );

View file

@ -13,7 +13,7 @@ import type {
ShoppingItem, ShoppingItem,
} from '@meshitrack/shared'; } from '@meshitrack/shared';
import { ShoppingListSourceType } 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 { NotFoundError } from '../../common/errors.js';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
@ -158,7 +158,7 @@ export class ShoppingListsService {
householdId: string, householdId: string,
itemId: string, itemId: string,
data: UpdateShoppingItemInput, data: UpdateShoppingItemInput,
userId: string userId: string,
) { ) {
const updates: Partial<ShoppingItem> = { ...data }; const updates: Partial<ShoppingItem> = { ...data };
@ -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 name = `Groceries for Week of ${dateStr}`;
const newList = await this.shoppingListsRepository.create({ const newList = await this.shoppingListsRepository.create({
@ -242,7 +245,9 @@ export class ShoppingListsService {
let addedCount = 0; let addedCount = 0;
let pricesLogged = 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) { for (const item of pendingItems) {
// 1. Promote item to active pantry // 1. Promote item to active pantry
@ -257,7 +262,7 @@ export class ShoppingListsService {
notes: item.notes || undefined, notes: item.notes || undefined,
}, },
householdId, householdId,
userId userId,
); );
addedCount++; addedCount++;
@ -275,7 +280,7 @@ export class ShoppingListsService {
currency: 'USD', currency: 'USD',
}, },
householdId, householdId,
userId userId,
); );
pricesLogged++; pricesLogged++;
} }
@ -347,7 +352,9 @@ export class ShoppingListsService {
} }
// Sort to surface the cheapest/fullest single store options first // 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 }; return { singleStoreOptions };
} }

View file

@ -17,7 +17,7 @@ const priceRecordSchema = new mongoose.Schema(
notes: { type: String }, notes: { type: String },
createdBy: { type: String, required: true }, createdBy: { type: String, required: true },
}, },
{ timestamps: { createdAt: true, updatedAt: false } } { timestamps: { createdAt: true, updatedAt: false } },
); );
// Performance Indexes for Lookup Speed and Aggregations // Performance Indexes for Lookup Speed and Aggregations

View file

@ -17,7 +17,7 @@ const shoppingItemSchema = new mongoose.Schema(
category: { type: String }, category: { type: String },
addedToPantry: { type: Boolean, required: true, default: false }, addedToPantry: { type: Boolean, required: true, default: false },
}, },
{ _id: false } { _id: false },
); );
const shoppingListSourceSchema = new mongoose.Schema( const shoppingListSourceSchema = new mongoose.Schema(
@ -25,7 +25,7 @@ const shoppingListSourceSchema = new mongoose.Schema(
type: { type: String, required: true }, // values from ShoppingListSourceType type: { type: String, required: true }, // values from ShoppingListSourceType
referenceId: { type: String }, referenceId: { type: String },
}, },
{ _id: false } { _id: false },
); );
const shoppingListSchema = new mongoose.Schema( const shoppingListSchema = new mongoose.Schema(
@ -44,7 +44,7 @@ const shoppingListSchema = new mongoose.Schema(
completedAt: { type: Date }, completedAt: { type: Date },
createdBy: { type: String, required: true }, createdBy: { type: String, required: true },
}, },
{ timestamps: true } { timestamps: true },
); );
shoppingListSchema.index({ householdId: 1, status: 1 }); shoppingListSchema.index({ householdId: 1, status: 1 });

View file

@ -6,7 +6,7 @@ import {
ForbiddenError, ForbiddenError,
ConflictError, ConflictError,
BadRequestError, BadRequestError,
} from './errors.js'; } from '../../src/common/errors.js';
describe(AppError.name, () => { describe(AppError.name, () => {
it('sets statusCode, error, message, and details', () => { it('sets statusCode, error, message, and details', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import config from './configuration.js'; import config from '../../src/config/configuration.js';
describe('configuration', () => { describe('configuration', () => {
it('exports default config values', () => { it('exports default config values', () => {

View file

@ -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<T>(
repoClass: new (...args: any[]) => T
): Record<keyof T, any> {
const mock: Record<string, any> = {};
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<keyof T, any>;
}

View file

@ -63,9 +63,9 @@ vi.mock('jose', () => ({
jwtVerify: vi.fn(), jwtVerify: vi.fn(),
})); }));
import { buildApp } from './main.js'; import { buildApp } from '../src/main.js';
import * as jose from 'jose'; import * as jose from 'jose';
import { NotFoundError } from './common/errors.js'; import { NotFoundError } from '../src/common/errors.js';
describe('buildApp', () => { describe('buildApp', () => {
beforeEach(() => { beforeEach(() => {

View file

@ -7,7 +7,7 @@ const { mockFind, mockSave, mockInsertMany, mockAggregate } = vi.hoisted(() => (
mockAggregate: vi.fn(), mockAggregate: vi.fn(),
})); }));
vi.mock('../../schemas/cabinet-event.schema.js', () => { vi.mock('../../../src/schemas/cabinet-event.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -36,7 +36,7 @@ vi.mock('../../schemas/cabinet-event.schema.js', () => {
return { CabinetEventModel: FakeModel }; return { CabinetEventModel: FakeModel };
}); });
import { CabinetEventsRepository } from './cabinet-events.repository.js'; import { CabinetEventsRepository } from '../../../src/modules/cabinet-events/cabinet-events.repository.js';
describe(CabinetEventsRepository.name, () => { describe(CabinetEventsRepository.name, () => {
let repo: CabinetEventsRepository; let repo: CabinetEventsRepository;

View file

@ -24,7 +24,7 @@ const { mockListEvents, mockGetEventsByItem, mockGetSpendingSummary } = vi.hoist
mockGetSpendingSummary: vi.fn(), mockGetSpendingSummary: vi.fn(),
})); }));
vi.mock('./cabinet-events.repository.js', () => ({ vi.mock('../../../src/modules/cabinet-events/cabinet-events.repository.js', () => ({
CabinetEventsRepository: class { CabinetEventsRepository: class {
create = vi.fn(); create = vi.fn();
createMany = 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 { CabinetEventsService: class {
logEvent = vi.fn(); logEvent = vi.fn();
logEvents = 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import cabinetEventsRoutes from './cabinet-events.routes.js'; import cabinetEventsRoutes from '../../../src/modules/cabinet-events/cabinet-events.routes.js';
function makeFakeEvent(overrides = {}) { function makeFakeEvent(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, () => { describe(CabinetEventsService.name, () => {
const mockCabinetEventsRepo = { const mockCabinetEventsRepo = {

View file

@ -10,7 +10,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave, mockCountDocument
mockAggregate: vi.fn(), mockAggregate: vi.fn(),
})); }));
vi.mock('../../schemas/cabinet-item.schema.js', () => { vi.mock('../../../src/schemas/cabinet-item.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -54,7 +54,7 @@ vi.mock('../../schemas/cabinet-item.schema.js', () => {
return { CabinetItemModel: FakeModel }; return { CabinetItemModel: FakeModel };
}); });
import { CabinetRepository } from './cabinet.repository.js'; import { CabinetRepository } from '../../../src/modules/cabinet/cabinet.repository.js';
describe(CabinetRepository.name, () => { describe(CabinetRepository.name, () => {
let repo: CabinetRepository; let repo: CabinetRepository;

View file

@ -45,7 +45,7 @@ const {
mockFindActiveByMedicineForFEFO: vi.fn(), mockFindActiveByMedicineForFEFO: vi.fn(),
})); }));
vi.mock('./cabinet.repository.js', () => ({ vi.mock('../../../src/modules/cabinet/cabinet.repository.js', () => ({
CabinetRepository: class { CabinetRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; findById = mockFindById;
@ -65,7 +65,7 @@ const { mockMedicineFindById } = vi.hoisted(() => ({
mockMedicineFindById: vi.fn(), mockMedicineFindById: vi.fn(),
})); }));
vi.mock('../medicines/medicines.repository.js', () => ({ vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({
MedicinesRepository: class { MedicinesRepository: class {
findById = mockMedicineFindById; findById = mockMedicineFindById;
findByHousehold = vi.fn(); findByHousehold = vi.fn();
@ -80,7 +80,7 @@ const { mockProductFindById } = vi.hoisted(() => ({
mockProductFindById: vi.fn(), mockProductFindById: vi.fn(),
})); }));
vi.mock('../medicine-products/medicine-products.repository.js', () => ({ vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({
MedicineProductsRepository: class { MedicineProductsRepository: class {
findById = mockProductFindById; findById = mockProductFindById;
findByMedicine = vi.fn(); 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 { MedicineProductsService: class {
listByMedicine = vi.fn(); listByMedicine = vi.fn();
getById = 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 { MedicinesService: class {
list = vi.fn(); list = vi.fn();
getById = 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 { CabinetEventsRepository: class {
create = vi.fn(); create = vi.fn();
createMany = 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 { CabinetEventsService: class {
logEvent = vi.fn(); logEvent = vi.fn();
logEvents = 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import medicinesRoutes from '../medicines/medicines.routes.js'; import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js';
import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js'; import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js';
import cabinetEventsRoutes from '../cabinet-events/cabinet-events.routes.js'; import cabinetEventsRoutes from '../../../src/modules/cabinet-events/cabinet-events.routes.js';
import cabinetRoutes from './cabinet.routes.js'; import cabinetRoutes from '../../../src/modules/cabinet/cabinet.routes.js';
function makeFakeCabinetItem(overrides = {}) { function makeFakeCabinetItem(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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'; import { CabinetEventType, CabinetEventSourceType } from '@meshitrack/shared';
describe(CabinetService.name, () => { describe(CabinetService.name, () => {

View file

@ -16,7 +16,7 @@ const {
mockFindById: vi.fn(), mockFindById: vi.fn(),
})); }));
vi.mock('../../schemas/freshness-rule.schema.js', () => { vi.mock('../../../src/schemas/freshness-rule.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -62,7 +62,7 @@ vi.mock('../../schemas/freshness-rule.schema.js', () => {
return { FreshnessRuleModel: FakeModel }; return { FreshnessRuleModel: FakeModel };
}); });
import { FreshnessRulesRepository } from './freshness-rules.repository.js'; import { FreshnessRulesRepository } from '../../../src/modules/freshness-rules/freshness-rules.repository.js';
describe(FreshnessRulesRepository.name, () => { describe(FreshnessRulesRepository.name, () => {
let repo: FreshnessRulesRepository; let repo: FreshnessRulesRepository;

View file

@ -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 { FreshnessRulesRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import freshnessRulesRoutes from './freshness-rules.routes.js'; import freshnessRulesRoutes from '../../../src/modules/freshness-rules/freshness-rules.routes.js';
function makeRule(overrides: Record<string, unknown> = {}) { function makeRule(overrides: Record<string, unknown> = {}) {
return { return {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { FreshnessRulesService } from './freshness-rules.service.js'; import { FreshnessRulesService } from '../../../src/modules/freshness-rules/freshness-rules.service.js';
import { NotFoundError, BadRequestError } from '../../common/errors.js'; import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
import { FreshnessRuleSource } from '@meshitrack/shared'; import { FreshnessRuleSource } from '@meshitrack/shared';
const mockRepo = { const mockRepo = {

View file

@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import Fastify from 'fastify'; import Fastify from 'fastify';
import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod'; 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', () => { describe('Health Routes', () => {
async function buildTestApp() { async function buildTestApp() {

View file

@ -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 { class MockHouseholdModel {
_data: Record<string, unknown>; _data: Record<string, unknown>;
constructor(data: Record<string, unknown>) { constructor(data: Record<string, unknown>) {
@ -36,7 +36,7 @@ vi.mock('../../schemas/household.schema.js', () => {
return { HouseholdModel: MockHouseholdModel }; return { HouseholdModel: MockHouseholdModel };
}); });
import { HouseholdsRepository } from './households.repository.js'; import { HouseholdsRepository } from '../../../src/modules/households/households.repository.js';
describe('HouseholdsRepository', () => { describe('HouseholdsRepository', () => {
let repo: HouseholdsRepository; let repo: HouseholdsRepository;

View file

@ -41,7 +41,7 @@ const {
mockUserUpdate: vi.fn(), mockUserUpdate: vi.fn(),
})); }));
vi.mock('./households.repository.js', () => ({ vi.mock('../../../src/modules/households/households.repository.js', () => ({
HouseholdsRepository: class { HouseholdsRepository: class {
create = mockCreate; create = mockCreate;
findById = mockFindById; 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 { UsersRepository: class {
findByKeycloakId = mockFindByKeycloakId; findByKeycloakId = mockFindByKeycloakId;
update = mockUserUpdate; update = mockUserUpdate;
@ -75,10 +75,10 @@ vi.mock('mongoose', () => ({
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import householdsRoutes from './households.routes.js'; import householdsRoutes from '../../../src/modules/households/households.routes.js';
function makeFakeHousehold(overrides = {}) { function makeFakeHousehold(overrides = {}) {
return { return {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { HouseholdsService } from './households.service.js'; import { HouseholdsService } from '../../../src/modules/households/households.service.js';
import { NotFoundError, ForbiddenError, ConflictError } from '../../common/errors.js'; import { NotFoundError, ForbiddenError, ConflictError } from '../../../src/common/errors.js';
import { HouseholdRole } from '@meshitrack/shared'; import { HouseholdRole } from '@meshitrack/shared';
// Mock uuid to return deterministic values // Mock uuid to return deterministic values

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi } from 'vitest'; import { describe, it, expect, vi } from 'vitest';
import { NoOpLlmProvider } from './no-op-llm.provider.js'; import { NoOpLlmProvider } from '../../../src/modules/llm/no-op-llm.provider.js';
import { LLM_PROVIDER } from './llm-provider.interface.js'; import { LLM_PROVIDER } from '../../../src/modules/llm/llm-provider.interface.js';
describe(NoOpLlmProvider.name, () => { describe(NoOpLlmProvider.name, () => {
const provider = new NoOpLlmProvider(); const provider = new NoOpLlmProvider();

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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'; import { MealPlanStatus } from '@meshitrack/shared';
const { mockSave, MockMealPlanModel } = vi.hoisted(() => { const { mockSave, MockMealPlanModel } = vi.hoisted(() => {
@ -17,11 +17,11 @@ const { mockSave, MockMealPlanModel } = vi.hoisted(() => {
return { mockSave, MockMealPlanModel: MockModel }; return { mockSave, MockMealPlanModel: MockModel };
}); });
vi.mock('../../schemas/meal-plan.schema.js', () => ({ vi.mock('../../../src/schemas/meal-plan.schema.js', () => ({
MealPlanModel: MockMealPlanModel, 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) { function makeChain(result: unknown = null) {
return { return {

View file

@ -37,7 +37,7 @@ const {
mockDelete: vi.fn(), mockDelete: vi.fn(),
})); }));
vi.mock('./meal-plans.repository.js', () => ({ vi.mock('../../../src/modules/meal-plans/meal-plans.repository.js', () => ({
MealPlanRepository: class { MealPlanRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; findById = mockFindById;
@ -50,42 +50,42 @@ vi.mock('./meal-plans.repository.js', () => ({
})); }));
// Mock prerequisite repositories to allow SuggestionEngine/Gap to resolve // 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 { RecipesRepository: class {
findByHousehold = vi.fn().mockResolvedValue({ data: [] }); findByHousehold = vi.fn().mockResolvedValue({ data: [] });
findById = vi.fn(); findById = vi.fn();
}, },
})); }));
vi.mock('../pantry/pantry.repository.js', () => ({ vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({
PantryRepository: class { PantryRepository: class {
findActiveByHousehold = vi.fn().mockResolvedValue([]); findActiveByHousehold = vi.fn().mockResolvedValue([]);
}, },
})); }));
vi.mock('../nutrition-targets/nutrition-target.repository.js', () => ({ vi.mock('../../../src/modules/nutrition-targets/nutrition-target.repository.js', () => ({
NutritionTargetRepository: class { NutritionTargetRepository: class {
findByUser = vi.fn().mockResolvedValue(null); findByUser = vi.fn().mockResolvedValue(null);
}, },
})); }));
vi.mock('../products/products.repository.js', () => ({ vi.mock('../../../src/modules/products/products.repository.js', () => ({
ProductsRepository: class { ProductsRepository: class {
findByIds = vi.fn().mockResolvedValue([]); findByIds = vi.fn().mockResolvedValue([]);
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import mealPlanRoutes from './meal-plans.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 }; const emptyNutrition = { calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0, sugar: 0, sodium: 0, saturatedFat: 0, cholesterol: 0 };

View file

@ -1,8 +1,8 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MealPlanService } from './meal-plans.service.js'; import { MealPlanService } from '../../../src/modules/meal-plans/meal-plans.service.js';
import type { MealPlanRepository } from './meal-plans.repository.js'; import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
import { MealPlanStatus, MealType } from '@meshitrack/shared'; import { MealPlanStatus, MealType } from '@meshitrack/shared';
import { BadRequestError, NotFoundError } from '../../common/errors.js'; import { BadRequestError, NotFoundError } from '../../../src/common/errors.js';
describe(MealPlanService.name, () => { describe(MealPlanService.name, () => {
let service: MealPlanService; let service: MealPlanService;

View file

@ -1,10 +1,10 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ShoppingGapService } from './shopping-gap.service.js'; import { ShoppingGapService } from '../../../src/modules/meal-plans/shopping-gap.service.js';
import type { MealPlanRepository } from './meal-plans.repository.js'; import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
import type { RecipesRepository } from '../recipes/recipes.repository.js'; import type { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js';
import type { PantryRepository } from '../pantry/pantry.repository.js'; import type { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js';
import type { ProductsRepository } from '../products/products.repository.js'; import type { ProductsRepository } from '../../../src/modules/products/products.repository.js';
import { NotFoundError } from '../../common/errors.js'; import { NotFoundError } from '../../../src/common/errors.js';
describe(ShoppingGapService.name, () => { describe(ShoppingGapService.name, () => {
let service: ShoppingGapService; let service: ShoppingGapService;

View file

@ -1,9 +1,9 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { SuggestionEngineService } from './suggestion-engine.service.js'; import { SuggestionEngineService } from '../../../src/modules/meal-plans/suggestion-engine.service.js';
import type { RecipesRepository } from '../recipes/recipes.repository.js'; import type { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js';
import type { PantryRepository } from '../pantry/pantry.repository.js'; import type { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js';
import type { MealPlanRepository } from './meal-plans.repository.js'; import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
import type { NutritionTargetRepository } from '../nutrition-targets/nutrition-target.repository.js'; import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';
describe(SuggestionEngineService.name, () => { describe(SuggestionEngineService.name, () => {
let service: SuggestionEngineService; let service: SuggestionEngineService;

View file

@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockAggregate, mockSave } = vi.hoisted(() => ({
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/medicine-price.schema.js', () => { vi.mock('../../../src/schemas/medicine-price.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -37,7 +37,7 @@ vi.mock('../../schemas/medicine-price.schema.js', () => {
return { MedicinePriceModel: FakeModel }; return { MedicinePriceModel: FakeModel };
}); });
import { MedicinePricesRepository } from './medicine-prices.repository.js'; import { MedicinePricesRepository } from '../../../src/modules/medicine-prices/medicine-prices.repository.js';
describe(MedicinePricesRepository.name, () => { describe(MedicinePricesRepository.name, () => {
let repo: MedicinePricesRepository; let repo: MedicinePricesRepository;

View file

@ -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 { MedicinePricesRepository: class {
create = vi.fn(); create = vi.fn();
findByMedicine = 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 { MedicinePricesService: class {
recordPrice = mockRecordPrice; recordPrice = mockRecordPrice;
getPriceHistory = mockGetPriceHistory; 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 { MedicineProductsRepository: class {
findById = vi.fn(); findById = vi.fn();
}, },
})); }));
vi.mock('../stores/stores.repository.js', () => ({ vi.mock('../../../src/modules/stores/stores.repository.js', () => ({
StoresRepository: class { StoresRepository: class {
findById = vi.fn(); findById = vi.fn();
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import medicinePricesRoutes from './medicine-prices.routes.js'; import medicinePricesRoutes from '../../../src/modules/medicine-prices/medicine-prices.routes.js';
function makeFakePriceRecord(overrides = {}) { function makeFakePriceRecord(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, () => { describe(MedicinePricesService.name, () => {
const mockPricesRepo = { const mockPricesRepo = {

View file

@ -30,7 +30,7 @@ const {
}; };
}); });
vi.mock('../../schemas/medicine-product.schema.js', () => { vi.mock('../../../src/schemas/medicine-product.schema.js', () => {
class MockMedicineProductModel { class MockMedicineProductModel {
_data: Record<string, unknown>; _data: Record<string, unknown>;
constructor(data: Record<string, unknown>) { constructor(data: Record<string, unknown>) {
@ -52,7 +52,7 @@ vi.mock('../../schemas/medicine-product.schema.js', () => {
return { MedicineProductModel: MockMedicineProductModel }; return { MedicineProductModel: MockMedicineProductModel };
}); });
import { MedicineProductsRepository } from './medicine-products.repository.js'; import { MedicineProductsRepository } from '../../../src/modules/medicine-products/medicine-products.repository.js';
describe(MedicineProductsRepository.name, () => { describe(MedicineProductsRepository.name, () => {
let repo: MedicineProductsRepository; let repo: MedicineProductsRepository;

View file

@ -35,7 +35,7 @@ const {
mockMedicineFindById: vi.fn(), mockMedicineFindById: vi.fn(),
})); }));
vi.mock('./medicine-products.repository.js', () => ({ vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({
MedicineProductsRepository: class { MedicineProductsRepository: class {
findByMedicine = mockFindByMedicine; findByMedicine = mockFindByMedicine;
findById = mockFindById; 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 { MedicinesRepository: class {
findById = mockMedicineFindById; findById = mockMedicineFindById;
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import medicinesRoutes from '../medicines/medicines.routes.js'; import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js';
import medicineProductsRoutes from './medicine-products.routes.js'; import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js';
function makeFakeProduct(overrides = {}) { function makeFakeProduct(overrides = {}) {
return { return {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MedicineProductsService } from './medicine-products.service.js'; import { MedicineProductsService } from '../../../src/modules/medicine-products/medicine-products.service.js';
import { NotFoundError } from '../../common/errors.js'; import { NotFoundError } from '../../../src/common/errors.js';
import { DosageUnit, MedicineProductSource } from '@meshitrack/shared'; import { DosageUnit, MedicineProductSource } from '@meshitrack/shared';
describe(MedicineProductsService.name, () => { describe(MedicineProductsService.name, () => {

View file

@ -27,7 +27,7 @@ const {
}; };
}); });
vi.mock('../../schemas/medicine.schema.js', () => { vi.mock('../../../src/schemas/medicine.schema.js', () => {
class MockMedicineModel { class MockMedicineModel {
_data: Record<string, unknown>; _data: Record<string, unknown>;
constructor(data: Record<string, unknown>) { constructor(data: Record<string, unknown>) {
@ -48,7 +48,7 @@ vi.mock('../../schemas/medicine.schema.js', () => {
return { MedicineModel: MockMedicineModel }; return { MedicineModel: MockMedicineModel };
}); });
import { MedicinesRepository } from './medicines.repository.js'; import { MedicinesRepository } from '../../../src/modules/medicines/medicines.repository.js';
describe(MedicinesRepository.name, () => { describe(MedicinesRepository.name, () => {
let repo: MedicinesRepository; let repo: MedicinesRepository;

View file

@ -37,7 +37,7 @@ const {
mockCountByMedicineId: vi.fn(), mockCountByMedicineId: vi.fn(),
})); }));
vi.mock('./medicines.repository.js', () => ({ vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({
MedicinesRepository: class { MedicinesRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; 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 { MedicineProductsRepository: class {
countByMedicineId = mockCountByMedicineId; countByMedicineId = mockCountByMedicineId;
}, },
})); }));
vi.mock('../medicine-products/medicine-products.service.js', () => ({ vi.mock('../../../src/modules/medicine-products/medicine-products.service.js', () => ({
MedicineProductsService: class { MedicineProductsService: class {
listByMedicine = vi.fn(); listByMedicine = vi.fn();
getById = 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import medicinesRoutes from './medicines.routes.js'; import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js';
import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js'; import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js';
function makeFakeMedicine(overrides = {}) { function makeFakeMedicine(overrides = {}) {
return { return {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MedicinesService } from './medicines.service.js'; import { MedicinesService } from '../../../src/modules/medicines/medicines.service.js';
import { NotFoundError, ConflictError } from '../../common/errors.js'; import { NotFoundError, ConflictError } from '../../../src/common/errors.js';
import { MedicineForm, StrengthUnit, MedicineCategory } from '@meshitrack/shared'; import { MedicineForm, StrengthUnit, MedicineCategory } from '@meshitrack/shared';
describe(MedicinesService.name, () => { describe(MedicinesService.name, () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, MockTargetModel } = vi.hoisted(() => {
const mockSave = vi.fn(); const mockSave = vi.fn();
@ -16,11 +16,11 @@ const { mockSave, MockTargetModel } = vi.hoisted(() => {
return { mockSave, MockTargetModel: MockModel }; return { mockSave, MockTargetModel: MockModel };
}); });
vi.mock('../../schemas/nutrition-target.schema.js', () => ({ vi.mock('../../../src/schemas/nutrition-target.schema.js', () => ({
NutritionTargetModel: MockTargetModel, 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) { function makeChain(result: unknown = null) {
return { return {

View file

@ -30,7 +30,7 @@ const {
mockCreate: vi.fn(), mockCreate: vi.fn(),
})); }));
vi.mock('./nutrition-target.repository.js', () => ({ vi.mock('../../../src/modules/nutrition-targets/nutrition-target.repository.js', () => ({
NutritionTargetRepository: class { NutritionTargetRepository: class {
findByUser = mockFindByUser; findByUser = mockFindByUser;
findAllByUser = mockFindAllByUser; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import nutritionTargetRoutes from './nutrition-target.routes.js'; import nutritionTargetRoutes from '../../../src/modules/nutrition-targets/nutrition-target.routes.js';
function makeTarget(overrides = {}) { function makeTarget(overrides = {}) {
return { return {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { NutritionTargetService } from './nutrition-target.service.js'; import { NutritionTargetService } from '../../../src/modules/nutrition-targets/nutrition-target.service.js';
import type { NutritionTargetRepository } from './nutrition-target.repository.js'; import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';
describe(NutritionTargetService.name, () => { describe(NutritionTargetService.name, () => {
let service: NutritionTargetService; let service: NutritionTargetService;

View file

@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/organizer-fill.schema.js', () => { vi.mock('../../../src/schemas/organizer-fill.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -41,7 +41,7 @@ vi.mock('../../schemas/organizer-fill.schema.js', () => {
return { OrganizerFillModel: FakeModel }; return { OrganizerFillModel: FakeModel };
}); });
import { OrganizerRepository } from './organizer.repository.js'; import { OrganizerRepository } from '../../../src/modules/organizer/organizer.repository.js';
describe(OrganizerRepository.name, () => { describe(OrganizerRepository.name, () => {
let repo: OrganizerRepository; let repo: OrganizerRepository;

View file

@ -27,7 +27,7 @@ const { mockListFills, mockGetFillById, mockPreview, mockFill, mockUndoFill } =
mockUndoFill: vi.fn(), mockUndoFill: vi.fn(),
})); }));
vi.mock('./organizer.repository.js', () => ({ vi.mock('../../../src/modules/organizer/organizer.repository.js', () => ({
OrganizerRepository: class { OrganizerRepository: class {
create = vi.fn(); create = vi.fn();
update = 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 { OrganizerService: class {
listFills = mockListFills; listFills = mockListFills;
getFillById = mockGetFillById; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import organizerRoutes from './organizer.routes.js'; import organizerRoutes from '../../../src/modules/organizer/organizer.routes.js';
function makeFakeFill(overrides = {}) { function makeFakeFill(overrides = {}) {
return { return {

View file

@ -19,7 +19,7 @@ vi.mock('mongoose', () => {
return { default: { startSession: vi.fn().mockResolvedValue(mockSession) } }; 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, () => { describe(OrganizerService.name, () => {
const mockOrganizerRepo = { const mockOrganizerRepo = {

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; 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'; import { ItemStatus, FreshnessUrgency, FreshnessSource, StorageLocation } from '@meshitrack/shared';
describe(FreshnessCalculatorService.name, () => { describe(FreshnessCalculatorService.name, () => {

View file

@ -20,7 +20,7 @@ const {
mockFindByIdAndUpdate: vi.fn(), mockFindByIdAndUpdate: vi.fn(),
})); }));
vi.mock('../../schemas/pantry-item.schema.js', () => { vi.mock('../../../src/schemas/pantry-item.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -75,7 +75,7 @@ vi.mock('../../schemas/pantry-item.schema.js', () => {
return { PantryItemModel: FakeModel }; return { PantryItemModel: FakeModel };
}); });
import { PantryRepository } from './pantry.repository.js'; import { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js';
describe(PantryRepository.name, () => { describe(PantryRepository.name, () => {
let repo: PantryRepository; let repo: PantryRepository;

View file

@ -54,7 +54,7 @@ const { mockFindApplicableRule } = vi.hoisted(() => ({
mockFindApplicableRule: vi.fn(), mockFindApplicableRule: vi.fn(),
})); }));
vi.mock('./pantry.repository.js', () => ({ vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({
PantryRepository: class { PantryRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; 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 { ProductsRepository: class {
findById = mockProductFindById; findById = mockProductFindById;
findByIds = vi.fn(); findByIds = vi.fn();
}, },
})); }));
vi.mock('../freshness-rules/freshness-rules.repository.js', () => ({ vi.mock('../../../src/modules/freshness-rules/freshness-rules.repository.js', () => ({
FreshnessRulesRepository: class { FreshnessRulesRepository: class {
findApplicableRule = mockFindApplicableRule; findApplicableRule = mockFindApplicableRule;
findByHousehold = vi.fn(); 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import pantryRoutes from './pantry.routes.js'; import pantryRoutes from '../../../src/modules/pantry/pantry.routes.js';
const freshness = { const freshness = {
estimatedExpiryDate: new Date('2024-02-01').toISOString(), estimatedExpiryDate: new Date('2024-02-01').toISOString(),

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PantryService } from './pantry.service.js'; import { PantryService } from '../../../src/modules/pantry/pantry.service.js';
import { NotFoundError, BadRequestError } from '../../common/errors.js'; import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
import { ItemStatus } from '@meshitrack/shared'; import { ItemStatus } from '@meshitrack/shared';
const mockPantryRepo = { const mockPantryRepo = {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, MockPriceRecordModel } = vi.hoisted(() => {
const mockSave = vi.fn(); const mockSave = vi.fn();
@ -17,11 +17,11 @@ const { mockSave, MockPriceRecordModel } = vi.hoisted(() => {
return { mockSave, MockPriceRecordModel: MockModel }; return { mockSave, MockPriceRecordModel: MockModel };
}); });
vi.mock('../../schemas/price-record.schema.js', () => ({ vi.mock('../../../src/schemas/price-record.schema.js', () => ({
PriceRecordModel: MockPriceRecordModel, 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) { function makeChain(result: unknown = null) {
return { return {

View file

@ -23,7 +23,7 @@ const mockFindByProduct = vi.fn();
const mockCompareStores = vi.fn(); const mockCompareStores = vi.fn();
const mockGetAnalytics = vi.fn(); const mockGetAnalytics = vi.fn();
vi.mock('./prices.repository.js', () => ({ vi.mock('../../../src/modules/prices/prices.repository.js', () => ({
PricesRepository: class { PricesRepository: class {
create = mockCreate; create = mockCreate;
createMany = mockCreateMany; 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 { ProductsRepository: class {
findById = vi.fn().mockResolvedValue({ name: 'Mock Product' }); findById = vi.fn().mockResolvedValue({ name: 'Mock Product' });
findByIds = vi.fn().mockResolvedValue([{ _id: 'p1', 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 { StoresRepository: class {
findById = vi.fn().mockResolvedValue({ name: 'Mock Store' }); findById = vi.fn().mockResolvedValue({ name: 'Mock Store' });
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import pricesRoutes from './prices.routes.js'; import pricesRoutes from '../../../src/modules/prices/prices.routes.js';
describe('prices.routes', () => { describe('prices.routes', () => {
let app: any; let app: any;

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PricesService } from './prices.service.js'; import { PricesService } from '../../../src/modules/prices/prices.service.js';
import { NotFoundError } from '../../common/errors.js'; import { NotFoundError } from '../../../src/common/errors.js';
describe('PricesService', () => { describe('PricesService', () => {
let service: PricesService; let service: PricesService;

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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', () => ({ vi.mock('undici', () => ({
request: vi.fn(), request: vi.fn(),

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; 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'; import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared';
describe('parseCsv', () => { describe('parseCsv', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, MockProductModel } = vi.hoisted(() => {
const mockSave = vi.fn(); const mockSave = vi.fn();
@ -16,11 +16,11 @@ const { mockSave, MockProductModel } = vi.hoisted(() => {
return { mockSave, MockProductModel }; return { mockSave, MockProductModel };
}); });
vi.mock('../../schemas/product.schema.js', () => ({ vi.mock('../../../src/schemas/product.schema.js', () => ({
ProductModel: MockProductModel, ProductModel: MockProductModel,
})); }));
const { ProductModel } = await import('../../schemas/product.schema.js'); const { ProductModel } = await import('../../../src/schemas/product.schema.js');
function makeChain(result: unknown = null) { function makeChain(result: unknown = null) {
return { return {

View file

@ -41,7 +41,7 @@ const {
mockBarcodeLookup: vi.fn(), mockBarcodeLookup: vi.fn(),
})); }));
vi.mock('./products.repository.js', () => ({ vi.mock('../../../src/modules/products/products.repository.js', () => ({
ProductsRepository: class { ProductsRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; 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 { BarcodeService: class {
lookup = mockBarcodeLookup; lookup = mockBarcodeLookup;
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import productsRoutes from './products.routes.js'; import productsRoutes from '../../../src/modules/products/products.routes.js';
function makeFakeProduct(overrides: Record<string, unknown> = {}) { function makeFakeProduct(overrides: Record<string, unknown> = {}) {
return { return {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ProductsService } from './products.service.js'; import { ProductsService } from '../../../src/modules/products/products.service.js';
import { NotFoundError, ConflictError } from '../../common/errors.js'; import { NotFoundError, ConflictError } from '../../../src/common/errors.js';
import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared'; import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared';
const mockRepo = { const mockRepo = {

View file

@ -8,7 +8,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockAggregate, mockSave } =
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/purchase.schema.js', () => { vi.mock('../../../src/schemas/purchase.schema.js', () => {
const findChain = () => ({ const findChain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -36,7 +36,7 @@ vi.mock('../../schemas/purchase.schema.js', () => {
return { PurchaseModel: FakeModel }; return { PurchaseModel: FakeModel };
}); });
import { PurchasesRepository } from './purchases.repository.js'; import { PurchasesRepository } from '../../../src/modules/purchases/purchases.repository.js';
const makeItem = (overrides = {}) => ({ const makeItem = (overrides = {}) => ({
medicineProductId: 'mp-1', medicineProductId: 'mp-1',
@ -101,7 +101,7 @@ describe(PurchasesRepository.name, () => {
it('filters by status when provided', async () => { it('filters by status when provided', async () => {
mockFind.mockResolvedValue([]); 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' }); await repo.findByHousehold('hh1', { limit: 20, status: 'ordered' });
@ -112,7 +112,7 @@ describe(PurchasesRepository.name, () => {
it('filters by storeId when provided', async () => { it('filters by storeId when provided', async () => {
mockFind.mockResolvedValue([]); 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' }); await repo.findByHousehold('hh1', { limit: 20, storeId: 'st-1' });
@ -122,7 +122,7 @@ describe(PurchasesRepository.name, () => {
it('applies cursor filter when provided', async () => { it('applies cursor filter when provided', async () => {
mockFind.mockResolvedValue([]); mockFind.mockResolvedValue([]);
const cursor = Buffer.from('p-1').toString('base64'); 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 }); await repo.findByHousehold('hh1', { limit: 20, cursor });
@ -172,7 +172,7 @@ describe(PurchasesRepository.name, () => {
it('includes items in update set when provided', async () => { it('includes items in update set when provided', async () => {
const updated = { _id: 'p-1' }; const updated = { _id: 'p-1' };
mockFindOneAndUpdate.mockResolvedValue(updated); 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 }]; const items = [{ name: 'X', quantity: 1, unit: 'tablet', addedToCabinet: false }];
await repo.update('p-1', 'hh1', { items } as never); 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 () => { it('sets status to in_cabinet and all items addedToCabinet', async () => {
const updated = { _id: 'p-1', status: 'in_cabinet' }; const updated = { _id: 'p-1', status: 'in_cabinet' };
mockFindOneAndUpdate.mockResolvedValue(updated); 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'); 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 () => { it('builds per-index update set and calls findOneAndUpdate', async () => {
const updated = { _id: 'p-1', items: [] }; const updated = { _id: 'p-1', items: [] };
mockFindOneAndUpdate.mockResolvedValue(updated); 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]); const result = await repo.markItemsAddedToCabinet('p-1', 'hh1', [0, 2]);

View file

@ -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 { PurchasesRepository: class {
create = vi.fn(); create = vi.fn();
findByHousehold = 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 { PurchasesService: class {
list = mockList; list = mockList;
getById = mockGetById; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import purchasesRoutes from './purchases.routes.js'; import purchasesRoutes from '../../../src/modules/purchases/purchases.routes.js';
function makeFakePurchase(overrides = {}) { function makeFakePurchase(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, () => { describe(PurchasesService.name, () => {
const mockPurchasesRepo = { const mockPurchasesRepo = {

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; 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'; import { NutritionWarning } from '@meshitrack/shared';
const service = new NutritionCalculatorService(); const service = new NutritionCalculatorService();

View file

@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/recipe.schema.js', () => { vi.mock('../../../src/schemas/recipe.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -41,7 +41,7 @@ vi.mock('../../schemas/recipe.schema.js', () => {
return { RecipeModel: FakeModel }; return { RecipeModel: FakeModel };
}); });
import { RecipesRepository } from './recipes.repository.js'; import { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js';
describe(RecipesRepository.name, () => { describe(RecipesRepository.name, () => {
let repo: RecipesRepository; let repo: RecipesRepository;

View file

@ -40,7 +40,7 @@ const { mockFindByIds } = vi.hoisted(() => ({
mockFindByIds: vi.fn(), mockFindByIds: vi.fn(),
})); }));
vi.mock('./recipes.repository.js', () => ({ vi.mock('../../../src/modules/recipes/recipes.repository.js', () => ({
RecipesRepository: class { RecipesRepository: class {
findByHousehold = mockFindByHousehold; findByHousehold = mockFindByHousehold;
findById = mockFindById; 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 { ProductsRepository: class {
findByIds = mockFindByIds; findByIds = mockFindByIds;
findById = vi.fn(); findById = vi.fn();
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import recipesRoutes from './recipes.routes.js'; import recipesRoutes from '../../../src/modules/recipes/recipes.routes.js';
const nutrition = { calories: 200, protein: 20, carbs: 10, fat: 8 }; const nutrition = { calories: 200, protein: 20, carbs: 10, fat: 8 };

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { RecipesService } from './recipes.service.js'; import { RecipesService } from '../../../src/modules/recipes/recipes.service.js';
import { NotFoundError, BadRequestError } from '../../common/errors.js'; import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
const makeProduct = (id: string, servingUnit = 'g', servingSize = 100) => ({ const makeProduct = (id: string, servingUnit = 'g', servingSize = 100) => ({
_id: { toString: () => id }, _id: { toString: () => id },

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; 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('toMetric', () => {
describe('metric pass-through', () => { describe('metric pass-through', () => {

View file

@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/refill-list.schema.js', () => { vi.mock('../../../src/schemas/refill-list.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -33,7 +33,7 @@ vi.mock('../../schemas/refill-list.schema.js', () => {
return { RefillListModel: FakeModel }; return { RefillListModel: FakeModel };
}); });
import { RefillsRepository } from './refills.repository.js'; import { RefillsRepository } from '../../../src/modules/refills/refills.repository.js';
describe(RefillsRepository.name, () => { describe(RefillsRepository.name, () => {
let repo: RefillsRepository; let repo: RefillsRepository;

View file

@ -38,7 +38,7 @@ const {
mockGetStoreComparison: vi.fn(), mockGetStoreComparison: vi.fn(),
})); }));
vi.mock('./refills.repository.js', () => ({ vi.mock('../../../src/modules/refills/refills.repository.js', () => ({
RefillsRepository: class { RefillsRepository: class {
create = vi.fn(); create = vi.fn();
findByHousehold = 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 { RefillsService: class {
getAlerts = mockGetAlerts; getAlerts = mockGetAlerts;
createList = mockCreateList; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import refillsRoutes from './refills.routes.js'; import refillsRoutes from '../../../src/modules/refills/refills.routes.js';
function makeFakeRefillList(overrides = {}) { function makeFakeRefillList(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, () => { describe(RefillsService.name, () => {
const mockRepo = { const mockRepo = {

View file

@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/regimen.schema.js', () => { vi.mock('../../../src/schemas/regimen.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -41,7 +41,7 @@ vi.mock('../../schemas/regimen.schema.js', () => {
return { RegimenModel: FakeModel }; return { RegimenModel: FakeModel };
}); });
import { RegimensRepository } from './regimens.repository.js'; import { RegimensRepository } from '../../../src/modules/regimens/regimens.repository.js';
describe(RegimensRepository.name, () => { describe(RegimensRepository.name, () => {
let repo: RegimensRepository; let repo: RegimensRepository;

View file

@ -29,7 +29,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockDelete, mockCalculate
mockCalculateBurnRates: vi.fn(), mockCalculateBurnRates: vi.fn(),
})); }));
vi.mock('./regimens.repository.js', () => ({ vi.mock('../../../src/modules/regimens/regimens.repository.js', () => ({
RegimensRepository: class { RegimensRepository: class {
findByHousehold = vi.fn(); findByHousehold = vi.fn();
findById = 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 { RegimensService: class {
list = mockList; list = mockList;
getById = mockGetById; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import regimensRoutes from './regimens.routes.js'; import regimensRoutes from '../../../src/modules/regimens/regimens.routes.js';
function makeFakeRegimen(overrides = {}) { function makeFakeRegimen(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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'; import { DosageFrequency } from '@meshitrack/shared';
describe(RegimensService.name, () => { describe(RegimensService.name, () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, MockShoppingListModel } = vi.hoisted(() => {
const mockSave = vi.fn(); const mockSave = vi.fn();
@ -16,11 +16,11 @@ const { mockSave, MockShoppingListModel } = vi.hoisted(() => {
return { mockSave, MockShoppingListModel: MockModel }; return { mockSave, MockShoppingListModel: MockModel };
}); });
vi.mock('../../schemas/shopping-list.schema.js', () => ({ vi.mock('../../../src/schemas/shopping-list.schema.js', () => ({
ShoppingListModel: MockShoppingListModel, 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) { function makeChain(result: unknown = null) {
return { return {

View file

@ -26,7 +26,7 @@ const mockAddItem = vi.fn();
const mockUpdateItem = vi.fn(); const mockUpdateItem = vi.fn();
const mockRemoveItem = vi.fn(); const mockRemoveItem = vi.fn();
vi.mock('./shopping-lists.repository.js', () => ({ vi.mock('../../../src/modules/shopping-lists/shopping-lists.repository.js', () => ({
ShoppingListsRepository: class { ShoppingListsRepository: class {
list = mockList; list = mockList;
findById = mockFindById; 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 { ShoppingGapService: class {
calculateGap = vi.fn().mockResolvedValue({ missingItems: [] }); calculateGap = vi.fn().mockResolvedValue({ missingItems: [] });
}, },
})); }));
vi.mock('../pantry/pantry.service.js', () => ({ vi.mock('../../../src/modules/pantry/pantry.service.js', () => ({
PantryService: class { PantryService: class {
create = vi.fn().mockResolvedValue({ _id: 'pant1' }); create = vi.fn().mockResolvedValue({ _id: 'pant1' });
}, },
})); }));
vi.mock('../products/products.repository.js', () => ({ vi.mock('../../../src/modules/products/products.repository.js', () => ({
ProductsRepository: class { ProductsRepository: class {
findById = vi.fn().mockResolvedValue({ category: 'dairy' }); findById = vi.fn().mockResolvedValue({ category: 'dairy' });
}, },
})); }));
vi.mock('../prices/prices.service.js', () => ({ vi.mock('../../../src/modules/prices/prices.service.js', () => ({
PricesService: class { PricesService: class {
estimatePrice = vi.fn().mockResolvedValue(5.0); estimatePrice = vi.fn().mockResolvedValue(5.0);
recordPrice = vi.fn().mockResolvedValue({}); 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 { MealPlanRepository: class {
findById = vi.fn().mockResolvedValue({ _id: 'mp1', weekStartDate: new Date() }); findById = vi.fn().mockResolvedValue({ _id: 'mp1', weekStartDate: new Date() });
update = vi.fn().mockResolvedValue({}); update = vi.fn().mockResolvedValue({});
}, },
})); }));
vi.mock('../users/users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import shoppingListsRoutes from './shopping-lists.routes.js'; import shoppingListsRoutes from '../../../src/modules/shopping-lists/shopping-lists.routes.js';
describe('shopping-lists.routes', () => { describe('shopping-lists.routes', () => {
let app: any; let app: any;

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ShoppingListsService } from './shopping-lists.service.js'; import { ShoppingListsService } from '../../../src/modules/shopping-lists/shopping-lists.service.js';
import { NotFoundError } from '../../common/errors.js'; import { NotFoundError } from '../../../src/common/errors.js';
describe('ShoppingListsService', () => { describe('ShoppingListsService', () => {
let service: ShoppingListsService; let service: ShoppingListsService;

View file

@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
mockSave: vi.fn(), mockSave: vi.fn(),
})); }));
vi.mock('../../schemas/store.schema.js', () => { vi.mock('../../../src/schemas/store.schema.js', () => {
const chain = () => ({ const chain = () => ({
sort: vi.fn().mockReturnThis(), sort: vi.fn().mockReturnThis(),
limit: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(),
@ -33,7 +33,7 @@ vi.mock('../../schemas/store.schema.js', () => {
return { StoreModel: FakeModel }; return { StoreModel: FakeModel };
}); });
import { StoresRepository } from './stores.repository.js'; import { StoresRepository } from '../../../src/modules/stores/stores.repository.js';
describe(StoresRepository.name, () => { describe(StoresRepository.name, () => {
let repo: StoresRepository; let repo: StoresRepository;

View file

@ -26,7 +26,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockDeactivate } = vi.hoi
mockDeactivate: vi.fn(), mockDeactivate: vi.fn(),
})); }));
vi.mock('./stores.repository.js', () => ({ vi.mock('../../../src/modules/stores/stores.repository.js', () => ({
StoresRepository: class { StoresRepository: class {
findByHousehold = vi.fn(); findByHousehold = vi.fn();
findById = 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 { StoresService: class {
list = mockList; list = mockList;
getById = mockGetById; 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 { UsersRepository: class {
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] }); upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../plugins/household.plugin.js'; import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../users/users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
import storesRoutes from './stores.routes.js'; import storesRoutes from '../../../src/modules/stores/stores.routes.js';
function makeFakeStore(overrides = {}) { function makeFakeStore(overrides = {}) {
return { return {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; 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, () => { describe(StoresService.name, () => {
const mockRepo = { const mockRepo = {

View file

@ -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 { class MockUserModel {
_data: Record<string, unknown>; _data: Record<string, unknown>;
constructor(data: Record<string, unknown>) { constructor(data: Record<string, unknown>) {
@ -36,7 +36,7 @@ vi.mock('../../schemas/user.schema.js', () => {
return { UserModel: MockUserModel }; return { UserModel: MockUserModel };
}); });
import { UsersRepository } from './users.repository.js'; import { UsersRepository } from '../../../src/modules/users/users.repository.js';
describe('UsersRepository', () => { describe('UsersRepository', () => {
let repo: UsersRepository; let repo: UsersRepository;

View file

@ -24,15 +24,15 @@ const { mockUpsertFromToken, mockFindByKeycloakId } = vi.hoisted(() => ({
mockUpsertFromToken: vi.fn(), mockUpsertFromToken: vi.fn(),
mockFindByKeycloakId: vi.fn(), mockFindByKeycloakId: vi.fn(),
})); }));
vi.mock('./users.repository.js', () => ({ vi.mock('../../../src/modules/users/users.repository.js', () => ({
UsersRepository: class MockUsersRepository { UsersRepository: class MockUsersRepository {
upsertFromToken = mockUpsertFromToken; upsertFromToken = mockUpsertFromToken;
findByKeycloakId = mockFindByKeycloakId; findByKeycloakId = mockFindByKeycloakId;
}, },
})); }));
import authPlugin from '../../plugins/auth.plugin.js'; import authPlugin from '../../../src/plugins/auth.plugin.js';
import usersRoutes from './users.routes.js'; import usersRoutes from '../../../src/modules/users/users.routes.js';
describe('users.routes', () => { describe('users.routes', () => {
async function buildTestApp() { async function buildTestApp() {

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UsersService } from './users.service.js'; import { UsersService } from '../../../src/modules/users/users.service.js';
import { NotFoundError } from '../../common/errors.js'; import { NotFoundError } from '../../../src/common/errors.js';
describe('UsersService', () => { describe('UsersService', () => {
const mockRepo = { const mockRepo = {

View file

@ -19,7 +19,7 @@ const { mockFindByKeycloakId, mockUpsertFromToken } = vi.hoisted(() => ({
mockUpsertFromToken: vi.fn(), mockUpsertFromToken: vi.fn(),
})); }));
import authPlugin from './auth.plugin.js'; import authPlugin from '../../src/plugins/auth.plugin.js';
import * as jose from 'jose'; import * as jose from 'jose';
describe('auth.plugin', () => { describe('auth.plugin', () => {

View file

@ -22,8 +22,8 @@ const { mockFindByKeycloakId } = vi.hoisted(() => ({
mockFindByKeycloakId: vi.fn(), mockFindByKeycloakId: vi.fn(),
})); }));
import authPlugin from './auth.plugin.js'; import authPlugin from '../../src/plugins/auth.plugin.js';
import householdPlugin from './household.plugin.js'; import householdPlugin from '../../src/plugins/household.plugin.js';
describe('household.plugin', () => { describe('household.plugin', () => {
async function buildApp() { async function buildApp() {

View file

@ -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 mongoose from 'mongoose';
import { diContainer } from '@fastify/awilix'; import { diContainer } from '@fastify/awilix';

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { HouseholdModel } from './household.schema.js'; import { HouseholdModel } from '../../src/schemas/household.schema.js';
describe('HouseholdModel', () => { describe('HouseholdModel', () => {
it('is a valid mongoose model', () => { it('is a valid mongoose model', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; 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, () => { describe(MedicineProductModel.name, () => {
it('is a valid mongoose model', () => { it('is a valid mongoose model', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { MedicineModel } from './medicine.schema.js'; import { MedicineModel } from '../../src/schemas/medicine.schema.js';
describe(MedicineModel.name, () => { describe(MedicineModel.name, () => {
it('is a valid mongoose model', () => { it('is a valid mongoose model', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { ProductModel } from './product.schema.js'; import { ProductModel } from '../../src/schemas/product.schema.js';
describe(ProductModel.name, () => { describe(ProductModel.name, () => {
it('is a valid mongoose model', () => { it('is a valid mongoose model', () => {

Some files were not shown because too many files have changed in this diff Show more