import assert from 'node:assert/strict'; import { V2DemoFixture as fixture } from './v2-demo-fixture.mjs'; const firebaseApiKey = process.env.FIREBASE_API_KEY || 'AIzaSyBqRtZPMGU-Sz5x5UnRrunKu5NSWYyPRn8'; const demoEmail = process.env.V2_SMOKE_EMAIL || fixture.users.businessOwner.email; const demoPassword = process.env.V2_SMOKE_PASSWORD || 'Demo2026!'; const commandBaseUrl = process.env.COMMAND_API_BASE_URL || 'https://krow-command-api-v2-e3g6witsvq-uc.a.run.app'; const queryBaseUrl = process.env.QUERY_API_BASE_URL || 'https://krow-query-api-v2-e3g6witsvq-uc.a.run.app'; async function signInWithPassword() { const response = await fetch( `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${firebaseApiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: demoEmail, password: demoPassword, returnSecureToken: true, }), } ); const payload = await response.json(); if (!response.ok) { throw new Error(`Firebase sign-in failed: ${JSON.stringify(payload)}`); } return { idToken: payload.idToken, localId: payload.localId, }; } async function apiCall(baseUrl, path, { method = 'GET', token, idempotencyKey, body, expectedStatus = 200, } = {}) { const headers = {}; if (token) { headers.Authorization = `Bearer ${token}`; } if (idempotencyKey) { headers['Idempotency-Key'] = idempotencyKey; } if (body !== undefined) { headers['Content-Type'] = 'application/json'; } const response = await fetch(`${baseUrl}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), }); const text = await response.text(); const payload = text ? JSON.parse(text) : {}; if (response.status !== expectedStatus) { throw new Error(`${method} ${path} expected ${expectedStatus}, got ${response.status}: ${text}`); } return payload; } function uniqueKey(prefix) { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; } function logStep(step, payload) { // eslint-disable-next-line no-console console.log(`[live-smoke-v2] ${step}: ${JSON.stringify(payload)}`); } async function main() { const auth = await signInWithPassword(); assert.equal(auth.localId, fixture.users.businessOwner.id); logStep('auth.ok', { uid: auth.localId, email: demoEmail }); const listOrders = await apiCall( queryBaseUrl, `/query/tenants/${fixture.tenant.id}/orders`, { token: auth.idToken } ); assert.ok(Array.isArray(listOrders.items)); assert.ok(listOrders.items.some((item) => item.id === fixture.orders.open.id)); logStep('orders.list.ok', { count: listOrders.items.length }); const openOrderDetail = await apiCall( queryBaseUrl, `/query/tenants/${fixture.tenant.id}/orders/${fixture.orders.open.id}`, { token: auth.idToken } ); assert.equal(openOrderDetail.id, fixture.orders.open.id); assert.equal(openOrderDetail.shifts[0].id, fixture.shifts.open.id); logStep('orders.detail.ok', { orderId: openOrderDetail.id, shiftCount: openOrderDetail.shifts.length }); const favoriteResult = await apiCall( commandBaseUrl, `/commands/businesses/${fixture.business.id}/favorite-staff`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('favorite'), body: { tenantId: fixture.tenant.id, staffId: fixture.staff.ana.id, }, } ); assert.equal(favoriteResult.staffId, fixture.staff.ana.id); logStep('favorites.add.ok', favoriteResult); const favoriteList = await apiCall( queryBaseUrl, `/query/tenants/${fixture.tenant.id}/businesses/${fixture.business.id}/favorite-staff`, { token: auth.idToken } ); assert.ok(favoriteList.items.some((item) => item.staffId === fixture.staff.ana.id)); logStep('favorites.list.ok', { count: favoriteList.items.length }); const reviewResult = await apiCall( commandBaseUrl, `/commands/assignments/${fixture.assignments.completedAna.id}/reviews`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('review'), body: { tenantId: fixture.tenant.id, businessId: fixture.business.id, staffId: fixture.staff.ana.id, rating: 5, reviewText: 'Live smoke review', tags: ['smoke', 'reliable'], }, } ); assert.equal(reviewResult.staffId, fixture.staff.ana.id); logStep('reviews.create.ok', reviewResult); const reviewSummary = await apiCall( queryBaseUrl, `/query/tenants/${fixture.tenant.id}/staff/${fixture.staff.ana.id}/review-summary`, { token: auth.idToken } ); assert.equal(reviewSummary.staffId, fixture.staff.ana.id); assert.ok(reviewSummary.ratingCount >= 1); logStep('reviews.summary.ok', { ratingCount: reviewSummary.ratingCount, averageRating: reviewSummary.averageRating }); const assigned = await apiCall( commandBaseUrl, `/commands/shifts/${fixture.shifts.open.id}/assign-staff`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('assign'), body: { tenantId: fixture.tenant.id, shiftRoleId: fixture.shiftRoles.openBarista.id, workforceId: fixture.workforce.ana.id, applicationId: fixture.applications.openAna.id, }, } ); assert.equal(assigned.shiftId, fixture.shifts.open.id); logStep('assign.ok', assigned); const accepted = await apiCall( commandBaseUrl, `/commands/shifts/${fixture.shifts.open.id}/accept`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('accept'), body: { shiftRoleId: fixture.shiftRoles.openBarista.id, workforceId: fixture.workforce.ana.id, }, } ); assert.ok(['ASSIGNED', 'ACCEPTED', 'CHECKED_IN', 'CHECKED_OUT', 'COMPLETED'].includes(accepted.status)); const liveAssignmentId = accepted.assignmentId || assigned.assignmentId; logStep('accept.ok', accepted); const clockIn = await apiCall( commandBaseUrl, '/commands/attendance/clock-in', { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('clockin'), body: { assignmentId: liveAssignmentId, sourceType: 'NFC', sourceReference: 'smoke', nfcTagUid: fixture.clockPoint.nfcTagUid, deviceId: 'smoke-device', latitude: fixture.clockPoint.latitude, longitude: fixture.clockPoint.longitude, accuracyMeters: 5, }, } ); assert.equal(clockIn.assignmentId, liveAssignmentId); logStep('attendance.clockin.ok', clockIn); const clockOut = await apiCall( commandBaseUrl, '/commands/attendance/clock-out', { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('clockout'), body: { assignmentId: liveAssignmentId, sourceType: 'NFC', sourceReference: 'smoke', nfcTagUid: fixture.clockPoint.nfcTagUid, deviceId: 'smoke-device', latitude: fixture.clockPoint.latitude, longitude: fixture.clockPoint.longitude, accuracyMeters: 5, }, } ); assert.equal(clockOut.assignmentId, liveAssignmentId); logStep('attendance.clockout.ok', clockOut); const attendance = await apiCall( queryBaseUrl, `/query/tenants/${fixture.tenant.id}/assignments/${liveAssignmentId}/attendance`, { token: auth.idToken } ); assert.ok(Array.isArray(attendance.events)); assert.ok(attendance.events.length >= 2); logStep('attendance.query.ok', { eventCount: attendance.events.length, sessionStatus: attendance.sessionStatus }); const orderNumber = `ORD-V2-SMOKE-${Date.now()}`; const createdOrder = await apiCall( commandBaseUrl, '/commands/orders/create', { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('order-create'), body: { tenantId: fixture.tenant.id, businessId: fixture.business.id, vendorId: fixture.vendor.id, orderNumber, title: 'Smoke created order', serviceType: 'EVENT', shifts: [ { shiftCode: `SHIFT-${Date.now()}`, title: 'Smoke shift', startsAt: new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString(), endsAt: new Date(Date.now() + 6 * 60 * 60 * 1000).toISOString(), requiredWorkers: 1, clockPointId: fixture.clockPoint.id, roles: [ { roleCode: fixture.roles.barista.code, roleName: fixture.roles.barista.name, workersNeeded: 1, payRateCents: 2200, billRateCents: 3500, }, ], }, ], }, } ); assert.equal(createdOrder.orderNumber, orderNumber); logStep('orders.create.ok', createdOrder); const updatedOrder = await apiCall( commandBaseUrl, `/commands/orders/${createdOrder.orderId}/update`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('order-update'), body: { tenantId: fixture.tenant.id, title: 'Smoke updated order', notes: 'updated during live smoke', }, } ); assert.equal(updatedOrder.orderId, createdOrder.orderId); logStep('orders.update.ok', updatedOrder); const changedShift = await apiCall( commandBaseUrl, `/commands/shifts/${createdOrder.shiftIds[0]}/change-status`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('shift-status'), body: { tenantId: fixture.tenant.id, status: 'PENDING_CONFIRMATION', reason: 'live smoke transition', }, } ); assert.equal(changedShift.status, 'PENDING_CONFIRMATION'); logStep('shift.status.ok', changedShift); const cancelledOrder = await apiCall( commandBaseUrl, `/commands/orders/${createdOrder.orderId}/cancel`, { method: 'POST', token: auth.idToken, idempotencyKey: uniqueKey('order-cancel'), body: { tenantId: fixture.tenant.id, reason: 'live smoke cleanup', }, } ); assert.equal(cancelledOrder.status, 'CANCELLED'); logStep('orders.cancel.ok', cancelledOrder); const cancelledOrderDetail = await apiCall( queryBaseUrl, `/query/tenants/${fixture.tenant.id}/orders/${createdOrder.orderId}`, { token: auth.idToken } ); assert.equal(cancelledOrderDetail.status, 'CANCELLED'); logStep('orders.cancel.verify.ok', { orderId: cancelledOrderDetail.id, status: cancelledOrderDetail.status }); // eslint-disable-next-line no-console console.log('LIVE_SMOKE_V2_OK'); } main().catch((error) => { // eslint-disable-next-line no-console console.error(error); process.exit(1); });