1f23e65c0b
Replaces all user-facing instances of "TeaBoy" with "Tx" across the application, including titles, locale files, database settings, seed data, and API documentation. Also updates internal storage keys and session secrets to remove the old branding. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 07b19cb0-11b5-4be9-8932-ae4820eb73b8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/HxTkDPZ Replit-Helium-Checkpoint-Created: true
478 lines
19 KiB
JavaScript
478 lines
19 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 });
|
|
|
|
let adminId;
|
|
let adminUsername;
|
|
let regularId;
|
|
let regularUsername;
|
|
let adminCookie;
|
|
let regularCookie;
|
|
let createdGroupIds = [];
|
|
|
|
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 stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
adminUsername = `gr_admin_${stamp}`;
|
|
regularUsername = `gr_user_${stamp}`;
|
|
|
|
const a = await pool.query(
|
|
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
|
|
VALUES ($1, $2, $3, 'Group 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],
|
|
);
|
|
await pool.query(
|
|
`INSERT INTO user_groups (user_id, group_id)
|
|
SELECT $1, id FROM groups WHERE name IN ('Everyone', 'Admins')
|
|
ON CONFLICT DO NOTHING`,
|
|
[adminId],
|
|
);
|
|
|
|
const r = await pool.query(
|
|
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
|
|
VALUES ($1, $2, $3, 'Group User', 'en', true) RETURNING id`,
|
|
[regularUsername, `${regularUsername}@example.com`, TEST_PASSWORD_HASH],
|
|
);
|
|
regularId = r.rows[0].id;
|
|
await pool.query(
|
|
`INSERT INTO user_roles (user_id, role_id) SELECT $1, id FROM roles WHERE name = 'user'`,
|
|
[regularId],
|
|
);
|
|
await pool.query(
|
|
`INSERT INTO user_groups (user_id, group_id)
|
|
SELECT $1, id FROM groups WHERE name = 'Everyone'
|
|
ON CONFLICT DO NOTHING`,
|
|
[regularId],
|
|
);
|
|
|
|
adminCookie = await loginAndGetCookie(adminUsername, TEST_PASSWORD);
|
|
regularCookie = await loginAndGetCookie(regularUsername, TEST_PASSWORD);
|
|
});
|
|
|
|
after(async () => {
|
|
if (createdGroupIds.length) {
|
|
await pool.query(
|
|
`DELETE FROM user_groups WHERE group_id = ANY($1::int[])`,
|
|
[createdGroupIds],
|
|
);
|
|
await pool.query(
|
|
`DELETE FROM group_apps WHERE group_id = ANY($1::int[])`,
|
|
[createdGroupIds],
|
|
);
|
|
await pool.query(
|
|
`DELETE FROM group_roles WHERE group_id = ANY($1::int[])`,
|
|
[createdGroupIds],
|
|
);
|
|
await pool.query(`DELETE FROM groups WHERE id = ANY($1::int[])`, [
|
|
createdGroupIds,
|
|
]);
|
|
}
|
|
for (const uid of [adminId, regularId]) {
|
|
if (uid !== undefined) {
|
|
await pool.query(`DELETE FROM user_groups WHERE user_id = $1`, [uid]);
|
|
await pool.query(`DELETE FROM user_roles WHERE user_id = $1`, [uid]);
|
|
await pool.query(`DELETE FROM users WHERE id = $1`, [uid]);
|
|
}
|
|
}
|
|
await pool.end();
|
|
});
|
|
|
|
test("GET /api/groups requires admin (regular user gets 403)", async () => {
|
|
const res = await fetch(`${API_BASE}/api/groups`, {
|
|
headers: { Cookie: regularCookie },
|
|
});
|
|
assert.equal(res.status, 403);
|
|
});
|
|
|
|
test("admin lists default seed groups including Admins, Tx, Everyone", async () => {
|
|
const res = await fetch(`${API_BASE}/api/groups`, {
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
assert.equal(res.status, 200);
|
|
const list = await res.json();
|
|
const names = new Set(list.map((g) => g.name));
|
|
assert.ok(names.has("Admins"));
|
|
assert.ok(names.has("Tx"));
|
|
assert.ok(names.has("Everyone"));
|
|
});
|
|
|
|
test("admin creates a group, sees it, and assigns a member", async () => {
|
|
const name = `TestGrp_${Date.now().toString(36)}`;
|
|
const create = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({
|
|
name,
|
|
descriptionEn: "Test group",
|
|
userIds: [regularId],
|
|
}),
|
|
});
|
|
assert.equal(create.status, 201);
|
|
const created = await create.json();
|
|
createdGroupIds.push(created.id);
|
|
assert.equal(created.name, name);
|
|
|
|
// Detail should include regularId
|
|
const detailRes = await fetch(`${API_BASE}/api/groups/${created.id}`, {
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
assert.equal(detailRes.status, 200);
|
|
const detail = await detailRes.json();
|
|
assert.ok(detail.userIds.includes(regularId));
|
|
});
|
|
|
|
test("creating a group with invalid userIds returns 400 and does not create rows", async () => {
|
|
const before = await pool.query(`SELECT COUNT(*)::int AS c FROM groups`);
|
|
const maxRow = await pool.query(`SELECT COALESCE(MAX(id), 0) AS m FROM users`);
|
|
const badId = Number(maxRow.rows[0].m) + 100000;
|
|
const res = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ name: `Bad_${Date.now()}`, userIds: [badId] }),
|
|
});
|
|
assert.equal(res.status, 400);
|
|
const after = await pool.query(`SELECT COUNT(*)::int AS c FROM groups`);
|
|
assert.equal(after.rows[0].c, before.rows[0].c, "no group should be created");
|
|
});
|
|
|
|
test("admin role inherited via group_roles grants access to admin endpoints", async () => {
|
|
// Create a group, attach the 'admin' role to it, add a fresh non-admin user
|
|
const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
const inheritedUsername = `gr_inherit_${stamp}`;
|
|
const u = await pool.query(
|
|
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
|
|
VALUES ($1, $2, $3, 'Inherited', 'en', true) RETURNING id`,
|
|
[inheritedUsername, `${inheritedUsername}@example.com`, TEST_PASSWORD_HASH],
|
|
);
|
|
const inheritedId = u.rows[0].id;
|
|
// No direct admin role; rely on group_roles
|
|
await pool.query(
|
|
`INSERT INTO user_roles (user_id, role_id) SELECT $1, id FROM roles WHERE name = 'user'`,
|
|
[inheritedId],
|
|
);
|
|
const grpName = `AdminInheritGrp_${stamp}`;
|
|
const g = await pool.query(
|
|
`INSERT INTO groups (name) VALUES ($1) RETURNING id`,
|
|
[grpName],
|
|
);
|
|
const grpId = g.rows[0].id;
|
|
createdGroupIds.push(grpId);
|
|
await pool.query(
|
|
`INSERT INTO group_roles (group_id, role_id) SELECT $1, id FROM roles WHERE name = 'admin'`,
|
|
[grpId],
|
|
);
|
|
await pool.query(
|
|
`INSERT INTO user_groups (user_id, group_id) VALUES ($1, $2)`,
|
|
[inheritedId, grpId],
|
|
);
|
|
|
|
const cookie = await loginAndGetCookie(inheritedUsername, TEST_PASSWORD);
|
|
const res = await fetch(`${API_BASE}/api/groups`, { headers: { Cookie: cookie } });
|
|
assert.equal(res.status, 200, "group-role-only user must be granted admin access");
|
|
|
|
// Cleanup user
|
|
await pool.query(`DELETE FROM user_groups WHERE user_id = $1`, [inheritedId]);
|
|
await pool.query(`DELETE FROM user_roles WHERE user_id = $1`, [inheritedId]);
|
|
await pool.query(`DELETE FROM users WHERE id = $1`, [inheritedId]);
|
|
});
|
|
|
|
test("deactivated admin session is denied on admin endpoints", async () => {
|
|
const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
const u = `deact_admin_${stamp}`;
|
|
const r = await pool.query(
|
|
`INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
|
|
VALUES ($1, $2, $3, 'Deact', 'en', true) RETURNING id`,
|
|
[u, `${u}@example.com`, TEST_PASSWORD_HASH],
|
|
);
|
|
const uid = r.rows[0].id;
|
|
await pool.query(
|
|
`INSERT INTO user_roles (user_id, role_id) SELECT $1, id FROM roles WHERE name = 'admin'`,
|
|
[uid],
|
|
);
|
|
const cookie = await loginAndGetCookie(u, TEST_PASSWORD);
|
|
const ok = await fetch(`${API_BASE}/api/groups`, { headers: { Cookie: cookie } });
|
|
assert.equal(ok.status, 200);
|
|
// Deactivate after login
|
|
await pool.query(`UPDATE users SET is_active = false WHERE id = $1`, [uid]);
|
|
const denied = await fetch(`${API_BASE}/api/groups`, { headers: { Cookie: cookie } });
|
|
assert.equal(denied.status, 401, "inactive user must be rejected");
|
|
await pool.query(`DELETE FROM user_roles WHERE user_id = $1`, [uid]);
|
|
await pool.query(`DELETE FROM users WHERE id = $1`, [uid]);
|
|
});
|
|
|
|
test("group create rolls back atomically when assignment fails", async () => {
|
|
// Validation rejects bad userIds before any insert (validateAssignmentIds runs first),
|
|
// so no `groups` row should be created and we should get a clean 400.
|
|
const before = await pool.query(`SELECT COUNT(*)::int AS c FROM groups`);
|
|
const maxRow = await pool.query(`SELECT COALESCE(MAX(id), 0) AS m FROM apps`);
|
|
const badAppId = Number(maxRow.rows[0].m) + 100000;
|
|
const name = `RollbackGrp_${Date.now().toString(36)}`;
|
|
const res = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ name, appIds: [badAppId] }),
|
|
});
|
|
assert.equal(res.status, 400);
|
|
const after = await pool.query(`SELECT COUNT(*)::int AS c FROM groups`);
|
|
assert.equal(after.rows[0].c, before.rows[0].c, "no group row should leak");
|
|
const leaked = await pool.query(`SELECT id FROM groups WHERE name = $1`, [name]);
|
|
assert.equal(leaked.rowCount, 0);
|
|
});
|
|
|
|
test("PATCH /api/groups/:id updates fields and replaces assignments transactionally", async () => {
|
|
const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
const create = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ name: `UpdGrp_${stamp}`, descriptionEn: "before" }),
|
|
});
|
|
assert.equal(create.status, 201);
|
|
const created = await create.json();
|
|
createdGroupIds.push(created.id);
|
|
|
|
const newName = `UpdGrpRenamed_${stamp}`;
|
|
const patch = await fetch(`${API_BASE}/api/groups/${created.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({
|
|
name: newName,
|
|
descriptionEn: "after",
|
|
userIds: [adminId, regularId],
|
|
}),
|
|
});
|
|
assert.equal(patch.status, 200);
|
|
const updated = await patch.json();
|
|
assert.equal(updated.name, newName);
|
|
assert.equal(updated.descriptionEn, "after");
|
|
assert.deepEqual(new Set(updated.userIds), new Set([adminId, regularId]));
|
|
|
|
// Replace assignment with just the regular user; admin should be removed.
|
|
const patch2 = await fetch(`${API_BASE}/api/groups/${created.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ userIds: [regularId] }),
|
|
});
|
|
assert.equal(patch2.status, 200);
|
|
const detail = await (
|
|
await fetch(`${API_BASE}/api/groups/${created.id}`, { headers: { Cookie: adminCookie } })
|
|
).json();
|
|
assert.deepEqual(detail.userIds, [regularId]);
|
|
|
|
// Bad userId on update — must 400, must not partially apply (membership unchanged).
|
|
const maxRow = await pool.query(`SELECT COALESCE(MAX(id), 0) AS m FROM users`);
|
|
const badId = Number(maxRow.rows[0].m) + 100000;
|
|
const patchBad = await fetch(`${API_BASE}/api/groups/${created.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ userIds: [adminId, badId] }),
|
|
});
|
|
assert.equal(patchBad.status, 400);
|
|
const detail2 = await (
|
|
await fetch(`${API_BASE}/api/groups/${created.id}`, { headers: { Cookie: adminCookie } })
|
|
).json();
|
|
assert.deepEqual(detail2.userIds, [regularId], "membership must be unchanged after rejected update");
|
|
});
|
|
|
|
test("creating/updating a group with invalid roleIds (or appIds) returns 400", async () => {
|
|
const maxRoleRow = await pool.query(`SELECT COALESCE(MAX(id), 0) AS m FROM roles`);
|
|
const maxAppRow = await pool.query(`SELECT COALESCE(MAX(id), 0) AS m FROM apps`);
|
|
const badRoleId = Number(maxRoleRow.rows[0].m) + 100000;
|
|
const badAppId = Number(maxAppRow.rows[0].m) + 100000;
|
|
|
|
// CREATE with bad roleId — must 400 and create no row.
|
|
const beforeCount = await pool.query(`SELECT COUNT(*)::int AS c FROM groups`);
|
|
const createRes = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ name: `BadRole_${Date.now()}`, roleIds: [badRoleId] }),
|
|
});
|
|
assert.equal(createRes.status, 400);
|
|
const afterCount = await pool.query(`SELECT COUNT(*)::int AS c FROM groups`);
|
|
assert.equal(afterCount.rows[0].c, beforeCount.rows[0].c);
|
|
|
|
// Build a clean group, then PATCH with bad roleIds and bad appIds.
|
|
const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
const seedRes = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ name: `BadRolePatch_${stamp}` }),
|
|
});
|
|
assert.equal(seedRes.status, 201);
|
|
const seed = await seedRes.json();
|
|
createdGroupIds.push(seed.id);
|
|
|
|
const patchRoleRes = await fetch(`${API_BASE}/api/groups/${seed.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ roleIds: [badRoleId] }),
|
|
});
|
|
assert.equal(patchRoleRes.status, 400);
|
|
|
|
const patchAppRes = await fetch(`${API_BASE}/api/groups/${seed.id}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ appIds: [badAppId] }),
|
|
});
|
|
assert.equal(patchAppRes.status, 400);
|
|
|
|
// Confirm no role/app links leaked onto the seed group from rejected updates.
|
|
const detail = await (
|
|
await fetch(`${API_BASE}/api/groups/${seed.id}`, { headers: { Cookie: adminCookie } })
|
|
).json();
|
|
assert.deepEqual(detail.roleIds, []);
|
|
assert.deepEqual(detail.appIds, []);
|
|
});
|
|
|
|
test("DELETE /api/groups/:id removes a custom group; system groups are protected", async () => {
|
|
const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
const create = await fetch(`${API_BASE}/api/groups`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Cookie: adminCookie },
|
|
body: JSON.stringify({ name: `DelGrp_${stamp}` }),
|
|
});
|
|
assert.equal(create.status, 201);
|
|
const created = await create.json();
|
|
|
|
const del = await fetch(`${API_BASE}/api/groups/${created.id}`, {
|
|
method: "DELETE",
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
assert.equal(del.status, 204);
|
|
const after = await pool.query(`SELECT id FROM groups WHERE id = $1`, [created.id]);
|
|
assert.equal(after.rowCount, 0, "group row should be gone");
|
|
|
|
// System-group guard: trying to delete Everyone must fail and leave row intact.
|
|
const sysRow = await pool.query(
|
|
`SELECT id FROM groups WHERE name = 'Everyone' AND is_system = 1`,
|
|
);
|
|
assert.ok(sysRow.rowCount > 0, "Everyone system group must exist");
|
|
const everyoneId = sysRow.rows[0].id;
|
|
const denied = await fetch(`${API_BASE}/api/groups/${everyoneId}`, {
|
|
method: "DELETE",
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
assert.equal(denied.status, 400);
|
|
const stillThere = await pool.query(`SELECT id FROM groups WHERE id = $1`, [everyoneId]);
|
|
assert.equal(stillThere.rowCount, 1, "system group must not be deleted");
|
|
});
|
|
|
|
test("/auth/register auto-assigns the new user to the Everyone system group", async () => {
|
|
// Ensure registration is open for the duration of this test, then restore.
|
|
const prior = await pool.query(
|
|
`SELECT registration_open FROM app_settings WHERE id = 1`,
|
|
);
|
|
const priorOpen = prior.rows[0]?.registration_open;
|
|
await pool.query(
|
|
`INSERT INTO app_settings (id, registration_open) VALUES (1, true)
|
|
ON CONFLICT (id) DO UPDATE SET registration_open = true`,
|
|
);
|
|
|
|
const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
const username = `reg_everyone_${stamp}`;
|
|
let newUserId;
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/auth/register`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
username,
|
|
email: `${username}@example.com`,
|
|
password: TEST_PASSWORD,
|
|
displayNameEn: "Reg Everyone",
|
|
preferredLanguage: "en",
|
|
}),
|
|
});
|
|
assert.equal(res.status, 201, `register expected 201, got ${res.status}`);
|
|
const body = await res.json();
|
|
assert.ok(Array.isArray(body.groups), "auth user payload must include groups[]");
|
|
assert.ok(
|
|
body.groups.some((g) => g.name === "Everyone"),
|
|
"register response must list Everyone in groups",
|
|
);
|
|
|
|
// Confirm at the DB level that the membership was actually written.
|
|
const userRow = await pool.query(`SELECT id FROM users WHERE username = $1`, [username]);
|
|
newUserId = userRow.rows[0]?.id;
|
|
const linkRow = await pool.query(
|
|
`SELECT 1 FROM user_groups ug
|
|
JOIN groups g ON g.id = ug.group_id
|
|
WHERE ug.user_id = $1 AND g.name = 'Everyone'`,
|
|
[newUserId],
|
|
);
|
|
assert.equal(linkRow.rowCount, 1, "user_groups must link new user to Everyone");
|
|
} finally {
|
|
if (newUserId !== undefined) {
|
|
await pool.query(`DELETE FROM user_groups WHERE user_id = $1`, [newUserId]);
|
|
await pool.query(`DELETE FROM user_roles WHERE user_id = $1`, [newUserId]);
|
|
await pool.query(`DELETE FROM users WHERE id = $1`, [newUserId]);
|
|
}
|
|
if (priorOpen !== undefined) {
|
|
await pool.query(
|
|
`UPDATE app_settings SET registration_open = $1 WHERE id = 1`,
|
|
[priorOpen],
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("GET /api/users supports q, groupId, and status filters", async () => {
|
|
// q filter on partial username
|
|
const qRes = await fetch(
|
|
`${API_BASE}/api/users?q=${encodeURIComponent(regularUsername.slice(0, 8))}`,
|
|
{ headers: { Cookie: adminCookie } },
|
|
);
|
|
assert.equal(qRes.status, 200);
|
|
const qList = await qRes.json();
|
|
assert.ok(qList.some((u) => u.id === regularId));
|
|
|
|
// status=active includes our active user
|
|
const statusRes = await fetch(`${API_BASE}/api/users?status=active`, {
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
const statusList = await statusRes.json();
|
|
assert.ok(statusList.every((u) => u.isActive === true));
|
|
|
|
// groupId filter — use Everyone (every user belongs)
|
|
const groupsRes = await fetch(`${API_BASE}/api/groups`, {
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
const groups = await groupsRes.json();
|
|
const everyone = groups.find((g) => g.name === "Everyone");
|
|
assert.ok(everyone, "Everyone group must exist");
|
|
const grpRes = await fetch(`${API_BASE}/api/users?groupId=${everyone.id}`, {
|
|
headers: { Cookie: adminCookie },
|
|
});
|
|
const grpList = await grpRes.json();
|
|
assert.ok(grpList.some((u) => u.id === adminId));
|
|
});
|