Tests refactor
This commit is contained in:
parent
245520fb50
commit
99134d8556
165 changed files with 911 additions and 531 deletions
|
|
@ -46,21 +46,24 @@ docker compose -f docker/docker-compose.yml up -d # Start all services
|
|||
docker compose -f docker/docker-compose.yml down # Stop all services
|
||||
```
|
||||
|
||||
## Implementation Workflow
|
||||
## Implementation Workflow (TDD-First)
|
||||
|
||||
Every feature or phase implementation MUST start with a rigorous planning and design phase:
|
||||
|
||||
0. **Mandatory Planning (Grilling)**: Before starting any feature or bug fix, you MUST invoke the `grill-with-docs` skill.
|
||||
- Run the skill using: `view_file` on `.agents/skills/grill-with-docs/SKILL.md` and follow its instructions.
|
||||
- This session will stress-test your plan against the existing domain model, terminology, and documentation (`CONTEXT.md`, ADRs).
|
||||
- Decisions must be crystallized and documentation (glossary/ADRs) updated before moving to implementation.
|
||||
- Stress-test your plan against the existing domain model, terminology, and documentation (`CONTEXT.md`, ADRs, `docs/tdd-transition-plan.md`).
|
||||
- Decisions must be crystallized and documentation updated before moving to implementation.
|
||||
|
||||
1. **Vertical Slice Implementation**: Follow this approach for the actual build:
|
||||
2. **Database Layer**: Create the Mongoose schema and Repository in `packages/api`. All reads must use `.lean().exec()`.
|
||||
3. **Service Layer**: Implement business logic in the Service class, using Awilix for constructor injection. Add unit tests.
|
||||
4. **Route Layer**: Create the Fastify route plugin and register it. Add route tests (using `app.inject`).
|
||||
5. **Web API Client**: Implement the frontend service in `packages/web/src/services/`. Add unit tests.
|
||||
6. **Web UI**: Create the Next.js pages and components. Use Server Components by default. Add component tests (React Testing Library).
|
||||
1. **Strict TDD Cycle**: All code changes must strictly follow the Red-Green-Refactor loop as detailed in [TDD Guide](docs/instructions/tdd.md). Do not write any production code before writing its failing test.
|
||||
|
||||
2. **Vertical Slice TDD Order**: Build each vertical slice following this exact test-first sequence:
|
||||
- **Shared Layer**: Write validation schema tests first in `packages/shared/tests/` -> Implement schemas in `packages/shared/src/`.
|
||||
- **Database Layer**: Write integration tests in `packages/api/tests/` using `mongodb-memory-server` -> Implement Mongoose schemas and Repository in `packages/api/src/`. All reads must use `.lean().exec()`.
|
||||
- **Service Layer**: Write unit tests in `packages/api/tests/` using `createMockRepository` -> Implement business logic Service class in `packages/api/src/` with Awilix constructor injection.
|
||||
- **Route Layer**: Write route tests in `packages/api/tests/` using `app.inject` -> Implement Fastify route plugin in `packages/api/src/`.
|
||||
- **Web API Client**: Write service unit tests in `packages/web/tests/` using MSW mocking -> Implement the service client in `packages/web/src/`.
|
||||
- **Web UI**: Write component tests in `packages/web/tests/` using React Testing Library -> Implement Next.js pages and components in `packages/web/src/`. Use Server Components by default.
|
||||
|
||||
**Verification Gate (Mandatory)**:
|
||||
Before considering a task complete, run:
|
||||
|
|
|
|||
|
|
@ -97,19 +97,20 @@ Separate each group with a blank line.
|
|||
| React hook | camelCase | `useProducts`, `usePantryItems` |
|
||||
| CSS class (Tailwind) | kebab-case | via Tailwind utilities |
|
||||
|
||||
## Implementation Workflow (Vertical Slice)
|
||||
## Implementation Workflow (TDD-First)
|
||||
|
||||
Every feature or phase implementation must follow this order to ensure consistency and type safety across the monorepo:
|
||||
Every feature or phase implementation must follow this exact test-first sequence, strictly practicing the Red-Green-Refactor cycle:
|
||||
|
||||
1. **Shared Layer (`packages/shared`)**: Define types, enums, and Zod schemas. Add unit tests.
|
||||
2. **Database Layer (`packages/api`)**: Create Mongoose schema and Repository. Use `.lean().exec()` on all reads. Add integration tests.
|
||||
3. **Service Layer (`packages/api`)**: Implement business logic using Awilix DI. Add unit tests.
|
||||
4. **Route Layer (`packages/api`)**: Create Fastify route plugin. Add route tests.
|
||||
5. **Web API Client (`packages/web`)**: Implement frontend service. Add unit tests.
|
||||
6. **Web UI (`packages/web`)**: Create Next.js pages/components (Server Components by default). Add component tests.
|
||||
1. **Shared Layer (`packages/shared`)**: Write validation schema and enum tests first in `tests/`. Implement schemas and types inside `src/`.
|
||||
2. **Database Layer (`packages/api`)**: Write schema and repository integration tests first in `tests/` (using `mongodb-memory-server`). Create Mongoose schema and Repository in `src/`. All reads must use `.lean().exec()`.
|
||||
3. **Service Layer (`packages/api`)**: Write service unit tests first in `tests/` using the `createMockRepository` helper to mock repository methods. Implement business logic Service class inside `src/` using Awilix constructor injection.
|
||||
4. **Route Layer (`packages/api`)**: Write Fastify route plugin tests first in `tests/` using `app.inject` and resolving mocks. Implement the Fastify route plugin inside `src/`.
|
||||
5. **Web API Client (`packages/web`)**: Write service unit tests first in `tests/` utilizing MSW to mock backend requests. Implement frontend service in `src/`.
|
||||
6. **Web UI (`packages/web`)**: Write component and page tests first in `tests/` using React Testing Library role queries. Create Next.js pages and components inside `src/` (Server Components by default).
|
||||
|
||||
**Mandatory Verification**: Every task must end with `npm run build`, `npm run test:cov`, and `npm run lint` all passing with the defined coverage thresholds.
|
||||
|
||||
|
||||
## Code Organization Rules
|
||||
|
||||
### API (NestJS)
|
||||
|
|
|
|||
167
docs/instructions/tdd.md
Normal file
167
docs/instructions/tdd.md
Normal 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
181
docs/tdd-transition-plan.md
Normal 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.
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
"lint": "turbo run lint",
|
||||
"lint-fix": "turbo run lint-fix",
|
||||
"test": "turbo run test",
|
||||
"test:watch": "turbo run test:watch",
|
||||
"test:cov": "turbo run test:cov",
|
||||
"typecheck": "turbo run typecheck",
|
||||
"clean": "turbo run clean",
|
||||
|
|
|
|||
|
|
@ -133,7 +133,9 @@ export default fp(
|
|||
// Make sure other module deps are available for classes instantiated by SuggestionEngine/ShoppingGap
|
||||
recipesRepository: asClass(RecipesRepository, { lifetime: Lifetime.SINGLETON }),
|
||||
pantryRepository: asClass(PantryRepository, { lifetime: Lifetime.SINGLETON }),
|
||||
nutritionTargetRepository: asClass(NutritionTargetRepository, { lifetime: Lifetime.SINGLETON }),
|
||||
nutritionTargetRepository: asClass(NutritionTargetRepository, {
|
||||
lifetime: Lifetime.SINGLETON,
|
||||
}),
|
||||
productsRepository: asClass(ProductsRepository, { lifetime: Lifetime.SINGLETON }),
|
||||
});
|
||||
|
||||
|
|
@ -181,7 +183,7 @@ export default fp(
|
|||
variety: z.number(),
|
||||
}),
|
||||
reasoning: z.array(z.string()),
|
||||
})
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
|
@ -190,7 +192,7 @@ export default fp(
|
|||
const suggestions = await engine.getSuggestions(
|
||||
request.params.householdId,
|
||||
request.user.keycloakId,
|
||||
{ limit: request.query.limit }
|
||||
{ limit: request.query.limit },
|
||||
);
|
||||
return reply.send(suggestions);
|
||||
},
|
||||
|
|
@ -215,7 +217,7 @@ export default fp(
|
|||
const service = fastify.diContainer.resolve('mealPlanService');
|
||||
const plan = await service.getByWeek(
|
||||
request.params.householdId,
|
||||
request.params.weekStartDate
|
||||
request.params.weekStartDate,
|
||||
);
|
||||
if (!plan) {
|
||||
return reply.status(200).send({ message: 'No meal plan scheduled for this week' });
|
||||
|
|
@ -253,7 +255,7 @@ export default fp(
|
|||
const plan = await service.create(
|
||||
request.params.householdId,
|
||||
request.user.keycloakId,
|
||||
request.body
|
||||
request.body,
|
||||
);
|
||||
return reply.status(201).send(toPlanResponse(plan as AnyPlanDoc));
|
||||
},
|
||||
|
|
@ -273,7 +275,7 @@ export default fp(
|
|||
const plan = await service.update(
|
||||
request.params.id,
|
||||
request.params.householdId,
|
||||
request.body
|
||||
request.body,
|
||||
);
|
||||
return reply.send(toPlanResponse(plan as AnyPlanDoc));
|
||||
},
|
||||
|
|
@ -295,7 +297,7 @@ export default fp(
|
|||
const plan = await service.updateStatus(
|
||||
request.params.id,
|
||||
request.params.householdId,
|
||||
request.body.status
|
||||
request.body.status,
|
||||
);
|
||||
return reply.send(toPlanResponse(plan as AnyPlanDoc));
|
||||
},
|
||||
|
|
@ -334,17 +336,14 @@ export default fp(
|
|||
pantryQuantity: z.number(),
|
||||
missingQuantity: z.number(),
|
||||
unit: z.string(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
handler: async (request, reply) => {
|
||||
const service = fastify.diContainer.resolve('shoppingGapService');
|
||||
const result = await service.calculateGap(
|
||||
request.params.householdId,
|
||||
request.params.id
|
||||
);
|
||||
const result = await service.calculateGap(request.params.householdId, request.params.id);
|
||||
return reply.send(result);
|
||||
},
|
||||
});
|
||||
|
|
@ -352,5 +351,5 @@ export default fp(
|
|||
{
|
||||
name: 'meal-plans-routes',
|
||||
dependencies: ['auth-plugin'],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type {
|
|||
NutritionInfo,
|
||||
MealPlanDaySchema,
|
||||
} from '@meshitrack/shared';
|
||||
import { z } from 'zod/v4';
|
||||
import { type z } from 'zod/v4';
|
||||
|
||||
type MealPlanDay = z.infer<typeof MealPlanDaySchema>;
|
||||
|
||||
|
|
@ -39,19 +39,12 @@ export class MealPlanService {
|
|||
return this.mealPlanRepository.findByWeek(householdId, weekStartDate);
|
||||
}
|
||||
|
||||
public async create(
|
||||
householdId: string,
|
||||
createdBy: string,
|
||||
input: CreateMealPlanInput
|
||||
) {
|
||||
public async create(householdId: string, createdBy: string, input: CreateMealPlanInput) {
|
||||
// Prevent overlapping meal plans for same household/week
|
||||
const existing = await this.mealPlanRepository.findByWeek(
|
||||
householdId,
|
||||
input.weekStartDate
|
||||
);
|
||||
const existing = await this.mealPlanRepository.findByWeek(householdId, input.weekStartDate);
|
||||
if (existing) {
|
||||
throw new BadRequestError(
|
||||
`A meal plan already exists for household ${householdId} starting ${input.weekStartDate}`
|
||||
`A meal plan already exists for household ${householdId} starting ${input.weekStartDate}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -66,11 +59,7 @@ export class MealPlanService {
|
|||
});
|
||||
}
|
||||
|
||||
public async update(
|
||||
id: string,
|
||||
householdId: string,
|
||||
input: UpdateMealPlanInput
|
||||
) {
|
||||
public async update(id: string, householdId: string, input: UpdateMealPlanInput) {
|
||||
const existing = await this.getById(id, householdId);
|
||||
|
||||
const data: Record<string, unknown> = {};
|
||||
|
|
@ -90,11 +79,7 @@ export class MealPlanService {
|
|||
return updated;
|
||||
}
|
||||
|
||||
public async updateStatus(
|
||||
id: string,
|
||||
householdId: string,
|
||||
status: MealPlanStatus
|
||||
) {
|
||||
public async updateStatus(id: string, householdId: string, status: MealPlanStatus) {
|
||||
await this.getById(id, householdId);
|
||||
const updated = await this.mealPlanRepository.updateStatus(id, householdId, status);
|
||||
if (!updated) {
|
||||
|
|
|
|||
|
|
@ -44,10 +44,7 @@ export class ShoppingGapService {
|
|||
this.productsRepository = productsRepository;
|
||||
}
|
||||
|
||||
public async calculateGap(
|
||||
householdId: string,
|
||||
mealPlanId: string
|
||||
): Promise<ShoppingGapResult> {
|
||||
public async calculateGap(householdId: string, mealPlanId: string): Promise<ShoppingGapResult> {
|
||||
const plan = await this.mealPlanRepository.findById(mealPlanId, householdId);
|
||||
if (!plan) {
|
||||
throw new NotFoundError('Meal plan not found');
|
||||
|
|
@ -75,7 +72,7 @@ export class ShoppingGapService {
|
|||
// 2. Fetch all referenced recipes in parallel
|
||||
const recipeIds = Array.from(recipeIdSet);
|
||||
const recipeDocs = await Promise.all(
|
||||
recipeIds.map((id) => this.recipesRepository.findById(id, householdId))
|
||||
recipeIds.map((id) => this.recipesRepository.findById(id, householdId)),
|
||||
);
|
||||
const recipesMap = new Map<string, any>();
|
||||
for (const doc of recipeDocs) {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class SuggestionEngineService {
|
|||
public async getSuggestions(
|
||||
householdId: string,
|
||||
userId: string,
|
||||
options: { limit?: number } = {}
|
||||
options: { limit?: number } = {},
|
||||
): Promise<ScoredRecipe[]> {
|
||||
const limit = options.limit ?? 5;
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ export class SuggestionEngineService {
|
|||
recipeId,
|
||||
pantryInventory,
|
||||
activeTarget as Record<string, unknown> | null,
|
||||
lastEaten
|
||||
lastEaten,
|
||||
);
|
||||
|
||||
const totalScore =
|
||||
|
|
@ -153,9 +153,7 @@ export class SuggestionEngineService {
|
|||
}
|
||||
|
||||
// 5. Sort descending and limit
|
||||
return scoredList
|
||||
.sort((a, b) => b.totalScore - a.totalScore)
|
||||
.slice(0, limit);
|
||||
return scoredList.sort((a, b) => b.totalScore - a.totalScore).slice(0, limit);
|
||||
}
|
||||
|
||||
private scoreRecipe(
|
||||
|
|
@ -164,7 +162,7 @@ export class SuggestionEngineService {
|
|||
recipeId: string,
|
||||
inventory: Map<string, { qty: number; minDays: number; maxUrgencyWeight: number }>,
|
||||
target: Record<string, unknown> | null,
|
||||
lastEaten: Map<string, number>
|
||||
lastEaten: Map<string, number>,
|
||||
) {
|
||||
// Filter non-optional ingredients
|
||||
const requiredIngs = ingredients.filter((ing) => !ing.isOptional);
|
||||
|
|
@ -262,7 +260,7 @@ export class SuggestionEngineService {
|
|||
|
||||
private generateReasoning(
|
||||
scores: { coverage: number; urgency: number; nutrition: number; variety: number },
|
||||
recipeName: string
|
||||
recipeName: string,
|
||||
): string[] {
|
||||
const reasons: string[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,11 @@ import { NutritionTargetModel } from '../../schemas/nutrition-target.schema.js';
|
|||
|
||||
export class NutritionTargetRepository {
|
||||
public async findByUser(userId: string, householdId: string) {
|
||||
return NutritionTargetModel.findOne({ userId, householdId, isActive: true })
|
||||
.lean()
|
||||
.exec();
|
||||
return NutritionTargetModel.findOne({ userId, householdId, isActive: true }).lean().exec();
|
||||
}
|
||||
|
||||
public async findAllByUser(userId: string, householdId: string) {
|
||||
return NutritionTargetModel.find({ userId, householdId })
|
||||
.sort({ createdAt: -1 })
|
||||
.lean()
|
||||
.exec();
|
||||
return NutritionTargetModel.find({ userId, householdId }).sort({ createdAt: -1 }).lean().exec();
|
||||
}
|
||||
|
||||
public async create(data: Record<string, unknown>) {
|
||||
|
|
@ -23,15 +18,20 @@ export class NutritionTargetRepository {
|
|||
public async deactivateAllForUser(userId: string, householdId: string) {
|
||||
return NutritionTargetModel.updateMany(
|
||||
{ userId, householdId, isActive: true },
|
||||
{ $set: { isActive: false } }
|
||||
{ $set: { isActive: false } },
|
||||
).exec();
|
||||
}
|
||||
|
||||
public async update(id: string, userId: string, householdId: string, data: Record<string, unknown>) {
|
||||
public async update(
|
||||
id: string,
|
||||
userId: string,
|
||||
householdId: string,
|
||||
data: Record<string, unknown>,
|
||||
) {
|
||||
return NutritionTargetModel.findOneAndUpdate(
|
||||
{ _id: id, userId, householdId },
|
||||
{ $set: data },
|
||||
{ new: true, lean: true }
|
||||
{ new: true, lean: true },
|
||||
).exec();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ import fp from 'fastify-plugin';
|
|||
import { asClass, Lifetime } from 'awilix';
|
||||
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
|
||||
import { z } from 'zod/v4';
|
||||
import {
|
||||
NutritionTargetSchema,
|
||||
NutritionTargetResponseSchema,
|
||||
} from '@meshitrack/shared';
|
||||
import { NutritionTargetSchema, NutritionTargetResponseSchema } from '@meshitrack/shared';
|
||||
import { NutritionTargetRepository } from './nutrition-target.repository.js';
|
||||
import { NutritionTargetService } from './nutrition-target.service.js';
|
||||
|
||||
|
|
@ -33,7 +30,9 @@ function toIso(v: string | Date): string {
|
|||
return typeof v === 'string' ? v : v.toISOString();
|
||||
}
|
||||
|
||||
function toNutritionTargetResponse(doc: AnyTargetDoc): z.infer<typeof NutritionTargetResponseSchema> {
|
||||
function toNutritionTargetResponse(
|
||||
doc: AnyTargetDoc,
|
||||
): z.infer<typeof NutritionTargetResponseSchema> {
|
||||
return {
|
||||
_id: toStr(doc._id),
|
||||
userId: doc.userId,
|
||||
|
|
@ -61,7 +60,9 @@ declare module '@fastify/awilix' {
|
|||
export default fp(
|
||||
async (fastify) => {
|
||||
fastify.diContainer.register({
|
||||
nutritionTargetRepository: asClass(NutritionTargetRepository, { lifetime: Lifetime.SINGLETON }),
|
||||
nutritionTargetRepository: asClass(NutritionTargetRepository, {
|
||||
lifetime: Lifetime.SINGLETON,
|
||||
}),
|
||||
nutritionTargetService: asClass(NutritionTargetService, { lifetime: Lifetime.SINGLETON }),
|
||||
});
|
||||
|
||||
|
|
@ -124,11 +125,7 @@ export default fp(
|
|||
handler: async (request, reply) => {
|
||||
const service = fastify.diContainer.resolve('nutritionTargetService');
|
||||
const userId = request.user.keycloakId;
|
||||
const target = await service.setTarget(
|
||||
userId,
|
||||
request.params.householdId,
|
||||
request.body
|
||||
);
|
||||
const target = await service.setTarget(userId, request.params.householdId, request.body);
|
||||
return reply.status(201).send(toNutritionTargetResponse(target as AnyTargetDoc));
|
||||
},
|
||||
});
|
||||
|
|
@ -155,5 +152,5 @@ export default fp(
|
|||
{
|
||||
name: 'nutrition-targets-routes',
|
||||
dependencies: ['auth-plugin'],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,11 +22,7 @@ export class NutritionTargetService {
|
|||
return this.nutritionTargetRepository.findAllByUser(userId, householdId);
|
||||
}
|
||||
|
||||
public async setTarget(
|
||||
userId: string,
|
||||
householdId: string,
|
||||
input: SetNutritionTargetInput
|
||||
) {
|
||||
public async setTarget(userId: string, householdId: string, input: SetNutritionTargetInput) {
|
||||
// Maintain invariant: only one target is active per user per household
|
||||
if (input.isActive !== false) {
|
||||
await this.nutritionTargetRepository.deactivateAllForUser(userId, householdId);
|
||||
|
|
@ -52,20 +48,20 @@ export class NutritionTargetService {
|
|||
|
||||
switch (preset) {
|
||||
case 'loss':
|
||||
proteinPct = 0.40;
|
||||
carbsPct = 0.30;
|
||||
fatPct = 0.30;
|
||||
proteinPct = 0.4;
|
||||
carbsPct = 0.3;
|
||||
fatPct = 0.3;
|
||||
break;
|
||||
case 'gain':
|
||||
proteinPct = 0.25;
|
||||
carbsPct = 0.50;
|
||||
carbsPct = 0.5;
|
||||
fatPct = 0.25;
|
||||
break;
|
||||
case 'maintenance':
|
||||
default:
|
||||
proteinPct = 0.30;
|
||||
carbsPct = 0.40;
|
||||
fatPct = 0.30;
|
||||
proteinPct = 0.3;
|
||||
carbsPct = 0.4;
|
||||
fatPct = 0.3;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@ export class PricesRepository {
|
|||
|
||||
public async createMany(data: CreatePriceRecordData[]) {
|
||||
const records = await PriceRecordModel.insertMany(data);
|
||||
return records.map(r => r.toObject());
|
||||
return records.map((r) => r.toObject());
|
||||
}
|
||||
|
||||
public async findByProduct(
|
||||
householdId: string,
|
||||
productId: string,
|
||||
query: PriceHistoryQueryInput
|
||||
query: PriceHistoryQueryInput,
|
||||
) {
|
||||
const filter: Record<string, unknown> = { householdId, productId };
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export default fp(
|
|||
const record = await service.recordPrice(
|
||||
request.body,
|
||||
request.params.householdId,
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
return reply.status(201).send(toPriceRecordResponse(record));
|
||||
},
|
||||
|
|
@ -114,7 +114,7 @@ export default fp(
|
|||
const records = await service.recordBulkPrices(
|
||||
request.body,
|
||||
request.params.householdId,
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
return reply.status(201).send(records.map(toPriceRecordResponse));
|
||||
},
|
||||
|
|
@ -133,7 +133,7 @@ export default fp(
|
|||
const result = await service.getPriceHistory(
|
||||
request.params.productId,
|
||||
request.params.householdId,
|
||||
request.query
|
||||
request.query,
|
||||
);
|
||||
return reply.send({
|
||||
data: result.data.map(toPriceRecordResponse),
|
||||
|
|
@ -153,7 +153,7 @@ export default fp(
|
|||
const service = fastify.diContainer.resolve('pricesService');
|
||||
const results = await service.compareStores(
|
||||
request.params.productId,
|
||||
request.params.householdId
|
||||
request.params.householdId,
|
||||
);
|
||||
return reply.send({
|
||||
data: results.map((r) => ({
|
||||
|
|
@ -184,5 +184,5 @@ export default fp(
|
|||
{
|
||||
name: 'prices-routes',
|
||||
dependencies: ['auth-plugin'],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ export class PricesService {
|
|||
/**
|
||||
* Validates entity existence, computes pricePerUnit, and persists record
|
||||
*/
|
||||
public async recordPrice(
|
||||
data: CreatePriceRecordInput,
|
||||
householdId: string,
|
||||
userId: string
|
||||
) {
|
||||
public async recordPrice(data: CreatePriceRecordInput, householdId: string, userId: string) {
|
||||
const [product, store] = await Promise.all([
|
||||
this.productsRepository.findById(data.productId, householdId),
|
||||
this.storesRepository.findById(data.storeId, householdId),
|
||||
|
|
@ -64,11 +60,7 @@ export class PricesService {
|
|||
/**
|
||||
* Ingests a list of purchased products in a single transaction
|
||||
*/
|
||||
public async recordBulkPrices(
|
||||
data: BulkPriceRecordInput,
|
||||
householdId: string,
|
||||
userId: string
|
||||
) {
|
||||
public async recordBulkPrices(data: BulkPriceRecordInput, householdId: string, userId: string) {
|
||||
const store = await this.storesRepository.findById(data.storeId, householdId);
|
||||
if (!store) throw new NotFoundError(`Store not found: ${data.storeId}`);
|
||||
|
||||
|
|
@ -109,7 +101,7 @@ export class PricesService {
|
|||
public async getPriceHistory(
|
||||
productId: string,
|
||||
householdId: string,
|
||||
query: PriceHistoryQueryInput
|
||||
query: PriceHistoryQueryInput,
|
||||
) {
|
||||
return this.pricesRepository.findByProduct(householdId, productId, query);
|
||||
}
|
||||
|
|
@ -129,13 +121,16 @@ export class PricesService {
|
|||
public async estimatePrice(
|
||||
productId: string,
|
||||
householdId: string,
|
||||
storeId?: string
|
||||
storeId?: string,
|
||||
): Promise<number | null> {
|
||||
const latest = await this.pricesRepository.getLatestForProduct(householdId, productId, storeId);
|
||||
if (!latest) {
|
||||
// If a specific store was requested but has no history, fall back to the generic latest across all stores
|
||||
if (storeId) {
|
||||
const genericLatest = await this.pricesRepository.getLatestForProduct(householdId, productId);
|
||||
const genericLatest = await this.pricesRepository.getLatestForProduct(
|
||||
householdId,
|
||||
productId,
|
||||
);
|
||||
return genericLatest ? genericLatest.price : null;
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export class ShoppingListsRepository {
|
|||
.sort({ createdAt: -1 })
|
||||
.lean()
|
||||
.exec();
|
||||
return lists.map(l => this.sortItems(l));
|
||||
return lists.map((l) => this.sortItems(l));
|
||||
}
|
||||
|
||||
public async findById(id: string, householdId: string) {
|
||||
|
|
@ -43,19 +43,24 @@ export class ShoppingListsRepository {
|
|||
}
|
||||
|
||||
public async findActiveByHousehold(householdId: string) {
|
||||
const lists = await ShoppingListModel.find({ householdId, status: { $in: ['active', 'shopping'] } })
|
||||
const lists = await ShoppingListModel.find({
|
||||
householdId,
|
||||
status: { $in: ['active', 'shopping'] },
|
||||
})
|
||||
.sort({ updatedAt: -1 })
|
||||
.lean()
|
||||
.exec();
|
||||
return lists.map(l => this.sortItems(l));
|
||||
return lists.map((l) => this.sortItems(l));
|
||||
}
|
||||
|
||||
public async update(id: string, householdId: string, data: UpdateShoppingListInput) {
|
||||
const updated = await ShoppingListModel.findOneAndUpdate(
|
||||
{ _id: id, householdId },
|
||||
{ $set: data },
|
||||
{ new: true }
|
||||
).lean().exec();
|
||||
{ new: true },
|
||||
)
|
||||
.lean()
|
||||
.exec();
|
||||
return this.sortItems(updated);
|
||||
}
|
||||
|
||||
|
|
@ -69,8 +74,10 @@ export class ShoppingListsRepository {
|
|||
const updated = await ShoppingListModel.findOneAndUpdate(
|
||||
{ _id: id, householdId },
|
||||
{ $push: { items: item } },
|
||||
{ new: true }
|
||||
).lean().exec();
|
||||
{ new: true },
|
||||
)
|
||||
.lean()
|
||||
.exec();
|
||||
return this.sortItems(updated);
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +85,7 @@ export class ShoppingListsRepository {
|
|||
id: string,
|
||||
householdId: string,
|
||||
itemId: string,
|
||||
updates: Partial<ShoppingItem>
|
||||
updates: Partial<ShoppingItem>,
|
||||
) {
|
||||
const setUpdates: Record<string, unknown> = {};
|
||||
for (const [key, val] of Object.entries(updates)) {
|
||||
|
|
@ -88,8 +95,10 @@ export class ShoppingListsRepository {
|
|||
const updated = await ShoppingListModel.findOneAndUpdate(
|
||||
{ _id: id, householdId, 'items.id': itemId },
|
||||
{ $set: setUpdates },
|
||||
{ new: true }
|
||||
).lean().exec();
|
||||
{ new: true },
|
||||
)
|
||||
.lean()
|
||||
.exec();
|
||||
return this.sortItems(updated);
|
||||
}
|
||||
|
||||
|
|
@ -97,8 +106,10 @@ export class ShoppingListsRepository {
|
|||
const updated = await ShoppingListModel.findOneAndUpdate(
|
||||
{ _id: id, householdId },
|
||||
{ $pull: { items: { id: itemId } } },
|
||||
{ new: true }
|
||||
).lean().exec();
|
||||
{ new: true },
|
||||
)
|
||||
.lean()
|
||||
.exec();
|
||||
return this.sortItems(updated);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export default fp(
|
|||
const list = await service.create(
|
||||
request.body,
|
||||
request.params.householdId,
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
return reply.status(201).send(serializeList(list));
|
||||
},
|
||||
|
|
@ -147,7 +147,7 @@ export default fp(
|
|||
const list = await service.update(
|
||||
request.params.id,
|
||||
request.params.householdId,
|
||||
request.body
|
||||
request.body,
|
||||
);
|
||||
return reply.send(serializeList(list));
|
||||
},
|
||||
|
|
@ -181,7 +181,7 @@ export default fp(
|
|||
const { list, addedItem } = await service.addItem(
|
||||
request.params.id,
|
||||
request.params.householdId,
|
||||
request.body
|
||||
request.body,
|
||||
);
|
||||
|
||||
// Emit real-time update notification to existing connected viewers
|
||||
|
|
@ -209,11 +209,13 @@ export default fp(
|
|||
request.params.householdId,
|
||||
request.params.itemId,
|
||||
request.body,
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
|
||||
// Broadcast the precise item differential state update to sibling websocket listeners
|
||||
const matchedItem = updatedList.items.find((i: ShoppingItem) => i.id === request.params.itemId);
|
||||
const matchedItem = updatedList.items.find(
|
||||
(i: ShoppingItem) => i.id === request.params.itemId,
|
||||
);
|
||||
if (matchedItem) {
|
||||
broadcastToList(request.params.id, null as any, {
|
||||
type: 'ITEM_UPDATED',
|
||||
|
|
@ -242,7 +244,7 @@ export default fp(
|
|||
const list = await service.removeItem(
|
||||
request.params.id,
|
||||
request.params.householdId,
|
||||
request.params.itemId
|
||||
request.params.itemId,
|
||||
);
|
||||
|
||||
broadcastToList(request.params.id, null as any, {
|
||||
|
|
@ -268,7 +270,7 @@ export default fp(
|
|||
const list = await service.createFromMealPlan(
|
||||
request.params.mealPlanId,
|
||||
request.params.householdId,
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
return reply.status(201).send(serializeList(list));
|
||||
},
|
||||
|
|
@ -286,7 +288,7 @@ export default fp(
|
|||
const results = await service.syncCheckedToPantry(
|
||||
request.params.id,
|
||||
request.params.householdId,
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
return reply.send(results);
|
||||
},
|
||||
|
|
@ -303,7 +305,7 @@ export default fp(
|
|||
const service = fastify.diContainer.resolve('shoppingListsService');
|
||||
const comparison = await service.getStoreComparison(
|
||||
request.params.id,
|
||||
request.params.householdId
|
||||
request.params.householdId,
|
||||
);
|
||||
return reply.send(comparison);
|
||||
},
|
||||
|
|
@ -339,10 +341,12 @@ export default fp(
|
|||
request.params.householdId,
|
||||
payload.itemId,
|
||||
{ checked: payload.checked },
|
||||
request.user.keycloakId
|
||||
request.user.keycloakId,
|
||||
);
|
||||
|
||||
const matched = updatedList.items.find((it: ShoppingItem) => it.id === payload.itemId);
|
||||
const matched = updatedList.items.find(
|
||||
(it: ShoppingItem) => it.id === payload.itemId,
|
||||
);
|
||||
|
||||
// Echo back differential confirmation to everyone else on the floor
|
||||
broadcastToList(listId, socket, {
|
||||
|
|
@ -352,7 +356,7 @@ export default fp(
|
|||
checked: payload.checked,
|
||||
checkedAt: matched?.checkedAt?.toISOString(),
|
||||
checkedBy: matched?.checkedBy,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -370,12 +374,12 @@ export default fp(
|
|||
}
|
||||
request.log.info({ listId }, 'Client severed sync handshake connection');
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
/* v8 ignore stop */
|
||||
},
|
||||
{
|
||||
name: 'shopping-lists-routes',
|
||||
dependencies: ['auth-plugin'],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import type {
|
|||
ShoppingItem,
|
||||
} from '@meshitrack/shared';
|
||||
import { ShoppingListSourceType } from '@meshitrack/shared';
|
||||
import { StorageLocation, ServingUnit } from '@meshitrack/shared';
|
||||
import { StorageLocation, type ServingUnit } from '@meshitrack/shared';
|
||||
import { NotFoundError } from '../../common/errors.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ export class ShoppingListsService {
|
|||
householdId: string,
|
||||
itemId: string,
|
||||
data: UpdateShoppingItemInput,
|
||||
userId: string
|
||||
userId: string,
|
||||
) {
|
||||
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 newList = await this.shoppingListsRepository.create({
|
||||
|
|
@ -242,7 +245,9 @@ export class ShoppingListsService {
|
|||
let addedCount = 0;
|
||||
let pricesLogged = 0;
|
||||
|
||||
const pendingItems = list.items.filter((it: ShoppingItem) => it.checked && !it.addedToPantry && it.productId);
|
||||
const pendingItems = list.items.filter(
|
||||
(it: ShoppingItem) => it.checked && !it.addedToPantry && it.productId,
|
||||
);
|
||||
|
||||
for (const item of pendingItems) {
|
||||
// 1. Promote item to active pantry
|
||||
|
|
@ -257,7 +262,7 @@ export class ShoppingListsService {
|
|||
notes: item.notes || undefined,
|
||||
},
|
||||
householdId,
|
||||
userId
|
||||
userId,
|
||||
);
|
||||
addedCount++;
|
||||
|
||||
|
|
@ -275,7 +280,7 @@ export class ShoppingListsService {
|
|||
currency: 'USD',
|
||||
},
|
||||
householdId,
|
||||
userId
|
||||
userId,
|
||||
);
|
||||
pricesLogged++;
|
||||
}
|
||||
|
|
@ -347,7 +352,9 @@ export class ShoppingListsService {
|
|||
}
|
||||
|
||||
// Sort to surface the cheapest/fullest single store options first
|
||||
singleStoreOptions.sort((a, b) => b.itemsCovered - a.itemsCovered || a.estimatedTotal - b.estimatedTotal);
|
||||
singleStoreOptions.sort(
|
||||
(a, b) => b.itemsCovered - a.itemsCovered || a.estimatedTotal - b.estimatedTotal,
|
||||
);
|
||||
|
||||
return { singleStoreOptions };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const priceRecordSchema = new mongoose.Schema(
|
|||
notes: { type: String },
|
||||
createdBy: { type: String, required: true },
|
||||
},
|
||||
{ timestamps: { createdAt: true, updatedAt: false } }
|
||||
{ timestamps: { createdAt: true, updatedAt: false } },
|
||||
);
|
||||
|
||||
// Performance Indexes for Lookup Speed and Aggregations
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const shoppingItemSchema = new mongoose.Schema(
|
|||
category: { type: String },
|
||||
addedToPantry: { type: Boolean, required: true, default: false },
|
||||
},
|
||||
{ _id: false }
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const shoppingListSourceSchema = new mongoose.Schema(
|
||||
|
|
@ -25,7 +25,7 @@ const shoppingListSourceSchema = new mongoose.Schema(
|
|||
type: { type: String, required: true }, // values from ShoppingListSourceType
|
||||
referenceId: { type: String },
|
||||
},
|
||||
{ _id: false }
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const shoppingListSchema = new mongoose.Schema(
|
||||
|
|
@ -44,7 +44,7 @@ const shoppingListSchema = new mongoose.Schema(
|
|||
completedAt: { type: Date },
|
||||
createdBy: { type: String, required: true },
|
||||
},
|
||||
{ timestamps: true }
|
||||
{ timestamps: true },
|
||||
);
|
||||
|
||||
shoppingListSchema.index({ householdId: 1, status: 1 });
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
ForbiddenError,
|
||||
ConflictError,
|
||||
BadRequestError,
|
||||
} from './errors.js';
|
||||
} from '../../src/common/errors.js';
|
||||
|
||||
describe(AppError.name, () => {
|
||||
it('sets statusCode, error, message, and details', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import config from './configuration.js';
|
||||
import config from '../../src/config/configuration.js';
|
||||
|
||||
describe('configuration', () => {
|
||||
it('exports default config values', () => {
|
||||
34
packages/api/tests/helpers/mock-repository.ts
Normal file
34
packages/api/tests/helpers/mock-repository.ts
Normal 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>;
|
||||
}
|
||||
|
|
@ -63,9 +63,9 @@ vi.mock('jose', () => ({
|
|||
jwtVerify: vi.fn(),
|
||||
}));
|
||||
|
||||
import { buildApp } from './main.js';
|
||||
import { buildApp } from '../src/main.js';
|
||||
import * as jose from 'jose';
|
||||
import { NotFoundError } from './common/errors.js';
|
||||
import { NotFoundError } from '../src/common/errors.js';
|
||||
|
||||
describe('buildApp', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockSave, mockInsertMany, mockAggregate } = vi.hoisted(() => (
|
|||
mockAggregate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/cabinet-event.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/cabinet-event.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -36,7 +36,7 @@ vi.mock('../../schemas/cabinet-event.schema.js', () => {
|
|||
return { CabinetEventModel: FakeModel };
|
||||
});
|
||||
|
||||
import { CabinetEventsRepository } from './cabinet-events.repository.js';
|
||||
import { CabinetEventsRepository } from '../../../src/modules/cabinet-events/cabinet-events.repository.js';
|
||||
|
||||
describe(CabinetEventsRepository.name, () => {
|
||||
let repo: CabinetEventsRepository;
|
||||
|
|
@ -24,7 +24,7 @@ const { mockListEvents, mockGetEventsByItem, mockGetSpendingSummary } = vi.hoist
|
|||
mockGetSpendingSummary: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./cabinet-events.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/cabinet-events/cabinet-events.repository.js', () => ({
|
||||
CabinetEventsRepository: class {
|
||||
create = vi.fn();
|
||||
createMany = vi.fn();
|
||||
|
|
@ -35,7 +35,7 @@ vi.mock('./cabinet-events.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./cabinet-events.service.js', () => ({
|
||||
vi.mock('../../../src/modules/cabinet-events/cabinet-events.service.js', () => ({
|
||||
CabinetEventsService: class {
|
||||
logEvent = vi.fn();
|
||||
logEvents = vi.fn();
|
||||
|
|
@ -46,17 +46,17 @@ vi.mock('./cabinet-events.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import cabinetEventsRoutes from './cabinet-events.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import cabinetEventsRoutes from '../../../src/modules/cabinet-events/cabinet-events.routes.js';
|
||||
|
||||
function makeFakeEvent(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { CabinetEventsService } from './cabinet-events.service.js';
|
||||
import { CabinetEventsService } from '../../../src/modules/cabinet-events/cabinet-events.service.js';
|
||||
|
||||
describe(CabinetEventsService.name, () => {
|
||||
const mockCabinetEventsRepo = {
|
||||
|
|
@ -10,7 +10,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave, mockCountDocument
|
|||
mockAggregate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/cabinet-item.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/cabinet-item.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -54,7 +54,7 @@ vi.mock('../../schemas/cabinet-item.schema.js', () => {
|
|||
return { CabinetItemModel: FakeModel };
|
||||
});
|
||||
|
||||
import { CabinetRepository } from './cabinet.repository.js';
|
||||
import { CabinetRepository } from '../../../src/modules/cabinet/cabinet.repository.js';
|
||||
|
||||
describe(CabinetRepository.name, () => {
|
||||
let repo: CabinetRepository;
|
||||
|
|
@ -45,7 +45,7 @@ const {
|
|||
mockFindActiveByMedicineForFEFO: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./cabinet.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/cabinet/cabinet.repository.js', () => ({
|
||||
CabinetRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -65,7 +65,7 @@ const { mockMedicineFindById } = vi.hoisted(() => ({
|
|||
mockMedicineFindById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../medicines/medicines.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({
|
||||
MedicinesRepository: class {
|
||||
findById = mockMedicineFindById;
|
||||
findByHousehold = vi.fn();
|
||||
|
|
@ -80,7 +80,7 @@ const { mockProductFindById } = vi.hoisted(() => ({
|
|||
mockProductFindById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../medicine-products/medicine-products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({
|
||||
MedicineProductsRepository: class {
|
||||
findById = mockProductFindById;
|
||||
findByMedicine = vi.fn();
|
||||
|
|
@ -91,7 +91,7 @@ vi.mock('../medicine-products/medicine-products.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../medicine-products/medicine-products.service.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-products/medicine-products.service.js', () => ({
|
||||
MedicineProductsService: class {
|
||||
listByMedicine = vi.fn();
|
||||
getById = vi.fn();
|
||||
|
|
@ -101,7 +101,7 @@ vi.mock('../medicine-products/medicine-products.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../medicines/medicines.service.js', () => ({
|
||||
vi.mock('../../../src/modules/medicines/medicines.service.js', () => ({
|
||||
MedicinesService: class {
|
||||
list = vi.fn();
|
||||
getById = vi.fn();
|
||||
|
|
@ -111,7 +111,7 @@ vi.mock('../medicines/medicines.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../cabinet-events/cabinet-events.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/cabinet-events/cabinet-events.repository.js', () => ({
|
||||
CabinetEventsRepository: class {
|
||||
create = vi.fn();
|
||||
createMany = vi.fn();
|
||||
|
|
@ -122,7 +122,7 @@ vi.mock('../cabinet-events/cabinet-events.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../cabinet-events/cabinet-events.service.js', () => ({
|
||||
vi.mock('../../../src/modules/cabinet-events/cabinet-events.service.js', () => ({
|
||||
CabinetEventsService: class {
|
||||
logEvent = vi.fn();
|
||||
logEvents = vi.fn();
|
||||
|
|
@ -133,20 +133,20 @@ vi.mock('../cabinet-events/cabinet-events.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import medicinesRoutes from '../medicines/medicines.routes.js';
|
||||
import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js';
|
||||
import cabinetEventsRoutes from '../cabinet-events/cabinet-events.routes.js';
|
||||
import cabinetRoutes from './cabinet.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js';
|
||||
import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js';
|
||||
import cabinetEventsRoutes from '../../../src/modules/cabinet-events/cabinet-events.routes.js';
|
||||
import cabinetRoutes from '../../../src/modules/cabinet/cabinet.routes.js';
|
||||
|
||||
function makeFakeCabinetItem(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { CabinetService } from './cabinet.service.js';
|
||||
import { CabinetService } from '../../../src/modules/cabinet/cabinet.service.js';
|
||||
import { CabinetEventType, CabinetEventSourceType } from '@meshitrack/shared';
|
||||
|
||||
describe(CabinetService.name, () => {
|
||||
|
|
@ -16,7 +16,7 @@ const {
|
|||
mockFindById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/freshness-rule.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/freshness-rule.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -62,7 +62,7 @@ vi.mock('../../schemas/freshness-rule.schema.js', () => {
|
|||
return { FreshnessRuleModel: FakeModel };
|
||||
});
|
||||
|
||||
import { FreshnessRulesRepository } from './freshness-rules.repository.js';
|
||||
import { FreshnessRulesRepository } from '../../../src/modules/freshness-rules/freshness-rules.repository.js';
|
||||
|
||||
describe(FreshnessRulesRepository.name, () => {
|
||||
let repo: FreshnessRulesRepository;
|
||||
|
|
@ -28,7 +28,7 @@ const { mockFindByHousehold, mockFindById, mockCreate, mockUpdate, mockDelete }
|
|||
}),
|
||||
);
|
||||
|
||||
vi.mock('./freshness-rules.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/freshness-rules/freshness-rules.repository.js', () => ({
|
||||
FreshnessRulesRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -39,17 +39,17 @@ vi.mock('./freshness-rules.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import freshnessRulesRoutes from './freshness-rules.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import freshnessRulesRoutes from '../../../src/modules/freshness-rules/freshness-rules.routes.js';
|
||||
|
||||
function makeRule(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { FreshnessRulesService } from './freshness-rules.service.js';
|
||||
import { NotFoundError, BadRequestError } from '../../common/errors.js';
|
||||
import { FreshnessRulesService } from '../../../src/modules/freshness-rules/freshness-rules.service.js';
|
||||
import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
|
||||
import { FreshnessRuleSource } from '@meshitrack/shared';
|
||||
|
||||
const mockRepo = {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import Fastify from 'fastify';
|
||||
import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
|
||||
import healthRoutes from './health.routes.js';
|
||||
import healthRoutes from '../../../src/modules/health/health.routes.js';
|
||||
|
||||
describe('Health Routes', () => {
|
||||
async function buildTestApp() {
|
||||
|
|
@ -15,7 +15,7 @@ const { _mockLean, mockExec, mockFindById, mockFindOne, mockFindByIdAndUpdate, m
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/household.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/household.schema.js', () => {
|
||||
class MockHouseholdModel {
|
||||
_data: Record<string, unknown>;
|
||||
constructor(data: Record<string, unknown>) {
|
||||
|
|
@ -36,7 +36,7 @@ vi.mock('../../schemas/household.schema.js', () => {
|
|||
return { HouseholdModel: MockHouseholdModel };
|
||||
});
|
||||
|
||||
import { HouseholdsRepository } from './households.repository.js';
|
||||
import { HouseholdsRepository } from '../../../src/modules/households/households.repository.js';
|
||||
|
||||
describe('HouseholdsRepository', () => {
|
||||
let repo: HouseholdsRepository;
|
||||
|
|
@ -41,7 +41,7 @@ const {
|
|||
mockUserUpdate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./households.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/households/households.repository.js', () => ({
|
||||
HouseholdsRepository: class {
|
||||
create = mockCreate;
|
||||
findById = mockFindById;
|
||||
|
|
@ -52,7 +52,7 @@ vi.mock('./households.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = mockFindByKeycloakId;
|
||||
update = mockUserUpdate;
|
||||
|
|
@ -75,10 +75,10 @@ vi.mock('mongoose', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import householdsRoutes from './households.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import householdsRoutes from '../../../src/modules/households/households.routes.js';
|
||||
|
||||
function makeFakeHousehold(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { HouseholdsService } from './households.service.js';
|
||||
import { NotFoundError, ForbiddenError, ConflictError } from '../../common/errors.js';
|
||||
import { HouseholdsService } from '../../../src/modules/households/households.service.js';
|
||||
import { NotFoundError, ForbiddenError, ConflictError } from '../../../src/common/errors.js';
|
||||
import { HouseholdRole } from '@meshitrack/shared';
|
||||
|
||||
// Mock uuid to return deterministic values
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { NoOpLlmProvider } from './no-op-llm.provider.js';
|
||||
import { LLM_PROVIDER } from './llm-provider.interface.js';
|
||||
import { NoOpLlmProvider } from '../../../src/modules/llm/no-op-llm.provider.js';
|
||||
import { LLM_PROVIDER } from '../../../src/modules/llm/llm-provider.interface.js';
|
||||
|
||||
describe(NoOpLlmProvider.name, () => {
|
||||
const provider = new NoOpLlmProvider();
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MealPlanRepository } from './meal-plans.repository.js';
|
||||
import { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
|
||||
import { MealPlanStatus } from '@meshitrack/shared';
|
||||
|
||||
const { mockSave, MockMealPlanModel } = vi.hoisted(() => {
|
||||
|
|
@ -17,11 +17,11 @@ const { mockSave, MockMealPlanModel } = vi.hoisted(() => {
|
|||
return { mockSave, MockMealPlanModel: MockModel };
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/meal-plan.schema.js', () => ({
|
||||
vi.mock('../../../src/schemas/meal-plan.schema.js', () => ({
|
||||
MealPlanModel: MockMealPlanModel,
|
||||
}));
|
||||
|
||||
const { MealPlanModel } = await import('../../schemas/meal-plan.schema.js');
|
||||
const { MealPlanModel } = await import('../../../src/schemas/meal-plan.schema.js');
|
||||
|
||||
function makeChain(result: unknown = null) {
|
||||
return {
|
||||
|
|
@ -37,7 +37,7 @@ const {
|
|||
mockDelete: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./meal-plans.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/meal-plans/meal-plans.repository.js', () => ({
|
||||
MealPlanRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -50,42 +50,42 @@ vi.mock('./meal-plans.repository.js', () => ({
|
|||
}));
|
||||
|
||||
// Mock prerequisite repositories to allow SuggestionEngine/Gap to resolve
|
||||
vi.mock('../recipes/recipes.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/recipes/recipes.repository.js', () => ({
|
||||
RecipesRepository: class {
|
||||
findByHousehold = vi.fn().mockResolvedValue({ data: [] });
|
||||
findById = vi.fn();
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../pantry/pantry.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({
|
||||
PantryRepository: class {
|
||||
findActiveByHousehold = vi.fn().mockResolvedValue([]);
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../nutrition-targets/nutrition-target.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/nutrition-targets/nutrition-target.repository.js', () => ({
|
||||
NutritionTargetRepository: class {
|
||||
findByUser = vi.fn().mockResolvedValue(null);
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../products/products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/products/products.repository.js', () => ({
|
||||
ProductsRepository: class {
|
||||
findByIds = vi.fn().mockResolvedValue([]);
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import mealPlanRoutes from './meal-plans.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import mealPlanRoutes from '../../../src/modules/meal-plans/meal-plans.routes.js';
|
||||
|
||||
const emptyNutrition = { calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0, sugar: 0, sodium: 0, saturatedFat: 0, cholesterol: 0 };
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MealPlanService } from './meal-plans.service.js';
|
||||
import type { MealPlanRepository } from './meal-plans.repository.js';
|
||||
import { MealPlanService } from '../../../src/modules/meal-plans/meal-plans.service.js';
|
||||
import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
|
||||
import { MealPlanStatus, MealType } from '@meshitrack/shared';
|
||||
import { BadRequestError, NotFoundError } from '../../common/errors.js';
|
||||
import { BadRequestError, NotFoundError } from '../../../src/common/errors.js';
|
||||
|
||||
describe(MealPlanService.name, () => {
|
||||
let service: MealPlanService;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ShoppingGapService } from './shopping-gap.service.js';
|
||||
import type { MealPlanRepository } from './meal-plans.repository.js';
|
||||
import type { RecipesRepository } from '../recipes/recipes.repository.js';
|
||||
import type { PantryRepository } from '../pantry/pantry.repository.js';
|
||||
import type { ProductsRepository } from '../products/products.repository.js';
|
||||
import { NotFoundError } from '../../common/errors.js';
|
||||
import { ShoppingGapService } from '../../../src/modules/meal-plans/shopping-gap.service.js';
|
||||
import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
|
||||
import type { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js';
|
||||
import type { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js';
|
||||
import type { ProductsRepository } from '../../../src/modules/products/products.repository.js';
|
||||
import { NotFoundError } from '../../../src/common/errors.js';
|
||||
|
||||
describe(ShoppingGapService.name, () => {
|
||||
let service: ShoppingGapService;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { SuggestionEngineService } from './suggestion-engine.service.js';
|
||||
import type { RecipesRepository } from '../recipes/recipes.repository.js';
|
||||
import type { PantryRepository } from '../pantry/pantry.repository.js';
|
||||
import type { MealPlanRepository } from './meal-plans.repository.js';
|
||||
import type { NutritionTargetRepository } from '../nutrition-targets/nutrition-target.repository.js';
|
||||
import { SuggestionEngineService } from '../../../src/modules/meal-plans/suggestion-engine.service.js';
|
||||
import type { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js';
|
||||
import type { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js';
|
||||
import type { MealPlanRepository } from '../../../src/modules/meal-plans/meal-plans.repository.js';
|
||||
import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';
|
||||
|
||||
describe(SuggestionEngineService.name, () => {
|
||||
let service: SuggestionEngineService;
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockAggregate, mockSave } = vi.hoisted(() => ({
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/medicine-price.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/medicine-price.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -37,7 +37,7 @@ vi.mock('../../schemas/medicine-price.schema.js', () => {
|
|||
return { MedicinePriceModel: FakeModel };
|
||||
});
|
||||
|
||||
import { MedicinePricesRepository } from './medicine-prices.repository.js';
|
||||
import { MedicinePricesRepository } from '../../../src/modules/medicine-prices/medicine-prices.repository.js';
|
||||
|
||||
describe(MedicinePricesRepository.name, () => {
|
||||
let repo: MedicinePricesRepository;
|
||||
|
|
@ -27,7 +27,7 @@ const { mockRecordPrice, mockGetPriceHistory, mockCompareStores, mockGetAnalytic
|
|||
}),
|
||||
);
|
||||
|
||||
vi.mock('./medicine-prices.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-prices/medicine-prices.repository.js', () => ({
|
||||
MedicinePricesRepository: class {
|
||||
create = vi.fn();
|
||||
findByMedicine = vi.fn();
|
||||
|
|
@ -37,7 +37,7 @@ vi.mock('./medicine-prices.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./medicine-prices.service.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-prices/medicine-prices.service.js', () => ({
|
||||
MedicinePricesService: class {
|
||||
recordPrice = mockRecordPrice;
|
||||
getPriceHistory = mockGetPriceHistory;
|
||||
|
|
@ -46,29 +46,29 @@ vi.mock('./medicine-prices.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../medicine-products/medicine-products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({
|
||||
MedicineProductsRepository: class {
|
||||
findById = vi.fn();
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../stores/stores.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/stores/stores.repository.js', () => ({
|
||||
StoresRepository: class {
|
||||
findById = vi.fn();
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import medicinePricesRoutes from './medicine-prices.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import medicinePricesRoutes from '../../../src/modules/medicine-prices/medicine-prices.routes.js';
|
||||
|
||||
function makeFakePriceRecord(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MedicinePricesService } from './medicine-prices.service.js';
|
||||
import { MedicinePricesService } from '../../../src/modules/medicine-prices/medicine-prices.service.js';
|
||||
|
||||
describe(MedicinePricesService.name, () => {
|
||||
const mockPricesRepo = {
|
||||
|
|
@ -30,7 +30,7 @@ const {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/medicine-product.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/medicine-product.schema.js', () => {
|
||||
class MockMedicineProductModel {
|
||||
_data: Record<string, unknown>;
|
||||
constructor(data: Record<string, unknown>) {
|
||||
|
|
@ -52,7 +52,7 @@ vi.mock('../../schemas/medicine-product.schema.js', () => {
|
|||
return { MedicineProductModel: MockMedicineProductModel };
|
||||
});
|
||||
|
||||
import { MedicineProductsRepository } from './medicine-products.repository.js';
|
||||
import { MedicineProductsRepository } from '../../../src/modules/medicine-products/medicine-products.repository.js';
|
||||
|
||||
describe(MedicineProductsRepository.name, () => {
|
||||
let repo: MedicineProductsRepository;
|
||||
|
|
@ -35,7 +35,7 @@ const {
|
|||
mockMedicineFindById: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./medicine-products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({
|
||||
MedicineProductsRepository: class {
|
||||
findByMedicine = mockFindByMedicine;
|
||||
findById = mockFindById;
|
||||
|
|
@ -45,24 +45,24 @@ vi.mock('./medicine-products.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../medicines/medicines.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({
|
||||
MedicinesRepository: class {
|
||||
findById = mockMedicineFindById;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import medicinesRoutes from '../medicines/medicines.routes.js';
|
||||
import medicineProductsRoutes from './medicine-products.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js';
|
||||
import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js';
|
||||
|
||||
function makeFakeProduct(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MedicineProductsService } from './medicine-products.service.js';
|
||||
import { NotFoundError } from '../../common/errors.js';
|
||||
import { MedicineProductsService } from '../../../src/modules/medicine-products/medicine-products.service.js';
|
||||
import { NotFoundError } from '../../../src/common/errors.js';
|
||||
import { DosageUnit, MedicineProductSource } from '@meshitrack/shared';
|
||||
|
||||
describe(MedicineProductsService.name, () => {
|
||||
|
|
@ -27,7 +27,7 @@ const {
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/medicine.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/medicine.schema.js', () => {
|
||||
class MockMedicineModel {
|
||||
_data: Record<string, unknown>;
|
||||
constructor(data: Record<string, unknown>) {
|
||||
|
|
@ -48,7 +48,7 @@ vi.mock('../../schemas/medicine.schema.js', () => {
|
|||
return { MedicineModel: MockMedicineModel };
|
||||
});
|
||||
|
||||
import { MedicinesRepository } from './medicines.repository.js';
|
||||
import { MedicinesRepository } from '../../../src/modules/medicines/medicines.repository.js';
|
||||
|
||||
describe(MedicinesRepository.name, () => {
|
||||
let repo: MedicinesRepository;
|
||||
|
|
@ -37,7 +37,7 @@ const {
|
|||
mockCountByMedicineId: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./medicines.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicines/medicines.repository.js', () => ({
|
||||
MedicinesRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -48,13 +48,13 @@ vi.mock('./medicines.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../medicine-products/medicine-products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-products/medicine-products.repository.js', () => ({
|
||||
MedicineProductsRepository: class {
|
||||
countByMedicineId = mockCountByMedicineId;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../medicine-products/medicine-products.service.js', () => ({
|
||||
vi.mock('../../../src/modules/medicine-products/medicine-products.service.js', () => ({
|
||||
MedicineProductsService: class {
|
||||
listByMedicine = vi.fn();
|
||||
getById = vi.fn();
|
||||
|
|
@ -64,18 +64,18 @@ vi.mock('../medicine-products/medicine-products.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import medicinesRoutes from './medicines.routes.js';
|
||||
import medicineProductsRoutes from '../medicine-products/medicine-products.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import medicinesRoutes from '../../../src/modules/medicines/medicines.routes.js';
|
||||
import medicineProductsRoutes from '../../../src/modules/medicine-products/medicine-products.routes.js';
|
||||
|
||||
function makeFakeMedicine(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MedicinesService } from './medicines.service.js';
|
||||
import { NotFoundError, ConflictError } from '../../common/errors.js';
|
||||
import { MedicinesService } from '../../../src/modules/medicines/medicines.service.js';
|
||||
import { NotFoundError, ConflictError } from '../../../src/common/errors.js';
|
||||
import { MedicineForm, StrengthUnit, MedicineCategory } from '@meshitrack/shared';
|
||||
|
||||
describe(MedicinesService.name, () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { NutritionTargetRepository } from './nutrition-target.repository.js';
|
||||
import { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';
|
||||
|
||||
const { mockSave, MockTargetModel } = vi.hoisted(() => {
|
||||
const mockSave = vi.fn();
|
||||
|
|
@ -16,11 +16,11 @@ const { mockSave, MockTargetModel } = vi.hoisted(() => {
|
|||
return { mockSave, MockTargetModel: MockModel };
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/nutrition-target.schema.js', () => ({
|
||||
vi.mock('../../../src/schemas/nutrition-target.schema.js', () => ({
|
||||
NutritionTargetModel: MockTargetModel,
|
||||
}));
|
||||
|
||||
const { NutritionTargetModel } = await import('../../schemas/nutrition-target.schema.js');
|
||||
const { NutritionTargetModel } = await import('../../../src/schemas/nutrition-target.schema.js');
|
||||
|
||||
function makeChain(result: unknown = null) {
|
||||
return {
|
||||
|
|
@ -30,7 +30,7 @@ const {
|
|||
mockCreate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./nutrition-target.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/nutrition-targets/nutrition-target.repository.js', () => ({
|
||||
NutritionTargetRepository: class {
|
||||
findByUser = mockFindByUser;
|
||||
findAllByUser = mockFindAllByUser;
|
||||
|
|
@ -39,17 +39,17 @@ vi.mock('./nutrition-target.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import nutritionTargetRoutes from './nutrition-target.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import nutritionTargetRoutes from '../../../src/modules/nutrition-targets/nutrition-target.routes.js';
|
||||
|
||||
function makeTarget(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { NutritionTargetService } from './nutrition-target.service.js';
|
||||
import type { NutritionTargetRepository } from './nutrition-target.repository.js';
|
||||
import { NutritionTargetService } from '../../../src/modules/nutrition-targets/nutrition-target.service.js';
|
||||
import type { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';
|
||||
|
||||
describe(NutritionTargetService.name, () => {
|
||||
let service: NutritionTargetService;
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/organizer-fill.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/organizer-fill.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -41,7 +41,7 @@ vi.mock('../../schemas/organizer-fill.schema.js', () => {
|
|||
return { OrganizerFillModel: FakeModel };
|
||||
});
|
||||
|
||||
import { OrganizerRepository } from './organizer.repository.js';
|
||||
import { OrganizerRepository } from '../../../src/modules/organizer/organizer.repository.js';
|
||||
|
||||
describe(OrganizerRepository.name, () => {
|
||||
let repo: OrganizerRepository;
|
||||
|
|
@ -27,7 +27,7 @@ const { mockListFills, mockGetFillById, mockPreview, mockFill, mockUndoFill } =
|
|||
mockUndoFill: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./organizer.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/organizer/organizer.repository.js', () => ({
|
||||
OrganizerRepository: class {
|
||||
create = vi.fn();
|
||||
update = vi.fn();
|
||||
|
|
@ -37,7 +37,7 @@ vi.mock('./organizer.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./organizer.service.js', () => ({
|
||||
vi.mock('../../../src/modules/organizer/organizer.service.js', () => ({
|
||||
OrganizerService: class {
|
||||
listFills = mockListFills;
|
||||
getFillById = mockGetFillById;
|
||||
|
|
@ -47,17 +47,17 @@ vi.mock('./organizer.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import organizerRoutes from './organizer.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import organizerRoutes from '../../../src/modules/organizer/organizer.routes.js';
|
||||
|
||||
function makeFakeFill(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -19,7 +19,7 @@ vi.mock('mongoose', () => {
|
|||
return { default: { startSession: vi.fn().mockResolvedValue(mockSession) } };
|
||||
});
|
||||
|
||||
import { OrganizerService } from './organizer.service.js';
|
||||
import { OrganizerService } from '../../../src/modules/organizer/organizer.service.js';
|
||||
|
||||
describe(OrganizerService.name, () => {
|
||||
const mockOrganizerRepo = {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { FreshnessCalculatorService } from './freshness-calculator.service.js';
|
||||
import { FreshnessCalculatorService } from '../../../src/modules/pantry/freshness-calculator.service.js';
|
||||
import { ItemStatus, FreshnessUrgency, FreshnessSource, StorageLocation } from '@meshitrack/shared';
|
||||
|
||||
describe(FreshnessCalculatorService.name, () => {
|
||||
|
|
@ -20,7 +20,7 @@ const {
|
|||
mockFindByIdAndUpdate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/pantry-item.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/pantry-item.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -75,7 +75,7 @@ vi.mock('../../schemas/pantry-item.schema.js', () => {
|
|||
return { PantryItemModel: FakeModel };
|
||||
});
|
||||
|
||||
import { PantryRepository } from './pantry.repository.js';
|
||||
import { PantryRepository } from '../../../src/modules/pantry/pantry.repository.js';
|
||||
|
||||
describe(PantryRepository.name, () => {
|
||||
let repo: PantryRepository;
|
||||
|
|
@ -54,7 +54,7 @@ const { mockFindApplicableRule } = vi.hoisted(() => ({
|
|||
mockFindApplicableRule: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./pantry.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({
|
||||
PantryRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -71,14 +71,14 @@ vi.mock('./pantry.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../products/products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/products/products.repository.js', () => ({
|
||||
ProductsRepository: class {
|
||||
findById = mockProductFindById;
|
||||
findByIds = vi.fn();
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../freshness-rules/freshness-rules.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/freshness-rules/freshness-rules.repository.js', () => ({
|
||||
FreshnessRulesRepository: class {
|
||||
findApplicableRule = mockFindApplicableRule;
|
||||
findByHousehold = vi.fn();
|
||||
|
|
@ -89,17 +89,17 @@ vi.mock('../freshness-rules/freshness-rules.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import pantryRoutes from './pantry.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import pantryRoutes from '../../../src/modules/pantry/pantry.routes.js';
|
||||
|
||||
const freshness = {
|
||||
estimatedExpiryDate: new Date('2024-02-01').toISOString(),
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { PantryService } from './pantry.service.js';
|
||||
import { NotFoundError, BadRequestError } from '../../common/errors.js';
|
||||
import { PantryService } from '../../../src/modules/pantry/pantry.service.js';
|
||||
import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
|
||||
import { ItemStatus } from '@meshitrack/shared';
|
||||
|
||||
const mockPantryRepo = {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { PricesRepository } from './prices.repository.js';
|
||||
import { PricesRepository } from '../../../src/modules/prices/prices.repository.js';
|
||||
|
||||
const { mockSave, MockPriceRecordModel } = vi.hoisted(() => {
|
||||
const mockSave = vi.fn();
|
||||
|
|
@ -17,11 +17,11 @@ const { mockSave, MockPriceRecordModel } = vi.hoisted(() => {
|
|||
return { mockSave, MockPriceRecordModel: MockModel };
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/price-record.schema.js', () => ({
|
||||
vi.mock('../../../src/schemas/price-record.schema.js', () => ({
|
||||
PriceRecordModel: MockPriceRecordModel,
|
||||
}));
|
||||
|
||||
const { PriceRecordModel } = await import('../../schemas/price-record.schema.js');
|
||||
const { PriceRecordModel } = await import('../../../src/schemas/price-record.schema.js');
|
||||
|
||||
function makeChain(result: unknown = null) {
|
||||
return {
|
||||
|
|
@ -23,7 +23,7 @@ const mockFindByProduct = vi.fn();
|
|||
const mockCompareStores = vi.fn();
|
||||
const mockGetAnalytics = vi.fn();
|
||||
|
||||
vi.mock('./prices.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/prices/prices.repository.js', () => ({
|
||||
PricesRepository: class {
|
||||
create = mockCreate;
|
||||
createMany = mockCreateMany;
|
||||
|
|
@ -33,30 +33,30 @@ vi.mock('./prices.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../products/products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/products/products.repository.js', () => ({
|
||||
ProductsRepository: class {
|
||||
findById = vi.fn().mockResolvedValue({ name: 'Mock Product' });
|
||||
findByIds = vi.fn().mockResolvedValue([{ _id: 'p1', name: 'Mock Product' }]);
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../stores/stores.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/stores/stores.repository.js', () => ({
|
||||
StoresRepository: class {
|
||||
findById = vi.fn().mockResolvedValue({ name: 'Mock Store' });
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import pricesRoutes from './prices.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import pricesRoutes from '../../../src/modules/prices/prices.routes.js';
|
||||
|
||||
describe('prices.routes', () => {
|
||||
let app: any;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { PricesService } from './prices.service.js';
|
||||
import { NotFoundError } from '../../common/errors.js';
|
||||
import { PricesService } from '../../../src/modules/prices/prices.service.js';
|
||||
import { NotFoundError } from '../../../src/common/errors.js';
|
||||
|
||||
describe('PricesService', () => {
|
||||
let service: PricesService;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { BarcodeService } from './barcode.service.js';
|
||||
import { BarcodeService } from '../../../src/modules/products/barcode.service.js';
|
||||
|
||||
vi.mock('undici', () => ({
|
||||
request: vi.fn(),
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { parseCsv, MAX_FILE_SIZE, MAX_ROWS } from './csv-parser.js';
|
||||
import { parseCsv, MAX_FILE_SIZE, MAX_ROWS } from '../../../src/modules/products/csv-parser.js';
|
||||
import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared';
|
||||
|
||||
describe('parseCsv', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ProductsRepository } from './products.repository.js';
|
||||
import { ProductsRepository } from '../../../src/modules/products/products.repository.js';
|
||||
|
||||
const { mockSave, MockProductModel } = vi.hoisted(() => {
|
||||
const mockSave = vi.fn();
|
||||
|
|
@ -16,11 +16,11 @@ const { mockSave, MockProductModel } = vi.hoisted(() => {
|
|||
return { mockSave, MockProductModel };
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/product.schema.js', () => ({
|
||||
vi.mock('../../../src/schemas/product.schema.js', () => ({
|
||||
ProductModel: MockProductModel,
|
||||
}));
|
||||
|
||||
const { ProductModel } = await import('../../schemas/product.schema.js');
|
||||
const { ProductModel } = await import('../../../src/schemas/product.schema.js');
|
||||
|
||||
function makeChain(result: unknown = null) {
|
||||
return {
|
||||
|
|
@ -41,7 +41,7 @@ const {
|
|||
mockBarcodeLookup: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/products/products.repository.js', () => ({
|
||||
ProductsRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -55,23 +55,23 @@ vi.mock('./products.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./barcode.service.js', () => ({
|
||||
vi.mock('../../../src/modules/products/barcode.service.js', () => ({
|
||||
BarcodeService: class {
|
||||
lookup = mockBarcodeLookup;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import productsRoutes from './products.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import productsRoutes from '../../../src/modules/products/products.routes.js';
|
||||
|
||||
function makeFakeProduct(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ProductsService } from './products.service.js';
|
||||
import { NotFoundError, ConflictError } from '../../common/errors.js';
|
||||
import { ProductsService } from '../../../src/modules/products/products.service.js';
|
||||
import { NotFoundError, ConflictError } from '../../../src/common/errors.js';
|
||||
import { ProductCategory, ServingUnit, ProductSource } from '@meshitrack/shared';
|
||||
|
||||
const mockRepo = {
|
||||
|
|
@ -8,7 +8,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockAggregate, mockSave } =
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/purchase.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/purchase.schema.js', () => {
|
||||
const findChain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -36,7 +36,7 @@ vi.mock('../../schemas/purchase.schema.js', () => {
|
|||
return { PurchaseModel: FakeModel };
|
||||
});
|
||||
|
||||
import { PurchasesRepository } from './purchases.repository.js';
|
||||
import { PurchasesRepository } from '../../../src/modules/purchases/purchases.repository.js';
|
||||
|
||||
const makeItem = (overrides = {}) => ({
|
||||
medicineProductId: 'mp-1',
|
||||
|
|
@ -101,7 +101,7 @@ describe(PurchasesRepository.name, () => {
|
|||
|
||||
it('filters by status when provided', async () => {
|
||||
mockFind.mockResolvedValue([]);
|
||||
const { PurchaseModel } = await import('../../schemas/purchase.schema.js');
|
||||
const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js');
|
||||
|
||||
await repo.findByHousehold('hh1', { limit: 20, status: 'ordered' });
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ describe(PurchasesRepository.name, () => {
|
|||
|
||||
it('filters by storeId when provided', async () => {
|
||||
mockFind.mockResolvedValue([]);
|
||||
const { PurchaseModel } = await import('../../schemas/purchase.schema.js');
|
||||
const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js');
|
||||
|
||||
await repo.findByHousehold('hh1', { limit: 20, storeId: 'st-1' });
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ describe(PurchasesRepository.name, () => {
|
|||
it('applies cursor filter when provided', async () => {
|
||||
mockFind.mockResolvedValue([]);
|
||||
const cursor = Buffer.from('p-1').toString('base64');
|
||||
const { PurchaseModel } = await import('../../schemas/purchase.schema.js');
|
||||
const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js');
|
||||
|
||||
await repo.findByHousehold('hh1', { limit: 20, cursor });
|
||||
|
||||
|
|
@ -172,7 +172,7 @@ describe(PurchasesRepository.name, () => {
|
|||
it('includes items in update set when provided', async () => {
|
||||
const updated = { _id: 'p-1' };
|
||||
mockFindOneAndUpdate.mockResolvedValue(updated);
|
||||
const { PurchaseModel } = await import('../../schemas/purchase.schema.js');
|
||||
const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js');
|
||||
const items = [{ name: 'X', quantity: 1, unit: 'tablet', addedToCabinet: false }];
|
||||
|
||||
await repo.update('p-1', 'hh1', { items } as never);
|
||||
|
|
@ -189,7 +189,7 @@ describe(PurchasesRepository.name, () => {
|
|||
it('sets status to in_cabinet and all items addedToCabinet', async () => {
|
||||
const updated = { _id: 'p-1', status: 'in_cabinet' };
|
||||
mockFindOneAndUpdate.mockResolvedValue(updated);
|
||||
const { PurchaseModel } = await import('../../schemas/purchase.schema.js');
|
||||
const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js');
|
||||
|
||||
const result = await repo.receiveAll('p-1', 'hh1');
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ describe(PurchasesRepository.name, () => {
|
|||
it('builds per-index update set and calls findOneAndUpdate', async () => {
|
||||
const updated = { _id: 'p-1', items: [] };
|
||||
mockFindOneAndUpdate.mockResolvedValue(updated);
|
||||
const { PurchaseModel } = await import('../../schemas/purchase.schema.js');
|
||||
const { PurchaseModel } = await import('../../../src/schemas/purchase.schema.js');
|
||||
|
||||
const result = await repo.markItemsAddedToCabinet('p-1', 'hh1', [0, 2]);
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockReceive, mockDelete }
|
|||
}),
|
||||
);
|
||||
|
||||
vi.mock('./purchases.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/purchases/purchases.repository.js', () => ({
|
||||
PurchasesRepository: class {
|
||||
create = vi.fn();
|
||||
findByHousehold = vi.fn();
|
||||
|
|
@ -41,7 +41,7 @@ vi.mock('./purchases.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./purchases.service.js', () => ({
|
||||
vi.mock('../../../src/modules/purchases/purchases.service.js', () => ({
|
||||
PurchasesService: class {
|
||||
list = mockList;
|
||||
getById = mockGetById;
|
||||
|
|
@ -52,17 +52,17 @@ vi.mock('./purchases.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import purchasesRoutes from './purchases.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import purchasesRoutes from '../../../src/modules/purchases/purchases.routes.js';
|
||||
|
||||
function makeFakePurchase(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { PurchasesService } from './purchases.service.js';
|
||||
import { PurchasesService } from '../../../src/modules/purchases/purchases.service.js';
|
||||
|
||||
describe(PurchasesService.name, () => {
|
||||
const mockPurchasesRepo = {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { NutritionCalculatorService } from './nutrition-calculator.service.js';
|
||||
import { NutritionCalculatorService } from '../../../src/modules/recipes/nutrition-calculator.service.js';
|
||||
import { NutritionWarning } from '@meshitrack/shared';
|
||||
|
||||
const service = new NutritionCalculatorService();
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/recipe.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/recipe.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -41,7 +41,7 @@ vi.mock('../../schemas/recipe.schema.js', () => {
|
|||
return { RecipeModel: FakeModel };
|
||||
});
|
||||
|
||||
import { RecipesRepository } from './recipes.repository.js';
|
||||
import { RecipesRepository } from '../../../src/modules/recipes/recipes.repository.js';
|
||||
|
||||
describe(RecipesRepository.name, () => {
|
||||
let repo: RecipesRepository;
|
||||
|
|
@ -40,7 +40,7 @@ const { mockFindByIds } = vi.hoisted(() => ({
|
|||
mockFindByIds: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./recipes.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/recipes/recipes.repository.js', () => ({
|
||||
RecipesRepository: class {
|
||||
findByHousehold = mockFindByHousehold;
|
||||
findById = mockFindById;
|
||||
|
|
@ -52,24 +52,24 @@ vi.mock('./recipes.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../products/products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/products/products.repository.js', () => ({
|
||||
ProductsRepository: class {
|
||||
findByIds = mockFindByIds;
|
||||
findById = vi.fn();
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import recipesRoutes from './recipes.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import recipesRoutes from '../../../src/modules/recipes/recipes.routes.js';
|
||||
|
||||
const nutrition = { calories: 200, protein: 20, carbs: 10, fat: 8 };
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { RecipesService } from './recipes.service.js';
|
||||
import { NotFoundError, BadRequestError } from '../../common/errors.js';
|
||||
import { RecipesService } from '../../../src/modules/recipes/recipes.service.js';
|
||||
import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
|
||||
|
||||
const makeProduct = (id: string, servingUnit = 'g', servingSize = 100) => ({
|
||||
_id: { toString: () => id },
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { toMetric } from './unit-conversion.service.js';
|
||||
import { toMetric } from '../../../src/modules/recipes/unit-conversion.service.js';
|
||||
|
||||
describe('toMetric', () => {
|
||||
describe('metric pass-through', () => {
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/refill-list.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/refill-list.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -33,7 +33,7 @@ vi.mock('../../schemas/refill-list.schema.js', () => {
|
|||
return { RefillListModel: FakeModel };
|
||||
});
|
||||
|
||||
import { RefillsRepository } from './refills.repository.js';
|
||||
import { RefillsRepository } from '../../../src/modules/refills/refills.repository.js';
|
||||
|
||||
describe(RefillsRepository.name, () => {
|
||||
let repo: RefillsRepository;
|
||||
|
|
@ -38,7 +38,7 @@ const {
|
|||
mockGetStoreComparison: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./refills.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/refills/refills.repository.js', () => ({
|
||||
RefillsRepository: class {
|
||||
create = vi.fn();
|
||||
findByHousehold = vi.fn();
|
||||
|
|
@ -49,7 +49,7 @@ vi.mock('./refills.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./refills.service.js', () => ({
|
||||
vi.mock('../../../src/modules/refills/refills.service.js', () => ({
|
||||
RefillsService: class {
|
||||
getAlerts = mockGetAlerts;
|
||||
createList = mockCreateList;
|
||||
|
|
@ -62,17 +62,17 @@ vi.mock('./refills.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import refillsRoutes from './refills.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import refillsRoutes from '../../../src/modules/refills/refills.routes.js';
|
||||
|
||||
function makeFakeRefillList(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { RefillsService } from './refills.service.js';
|
||||
import { RefillsService } from '../../../src/modules/refills/refills.service.js';
|
||||
|
||||
describe(RefillsService.name, () => {
|
||||
const mockRepo = {
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/regimen.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/regimen.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -41,7 +41,7 @@ vi.mock('../../schemas/regimen.schema.js', () => {
|
|||
return { RegimenModel: FakeModel };
|
||||
});
|
||||
|
||||
import { RegimensRepository } from './regimens.repository.js';
|
||||
import { RegimensRepository } from '../../../src/modules/regimens/regimens.repository.js';
|
||||
|
||||
describe(RegimensRepository.name, () => {
|
||||
let repo: RegimensRepository;
|
||||
|
|
@ -29,7 +29,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockDelete, mockCalculate
|
|||
mockCalculateBurnRates: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./regimens.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/regimens/regimens.repository.js', () => ({
|
||||
RegimensRepository: class {
|
||||
findByHousehold = vi.fn();
|
||||
findById = vi.fn();
|
||||
|
|
@ -41,7 +41,7 @@ vi.mock('./regimens.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./regimens.service.js', () => ({
|
||||
vi.mock('../../../src/modules/regimens/regimens.service.js', () => ({
|
||||
RegimensService: class {
|
||||
list = mockList;
|
||||
getById = mockGetById;
|
||||
|
|
@ -53,17 +53,17 @@ vi.mock('./regimens.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import regimensRoutes from './regimens.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import regimensRoutes from '../../../src/modules/regimens/regimens.routes.js';
|
||||
|
||||
function makeFakeRegimen(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { RegimensService } from './regimens.service.js';
|
||||
import { RegimensService } from '../../../src/modules/regimens/regimens.service.js';
|
||||
import { DosageFrequency } from '@meshitrack/shared';
|
||||
|
||||
describe(RegimensService.name, () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ShoppingListsRepository } from './shopping-lists.repository.js';
|
||||
import { ShoppingListsRepository } from '../../../src/modules/shopping-lists/shopping-lists.repository.js';
|
||||
|
||||
const { mockSave, MockShoppingListModel } = vi.hoisted(() => {
|
||||
const mockSave = vi.fn();
|
||||
|
|
@ -16,11 +16,11 @@ const { mockSave, MockShoppingListModel } = vi.hoisted(() => {
|
|||
return { mockSave, MockShoppingListModel: MockModel };
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/shopping-list.schema.js', () => ({
|
||||
vi.mock('../../../src/schemas/shopping-list.schema.js', () => ({
|
||||
ShoppingListModel: MockShoppingListModel,
|
||||
}));
|
||||
|
||||
const { ShoppingListModel } = await import('../../schemas/shopping-list.schema.js');
|
||||
const { ShoppingListModel } = await import('../../../src/schemas/shopping-list.schema.js');
|
||||
|
||||
function makeChain(result: unknown = null) {
|
||||
return {
|
||||
|
|
@ -26,7 +26,7 @@ const mockAddItem = vi.fn();
|
|||
const mockUpdateItem = vi.fn();
|
||||
const mockRemoveItem = vi.fn();
|
||||
|
||||
vi.mock('./shopping-lists.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/shopping-lists/shopping-lists.repository.js', () => ({
|
||||
ShoppingListsRepository: class {
|
||||
list = mockList;
|
||||
findById = mockFindById;
|
||||
|
|
@ -39,25 +39,25 @@ vi.mock('./shopping-lists.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../meal-plans/shopping-gap.service.js', () => ({
|
||||
vi.mock('../../../src/modules/meal-plans/shopping-gap.service.js', () => ({
|
||||
ShoppingGapService: class {
|
||||
calculateGap = vi.fn().mockResolvedValue({ missingItems: [] });
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../pantry/pantry.service.js', () => ({
|
||||
vi.mock('../../../src/modules/pantry/pantry.service.js', () => ({
|
||||
PantryService: class {
|
||||
create = vi.fn().mockResolvedValue({ _id: 'pant1' });
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../products/products.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/products/products.repository.js', () => ({
|
||||
ProductsRepository: class {
|
||||
findById = vi.fn().mockResolvedValue({ category: 'dairy' });
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../prices/prices.service.js', () => ({
|
||||
vi.mock('../../../src/modules/prices/prices.service.js', () => ({
|
||||
PricesService: class {
|
||||
estimatePrice = vi.fn().mockResolvedValue(5.0);
|
||||
recordPrice = vi.fn().mockResolvedValue({});
|
||||
|
|
@ -65,24 +65,24 @@ vi.mock('../prices/prices.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../meal-plans/meal-plans.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/meal-plans/meal-plans.repository.js', () => ({
|
||||
MealPlanRepository: class {
|
||||
findById = vi.fn().mockResolvedValue({ _id: 'mp1', weekStartDate: new Date() });
|
||||
update = vi.fn().mockResolvedValue({});
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import shoppingListsRoutes from './shopping-lists.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import shoppingListsRoutes from '../../../src/modules/shopping-lists/shopping-lists.routes.js';
|
||||
|
||||
describe('shopping-lists.routes', () => {
|
||||
let app: any;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ShoppingListsService } from './shopping-lists.service.js';
|
||||
import { NotFoundError } from '../../common/errors.js';
|
||||
import { ShoppingListsService } from '../../../src/modules/shopping-lists/shopping-lists.service.js';
|
||||
import { NotFoundError } from '../../../src/common/errors.js';
|
||||
|
||||
describe('ShoppingListsService', () => {
|
||||
let service: ShoppingListsService;
|
||||
|
|
@ -7,7 +7,7 @@ const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(()
|
|||
mockSave: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../schemas/store.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/store.schema.js', () => {
|
||||
const chain = () => ({
|
||||
sort: vi.fn().mockReturnThis(),
|
||||
limit: vi.fn().mockReturnThis(),
|
||||
|
|
@ -33,7 +33,7 @@ vi.mock('../../schemas/store.schema.js', () => {
|
|||
return { StoreModel: FakeModel };
|
||||
});
|
||||
|
||||
import { StoresRepository } from './stores.repository.js';
|
||||
import { StoresRepository } from '../../../src/modules/stores/stores.repository.js';
|
||||
|
||||
describe(StoresRepository.name, () => {
|
||||
let repo: StoresRepository;
|
||||
|
|
@ -26,7 +26,7 @@ const { mockList, mockGetById, mockCreate, mockUpdate, mockDeactivate } = vi.hoi
|
|||
mockDeactivate: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./stores.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/stores/stores.repository.js', () => ({
|
||||
StoresRepository: class {
|
||||
findByHousehold = vi.fn();
|
||||
findById = vi.fn();
|
||||
|
|
@ -36,7 +36,7 @@ vi.mock('./stores.repository.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('./stores.service.js', () => ({
|
||||
vi.mock('../../../src/modules/stores/stores.service.js', () => ({
|
||||
StoresService: class {
|
||||
list = mockList;
|
||||
getById = mockGetById;
|
||||
|
|
@ -46,17 +46,17 @@ vi.mock('./stores.service.js', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../users/users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class {
|
||||
findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../plugins/household.plugin.js';
|
||||
import usersRoutes from '../users/users.routes.js';
|
||||
import storesRoutes from './stores.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../../src/plugins/household.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
import storesRoutes from '../../../src/modules/stores/stores.routes.js';
|
||||
|
||||
function makeFakeStore(overrides = {}) {
|
||||
return {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { StoresService } from './stores.service.js';
|
||||
import { StoresService } from '../../../src/modules/stores/stores.service.js';
|
||||
|
||||
describe(StoresService.name, () => {
|
||||
const mockRepo = {
|
||||
|
|
@ -15,7 +15,7 @@ const { mockLean, mockExec, mockFindOne, mockFindById, mockFindOneAndUpdate, moc
|
|||
};
|
||||
});
|
||||
|
||||
vi.mock('../../schemas/user.schema.js', () => {
|
||||
vi.mock('../../../src/schemas/user.schema.js', () => {
|
||||
class MockUserModel {
|
||||
_data: Record<string, unknown>;
|
||||
constructor(data: Record<string, unknown>) {
|
||||
|
|
@ -36,7 +36,7 @@ vi.mock('../../schemas/user.schema.js', () => {
|
|||
return { UserModel: MockUserModel };
|
||||
});
|
||||
|
||||
import { UsersRepository } from './users.repository.js';
|
||||
import { UsersRepository } from '../../../src/modules/users/users.repository.js';
|
||||
|
||||
describe('UsersRepository', () => {
|
||||
let repo: UsersRepository;
|
||||
|
|
@ -24,15 +24,15 @@ const { mockUpsertFromToken, mockFindByKeycloakId } = vi.hoisted(() => ({
|
|||
mockUpsertFromToken: vi.fn(),
|
||||
mockFindByKeycloakId: vi.fn(),
|
||||
}));
|
||||
vi.mock('./users.repository.js', () => ({
|
||||
vi.mock('../../../src/modules/users/users.repository.js', () => ({
|
||||
UsersRepository: class MockUsersRepository {
|
||||
upsertFromToken = mockUpsertFromToken;
|
||||
findByKeycloakId = mockFindByKeycloakId;
|
||||
},
|
||||
}));
|
||||
|
||||
import authPlugin from '../../plugins/auth.plugin.js';
|
||||
import usersRoutes from './users.routes.js';
|
||||
import authPlugin from '../../../src/plugins/auth.plugin.js';
|
||||
import usersRoutes from '../../../src/modules/users/users.routes.js';
|
||||
|
||||
describe('users.routes', () => {
|
||||
async function buildTestApp() {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { UsersService } from './users.service.js';
|
||||
import { NotFoundError } from '../../common/errors.js';
|
||||
import { UsersService } from '../../../src/modules/users/users.service.js';
|
||||
import { NotFoundError } from '../../../src/common/errors.js';
|
||||
|
||||
describe('UsersService', () => {
|
||||
const mockRepo = {
|
||||
|
|
@ -19,7 +19,7 @@ const { mockFindByKeycloakId, mockUpsertFromToken } = vi.hoisted(() => ({
|
|||
mockUpsertFromToken: vi.fn(),
|
||||
}));
|
||||
|
||||
import authPlugin from './auth.plugin.js';
|
||||
import authPlugin from '../../src/plugins/auth.plugin.js';
|
||||
import * as jose from 'jose';
|
||||
|
||||
describe('auth.plugin', () => {
|
||||
|
|
@ -22,8 +22,8 @@ const { mockFindByKeycloakId } = vi.hoisted(() => ({
|
|||
mockFindByKeycloakId: vi.fn(),
|
||||
}));
|
||||
|
||||
import authPlugin from './auth.plugin.js';
|
||||
import householdPlugin from './household.plugin.js';
|
||||
import authPlugin from '../../src/plugins/auth.plugin.js';
|
||||
import householdPlugin from '../../src/plugins/household.plugin.js';
|
||||
|
||||
describe('household.plugin', () => {
|
||||
async function buildApp() {
|
||||
|
|
@ -15,7 +15,7 @@ vi.mock('@fastify/awilix', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
import mongoosePlugin from './mongoose.plugin.js';
|
||||
import mongoosePlugin from '../../src/plugins/mongoose.plugin.js';
|
||||
import mongoose from 'mongoose';
|
||||
import { diContainer } from '@fastify/awilix';
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { HouseholdModel } from './household.schema.js';
|
||||
import { HouseholdModel } from '../../src/schemas/household.schema.js';
|
||||
|
||||
describe('HouseholdModel', () => {
|
||||
it('is a valid mongoose model', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { MedicineProductModel } from './medicine-product.schema.js';
|
||||
import { MedicineProductModel } from '../../src/schemas/medicine-product.schema.js';
|
||||
|
||||
describe(MedicineProductModel.name, () => {
|
||||
it('is a valid mongoose model', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { MedicineModel } from './medicine.schema.js';
|
||||
import { MedicineModel } from '../../src/schemas/medicine.schema.js';
|
||||
|
||||
describe(MedicineModel.name, () => {
|
||||
it('is a valid mongoose model', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { ProductModel } from './product.schema.js';
|
||||
import { ProductModel } from '../../src/schemas/product.schema.js';
|
||||
|
||||
describe(ProductModel.name, () => {
|
||||
it('is a valid mongoose model', () => {
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue