import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; import { AssessmentService } from '../src/assessment/assessment.service'; import { AssessmentSession } from '../src/assessment/entities/assessment-session.entity'; import { AssessmentQuestion } from '../src/assessment/entities/assessment-question.entity'; import { AssessmentAnswer } from '../src/assessment/entities/assessment-answer.entity'; import { AssessmentCertificate } from '../src/assessment/entities/assessment-certificate.entity'; import { QuestionBank } from '../src/assessment/entities/question-bank.entity'; import { QuestionBankItem } from '../src/assessment/entities/question-bank-item.entity'; import { KnowledgeBaseService } from '../src/knowledge-base/knowledge-base.service'; import { KnowledgeGroupService } from '../src/knowledge-group/knowledge-group.service'; import { ModelConfigService } from '../src/model-config/model-config.service'; import { ConfigService } from '@nestjs/config'; import { TemplateService } from '../src/assessment/services/template.service'; import { ContentFilterService } from '../src/assessment/services/content-filter.service'; import { QuestionOutlineService } from '../src/assessment/services/question-outline.service'; import { QuestionBankService } from '../src/assessment/services/question-bank.service'; import { RagService } from '../src/rag/rag.service'; import { ChatService } from '../src/chat/chat.service'; import { I18nService } from '../src/i18n/i18n.service'; import { TenantService } from '../src/tenant/tenant.service'; const mockManager = () => ({ findOne: jest.fn(), delete: jest.fn().mockResolvedValue({ affected: 1 }), save: jest.fn(), }); const mockDataSource = () => ({ transaction: jest.fn(async (cb: any) => cb(mockManager())), }); /** * Certificate integration tests — verify the full certificate lifecycle * through the AssessmentService with mocked repositories. */ describe('Certificate (integration)', () => { let service: AssessmentService; let sessionRepo: any; let certificateRepo: any; const mockRepo = () => ({ find: jest.fn(), findOne: jest.fn(), save: jest.fn(), create: jest.fn(), delete: jest.fn(), }); const mockSvc = () => ({}); beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ AssessmentService, { provide: getRepositoryToken(AssessmentSession), useFactory: mockRepo }, { provide: getRepositoryToken(AssessmentQuestion), useFactory: mockRepo }, { provide: getRepositoryToken(AssessmentAnswer), useFactory: mockRepo }, { provide: getRepositoryToken(AssessmentCertificate), useFactory: mockRepo }, { provide: getRepositoryToken(QuestionBank), useFactory: mockRepo }, { provide: getRepositoryToken(QuestionBankItem), useFactory: mockRepo }, { provide: KnowledgeBaseService, useFactory: mockSvc }, { provide: KnowledgeGroupService, useFactory: mockSvc }, { provide: ModelConfigService, useFactory: mockSvc }, { provide: ConfigService, useFactory: mockSvc }, { provide: TemplateService, useFactory: mockSvc }, { provide: ContentFilterService, useFactory: mockSvc }, { provide: QuestionOutlineService, useFactory: mockSvc }, { provide: QuestionBankService, useFactory: mockSvc }, { provide: RagService, useFactory: mockSvc }, { provide: ChatService, useFactory: mockSvc }, { provide: I18nService, useFactory: mockSvc }, { provide: TenantService, useFactory: mockSvc }, { provide: DataSource, useFactory: mockDataSource }, ], }).compile(); service = module.get(AssessmentService); sessionRepo = module.get(getRepositoryToken(AssessmentSession)); certificateRepo = module.get(getRepositoryToken(AssessmentCertificate)); }); beforeEach(() => { jest.clearAllMocks(); }); describe('verifyCertificate (public endpoint logic)', () => { it('should return { valid: false } for unknown certificate ID', async () => { certificateRepo.findOne.mockResolvedValue(null); const result = await service.verifyCertificate('no-cert'); expect(result.valid).toBe(false); expect(result.message).toContain('not found'); }); it('should return { valid: true } with certificate data for known ID', async () => { certificateRepo.findOne.mockResolvedValue({ id: 'cert-1', level: 'Expert', totalScore: 95, passed: true, issuedAt: new Date('2026-01-01'), userId: 'user-1', }); const result = await service.verifyCertificate('cert-1'); expect(result.valid).toBe(true); expect(result.certificate!.level).toBe('Expert'); expect(result.certificate!.userId).toBe('user-1'); }); }); describe('getPublicCertificateInfo (public endpoint logic)', () => { it('should return { exists: false } for session without certificate', async () => { certificateRepo.findOne.mockResolvedValue(null); const result = await service.getPublicCertificateInfo('no-session'); expect(result.exists).toBe(false); expect(result.message).toContain('not found'); }); it('should return certificate info for session with certificate', async () => { certificateRepo.findOne.mockResolvedValue({ id: 'cert-1', sessionId: 'session-1', level: 'Advanced', totalScore: 85, passed: true, issuedAt: new Date('2026-01-01'), dimensionScores: { prompt: 80, llm: 90 }, }); const result = await service.getPublicCertificateInfo('session-1'); expect(result.exists).toBe(true); expect(result.certificate!.level).toBe('Advanced'); expect(result.certificate!.totalScore).toBe(85); }); }); describe('Certificate lifecycle', () => { it('should generate certificate then verify it', async () => { sessionRepo.findOne.mockResolvedValue({ id: 'session-lc', userId: 'user-1', status: 'COMPLETED', finalScore: 88, templateId: 'template-1', }); certificateRepo.findOne.mockResolvedValueOnce(null); certificateRepo.create.mockReturnValue({ id: 'cert-lc' }); certificateRepo.save.mockResolvedValue({ id: 'cert-lc', level: 'Advanced', totalScore: 88, passed: true, userId: 'user-1', sessionId: 'session-lc', }); const cert = await service.generateCertificate('session-lc', 'user-1', 'tenant-1'); expect(cert.level).toBe('Advanced'); certificateRepo.findOne.mockResolvedValueOnce({ id: 'cert-lc', level: 'Advanced', totalScore: 88, passed: true, issuedAt: new Date(), userId: 'user-1', }); const verified = await service.verifyCertificate('cert-lc'); expect(verified.valid).toBe(true); expect(verified.certificate!.totalScore).toBe(88); }); it('should be idempotent — returning existing certificate on re-generation', async () => { const existing = { id: 'cert-dup', sessionId: 'session-dup', level: 'Proficient' }; sessionRepo.findOne.mockResolvedValue({ id: 'session-dup', userId: 'user-1', status: 'COMPLETED', finalScore: 65, }); certificateRepo.findOne.mockResolvedValue(existing); const result = await service.generateCertificate('session-dup', 'user-1', 'tenant-1'); expect(result).toBe(existing); expect(certificateRepo.create).not.toHaveBeenCalled(); }); it('should determine correct level for different scores', async () => { const testCases = [ { score: 95, expectedLevel: 'Expert' }, { score: 80, expectedLevel: 'Advanced' }, { score: 65, expectedLevel: 'Proficient' }, { score: 45, expectedLevel: 'Novice' }, ]; for (const { score, expectedLevel } of testCases) { sessionRepo.findOne.mockResolvedValue({ id: `session-${score}`, userId: 'user-1', status: 'COMPLETED', finalScore: score, templateId: 't1', }); certificateRepo.findOne.mockResolvedValue(null); certificateRepo.create.mockReturnValue({ id: `cert-${score}` }); certificateRepo.save.mockResolvedValue({ id: `cert-${score}`, level: expectedLevel }); const cert = await service.generateCertificate(`session-${score}`, 'user-1', 'tenant-1'); expect(cert.level).toBe(expectedLevel); } }); }); });