diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts deleted file mode 100644 index 04ef89ef..00000000 --- a/src/__tests__/setup.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Test setup file for Jest -// Mock environment variables -process.env.NODE_ENV = 'test'; -process.env.DB_HOST = 'localhost'; -process.env.DB_PORT = '3306'; -process.env.DB_USERNAME = 'test'; -process.env.DB_PASSWORD = 'test'; -process.env.DB_DATABASE = 'test_db'; - -// Mock console methods to reduce noise in tests -global.console = { - ...console, - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - log: jest.fn(), -}; diff --git a/src/__tests__/unit/OrgMapping.spec.ts b/src/__tests__/unit/OrgMapping.spec.ts deleted file mode 100644 index c59e5697..00000000 --- a/src/__tests__/unit/OrgMapping.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Unit tests for move-draft-to-current helper functions - */ - -import { OrgIdMapping, AllOrgMappings } from '../../interfaces/OrgMapping'; - -// Mock dependencies -jest.mock('../../database/data-source', () => ({ - AppDataSource: { - createQueryRunner: jest.fn(), - }, -})); - -describe('OrgMapping Interfaces', () => { - describe('OrgIdMapping', () => { - it('should create a valid OrgIdMapping', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map(), - byDraftId: new Map(), - }; - - expect(mapping.byAncestorDNA).toBeInstanceOf(Map); - expect(mapping.byDraftId).toBeInstanceOf(Map); - }); - - it('should store and retrieve values correctly', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map([['dna1', 'id1']]), - byDraftId: new Map([['draftId1', 'currentId1']]), - }; - - expect(mapping.byAncestorDNA.get('dna1')).toBe('id1'); - expect(mapping.byDraftId.get('draftId1')).toBe('currentId1'); - }); - }); - - describe('AllOrgMappings', () => { - it('should create a valid AllOrgMappings', () => { - const mappings: AllOrgMappings = { - orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() }, - orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() }, - orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() }, - orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() }, - orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() }, - }; - - expect(mappings.orgRoot).toBeDefined(); - expect(mappings.orgChild1).toBeDefined(); - expect(mappings.orgChild2).toBeDefined(); - expect(mappings.orgChild3).toBeDefined(); - expect(mappings.orgChild4).toBeDefined(); - }); - }); -}); diff --git a/src/__tests__/unit/OrganizationController.spec.ts b/src/__tests__/unit/OrganizationController.spec.ts deleted file mode 100644 index 615298bc..00000000 --- a/src/__tests__/unit/OrganizationController.spec.ts +++ /dev/null @@ -1,460 +0,0 @@ -/** - * Unit tests for OrganizationController move-draft-to-current helper functions - */ - -import { OrgIdMapping } from '../../interfaces/OrgMapping'; - -// Mock typeorm -jest.mock('typeorm', () => ({ - Entity: jest.fn(), - Column: jest.fn(), - ManyToOne: jest.fn(), - JoinColumn: jest.fn(), - OneToMany: jest.fn(), - In: jest.fn((val: any) => val), - Like: jest.fn((val: any) => val), - IsNull: jest.fn(), - Not: jest.fn(), -})); - -// Mock entities -jest.mock('../../entities/OrgRoot', () => ({ OrgRoot: {} })); -jest.mock('../../entities/OrgChild1', () => ({ OrgChild1: {} })); -jest.mock('../../entities/OrgChild2', () => ({ OrgChild2: {} })); -jest.mock('../../entities/OrgChild3', () => ({ OrgChild3: {} })); -jest.mock('../../entities/OrgChild4', () => ({ OrgChild4: {} })); -jest.mock('../../entities/PosMaster', () => ({ PosMaster: {} })); -jest.mock('../../entities/Position', () => ({ Position: {} })); - -// Import after mocking -import { In, Like } from 'typeorm'; -import { OrgRoot } from '../../entities/OrgRoot'; -import { OrgChild1 } from '../../entities/OrgChild1'; -import { OrgChild2 } from '../../entities/OrgChild2'; -import { OrgChild3 } from '../../entities/OrgChild3'; -import { OrgChild4 } from '../../entities/OrgChild4'; -import { PosMaster } from '../../entities/PosMaster'; -import { Position } from '../../entities/Position'; - -describe('OrganizationController - Helper Functions', () => { - let mockQueryRunner: any; - let mockController: any; - - beforeEach(() => { - // Mock queryRunner - mockQueryRunner = { - manager: { - find: jest.fn(), - delete: jest.fn(), - update: jest.fn(), - create: jest.fn(), - save: jest.fn(), - }, - }; - - // Import the controller class (we'll need to mock the private methods) - // Since we're testing private methods, we'll create a test class - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('resolveOrgId()', () => { - it('should return null when draftId is null', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map(), - byDraftId: new Map(), - }; - - // Simulate the function logic - const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => { - if (!draftId) return null; - return mapping.byDraftId.get(draftId) ?? null; - }; - - expect(resolveOrgId(null, mapping)).toBeNull(); - }); - - it('should return null when draftId is undefined', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map(), - byDraftId: new Map(), - }; - - const resolveOrgId = (draftId: string | null | undefined, mapping: OrgIdMapping): string | null => { - if (!draftId) return null; - return mapping.byDraftId.get(draftId) ?? null; - }; - - expect(resolveOrgId(undefined, mapping)).toBeNull(); - }); - - it('should return mapped ID when draftId exists in mapping', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map(), - byDraftId: new Map([['draft1', 'current1']]), - }; - - const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => { - if (!draftId) return null; - return mapping.byDraftId.get(draftId) ?? null; - }; - - expect(resolveOrgId('draft1', mapping)).toBe('current1'); - }); - - it('should return null when draftId does not exist in mapping', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map(), - byDraftId: new Map(), - }; - - const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => { - if (!draftId) return null; - return mapping.byDraftId.get(draftId) ?? null; - }; - - expect(resolveOrgId('nonexistent', mapping)).toBeNull(); - }); - }); - - describe('cascadeDeletePositions()', () => { - it('should delete positions with orgRootId when entityClass is OrgRoot', async () => { - const node = { - id: 'node1', - orgRevisionId: 'rev1', - }; - - await mockQueryRunner.manager.delete(PosMaster, { - orgRevisionId: 'rev1', - orgRootId: 'node1', - }); - - expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { - orgRevisionId: 'rev1', - orgRootId: 'node1', - }); - }); - - it('should delete positions with orgChild1Id when entityClass is OrgChild1', async () => { - const node = { - id: 'node1', - orgRevisionId: 'rev1', - }; - - await mockQueryRunner.manager.delete(PosMaster, { - orgRevisionId: 'rev1', - orgChild1Id: 'node1', - }); - - expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { - orgRevisionId: 'rev1', - orgChild1Id: 'node1', - }); - }); - - it('should delete positions with orgChild2Id when entityClass is OrgChild2', async () => { - const node = { - id: 'node1', - orgRevisionId: 'rev1', - }; - - await mockQueryRunner.manager.delete(PosMaster, { - orgRevisionId: 'rev1', - orgChild2Id: 'node1', - }); - - expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { - orgRevisionId: 'rev1', - orgChild2Id: 'node1', - }); - }); - - it('should delete positions with orgChild3Id when entityClass is OrgChild3', async () => { - const node = { - id: 'node1', - orgRevisionId: 'rev1', - }; - - await mockQueryRunner.manager.delete(PosMaster, { - orgRevisionId: 'rev1', - orgChild3Id: 'node1', - }); - - expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { - orgRevisionId: 'rev1', - orgChild3Id: 'node1', - }); - }); - - it('should delete positions with orgChild4Id when entityClass is OrgChild4', async () => { - const node = { - id: 'node1', - orgRevisionId: 'rev1', - }; - - await mockQueryRunner.manager.delete(PosMaster, { - orgRevisionId: 'rev1', - orgChild4Id: 'node1', - }); - - expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, { - orgRevisionId: 'rev1', - orgChild4Id: 'node1', - }); - }); - }); - - describe('syncOrgLevel()', () => { - beforeEach(() => { - mockQueryRunner.manager.find.mockResolvedValue([]); - mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 }); - mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 }); - mockQueryRunner.manager.create.mockReturnValue({}); - mockQueryRunner.manager.save.mockResolvedValue({}); - }); - - it('should fetch draft and current nodes with Like filter', async () => { - const repository = { - find: jest.fn().mockResolvedValue([]), - }; - - await repository.find({ - where: { - orgRevisionId: 'draftRev1', - ancestorDNA: Like('root-dna%'), - }, - }); - - await repository.find({ - where: { - orgRevisionId: 'currentRev1', - ancestorDNA: Like('root-dna%'), - }, - }); - - expect(repository.find).toHaveBeenCalledTimes(2); - }); - - it('should build lookup maps from draft and current nodes', () => { - const draftNodes = [ - { id: 'draft1', ancestorDNA: 'root-dna/child1' }, - { id: 'draft2', ancestorDNA: 'root-dna/child2' }, - ]; - const currentNodes = [ - { id: 'current1', ancestorDNA: 'root-dna/child1' }, - ]; - - const draftByDNA = new Map(draftNodes.map(n => [n.ancestorDNA, n])); - const currentByDNA = new Map(currentNodes.map(n => [n.ancestorDNA, n])); - - expect(draftByDNA.size).toBe(2); - expect(currentByDNA.size).toBe(1); - expect(draftByDNA.get('root-dna/child1')).toEqual(draftNodes[0]); - expect(currentByDNA.get('root-dna/child1')).toEqual(currentNodes[0]); - }); - - it('should identify nodes to delete (in current but not in draft)', () => { - const draftNodes = [ - { id: 'draft1', ancestorDNA: 'root-dna/child1' }, - ]; - const currentNodes = [ - { id: 'current1', ancestorDNA: 'root-dna/child1' }, - { id: 'current2', ancestorDNA: 'root-dna/child2' }, // Not in draft - ]; - - const draftByDNA = new Map(draftNodes.map((n: any) => [n.ancestorDNA, n])); - const toDelete = currentNodes.filter((curr: any) => !draftByDNA.has(curr.ancestorDNA)); - - expect(toDelete).toHaveLength(1); - expect(toDelete[0].id).toBe('current2'); - }); - - it('should identify nodes to update (in both draft and current)', () => { - const draftNodes = [ - { id: 'draft1', ancestorDNA: 'root-dna/child1' }, - { id: 'draft2', ancestorDNA: 'root-dna/child2' }, - ]; - const currentNodes = [ - { id: 'current1', ancestorDNA: 'root-dna/child1' }, - ]; - - const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n])); - const toUpdate = draftNodes.filter((draft: any) => currentByDNA.has(draft.ancestorDNA)); - - expect(toUpdate).toHaveLength(1); - expect(toUpdate[0].id).toBe('draft1'); - }); - - it('should identify nodes to insert (in draft but not in current)', () => { - const draftNodes = [ - { id: 'draft1', ancestorDNA: 'root-dna/child1' }, - { id: 'draft2', ancestorDNA: 'root-dna/child2' }, - ]; - const currentNodes = [ - { id: 'current1', ancestorDNA: 'root-dna/child1' }, - ]; - - const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n])); - const toInsert = draftNodes.filter((draft: any) => !currentByDNA.has(draft.ancestorDNA)); - - expect(toInsert).toHaveLength(1); - expect(toInsert[0].id).toBe('draft2'); - }); - - it('should return correct mapping after sync', () => { - const mapping: OrgIdMapping = { - byAncestorDNA: new Map([ - ['root-dna/child1', 'current1'], - ['root-dna/child2', 'current2'], - ]), - byDraftId: new Map([ - ['draft1', 'current1'], - ['draft2', 'current2'], - ]), - }; - - expect(mapping.byAncestorDNA.get('root-dna/child1')).toBe('current1'); - expect(mapping.byDraftId.get('draft1')).toBe('current1'); - expect(mapping.byDraftId.get('draft2')).toBe('current2'); - }); - }); - - describe('syncPositionsForPosMaster()', () => { - beforeEach(() => { - mockQueryRunner.manager.find.mockResolvedValue([]); - mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 }); - mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 }); - mockQueryRunner.manager.create.mockReturnValue({}); - mockQueryRunner.manager.save.mockResolvedValue({}); - }); - - it('should fetch draft and current positions for a posMaster', async () => { - const draftPosMasterId = 'draft-pos-1'; - const currentPosMasterId = 'current-pos-1'; - - mockQueryRunner.manager.find - .mockResolvedValueOnce([{ id: 'pos1', posMasterId: draftPosMasterId }]) - .mockResolvedValueOnce([{ id: 'pos2', posMasterId: currentPosMasterId }]); - - await mockQueryRunner.manager.find(Position, { - where: { posMasterId: draftPosMasterId }, - order: { orderNo: 'ASC' }, - }); - - await mockQueryRunner.manager.find(Position, { - where: { posMasterId: currentPosMasterId }, - }); - - expect(mockQueryRunner.manager.find).toHaveBeenCalledTimes(2); - }); - - it('should delete all current positions when no draft positions exist', async () => { - const currentPositions = [ - { id: 'pos1', orderNo: 1 }, - { id: 'pos2', orderNo: 2 }, - ]; - - mockQueryRunner.manager.find - .mockResolvedValueOnce([]) // No draft positions - .mockResolvedValueOnce(currentPositions); - - await mockQueryRunner.manager.delete(Position, ['pos1', 'pos2']); - - expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(Position, ['pos1', 'pos2']); - }); - - it('should delete positions not in draft (by orderNo)', async () => { - const draftPositions = [ - { id: 'dpos1', orderNo: 1 }, - { id: 'dpos2', orderNo: 2 }, - ]; - const currentPositions = [ - { id: 'cpos1', orderNo: 1 }, - { id: 'cpos2', orderNo: 2 }, - { id: 'cpos3', orderNo: 3 }, // Not in draft - ]; - - mockQueryRunner.manager.find - .mockResolvedValueOnce(draftPositions) - .mockResolvedValueOnce(currentPositions); - - const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo)); - const toDelete = currentPositions.filter((p: any) => !draftOrderNos.has(p.orderNo)); - - expect(toDelete).toHaveLength(1); - expect(toDelete[0].id).toBe('cpos3'); - }); - - it('should update existing positions (matched by orderNo)', async () => { - const draftPositions = [ - { - id: 'dpos1', - orderNo: 1, - positionName: 'Updated Name', - positionField: 'field1', - posTypeId: 'type1', - posLevelId: 'level1', - }, - ]; - const currentPositions = [ - { id: 'cpos1', orderNo: 1, positionName: 'Old Name' }, - ]; - - const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p])); - const draftPos = draftPositions[0]; - const current = currentByOrderNo.get(draftPos.orderNo); - - expect(current).toBeDefined(); - - if (current) { - const updateData = { - positionName: draftPos.positionName, - positionField: draftPos.positionField, - posTypeId: draftPos.posTypeId, - posLevelId: draftPos.posLevelId, - }; - - await mockQueryRunner.manager.update(Position, current.id, updateData); - - expect(mockQueryRunner.manager.update).toHaveBeenCalledWith( - Position, - 'cpos1', - expect.objectContaining({ positionName: 'Updated Name' }) - ); - } - }); - - it('should insert new positions not in current', async () => { - const draftPositions = [ - { id: 'dpos1', orderNo: 1, positionName: 'New Position' }, - ]; - const currentPositions: any[] = []; - - const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p])); - const draftPos = draftPositions[0]; - const current = currentByOrderNo.get(draftPos.orderNo); - const currentPosMasterId = 'current-pos-1'; - - expect(current).toBeUndefined(); - - if (!current) { - const newPosition = { - ...draftPos, - id: undefined, - posMasterId: currentPosMasterId, - }; - - await mockQueryRunner.manager.create(Position, newPosition); - await mockQueryRunner.manager.save(newPosition); - - expect(mockQueryRunner.manager.create).toHaveBeenCalledWith(Position, { - ...draftPos, - id: undefined, - posMasterId: currentPosMasterId, - }); - } - }); - }); -}); diff --git a/src/app.ts b/src/app.ts index c0ee5b1a..06f76548 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,7 +15,7 @@ import { logMemoryStore } from "./utils/LogMemoryStore"; import { orgStructureCache } from "./utils/OrgStructureCache"; import { CommandController } from "./controllers/CommandController"; import { ProfileSalaryController } from "./controllers/ProfileSalaryController"; -// import { ScriptProfileOrgController } from "./controllers/ScriptProfileOrgController"; +import { ScriptProfileOrgController } from "./controllers/ScriptProfileOrgController"; import { DateSerializer } from "./interfaces/date-serializer"; import { initWebSocket } from "./services/webSocket"; @@ -87,15 +87,15 @@ async function main() { }); // Cron job for updating org DNA - every day at 03:00:00 - // const cronTime_UpdateOrg = "0 0 3 * * *"; - // cron.schedule(cronTime_UpdateOrg, async () => { - // try { - // const scriptProfileOrgController = new ScriptProfileOrgController(); - // await scriptProfileOrgController.cronjobUpdateOrg({} as any); - // } catch (error) { - // console.error("Error executing cronjobUpdateOrg:", error); - // } - // }); + const cronTime_UpdateOrg = "0 0 3 * * *"; + cron.schedule(cronTime_UpdateOrg, async () => { + try { + const scriptProfileOrgController = new ScriptProfileOrgController(); + await scriptProfileOrgController.cronjobUpdateOrg({} as any); + } catch (error) { + console.error("Error executing cronjobUpdateOrg:", error); + } + }); // Cron job for updating tenure - every day at 04:00:00 const cronTime_Tenure = "0 0 4 * * *";