const MaterialPackageService = require('../src/services/materialPackageService'); jest.mock('../src/utils/logger', () => ({ info: jest.fn(), error: jest.fn() })); describe('MaterialPackageService aggregate creation', () => { function fixture({ failChild = false } = {}) { const connection = { beginTransaction: jest.fn(), commit: jest.fn(), rollback: jest.fn(), release: jest.fn(), execute: jest.fn(async sql => { if (sql.includes('INSERT INTO material_packages')) return [{ insertId: 7 }]; if (failChild && sql.includes('INSERT INTO package_materials')) throw new Error('material failed'); if (sql.includes('INSERT INTO package_materials')) return [{ insertId: 11 }]; if (sql.includes('SELECT * FROM material_packages')) return [[{ id: 7, name: 'Package', version: 1 }]]; throw new Error(`Unexpected SQL: ${sql}`); }) }; return { connection, service: new MaterialPackageService({ pool: { getConnection: async () => connection } }) }; } test('creates the package and all materials atomically and returns its current version', async () => { const { service, connection } = fixture(); const result = await service.createPackage({ name: 'Package', description: 'Description', category: 'Roof', createdBy: 'operator', materials: [{ materialName: 'Tile', materialCategory: 'Roof', quantity: 2, unit: 'stk', unitPrice: 10 }] }); expect(result).toMatchObject({ id: 7, version: 1, package: { id: 7, version: 1 } }); expect(connection.beginTransaction).toHaveBeenCalledTimes(1); expect(connection.commit).toHaveBeenCalledTimes(1); expect(connection.rollback).not.toHaveBeenCalled(); expect(connection.release).toHaveBeenCalledTimes(1); }); test('rolls the parent back if a material insert fails', async () => { const { service, connection } = fixture({ failChild: true }); await expect(service.createPackage({ name: 'Package', description: 'Description', category: 'Roof', createdBy: 'operator', materials: [{ materialName: 'Tile', materialCategory: 'Roof', quantity: 2, unit: 'stk', unitPrice: 10 }] })).rejects.toThrow('material failed'); expect(connection.rollback).toHaveBeenCalledTimes(1); expect(connection.commit).not.toHaveBeenCalled(); }); });