160 lines
5.0 KiB
TypeScript
160 lines
5.0 KiB
TypeScript
import { strict as assert } from 'node:assert';
|
|
import { describe, it } from 'node:test';
|
|
import type { Contract, SlaTerm } from '@pig/db';
|
|
import {
|
|
renewalAlarm,
|
|
resolveContractPrecedence,
|
|
validateParentRelationship,
|
|
} from '../src/services/contracts';
|
|
|
|
function contract(overrides: Partial<Contract>): Contract {
|
|
return {
|
|
id: '10000000-0000-4000-8000-000000000001',
|
|
accountId: '20000000-0000-4000-8000-000000000002',
|
|
type: 'msa',
|
|
status: 'executed',
|
|
side: 'demand',
|
|
title: 'Master agreement',
|
|
externalReference: null,
|
|
demandDealId: null,
|
|
supplyDealId: null,
|
|
capacityCommitmentId: null,
|
|
parentContractId: null,
|
|
contractingPartyName: null,
|
|
takeOrPayFloorPct: null,
|
|
prepaidPct: null,
|
|
terminationTier: null,
|
|
assignableOnDefault: false,
|
|
assignmentDeadlineBusinessDays: null,
|
|
effectiveAt: null,
|
|
expiresAt: null,
|
|
executedAt: null,
|
|
terminatedAt: null,
|
|
isAutoRenew: false,
|
|
noticeDays: null,
|
|
valueCents: null,
|
|
currency: 'USD',
|
|
governingLaw: null,
|
|
documentUrl: null,
|
|
ownerUserId: null,
|
|
notes: null,
|
|
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function sla(overrides: Partial<SlaTerm>): SlaTerm {
|
|
return {
|
|
id: '30000000-0000-4000-8000-000000000003',
|
|
contractId: '10000000-0000-4000-8000-000000000001',
|
|
kind: 'negotiated',
|
|
uptimeTargetPct: null,
|
|
nodeReplacementHours: null,
|
|
mttrHours: null,
|
|
supportResponseHours: null,
|
|
measurementWindow: 'monthly',
|
|
measurementUnit: 'cluster',
|
|
remedyType: 'service_credit',
|
|
abatementTriggerValue: null,
|
|
abatementTriggerUnit: null,
|
|
claimDeadlineValue: null,
|
|
claimDeadlineUnit: 'days',
|
|
creditExpiryMonths: null,
|
|
isSoleRemedy: true,
|
|
sparePoolObligation: null,
|
|
sparePoolScope: [],
|
|
maintenanceClasses: [],
|
|
reasonableEndeavoursDaysPerYear: null,
|
|
rcaDeliveryHours: null,
|
|
creditSchedule: [],
|
|
creditCapPct: null,
|
|
exclusions: null,
|
|
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('contract precedence', () => {
|
|
it('uses explicit order-form terms before the MSA without copying inherited values', () => {
|
|
const master = contract({ takeOrPayFloorPct: '70.00', prepaidPct: '25.00' });
|
|
const order = contract({
|
|
id: '10000000-0000-4000-8000-000000000004',
|
|
type: 'order_form',
|
|
title: 'Order form',
|
|
parentContractId: master.id,
|
|
takeOrPayFloorPct: '90.00',
|
|
});
|
|
const resolved = resolveContractPrecedence(order.id, [master, order], [
|
|
sla({ uptimeTargetPct: '99.900' }),
|
|
]);
|
|
|
|
const floor = resolved.contract.takeOrPayFloorPct;
|
|
const prepaid = resolved.contract.prepaidPct;
|
|
const uptime = resolved.sla.uptimeTargetPct;
|
|
assert.ok(floor);
|
|
assert.ok(prepaid);
|
|
assert.ok(uptime);
|
|
|
|
assert.equal(floor.value, '90.00');
|
|
assert.equal(floor.inherited, false);
|
|
assert.equal(prepaid.value, '25.00');
|
|
assert.equal(prepaid.inherited, true);
|
|
assert.equal(uptime.sourceContractId, master.id);
|
|
});
|
|
|
|
it('treats false, zero and an empty list as deliberate child overrides', () => {
|
|
const master = contract({ assignableOnDefault: true });
|
|
const child = contract({
|
|
id: '10000000-0000-4000-8000-000000000004',
|
|
parentContractId: master.id,
|
|
assignableOnDefault: false,
|
|
});
|
|
const resolved = resolveContractPrecedence(child.id, [master, child], [
|
|
sla({ sparePoolScope: ['compute nodes', 'network switches'] }),
|
|
sla({
|
|
id: '30000000-0000-4000-8000-000000000004',
|
|
contractId: child.id,
|
|
reasonableEndeavoursDaysPerYear: 0,
|
|
sparePoolScope: [],
|
|
}),
|
|
]);
|
|
|
|
const assignable = resolved.contract.assignableOnDefault;
|
|
const reasonableEndeavours = resolved.sla.reasonableEndeavoursDaysPerYear;
|
|
const sparePoolScope = resolved.sla.sparePoolScope;
|
|
assert.ok(assignable);
|
|
assert.ok(reasonableEndeavours);
|
|
assert.ok(sparePoolScope);
|
|
|
|
assert.equal(assignable.value, false);
|
|
assert.equal(reasonableEndeavours.value, 0);
|
|
assert.deepEqual(sparePoolScope.value, []);
|
|
});
|
|
});
|
|
|
|
describe('contract alarms and hierarchy', () => {
|
|
it('raises the renewal alarm at the negotiated notice deadline, not at expiry', () => {
|
|
const result = renewalAlarm(
|
|
{
|
|
isAutoRenew: true,
|
|
expiresAt: new Date('2026-04-01T00:00:00.000Z'),
|
|
noticeDays: 60,
|
|
},
|
|
new Date('2026-02-15T00:00:00.000Z'),
|
|
);
|
|
assert.equal(result.renewalNoticeAt?.toISOString(), '2026-01-31T00:00:00.000Z');
|
|
assert.equal(result.renewalState, 'due');
|
|
});
|
|
|
|
it('rejects cross-account parentage even when both records otherwise look valid', () => {
|
|
const child = contract({ id: '10000000-0000-4000-8000-000000000004' });
|
|
const parent = contract({ accountId: '20000000-0000-4000-8000-000000000009' });
|
|
assert.equal(
|
|
validateParentRelationship(child, parent, []),
|
|
'Parent and child contracts must belong to the same account.',
|
|
);
|
|
});
|
|
});
|