import { UserAiController } from './user-ai.controller'; describe('UserAiController', () => { let controller: UserAiController; let mockService: any; beforeEach(() => { mockService = { getProfile: jest.fn(), saveProfile: jest.fn(), getSettings: jest.fn(), updateSettings: jest.fn(), listCredentials: jest.fn(), createCredential: jest.fn(), updateCredential: jest.fn(), deleteCredential: jest.fn(), testCredential: jest.fn(), createAnalysisJob: jest.fn(), cancelJob: jest.fn(), getJob: jest.fn(), listJobs: jest.fn(), publishQuiz: jest.fn(), publishFlashcard: jest.fn(), getAnalysis: jest.fn(), listAnalyses: jest.fn(), listRecommendations: jest.fn(), listWeakPoints: jest.fn(), getQuiz: jest.fn(), getQuizQuestions: jest.fn(), listQuizzes: jest.fn(), triggerReanalysis: jest.fn(), listNotifications: jest.fn(), submitArtifactFeedback: jest.fn(), submitFeedback: jest.fn(), getFlashcard: jest.fn(), listFlashcards: jest.fn(), }; controller = new UserAiController(mockService); }); const req = (id = 'u1') => ({ user: { id } }) as any; // ═══════════════════════════════════════════════════════════════════ // Profile // ═══════════════════════════════════════════════════════════════════ describe('Profile', () => { it('GET profile returns profile or empty object', async () => { mockService.getProfile.mockResolvedValue({ learningGoal: 'exam' }); const result = await controller.getProfile(req()); expect(result).toEqual({ learningGoal: 'exam' }); expect(mockService.getProfile).toHaveBeenCalledWith('u1'); }); it('GET profile returns {} when service returns null', async () => { mockService.getProfile.mockResolvedValue(null); const result = await controller.getProfile(req()); expect(result).toEqual({}); }); it('PUT profile delegates to service', async () => { const dto = { learningGoal: 'exam' }; mockService.saveProfile.mockResolvedValue({ id: 'p1' }); const result = await controller.saveProfile(req(), dto as any); expect(mockService.saveProfile).toHaveBeenCalledWith('u1', dto); expect(result).toEqual({ id: 'p1' }); }); }); // ═══════════════════════════════════════════════════════════════════ // Settings // ═══════════════════════════════════════════════════════════════════ describe('Settings', () => { it('GET settings', async () => { mockService.getSettings.mockResolvedValue({ allowAiAnalysis: true }); const result = await controller.getSettings(req()); expect(mockService.getSettings).toHaveBeenCalledWith('u1'); expect(result).toEqual({ allowAiAnalysis: true }); }); it('PUT settings', async () => { const dto = { allowAiAnalysis: false }; mockService.updateSettings.mockResolvedValue({ id: 's1' }); const result = await controller.updateSettings(req(), dto as any); expect(mockService.updateSettings).toHaveBeenCalledWith('u1', dto); expect(result).toEqual({ id: 's1' }); }); }); // ═══════════════════════════════════════════════════════════════════ // Model Credentials // ═══════════════════════════════════════════════════════════════════ describe('Model Credentials', () => { it('GET model-credentials lists credentials', async () => { mockService.listCredentials.mockResolvedValue([{ id: 'c1' }]); const result = await controller.listCredentials(req()); expect(mockService.listCredentials).toHaveBeenCalledWith('u1'); expect(result).toEqual([{ id: 'c1' }]); }); it('POST model-credentials creates credential', async () => { const dto = { name: 'MyKey', apiKey: 'sk-xxx' }; mockService.createCredential.mockResolvedValue({ id: 'c1' }); const result = await controller.createCredential(req(), dto as any); expect(mockService.createCredential).toHaveBeenCalledWith('u1', dto); expect(result).toEqual({ id: 'c1' }); }); it('PUT model-credentials/:id updates credential', async () => { const dto = { name: 'Updated' }; mockService.updateCredential.mockResolvedValue({ id: 'c1' }); const result = await controller.updateCredential(req(), 'c1', dto as any); expect(mockService.updateCredential).toHaveBeenCalledWith('u1', 'c1', dto); expect(result).toEqual({ id: 'c1' }); }); it('DELETE model-credentials/:id returns { ok: true }', async () => { mockService.deleteCredential.mockResolvedValue(undefined); const result = await controller.deleteCredential(req(), 'c1'); expect(mockService.deleteCredential).toHaveBeenCalledWith('u1', 'c1'); expect(result).toEqual({ ok: true }); }); it('POST model-credentials/:id/test tests credential', async () => { mockService.testCredential.mockResolvedValue({ success: true }); const result = await controller.testCredential(req(), 'c1'); expect(mockService.testCredential).toHaveBeenCalledWith('u1', 'c1'); expect(result).toEqual({ success: true }); }); }); // ═══════════════════════════════════════════════════════════════════ // Analysis Jobs // ═══════════════════════════════════════════════════════════════════ describe('Analysis Jobs', () => { it('POST jobs creates analysis job', async () => { const dto = { jobType: 'quiz_generation', targetType: 'knowledge_base', targetId: 'kb1' }; mockService.createAnalysisJob.mockResolvedValue({ jobId: 'j1' }); const result = await controller.createAnalysisJob(req(), dto as any); expect(mockService.createAnalysisJob).toHaveBeenCalledWith('u1', dto); expect(result).toEqual({ jobId: 'j1' }); }); it('POST jobs/:jobId/cancel cancels job', async () => { mockService.cancelJob.mockResolvedValue({ status: 'cancelled' }); const result = await controller.cancelJob(req(), 'j1'); expect(mockService.cancelJob).toHaveBeenCalledWith('u1', 'j1'); expect(result).toEqual({ status: 'cancelled' }); }); it('GET jobs/:jobId gets job detail', async () => { mockService.getJob.mockResolvedValue({ id: 'j1', status: 'running' }); const result = await controller.getJob(req(), 'j1'); expect(mockService.getJob).toHaveBeenCalledWith('u1', 'j1'); expect(result).toEqual({ id: 'j1', status: 'running' }); }); it('GET jobs lists jobs with query filters', async () => { mockService.listJobs.mockResolvedValue([{ id: 'j1' }]); const result = await controller.listJobs(req(), 'running', '10'); expect(mockService.listJobs).toHaveBeenCalledWith('u1', 'running', 10); expect(result).toEqual([{ id: 'j1' }]); }); it('GET jobs without take returns undefined', async () => { mockService.listJobs.mockResolvedValue([]); await controller.listJobs(req(), undefined, undefined); expect(mockService.listJobs).toHaveBeenCalledWith('u1', undefined, undefined); }); }); // ═══════════════════════════════════════════════════════════════════ // Quiz & Flashcard Publish // ═══════════════════════════════════════════════════════════════════ describe('Publish', () => { it('POST quizzes/:quizId/publish', async () => { mockService.publishQuiz.mockResolvedValue({ status: 'published' }); const result = await controller.publishQuiz(req(), 'q1'); expect(mockService.publishQuiz).toHaveBeenCalledWith('u1', 'q1'); expect(result).toEqual({ status: 'published' }); }); it('POST flashcards/:cardId/publish', async () => { mockService.publishFlashcard.mockResolvedValue({ status: 'published' }); const result = await controller.publishFlashcard(req(), 'fc1'); expect(mockService.publishFlashcard).toHaveBeenCalledWith('u1', 'fc1'); expect(result).toEqual({ status: 'published' }); }); }); // ═══════════════════════════════════════════════════════════════════ // Analysis Results // ═══════════════════════════════════════════════════════════════════ describe('Analysis Results', () => { it('GET analyses/:id', async () => { mockService.getAnalysis.mockResolvedValue({ id: 'a1' }); const result = await controller.getAnalysis(req(), 'a1'); expect(mockService.getAnalysis).toHaveBeenCalledWith('u1', 'a1'); expect(result).toEqual({ id: 'a1' }); }); it('GET analyses with filters', async () => { mockService.listAnalyses.mockResolvedValue([{ id: 'a1' }]); const result = await controller.listAnalyses(req(), 'material', 'm1', '5'); expect(mockService.listAnalyses).toHaveBeenCalledWith('u1', 'material', 'm1', 5); expect(result).toEqual([{ id: 'a1' }]); }); it('GET analyses without filters', async () => { mockService.listAnalyses.mockResolvedValue([]); await controller.listAnalyses(req()); expect(mockService.listAnalyses).toHaveBeenCalledWith('u1', undefined, undefined, undefined); }); it('GET recommendations with filters', async () => { mockService.listRecommendations.mockResolvedValue([{ id: 'r1' }]); const result = await controller.listRecommendations(req(), 'material', 'm1', 'active', '5'); expect(mockService.listRecommendations).toHaveBeenCalledWith('u1', 'material', 'm1', 'active', 5); expect(result).toEqual([{ id: 'r1' }]); }); it('GET recommendations without filters', async () => { mockService.listRecommendations.mockResolvedValue([]); await controller.listRecommendations(req()); expect(mockService.listRecommendations).toHaveBeenCalledWith('u1', undefined, undefined, undefined, undefined); }); it('GET weak-points with filters', async () => { mockService.listWeakPoints.mockResolvedValue([{ id: 'w1' }]); const result = await controller.listWeakPoints(req(), 'material', 'm1', 'active', '5'); expect(mockService.listWeakPoints).toHaveBeenCalledWith('u1', 'material', 'm1', 'active', 5); expect(result).toEqual([{ id: 'w1' }]); }); }); // ═══════════════════════════════════════════════════════════════════ // Quizzes // ═══════════════════════════════════════════════════════════════════ describe('Quizzes', () => { it('GET quizzes/:quizId', async () => { mockService.getQuiz.mockResolvedValue({ id: 'q1', title: 'Test' }); const result = await controller.getQuiz(req(), 'q1'); expect(mockService.getQuiz).toHaveBeenCalledWith('u1', 'q1'); expect(result).toEqual({ id: 'q1', title: 'Test' }); }); it('GET quizzes/:quizId/questions', async () => { mockService.getQuizQuestions.mockResolvedValue([{ id: 'qq1' }]); const result = await controller.getQuizQuestions(req(), 'q1'); expect(mockService.getQuizQuestions).toHaveBeenCalledWith('u1', 'q1'); expect(result).toEqual([{ id: 'qq1' }]); }); it('GET quizzes with filters', async () => { mockService.listQuizzes.mockResolvedValue([{ id: 'q1' }]); const result = await controller.listQuizzes(req(), 'kb1', 'ready', '10'); expect(mockService.listQuizzes).toHaveBeenCalledWith('u1', 'kb1', 'ready', 10); expect(result).toEqual([{ id: 'q1' }]); }); }); // ═══════════════════════════════════════════════════════════════════ // Reanalysis // ═══════════════════════════════════════════════════════════════════ describe('Reanalysis', () => { it('POST reanalyze triggers reanalysis', async () => { mockService.triggerReanalysis.mockResolvedValue({ jobId: 'j1' }); const result = await controller.triggerReanalysis(req(), 'material', 'm1'); expect(mockService.triggerReanalysis).toHaveBeenCalledWith('u1', 'material', 'm1'); expect(result).toEqual({ jobId: 'j1' }); }); }); // ═══════════════════════════════════════════════════════════════════ // Notifications // ═══════════════════════════════════════════════════════════════════ describe('Notifications', () => { it('GET notifications with take', async () => { mockService.listNotifications.mockResolvedValue([{ id: 'n1' }]); const result = await controller.listNotifications(req(), '20'); expect(mockService.listNotifications).toHaveBeenCalledWith('u1', 20); expect(result).toEqual([{ id: 'n1' }]); }); it('GET notifications without take', async () => { mockService.listNotifications.mockResolvedValue([]); await controller.listNotifications(req()); expect(mockService.listNotifications).toHaveBeenCalledWith('u1', undefined); }); }); // ═══════════════════════════════════════════════════════════════════ // Feedback // ═══════════════════════════════════════════════════════════════════ describe('Feedback', () => { it('POST artifacts/:type/:id/feedback', async () => { const dto = { feedbackType: 'like', reason: 'good' }; mockService.submitArtifactFeedback.mockResolvedValue({ ok: true }); const result = await controller.submitArtifactFeedback(req(), 'quiz', 'q1', dto); expect(mockService.submitArtifactFeedback).toHaveBeenCalledWith('u1', 'quiz', 'q1', dto); expect(result).toEqual({ ok: true }); }); it('POST feedback', async () => { const dto = { category: 'bug', content: 'issue desc', email: 'a@b.com' }; mockService.submitFeedback.mockResolvedValue({ ok: true }); const result = await controller.submitFeedback(req(), dto); expect(mockService.submitFeedback).toHaveBeenCalledWith('u1', dto); expect(result).toEqual({ ok: true }); }); }); // ═══════════════════════════════════════════════════════════════════ // Flashcards // ═══════════════════════════════════════════════════════════════════ describe('Flashcards', () => { it('GET flashcards/:cardId', async () => { mockService.getFlashcard.mockResolvedValue({ id: 'fc1', front: 'Q', back: 'A' }); const result = await controller.getFlashcard(req(), 'fc1'); expect(mockService.getFlashcard).toHaveBeenCalledWith('u1', 'fc1'); expect(result).toEqual({ id: 'fc1', front: 'Q', back: 'A' }); }); it('GET flashcards with filters', async () => { mockService.listFlashcards.mockResolvedValue([{ id: 'fc1' }]); const result = await controller.listFlashcards(req(), 'kp1', 'draft', '10'); expect(mockService.listFlashcards).toHaveBeenCalledWith('u1', 'kp1', 'draft', 10); expect(result).toEqual([{ id: 'fc1' }]); }); it('GET flashcards without filters', async () => { mockService.listFlashcards.mockResolvedValue([]); await controller.listFlashcards(req()); expect(mockService.listFlashcards).toHaveBeenCalledWith('u1', undefined, undefined, undefined); }); }); });