Files
TREK/server/tests/unit/nest/place-enrichment.controller.test.ts
Maurice 724c88c201 style: format server and shared with prettier
A mechanical prettier --write over server/src, server/tests, server/scripts
and shared/src with the workspaces' .prettierrc; nothing but layout and
import order changes, and every file keeps the same set of imports. shared
is formatted throughout and its format baseline is empty. The server
baseline keeps the 35 files that Prettier would grow past their lint:size
limit (they need a split first, which is not a formatting change), and the
generated entity files stay as their generator writes them.
2026-10-10 21:23:37 +02:00

82 lines
3.6 KiB
TypeScript

/**
* Unit tests for PlaceEnrichmentController — ENRICH-040 through ENRICH-046.
* Service and rate limiter are stubbed; this covers the rate-limit gate, the
* pass-through and the deliberate swallowing of provider failures.
*/
import { RateLimitService } from '../../../src/nest/common/rate-limit.service';
import { PlaceEnrichmentController } from '../../../src/nest/place-enrichment/place-enrichment.controller';
import type { PlaceEnrichmentService } from '../../../src/nest/place-enrichment/place-enrichment.service';
import type { User } from '../../../src/types';
import { HttpException } from '@nestjs/common';
import type { Request } from 'express';
import { describe, it, expect, vi } from 'vitest';
const USER = { id: 7 } as User;
const REQ = {} as Request;
const BODY = { lat: 50.9, lng: 6.96, name: 'Museum Ludwig', placeId: 'ChIJmuseum' };
const RESULT = { photos: [], description: null, facts: [] };
function make(over: Partial<PlaceEnrichmentService> = {}, rl = new RateLimitService()) {
const service = { enrich: vi.fn(async () => RESULT), ...over } as unknown as PlaceEnrichmentService;
return { controller: new PlaceEnrichmentController(service, rl), service, rl };
}
describe('PlaceEnrichmentController', () => {
it('ENRICH-040: forwards the validated body with the signed-in user id', async () => {
const { controller, service } = make();
await expect(controller.enrich(USER, REQ, BODY)).resolves.toEqual(RESULT);
expect(service.enrich).toHaveBeenCalledWith(7, BODY);
});
it('ENRICH-041: answers 429 once a user passes sixty calls a minute', async () => {
const { controller, service } = make();
for (let i = 0; i < 60; i++) await controller.enrich(USER, REQ, BODY);
await expect(controller.enrich(USER, REQ, BODY)).rejects.toBeInstanceOf(HttpException);
expect(service.enrich).toHaveBeenCalledTimes(60);
});
it('ENRICH-042: carries the 429 status and message', async () => {
const rl = new RateLimitService();
const { controller } = make({}, rl);
vi.spyOn(rl, 'check').mockResolvedValue(false);
const err = await controller.enrich(USER, REQ, BODY).catch((e: HttpException) => e);
expect((err as HttpException).getStatus()).toBe(429);
expect((err as HttpException).getResponse()).toEqual({ error: 'Too many requests' });
});
it('ENRICH-043: buckets per user, so one busy account cannot lock out another', async () => {
const { controller } = make();
for (let i = 0; i < 60; i++) await controller.enrich(USER, REQ, BODY);
await expect(controller.enrich(USER, REQ, BODY)).rejects.toBeInstanceOf(HttpException);
await expect(controller.enrich({ id: 8 } as User, REQ, BODY)).resolves.toEqual(RESULT);
});
it('ENRICH-044: turns a provider failure into an empty result, not a 500', async () => {
// The column is an aid, not a step — adding a place must not break because
// Wikimedia is down.
const { controller } = make({
enrich: vi.fn(async () => {
throw new Error('commons down');
}),
});
const err = vi.spyOn(console, 'error').mockImplementation(() => {});
await expect(controller.enrich(USER, REQ, BODY)).resolves.toEqual({ photos: [], description: null, facts: [] });
expect(err).toHaveBeenCalled();
err.mockRestore();
});
it('ENRICH-045: passes the disabled envelope through untouched', async () => {
const disabled = { photos: [], description: null, facts: [], disabled: true };
const { controller } = make({ enrich: vi.fn(async () => disabled) });
await expect(controller.enrich(USER, REQ, BODY)).resolves.toEqual(disabled);
});
});