updates on the ui and updates agents

This commit is contained in:
2026-10-09 13:39:14 +05:30
parent 3d07ea8170
commit 5faf0e1deb
13 changed files with 1572 additions and 45 deletions

View File

@@ -38,16 +38,31 @@ beforeEach(() => {
describe('which proposals can be executed', () => {
it('only verbs with a real endpoint behind them', () => {
expect(executableTools().sort()).toEqual(['notifyRider']);
// alert_low_battery_rider joined notifyRider: it targets the same
// POST /admin/milers/:id/notify, so it was never a missing capability —
// just a verb nothing had been wired to.
expect(executableTools().sort()).toEqual([
'alert_low_battery_rider', 'assignMiler', 'notifyRider', 'trigger_auto_dispatch'
]);
});
it('canExecuteProposal is false for the skills tools with no endpoint', () => {
['trigger_auto_dispatch', 'enforce_cash_handoff', 'enforce_otp_verification', 'alert_low_battery_rider']
// Still review-only. Each needs an endpoint that does not exist yet —
// seed.go marks all three Target: "none yet".
['enforce_cash_handoff', 'enforce_otp_verification', 'dispatch_hub_idle_parcels']
.forEach((tool) => {
expect(canExecuteProposal(finding(tool, [row()]))).toBe(false);
});
});
it('alert_low_battery_rider notifies the riders on the finding', async () => {
const result = await executeProposal(finding('alert_low_battery_rider', [row()]));
expect(result.ok).toBe(true);
// Keyed on milerprofileid, not userid — the two are different small
// integers on the same record and the wrong one fails silently.
expect(api.notifyRider).toHaveBeenCalledWith(99, expect.any(String), expect.stringMatching(/charge/i));
});
// Resolving quietly would let a caller mistake "nothing happened" for success.
it('throws rather than silently doing nothing', async () => {
await expect(executeProposal(finding('enforce_cash_handoff', [row()]))).rejects.toThrow(/review-only/i);
@@ -130,18 +145,92 @@ describe('notifyRider', () => {
});
describe('assignMiler', () => {
// Deliberately review-only in the console. The branch executed it through
// POST /hub/bookings/batch-assign, which is behind HubStaffAuth (roleid 6
// only) — every admin/manager/executive click would 403. Until an admin
// batch-assign route exists, the card must say "Review only", not fail.
it('is not executable, so its card renders "Review only"', () => {
expect(canExecuteProposal(finding('assignMiler', [row({ bookingid: 1 })]))).toBe(false);
// This was review-only because POST /hub/bookings/batch-assign is behind
// HubStaffAuth (roleid 6 only) and 403s for every token this console issues.
// It is executable now that POST /admin/bookings/batch-assign runs the same
// solver under admin auth.
it('is executable', () => {
expect(canExecuteProposal(finding('assignMiler', [row({ bookingid: 1 })]))).toBe(true);
});
it('throws instead of pretending, and never calls the hub-only endpoint', async () => {
await expect(executeProposal(finding('assignMiler', [row()]))).rejects.toThrow(/review-only/);
it('sends the findings distinct booking ids, deduplicated', async () => {
api.batchAssignBookings.mockResolvedValue({ assigned: 2, skipped: 0, riderssequenced: 1, results: [] });
await executeProposal(finding('assignMiler', [
row({ bookingid: 11 }), row({ bookingid: 12 }), row({ bookingid: 11 })
]));
expect(api.batchAssignBookings).toHaveBeenCalledWith([11, 12]);
});
// The rule the whole module exists for: never report an action that did not
// happen. Four of nine landing is four of nine.
it('reports a partial assignment as partial, naming the reasons', async () => {
api.batchAssignBookings.mockResolvedValue({
assigned: 1, skipped: 1, riderssequenced: 1,
results: [
{ bookingid: 11, bookingno: 'DM-11', assigned: true, mileruserid: 7 },
{ bookingid: 12, bookingno: 'DM-12', assigned: false, reason: 'no available rider under capacity' }
]
});
const res = await executeProposal(finding('assignMiler', [row({ bookingid: 11 }), row({ bookingid: 12 })]));
expect(res.ok).toBe(true);
// Declared so reportActed records "partial" rather than "ok". Caught by
// driving the real page: the message said partial, the telemetry said ok.
expect(res.partial).toBe(true);
expect(res.message).toMatch(/Assigned 1 order/);
expect(res.message).toMatch(/DM-12 \(no available rider under capacity\)/);
});
// The mocks above return a FLAT object, but batchAssignBookings returns
// `response.data` — and utils.OK wraps everything as {success, data}. So
// production hands the executor {data: {assigned, skipped, ...}}. Testing
// only the flat shape would leave the real one unproven.
it('reads the real utils.OK envelope, not just a flat object', async () => {
api.batchAssignBookings.mockResolvedValue({
success: true,
data: {
assigned: 1, skipped: 1, riderssequenced: 1,
results: [
{ bookingid: 11, bookingno: 'DM-11', assigned: true, mileruserid: 7 },
{ bookingid: 12, bookingno: 'DM-12', assigned: false, reason: 'no available rider under capacity' }
]
}
});
const res = await executeProposal(finding('assignMiler', [row({ bookingid: 11 }), row({ bookingid: 12 })]));
expect(res.ok).toBe(true);
expect(res.message).toMatch(/Assigned 1 order/);
expect(res.message).toMatch(/DM-12/);
});
it('is not ok when nothing was assigned', async () => {
api.batchAssignBookings.mockResolvedValue({
assigned: 0, skipped: 1, riderssequenced: 0,
results: [{ bookingid: 12, bookingno: 'DM-12', assigned: false, reason: 'no available rider under capacity' }]
});
const res = await executeProposal(finding('assignMiler', [row({ bookingid: 12 })]));
expect(res.ok).toBe(false);
expect(res.sourceCalls[0].status).toBe('error');
});
// Stop sequencing needs ROUTE_OPTIMIZER_URL set in the cluster. Saying
// nothing when it is zero leaves the operator assuming routes were ordered.
it('says so when stops were not sequenced', async () => {
api.batchAssignBookings.mockResolvedValue({ assigned: 2, skipped: 0, riderssequenced: 0, results: [] });
const res = await executeProposal(finding('assignMiler', [row({ bookingid: 11 }), row({ bookingid: 12 })]));
expect(res.message).toMatch(/not sequenced/i);
});
it('refuses findings with no booking rather than calling the endpoint', async () => {
const res = await executeProposal(finding('assignMiler', [row({ bookingid: undefined })]));
expect(res.ok).toBe(false);
expect(api.batchAssignBookings).not.toHaveBeenCalled();
});
it('reports a thrown error instead of claiming success', async () => {
api.batchAssignBookings.mockRejectedValue(new Error('403 forbidden'));
const res = await executeProposal(finding('assignMiler', [row({ bookingid: 11 })]));
expect(res.ok).toBe(false);
expect(res.message).toMatch(/403/);
});
});
// The gap the tests above have: they mock buildMilerLookup, so the id