Deleting products: by their creators, and removal by moderation
CI / test (push) Successful in 1m4s

A creator deletes a product: it's no longer listed and nobody new can get it, while people who have it
keep it (its page, files and library entries stay, and rooms keep showing it), as old revisions stay
available. Moderation removes one everywhere, with a reason: its page and files answer 410, and it
leaves libraries and rooms. Both are on the product's page and in the audit log.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 08:10:24 -07:00
1 parent 07bbc11565
commit 97ce2be569
12 files changed
+2435 -14

No files matched your search

+35
View File
@@ -278,3 +278,38 @@ test("an IMVU account's avatar picture is kept only from IMVU's own hosts", () =
expect(user("https://evil.example/p.png").picture).toBeNull();
expect(user(undefined).picture).toBeNull();
});
test("creators delete products (those who have them keep them); moderation removes them everywhere", async () => {
const { admin, catalogID, auth } = shared;
const maker = await person("deleter", { id: 511, ageVerified: true });
const owner = await person("keeper", { id: 512, ageVerified: true });
const late = await person("latecomer", { id: 513, ageVerified: true });
const make = async (name: string) => {
const token = (await call(maker, "POST", "/api/v1/products/upload", { catalog: catalogID })).json.token;
const r = await call(undefined, "POST", "/api/v1/catalog/revisions", { uploadToken: token, files: [file(`${name}.xml`, 1)], rating: "general", parent: 500, name, notes: "" }, auth);
return r.json.product as string;
};
const listed = async () => (await call(undefined, "GET", "/api/v1/products?q=gone")).json.products.map((p: any) => p.name);
const id = await make("Gone Hat");
expect((await call(owner, "PUT", `/api/v1/library/${id}`)).status).toBe(200);
// Only its creator deletes it; then it's not listed and nobody new adds it, but its page, the library entry and
// the files stay for those who have it.
expect((await call(owner, "DELETE", `/api/v1/products/${id}`)).status).toBe(403);
expect((await call(maker, "DELETE", `/api/v1/products/${id}`)).status).toBe(204);
expect(await listed()).toEqual([]);
expect((await call(undefined, "GET", `/api/v1/products/${id}`)).json.product.deleted).toBe(true);
expect((await call(late, "PUT", `/api/v1/library/${id}`)).json.error).toBe("deleted");
expect((await call(owner, "GET", "/api/v1/library")).json.library.map((l: any) => l.product.id)).toEqual([id]);
expect((await call(owner, "GET", `/api/v1/products/${id}/files`)).status).toBe(200);
expect((await call(maker, "PATCH", `/api/v1/products/${id}`, { name: "Back" })).json.error).toBe("deleted");
// Moderation removes one: no page, no files, out of libraries.
const bad = await make("Gone Shoes");
expect((await call(owner, "PUT", `/api/v1/library/${bad}`)).status).toBe(200);
expect((await call(maker, "POST", `/api/v1/products/${bad}/remove`, { reason: "Stolen" })).status).toBe(403);
expect((await call(admin, "POST", `/api/v1/products/${bad}/remove`, { reason: "Stolen" })).status).toBe(200);
expect((await call(undefined, "GET", `/api/v1/products/${bad}`)).status).toBe(410);
expect((await call(owner, "GET", `/api/v1/products/${bad}/files`)).status).toBe(410);
expect((await call(owner, "GET", "/api/v1/library")).json.library.map((l: any) => l.product.id)).toEqual([id]);
});