feat(attendance): add notification delivery and NFC security foundation
This commit is contained in:
@@ -48,13 +48,30 @@ function createMobileHandlers() {
|
||||
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',
|
||||
@@ -157,6 +174,36 @@ test('POST /commands/client/billing/invoices/:invoiceId/approve injects invoice
|
||||
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)
|
||||
@@ -183,11 +230,13 @@ test('POST /commands/staff/clock-in accepts shift-based payload', async () => {
|
||||
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 () => {
|
||||
@@ -230,6 +279,35 @@ test('POST /commands/staff/location-streams accepts batched location payloads',
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user