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:
@@ -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')`,
|
||||
|
||||
Reference in New Issue
Block a user