Task #118 — Fix delete-force-warnings.test.mjs after audit-action rename + harden services tests

T1 (audit-action rename in #93):
- Updated the user force-delete test to look up audit_logs by
  action = ANY(['user.delete', 'user.force_delete']).
- Updated the app  force-delete test to look up audit_logs by
  action = ANY(['app.delete',  'app.force_delete']).
- Used array-of-actions matchers (forward-compatible with the old
  name in case anything else still emits it).
- The service force-delete test was left untouched because
  artifacts/api-server/src/routes/services.ts still emits
  'service.force_delete' (only user/app/group were renamed in #93).

T2 (services per-run uniqueness + defensive sweep):
- Added module-level SVC_STAMP and SVC_PREFIX = `TestSvc_<stamp>_`.
- Renamed the three services-test name_en literals to
  `${SVC_PREFIX}Clean`, `${SVC_PREFIX}Busy`, `${SVC_PREFIX}Force`
  so re-running the file no longer collides on services_name_en_unique.
- Now pushes every created service id to createdServiceIds (clean +
  busy + force), not just busy.
- Added a defensive sweep in after() that, after the tracked-id
  cleanup, deletes any leftover services whose name_en starts with
  SVC_PREFIX, plus their child rows in service_orders. This mirrors
  the apps-side sweep added in Task #116.

Pre-existing pollution cleanup:
- Removed the orphan rows left over from the failed 2026-04-28
  test runs (services ids 166/168 = 'Clean Svc'/'Force Svc',
  user ids 4397/4889 = del_force_*) and their child rows. These
  predate the fix; removing them now keeps the workspace clean.

Verification:
- pnpm --filter @workspace/api-server node --test
  tests/delete-force-warnings.test.mjs: 9/9 PASS twice in a row.
- Post-run sweep query: 0 leftover apps, 0 leftover services with
  the test prefixes, 0 leftover admin users.

Out of scope (not touched):
- Other test files in artifacts/api-server/tests/.
- The audit-action rename itself (already merged in #93).
- The wider failing `test` workflow — its other failures are
  unrelated to this file.
This commit is contained in:
riyadhafraa
2026-04-29 06:34:27 +00:00
parent 7c99bcdf9e
commit 675fef47f1
@@ -14,6 +14,9 @@ const TEST_PASSWORD_HASH =
const pool = new pg.Pool({ connectionString: DATABASE_URL });
const SVC_STAMP = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
const SVC_PREFIX = `TestSvc_${SVC_STAMP}_`;
let adminId;
let adminUsername;
let adminCookie;
@@ -70,6 +73,24 @@ after(async () => {
createdServiceIds,
]);
}
// Defensive sweep: kill any leftover services whose name_en matches the
// per-run test prefix, plus their child rows in service_orders. Mirrors
// the apps-side sweep added in Task #116.
const svcSweep = await pool.query(
`SELECT id FROM services WHERE name_en LIKE $1`,
[`${SVC_PREFIX}%`],
);
if (svcSweep.rowCount > 0) {
const svcIds = svcSweep.rows.map((r) => r.id);
await pool.query(
`DELETE FROM service_orders WHERE service_id = ANY($1::int[])`,
[svcIds],
);
await pool.query(`DELETE FROM services WHERE id = ANY($1::int[])`, [
svcIds,
]);
}
if (createdAppIds.length) {
await pool.query(`DELETE FROM app_opens WHERE app_id = ANY($1::int[])`, [
createdAppIds,
@@ -223,8 +244,8 @@ test("DELETE /api/users/:id?force=true deletes user and writes audit log", async
assert.equal(check.rowCount, 0);
const audit = await pool.query(
`SELECT * FROM audit_logs WHERE action = 'user.force_delete' AND target_id = $1`,
[uid],
`SELECT * FROM audit_logs WHERE action = ANY($1) AND target_id = $2`,
[["user.delete", "user.force_delete"], uid],
);
assert.equal(audit.rowCount, 1);
assert.equal(audit.rows[0].actor_user_id, adminId);
@@ -324,8 +345,8 @@ test("DELETE /api/apps/:id?force=true deletes app and writes audit log", async (
assert.equal(check.rowCount, 0);
const audit = await pool.query(
`SELECT * FROM audit_logs WHERE action = 'app.force_delete' AND target_id = $1`,
[String(appId)],
`SELECT * FROM audit_logs WHERE action = ANY($1) AND target_id = $2`,
[["app.delete", "app.force_delete"], String(appId)],
);
assert.equal(audit.rowCount, 1);
});
@@ -334,9 +355,11 @@ test("DELETE /api/apps/:id?force=true deletes app and writes audit log", async (
test("DELETE /api/services/:id without dependents succeeds (no force needed)", async () => {
const s = await pool.query(
`INSERT INTO services (name_ar, name_en, price, is_available) VALUES ('خ', 'Clean Svc', 1.00, true) RETURNING id`,
`INSERT INTO services (name_ar, name_en, price, is_available) VALUES ('خ', $1, 1.00, true) RETURNING id`,
[`${SVC_PREFIX}Clean`],
);
const id = s.rows[0].id;
createdServiceIds.push(id);
const res = await fetch(`${API_BASE}/api/services/${id}`, {
method: "DELETE",
@@ -347,7 +370,8 @@ test("DELETE /api/services/:id without dependents succeeds (no force needed)", a
test("DELETE /api/services/:id with orders returns 409 with counts", async () => {
const s = await pool.query(
`INSERT INTO services (name_ar, name_en, price, is_available) VALUES ('خ', 'Busy Svc', 2.00, true) RETURNING id`,
`INSERT INTO services (name_ar, name_en, price, is_available) VALUES ('خ', $1, 2.00, true) RETURNING id`,
[`${SVC_PREFIX}Busy`],
);
const sid = s.rows[0].id;
createdServiceIds.push(sid);
@@ -373,9 +397,11 @@ test("DELETE /api/services/:id with orders returns 409 with counts", async () =>
test("DELETE /api/services/:id?force=true deletes service + orders and writes audit log", async () => {
const s = await pool.query(
`INSERT INTO services (name_ar, name_en, price, is_available) VALUES ('خ', 'Force Svc', 3.00, true) RETURNING id`,
`INSERT INTO services (name_ar, name_en, price, is_available) VALUES ('خ', $1, 3.00, true) RETURNING id`,
[`${SVC_PREFIX}Force`],
);
const sid = s.rows[0].id;
createdServiceIds.push(sid);
await pool.query(
`INSERT INTO service_orders (user_id, service_id, status) VALUES ($1, $2, 'pending')`,