import {describe, it, expect, vi, beforeEach} from 'vitest' import hookFactory from '../index.js' // Helpers pour construire les mocks Directus --------------------------------- /** * Construit un mock de database (knex) chaînable. * Chaque méthode retourne `this` sauf `.first()` qui résout la valeur fournie. */ const makeDatabase = (resolvedRow) => { const db = vi.fn(() => db) db.select = vi.fn(() => db) db.where = vi.fn(() => db) db.first = vi.fn(() => Promise.resolve(resolvedRow)) return db } /** * Construit un mock de VersionsService. * `compareResult` est la valeur renvoyée par `compare()`. */ const makeVersionsService = (compareResult) => ({ VersionsService: class { compare() { return Promise.resolve(compareResult) } }, }) /** * Enregistre les callbacks du hook et les expose par événement. * Permet d'appeler directement `callbacks['items.create'](input, meta, ctx)`. */ const mountHook = (services) => { const callbacks = {} const filter = (event, handler) => { callbacks[event] = handler } hookFactory({filter}, {services}) return callbacks } // Dates de référence --------------------------------------------------------- const RECENT_DATE = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString() // hier const OLD_DATE = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString() // il y a 4 jours const SCHEMA = {} // Tests ---------------------------------------------------------------------- describe('disallow-votes — items.create', () => { let callbacks beforeEach(() => { callbacks = mountHook(makeVersionsService({outdated: false})) }) it('lève une erreur si versionId est absent', async () => { const db = makeDatabase(null) await expect( callbacks['items.create']({}, {collection: 'votes'}, {database: db, schema: SCHEMA}) ).rejects.toThrow('identifiant de la version est manquant') }) it('lève une erreur si la version est introuvable', async () => { const db = makeDatabase(undefined) await expect( callbacks['items.create']( {content_version_id: 'abc'}, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) ).rejects.toThrow('Version non trouvée') }) it('lève une erreur si la version a plus de 3 jours', async () => { const db = makeDatabase({date_created: OLD_DATE}) await expect( callbacks['items.create']( {content_version_id: 'abc'}, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) ).rejects.toThrow('3 jours') }) it('lève une erreur si la version est obsolète', async () => { const db = makeDatabase({date_created: RECENT_DATE}) callbacks = mountHook(makeVersionsService({outdated: true})) await expect( callbacks['items.create']( {content_version_id: 'abc'}, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) ).rejects.toThrow('version obsolète') }) it('autorise le vote si la version est récente et valide', async () => { const db = makeDatabase({date_created: RECENT_DATE}) const input = {content_version_id: 'abc'} const result = await callbacks['items.create']( input, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) expect(result).toBe(input) }) it('autorise le vote si compare() échoue (non bloquant)', async () => { const db = makeDatabase({date_created: RECENT_DATE}) const servicesWithBrokenCompare = { VersionsService: class { compare() { return Promise.reject(new Error('service indisponible')) } }, } callbacks = mountHook(servicesWithBrokenCompare) const input = {content_version_id: 'abc'} const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) const result = await callbacks['items.create']( input, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) expect(result).toBe(input) expect(warnSpy).toHaveBeenCalled() warnSpy.mockRestore() }) it('ignore les collections qui ne sont pas "votes"', async () => { const db = makeDatabase(null) const input = {content_version_id: 'abc'} const result = await callbacks['items.create']( input, {collection: 'articles'}, {database: db, schema: SCHEMA}, ) expect(result).toBe(input) expect(db).not.toHaveBeenCalled() }) }) describe('disallow-votes — items.update', () => { it('applique la même logique que items.create', async () => { const db = makeDatabase({date_created: OLD_DATE}) const callbacks = mountHook(makeVersionsService({outdated: false})) await expect( callbacks['items.update']( {content_version_id: 'abc'}, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) ).rejects.toThrow('3 jours') }) }) describe('disallow-votes — items.delete', () => { it('lève une erreur si voteId est absent', async () => { const db = makeDatabase(null) const callbacks = mountHook(makeVersionsService({outdated: false})) await expect( callbacks['items.delete']([], {collection: 'votes'}, {database: db, schema: SCHEMA}) ).rejects.toThrow('identifiant du vote est manquant') }) it('lève une erreur si le vote est introuvable', async () => { const db = makeDatabase(undefined) const callbacks = mountHook(makeVersionsService({outdated: false})) await expect( callbacks['items.delete'](['vote-1'], {collection: 'votes'}, {database: db, schema: SCHEMA}) ).rejects.toThrow('Vote non trouvé') }) it('lève une erreur si la version associée au vote a plus de 3 jours', async () => { let callCount = 0 const db = vi.fn(() => db) db.select = vi.fn(() => db) db.where = vi.fn(() => db) db.first = vi.fn(() => { callCount++ // Premier appel : lookup du vote → retourne content_version_id // Deuxième appel : lookup de la version → retourne date ancienne return Promise.resolve( callCount === 1 ? {content_version_id: 'v-abc'} : {date_created: OLD_DATE} ) }) const callbacks = mountHook(makeVersionsService({outdated: false})) await expect( callbacks['items.delete'](['vote-1'], {collection: 'votes'}, {database: db, schema: SCHEMA}) ).rejects.toThrow('3 jours') }) it('autorise la suppression si la version est encore valide', async () => { let callCount = 0 const db = vi.fn(() => db) db.select = vi.fn(() => db) db.where = vi.fn(() => db) db.first = vi.fn(() => { callCount++ return Promise.resolve( callCount === 1 ? {content_version_id: 'v-abc'} : {date_created: RECENT_DATE} ) }) const callbacks = mountHook(makeVersionsService({outdated: false})) const input = ['vote-1'] const result = await callbacks['items.delete']( input, {collection: 'votes'}, {database: db, schema: SCHEMA}, ) expect(result).toBe(input) }) })