diff --git a/server/scripts/eslint-baseline.json b/server/scripts/eslint-baseline.json index 1fa1011a6..c950e9d80 100644 --- a/server/scripts/eslint-baseline.json +++ b/server/scripts/eslint-baseline.json @@ -1,7 +1,7 @@ { "src": { "@typescript-eslint/no-explicit-any": 90, - "@typescript-eslint/no-non-null-assertion": 297, + "@typescript-eslint/no-non-null-assertion": 265, "@typescript-eslint/no-require-imports": 18, "@typescript-eslint/no-unsafe-function-type": 1, "@typescript-eslint/no-unused-vars": 36, diff --git a/server/scripts/size-baseline.json b/server/scripts/size-baseline.json index 5ac368cf2..8a1c5b298 100644 --- a/server/scripts/size-baseline.json +++ b/server/scripts/size-baseline.json @@ -7,16 +7,16 @@ "src/db/repositories/Users.repository.ts": 1446, "src/nest/atlas/atlas-geo.ts": 1005, "src/nest/atlas/atlas.service.ts": 1182, - "src/nest/auth/auth.service.ts": 1150, + "src/nest/auth/auth.service.ts": 1143, "src/nest/budget/budget.service.ts": 1702, - "src/nest/collections/collections.service.ts": 1488, + "src/nest/collections/collections.service.ts": 1483, "src/nest/doc-sync/doc-sync.service.ts": 1060, "src/nest/doc-sync/providers/synology-drive.client.ts": 1160, "src/nest/journey/journey-domain.service.ts": 1799, - "src/nest/places/places.service.ts": 1601, + "src/nest/places/places.service.ts": 1598, "src/nest/plugins/plugin-runtime.service.ts": 1463, "src/nest/plugins/runtime/plugin-sdk.ts": 1094, "src/nest/reservations/reservations.service.ts": 1239, "src/nest/trips/trips.service.ts": 1009, - "src/nest/vacay/vacay.service.ts": 1440 + "src/nest/vacay/vacay.service.ts": 1433 } diff --git a/server/scripts/strict-baseline.json b/server/scripts/strict-baseline.json index 1c1a8890b..1d0fa82dc 100644 --- a/server/scripts/strict-baseline.json +++ b/server/scripts/strict-baseline.json @@ -58,7 +58,6 @@ "src/mcp/tools.ts": 1, "src/nest-mcp/registry.ts": 2, "src/nest/accommodations/accommodations.service.ts": 2, - "src/nest/admin/admin.controller.ts": 1, "src/nest/atlas/atlas-geo.ts": 2, "src/nest/auth/auth.service.ts": 1, "src/nest/budget/budget.service.ts": 2, @@ -88,8 +87,7 @@ "src/nest/packing/admin-packing-templates.controller.ts": 3, "src/nest/packing/packing.controller.ts": 1, "src/nest/packing/packing.dto.ts": 1, - "src/nest/packing/packing.mcp.ts": 1, - "src/nest/places/places.controller.ts": 2, + "src/nest/places/places.controller.ts": 1, "src/nest/places/places.mcp.ts": 5, "src/nest/platform/discovery-metadata.service.ts": 3, "src/nest/plugins/host/plugin-jobs.ts": 1, @@ -103,7 +101,6 @@ "src/nest/roadtrip/roadtrip-router.service.ts": 12, "src/nest/settings/settings.service.ts": 1, "src/nest/storage/drivers/s3.driver.ts": 1, - "src/nest/trips/trips.controller.ts": 2, - "src/nest/trips/trips.mcp.ts": 1, + "src/nest/trips/trips.controller.ts": 1, "src/nest/trips/trips.service.ts": 1 } diff --git a/server/src/nest/admin/admin.service.ts b/server/src/nest/admin/admin.service.ts index a01cd8404..5db865587 100644 --- a/server/src/nest/admin/admin.service.ts +++ b/server/src/nest/admin/admin.service.ts @@ -172,7 +172,7 @@ export class AdminService { } const pwCheck = validatePassword(password); - if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason); + if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason ?? ''); if (data.role && !['user', 'admin'].includes(data.role)) { throw new DomainError(400, 'Invalid role'); @@ -236,7 +236,7 @@ export class AdminService { if (password) { const pwCheck = validatePassword(password); - if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason); + if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason ?? ''); } const passwordHash = password ? bcrypt.hashSync(password, BCRYPT_COST) : null; diff --git a/server/src/nest/auth/auth.service.ts b/server/src/nest/auth/auth.service.ts index cabb5447c..98dbd33b7 100644 --- a/server/src/nest/auth/auth.service.ts +++ b/server/src/nest/auth/auth.service.ts @@ -459,7 +459,7 @@ export class AuthService { } const pwCheck = validatePassword(password); - if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason); + if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason ?? ''); if (!EMAIL_REGEX.test(email)) { throw new DomainError(400, 'Invalid email format'); @@ -638,7 +638,7 @@ export class AuthService { if (!new_password) throw new DomainError(400, 'New password is required'); const pwCheck = validatePassword(new_password); - if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason); + if (!pwCheck.ok) throw new DomainError(400, pwCheck.reason ?? ''); const user = await this.usersRepo.getPasswordHashAndVersion(userId); if (!user || !bcrypt.compareSync(current_password, user.password_hash)) { diff --git a/server/src/nest/vacay/vacay.mcp.ts b/server/src/nest/vacay/vacay.mcp.ts index e5e01c28b..ba01311ed 100644 --- a/server/src/nest/vacay/vacay.mcp.ts +++ b/server/src/nest/vacay/vacay.mcp.ts @@ -655,10 +655,11 @@ export class VacayMcp { text: JSON.stringify(data, null, 2), }], }); - if (!plan.holidays_enabled || !plan.holidays_region) return json([]); + const region = plan.holidays_region; + if (!plan.holidays_enabled || !region) return json([]); const yearStr = Array.isArray(year) ? year[0] : year; // A failed fetch reads as no holidays here, as it always has. - const result = await catchDomainError(() => this.vacay.getHolidays(yearStr, plan.holidays_region)); + const result = await catchDomainError(() => this.vacay.getHolidays(yearStr, region)); return json(result instanceof DomainError ? [] : (result.data ?? [])); } } diff --git a/server/tests/unit/mcp/tool-input-schemas.test.ts b/server/tests/unit/mcp/tool-input-schemas.test.ts index e4d0b4130..6eec3b1da 100644 --- a/server/tests/unit/mcp/tool-input-schemas.test.ts +++ b/server/tests/unit/mcp/tool-input-schemas.test.ts @@ -40,8 +40,10 @@ function methodNames(ctor: ClassRef): string[] { async function toolSchemas(): Promise>> { const out = new Map>(); - for (const file of mcpFiles(NEST_ROOT).sort()) { - const mod = (await import(file)) as Record; + const files = mcpFiles(NEST_ROOT).sort(); + const modules = (await Promise.all(files.map((file) => import(file)))) as Record[]; + for (const [index, mod] of modules.entries()) { + const file = files[index]; for (const exported of Object.values(mod)) { if (!isMcpController(exported)) continue; for (const name of methodNames(exported)) { diff --git a/server/tests/unit/nest/accommodations.event-parity.test.ts b/server/tests/unit/nest/accommodations.event-parity.test.ts index c2c49a52d..c43b5bae3 100644 --- a/server/tests/unit/nest/accommodations.event-parity.test.ts +++ b/server/tests/unit/nest/accommodations.event-parity.test.ts @@ -182,8 +182,10 @@ describe('stay use cases: the shared gate and checks', () => { it('ACC-UC-006: an edit or a cancellation of a stay that is not on the trip is a 404', async () => { const svc = makeService({ broadcast: vi.fn() }, { getAccommodation: vi.fn().mockResolvedValue(undefined) }); - for (const run of [() => svc.updateStay(5, 9, {}, writer()), () => svc.deleteStay(5, 9, writer())]) { - const err = (await run().catch((e: unknown) => e)) as DomainError; + const errors = (await Promise.all( + [svc.updateStay(5, 9, {}, writer()), svc.deleteStay(5, 9, writer())].map((p) => p.catch((e: unknown) => e)), + )) as DomainError[]; + for (const err of errors) { expect(err.toBody()).toEqual({ error: 'Accommodation not found' }); expect(err.mcpMessage).toBe('Accommodation not found.'); } diff --git a/server/tests/unit/nest/packing-writes.service.test.ts b/server/tests/unit/nest/packing-writes.service.test.ts index eff6ce641..0e9e3411e 100644 --- a/server/tests/unit/nest/packing-writes.service.test.ts +++ b/server/tests/unit/nest/packing-writes.service.test.ts @@ -149,11 +149,14 @@ describe('packing item use cases: refusals', () => { it("PACK-UC-002: a missing item is REST's 404 and MCP's own sentence, for update and delete", async () => { const s = setup({ updateItem: vi.fn().mockResolvedValue(null), deleteItem: vi.fn().mockResolvedValue(null) }); - for (const call of [ + const calls = [ (w: never) => s.writes.updateItem(5, 3, { name: 'x' }, ['name'], w), (w: never) => s.writes.deleteItem(5, 3, w), - ]) { - const err = (await s.run('mcp', call as never).catch((e: unknown) => e)) as DomainError; + ]; + const errors = (await Promise.all( + calls.map((call) => s.run('mcp', call as never).catch((e: unknown) => e)), + )) as DomainError[]; + for (const err of errors) { expect(err.toBody()).toEqual({ error: 'Item not found' }); expect(mcpText(err)).toBe('Packing item not found.'); } @@ -164,11 +167,14 @@ describe('packing item use cases: refusals', () => { createItem: vi.fn().mockResolvedValue({ invalidBag: true }), updateItem: vi.fn().mockResolvedValue({ invalidBag: true }), }); - for (const call of [ + const calls = [ (w: never) => s.writes.createItem(5, { name: 'x', bag_id: 99 }, w), (w: never) => s.writes.updateItem(5, 3, { bag_id: 99 }, ['bag_id'], w), - ]) { - const err = (await s.run('mcp', call as never).catch((e: unknown) => e)) as DomainError; + ]; + const errors = (await Promise.all( + calls.map((call) => s.run('mcp', call as never).catch((e: unknown) => e)), + )) as DomainError[]; + for (const err of errors) { expect(err.getStatus()).toBe(400); expect(err.toBody()).toEqual({ error: 'Bag not found' }); expect(mcpText(err)).toBe('Bag not found.');