Files
TX/artifacts/api-server/tests/list-dependency-counts.test.mjs
Riyadh 1e5a22d2f9 Task #511: Fully remove Chat feature
Destructive removal per user confirmation ("حذف نهائي ما يرجع").

Removed:
- API: routes/conversations.ts, schema/conversations.ts, all chat
  socket handlers in src/index.ts, /admin/users/:id/dependents/
  conversations+messages endpoints, conversation/message dependency
  counts in users/stats routes.
- Web: pages/chat.tsx, /chat route, dock chat filter, MessageSquare
  icon and messages StatCard on home, all chat-related UI in
  notifications + admin (dependency badges, delete-dialog rows,
  UserDependentConversations/Messages sections, count map keys).
- Locales: nav.chat, home.stats.messages, full chat.* block,
  admin.deleteUser conv/msgCount, admin.users.counts.conv/msg,
  admin.audit.unit.conversation_*/message_*, admin.dependents.user*.
- OpenAPI spec: tags, all /conversations/* paths, conv/msg dependent
  paths, related schemas (ConversationWithDetails, MessageWithSender,
  UserDependentConversation/MessageItem+Page, etc.), UserProfile and
  UserDeletionConflict conv/msg fields, HomeStats.unreadMessages.
  Regenerated client via orval.
- Database: dropped message_reads, messages,
  conversation_participants, conversations (CASCADE); deleted
  notifications with related_type='conversation' or type='chat';
  deleted apps row with slug='chat'; ran drizzle push-force.
- Seed: removed chat:access permission + user-role assignment +
  seeded chat app entry from scripts/src/seed.ts.
- Tests: deleted conversations-leave.test.mjs; cleaned chat refs from
  list-dependency-counts, delete-force-warnings, audit-log-coverage,
  and admin-inline-dependency-counts (e2e) — replaced chat dependents
  with note dependents where needed for force-delete coverage.

Notes preserved: notes.tsx noConversationsYet/conversationWith refer
to NOTE THREADS (not chat) and were intentionally NOT touched.

executive-meetings.ts not modified per replit.md restriction.
Pre-existing flaky test failures in executive-meetings/group/etc
suites remain unrelated to this task.
2026-05-12 10:51:31 +00:00

235 lines
8.6 KiB
JavaScript

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;
if (!DATABASE_URL) {
throw new Error("DATABASE_URL must be set to run these tests");
}
const TEST_PASSWORD = "TestPass123!";
const TEST_PASSWORD_HASH =
"$2b$10$Bs636ukPMyz01nKrsi.5m.JlDXSN22AVCvn8cgPWWDbo5yJRQX2vu";
const pool = new pg.Pool({ connectionString: DATABASE_URL });
const STAMP = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
let adminId;
let adminCookie;
let depUserId;
let depAppId;
let depServiceId;
let depGroupId;
let depPermissionId;
async function loginAndGetCookie(username, password) {
const res = await fetch(`${API_BASE}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
assert.equal(res.status, 200, `login expected 200, got ${res.status}`);
const setCookie = res.headers.get("set-cookie");
return setCookie
.split(",")
.map((c) => c.split(";")[0].trim())
.find((c) => c.startsWith("connect.sid="));
}
before(async () => {
const adminUsername = `dep_admin_${STAMP}`;
const a = await pool.query(
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
VALUES ($1, $2, $3, 'Dep Admin', 'en', true) RETURNING id`,
[adminUsername, `${adminUsername}@example.com`, TEST_PASSWORD_HASH],
);
adminId = a.rows[0].id;
await pool.query(
`INSERT INTO user_roles (user_id, role_id) SELECT $1, id FROM roles WHERE name = 'admin'`,
[adminId],
);
const u = await pool.query(
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
VALUES ($1, $2, $3, 'Dep User', 'en', true) RETURNING id`,
[`dep_user_${STAMP}`, `dep_user_${STAMP}@example.com`, TEST_PASSWORD_HASH],
);
depUserId = u.rows[0].id;
const app = await pool.query(
`INSERT INTO apps (slug, name_ar, name_en, route, color, icon_name)
VALUES ($1, $2, $3, '/dep', '#000', 'Box') RETURNING id`,
[`dep-app-${STAMP}`, `DepApp_${STAMP}`, `DepApp_${STAMP}`],
);
depAppId = app.rows[0].id;
const svc = await pool.query(
`INSERT INTO services (name_ar, name_en, description_ar, description_en)
VALUES ($1, $2, 'desc', 'desc') RETURNING id`,
[`DepSvc_${STAMP}`, `DepSvc_${STAMP}`],
);
depServiceId = svc.rows[0].id;
const grp = await pool.query(
`INSERT INTO groups (name, description_en) VALUES ($1, 'dep grp') RETURNING id`,
[`DepGrp_${STAMP}`],
);
depGroupId = grp.rows[0].id;
// Create a custom permission to attach as an app restriction.
const perm = await pool.query(
`INSERT INTO permissions (name, description_en) VALUES ($1, 'dep perm') RETURNING id`,
[`dep_perm_${STAMP}`],
);
depPermissionId = perm.rows[0].id;
// Dependents on the user: a note and an order.
await pool.query(
`INSERT INTO notes (user_id, content) VALUES ($1, 'note for dep test')`,
[depUserId],
);
await pool.query(
`INSERT INTO service_orders (service_id, user_id, status, notes)
VALUES ($1, $2, 'pending', 'dep order')`,
[depServiceId, depUserId],
);
// App dependents: group link, permission restriction, an open record.
await pool.query(
`INSERT INTO group_apps (group_id, app_id) VALUES ($1, $2)`,
[depGroupId, depAppId],
);
await pool.query(
`INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2)`,
[depAppId, depPermissionId],
);
await pool.query(
`INSERT INTO app_opens (app_id, user_id) VALUES ($1, $2)`,
[depAppId, depUserId],
);
adminCookie = await loginAndGetCookie(adminUsername, TEST_PASSWORD);
});
after(async () => {
await pool.query(`DELETE FROM service_orders WHERE user_id = $1`, [depUserId]);
await pool.query(`DELETE FROM notes WHERE user_id = $1`, [depUserId]);
await pool.query(`DELETE FROM app_opens WHERE app_id = $1`, [depAppId]);
await pool.query(`DELETE FROM app_permissions WHERE app_id = $1`, [depAppId]);
await pool.query(`DELETE FROM group_apps WHERE app_id = $1`, [depAppId]);
await pool.query(`DELETE FROM permissions WHERE id = $1`, [depPermissionId]);
await pool.query(`DELETE FROM apps WHERE id = $1`, [depAppId]);
await pool.query(`DELETE FROM services WHERE id = $1`, [depServiceId]);
await pool.query(`DELETE FROM groups WHERE id = $1`, [depGroupId]);
await pool.query(`DELETE FROM user_roles WHERE user_id = ANY($1::int[])`, [[adminId, depUserId]]);
await pool.query(`DELETE FROM users WHERE id = ANY($1::int[])`, [[adminId, depUserId]]);
await pool.end();
});
test("GET /api/admin/apps exposes groupCount/restrictionCount/openCount", async () => {
const res = await fetch(`${API_BASE}/api/admin/apps`, {
headers: { Cookie: adminCookie },
});
assert.equal(res.status, 200);
const apps = await res.json();
const target = apps.find((a) => a.id === depAppId);
assert.ok(target, "expected dep app in admin apps list");
assert.equal(target.groupCount, 1, "groupCount should be 1");
assert.equal(target.restrictionCount, 1, "restrictionCount should be 1");
assert.equal(target.openCount, 1, "openCount should be 1");
});
test("GET /api/services exposes orderCount", async () => {
const res = await fetch(`${API_BASE}/api/services`, {
headers: { Cookie: adminCookie },
});
assert.equal(res.status, 200);
const services = await res.json();
const target = services.find((s) => s.id === depServiceId);
assert.ok(target, "expected dep service in services list");
assert.equal(target.orderCount, 1, "orderCount should be 1");
});
test("GET /api/users exposes noteCount/orderCount", async () => {
const res = await fetch(`${API_BASE}/api/users`, {
headers: { Cookie: adminCookie },
});
assert.equal(res.status, 200);
const users = await res.json();
const target = users.find((u) => u.id === depUserId);
assert.ok(target, "expected dep user in users list");
assert.equal(target.noteCount, 1, "noteCount should be 1");
assert.equal(target.orderCount, 1, "orderCount should be 1");
});
test("Apps list returns zero counts for entities without dependents", async () => {
const cleanApp = await pool.query(
`INSERT INTO apps (slug, name_ar, name_en, route, color, icon_name)
VALUES ($1, $2, $3, '/clean', '#000', 'Box') RETURNING id`,
[`clean-app-${STAMP}`, `CleanApp_${STAMP}`, `CleanApp_${STAMP}`],
);
const cleanAppId = cleanApp.rows[0].id;
try {
const res = await fetch(`${API_BASE}/api/admin/apps`, {
headers: { Cookie: adminCookie },
});
assert.equal(res.status, 200);
const apps = await res.json();
const target = apps.find((a) => a.id === cleanAppId);
assert.ok(target, "expected clean app in admin apps list");
assert.equal(target.groupCount, 0);
assert.equal(target.restrictionCount, 0);
assert.equal(target.openCount, 0);
} finally {
await pool.query(`DELETE FROM apps WHERE id = $1`, [cleanAppId]);
}
});
test("Services list returns zero orderCount for services without orders", async () => {
const cleanSvc = await pool.query(
`INSERT INTO services (name_ar, name_en, description_ar, description_en)
VALUES ($1, $2, 'd', 'd') RETURNING id`,
[`CleanSvc_${STAMP}`, `CleanSvc_${STAMP}`],
);
const cleanSvcId = cleanSvc.rows[0].id;
try {
const res = await fetch(`${API_BASE}/api/services`, {
headers: { Cookie: adminCookie },
});
assert.equal(res.status, 200);
const services = await res.json();
const target = services.find((s) => s.id === cleanSvcId);
assert.ok(target, "expected clean service in services list");
assert.equal(target.orderCount, 0);
} finally {
await pool.query(`DELETE FROM services WHERE id = $1`, [cleanSvcId]);
}
});
test("Users list returns zero counts for users without dependents", async () => {
const cleanUser = await pool.query(
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
VALUES ($1, $2, $3, 'Clean User', 'en', true) RETURNING id`,
[`clean_user_${STAMP}`, `clean_user_${STAMP}@example.com`, TEST_PASSWORD_HASH],
);
const cleanUserId = cleanUser.rows[0].id;
try {
const res = await fetch(`${API_BASE}/api/users`, {
headers: { Cookie: adminCookie },
});
assert.equal(res.status, 200);
const users = await res.json();
const target = users.find((u) => u.id === cleanUserId);
assert.ok(target, "expected clean user in users list");
assert.equal(target.noteCount, 0);
assert.equal(target.orderCount, 0);
} finally {
await pool.query(`DELETE FROM users WHERE id = $1`, [cleanUserId]);
}
});