import { describe, test, expect, vi, beforeEach } from 'vitest' import type { Response, NextFunction } from 'express' import type { ExtendedRequest } from '@/types' import findPage from '@/frame/lib/find-page' import resolveRecommended from '../middleware/resolve-recommended' // Mock the findPage function vi.mock('@/frame/lib/find-page', () => ({ default: vi.fn(), })) // Mock the renderContent function vi.mock('@/content-render/index', () => ({ renderContent: vi.fn((content) => `
${content}
`), })) describe('resolveRecommended middleware', () => { const mockFindPage = vi.mocked(findPage) const createMockRequest = (pageData: any = {}, contextData: any = {}): ExtendedRequest => ({ context: { page: pageData, pages: { '/test-article': { title: 'Test Article', intro: 'Test intro', relativePath: 'test/article.md', }, }, redirects: {}, currentVersion: 'free-pro-team@latest', currentLanguage: 'en', ...contextData, }, }) as ExtendedRequest const mockRes = {} as Response const mockNext = vi.fn() as NextFunction beforeEach(() => { vi.clearAllMocks() }) test('should call next when no context', async () => { const req = {} as ExtendedRequest await resolveRecommended(req, mockRes, mockNext) expect(mockNext).toHaveBeenCalled() expect(mockFindPage).not.toHaveBeenCalled() }) test('should call next when no page', async () => { const req = { context: {} } as ExtendedRequest await resolveRecommended(req, mockRes, mockNext) expect(mockNext).toHaveBeenCalled() expect(mockFindPage).not.toHaveBeenCalled() }) test('should call next when no pages collection', async () => { const req = createMockRequest({ rawRecommended: ['/test-article'] }, { pages: undefined }) await resolveRecommended(req, mockRes, mockNext) expect(mockNext).toHaveBeenCalled() expect(mockFindPage).not.toHaveBeenCalled() }) test('should call next when no rawRecommended', async () => { const req = createMockRequest() await resolveRecommended(req, mockRes, mockNext) expect(mockNext).toHaveBeenCalled() expect(mockFindPage).not.toHaveBeenCalled() }) test('should resolve recommended articles when they exist', async () => { const testPage: PartialTest intro
', href: '/copilot/tutorials/article', category: ['copilot', 'tutorials'], }, ]) expect(mockNext).toHaveBeenCalled() }) test('should not resolve spotlight articles when there are recommended articles', async () => { const testPage: PartialTest intro
', href: '/copilot/tutorials/article', category: ['copilot', 'tutorials'], }, ]) expect(mockNext).toHaveBeenCalled() }) test('should handle articles not found', async () => { mockFindPage.mockReturnValue(undefined) const req = createMockRequest({ rawRecommended: ['/nonexistent-article'] }) await resolveRecommended(req, mockRes, mockNext) expect(mockFindPage).toHaveBeenCalledWith( '/en/nonexistent-article', req.context!.pages, req.context!.redirects, ) expect((req.context!.page as any).recommended).toEqual([]) expect(mockNext).toHaveBeenCalled() }) test('should handle errors gracefully', async () => { mockFindPage.mockImplementation(() => { throw new Error('Test error') }) const req = createMockRequest({ rawRecommended: ['/error-article'] }) await resolveRecommended(req, mockRes, mockNext) expect((req.context!.page as any).recommended).toEqual([]) expect(mockNext).toHaveBeenCalled() }) test('should handle mixed valid and invalid articles', async () => { const testPage: PartialValid intro
', href: '/test/valid', category: ['test'], }, ]) expect(mockNext).toHaveBeenCalled() }) test('should try page-relative path when content-relative fails', async () => { const testPage: PartialRelative intro
', href: '/copilot/relative-article', // Updated to clean path category: ['copilot'], }, ]) expect(mockNext).toHaveBeenCalled() }) test('returns paths without language or version prefixes', async () => { const testPage: PartialTutorial intro
', href: '/copilot/tutorials/tutorial-page', category: ['copilot', 'tutorials', 'tutorial-page'], }, ]) expect(mockNext).toHaveBeenCalled() }) test('should filter out articles not available in current version', async () => { // Create a test page that is only available in fpt, not ghec const fptOnlyPage: Partial