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
201 lines
9.0 KiB
TypeScript
201 lines
9.0 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
import { DataSource } from 'typeorm';
|
|
import { AssessmentService } from './assessment.service';
|
|
import { AssessmentSession, AssessmentStatus } from './entities/assessment-session.entity';
|
|
import { AssessmentQuestion } from './entities/assessment-question.entity';
|
|
import { AssessmentAnswer } from './entities/assessment-answer.entity';
|
|
import { AssessmentCertificate } from './entities/assessment-certificate.entity';
|
|
import { QuestionBank } from './entities/question-bank.entity';
|
|
import { QuestionBankItem } from './entities/question-bank-item.entity';
|
|
import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';
|
|
import { KnowledgeGroupService } from '../knowledge-group/knowledge-group.service';
|
|
import { ModelConfigService } from '../model-config/model-config.service';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { TemplateService } from './services/template.service';
|
|
import { ContentFilterService } from './services/content-filter.service';
|
|
import { QuestionOutlineService } from './services/question-outline.service';
|
|
import { QuestionBankService } from './services/question-bank.service';
|
|
import { RagService } from '../rag/rag.service';
|
|
import { ChatService } from '../chat/chat.service';
|
|
import { I18nService } from '../i18n/i18n.service';
|
|
import { TenantService } from '../tenant/tenant.service';
|
|
import { NotFoundException } from '@nestjs/common';
|
|
|
|
describe('AssessmentService', () => {
|
|
let service: AssessmentService;
|
|
let sessionRepository: any;
|
|
let certificateRepository: any;
|
|
let dataSource: any;
|
|
|
|
const mockRepository = () => ({
|
|
delete: jest.fn(),
|
|
find: jest.fn(),
|
|
findOne: jest.fn(),
|
|
save: jest.fn(),
|
|
create: jest.fn(),
|
|
});
|
|
|
|
const mockService = () => ({});
|
|
|
|
const regularUser = { id: 'user-1', role: 'user' };
|
|
const adminUser = { id: 'admin-1', role: 'admin' };
|
|
|
|
const mockManager = (overrides?: any) => ({
|
|
findOne: jest.fn(),
|
|
delete: jest.fn().mockResolvedValue({ affected: 1 }),
|
|
save: jest.fn(),
|
|
...overrides,
|
|
});
|
|
|
|
const mockDataSource = (manager?: any) => ({
|
|
transaction: jest.fn(async (cb: any) => {
|
|
return cb(manager || mockManager());
|
|
}),
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
AssessmentService,
|
|
{ provide: getRepositoryToken(AssessmentSession), useFactory: mockRepository },
|
|
{ provide: getRepositoryToken(AssessmentQuestion), useFactory: mockRepository },
|
|
{ provide: getRepositoryToken(AssessmentAnswer), useFactory: mockRepository },
|
|
{ provide: getRepositoryToken(AssessmentCertificate), useFactory: mockRepository },
|
|
{ provide: getRepositoryToken(QuestionBank), useFactory: mockRepository },
|
|
{ provide: getRepositoryToken(QuestionBankItem), useFactory: mockRepository },
|
|
{ provide: KnowledgeBaseService, useFactory: mockService },
|
|
{ provide: KnowledgeGroupService, useFactory: mockService },
|
|
{ provide: ModelConfigService, useFactory: mockService },
|
|
{ provide: ConfigService, useFactory: mockService },
|
|
{ provide: TemplateService, useFactory: mockService },
|
|
{ provide: ContentFilterService, useFactory: mockService },
|
|
{ provide: QuestionOutlineService, useFactory: mockService },
|
|
{ provide: QuestionBankService, useFactory: mockService },
|
|
{ provide: RagService, useFactory: mockService },
|
|
{ provide: ChatService, useFactory: mockService },
|
|
{ provide: I18nService, useFactory: mockService },
|
|
{ provide: TenantService, useFactory: mockService },
|
|
{ provide: DataSource, useFactory: () => mockDataSource(mockManager()) },
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<AssessmentService>(AssessmentService);
|
|
sessionRepository = module.get(getRepositoryToken(AssessmentSession));
|
|
certificateRepository = module.get(getRepositoryToken(AssessmentCertificate));
|
|
dataSource = module.get(DataSource);
|
|
});
|
|
|
|
it('should be defined', () => {
|
|
expect(service).toBeDefined();
|
|
});
|
|
|
|
describe('deleteSession', () => {
|
|
it('should delete a session when non-admin user owns it', async () => {
|
|
const manager = mockManager({
|
|
findOne: jest.fn().mockResolvedValue({ id: 'session-id', userId: 'user-1' }),
|
|
});
|
|
dataSource.transaction.mockImplementation(async (cb: any) => cb(manager));
|
|
|
|
await expect(service.deleteSession('session-id', regularUser)).resolves.not.toThrow();
|
|
expect(manager.findOne).toHaveBeenCalledWith(AssessmentSession, { where: { id: 'session-id', userId: 'user-1' } });
|
|
expect(manager.delete).toHaveBeenCalledWith(AssessmentCertificate, { sessionId: 'session-id' });
|
|
expect(manager.delete).toHaveBeenCalledWith(AssessmentSession, { id: 'session-id' });
|
|
});
|
|
|
|
it('should delete any session when admin user', async () => {
|
|
const manager = mockManager({
|
|
findOne: jest.fn().mockResolvedValue({ id: 'other-session', userId: 'user-2' }),
|
|
});
|
|
dataSource.transaction.mockImplementation(async (cb: any) => cb(manager));
|
|
|
|
await expect(service.deleteSession('other-session', adminUser)).resolves.not.toThrow();
|
|
expect(manager.findOne).toHaveBeenCalledWith(AssessmentSession, { where: { id: 'other-session' } });
|
|
});
|
|
|
|
it('should throw NotFoundException if session not found', async () => {
|
|
const manager = mockManager({
|
|
findOne: jest.fn().mockResolvedValue(null),
|
|
});
|
|
dataSource.transaction.mockImplementation(async (cb: any) => cb(manager));
|
|
|
|
await expect(service.deleteSession('non-existent', regularUser)).rejects.toThrow(NotFoundException);
|
|
});
|
|
});
|
|
|
|
describe('generateCertificate', () => {
|
|
const completedSession = {
|
|
id: 'session-1',
|
|
userId: 'user-1',
|
|
status: AssessmentStatus.COMPLETED,
|
|
finalScore: 85,
|
|
templateId: 'template-1',
|
|
};
|
|
|
|
it('should throw NotFoundException when session does not exist', async () => {
|
|
sessionRepository.findOne.mockResolvedValue(null);
|
|
await expect(
|
|
service.generateCertificate('no-session', 'user-1', 'tenant-1'),
|
|
).rejects.toThrow(NotFoundException);
|
|
});
|
|
|
|
it('should throw Error when session is not completed', async () => {
|
|
sessionRepository.findOne.mockResolvedValue({
|
|
...completedSession,
|
|
status: AssessmentStatus.IN_PROGRESS,
|
|
});
|
|
await expect(
|
|
service.generateCertificate('session-1', 'user-1', 'tenant-1'),
|
|
).rejects.toThrow('Session not completed');
|
|
});
|
|
|
|
it('should return existing certificate if already generated (idempotent)', async () => {
|
|
const existingCert = { id: 'cert-1', sessionId: 'session-1' };
|
|
sessionRepository.findOne.mockResolvedValue(completedSession);
|
|
certificateRepository.findOne.mockResolvedValue(existingCert);
|
|
const result = await service.generateCertificate('session-1', 'user-1', 'tenant-1');
|
|
expect(result).toEqual(existingCert);
|
|
expect(certificateRepository.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should create a new certificate with correct level for score >= 90 (Expert)', async () => {
|
|
sessionRepository.findOne.mockResolvedValue({ ...completedSession, finalScore: 95 });
|
|
certificateRepository.findOne.mockResolvedValue(null);
|
|
certificateRepository.create.mockReturnValue({ id: 'cert-new' });
|
|
certificateRepository.save.mockResolvedValue({ id: 'cert-new', level: 'Expert' });
|
|
|
|
const result = await service.generateCertificate('session-1', 'user-1', 'tenant-1');
|
|
expect(result).toBeDefined();
|
|
expect(certificateRepository.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({ level: 'Expert', totalScore: 95 }),
|
|
);
|
|
});
|
|
|
|
it('should create a new certificate with Advanced level for score 75-89', async () => {
|
|
sessionRepository.findOne.mockResolvedValue(completedSession);
|
|
certificateRepository.findOne.mockResolvedValue(null);
|
|
certificateRepository.create.mockReturnValue({ id: 'cert-new' });
|
|
certificateRepository.save.mockResolvedValue({ id: 'cert-new', level: 'Advanced' });
|
|
|
|
const result = await service.generateCertificate('session-1', 'user-1', 'tenant-1');
|
|
expect(result).toBeDefined();
|
|
expect(certificateRepository.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({ level: 'Advanced', totalScore: 85 }),
|
|
);
|
|
});
|
|
|
|
it('should create a new certificate with Novice level for score < 60', async () => {
|
|
sessionRepository.findOne.mockResolvedValue({ ...completedSession, finalScore: 45 });
|
|
certificateRepository.findOne.mockResolvedValue(null);
|
|
certificateRepository.create.mockReturnValue({ id: 'cert-new' });
|
|
certificateRepository.save.mockResolvedValue({ id: 'cert-new', level: 'Novice' });
|
|
|
|
const result = await service.generateCertificate('session-1', 'user-1', 'tenant-1');
|
|
expect(result).toBeDefined();
|
|
expect(certificateRepository.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({ level: 'Novice', totalScore: 45 }),
|
|
);
|
|
});
|
|
});
|
|
});
|