/** * 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, }); } }); }); });