import test, { beforeEach } from 'node:test'; import assert from 'node:assert/strict'; import request from 'supertest'; import { createApp } from '../src/app.js'; import { __resetIdempotencyStoreForTests } from '../src/services/idempotency-store.js'; process.env.AUTH_BYPASS = 'true'; beforeEach(() => { process.env.IDEMPOTENCY_STORE = 'memory'; delete process.env.IDEMPOTENCY_DATABASE_URL; delete process.env.DATABASE_URL; __resetIdempotencyStoreForTests(); }); function createMobileHandlers() { return { createClientOneTimeOrder: async (_actor, payload) => ({ orderId: 'order-1', orderType: 'ONE_TIME', eventName: payload.eventName, }), createClientRecurringOrder: async (_actor, payload) => ({ orderId: 'order-2', orderType: 'RECURRING', recurrenceDays: payload.recurrenceDays, }), createClientPermanentOrder: async (_actor, payload) => ({ orderId: 'order-3', orderType: 'PERMANENT', horizonDays: payload.horizonDays || 28, }), createEditedOrderCopy: async (_actor, payload) => ({ sourceOrderId: payload.orderId, orderId: 'order-4', cloned: true, }), cancelClientOrder: async (_actor, payload) => ({ orderId: payload.orderId, status: 'CANCELLED', }), createHub: async (_actor, payload) => ({ hubId: 'hub-1', name: payload.name, costCenterId: payload.costCenterId, }), approveInvoice: async (_actor, payload) => ({ invoiceId: payload.invoiceId, status: 'APPROVED', }), registerClientPushToken: async (_actor, payload) => ({ tokenId: 'push-token-client-1', platform: payload.platform, notificationsEnabled: payload.notificationsEnabled ?? true, }), unregisterClientPushToken: async () => ({ removedCount: 1, }), applyForShift: async (_actor, payload) => ({ shiftId: payload.shiftId, status: 'APPLIED', }), registerStaffPushToken: async (_actor, payload) => ({ tokenId: 'push-token-staff-1', platform: payload.platform, notificationsEnabled: payload.notificationsEnabled ?? true, }), unregisterStaffPushToken: async () => ({ removedCount: 1, }), staffClockIn: async (_actor, payload) => ({ assignmentId: payload.assignmentId || 'assignment-1', status: 'CLOCK_IN', proofNonce: payload.proofNonce || null, }), staffClockOut: async (_actor, payload) => ({ assignmentId: payload.assignmentId || 'assignment-1', status: 'CLOCK_OUT', }), submitCompletedShiftForApproval: async (_actor, payload) => ({ shiftId: payload.shiftId, timesheetId: 'timesheet-1', status: 'SUBMITTED', submitted: true, }), submitLocationStreamBatch: async (_actor, payload) => ({ assignmentId: payload.assignmentId || 'assignment-1', pointCount: payload.points.length, status: 'RECORDED', }), saveTaxFormDraft: async (_actor, payload) => ({ formType: payload.formType, status: 'DRAFT', }), addStaffBankAccount: async (_actor, payload) => ({ accountType: payload.accountType, last4: payload.accountNumber.slice(-4), }), }; } test('POST /commands/client/orders/one-time forwards one-time order payload', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/client/orders/one-time') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'client-order-1') .send({ hubId: '11111111-1111-4111-8111-111111111111', vendorId: '22222222-2222-4222-8222-222222222222', eventName: 'Google Cafe Coverage', orderDate: '2026-03-20', positions: [ { roleId: '33333333-3333-4333-8333-333333333333', startTime: '09:00', endTime: '17:00', workerCount: 2, hourlyRateCents: 2800, }, ], }); assert.equal(res.status, 200); assert.equal(res.body.orderId, 'order-1'); assert.equal(res.body.orderType, 'ONE_TIME'); assert.equal(res.body.eventName, 'Google Cafe Coverage'); }); test('POST /commands/client/orders/:orderId/edit injects order id from params', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/client/orders/44444444-4444-4444-8444-444444444444/edit') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'client-order-edit-1') .send({ eventName: 'Edited Order Copy', }); assert.equal(res.status, 200); assert.equal(res.body.sourceOrderId, '44444444-4444-4444-8444-444444444444'); assert.equal(res.body.cloned, true); }); test('POST /commands/client/hubs returns injected hub response', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/client/hubs') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'hub-create-1') .send({ tenantId: '11111111-1111-4111-8111-111111111111', businessId: '22222222-2222-4222-8222-222222222222', name: 'Google North Hub', locationName: 'North Campus', timezone: 'America/Los_Angeles', latitude: 37.422, longitude: -122.084, geofenceRadiusMeters: 100, clockInMode: 'GEO_REQUIRED', allowClockInOverride: true, costCenterId: '44444444-4444-4444-8444-444444444444', }); assert.equal(res.status, 200); assert.equal(res.body.hubId, 'hub-1'); assert.equal(res.body.name, 'Google North Hub'); }); test('POST /commands/client/billing/invoices/:invoiceId/approve injects invoice id from params', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/client/billing/invoices/55555555-5555-4555-8555-555555555555/approve') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'invoice-approve-1') .send({}); assert.equal(res.status, 200); assert.equal(res.body.invoiceId, '55555555-5555-4555-8555-555555555555'); assert.equal(res.body.status, 'APPROVED'); }); test('POST /commands/client/devices/push-tokens registers a client push token', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/client/devices/push-tokens') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'client-push-token-1') .send({ provider: 'FCM', platform: 'IOS', pushToken: 'f'.repeat(160), deviceId: 'iphone-15-pro', notificationsEnabled: true, }); assert.equal(res.status, 200); assert.equal(res.body.tokenId, 'push-token-client-1'); assert.equal(res.body.platform, 'IOS'); }); test('DELETE /commands/client/devices/push-tokens accepts tokenId from query params', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .delete('/commands/client/devices/push-tokens?tokenId=11111111-1111-4111-8111-111111111111&reason=SMOKE_CLEANUP') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'client-push-token-delete-1'); assert.equal(res.status, 200); assert.equal(res.body.removedCount, 1); }); test('POST /commands/staff/shifts/:shiftId/apply injects shift id from params', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/shifts/66666666-6666-4666-8666-666666666666/apply') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'shift-apply-1') .send({ note: 'Available tonight', }); assert.equal(res.status, 200); assert.equal(res.body.shiftId, '66666666-6666-4666-8666-666666666666'); assert.equal(res.body.status, 'APPLIED'); }); test('POST /commands/staff/clock-in accepts shift-based payload', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/clock-in') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'clock-in-1') .send({ shiftId: '77777777-7777-4777-8777-777777777777', sourceType: 'GEO', latitude: 37.422, longitude: -122.084, proofNonce: 'nonce-12345678', overrideReason: 'GPS timed out near the hub', }); assert.equal(res.status, 200); assert.equal(res.body.status, 'CLOCK_IN'); assert.equal(res.body.proofNonce, 'nonce-12345678'); }); test('POST /commands/staff/clock-out accepts assignment-based payload', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/clock-out') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'clock-out-1') .send({ assignmentId: '88888888-8888-4888-8888-888888888888', breakMinutes: 30, }); assert.equal(res.status, 200); assert.equal(res.body.status, 'CLOCK_OUT'); }); test('POST /commands/staff/location-streams accepts batched location payloads', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/location-streams') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'location-stream-1') .send({ assignmentId: '99999999-9999-4999-8999-999999999999', sourceType: 'GEO', deviceId: 'iphone-15', points: [ { capturedAt: '2026-03-16T08:00:00.000Z', latitude: 37.422, longitude: -122.084, accuracyMeters: 12, }, ], }); assert.equal(res.status, 200); assert.equal(res.body.status, 'RECORDED'); assert.equal(res.body.pointCount, 1); }); test('POST /commands/staff/devices/push-tokens registers a staff push token', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/devices/push-tokens') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'staff-push-token-1') .send({ provider: 'FCM', platform: 'ANDROID', pushToken: 'g'.repeat(170), deviceId: 'pixel-9', }); assert.equal(res.status, 200); assert.equal(res.body.tokenId, 'push-token-staff-1'); assert.equal(res.body.platform, 'ANDROID'); }); test('DELETE /commands/staff/devices/push-tokens accepts tokenId from query params', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .delete('/commands/staff/devices/push-tokens?tokenId=22222222-2222-4222-8222-222222222222&reason=SMOKE_CLEANUP') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'staff-push-token-delete-1'); assert.equal(res.status, 200); assert.equal(res.body.removedCount, 1); }); test('PUT /commands/staff/profile/tax-forms/:formType uppercases form type', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .put('/commands/staff/profile/tax-forms/w4') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'tax-form-1') .send({ fields: { filingStatus: 'single', }, }); assert.equal(res.status, 200); assert.equal(res.body.formType, 'W4'); assert.equal(res.body.status, 'DRAFT'); }); test('POST /commands/staff/profile/bank-accounts uppercases account type', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/profile/bank-accounts') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'bank-account-1') .send({ bankName: 'Demo Credit Union', accountNumber: '1234567890', routingNumber: '021000021', accountType: 'checking', }); assert.equal(res.status, 200); assert.equal(res.body.accountType, 'CHECKING'); assert.equal(res.body.last4, '7890'); }); test('POST /commands/staff/shifts/:shiftId/submit-for-approval injects shift id from params', async () => { const app = createApp({ mobileCommandHandlers: createMobileHandlers() }); const res = await request(app) .post('/commands/staff/shifts/77777777-7777-4777-8777-777777777777/submit-for-approval') .set('Authorization', 'Bearer test-token') .set('Idempotency-Key', 'shift-submit-approval-1') .send({ note: 'Worked full shift and ready for approval', }); assert.equal(res.status, 200); assert.equal(res.body.shiftId, '77777777-7777-4777-8777-777777777777'); assert.equal(res.body.timesheetId, 'timesheet-1'); assert.equal(res.body.submitted, true); });