All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import {
|
|
createUser,
|
|
db,
|
|
del,
|
|
get,
|
|
insertDump,
|
|
patch,
|
|
test,
|
|
} from "./helpers.ts";
|
|
import { assertEquals } from "@std/assert";
|
|
|
|
test("public profile: by username or id, without private fields", async () => {
|
|
const user = await createUser();
|
|
for (
|
|
const path of [`/api/users/${user.username}`, `/api/users/by-id/${user.id}`]
|
|
) {
|
|
const res = await get(path);
|
|
assertEquals(res.status, 200, path);
|
|
assertEquals(res.body.data.username, user.username);
|
|
assertEquals(res.body.data.email, undefined);
|
|
assertEquals(res.body.data.passwordHash, undefined);
|
|
}
|
|
assertEquals((await get(`/api/users/no_one_here`)).status, 404);
|
|
const me = await get("/api/users/me", { auth: user.auth });
|
|
assertEquals(me.body.data.email, user.email);
|
|
assertEquals(me.body.data.passwordHash, undefined);
|
|
});
|
|
|
|
test("PATCH /api/users/me: description and email only", async () => {
|
|
const user = await createUser();
|
|
const taken = await createUser();
|
|
const res = await patch("/api/users/me", {
|
|
auth: user.auth,
|
|
json: { description: "about me", email: "new@example.test" },
|
|
});
|
|
assertEquals(res.status, 200);
|
|
assertEquals(res.body.data.description, "about me");
|
|
assertEquals(res.body.data.email, undefined);
|
|
assertEquals(
|
|
(await get("/api/users/me", { auth: user.auth })).body.data.email,
|
|
"new@example.test",
|
|
);
|
|
// A role, username or password in the body is ignored, not applied.
|
|
await patch("/api/users/me", {
|
|
auth: user.auth,
|
|
json: { role: "admin", username: "hijack", password: "whatever-long" },
|
|
});
|
|
const row = db.prepare(`SELECT role, username FROM users WHERE id = ?;`).get(
|
|
user.id,
|
|
) as {
|
|
role: string;
|
|
username: string;
|
|
};
|
|
assertEquals(row, { role: "user", username: user.username });
|
|
|
|
assertEquals(
|
|
(await patch("/api/users/me", {
|
|
auth: user.auth,
|
|
json: { email: taken.email },
|
|
})).status,
|
|
409,
|
|
);
|
|
assertEquals(
|
|
(await patch("/api/users/me", { auth: user.auth, json: { email: "bad" } }))
|
|
.status,
|
|
400,
|
|
);
|
|
assertEquals(
|
|
(await patch("/api/users/me", {
|
|
auth: user.auth,
|
|
json: { description: "x".repeat(2001) },
|
|
})).status,
|
|
400,
|
|
);
|
|
});
|
|
|
|
test("PATCH /api/users/:id/role: admins only, never their own", async () => {
|
|
const admin = await createUser({ role: "admin" });
|
|
const mod = await createUser({ role: "moderator" });
|
|
const user = await createUser();
|
|
assertEquals(
|
|
(await patch(`/api/users/${user.id}/role`, {
|
|
auth: mod.auth,
|
|
json: { role: "admin" },
|
|
}))
|
|
.status,
|
|
403,
|
|
);
|
|
assertEquals(
|
|
(await patch(`/api/users/${admin.id}/role`, {
|
|
auth: admin.auth,
|
|
json: { role: "user" },
|
|
}))
|
|
.status,
|
|
400,
|
|
);
|
|
assertEquals(
|
|
(await patch(`/api/users/${user.id}/role`, {
|
|
auth: admin.auth,
|
|
json: { role: "god" },
|
|
}))
|
|
.status,
|
|
400,
|
|
);
|
|
const res = await patch(`/api/users/${user.id}/role`, {
|
|
auth: admin.auth,
|
|
json: { role: "moderator" },
|
|
});
|
|
assertEquals(res.status, 200);
|
|
assertEquals(res.body.data.role, "moderator");
|
|
assertEquals(res.body.data.email, undefined);
|
|
// The promoted user's existing token now carries moderator powers.
|
|
const { id: dumpId } = insertDump({ userId: admin.id });
|
|
assertEquals(
|
|
(await del(`/api/dumps/${dumpId}`, { auth: user.auth })).status,
|
|
200,
|
|
);
|
|
assertEquals(
|
|
(await patch(`/api/users/${crypto.randomUUID()}/role`, {
|
|
auth: admin.auth,
|
|
json: { role: "user" },
|
|
})).status,
|
|
404,
|
|
);
|
|
});
|
|
|
|
test("GET /api/users/search: prefix match for mentions, capped at 8", async () => {
|
|
for (let i = 0; i < 10; i++) await createUser({ username: `zed_${i}` });
|
|
await createUser({ username: "other_zed" });
|
|
const res = await get("/api/users/search?q=zed");
|
|
assertEquals(res.body.data.length, 8);
|
|
assertEquals(res.body.data[0], {
|
|
id: res.body.data[0].id,
|
|
username: "zed_0",
|
|
avatarMime: null,
|
|
});
|
|
assertEquals((await get("/api/users/search?q=")).body.data, []);
|
|
});
|
|
|
|
test("GET /api/users/:username/invitees: the invite tree below a user", async () => {
|
|
const root = await createUser();
|
|
const child = await createUser({
|
|
invitedBy: root.id,
|
|
createdAt: "2024-01-01T00:00:00Z",
|
|
});
|
|
const grandchild = await createUser({
|
|
invitedBy: child.id,
|
|
createdAt: "2024-01-02T00:00:00Z",
|
|
});
|
|
const tree = (await get(`/api/users/${root.username}/invitees`)).body.data;
|
|
assertEquals(
|
|
tree.map((
|
|
n: { username: string; invitedById: string },
|
|
) => [n.username, n.invitedById]),
|
|
[[child.username, root.id], [grandchild.username, child.id]],
|
|
);
|
|
const profile = await get(`/api/users/${child.username}`);
|
|
assertEquals(profile.body.data.invitedByUsername, root.username);
|
|
});
|