Files
aurak/server/test/app.e2e-spec.ts
Developer 82a9e75842 fix: code review — 7 issues resolved
(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
2026-05-19 10:06:30 +08:00

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);
}
});
});
});