267 lines
8.9 KiB
JavaScript
267 lines
8.9 KiB
JavaScript
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',
|
|
}),
|
|
applyForShift: async (_actor, payload) => ({
|
|
shiftId: payload.shiftId,
|
|
status: 'APPLIED',
|
|
}),
|
|
staffClockIn: async (_actor, payload) => ({
|
|
assignmentId: payload.assignmentId || 'assignment-1',
|
|
status: 'CLOCK_IN',
|
|
}),
|
|
staffClockOut: async (_actor, payload) => ({
|
|
assignmentId: payload.assignmentId || 'assignment-1',
|
|
status: 'CLOCK_OUT',
|
|
}),
|
|
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/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,
|
|
overrideReason: 'GPS timed out near the hub',
|
|
});
|
|
|
|
assert.equal(res.status, 200);
|
|
assert.equal(res.body.status, 'CLOCK_IN');
|
|
});
|
|
|
|
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('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');
|
|
});
|