82a9e75842
(C1) Add dimensionScores/radarData/passed columns to AssessmentSession (C2) Mock DataSource in service.spec.ts + app.e2e-spec.ts (C3) Mock AuditLogService in controller.spec.ts (C4) Rewrite deleteSession tests for dataSource.transaction (I1) batchDeleteSessions uses transaction with certificate cleanup (I2) extractDimensionScores reads from session property (I3/I5) PDF generator supports multi-page + newline splitting (I4) findOne inside transaction uses deleteCondition
215 lines
8.5 KiB
TypeScript
215 lines
8.5 KiB
TypeScript
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>(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);
|
|
}
|
|
});
|
|
});
|
|
});
|