2026-04-21 18:24:20 +00:00
import { test , before , after } from "node:test" ;
import assert from "node:assert/strict" ;
import pg from "pg" ;
const API _BASE = process . env . TEST _API _BASE ? ? "http://localhost:8080" ;
const DATABASE _URL = process . env . DATABASE _URL ;
2026-04-21 18:34:14 +00:00
if ( ! DATABASE _URL ) throw new Error ( "DATABASE_URL must be set to run these tests" ) ;
2026-04-21 18:24:20 +00:00
const TEST _PASSWORD = "TestPass123!" ;
const TEST _PASSWORD _HASH =
"$2b$10$Bs636ukPMyz01nKrsi.5m.JlDXSN22AVCvn8cgPWWDbo5yJRQX2vu" ;
const pool = new pg . Pool ( { connectionString : DATABASE _URL } ) ;
const createdUserIds = [ ] ;
const createdOrderIds = [ ] ;
async function createUser ( prefix , roleName ) {
const username = ` ${ prefix } _ ${ Date . now ( ) . toString ( 36 ) } _ ${ Math . random ( )
. toString ( 36 )
. slice ( 2 , 8 ) } ` ;
const { rows } = await pool . query (
` INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
VALUES ( $ 1, $ 2, $ 3, 'SO Test', 'en', true) RETURNING id ` ,
[ username , ` ${ username } @example.com ` , TEST _PASSWORD _HASH ] ,
) ;
const id = rows [ 0 ] . id ;
createdUserIds . push ( id ) ;
2026-04-21 18:34:14 +00:00
if ( roleName ) {
await pool . query (
` INSERT INTO user_roles (user_id, role_id) SELECT $ 1, id FROM roles WHERE name = $ 2 ` ,
[ id , roleName ] ,
) ;
}
2026-04-21 18:24:20 +00:00
return { id , username } ;
}
async function login ( username ) {
const res = await fetch ( ` ${ API _BASE } /api/auth/login ` , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { username , password : TEST _PASSWORD } ) ,
} ) ;
assert . equal ( res . status , 200 ) ;
const sc = res . headers . get ( "set-cookie" ) ;
return sc . split ( "," ) . map ( ( c ) => c . split ( ";" ) [ 0 ] . trim ( ) ) . find ( ( c ) =>
c . startsWith ( "connect.sid=" ) ,
) ;
}
async function call ( cookie , method , path , body ) {
return fetch ( ` ${ API _BASE } /api ${ path } ` , {
method ,
2026-04-21 18:34:14 +00:00
headers : { "Content-Type" : "application/json" , Cookie : cookie } ,
2026-04-21 18:24:20 +00:00
body : body ? JSON . stringify ( body ) : undefined ,
} ) ;
}
let serviceId ;
before ( async ( ) => {
const { rows } = await pool . query (
` SELECT id FROM services WHERE is_available = true ORDER BY id LIMIT 1 ` ,
) ;
if ( rows . length === 0 ) throw new Error ( "No available service to test with" ) ;
serviceId = rows [ 0 ] . id ;
} ) ;
after ( async ( ) => {
if ( createdOrderIds . length > 0 ) {
await pool . query ( ` DELETE FROM service_orders WHERE id = ANY( $ 1::int[]) ` , [
createdOrderIds ,
] ) ;
}
if ( createdUserIds . length > 0 ) {
2026-04-21 18:34:14 +00:00
await pool . query ( ` DELETE FROM service_orders WHERE user_id = ANY( $ 1::int[]) OR assigned_to = ANY( $ 1::int[]) ` , [ createdUserIds ] ) ;
await pool . query ( ` DELETE FROM notifications WHERE user_id = ANY( $ 1::int[]) ` , [ createdUserIds ] ) ;
await pool . query ( ` DELETE FROM user_roles WHERE user_id = ANY( $ 1::int[]) ` , [ createdUserIds ] ) ;
2026-04-21 18:24:20 +00:00
await pool . query ( ` DELETE FROM users WHERE id = ANY( $ 1::int[]) ` , [ createdUserIds ] ) ;
}
await pool . end ( ) ;
} ) ;
2026-04-21 18:34:14 +00:00
test ( "client can place + list own orders; service summary excludes descriptions" , async ( ) => {
2026-04-21 18:24:20 +00:00
const requester = await createUser ( "req" , "user" ) ;
2026-04-21 18:34:14 +00:00
const cookie = await login ( requester . username ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
let res = await call ( cookie , "POST" , "/orders" , {
2026-04-21 18:24:20 +00:00
serviceId ,
notes : "extra hot" ,
} ) ;
assert . equal ( res . status , 201 ) ;
const order = await res . json ( ) ;
createdOrderIds . push ( order . id ) ;
assert . equal ( order . status , "pending" ) ;
2026-04-21 18:34:14 +00:00
assert . equal ( order . userId , requester . id ) ;
2026-04-21 18:24:20 +00:00
assert . ok ( order . service ) ;
assert . ok ( order . service . nameAr ) ;
2026-04-21 18:34:14 +00:00
assert . ok ( order . service . nameEn ) ;
2026-04-21 18:24:20 +00:00
assert . equal ( order . service . descriptionAr , undefined ) ;
2026-04-21 18:34:14 +00:00
assert . equal ( order . service . descriptionEn , undefined ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
res = await call ( cookie , "GET" , "/orders/my" ) ;
2026-04-21 18:24:20 +00:00
assert . equal ( res . status , 200 ) ;
2026-04-21 18:34:14 +00:00
const list = await res . json ( ) ;
const found = list . find ( ( o ) => o . id === order . id ) ;
assert . ok ( found ) ;
assert . equal ( found . service . descriptionAr , undefined ) ;
} ) ;
test ( "non-receiver gets 403 from /incoming and /confirm-receipt" , async ( ) => {
const owner = await createUser ( "own" , "user" ) ;
const cookieOwner = await login ( owner . username ) ;
let res = await call ( cookieOwner , "POST" , "/orders" , { serviceId } ) ;
const order = await res . json ( ) ;
createdOrderIds . push ( order . id ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
// Non-receiver listing
res = await call ( cookieOwner , "GET" , "/orders/incoming" ) ;
2026-04-21 18:24:20 +00:00
assert . equal ( res . status , 403 ) ;
2026-04-21 18:34:14 +00:00
// Non-receiver tries to claim
res = await call ( cookieOwner , "PATCH" , ` /orders/ ${ order . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 403 ) ;
} ) ;
test ( "user with no roles is 403 on /incoming (middleware has no auto-bypass)" , async ( ) => {
const noRole = await createUser ( "noro" , null ) ;
const cookie = await login ( noRole . username ) ;
const res = await call ( cookie , "GET" , "/orders/incoming" ) ;
assert . equal ( res . status , 403 ) ;
} ) ;
test ( "two parallel confirm-receipt: one wins (200), other 409 already_claimed" , async ( ) => {
const owner = await createUser ( "own2" , "user" ) ;
const r1 = await createUser ( "r1" , "order_receiver" ) ;
const r2 = await createUser ( "r2" , "order_receiver" ) ;
const co = await login ( owner . username ) ;
const c1 = await login ( r1 . username ) ;
const c2 = await login ( r2 . username ) ;
let res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const order = await res . json ( ) ;
createdOrderIds . push ( order . id ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
const [ a , b ] = await Promise . all ( [
call ( c1 , "PATCH" , ` /orders/ ${ order . id } /confirm-receipt ` ) ,
call ( c2 , "PATCH" , ` /orders/ ${ order . id } /confirm-receipt ` ) ,
] ) ;
const codes = [ a . status , b . status ] . sort ( ) ;
assert . deepEqual ( codes , [ 200 , 409 ] ) ;
const losing = a . status === 409 ? a : b ;
const body = await losing . json ( ) ;
assert . equal ( body . error , "already_claimed" ) ;
} ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
test ( "status transitions matrix: receiver flows + owner/admin cancel rules" , async ( ) => {
const owner = await createUser ( "own3" , "user" ) ;
const recv = await createUser ( "recv3" , "order_receiver" ) ;
const stranger = await createUser ( "strg3" , "user" ) ;
const adminUser = await createUser ( "admn3" , "admin" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
const cs = await login ( stranger . username ) ;
const ca = await login ( adminUser . username ) ;
// Order 1: receiver flow pending → received → preparing → completed
let res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
let order = await res . json ( ) ;
createdOrderIds . push ( order . id ) ;
// Owner CAN cancel while pending
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const cancelOrder = await res . json ( ) ;
createdOrderIds . push ( cancelOrder . id ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ cancelOrder . id } /status ` , {
status : "cancelled" ,
2026-04-21 18:24:20 +00:00
} ) ;
assert . equal ( res . status , 200 ) ;
2026-04-21 18:34:14 +00:00
assert . equal ( ( await res . json ( ) ) . status , "cancelled" ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
// Stranger cannot move status to preparing
res = await call ( cs , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "preparing" ,
} ) ;
2026-04-21 18:24:20 +00:00
assert . equal ( res . status , 403 ) ;
2026-04-21 18:34:14 +00:00
// Receiver claims first
res = await call ( cr , "PATCH" , ` /orders/ ${ order . id } /confirm-receipt ` ) ;
2026-04-21 18:24:20 +00:00
assert . equal ( res . status , 200 ) ;
2026-04-21 18:34:14 +00:00
// Receiver moves to preparing (allowed from received)
res = await call ( cr , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "preparing" ,
} ) ;
2026-04-21 18:24:20 +00:00
assert . equal ( res . status , 200 ) ;
2026-04-21 18:34:14 +00:00
assert . equal ( ( await res . json ( ) ) . status , "preparing" ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
// Owner cannot cancel once preparing (only pending/received)
res = await call ( co , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 403 ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:34:14 +00:00
// Admin can cancel anytime
// (use a NEW preparing order)
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const o2 = await res . json ( ) ;
createdOrderIds . push ( o2 . id ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o2 . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o2 . id } /status ` , { status : "preparing" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( ca , "PATCH" , ` /orders/ ${ o2 . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
2026-04-21 18:24:20 +00:00
2026-04-21 18:58:19 +00:00
// Assigned receiver CAN cancel their own assigned order while preparing
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const o3 = await res . json ( ) ;
createdOrderIds . push ( o3 . id ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /status ` , { status : "preparing" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
assert . equal ( ( await res . json ( ) ) . status , "cancelled" ) ;
// A different receiver (not the assignee) cannot cancel
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const o4 = await res . json ( ) ;
createdOrderIds . push ( o4 . id ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o4 . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
const otherRecv = await createUser ( "recv4" , "order_receiver" ) ;
const cor = await login ( otherRecv . username ) ;
res = await call ( cor , "PATCH" , ` /orders/ ${ o4 . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 403 ) ;
2026-04-21 18:34:14 +00:00
// Receiver completes original order
res = await call ( cr , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "completed" ,
} ) ;
assert . equal ( res . status , 200 ) ;
assert . equal ( ( await res . json ( ) ) . status , "completed" ) ;
2026-04-21 18:24:20 +00:00
2026-04-27 11:28:14 +00:00
// Cannot transition completed → anything (non-admin); now 409 order_unavailable per Task #80
2026-04-21 18:34:14 +00:00
res = await call ( cr , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "preparing" ,
} ) ;
2026-04-27 11:28:14 +00:00
assert . equal ( res . status , 409 ) ;
2026-04-21 18:34:14 +00:00
// Admin CAN cancel a completed order
res = await call ( ca , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 200 ) ;
assert . equal ( ( await res . json ( ) ) . status , "cancelled" ) ;
2026-04-21 18:24:20 +00:00
} ) ;
2026-04-22 06:28:29 +00:00
test ( "requester gets a distinct notification when the receiver cancels a claimed order" , async ( ) => {
const owner = await createUser ( "ownN" , "user" ) ;
const recv = await createUser ( "recvN" , "order_receiver" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
let res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( res . status , 201 ) ;
const order = await res . json ( ) ;
createdOrderIds . push ( order . id ) ;
// Receiver claims, then cancels
res = await call ( cr , "PATCH" , ` /orders/ ${ order . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ order . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 200 ) ;
// Owner should have a cancellation notification with the receiver-cancel body
const { rows } = await pool . query (
` SELECT title_ar, title_en, body_ar, body_en
FROM notifications
WHERE user_id = $ 1 AND related_type = 'order' AND related_id = $ 2
ORDER BY id DESC ` ,
[ owner . id , order . id ] ,
) ;
const cancelNotif = rows . find (
( n ) => n . title _en === "Your order was cancelled" ,
) ;
assert . ok ( cancelNotif , "expected a cancellation notification for the owner" ) ;
assert . match (
cancelNotif . body _en ,
/claimed but later cancelled by the receiver/ ,
) ;
assert . match ( cancelNotif . body _ar , /استلم طلبك ولكن تم إلغاؤه لاحقاً/ ) ;
} ) ;
2026-04-22 06:37:01 +00:00
test ( "DELETE /orders/:id — owner can delete completed/cancelled, admin can delete any, others 403" , async ( ) => {
const owner = await createUser ( "delOwn" , "user" ) ;
const recv = await createUser ( "delRecv" , "order_receiver" ) ;
const stranger = await createUser ( "delStr" , "user" ) ;
const adminUser = await createUser ( "delAdm" , "admin" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
const cs = await login ( stranger . username ) ;
const ca = await login ( adminUser . username ) ;
// Helper to create an order owned by `co`
const placeOrder = async ( ) => {
const r = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( r . status , 201 ) ;
const o = await r . json ( ) ;
createdOrderIds . push ( o . id ) ;
return o ;
} ;
// 1) Pending order: owner cannot delete (not terminal)
const o1 = await placeOrder ( ) ;
let res = await call ( co , "DELETE" , ` /orders/ ${ o1 . id } ` ) ;
assert . equal ( res . status , 403 ) ;
// 2) Stranger cannot delete a terminal order they don't own
const o2 = await placeOrder ( ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ o2 . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cs , "DELETE" , ` /orders/ ${ o2 . id } ` ) ;
assert . equal ( res . status , 403 ) ;
// 3) Owner CAN delete a cancelled order they own
res = await call ( co , "DELETE" , ` /orders/ ${ o2 . id } ` ) ;
assert . equal ( res . status , 204 ) ;
// Confirm it's gone from /orders/my
res = await call ( co , "GET" , "/orders/my" ) ;
const list1 = await res . json ( ) ;
assert . ok ( ! list1 . find ( ( o ) => o . id === o2 . id ) ) ;
2026-04-22 06:38:13 +00:00
// 3b) Deleting the same order again returns 404 (idempotent semantics)
res = await call ( co , "DELETE" , ` /orders/ ${ o2 . id } ` ) ;
assert . equal ( res . status , 404 ) ;
2026-04-22 06:37:01 +00:00
// 4) Owner CAN delete a completed order
const o3 = await placeOrder ( ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /status ` , { status : "preparing" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /status ` , { status : "completed" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( co , "DELETE" , ` /orders/ ${ o3 . id } ` ) ;
assert . equal ( res . status , 204 ) ;
// 5) Admin CAN delete a non-terminal (pending) order
const o4 = await placeOrder ( ) ;
res = await call ( ca , "DELETE" , ` /orders/ ${ o4 . id } ` ) ;
assert . equal ( res . status , 204 ) ;
// 6) 404 for unknown id
res = await call ( co , "DELETE" , ` /orders/9999999 ` ) ;
assert . equal ( res . status , 404 ) ;
// 7) Unauthenticated → 401
res = await fetch ( ` ${ API _BASE } /api/orders/1 ` , { method : "DELETE" } ) ;
assert . equal ( res . status , 401 ) ;
} ) ;
2026-05-05 13:42:43 +00:00
test ( "DELETE /orders/:id — receivers can delete orders visible in their incoming view (unclaimed pending or own active); non-receiver, non-owner still 403" , async ( ) => {
2026-05-05 13:35:21 +00:00
const owner = await createUser ( "recvDelOwn" , "user" ) ;
const recv = await createUser ( "recvDelRecv" , "order_receiver" ) ;
const stranger = await createUser ( "recvDelStr" , "user" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
const cs = await login ( stranger . username ) ;
const place = async ( ) => {
const r = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( r . status , 201 ) ;
const o = await r . json ( ) ;
createdOrderIds . push ( o . id ) ;
return o ;
} ;
// 1) Receiver can delete a pending, unclaimed order.
const o1 = await place ( ) ;
let res = await call ( cr , "DELETE" , ` /orders/ ${ o1 . id } ` ) ;
assert . equal ( res . status , 204 , "receiver should be able to delete pending order" ) ;
// 2) Receiver can delete a received order (claimed by them).
const o2 = await place ( ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o2 . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "DELETE" , ` /orders/ ${ o2 . id } ` ) ;
assert . equal ( res . status , 204 ) ;
2026-05-05 13:42:43 +00:00
// 3) Receiver can delete an order they have claimed and moved to preparing
// (their own active order — visible in their incoming view).
2026-05-05 13:35:21 +00:00
const o3 = await place ( ) ;
2026-05-05 13:42:43 +00:00
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /confirm-receipt ` ) ;
2026-05-05 13:35:21 +00:00
assert . equal ( res . status , 200 ) ;
2026-05-05 13:42:43 +00:00
res = await call ( cr , "PATCH" , ` /orders/ ${ o3 . id } /status ` , { status : "preparing" } ) ;
2026-05-05 13:35:21 +00:00
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "DELETE" , ` /orders/ ${ o3 . id } ` ) ;
assert . equal ( res . status , 204 ) ;
2026-05-05 13:42:43 +00:00
// 3b) Receiver may NOT delete an order claimed by a *different* receiver —
// it isn't in their incoming view, so the API must reject it (403).
const otherRecv = await createUser ( "recvDelOther" , "order_receiver" ) ;
const cor = await login ( otherRecv . username ) ;
const o3b = await place ( ) ;
res = await call ( cor , "PATCH" , ` /orders/ ${ o3b . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cor , "PATCH" , ` /orders/ ${ o3b . id } /status ` , { status : "preparing" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "DELETE" , ` /orders/ ${ o3b . id } ` ) ;
assert . equal (
res . status ,
403 ,
"receiver should NOT delete another receiver's claimed order" ,
) ;
2026-05-05 13:35:21 +00:00
// 4) Non-receiver, non-owner still gets 403 on any state.
const o4 = await place ( ) ;
res = await call ( cs , "DELETE" , ` /orders/ ${ o4 . id } ` ) ;
assert . equal ( res . status , 403 ) ;
// 5) Receiver may NOT delete a terminal (completed/cancelled) order — that
// is the owner's cleanup responsibility, not the receiver's queue.
const o5 = await place ( ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o5 . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o5 . id } /status ` , { status : "preparing" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ o5 . id } /status ` , { status : "completed" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "DELETE" , ` /orders/ ${ o5 . id } ` ) ;
assert . equal ( res . status , 403 , "receiver should NOT delete completed orders" ) ;
const o6 = await place ( ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ o6 . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "DELETE" , ` /orders/ ${ o6 . id } ` ) ;
assert . equal ( res . status , 403 , "receiver should NOT delete cancelled orders" ) ;
} ) ;
test ( "POST /orders/bulk-delete — deletes authorized ids, reports failures, dedupes" , async ( ) => {
const owner = await createUser ( "bulkOwn" , "user" ) ;
const recv = await createUser ( "bulkRecv" , "order_receiver" ) ;
const stranger = await createUser ( "bulkStr" , "user" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
const cs = await login ( stranger . username ) ;
const place = async ( ) => {
const r = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( r . status , 201 ) ;
const o = await r . json ( ) ;
createdOrderIds . push ( o . id ) ;
return o ;
} ;
// Receiver deletes 3 ids; one is unknown → reported as failed; ids deduped.
const a = await place ( ) ;
const b = await place ( ) ;
const c = await place ( ) ;
const unknownId = 99999991 ;
let res = await call ( cr , "POST" , "/orders/bulk-delete" , {
ids : [ a . id , b . id , c . id , c . id , unknownId ] ,
} ) ;
assert . equal ( res . status , 200 ) ;
let body = await res . json ( ) ;
assert . deepEqual ( body . deletedIds . sort ( ) , [ a . id , b . id , c . id ] . sort ( ) ) ;
assert . deepEqual ( body . failedIds , [ unknownId ] ) ;
// Stranger tries to bulk-delete owner's pending orders → all rejected.
const d = await place ( ) ;
const e = await place ( ) ;
res = await call ( cs , "POST" , "/orders/bulk-delete" , { ids : [ d . id , e . id ] } ) ;
assert . equal ( res . status , 200 ) ;
body = await res . json ( ) ;
assert . deepEqual ( body . deletedIds , [ ] ) ;
assert . deepEqual ( body . failedIds . sort ( ) , [ d . id , e . id ] . sort ( ) ) ;
// Owner can bulk-delete only the cancelled one of two; pending stays.
res = await call ( co , "PATCH" , ` /orders/ ${ d . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( co , "POST" , "/orders/bulk-delete" , { ids : [ d . id , e . id ] } ) ;
assert . equal ( res . status , 200 ) ;
body = await res . json ( ) ;
assert . deepEqual ( body . deletedIds , [ d . id ] ) ;
assert . deepEqual ( body . failedIds , [ e . id ] ) ;
// Validation: empty ids → 400
res = await call ( cr , "POST" , "/orders/bulk-delete" , { ids : [ ] } ) ;
assert . equal ( res . status , 400 ) ;
// Unauthenticated → 401
res = await fetch ( ` ${ API _BASE } /api/orders/bulk-delete ` , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { ids : [ 1 ] } ) ,
} ) ;
assert . equal ( res . status , 401 ) ;
} ) ;
2026-04-22 09:04:28 +00:00
test ( "owner can restore (undo) a freshly cancelled order to its prior status; expired window blocks restore" , async ( ) => {
const owner = await createUser ( "undoOwn" , "user" ) ;
const recv = await createUser ( "undoRecv" , "order_receiver" ) ;
const stranger = await createUser ( "undoStr" , "user" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
const cs = await login ( stranger . username ) ;
// Case A: cancel from pending → restore to pending
let res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( res . status , 201 ) ;
const oA = await res . json ( ) ;
createdOrderIds . push ( oA . id ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ oA . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
// Stranger cannot restore
res = await call ( cs , "PATCH" , ` /orders/ ${ oA . id } /status ` , { status : "pending" } ) ;
assert . equal ( res . status , 403 ) ;
// Owner restores within window
res = await call ( co , "PATCH" , ` /orders/ ${ oA . id } /status ` , { status : "pending" } ) ;
assert . equal ( res . status , 200 ) ;
assert . equal ( ( await res . json ( ) ) . status , "pending" ) ;
// Case B: cancel from received → restore to received
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const oB = await res . json ( ) ;
createdOrderIds . push ( oB . id ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ oB . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ oB . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
// Restoring to "pending" should fail because assignedTo is set
res = await call ( co , "PATCH" , ` /orders/ ${ oB . id } /status ` , { status : "pending" } ) ;
assert . equal ( res . status , 400 ) ;
// Correct restore is "received"
res = await call ( co , "PATCH" , ` /orders/ ${ oB . id } /status ` , { status : "received" } ) ;
assert . equal ( res . status , 200 ) ;
assert . equal ( ( await res . json ( ) ) . status , "received" ) ;
// Case C: undo window expired → restore is rejected
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
const oC = await res . json ( ) ;
createdOrderIds . push ( oC . id ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ oC . id } /status ` , { status : "cancelled" } ) ;
assert . equal ( res . status , 200 ) ;
// Backdate updatedAt past the 15s server window.
await pool . query (
` UPDATE service_orders SET updated_at = NOW() - INTERVAL '60 seconds' WHERE id = $ 1 ` ,
[ oC . id ] ,
) ;
res = await call ( co , "PATCH" , ` /orders/ ${ oC . id } /status ` , { status : "pending" } ) ;
assert . equal ( res . status , 400 ) ;
const body = await res . json ( ) ;
assert . match ( body . error , /undo_window_expired/ ) ;
} ) ;
2026-04-27 12:11:57 +00:00
test ( "confirm-receipt returns 409 order_unavailable when the order is cancelled or completed" , async ( ) => {
const owner = await createUser ( "ownU" , "user" ) ;
const recv1 = await createUser ( "recvU1" , "order_receiver" ) ;
const recv2 = await createUser ( "recvU2" , "order_receiver" ) ;
const co = await login ( owner . username ) ;
const cr1 = await login ( recv1 . username ) ;
const cr2 = await login ( recv2 . username ) ;
// Case A: order cancelled before any claim → second receiver sees order_unavailable
let res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( res . status , 201 ) ;
const oCancelled = await res . json ( ) ;
createdOrderIds . push ( oCancelled . id ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ oCancelled . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr1 , "PATCH" , ` /orders/ ${ oCancelled . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 409 ) ;
let body = await res . json ( ) ;
assert . equal (
body . error ,
"order_unavailable" ,
"cancelled order should report order_unavailable, not already_claimed" ,
) ;
// Case B: order taken all the way to completed → another receiver sees order_unavailable
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( res . status , 201 ) ;
const oCompleted = await res . json ( ) ;
createdOrderIds . push ( oCompleted . id ) ;
res = await call ( cr1 , "PATCH" , ` /orders/ ${ oCompleted . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr1 , "PATCH" , ` /orders/ ${ oCompleted . id } /status ` , {
status : "preparing" ,
} ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr1 , "PATCH" , ` /orders/ ${ oCompleted . id } /status ` , {
status : "completed" ,
} ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr2 , "PATCH" , ` /orders/ ${ oCompleted . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 409 ) ;
body = await res . json ( ) ;
assert . equal (
body . error ,
"order_unavailable" ,
"completed order should report order_unavailable, not already_claimed" ,
) ;
} ) ;
test ( "PATCH /orders/:id/status returns 409 order_unavailable when targeting a terminal order" , async ( ) => {
const owner = await createUser ( "ownT" , "user" ) ;
const recv = await createUser ( "recvT" , "order_receiver" ) ;
const co = await login ( owner . username ) ;
const cr = await login ( recv . username ) ;
// Build a completed order (assignee = recv)
let res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( res . status , 201 ) ;
const oDone = await res . json ( ) ;
createdOrderIds . push ( oDone . id ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ oDone . id } /confirm-receipt ` ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ oDone . id } /status ` , {
status : "preparing" ,
} ) ;
assert . equal ( res . status , 200 ) ;
res = await call ( cr , "PATCH" , ` /orders/ ${ oDone . id } /status ` , {
status : "completed" ,
} ) ;
assert . equal ( res . status , 200 ) ;
// Assignee tries preparing on a completed order → 409 order_unavailable
res = await call ( cr , "PATCH" , ` /orders/ ${ oDone . id } /status ` , {
status : "preparing" ,
} ) ;
assert . equal ( res . status , 409 ) ;
let body = await res . json ( ) ;
assert . equal ( body . error , "order_unavailable" ) ;
// Assignee tries completed (again) on a completed order → 409 order_unavailable
res = await call ( cr , "PATCH" , ` /orders/ ${ oDone . id } /status ` , {
status : "completed" ,
} ) ;
assert . equal ( res . status , 409 ) ;
body = await res . json ( ) ;
assert . equal ( body . error , "order_unavailable" ) ;
// Owner tries to cancel a completed order → 409 order_unavailable (non-admin)
res = await call ( co , "PATCH" , ` /orders/ ${ oDone . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 409 ) ;
body = await res . json ( ) ;
assert . equal ( body . error , "order_unavailable" ) ;
// Build a cancelled order (no assignee)
res = await call ( co , "POST" , "/orders" , { serviceId } ) ;
assert . equal ( res . status , 201 ) ;
const oCxl = await res . json ( ) ;
createdOrderIds . push ( oCxl . id ) ;
res = await call ( co , "PATCH" , ` /orders/ ${ oCxl . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 200 ) ;
// Owner tries to cancel an already-cancelled order → 409 order_unavailable (non-admin)
res = await call ( co , "PATCH" , ` /orders/ ${ oCxl . id } /status ` , {
status : "cancelled" ,
} ) ;
assert . equal ( res . status , 409 ) ;
body = await res . json ( ) ;
assert . equal ( body . error , "order_unavailable" ) ;
} ) ;
2026-04-21 18:34:14 +00:00
test ( "unauthenticated cannot place order" , async ( ) => {
2026-04-21 18:24:20 +00:00
const res = await fetch ( ` ${ API _BASE } /api/orders ` , {
method : "POST" ,
headers : { "Content-Type" : "application/json" } ,
body : JSON . stringify ( { serviceId } ) ,
} ) ;
assert . equal ( res . status , 401 ) ;
} ) ;