690329:2209 Fixing refactor Correspondence GPT-5.3-Codex #04
CI / CD Pipeline / build (push) Successful in 20m35s
CI / CD Pipeline / deploy (push) Successful in 10m59s

This commit is contained in:
2026-03-29 22:09:40 +07:00
parent df3020012d
commit abbdebf2b9
21 changed files with 4426 additions and 24 deletions
@@ -1 +1 @@
{"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\due-date-reminder.service.spec.ts":[1,1598],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.service.spec.ts":[1,7578],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.service.spec.ts":[1,1526],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\document-numbering.service.spec.ts":[1,1474],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\casl\\ability.factory.spec.ts":[1,921],"E:\\np-dms\\lcbp3\\backend\\src\\common\\services\\uuid-resolver.service.spec.ts":[1,1166],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.service.spec.ts":[1,1175],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\parser.service.spec.ts":[1,4448],"E:\\np-dms\\lcbp3\\backend\\src\\common\\pipes\\parse-uuid.pipe.spec.ts":[1,369],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\user\\user.service.spec.ts":[1,1074],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.service.spec.ts":[1,1277],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.controller.spec.ts":[1,8589],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.service.spec.ts":[1,1251],"E:\\np-dms\\lcbp3\\backend\\src\\common\\entities\\uuid-base.entity.spec.ts":[1,460],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.service.spec.ts":[1,1043],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.controller.spec.ts":[1,2047],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\services\\manual-override.service.spec.ts":[1,936],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.controller.spec.ts":[1,1666],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.controller.spec.ts":[1,6122],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.controller.spec.ts":[1,1859],"E:\\np-dms\\lcbp3\\backend\\src\\app.controller.spec.ts":[1,564],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\json-schema\\json-schema.controller.spec.ts":[1,3140]}
{"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\due-date-reminder.service.spec.ts":[1,1598],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.service.spec.ts":[1,5627],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.service.spec.ts":[1,1526],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\document-numbering.service.spec.ts":[1,1474],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\casl\\ability.factory.spec.ts":[1,921],"E:\\np-dms\\lcbp3\\backend\\src\\common\\services\\uuid-resolver.service.spec.ts":[1,1166],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.service.spec.ts":[1,1175],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\parser.service.spec.ts":[1,4448],"E:\\np-dms\\lcbp3\\backend\\src\\common\\pipes\\parse-uuid.pipe.spec.ts":[1,369],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\user\\user.service.spec.ts":[1,1074],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.service.spec.ts":[1,1277],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.controller.spec.ts":[1,7855],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.service.spec.ts":[1,1251],"E:\\np-dms\\lcbp3\\backend\\src\\common\\entities\\uuid-base.entity.spec.ts":[1,460],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.service.spec.ts":[1,1043],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.controller.spec.ts":[1,2047],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\services\\manual-override.service.spec.ts":[1,936],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.controller.spec.ts":[1,1666],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.controller.spec.ts":[1,6122],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.controller.spec.ts":[1,1859],"E:\\np-dms\\lcbp3\\backend\\src\\app.controller.spec.ts":[1,564],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\json-schema\\json-schema.controller.spec.ts":[1,3140]}
@@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { ForbiddenException } from '@nestjs/common';
import { CorrespondenceService } from './correspondence.service';
import { Correspondence } from './entities/correspondence.entity';
import { CorrespondenceRevision } from './entities/correspondence-revision.entity';
@@ -173,6 +174,83 @@ describe('CorrespondenceService', () => {
});
describe('update', () => {
it('should allow non-draft update for org-admin+ permissions', async () => {
const mockUser = {
user_id: 1,
primaryOrganizationId: 10,
} as unknown as User;
const mockRevision = {
id: 100,
correspondenceId: 1,
isCurrent: true,
statusId: 23,
};
jest
.spyOn(revisionRepo, 'findOne')
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 23,
statusCode: 'SUBOWN',
});
const userService = testingModule.get<UserService>(UserService);
(userService.getUserPermissions as jest.Mock).mockResolvedValue([
'correspondence.cancel',
]);
jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue({
id: 1,
publicId: 'corr-uuid-1',
correspondenceNumber: 'CORR-001',
projectId: 1,
createdAt: new Date(),
revisions: [],
} as unknown as Correspondence);
await expect(
service.update(1, { subject: 'Updated Subject' }, mockUser)
).resolves.toBeDefined();
});
it('should reject non-draft update for non-admin permissions', async () => {
const mockUser = {
user_id: 2,
primaryOrganizationId: 10,
} as unknown as User;
const mockRevision = {
id: 101,
correspondenceId: 2,
isCurrent: true,
statusId: 23,
};
jest
.spyOn(revisionRepo, 'findOne')
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 23,
statusCode: 'SUBOWN',
});
const userService = testingModule.get<UserService>(UserService);
(userService.getUserPermissions as jest.Mock).mockResolvedValue([
'correspondence.edit',
]);
await expect(
service.update(2, { subject: 'Should Fail' }, mockUser)
).rejects.toThrow(ForbiddenException);
});
it('should NOT regenerate number if critical fields unchanged', async () => {
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
const mockRevision = {
@@ -631,8 +631,20 @@ export class CorrespondenceService {
const status = await this.statusRepo.findOne({
where: { id: revision.statusId },
});
if (status && status.statusCode !== 'DRAFT') {
throw new BadRequestException('Only DRAFT documents can be updated');
const permissions = await this.userService.getUserPermissions(
user.user_id
);
const canEditSubmittedOrLater =
permissions.includes('correspondence.cancel') ||
permissions.includes('system.manage_all');
if (!canEditSubmittedOrLater) {
throw new ForbiddenException(
'Only Org Admin or Superadmin can edit non-draft correspondences'
);
}
}
}