文件
hyb-oa/server/tests/unit/services/integrations/erp.test.js
T

238 行
7.6 KiB
JavaScript

const erp = require('../../../../src/services/integrations/erp');
describe('ERP Sync Services', () => {
const originalFetch = global.fetch;
afterEach(() => {
global.fetch = originalFetch;
vi.restoreAllMocks();
});
it('builds reimbursement journal payload via compat helper', () => {
const payload = erp.buildErpPayload({
reimbursement: {
id: 'r1',
form_number: 'F001',
owner_id: 'owner-1',
submitter_id: 'submitter-1',
department_id: 'dept-1',
total_amount: { toString: () => '100.00' },
payment_voucher_no: 'PV001',
payment_date: '2026-03-01T12:00:00.000Z',
paid_at: '2026-03-02T08:00:00.000Z',
owner: { legal_entity: { code: 'HY' } },
},
});
expect(payload.journalType).toBe('reimbursement_paid');
expect(payload.documentNo).toBe('F001');
expect(payload.paymentDate).toBe('2026-03-01');
expect(payload.dimensions.legalEntityCode).toBe('HY');
});
it('returns mock synced when journal export provider is mock', async () => {
const result = await erp.exportJournalToErp(
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
{ journalType: 'purchase_request_commitment', documentNo: 'PR-001' },
);
expect(result.ok).toBe(true);
expect(result.code).toBe('mock_synced');
expect(result.response.externalId).toContain('PR-001');
});
it('returns mock procurement result when provider is mock', async () => {
const result = await erp.exportProcurementToErp(
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
{ poNo: 'PO-001', contractNo: 'CT-001' },
);
expect(result.ok).toBe(true);
expect(result.code).toBe('mock_synced');
expect(result.response.externalPoNo).toContain('PO-001');
expect(result.response.externalContractNo).toContain('CT-001');
});
it('returns mock master data result when provider is mock', async () => {
const result = await erp.pullMasterDataFromErp(
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
{ resources: ['legal_entity', 'supplier'] },
);
expect(result.ok).toBe(true);
expect(result.code).toBe('mock_synced');
expect(result.data.legalEntities).toEqual([]);
expect(result.data.suppliers).toEqual([]);
});
it('handles http master data 4xx errors', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 422,
headers: { get: () => 'application/json' },
json: vi.fn().mockResolvedValue({ code: 'invalid_payload' }),
});
const result = await erp.pullMasterDataFromErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpMasterDataEndpoint: 'https://example.com/master-data',
erpMasterDataTimeoutMs: 500,
},
{ resources: ['legal_entity'] },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_master_data_http_4xx');
expect(result.retryable).toBe(false);
});
it('handles http journal export 5xx errors', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 503,
headers: { get: () => 'application/json' },
json: vi.fn().mockResolvedValue({ code: 'service_unavailable' }),
});
const result = await erp.exportJournalToErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpJournalExportEndpoint: 'https://example.com/journals',
erpSyncTimeoutMs: 500,
},
{ journalType: 'purchase_order_accrual', documentNo: 'PO-001' },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_sync_http_5xx');
expect(result.retryable).toBe(true);
});
it('handles http procurement export 4xx errors', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 400,
headers: { get: () => 'application/json' },
json: vi.fn().mockResolvedValue({ code: 'bad_request' }),
});
const result = await erp.exportProcurementToErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpProcurementExportEndpoint: 'https://example.com/procurement',
erpProcurementTimeoutMs: 500,
},
{ exportType: 'purchase_order_archive', poNo: 'PO-001' },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_procurement_http_4xx');
expect(result.retryable).toBe(false);
});
it('handles http procurement export timeout', async () => {
global.fetch = vi.fn().mockRejectedValue({ name: 'AbortError' });
const result = await erp.exportProcurementToErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpProcurementExportEndpoint: 'https://example.com/procurement',
erpProcurementTimeoutMs: 500,
},
{ exportType: 'purchase_order_archive', poNo: 'PO-001' },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_procurement_timeout');
expect(result.retryable).toBe(true);
});
it('handles http journal export timeout', async () => {
global.fetch = vi.fn().mockRejectedValue({ name: 'AbortError' });
const result = await erp.exportJournalToErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpJournalExportEndpoint: 'https://example.com/journals',
erpSyncTimeoutMs: 500,
},
{ documentNo: 'BX-001' },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_sync_timeout');
expect(result.retryable).toBe(true);
});
it('returns mock asset export result when provider is mock', async () => {
const result = await erp.exportAssetToErp(
{ erpSyncEnabled: true, erpSyncProvider: 'mock' },
{ assetNo: 'ZC-001', entityId: 'asset-1' },
);
expect(result.ok).toBe(true);
expect(result.code).toBe('mock_synced');
expect(result.response.externalAssetId).toContain('ZC-001');
expect(result.response.externalAssetNo).toContain('ZC-001');
});
it('handles http asset export 4xx errors', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 400,
headers: { get: () => 'application/json' },
json: vi.fn().mockResolvedValue({ code: 'bad_request' }),
});
const result = await erp.exportAssetToErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpAssetExportEndpoint: 'https://example.com/assets',
erpAssetTimeoutMs: 500,
},
{ exportType: 'asset_ledger_summary', assetNo: 'ZC-001' },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_asset_http_4xx');
expect(result.retryable).toBe(false);
});
it('handles http asset export timeout', async () => {
global.fetch = vi.fn().mockRejectedValue({ name: 'AbortError' });
const result = await erp.exportAssetToErp(
{
erpSyncEnabled: true,
erpSyncProvider: 'http',
erpAssetExportEndpoint: 'https://example.com/assets',
erpAssetTimeoutMs: 500,
},
{ exportType: 'asset_ledger_summary', assetNo: 'ZC-001' },
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_asset_timeout');
expect(result.retryable).toBe(true);
});
it('returns invalid provider if unsupported', async () => {
const result = await erp.exportJournalToErp(
{ erpSyncEnabled: true, erpSyncProvider: 'invalid_xyz' },
{},
);
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_sync_provider_invalid');
});
it('returns disabled if ERP sync is disabled', async () => {
const result = await erp.exportJournalToErp({ erpSyncEnabled: false }, {});
expect(result.ok).toBe(false);
expect(result.code).toBe('erp_sync_disabled');
});
});