feat(sync): harden the Hiboutik sync, share rate limits across instances, update dependencies (#4)

This commit is contained in:
2026-11-08 13:45:08 +00:00
parent 04960379e8
commit d07c4df52d
62 changed files with 1546 additions and 639 deletions

View File

@@ -10,14 +10,18 @@ import { PrismaService } from './../prisma/prisma.service';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
const hiboutikService = { startSync: jest.fn() };
// Rate limiting counters (ThrottlerDatabaseStorage): the database answers "not blocked" unless a test says otherwise
const notBlocked = { totalHits: 1, timeToExpire: 60, isBlocked: false, timeToBlockExpire: 0 };
const prisma = { $queryRaw: jest.fn(), throttlerRecord: { deleteMany: jest.fn().mockResolvedValue({ count: 0 }) } };
beforeEach(async () => {
hiboutikService.startSync.mockReset();
prisma.$queryRaw.mockReset().mockResolvedValue([notBlocked]);
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(PrismaService)
.useValue({})
.useValue(prisma)
.overrideProvider(HiboutikService)
.useValue(hiboutikService)
.compile();
@@ -36,6 +40,13 @@ describe('AppController (e2e)', () => {
return request(app.getHttpServer()).post('/sync').expect(409);
});
it('POST /sync answers 429 when the shared rate limiting counter is blocked', async () => {
prisma.$queryRaw.mockResolvedValue([{ totalHits: 6, timeToExpire: 30, isBlocked: true, timeToBlockExpire: 30 }]);
await request(app.getHttpServer()).post('/sync').expect(429).expect('Retry-After', '30');
expect(hiboutikService.startSync).not.toHaveBeenCalled();
});
afterEach(async () => {
await app.close();
});