import { describe, it, expect, vi, beforeEach } from 'vitest' const { mockFind, mockSort, mockLimit, mockSelect, mockLean } = vi.hoisted(() => ({ mockFind: vi.fn(), mockSort: vi.fn(), mockLimit: vi.fn(), mockSelect: vi.fn(), mockLean: vi.fn() })) vi.mock('../../../server/models/wikiArticle.js', () => ({ default: { find: mockFind } })) vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../server/utils/auth.js', () => ({ requireAuth: vi.fn() })) import { requireAuth } from '../../../server/utils/auth.js' import handler from '../../../server/api/wiki/recommended.get.js' import { createMockEvent } from '../helpers/createMockEvent.js' // Wire up the chained query builder function setupChain(result = []) { mockLean.mockResolvedValue(result) mockSelect.mockReturnValue({ lean: mockLean }) mockLimit.mockReturnValue({ select: mockSelect }) mockSort.mockReturnValue({ limit: mockLimit }) mockFind.mockReturnValue({ sort: mockSort }) } function makeMember(overrides = {}) { return { _id: 'member-1', craftTags: [], board: { topics: [] }, ...overrides } } function makeArticle(overrides = {}) { return { _id: 'article-1', title: 'Test Article', url: 'https://wiki.example.com/test-article', summary: 'A test article', tags: ['game-design'], collection: 'Guides', publishedAt: new Date('2026-04-01'), ...overrides } } describe('GET /api/wiki/recommended', () => { beforeEach(() => { vi.clearAllMocks() }) it('returns articles matching member tags', async () => { const member = makeMember({ craftTags: ['game-design', 'narrative'] }) requireAuth.mockResolvedValue(member) const articles = [makeArticle({ tags: ['game-design'] })] setupChain(articles) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended' }) const result = await handler(event) expect(result).toEqual(articles) expect(mockFind).toHaveBeenCalledWith( expect.objectContaining({ tags: { $in: expect.arrayContaining(['game-design', 'narrative']) } }) ) }) it('returns empty array when no tag overlap', async () => { const member = makeMember({ craftTags: ['audio'] }) requireAuth.mockResolvedValue(member) setupChain([]) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended' }) const result = await handler(event) expect(result).toEqual([]) }) it('excludes unpublished articles (publishedAt: null)', async () => { const member = makeMember({ craftTags: ['game-design'] }) requireAuth.mockResolvedValue(member) setupChain([]) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended' }) await handler(event) const filter = mockFind.mock.calls[0][0] expect(filter.publishedAt).toEqual({ $ne: null }) }) it('uses default limit of 10', async () => { const member = makeMember({ craftTags: ['game-design'] }) requireAuth.mockResolvedValue(member) setupChain([]) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended' }) await handler(event) expect(mockLimit).toHaveBeenCalledWith(10) }) it('accepts limit query param', async () => { const member = makeMember({ craftTags: ['game-design'] }) requireAuth.mockResolvedValue(member) setupChain([]) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended?limit=5' }) await handler(event) expect(mockLimit).toHaveBeenCalledWith(5) }) it('caps limit at 25', async () => { const member = makeMember({ craftTags: ['game-design'] }) requireAuth.mockResolvedValue(member) setupChain([]) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended?limit=100' }) await handler(event) expect(mockLimit).toHaveBeenCalledWith(25) }) it('returns empty array when member has no tags', async () => { const member = makeMember() requireAuth.mockResolvedValue(member) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended' }) const result = await handler(event) expect(result).toEqual([]) // Should not query the database at all expect(mockFind).not.toHaveBeenCalled() }) it('requires auth (401)', async () => { requireAuth.mockRejectedValue( createError({ statusCode: 401, statusMessage: 'Unauthorized' }) ) const event = createMockEvent({ method: 'GET', path: '/api/wiki/recommended' }) await expect(handler(event)).rejects.toMatchObject({ statusCode: 401 }) }) })