251216:0946 Docunment Number: Update specs/ and backend/
Some checks failed
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-16 09:46:37 +07:00
parent 78370fb590
commit 9c1e175b76
17 changed files with 932 additions and 3259 deletions

View File

@@ -41,4 +41,13 @@ export class DocumentNumberingController {
getErrorLogs(@Query('limit') limit?: number) {
return this.numberingService.getErrorLogs(limit ? Number(limit) : 100);
}
@Patch('counters/:id')
@Roles(Role.ADMIN)
async updateCounter(
@Param('id') id: number,
@Body('sequence') sequence: number
) {
return this.service.setCounterValue(id, sequence);
}
}

View File

@@ -2,8 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { DocumentNumberingService } from './document-numbering.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
import { Repository, OptimisticLockVersionMismatchError } from 'typeorm';
import { InternalServerErrorException } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { DocumentNumberCounter } from './entities/document-number-counter.entity';
import { DocumentNumberFormat } from './entities/document-number-format.entity';
import { Project } from '../project/entities/project.entity';
@@ -17,6 +16,7 @@ import { DocumentNumberError } from './entities/document-number-error.entity';
// Mock Redis and Redlock
const mockRedis = {
disconnect: jest.fn(),
on: jest.fn(),
};
const mockRedlock = {
acquire: jest.fn(),
@@ -37,9 +37,7 @@ jest.mock('redlock', () => {
describe('DocumentNumberingService', () => {
let service: DocumentNumberingService;
let module: TestingModule;
let counterRepo: Repository<DocumentNumberCounter>;
let formatRepo: Repository<DocumentNumberFormat>;
let auditRepo: Repository<DocumentNumberAudit>;
let formatRepo: jest.Mocked<{ findOne: jest.Mock }>;
const mockProject = { id: 1, projectCode: 'LCBP3' };
const mockOrg = { id: 1, name: 'Google' };
@@ -79,11 +77,17 @@ describe('DocumentNumberingService', () => {
},
{
provide: getRepositoryToken(DocumentNumberAudit),
useValue: { save: jest.fn() },
useValue: {
create: jest.fn().mockReturnValue({ id: 1 }),
save: jest.fn().mockResolvedValue({ id: 1 }),
},
},
{
provide: getRepositoryToken(DocumentNumberError),
useValue: { save: jest.fn() },
useValue: {
create: jest.fn().mockReturnValue({}),
save: jest.fn().mockResolvedValue({}),
},
},
// Mock other dependencies used inside generateNextNumber lookups
{
@@ -106,16 +110,26 @@ describe('DocumentNumberingService', () => {
provide: getRepositoryToken(CorrespondenceSubType),
useValue: { findOne: jest.fn() },
},
{
provide: DataSource,
useValue: {
transaction: jest.fn((cb) =>
cb({
findOne: jest.fn().mockResolvedValue(null),
create: jest.fn().mockReturnValue({ lastSequence: 0 }),
save: jest.fn().mockResolvedValue({ lastSequence: 1 }),
})
),
},
},
],
}).compile();
service = module.get<DocumentNumberingService>(DocumentNumberingService);
counterRepo = module.get(getRepositoryToken(DocumentNumberCounter));
formatRepo = module.get(getRepositoryToken(DocumentNumberFormat));
auditRepo = module.get(getRepositoryToken(DocumentNumberAudit));
});
afterEach(async () => {
afterEach(() => {
jest.clearAllMocks();
// Don't call onModuleDestroy - redisClient is mocked and would cause undefined error
});
@@ -136,46 +150,46 @@ describe('DocumentNumberingService', () => {
(typeRepo.findOne as jest.Mock).mockResolvedValue(mockType);
(disciplineRepo.findOne as jest.Mock).mockResolvedValue(mockDiscipline);
(formatRepo.findOne as jest.Mock).mockResolvedValue({
formatTemplate: '{SEQ}',
formatTemplate: '{SEQ:4}',
resetSequenceYearly: true,
});
(counterRepo.findOne as jest.Mock).mockResolvedValue(null); // First time
(counterRepo.save as jest.Mock).mockResolvedValue({ lastNumber: 1 });
service.onModuleInit();
const result = await service.generateNextNumber(mockContext);
expect(result).toBe('0001'); // Default padding 4 (see replaceTokens method)
expect(counterRepo.save).toHaveBeenCalled();
// expect(auditRepo.save).toHaveBeenCalled(); // Disabled in implementation
// Service returns object with number and auditId
expect(result).toHaveProperty('number');
expect(result).toHaveProperty('auditId');
expect(result.number).toBe('0001'); // Padded to 4 digits
});
it('should throw InternalServerErrorException if max retries exceeded', async () => {
it('should throw error when transaction fails', async () => {
const projectRepo = module.get(getRepositoryToken(Project));
const orgRepo = module.get(getRepositoryToken(Organization));
const typeRepo = module.get(getRepositoryToken(CorrespondenceType));
const disciplineRepo = module.get(getRepositoryToken(Discipline));
const dataSource = module.get(DataSource);
(projectRepo.findOne as jest.Mock).mockResolvedValue(mockProject);
(orgRepo.findOne as jest.Mock).mockResolvedValue(mockOrg);
(typeRepo.findOne as jest.Mock).mockResolvedValue(mockType);
(disciplineRepo.findOne as jest.Mock).mockResolvedValue(mockDiscipline);
(formatRepo.findOne as jest.Mock).mockResolvedValue({
formatTemplate: '{SEQ}',
formatTemplate: '{SEQ:4}',
resetSequenceYearly: true,
});
(counterRepo.findOne as jest.Mock).mockResolvedValue({ lastNumber: 1 });
// Always fail
(counterRepo.save as jest.Mock).mockRejectedValue(
new OptimisticLockVersionMismatchError('Counter', 1, 2)
// Mock transaction to throw error
(dataSource.transaction as jest.Mock).mockRejectedValue(
new Error('Transaction failed')
);
service.onModuleInit();
await expect(service.generateNextNumber(mockContext)).rejects.toThrow(
InternalServerErrorException
Error
);
expect(counterRepo.save).toHaveBeenCalledTimes(3); // Max retries
});
});
});

View File

@@ -1,736 +1,302 @@
// File: src/modules/document-numbering/document-numbering.service.ts
import {
Injectable,
OnModuleInit,
OnModuleDestroy,
InternalServerErrorException,
NotFoundException,
Logger,
} from '@nestjs/common';
// backend/src/modules/document-numbering/document-numbering.service.ts
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
Repository,
EntityManager,
OptimisticLockVersionMismatchError,
} from 'typeorm';
import { Repository, DataSource, IsNull } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
import Redlock from 'redlock';
// Entities
import { DocumentNumberCounter } from './entities/document-number-counter.entity';
import { DocumentNumberFormat } from './entities/document-number-format.entity';
import { Project } from '../project/entities/project.entity'; // สมมติ path
import { Organization } from '../organization/entities/organization.entity';
import { DocumentNumberAudit } from './entities/document-number-audit.entity';
import { DocumentNumberError } from './entities/document-number-error.entity';
import { Project } from '../project/entities/project.entity';
import { CorrespondenceType } from '../correspondence/entities/correspondence-type.entity';
import { Organization } from '../organization/entities/organization.entity';
import { Discipline } from '../master/entities/discipline.entity';
import { CorrespondenceSubType } from '../correspondence/entities/correspondence-sub-type.entity';
import { DocumentNumberAudit } from './entities/document-number-audit.entity'; // [P0-4]
import { DocumentNumberError } from './entities/document-number-error.entity'; // [P0-4]
// Interfaces
import {
GenerateNumberContext,
DecodedTokens,
} from './interfaces/document-numbering.interface.js';
} from './interfaces/document-numbering.interface';
@Injectable()
export class DocumentNumberingService implements OnModuleInit, OnModuleDestroy {
export class DocumentNumberingService implements OnModuleInit {
private readonly logger = new Logger(DocumentNumberingService.name);
private redisClient!: Redis;
private redlock!: Redlock;
private redisClient: Redis;
private redlock: Redlock;
constructor(
@InjectRepository(DocumentNumberCounter)
private counterRepo: Repository<DocumentNumberCounter>,
@InjectRepository(DocumentNumberFormat)
private formatRepo: Repository<DocumentNumberFormat>,
// Inject Repositories สำหรับดึง Code มาทำ Token Replacement
@InjectRepository(Project) private projectRepo: Repository<Project>,
@InjectRepository(Organization) private orgRepo: Repository<Organization>,
@InjectRepository(DocumentNumberAudit)
private auditRepo: Repository<DocumentNumberAudit>,
@InjectRepository(DocumentNumberError)
private errorRepo: Repository<DocumentNumberError>,
@InjectRepository(Project)
private projectRepo: Repository<Project>,
@InjectRepository(CorrespondenceType)
private typeRepo: Repository<CorrespondenceType>,
@InjectRepository(Organization)
private orgRepo: Repository<Organization>,
@InjectRepository(Discipline)
private disciplineRepo: Repository<Discipline>,
@InjectRepository(CorrespondenceSubType)
private subTypeRepo: Repository<CorrespondenceSubType>,
@InjectRepository(DocumentNumberAudit) // [P0-4]
private auditRepo: Repository<DocumentNumberAudit>,
@InjectRepository(DocumentNumberError) // [P0-4]
private errorRepo: Repository<DocumentNumberError>,
private dataSource: DataSource,
private configService: ConfigService
) {}
onModuleInit() {
// 1. Setup Redis Connection & Redlock
const host = this.configService.get<string>('REDIS_HOST', 'localhost');
const port = this.configService.get<number>('REDIS_PORT', 6379);
const password = this.configService.get<string>('REDIS_PASSWORD');
this.redisClient = new Redis({ host, port, password });
// Config Redlock สำหรับ Distributed Lock
this.redlock = new Redlock([this.redisClient], {
driftFactor: 0.01,
retryCount: 10, // Retry 10 ครั้ง
retryDelay: 200, // รอ 200ms ต่อครั้ง
retryJitter: 200,
this.redisClient = new Redis({
host,
port,
retryStrategy: (times) => Math.min(times * 50, 2000),
maxRetriesPerRequest: 3,
});
this.logger.log(
`Document Numbering Service initialized (Redis: ${host}:${port})`
);
this.redisClient.on('error', (err) => {
this.logger.error('Redis Client Error', err);
});
this.redlock = new Redlock([this.redisClient], {
driftFactor: 0.01,
retryCount: 3,
retryDelay: 200,
retryJitter: 200,
});
}
onModuleDestroy() {
this.redisClient.disconnect();
}
async generateNextNumber(
ctx: GenerateNumberContext
): Promise<{ number: string; auditId: number }> {
const currentYear = new Date().getFullYear();
/**
* สร้างเลขที่เอกสารใหม่ (Thread-Safe & Gap-free)
*/
async generateNextNumber(ctx: GenerateNumberContext): Promise<string> {
const year = ctx.year || new Date().getFullYear();
const disciplineId = ctx.disciplineId || 0;
// 1. Resolve Format & Determine Counter Scope & Reset Rule
const { template, counterTypeId, resetSequenceYearly } =
await this.resolveFormatAndScope(ctx);
const tokens = await this.resolveTokens(ctx, currentYear);
// 1. Resolve Tokens Outside Lock
const tokens = await this.resolveTokens(ctx, year);
// 2. Determine Counter Year Key
// If resetSequenceYearly is true => Use current year (2025)
// If resetSequenceYearly is false => Use year 0 (Continuous)
const counterYear = resetSequenceYearly ? currentYear : 0;
// 2. Get Format Template WITH META (Padding)
const { template, paddingLength } = await this.getFormatTemplateWithMeta(
ctx.projectId,
ctx.typeId,
disciplineId
);
// 3. Build Lock Key
const resourceKey = `counter:${ctx.projectId}:${counterTypeId ?? 'shared'}:${counterYear}`;
// 3. Resource Key
const resourceKey = `doc_num:${ctx.projectId}:${ctx.typeId}:${disciplineId}:${year}`;
const lockTtl = 5000;
let lock;
let lock: any;
try {
lock = await this.redlock.acquire([resourceKey], lockTtl);
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
try {
const recipientId = ctx.recipientOrganizationId ?? -1;
const subTypeId = ctx.subTypeId ?? 0;
const rfaTypeId = ctx.rfaTypeId ?? 0;
let counter = await this.counterRepo.findOne({
where: {
projectId: ctx.projectId,
originatorId: ctx.originatorId,
recipientOrganizationId: recipientId,
typeId: ctx.typeId,
subTypeId: subTypeId,
rfaTypeId: rfaTypeId,
disciplineId: disciplineId,
year: year,
},
});
if (!counter) {
counter = this.counterRepo.create({
projectId: ctx.projectId,
originatorId: ctx.originatorId,
recipientOrganizationId: recipientId,
typeId: ctx.typeId,
subTypeId: subTypeId,
rfaTypeId: rfaTypeId,
disciplineId: disciplineId,
year: year,
lastNumber: 0,
});
}
counter.lastNumber += 1;
await this.counterRepo.save(counter);
const generatedNumber = this.replaceTokens(
template,
tokens,
counter.lastNumber,
paddingLength // Pass padding from template
);
// Audit skipped for brevity in this block, assumed handled or TBD
return generatedNumber;
} catch (err) {
if (err instanceof OptimisticLockVersionMismatchError) {
continue;
}
throw err;
}
try {
lock = await this.redlock.acquire([`locks:${resourceKey}`], 5000);
} catch (e) {
this.logger.warn(
`Redlock failed for ${resourceKey}, proceeding with DB optimistic lock.`
);
}
throw new InternalServerErrorException('Failed to generate number');
} catch (error: any) {
// Error logging...
// 4. Increment Counter (Atomic Transaction)
const result = await this.dataSource.transaction(async (manager) => {
let counter = await manager.findOne(DocumentNumberCounter, {
where: {
projectId: ctx.projectId,
correspondenceTypeId:
counterTypeId === null ? IsNull() : counterTypeId,
year: counterYear,
},
});
if (!counter) {
counter = manager.create(DocumentNumberCounter, {
projectId: ctx.projectId,
correspondenceTypeId: counterTypeId, // Can be null
year: counterYear,
lastSequence: 0,
});
}
counter.lastSequence += 1;
return await manager.save(counter);
});
// 5. Generate Final String
const generatedNumber = this.replaceTokens(
template,
tokens,
result.lastSequence
);
// 6. Audit Log
const audit = await this.logAudit({
generatedNumber,
counterKey: resourceKey,
templateUsed: template,
context: ctx,
isSuccess: true,
});
return { number: generatedNumber, auditId: audit.id };
} catch (error) {
await this.logError(error, ctx, resourceKey);
throw error;
} finally {
if (lock) await lock.release().catch(() => {});
if (lock) await lock.release().catch((e) => this.logger.error(e));
}
}
async previewNextNumber(
ctx: GenerateNumberContext
): Promise<{ number: string; isDefaultTemplate: boolean }> {
const year = ctx.year || new Date().getFullYear();
const disciplineId = ctx.disciplineId || 0;
// --- Helper Methods ---
const tokens = await this.resolveTokens(ctx, year);
const { template, isDefault, paddingLength } =
await this.getFormatTemplateWithMeta(
ctx.projectId,
ctx.typeId,
disciplineId
);
const recipientId = ctx.recipientOrganizationId ?? -1;
const subTypeId = ctx.subTypeId ?? 0;
const rfaTypeId = ctx.rfaTypeId ?? 0;
const counter = await this.counterRepo.findOne({
where: {
projectId: ctx.projectId,
originatorId: ctx.originatorId,
recipientOrganizationId: recipientId,
typeId: ctx.typeId,
subTypeId: subTypeId,
rfaTypeId: rfaTypeId,
disciplineId: disciplineId,
year: year,
},
private async resolveFormatAndScope(ctx: GenerateNumberContext): Promise<{
template: string;
counterTypeId: number | null;
resetSequenceYearly: boolean;
}> {
// A. Try Specific Format
const specificFormat = await this.formatRepo.findOne({
where: { projectId: ctx.projectId, correspondenceTypeId: ctx.typeId },
});
const nextSeq = (counter?.lastNumber || 0) + 1;
if (specificFormat) {
return {
template: specificFormat.formatTemplate,
counterTypeId: ctx.typeId,
resetSequenceYearly: specificFormat.resetSequenceYearly,
};
}
const generatedNumber = this.replaceTokens(
template,
tokens,
nextSeq,
paddingLength
);
// B. Try Default Format (Type = NULL)
const defaultFormat = await this.formatRepo.findOne({
where: { projectId: ctx.projectId, correspondenceTypeId: IsNull() },
});
if (defaultFormat) {
return {
template: defaultFormat.formatTemplate,
counterTypeId: null, // Use shared counter
resetSequenceYearly: defaultFormat.resetSequenceYearly,
};
}
// C. System Fallback
return {
number: generatedNumber,
isDefaultTemplate: isDefault,
template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}',
counterTypeId: null, // Use shared counter
resetSequenceYearly: true, // Default fallback behavior
};
}
/**
* Helper: ดึงข้อมูล Code ต่างๆ จาก ID เพื่อนำมาแทนที่ใน Template
*/
private async resolveTokens(
ctx: GenerateNumberContext,
year: number
): Promise<DecodedTokens> {
const [project, org, type] = await Promise.all([
this.projectRepo.findOne({ where: { id: ctx.projectId } }),
this.orgRepo.findOne({ where: { id: ctx.originatorId } }),
this.typeRepo.findOne({ where: { id: ctx.typeId } }),
]);
if (!project || !org || !type) {
throw new NotFoundException('Project, Organization, or Type not found');
}
// [v1.5.1] Support Custom Tokens Override
const custom = ctx.customTokens || {};
let disciplineCode = custom.DISCIPLINE_CODE || '000';
if (!custom.DISCIPLINE_CODE && ctx.disciplineId) {
const discipline = await this.disciplineRepo.findOne({
where: { id: ctx.disciplineId },
});
if (discipline) disciplineCode = discipline.disciplineCode;
}
let subTypeCode = custom.SUB_TYPE_CODE || '00';
let subTypeNumber = custom.SUB_TYPE_NUMBER || '00';
if (!custom.SUB_TYPE_CODE && ctx.subTypeId) {
const subType = await this.subTypeRepo.findOne({
where: { id: ctx.subTypeId },
});
if (subType) {
subTypeCode = subType.subTypeCode;
subTypeNumber = subType.subTypeNumber || '00';
}
}
// Convert Christian Year to Buddhist Year if needed (Req usually uses Christian, but prepared logic)
// ใน Req 6B ตัวอย่างใช้ 2568 (พ.ศ.) ดังนั้นต้องแปลง
const yearTh = (year + 543).toString();
// [v1.5.1] Resolve recipient organization
let recipientCode = custom.RECIPIENT_CODE || custom.REC_CODE || '';
if (
!recipientCode &&
ctx.recipientOrganizationId &&
ctx.recipientOrganizationId > 0
) {
const recipient = await this.orgRepo.findOne({
where: { id: ctx.recipientOrganizationId },
});
if (recipient) {
recipientCode = recipient.organizationCode;
}
}
const [project, type, recipientCode, disciplineCode, orgCode] =
await Promise.all([
this.projectRepo.findOne({
where: { id: ctx.projectId },
select: ['projectCode'],
}),
this.typeRepo.findOne({
where: { id: ctx.typeId },
select: ['typeCode'],
}),
this.resolveRecipientCode(ctx.recipientOrganizationId),
this.resolveDisciplineCode(ctx.disciplineId),
this.resolveOrgCode(ctx.originatorOrganizationId),
]);
return {
projectCode: project.projectCode,
orgCode: org.organizationCode,
typeCode: type.typeCode,
disciplineCode,
subTypeCode,
subTypeNumber,
year: yearTh,
yearShort: yearTh.slice(-2), // 68
recipientCode, // [P1-4]
'{PROJECT}': project?.projectCode || 'PROJ',
'{TYPE}': type?.typeCode || 'DOC',
'{ORG}': orgCode,
'{RECIPIENT}': recipientCode,
'{DISCIPLINE}': disciplineCode,
'{YEAR}': year.toString().substring(2),
'{YEAR:BE}': (year + 543).toString().substring(2),
'{REV}': '0',
};
}
// --- Template Management ---
async getTemplates(): Promise<DocumentNumberFormat[]> {
const templates = await this.formatRepo.find({
relations: ['project'], // Join project for names if needed
order: {
projectId: 'ASC',
correspondenceTypeId: 'ASC',
disciplineId: 'ASC',
},
});
// Add documentTypeName via manual join or map if needed.
// Ideally we should relation to CorrespondenceType too, but for now we might need to fetch manually if relation not strict
return templates;
}
async saveTemplate(
dto: Partial<DocumentNumberFormat>
): Promise<DocumentNumberFormat> {
if (dto.id) {
await this.formatRepo.update(dto.id, dto);
return this.formatRepo.findOneOrFail({ where: { id: dto.id } });
}
const newTemplate = this.formatRepo.create(dto);
return this.formatRepo.save(newTemplate);
}
/**
* Helper: Find Template from DB or use Default (with metadata)
* Supports Specific Discipline -> Global Discipline Fallback
*/
private async getFormatTemplateWithMeta(
projectId: number,
typeId: number,
disciplineId: number = 0
): Promise<{ template: string; isDefault: boolean; paddingLength: number }> {
// 1. Try Specific Discipline
let format = await this.formatRepo.findOne({
where: {
projectId,
correspondenceTypeId: typeId,
disciplineId: disciplineId,
},
});
// 2. Fallback to All Disciplines (0) if specific not found
if (!format && disciplineId !== 0) {
format = await this.formatRepo.findOne({
where: { projectId, correspondenceTypeId: typeId, disciplineId: 0 },
});
}
if (format) {
return {
template: format.formatTemplate,
isDefault: false,
paddingLength: format.paddingLength,
};
}
// Default Fallback Format
return {
template: '{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR}',
isDefault: true,
paddingLength: 4,
};
}
/**
* Legacy wrapper for backward compatibility
*/
private async getFormatTemplate(
projectId: number,
typeId: number,
disciplineId: number = 0
): Promise<string> {
const { template } = await this.getFormatTemplateWithMeta(
projectId,
typeId,
disciplineId
);
return template;
}
/**
* Helper: แทนที่ Token ใน Template ด้วยค่าจริง
*/
private replaceTokens(
template: string,
tokens: DecodedTokens,
seq: number,
defaultPadding: number = 4
sequence: number
): string {
let result = template;
const replacements: Record<string, string> = {
'{PROJECT}': tokens.projectCode,
'{ORG}': tokens.orgCode,
'{TYPE}': tokens.typeCode,
'{DISCIPLINE}': tokens.disciplineCode,
'{SUBTYPE}': tokens.subTypeCode,
'{SUBTYPE_NUM}': tokens.subTypeNumber, // [Req 6B] For Transmittal/RFA
'{RECIPIENT}': tokens.recipientCode, // [P1-4] Recipient organization
'{YEAR}': tokens.year,
'{YEAR_SHORT}': tokens.yearShort,
};
// 1. Replace Standard Tokens
for (const [key, value] of Object.entries(replacements)) {
// ใช้ Global Replace
result = result.split(key).join(value);
for (const [key, value] of Object.entries(tokens)) {
result = result.replace(
new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'),
value
);
}
const seqMatch = result.match(/{SEQ:(\d+)}/);
if (seqMatch) {
const padding = parseInt(seqMatch[1], 10);
result = result.replace(
seqMatch[0],
sequence.toString().padStart(padding, '0')
);
}
// 2. Replace Sequence Token {SEQ:n} e.g., {SEQ:4}
// If n is provided in token, use it. If not, use Template Padding setting.
result = result.replace(/{SEQ(?::(\d+))?}/g, (_, digits) => {
const padLength = digits ? parseInt(digits, 10) : defaultPadding;
return seq.toString().padStart(padLength, '0');
});
return result;
}
/**
* [P0-4] Log successful number generation to audit table
*/
private async logAudit(
auditData: Partial<DocumentNumberAudit>
): Promise<void> {
try {
// Ensure operation is set, default to CONFIRM if not provided
const dataToSave = {
...auditData,
operation: auditData.operation || 'CONFIRM',
};
await this.auditRepo.save(dataToSave);
} catch (error) {
this.logger.error('Failed to log audit', error);
}
}
/**
* [P0-4] Log error to error table
*/
private async logError(
errorData: Partial<DocumentNumberError>
): Promise<void> {
try {
await this.errorRepo.save(errorData);
} catch (error) {
this.logger.error('Failed to log error', error);
}
}
/**
* [P0-4] Classify error type for logging
*/
private classifyError(error: any): string {
if (error.message?.includes('lock') || error.message?.includes('Lock')) {
return 'LOCK_TIMEOUT';
}
if (error instanceof OptimisticLockVersionMismatchError) {
return 'VERSION_CONFLICT';
}
if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
return 'REDIS_ERROR';
}
if (error.name === 'QueryFailedError') {
return 'DB_ERROR';
}
return 'VALIDATION_ERROR';
}
// --- Log Retrieval for Admin UI ---
async getAuditLogs(limit = 100): Promise<DocumentNumberAudit[]> {
return this.auditRepo.find({
order: { createdAt: 'DESC' },
take: limit,
private async resolveRecipientCode(recipientId?: number): Promise<string> {
if (!recipientId) return 'GEN';
const org = await this.orgRepo.findOne({
where: { id: recipientId },
select: ['organizationCode'],
});
return org ? org.organizationCode : 'GEN';
}
async getErrorLogs(limit = 100): Promise<DocumentNumberError[]> {
return this.errorRepo.find({
order: { createdAt: 'DESC' },
take: limit,
private async resolveOrgCode(orgId?: number): Promise<string> {
if (!orgId) return 'GEN';
const org = await this.orgRepo.findOne({
where: { id: orgId },
select: ['organizationCode'],
});
return org ? org.organizationCode : 'GEN';
}
// --- Admin Operations ---
private async resolveDisciplineCode(disciplineId?: number): Promise<string> {
if (!disciplineId) return 'GEN';
const discipline = await this.disciplineRepo.findOne({
where: { id: disciplineId },
select: ['code'],
});
return discipline ? discipline.code : 'GEN';
}
/**
* Manual Override: Force set the counter to a specific number.
* Useful for aligning with legacy systems or skipping numbers.
*/
async manualOverride(dto: any): Promise<void> {
const {
projectId,
originatorId,
typeId,
disciplineId,
year,
newSequence,
reason,
userId,
} = dto;
const resourceKey = `doc_num:${projectId}:${typeId}:${disciplineId || 0}:${year || new Date().getFullYear()}`;
const lockTtl = 5000;
let lock;
private async logAudit(data: any): Promise<DocumentNumberAudit> {
const audit = this.auditRepo.create({
...data,
projectId: data.context.projectId,
createdBy: data.context.userId,
ipAddress: data.context.ipAddress,
});
return await this.auditRepo.save(audit);
}
private async logError(error: any, ctx: any, key: string) {
this.logger.error(
`Document Numbering Error: ${error.message}`,
error.stack
);
try {
lock = await this.redlock.acquire([resourceKey], lockTtl);
// Find or Create Counter
let counter = await this.counterRepo.findOne({
where: {
projectId,
originatorId,
recipientOrganizationId: dto.recipientOrganizationId ?? -1,
typeId,
subTypeId: dto.subTypeId ?? 0,
rfaTypeId: dto.rfaTypeId ?? 0,
disciplineId: disciplineId || 0,
year: year || new Date().getFullYear(),
},
const errorRecord = this.errorRepo.create({
projectId: ctx.projectId,
errorType: error.name || 'UnknownError',
errorMessage: error.message,
stackTrace: error.stack,
counterKey: key,
inputPayload: JSON.stringify(ctx),
});
if (!counter) {
counter = this.counterRepo.create({
projectId,
originatorId,
recipientOrganizationId: dto.recipientOrganizationId ?? -1,
typeId,
subTypeId: dto.subTypeId ?? 0,
rfaTypeId: dto.rfaTypeId ?? 0,
disciplineId: disciplineId || 0,
year: year || new Date().getFullYear(),
lastNumber: 0,
});
}
const oldNumber = counter.lastNumber;
if (newSequence <= oldNumber) {
// Warning: Manual override to lower number might cause collisions
this.logger.warn(
`Manual override to lower sequence: ${oldNumber} -> ${newSequence}`
);
}
counter.lastNumber = newSequence;
await this.counterRepo.save(counter);
// Log Audit
await this.logAudit({
generatedNumber: `MANUAL-${newSequence}`,
counterKey: { key: resourceKey },
templateUsed: 'MANUAL_OVERRIDE',
documentId: 0,
userId: userId,
operation: 'MANUAL_OVERRIDE',
metadata: { reason, oldNumber, newNumber: newSequence },
});
} catch (error) {
throw error;
} finally {
if (lock) await lock.release().catch(() => {});
await this.errorRepo.save(errorRecord);
} catch (e) {
this.logger.error('Failed to save error log', e);
}
}
/**
* Bulk Import: Set initial counters for migration.
*/
async bulkImport(items: any[]): Promise<void> {
for (const item of items) {
// Reuse manualOverride logic loosely, or implement bulk specific logic
// optimizing by not locking if we assume offline migration
// For safety, let's just update repo directly
await this.manualOverride(item);
}
}
/**
* Cancel Number: Mark a number as cancelled/skipped in Audit.
* Does NOT rollback counter (unless specified).
*/
async cancelNumber(dto: any): Promise<void> {
const { userId, generatedNumber, reason } = dto;
await this.logAudit({
generatedNumber,
counterKey: {},
templateUsed: 'N/A',
documentId: 0,
userId,
operation: 'CANCEL',
metadata: { reason },
});
}
/**
* Void and Replace: Mark old number as void, generate new one to replace it.
* Used when users made a mistake in critical fields.
*/
async voidAndReplace(dto: any): Promise<string> {
const { oldNumber, reason, newGenerationContext } = dto;
// 1. Audit old number as VOID_REPLACE
await this.logAudit({
generatedNumber: oldNumber,
counterKey: {},
templateUsed: 'N/A',
documentId: 0, // Should link to doc if possible
userId: newGenerationContext.userId,
operation: 'VOID_REPLACE',
metadata: { reason, replacedByNewGeneration: true },
});
// 2. Generate New Number
return this.generateNextNumber(newGenerationContext);
}
/**
* Update Number for Draft:
* Handles logic when a Draft document changes critical fields (Project, Type, etc.)
* - Tries to rollback the old number if it's the latest one.
* - Otherwise, voids the old number.
* - Generates a new number for the new context.
*/
async updateNumberForDraft(
oldNumber: string,
oldCtx: GenerateNumberContext,
newCtx: GenerateNumberContext
): Promise<string> {
const year = oldCtx.year || new Date().getFullYear();
const disciplineId = oldCtx.disciplineId || 0;
const resourceKey = `doc_num:${oldCtx.projectId}:${oldCtx.typeId}:${disciplineId}:${year}`;
const lockTtl = 5000;
let lock;
try {
// 1. Try Rollback Old Number
lock = await this.redlock.acquire([resourceKey], lockTtl);
const recipientId = oldCtx.recipientOrganizationId ?? -1;
const subTypeId = oldCtx.subTypeId ?? 0;
const rfaTypeId = oldCtx.rfaTypeId ?? 0;
const counter = await this.counterRepo.findOne({
where: {
projectId: oldCtx.projectId,
originatorId: oldCtx.originatorId,
recipientOrganizationId: recipientId,
typeId: oldCtx.typeId,
subTypeId: subTypeId,
rfaTypeId: rfaTypeId,
disciplineId: disciplineId,
year: year,
},
});
if (counter && counter.lastNumber > 0) {
// Construct what the number SHOULD be if it matches lastNumber
const tokens = await this.resolveTokens(oldCtx, year);
const { template } = await this.getFormatTemplateWithMeta(
oldCtx.projectId,
oldCtx.typeId
);
const expectedNumber = this.replaceTokens(
template,
tokens,
counter.lastNumber
);
if (expectedNumber === oldNumber) {
// MATCH! We can rollback.
counter.lastNumber -= 1;
await this.counterRepo.save(counter);
await this.logAudit({
generatedNumber: oldNumber,
counterKey: { key: resourceKey },
templateUsed: template,
documentId: 0,
userId: newCtx.userId,
operation: 'RESERVE', // Use RESERVE or CANCEL to indicate rollback/freed up
metadata: {
action: 'ROLLBACK_DRAFT',
reason: 'Critical field changed in Draft',
},
});
this.logger.log(
`Rolled back number ${oldNumber} (Seq ${counter.lastNumber + 1})`
);
} else {
// NO MATCH. Cannot rollback. Mark as VOID_REPLACE.
await this.logAudit({
generatedNumber: oldNumber,
counterKey: { key: resourceKey },
templateUsed: 'N/A',
documentId: 0,
userId: newCtx.userId,
operation: 'VOID_REPLACE',
metadata: {
reason:
'Critical field changed in Draft (Rollback failed - not latest)',
},
});
}
} else {
// Counter not found or 0. Just Void.
await this.logAudit({
generatedNumber: oldNumber,
counterKey: {},
templateUsed: 'N/A',
documentId: 0,
userId: newCtx.userId,
operation: 'VOID_REPLACE',
metadata: { reason: 'Critical field changed (Counter not found)' },
});
}
} catch (err) {
this.logger.warn(`Failed to rollback number ${oldNumber}: ${err as any}`);
// Fallback: Ensure we at least void it in audit if rollback failed logic
await this.logAudit({
generatedNumber: oldNumber,
counterKey: {},
templateUsed: 'N/A',
documentId: 0,
userId: newCtx.userId,
operation: 'VOID_REPLACE',
metadata: { reason: 'Rollback error' },
});
} finally {
if (lock) await lock.release().catch(() => {});
}
// 2. Generate New Number
return this.generateNextNumber(newCtx);
}
}

View File

@@ -1,3 +1,5 @@
// backend/src/modules/document-numbering/entities/document-number-format.entity.ts
import {
Entity,
Column,
@@ -5,43 +7,46 @@ import {
ManyToOne,
JoinColumn,
Unique,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { Project } from '../../project/entities/project.entity';
// เรายังไม่มี CorrespondenceType Entity เดี๋ยวสร้าง Dummy ไว้ก่อน หรือข้าม Relation ไปก่อนได้
// แต่ตามหลักควรมี CorrespondenceType (Master Data)
import { CorrespondenceType } from '../../correspondence/entities/correspondence-type.entity';
@Entity('document_number_formats')
@Unique(['projectId', 'correspondenceTypeId']) // 1 Project + 1 Type มีได้แค่ 1 Format
@Unique(['projectId', 'correspondenceTypeId'])
export class DocumentNumberFormat {
@PrimaryGeneratedColumn()
id!: number;
id: number;
@Column({ name: 'project_id' })
projectId!: number;
projectId: number;
@Column({ name: 'correspondence_type_id' })
correspondenceTypeId!: number;
@Column({ name: 'correspondence_type_id', nullable: true })
correspondenceTypeId: number | null;
@Column({ name: 'format_template', length: 255 })
formatTemplate!: string;
@Column({ name: 'format_template', length: 100 })
formatTemplate: string;
@Column({ name: 'discipline_id', default: 0 })
disciplineId!: number;
@Column({ name: 'description', nullable: true })
description: string;
@Column({ name: 'example_number', length: 100, nullable: true })
exampleNumber?: string;
// [NEW] Control yearly reset behavior
@Column({ name: 'reset_sequence_yearly', default: true })
resetSequenceYearly: boolean;
@Column({ name: 'padding_length', default: 4 })
paddingLength!: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@Column({ name: 'reset_annually', default: true })
resetAnnually!: boolean;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@Column({ name: 'is_active', default: true })
isActive!: boolean;
// Relation
// Relations
@ManyToOne(() => Project)
@JoinColumn({ name: 'project_id' })
project?: Project;
project: Project;
@ManyToOne(() => CorrespondenceType)
@JoinColumn({ name: 'correspondence_type_id' })
correspondenceType: CorrespondenceType | null;
}

View File

@@ -1,332 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
FAIL src/modules/project/project.controller.spec.ts
ΓùÅ Test suite failed to run
Cannot find module './project.service.js' from 'modules/project/project.controller.spec.ts'
1 | import { Controller, Get, UseGuards } from '@nestjs/common';
> 2 | import { ProjectService } from './project.service.js';
| ^
3 | import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
4 |
5 | @Controller('projects')
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (modules/project/project.controller.spec.ts:2:1)
FAIL src/common/auth/auth.controller.spec.ts
ΓùÅ Test suite failed to run
Cannot find module './auth.service.js' from 'common/auth/auth.controller.spec.ts'
1 | import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
> 2 | import { AuthService } from './auth.service.js';
| ^
3 | import { LoginDto } from './dto/login.dto.js'; // <--- Import DTO
4 | import { RegisterDto } from './dto/register.dto.js'; // <--- Import DTO
5 |
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (common/auth/auth.controller.spec.ts:2:1)
PASS src/app.controller.spec.ts
[Nest] 12996 - 12/09/2025, 8:21:59 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 12996 - 12/09/2025, 8:21:59 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
FAIL src/common/file-storage/file-storage.controller.spec.ts
ΓùÅ Test suite failed to run
Cannot find module './file-storage.service.js' from 'common/file-storage/file-storage.controller.ts'
Require stack:
common/file-storage/file-storage.controller.ts
common/file-storage/file-storage.controller.spec.ts
19 | import type { Response } from 'express';
20 | import { FileInterceptor } from '@nestjs/platform-express';
> 21 | import { FileStorageService } from './file-storage.service.js';
| ^
22 | import { JwtAuthGuard } from '../guards/jwt-auth.guard.js';
23 |
24 | // Interface เพื่อระบุ Type ของ Request ที่ผ่าน JwtAuthGuard มาแล้ว
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (common/file-storage/file-storage.controller.ts:21:1)
at Object.<anonymous> (common/file-storage/file-storage.controller.spec.ts:2:1)
[Nest] 47932 - 12/09/2025, 8:21:59 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\52879b7a-b717-41b2-8b41-54bf707b187b.pdf
[Nest] 47932 - 12/09/2025, 8:21:59 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/common/file-storage/file-storage.service.spec.ts
PASS src/modules/user/user.service.spec.ts
PASS src/common/auth/casl/ability.factory.spec.ts
[Nest] 45332 - 12/09/2025, 8:21:59 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
[Nest] 45332 - 12/09/2025, 8:21:59 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
[Nest] 33588 - 12/09/2025, 8:21:59 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
FAIL src/modules/document-numbering/document-numbering.service.spec.ts
● DocumentNumberingService › should be defined
TypeError: Cannot read properties of undefined (reading 'disconnect')
86 |
87 | onModuleDestroy() {
> 88 | this.redisClient.disconnect();
| ^
89 | }
90 |
91 | /**
at DocumentNumberingService.onModuleDestroy (modules/document-numbering/document-numbering.service.ts:88:22)
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:120:13)
● DocumentNumberingService › generateNextNumber › should generate a new number successfully
expect(received).toBe(expected) // Object.is equality
Expected: "000001"
Received: "0001"
146 | const result = await service.generateNextNumber(mockContext);
147 |
> 148 | expect(result).toBe('000001'); // Default padding 6
| ^
149 | expect(counterRepo.save).toHaveBeenCalled();
150 | expect(auditRepo.save).toHaveBeenCalled();
151 | });
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:148:22)
FAIL src/modules/project/project.service.spec.ts
● ProjectService › should be defined
Nest can't resolve dependencies of the ProjectService (?, OrganizationRepository). Please make sure that the argument "ProjectRepository" at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If "ProjectRepository" is a provider, is it part of the current RootTestModule?
- If "ProjectRepository" is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing "ProjectRepository" */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
6 |
7 | beforeEach(async () => {
> 8 | const module: TestingModule = await Test.createTestingModule({
| ^
9 | providers: [ProjectService],
10 | }).compile();
11 |
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 0)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadProvider (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:103:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:56:13
at async Promise.all (index 3)
at TestingInstanceLoader.createInstancesOfProviders (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:55:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:40:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/project/project.service.spec.ts:8:35)
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
FAIL src/common/auth/auth.service.spec.ts
ΓùÅ Test suite failed to run
Cannot find module '../../modules/user/user.service.js' from 'common/auth/auth.service.ts'
Require stack:
common/auth/auth.service.ts
common/auth/auth.service.spec.ts
20 | import * as crypto from 'crypto';
21 |
> 22 | import { UserService } from '../../modules/user/user.service.js';
| ^
23 | import { User } from '../../modules/user/entities/user.entity';
24 | import { RegisterDto } from './dto/register.dto.js';
25 | import { RefreshToken } from './entities/refresh-token.entity'; // [P2-2]
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (common/auth/auth.service.ts:22:1)
at Object.<anonymous> (common/auth/auth.service.spec.ts:2:1)
PASS src/modules/json-schema/json-schema.controller.spec.ts
FAIL src/modules/correspondence/correspondence.service.spec.ts
● CorrespondenceService › should be defined
Nest can't resolve dependencies of the CorrespondenceService (?, CorrespondenceRevisionRepository, CorrespondenceTypeRepository, CorrespondenceStatusRepository, RoutingTemplateRepository, CorrespondenceRoutingRepository, CorrespondenceReferenceRepository, DocumentNumberingService, JsonSchemaService, WorkflowEngineService, UserService, DataSource, SearchService). Please make sure that the argument "CorrespondenceRepository" at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If "CorrespondenceRepository" is a provider, is it part of the current RootTestModule?
- If "CorrespondenceRepository" is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing "CorrespondenceRepository" */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
6 |
7 | beforeEach(async () => {
> 8 | const module: TestingModule = await Test.createTestingModule({
| ^
9 | providers: [CorrespondenceService],
10 | }).compile();
11 |
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 0)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadProvider (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:103:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:56:13
at async Promise.all (index 3)
at TestingInstanceLoader.createInstancesOfProviders (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:55:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:40:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:8:35)
FAIL src/modules/correspondence/correspondence.controller.spec.ts
● CorrespondenceController › should be defined
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
7 |
8 | beforeEach(async () => {
> 9 | const module: TestingModule = await Test.createTestingModule({
| ^
10 | controllers: [CorrespondenceController],
11 | providers: [
12 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:9:35)
Test Suites: 9 failed, 6 passed, 15 total
Tests: 7 failed, 37 passed, 44 total
Snapshots: 0 total
Time: 5.054 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -1,461 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
FAIL src/modules/project/project.controller.spec.ts
ΓùÅ Test suite failed to run
Cannot find module './project.service.js' from 'modules/project/project.controller.spec.ts'
1 | import { Controller, Get, UseGuards } from '@nestjs/common';
> 2 | import { ProjectService } from './project.service.js';
| ^
3 | import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard.js';
4 |
5 | @Controller('projects')
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (modules/project/project.controller.spec.ts:2:1)
FAIL src/common/auth/auth.controller.spec.ts
ΓùÅ Test suite failed to run
Cannot find module './auth.service.js' from 'common/auth/auth.controller.spec.ts'
1 | import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
> 2 | import { AuthService } from './auth.service.js';
| ^
3 | import { LoginDto } from './dto/login.dto.js'; // <--- Import DTO
4 | import { RegisterDto } from './dto/register.dto.js'; // <--- Import DTO
5 |
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (common/auth/auth.controller.spec.ts:2:1)
PASS src/app.controller.spec.ts
[Nest] 15476 - 12/09/2025, 8:24:46 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
PASS src/common/auth/casl/ability.factory.spec.ts
FAIL src/modules/project/project.service.spec.ts
● ProjectService › should be defined
Nest can't resolve dependencies of the ProjectService (?, OrganizationRepository). Please make sure that the argument "ProjectRepository" at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If "ProjectRepository" is a provider, is it part of the current RootTestModule?
- If "ProjectRepository" is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing "ProjectRepository" */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
6 |
7 | beforeEach(async () => {
> 8 | const module: TestingModule = await Test.createTestingModule({
| ^
9 | providers: [ProjectService],
10 | }).compile();
11 |
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 0)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadProvider (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:103:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:56:13
at async Promise.all (index 3)
at TestingInstanceLoader.createInstancesOfProviders (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:55:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:40:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/project/project.service.spec.ts:8:35)
PASS src/modules/user/user.service.spec.ts
[Nest] 11892 - 12/09/2025, 8:24:47 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
[Nest] 11892 - 12/09/2025, 8:24:47 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
FAIL src/modules/document-numbering/document-numbering.service.spec.ts
● DocumentNumberingService › should be defined
TypeError: Cannot read properties of undefined (reading 'disconnect')
86 |
87 | onModuleDestroy() {
> 88 | this.redisClient.disconnect();
| ^
89 | }
90 |
91 | /**
at DocumentNumberingService.onModuleDestroy (modules/document-numbering/document-numbering.service.ts:88:22)
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:120:13)
● DocumentNumberingService › generateNextNumber › should generate a new number successfully
expect(received).toBe(expected) // Object.is equality
Expected: "000001"
Received: "0001"
146 | const result = await service.generateNextNumber(mockContext);
147 |
> 148 | expect(result).toBe('000001'); // Default padding 6
| ^
149 | expect(counterRepo.save).toHaveBeenCalled();
150 | expect(auditRepo.save).toHaveBeenCalled();
151 | });
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:148:22)
[Nest] 25292 - 12/09/2025, 8:24:47 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 25292 - 12/09/2025, 8:24:47 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
[Nest] 23608 - 12/09/2025, 8:24:47 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\96ed1798-25e1-45c8-8a5c-9875978ce586.pdf
[Nest] 23608 - 12/09/2025, 8:24:47 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/common/file-storage/file-storage.service.spec.ts
FAIL src/common/auth/auth.service.spec.ts
● AuthService › should be defined
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › validateUser › should return user without password if validation succeeds
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › validateUser › should return null if user not found
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › validateUser › should return null if password mismatch
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › login › should return access and refresh tokens
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › register › should register a new user
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › refreshToken › should return new tokens if valid
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
● AuthService › refreshToken › should throw UnauthorizedException if token revoked
TypeError: Cannot redefine property: compare
at Function.defineProperty (<anonymous>)
94 | // Mock bcrypt
95 | jest
> 96 | .spyOn(bcrypt, 'compare')
| ^
97 | .mockImplementation(() => Promise.resolve(true));
98 | jest
99 | .spyOn(bcrypt, 'hash')
at ModuleMocker.spyOn (../node_modules/jest-mock/build/index.js:616:16)
at Object.<anonymous> (common/auth/auth.service.spec.ts:96:8)
FAIL src/modules/correspondence/correspondence.service.spec.ts
● CorrespondenceService › should be defined
Nest can't resolve dependencies of the CorrespondenceService (?, CorrespondenceRevisionRepository, CorrespondenceTypeRepository, CorrespondenceStatusRepository, RoutingTemplateRepository, CorrespondenceRoutingRepository, CorrespondenceReferenceRepository, DocumentNumberingService, JsonSchemaService, WorkflowEngineService, UserService, DataSource, SearchService). Please make sure that the argument "CorrespondenceRepository" at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If "CorrespondenceRepository" is a provider, is it part of the current RootTestModule?
- If "CorrespondenceRepository" is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing "CorrespondenceRepository" */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
6 |
7 | beforeEach(async () => {
> 8 | const module: TestingModule = await Test.createTestingModule({
| ^
9 | providers: [CorrespondenceService],
10 | }).compile();
11 |
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 0)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadProvider (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:103:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:56:13
at async Promise.all (index 3)
at TestingInstanceLoader.createInstancesOfProviders (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:55:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:40:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:8:35)
FAIL src/common/file-storage/file-storage.controller.spec.ts
● FileStorageController › should be defined
Nest can't resolve dependencies of the FileStorageController (?). Please make sure that the argument FileStorageService at index [0] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If FileStorageService is a provider, is it part of the current RootTestModule?
- If FileStorageService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing FileStorageService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
6 |
7 | beforeEach(async () => {
> 8 | const module: TestingModule = await Test.createTestingModule({
| ^
9 | controllers: [FileStorageController],
10 | }).compile();
11 |
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 0)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadController (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:94:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:68:13
at async Promise.all (index 0)
at TestingInstanceLoader.createInstancesOfControllers (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:67:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:42:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (common/file-storage/file-storage.controller.spec.ts:8:35)
PASS src/modules/json-schema/json-schema.controller.spec.ts
FAIL src/modules/correspondence/correspondence.controller.spec.ts
● CorrespondenceController › should be defined
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
7 |
8 | beforeEach(async () => {
> 9 | const module: TestingModule = await Test.createTestingModule({
| ^
10 | controllers: [CorrespondenceController],
11 | providers: [
12 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:9:35)
Test Suites: 9 failed, 6 passed, 15 total
Tests: 16 failed, 37 passed, 53 total
Snapshots: 0 total
Time: 5.448 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -1,440 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
PASS src/app.controller.spec.ts
[Nest] 18060 - 12/09/2025, 8:27:42 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
[Nest] 18060 - 12/09/2025, 8:27:43 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
FAIL src/modules/document-numbering/document-numbering.service.spec.ts
● DocumentNumberingService › should be defined
TypeError: Cannot read properties of undefined (reading 'disconnect')
86 |
87 | onModuleDestroy() {
> 88 | this.redisClient.disconnect();
| ^
89 | }
90 |
91 | /**
at DocumentNumberingService.onModuleDestroy (modules/document-numbering/document-numbering.service.ts:88:22)
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:120:13)
● DocumentNumberingService › generateNextNumber › should generate a new number successfully
expect(received).toBe(expected) // Object.is equality
Expected: "000001"
Received: "0001"
146 | const result = await service.generateNextNumber(mockContext);
147 |
> 148 | expect(result).toBe('000001'); // Default padding 6
| ^
149 | expect(counterRepo.save).toHaveBeenCalled();
150 | expect(auditRepo.save).toHaveBeenCalled();
151 | });
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:148:22)
[Nest] 14304 - 12/09/2025, 8:27:43 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
[Nest] 15080 - 12/09/2025, 8:27:43 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 15080 - 12/09/2025, 8:27:43 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
[Nest] 32376 - 12/09/2025, 8:27:43 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\8d470748-51dd-4d41-8b23-4c597fac61ae.pdf
PASS src/common/auth/casl/ability.factory.spec.ts
[Nest] 32376 - 12/09/2025, 8:27:43 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/common/file-storage/file-storage.service.spec.ts
PASS src/modules/user/user.service.spec.ts
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
FAIL src/modules/project/project.service.spec.ts
ΓùÅ Test suite failed to run
Cannot find module '../organization/entities/organization.entity' from 'modules/project/project.service.spec.ts'
3 | import { ProjectService } from './project.service';
4 | import { Project } from './entities/project.entity';
> 5 | import { Organization } from '../organization/entities/organization.entity';
| ^
6 |
7 | describe('ProjectService', () => {
8 | let service: ProjectService;
at Resolver._throwModNotFoundError (../node_modules/jest-resolve/build/index.js:863:11)
at Object.<anonymous> (modules/project/project.service.spec.ts:5:1)
PASS src/common/auth/auth.service.spec.ts
ΓùÅ Console
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
🔍 Checking login for: unknown
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
❌ User not found in database
at AuthService.validateUser (common/auth/auth.service.ts:51:15)
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
PASS src/common/file-storage/file-storage.controller.spec.ts
FAIL src/modules/correspondence/correspondence.service.spec.ts (5.059 s)
● CorrespondenceService › findAll › should return paginated correspondences
expect(received).toBeDefined()
Received: undefined
119 | it('should return paginated correspondences', async () => {
120 | const result = await service.findAll({ projectId: 1 });
> 121 | expect(result.data).toBeDefined();
| ^
122 | expect(result.meta).toBeDefined();
123 | });
124 | });
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:121:27)
PASS src/common/auth/auth.controller.spec.ts (5.065 s)
PASS src/modules/json-schema/json-schema.controller.spec.ts
FAIL src/modules/project/project.controller.spec.ts (5.155 s)
● ProjectController › should be defined
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
17 | };
18 |
> 19 | const module: TestingModule = await Test.createTestingModule({
| ^
20 | controllers: [ProjectController],
21 | providers: [
22 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/project/project.controller.spec.ts:19:35)
● ProjectController › findAll › should call projectService.findAll
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
17 | };
18 |
> 19 | const module: TestingModule = await Test.createTestingModule({
| ^
20 | controllers: [ProjectController],
21 | providers: [
22 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/project/project.controller.spec.ts:19:35)
● ProjectController › findAllOrganizations › should call projectService.findAllOrganizations
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
17 | };
18 |
> 19 | const module: TestingModule = await Test.createTestingModule({
| ^
20 | controllers: [ProjectController],
21 | providers: [
22 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/project/project.controller.spec.ts:19:35)
FAIL src/modules/correspondence/correspondence.controller.spec.ts (5.56 s)
● CorrespondenceController › should be defined
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
21 | };
22 |
> 23 | const module: TestingModule = await Test.createTestingModule({
| ^
24 | controllers: [CorrespondenceController],
25 | providers: [
26 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:23:35)
● CorrespondenceController › findAll › should return paginated correspondences
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
21 | };
22 |
> 23 | const module: TestingModule = await Test.createTestingModule({
| ^
24 | controllers: [CorrespondenceController],
25 | providers: [
26 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:23:35)
● CorrespondenceController › create › should create a correspondence
Nest can't resolve dependencies of the RbacGuard (Reflector, ?). Please make sure that the argument UserService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If UserService is a provider, is it part of the current RootTestModule?
- If UserService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing UserService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
21 | };
22 |
> 23 | const module: TestingModule = await Test.createTestingModule({
| ^
24 | controllers: [CorrespondenceController],
25 | providers: [
26 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadInjectable (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:99:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:80:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstancesOfInjectables (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:79:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:41:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:23:35)
Test Suites: 6 failed, 9 passed, 15 total
Tests: 11 failed, 52 passed, 63 total
Snapshots: 0 total
Time: 6.881 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -1,202 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
PASS src/app.controller.spec.ts
PASS src/modules/user/user.service.spec.ts
[Nest] 3520 - 12/09/2025, 8:29:38 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\0db75d72-efc1-4d36-a739-6fdeccb9f53a.pdf
[Nest] 3520 - 12/09/2025, 8:29:38 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/common/file-storage/file-storage.service.spec.ts
[Nest] 38888 - 12/09/2025, 8:29:38 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
[Nest] 38888 - 12/09/2025, 8:29:38 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
[Nest] 16508 - 12/09/2025, 8:29:38 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
FAIL src/modules/document-numbering/document-numbering.service.spec.ts
● DocumentNumberingService › should be defined
TypeError: Cannot read properties of undefined (reading 'disconnect')
86 |
87 | onModuleDestroy() {
> 88 | this.redisClient.disconnect();
| ^
89 | }
90 |
91 | /**
at DocumentNumberingService.onModuleDestroy (modules/document-numbering/document-numbering.service.ts:88:22)
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:120:13)
● DocumentNumberingService › generateNextNumber › should generate a new number successfully
expect(received).toBe(expected) // Object.is equality
Expected: "000001"
Received: "0001"
146 | const result = await service.generateNextNumber(mockContext);
147 |
> 148 | expect(result).toBe('000001'); // Default padding 6
| ^
149 | expect(counterRepo.save).toHaveBeenCalled();
150 | expect(auditRepo.save).toHaveBeenCalled();
151 | });
at Object.<anonymous> (modules/document-numbering/document-numbering.service.spec.ts:148:22)
PASS src/common/auth/casl/ability.factory.spec.ts
PASS src/modules/project/project.service.spec.ts
[Nest] 16436 - 12/09/2025, 8:29:39 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 16436 - 12/09/2025, 8:29:39 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
PASS src/common/auth/auth.service.spec.ts
ΓùÅ Console
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
🔍 Checking login for: unknown
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
❌ User not found in database
at AuthService.validateUser (common/auth/auth.service.ts:51:15)
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
PASS src/common/file-storage/file-storage.controller.spec.ts
PASS src/modules/project/project.controller.spec.ts
PASS src/modules/json-schema/json-schema.controller.spec.ts
PASS src/common/auth/auth.controller.spec.ts
FAIL src/modules/correspondence/correspondence.service.spec.ts
● CorrespondenceService › findAll › should return paginated correspondences
expect(received).toBeDefined()
Received: undefined
119 | it('should return paginated correspondences', async () => {
120 | const result = await service.findAll({ projectId: 1 });
> 121 | expect(result.data).toBeDefined();
| ^
122 | expect(result.meta).toBeDefined();
123 | });
124 | });
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:121:27)
FAIL src/modules/correspondence/correspondence.controller.spec.ts (5.39 s)
● CorrespondenceController › create › should create a correspondence
expect(jest.fn()).toHaveBeenCalledWith(...expected)
- Expected
+ Received
{"correspondence_type_id": 1, "project_id": 1, "subject": "Test Subject"},
- 1,
+ {"userId": 1},
Number of calls: 1
76 | const result = await controller.create(createDto as any, mockReq as any);
77 |
> 78 | expect(mockCorrespondenceService.create).toHaveBeenCalledWith(
| ^
79 | createDto,
80 | 1
81 | );
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:78:48)
Test Suites: 4 failed, 11 passed, 15 total
Tests: 6 failed, 60 passed, 66 total
Snapshots: 0 total
Time: 6.662 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -1,148 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
PASS src/app.controller.spec.ts
[Nest] 2908 - 12/09/2025, 8:31:31 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 2908 - 12/09/2025, 8:31:31 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
PASS src/common/auth/casl/ability.factory.spec.ts
PASS src/common/auth/auth.service.spec.ts
ΓùÅ Console
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
🔍 Checking login for: unknown
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
❌ User not found in database
at AuthService.validateUser (common/auth/auth.service.ts:51:15)
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
PASS src/modules/user/user.service.spec.ts
[Nest] 8380 - 12/09/2025, 8:31:32 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
PASS src/modules/project/project.service.spec.ts
[Nest] 7492 - 12/09/2025, 8:31:32 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\d95da96c-c9b6-4b11-9d67-9f4e19c76b8b.pdf
[Nest] 7492 - 12/09/2025, 8:31:32 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/modules/json-schema/json-schema.controller.spec.ts
[Nest] 45732 - 12/09/2025, 8:31:32 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
PASS src/common/file-storage/file-storage.service.spec.ts
[Nest] 45732 - 12/09/2025, 8:31:32 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
PASS src/modules/document-numbering/document-numbering.service.spec.ts
PASS src/modules/project/project.controller.spec.ts
PASS src/common/file-storage/file-storage.controller.spec.ts
FAIL src/modules/correspondence/correspondence.service.spec.ts
● CorrespondenceService › findAll › should return paginated correspondences
expect(received).toBeDefined()
Received: undefined
119 | it('should return paginated correspondences', async () => {
120 | const result = await service.findAll({ projectId: 1 });
> 121 | expect(result.data).toBeDefined();
| ^
122 | expect(result.meta).toBeDefined();
123 | });
124 | });
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:121:27)
PASS src/common/auth/auth.controller.spec.ts
PASS src/modules/correspondence/correspondence.controller.spec.ts (5.12 s)
Test Suites: 2 failed, 13 passed, 15 total
Tests: 3 failed, 63 passed, 66 total
Snapshots: 0 total
Time: 6.447 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -1,270 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
PASS src/app.controller.spec.ts
PASS src/common/auth/casl/ability.factory.spec.ts
PASS src/modules/user/user.service.spec.ts
PASS src/modules/project/project.service.spec.ts
[Nest] 29008 - 12/09/2025, 9:27:51 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
[Nest] 38744 - 12/09/2025, 9:27:51 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 38744 - 12/09/2025, 9:27:51 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
[Nest] 8912 - 12/09/2025, 9:27:52 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
[Nest] 8912 - 12/09/2025, 9:27:52 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
PASS src/modules/document-numbering/document-numbering.service.spec.ts
[Nest] 2912 - 12/09/2025, 9:27:52 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\14ebbf1a-e734-4b42-aa7b-bc93f75a48f4.pdf
[Nest] 2912 - 12/09/2025, 9:27:52 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/common/file-storage/file-storage.service.spec.ts
PASS src/common/auth/auth.service.spec.ts
ΓùÅ Console
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
🔍 Checking login for: unknown
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
❌ User not found in database
at AuthService.validateUser (common/auth/auth.service.ts:51:15)
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
PASS src/common/file-storage/file-storage.controller.spec.ts
PASS src/modules/json-schema/json-schema.controller.spec.ts
PASS src/modules/project/project.controller.spec.ts
PASS src/common/auth/auth.controller.spec.ts
FAIL src/modules/correspondence/correspondence.service.spec.ts
● CorrespondenceService › findAll › should return paginated correspondences
expect(received).toBeDefined()
Received: undefined
119 | it('should return paginated correspondences', async () => {
120 | const result = await service.findAll({ projectId: 1 });
> 121 | expect(result.data).toBeDefined();
| ^
122 | expect(result.meta).toBeDefined();
123 | });
124 | });
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:121:27)
FAIL src/modules/correspondence/correspondence.controller.spec.ts
● CorrespondenceController › should be defined
Nest can't resolve dependencies of the CorrespondenceController (CorrespondenceService, ?). Please make sure that the argument CorrespondenceWorkflowService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If CorrespondenceWorkflowService is a provider, is it part of the current RootTestModule?
- If CorrespondenceWorkflowService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing CorrespondenceWorkflowService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
23 | };
24 |
> 25 | const module: TestingModule = await Test.createTestingModule({
| ^
26 | controllers: [CorrespondenceController],
27 | providers: [
28 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadController (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:94:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:68:13
at async Promise.all (index 0)
at TestingInstanceLoader.createInstancesOfControllers (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:67:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:42:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:25:35)
● CorrespondenceController › findAll › should return paginated correspondences
Nest can't resolve dependencies of the CorrespondenceController (CorrespondenceService, ?). Please make sure that the argument CorrespondenceWorkflowService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If CorrespondenceWorkflowService is a provider, is it part of the current RootTestModule?
- If CorrespondenceWorkflowService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing CorrespondenceWorkflowService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
23 | };
24 |
> 25 | const module: TestingModule = await Test.createTestingModule({
| ^
26 | controllers: [CorrespondenceController],
27 | providers: [
28 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadController (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:94:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:68:13
at async Promise.all (index 0)
at TestingInstanceLoader.createInstancesOfControllers (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:67:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:42:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:25:35)
● CorrespondenceController › create › should create a correspondence
Nest can't resolve dependencies of the CorrespondenceController (CorrespondenceService, ?). Please make sure that the argument CorrespondenceWorkflowService at index [1] is available in the RootTestModule context.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If CorrespondenceWorkflowService is a provider, is it part of the current RootTestModule?
- If CorrespondenceWorkflowService is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing CorrespondenceWorkflowService */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errors
23 | };
24 |
> 25 | const module: TestingModule = await Test.createTestingModule({
| ^
26 | controllers: [CorrespondenceController],
27 | providers: [
28 | {
at TestingInjector.lookupComponentInParentModules (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:286:19)
at TestingInjector.resolveComponentWrapper (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-injector.js:19:45)
at resolveParam (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:140:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:169:27)
at TestingInjector.loadInstance (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:75:13)
at TestingInjector.loadController (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/injector.js:94:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:68:13
at async Promise.all (index 0)
at TestingInstanceLoader.createInstancesOfControllers (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:67:9)
at ../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:42:13
at async Promise.all (index 1)
at TestingInstanceLoader.createInstances (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:39:9)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+core@11.1.9_@nestjs_89e063bd3a6d5071b082cab065bf34d7/node_modules/@nestjs/core/injector/instance-loader.js:22:13)
at TestingInstanceLoader.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-instance-loader.js:9:9)
at TestingModuleBuilder.createInstancesOfDependencies (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:118:9)
at TestingModuleBuilder.compile (../../node_modules/.pnpm/@nestjs+testing@11.1.9_@nes_5fa0f54bf7d8c8acec998f5e81836857/node_modules/@nestjs/testing/testing-module.builder.js:74:9)
at Object.<anonymous> (modules/correspondence/correspondence.controller.spec.ts:25:35)
Test Suites: 3 failed, 12 passed, 15 total
Tests: 6 failed, 60 passed, 66 total
Snapshots: 0 total
Time: 5.303 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -1,148 +0,0 @@
> backend@1.5.1 test
> jest --forceExit
PASS src/app.controller.spec.ts
PASS src/modules/user/user.service.spec.ts
[Nest] 1796 - 12/09/2025, 9:47:09 AM ERROR [WorkflowEngineService] Transition Failed for inst-1: DB Error
PASS src/modules/workflow-engine/workflow-engine.service.spec.ts
[Nest] 35984 - 12/09/2025, 9:47:09 AM ERROR [WorkflowDslParser] Failed to parse stored DSL for definition 1
[Nest] 35984 - 12/09/2025, 9:47:09 AM ERROR [WorkflowDslParser] ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
at WorkflowDslParser.getParsedDsl (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.ts:163:32)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\workflow-engine\dsl\parser.service.spec.ts:178:22)
FAIL src/modules/workflow-engine/dsl/parser.service.spec.ts
● WorkflowDslParser › parse › should parse valid RFA workflow DSL
expect(received).toBe(expected) // Object.is equality
Expected: "RFA_APPROVAL"
Received: undefined
41 |
42 | expect(result).toBeDefined();
> 43 | expect(result.name).toBe('RFA_APPROVAL');
| ^
44 | expect(result.version).toBe('1.0.0');
45 | expect(result.isActive).toBe(true);
46 | expect(mockRepository.save).toHaveBeenCalled();
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:43:27)
● WorkflowDslParser › getParsedDsl › should retrieve and parse stored DSL
BadRequestException: Invalid stored DSL: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]
167 | error
168 | );
> 169 | throw new BadRequestException(`Invalid stored DSL: ${error?.message}`);
| ^
170 | }
171 | }
172 |
at WorkflowDslParser.getParsedDsl (modules/workflow-engine/dsl/parser.service.ts:169:13)
at Object.<anonymous> (modules/workflow-engine/dsl/parser.service.spec.ts:178:22)
PASS src/modules/project/project.service.spec.ts
PASS src/common/auth/casl/ability.factory.spec.ts
[Nest] 22776 - 12/09/2025, 9:47:09 AM ERROR [DocumentNumberingService] Error generating number for doc_num:1:1:1:2025
[Nest] 22776 - 12/09/2025, 9:47:09 AM ERROR [DocumentNumberingService] InternalServerErrorException: Failed to generate document number after retries.
at DocumentNumberingService.generateNextNumber (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.ts:182:13)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\modules\document-numbering\document-numbering.service.spec.ts:175:7) {
response: {
message: 'Failed to generate document number after retries.',
error: 'Internal Server Error',
statusCode: 500
},
status: 500,
options: {}
}
PASS src/modules/document-numbering/document-numbering.service.spec.ts
[Nest] 39316 - 12/09/2025, 9:47:09 AM ERROR [FileStorageService] Failed to write file: D:\nap-dms.lcbp3\backend\uploads\temp\cc1049ce-8717-4cc5-807b-a5793373fa3f.pdf
[Nest] 39316 - 12/09/2025, 9:47:09 AM ERROR [FileStorageService] Error: Write error
at Object.<anonymous> (D:\nap-dms.lcbp3\backend\src\common\file-storage\file-storage.service.spec.ts:90:9)
at Promise.finally.completed (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1557:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1497:10)
at _callCircusTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1007:40)
at processTicksAndRejections (node:internal/process/task_queues:105:5)
at _runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:947:3)
at D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:849:7
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:862:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at _runTestsForDescribeBlock (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:857:11)
at run (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:761:3)
at runAndTransformResultsToJestFormat (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\jestAdapterInit.js:1918:21)
at jestAdapter (D:\nap-dms.lcbp3\backend\node_modules\jest-circus\build\runner.js:101:19)
at runTestInternal (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:277:16)
at runTest (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:345:7)
at Object.worker (D:\nap-dms.lcbp3\backend\node_modules\jest-runner\build\testWorker.js:499:12)
PASS src/common/file-storage/file-storage.service.spec.ts
PASS src/common/auth/auth.service.spec.ts
ΓùÅ Console
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
🔍 Checking login for: unknown
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
console.log
❌ User not found in database
at AuthService.validateUser (common/auth/auth.service.ts:51:15)
console.log
🔍 Checking login for: testuser
at AuthService.validateUser (common/auth/auth.service.ts:43:13)
PASS src/common/file-storage/file-storage.controller.spec.ts
PASS src/modules/project/project.controller.spec.ts
PASS src/common/auth/auth.controller.spec.ts
PASS src/modules/json-schema/json-schema.controller.spec.ts
FAIL src/modules/correspondence/correspondence.service.spec.ts
● CorrespondenceService › findAll › should return paginated correspondences
expect(received).toBeDefined()
Received: undefined
119 | it('should return paginated correspondences', async () => {
120 | const result = await service.findAll({ projectId: 1 });
> 121 | expect(result.data).toBeDefined();
| ^
122 | expect(result.meta).toBeDefined();
123 | });
124 | });
at Object.<anonymous> (modules/correspondence/correspondence.service.spec.ts:121:27)
PASS src/modules/correspondence/correspondence.controller.spec.ts
Test Suites: 2 failed, 13 passed, 15 total
Tests: 3 failed, 64 passed, 67 total
Snapshots: 0 total
Time: 4.901 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?

View File

@@ -42,9 +42,9 @@
"next": "^16.0.7",
"next-auth": "5.0.0-beta.30",
"next-themes": "^0.4.6",
"react": "^18",
"react": "^19.0.0",
"react-day-picker": "^9.12.0",
"react-dom": "^18",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.8",
"react-hook-form": "^7.66.1",
"reactflow": "^11.11.4",
@@ -61,8 +61,8 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/uuid": "^11.0.0",
"@vitejs/plugin-react": "^5.1.2",
"autoprefixer": "^10.4.22",

1087
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,10 @@
---
title: 'Implementation Guide: Document Numbering System'
version: 1.5.1
version: 1.6.1
status: draft
owner: Development Team
last_updated: 2025-12-02
last_updated: 2025-12-16
related:
- specs/01-requirements/03.11-document-numbering.md
@@ -31,6 +31,21 @@ related:
### 1.1. Counter Table Schema
```sql
CREATE TABLE document_number_formats (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
correspondence_type_id INT NULL, -- NULL indicates default format for the project
format_template VARCHAR(100) NOT NULL,
reset_sequence_yearly TINYINT(1) DEFAULT 1,
description VARCHAR(255),
created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),
updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
UNIQUE KEY idx_unique_project_type (project_id, correspondence_type_id),
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE
);
CREATE TABLE document_number_counters (
project_id INT NOT NULL,
originator_organization_id INT NOT NULL,
@@ -73,14 +88,15 @@ CREATE TABLE document_number_counters (
```sql
CREATE TABLE document_number_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
document_id INT NOT NULL,
document_id INT NULL COMMENT 'FK to documents (NULL initially, updated after doc creation)',
generated_number VARCHAR(100) NOT NULL,
counter_key JSON NOT NULL COMMENT 'Counter key used (JSON format)',
template_used VARCHAR(200) NOT NULL,
user_id INT NOT NULL,
user_id INT NULL COMMENT 'FK to users (Allow NULL for system generation)',
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_success BOOLEAN DEFAULT TRUE COMMENT 'Track success/failure status',
-- Performance & Error Tracking
retry_count INT DEFAULT 0,
@@ -152,7 +168,50 @@ src/modules/document-numbering/
└── metrics.service.ts
```
### 2.2. TypeORM Entity
### 2.2. Number Generation Process
#### 2.2.1. Resolve Format Template:
* Query document_number_formats by project_id + type_id.
* If no result, query by project_id + NULL (Default Project Format).
* If still no result, apply System Default Template: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`.
* Determine resetSequenceYearly flag from the found format (default: true)
#### 2.2.2. Determine Counter Key:
* If resetSequenceYearly is True: Use Current Year (e.g., 2025).
* If resetSequenceYearly is False: Use 0 (Continuous).
* Use type_id from the resolved format (Specific ID or NULL).
#### 2.2.3. Generate Number:
* Use format template to generate number.
* Replace tokens with actual values:
* {PROJECT} -> Project Code
* {ORG} -> Originator Organization Code
* {RECIPIENT} -> Recipient Organization Code
* {TYPE} -> Type Code
* {YEAR} -> Current Year
* {SEQ} -> Sequence Number
* {REV} -> Revision Number
#### 2.2.4. Validate Number:
* Check if generated number is unique.
* If not unique, increment sequence and retry.
#### 2.2.5. Update Counter:
* Update document_number_counters with new sequence.
#### 2.2.6. Generate Audit Record:
* Create audit record with:
* Generated number
* Counter key used
* Template used
* User ID
* IP Address
* User Agent
#### 2.2.7. Return Generated Number:
* Return generated number to caller.
### 2.3. TypeORM Entity
```typescript
// File: src/modules/document-numbering/entities/document-number-counter.entity.ts
@@ -192,7 +251,7 @@ export class DocumentNumberCounter {
}
```
### 2.3. Redis Lock Service
### 2.4. Redis Lock Service
```typescript
// File: src/modules/document-numbering/services/document-numbering-lock.service.ts

View File

@@ -122,20 +122,19 @@ LCBP3-DMS ต้องสร้างเลขที่เอกสารอั
```sql
-- Format Templates
CREATE TABLE document_number_configs (
CREATE TABLE document_number_formats (
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
doc_type_id INT NOT NULL COMMENT 'Correspondence, RFA, Transmittal, Drawing',
sub_type_id INT DEFAULT 0 COMMENT 'ประเภทย่อย (nullable, use 0 for fallback)',
discipline_id INT DEFAULT 0 COMMENT 'สาขาวิชา (nullable, use 0 for fallback)',
template VARCHAR(255) NOT NULL COMMENT 'e.g. {PROJECT}-{ORG}-{TYPE}-{DISCIPLINE}-{SEQ:4}-{REV}',
correspondence_type_id INT NULL COMMENT 'Specific Type ID, or NULL for Project Default', -- CHANGED: Allow NULL
format_template VARCHAR(100) NOT NULL COMMENT 'e.g. {PROJECT}-{TYPE}-{YEAR}-{SEQ:4}',
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
version INT DEFAULT 0 NOT NULL COMMENT 'For template versioning',
FOREIGN KEY (project_id) REFERENCES projects(id),
FOREIGN KEY (doc_type_id) REFERENCES document_types(id),
UNIQUE KEY unique_config (project_id, doc_type_id, sub_type_id, discipline_id)
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types(id) ON DELETE CASCADE,
-- Note: Application logic must enforce single default format per project
UNIQUE KEY unique_format (project_id, correspondence_type_id)
) ENGINE=InnoDB COMMENT='Template configurations for document numbering';
-- Counter Table with Optimistic Locking
@@ -208,6 +207,19 @@ CREATE TABLE document_number_audit (
>
> **Always refer to**: [03.11-document-numbering.md](../01-requirements/03.11-document-numbering.md) as source of truth
### Format Resolution Strategy (Fallback Logic)
The system resolves the numbering format using the following priority:
1. **Specific Format:** Search for a record matching both `project_id` and `correspondence_type_id`.
2. **Default Format:** If not found, search for a record with matching `project_id` where `correspondence_type_id` is `NULL`.
3. **System Fallback:** If neither exists, use the hardcoded system default: `{ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE}`.
| Priority | Scenario | Template Source | Counter Scope (Key) | Reset Behavior |
| --- | --- | --- | --- | --- |
| 1 | Specific Format Found | Database (project_id, type_id) | Specific Type (type_id) | Based on reset_sequence_yearly flag |
| 2 | Default Format Found | Database (project_id, type_id=NULL) | Shared Counter (type_id=NULL) | Based on reset_sequence_yearly flag |
| 3 | Fallback (No Config) | System Default: {ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE} | Shared Counter (type_id=NULL) | Reset Yearly (Default: True) |
### Format Examples by Document Type
#### 1. Correspondence (หนังสือราชการ)

View File

@@ -107,7 +107,7 @@ LCBP3-DMS ต้องเลือก Technology Stack สำหรับพั
| Component | Technology | Rationale |
| :-------------------- | :------------------ | :------------------------------------- |
| **Framework** | Next.js 14+ | App Router, SSR/SSG, React integration |
| **UI Library** | React 18 | Industry standard, large ecosystem |
| **UI Library** | React 19 | Industry standard, large ecosystem |
| **Language** | TypeScript 5.x | Consistency with backend |
| **Styling** | Tailwind CSS | Utility-first, fast development |
| **Component Library** | shadcn/ui | Accessible, customizable, TypeScript |

View File

@@ -963,9 +963,10 @@ CREATE TABLE contract_drawing_attachments (
CREATE TABLE document_number_formats (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของตาราง',
project_id INT NOT NULL COMMENT 'โครงการ',
correspondence_type_id INT NOT NULL COMMENT 'ประเภทเอกสาร',
correspondence_type_id INT NULL COMMENT 'ประเภทเอกสาร',
discipline_id INT DEFAULT 0 COMMENT 'สาขางาน (0 = ทุกสาขา/ไม่ระบุ)',
format_template VARCHAR(255) NOT NULL COMMENT 'รูปแบบ Template (เช่น {ORG_CODE}-{TYPE_CODE}-{SEQ:4})',
format_template VARCHAR(255) NOT NULL COMMENT 'รูปแบบ Template (เช่น {ORG}-{RECIPIENT}-{SEQ:4}-{YEAR:BE})',
reset_sequence_yearly TINYINT(1) DEFAULT 1,
example_number VARCHAR(100) COMMENT 'ตัวอย่างเลขที่ได้จาก Template',
padding_length INT DEFAULT 4 COMMENT 'ความยาวของลำดับเลข (Padding)',
reset_annually BOOLEAN DEFAULT TRUE COMMENT 'เริ่มนับใหม่ทุกปี',
@@ -975,10 +976,9 @@ CREATE TABLE document_number_formats (
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'วันที่แก้ไขล่าสุด',
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE,
FOREIGN KEY (correspondence_type_id) REFERENCES correspondence_types (id) ON DELETE CASCADE,
UNIQUE KEY uk_proj_type_disc (
UNIQUE KEY unique_format (
project_id,
correspondence_type_id,
discipline_id
correspondence_type_id
)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตาราง Master เก็บ "รูปแบบ" Template ของเลขที่เอกสาร';