mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
82 lines
3.6 KiB
TypeScript
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').mockReturnValue(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);
|
|
});
|
|
});
|