mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-22 06:32:27 -05:00
518 lines
23 KiB
TypeScript
518 lines
23 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
import request from "supertest";
|
|
|
|
// No pacing here. config.ts reads the delays on load, so it's hoisted.
|
|
vi.hoisted(() => {
|
|
process.env.IMPORT_MAKERWORLD_CALL_DELAY_MS = "0";
|
|
process.env.COLLECTION_SYNC_ITEM_DELAY_MS = "0";
|
|
});
|
|
|
|
import { createApp } from "../src/app";
|
|
import { prisma } from "../src/db";
|
|
import { setUserMakerworldCookie } from "../src/services/makerworldCookieService";
|
|
import {
|
|
PRINTABLES_FORGOTTEN,
|
|
pushedPrintablesModels,
|
|
WAITING_FOR_GRAB,
|
|
} from "../src/services/providerListingsService";
|
|
import { syncCollection } from "../src/services/collectionSyncRunner";
|
|
import { getThingiverseAccessToken, setThingiverseAccessToken } from "../src/services/settingsService";
|
|
|
|
const app = createApp();
|
|
const stamp = Date.now();
|
|
let token: string;
|
|
let userId: string;
|
|
const auth = () => ({ Authorization: `Bearer ${token}` });
|
|
const originalFetch = global.fetch;
|
|
|
|
// Ids unique to this run, so reruns against the same database don't collide.
|
|
let nextId = (stamp % 1_000_000_000) * 10;
|
|
const newId = () => String(nextId++);
|
|
|
|
const THINGIVERSE_USER = `Liker${stamp}`;
|
|
let previousThingiverseToken: string | null = null;
|
|
// A real Printables user, but not this Thingport user's: only typed into the app.
|
|
const OTHER_PRINTABLES_USER = { id: "777", handle: `Other_${stamp}`, publicUsername: "Other" };
|
|
const PRINTABLES_USER = { id: "3560603", handle: `Tester_${stamp}`, publicUsername: "Tester" };
|
|
const printablesCollections = [
|
|
{ id: newId(), name: "Practical finds", printsCount: 23 },
|
|
{ id: newId(), name: "Cool toys", printsCount: 8 },
|
|
];
|
|
const makerworldCollections = [
|
|
{ id: newId(), title: "To print", status: 2, designCnt: 3 },
|
|
{ id: newId(), title: "Secret", status: 1, designCnt: 1 },
|
|
];
|
|
const makerworldDesigns: Record<string, string[]> = {
|
|
[makerworldCollections[0].id]: [newId(), newId(), newId()],
|
|
};
|
|
|
|
function json(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
}
|
|
|
|
function printablesCard(c: { id: string; name: string; printsCount: number }) {
|
|
return {
|
|
...c,
|
|
private: false,
|
|
user: { handle: PRINTABLES_USER.handle },
|
|
thumbnails11: [{ image: { filePath: `media/${c.id}.jpg` } }],
|
|
};
|
|
}
|
|
|
|
function printablesGraphql(query: string, variables: Record<string, unknown>): Response {
|
|
if (query.includes("user(id:")) {
|
|
if (variables.id === `@${OTHER_PRINTABLES_USER.handle}`) return json({ data: { user: OTHER_PRINTABLES_USER } });
|
|
const ok = variables.id === `@${PRINTABLES_USER.handle}` || variables.id === PRINTABLES_USER.id;
|
|
return json({ data: { user: ok ? PRINTABLES_USER : null } });
|
|
}
|
|
if (query.includes("userCollections"))
|
|
return json({ data: { collections: printablesCollections.map(printablesCard) } });
|
|
if (query.includes("moreCollectionModels"))
|
|
return json({ data: { moreCollectionModels: { items: [], cursor: "" } } });
|
|
if (query.includes("collection(id:")) {
|
|
const found = printablesCollections.find((c) => c.id === variables.id);
|
|
return json({ data: { collection: found ? printablesCard(found) : null } });
|
|
}
|
|
return json({ errors: [{ message: "unexpected query" }] });
|
|
}
|
|
|
|
function makerworldCard(c: (typeof makerworldCollections)[number]) {
|
|
return { ...c, designCover: [`https://makerworld.bblmw.com/${c.id}.jpg`], creator: { handle: "Tautve" } };
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
const res = await request(app)
|
|
.post("/api/register")
|
|
.send({ displayName: "Browse", email: `browse-${stamp}@example.com`, password: "password123" });
|
|
token = res.body.token;
|
|
userId = res.body.user.id;
|
|
|
|
global.fetch = vi.fn<(input: RequestInfo | URL, init?: RequestInit) => Promise<Response>>(async (input, init) => {
|
|
const url = String(input);
|
|
if (url.startsWith("https://api.printables.com/graphql")) {
|
|
const body = JSON.parse(String(init?.body ?? "{}")) as { query: string; variables: Record<string, unknown> };
|
|
return printablesGraphql(body.query, body.variables);
|
|
}
|
|
// Thingiverse's API as it answers for a user with two likes and one collection.
|
|
const thingiverse = url.match(/^https:\/\/api\.thingiverse\.com(\/[^?]*)/);
|
|
if (thingiverse) {
|
|
if (url.includes("access_token=rejected-token")) return json({}, 401);
|
|
if (url.includes("access_token=challenged-token")) {
|
|
return new Response("<title>Just a moment...</title>", {
|
|
status: 403,
|
|
headers: { "cf-mitigated": "challenge" },
|
|
});
|
|
}
|
|
const path = thingiverse[1];
|
|
if (path === `/users/${THINGIVERSE_USER}`) return json({ name: THINGIVERSE_USER, like_count: 2 });
|
|
if (path === `/users/${THINGIVERSE_USER}/likes`) {
|
|
return json([
|
|
{ id: 11, name: "Hook", thumbnail: "https://cdn.thingiverse.com/hook.jpg" },
|
|
{ id: 12, name: "Clip", thumbnail: "https://cdn.thingiverse.com/clip.jpg" },
|
|
]);
|
|
}
|
|
if (path === `/users/${THINGIVERSE_USER}/collections`) {
|
|
return json([{ id: 77, name: "Bits", count: 3, creator: { name: THINGIVERSE_USER } }]);
|
|
}
|
|
return json({ error: "not found" }, 404);
|
|
}
|
|
if (url.startsWith("https://api.bambulab.com/v1/design-service/my/favorites/listlite")) {
|
|
return json({ total: makerworldCollections.length, hits: makerworldCollections.map(makerworldCard) });
|
|
}
|
|
const card = url.match(/^https:\/\/api\.bambulab\.com\/v1\/design-service\/favorites\/(\d+)$/);
|
|
if (card) {
|
|
const found = makerworldCollections.find((c) => c.id === card[1]);
|
|
return found ? json(makerworldCard(found)) : json({ error: "not found" }, 404);
|
|
}
|
|
const head = url.match(/^https:\/\/makerworld\.com\/api\/v1\/design-service\/favorites\/(\d+)$/);
|
|
if (head) return makerworldDesigns[head[1]] ? json({ id: Number(head[1]), title: "To print" }) : json({}, 404);
|
|
const listing = url.match(/^https:\/\/makerworld\.com\/api\/v1\/design-service\/favorites\/(\d+)\/designs/);
|
|
if (listing) {
|
|
const ids = makerworldDesigns[listing[1]] ?? [];
|
|
return json({ total: ids.length, hits: ids.map((id) => ({ id: Number(id), title: `Model ${id}` })) });
|
|
}
|
|
return new Response("not found", { status: 404 });
|
|
}) as unknown as typeof fetch;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
global.fetch = originalFetch;
|
|
await setThingiverseAccessToken(previousThingiverseToken);
|
|
});
|
|
|
|
async function newCollection(name: string): Promise<string> {
|
|
const res = await request(app)
|
|
.post("/api/collections")
|
|
.set(auth())
|
|
.send({ name: `${name} ${stamp}` });
|
|
return res.body.id as string;
|
|
}
|
|
|
|
describe("browsing a provider's collections to sync with", () => {
|
|
it("says which providers can be browsed, and who the user is on them", async () => {
|
|
const res = await request(app).get("/api/provider-collections/accounts").set(auth());
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.makerworld).toEqual({ connected: false, account: null });
|
|
expect(res.body.printables).toEqual({ connected: true, account: null });
|
|
});
|
|
|
|
it("remembers a Printables user from their profile link, then lists their collections", async () => {
|
|
const before = await request(app).get("/api/provider-collections").query({ provider: "printables" }).set(auth());
|
|
expect(before.status).toBe(409);
|
|
|
|
const unknown = await request(app)
|
|
.put("/api/provider-collections/accounts/printables")
|
|
.set(auth())
|
|
.send({ value: "@nobody" });
|
|
expect(unknown.status).toBe(404);
|
|
|
|
const saved = await request(app)
|
|
.put("/api/provider-collections/accounts/printables")
|
|
.set(auth())
|
|
.send({ value: `https://www.printables.com/@${PRINTABLES_USER.handle}/collections` });
|
|
expect(saved.status).toBe(200);
|
|
expect(saved.body.printables.account).toBe(PRINTABLES_USER.handle);
|
|
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "printables" }).set(auth());
|
|
expect(list.status).toBe(200);
|
|
expect(list.body).toHaveLength(2);
|
|
expect(list.body[0]).toMatchObject({
|
|
id: printablesCollections[0].id,
|
|
name: "Practical finds",
|
|
model_count: 23,
|
|
url: `https://www.printables.com/@${PRINTABLES_USER.handle}/collections/${printablesCollections[0].id}`,
|
|
thumbnails: [`https://media.printables.com/media/${printablesCollections[0].id}.jpg`],
|
|
synced: null,
|
|
});
|
|
});
|
|
|
|
it("lists the user's own MakerWorld collections through their cookie, private ones included", async () => {
|
|
await setUserMakerworldCookie(userId, "token=test-bearer");
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "makerworld" }).set(auth());
|
|
expect(list.status).toBe(200);
|
|
expect(list.body.map((c: { name: string; private: boolean }) => [c.name, c.private])).toEqual([
|
|
["To print", false],
|
|
["Secret", true],
|
|
]);
|
|
});
|
|
|
|
it("looks up a pasted collection link as the same card", async () => {
|
|
const id = makerworldCollections[0].id;
|
|
const res = await request(app)
|
|
.get("/api/provider-collections/lookup")
|
|
.query({ url: `https://makerworld.com/en/collections/${id}-to-print` })
|
|
.set(auth());
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toMatchObject({ id, name: "To print", model_count: 3, owner: "Tautve", synced: null });
|
|
|
|
const notACollection = await request(app)
|
|
.get("/api/provider-collections/lookup")
|
|
.query({ url: "https://makerworld.com/en/models/123" })
|
|
.set(auth());
|
|
expect(notACollection.status).toBe(400);
|
|
});
|
|
});
|
|
|
|
describe("Thingiverse likes in the Browse tab", () => {
|
|
it("lists a user's likes first, counted from their profile, with a few of their models", async () => {
|
|
previousThingiverseToken = await getThingiverseAccessToken();
|
|
await setThingiverseAccessToken("test-token");
|
|
const saved = await request(app)
|
|
.put("/api/provider-collections/accounts/thingiverse")
|
|
.set(auth())
|
|
.send({ value: `https://www.thingiverse.com/${THINGIVERSE_USER}/designs` });
|
|
expect(saved.body.thingiverse.account).toBe(THINGIVERSE_USER);
|
|
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "thingiverse" }).set(auth());
|
|
expect(list.status).toBe(200);
|
|
expect(list.body.map((c: { name: string; likes: boolean }) => [c.name, c.likes])).toEqual([
|
|
["Liked models", true],
|
|
["Bits", false],
|
|
]);
|
|
expect(list.body[0]).toMatchObject({
|
|
id: `likes:${THINGIVERSE_USER.toLowerCase()}`,
|
|
url: `https://www.thingiverse.com/${THINGIVERSE_USER}/likes`,
|
|
model_count: 2,
|
|
thumbnails: ["https://cdn.thingiverse.com/hook.jpg", "https://cdn.thingiverse.com/clip.jpg"],
|
|
});
|
|
});
|
|
|
|
it("says the instance's token was rejected, rather than failing", async () => {
|
|
await setThingiverseAccessToken("rejected-token");
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "thingiverse" }).set(auth());
|
|
expect(list.status).toBe(400);
|
|
expect(list.body.detail).toMatch(/App Token .* was rejected/);
|
|
await setThingiverseAccessToken("test-token");
|
|
});
|
|
|
|
it("tells a Cloudflare challenge from a rejected token", async () => {
|
|
await setThingiverseAccessToken("challenged-token");
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "thingiverse" }).set(auth());
|
|
expect(list.status).toBe(429);
|
|
expect(list.body.detail).toMatch(/Cloudflare.*Setting up FlareSolverr/);
|
|
await setThingiverseAccessToken("test-token");
|
|
});
|
|
});
|
|
|
|
describe("turning sync on from the app", () => {
|
|
it("only follows new MakerWorld models: everything there now counts as seen", async () => {
|
|
const collectionId = await newCollection("MakerWorld");
|
|
const source = makerworldCollections[0];
|
|
const res = await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://makerworld.com/en/collections/${source.id}`, interval_hours: 6 });
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toMatchObject({ importing: false, sync: { provider: "makerworld", interval_hours: 6 } });
|
|
|
|
const sync = await prisma.collectionSync.findUniqueOrThrow({ where: { collectionId } });
|
|
expect(sync.knownIds.toSorted()).toEqual(makerworldDesigns[source.id].toSorted());
|
|
expect(await prisma.importJob.count({ where: { collectionSyncId: sync.id } })).toBe(0);
|
|
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "makerworld" }).set(auth());
|
|
expect(list.body[0].synced).toEqual({ collection_id: collectionId, collection_name: `MakerWorld ${stamp}` });
|
|
|
|
// One provider collection syncs with one Thingport collection at most.
|
|
const other = await newCollection("Second");
|
|
const again = await request(app)
|
|
.post(`/api/collection/${other}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://makerworld.com/en/collections/${source.id}` });
|
|
expect(again.status).toBe(409);
|
|
});
|
|
|
|
it("imports what a Printables collection holds now, through the sync's first check", async () => {
|
|
const collectionId = await newCollection("Printables");
|
|
const source = printablesCollections[1];
|
|
const res = await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://www.printables.com/@${PRINTABLES_USER.handle}/collections/${source.id}` });
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.importing).toBe(true);
|
|
|
|
// The first check runs in the background; the stand-in collection is empty, so it just finishes.
|
|
await vi.waitFor(
|
|
async () => {
|
|
const sync = await prisma.collectionSync.findUniqueOrThrow({ where: { collectionId } });
|
|
expect(sync.lastCheckedAt).not.toBeNull();
|
|
},
|
|
{ timeout: 5000 },
|
|
);
|
|
});
|
|
|
|
it("refuses print profile choices for providers without them", async () => {
|
|
const collectionId = await newCollection("Profiles");
|
|
const res = await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://www.printables.com/collections/${printablesCollections[0].id}`, profile_scope: "all" });
|
|
expect(res.status).toBe(400);
|
|
});
|
|
});
|
|
|
|
async function queuedUrls(collectionId: string): Promise<string[]> {
|
|
const sync = await prisma.collectionSync.findUniqueOrThrow({ where: { collectionId } });
|
|
const items = await prisma.importJobItem.findMany({ where: { job: { collectionSyncId: sync.id } } });
|
|
return items.map((item) => item.url).toSorted();
|
|
}
|
|
|
|
describe("private Printables collections and likes, through Thingport Grab", () => {
|
|
const privateId = newId();
|
|
const likedIds = [newId(), newId()];
|
|
const push = (body: Record<string, unknown>) =>
|
|
request(app)
|
|
.put("/api/provider-collections/printables/push")
|
|
.set(auth())
|
|
.send({ user: { id: PRINTABLES_USER.id, handle: PRINTABLES_USER.handle }, ...body });
|
|
const likesUrl = `https://www.printables.com/@${PRINTABLES_USER.handle}/collections/liked`;
|
|
|
|
it("lists the private collections and likes Grab read, after the public ones", async () => {
|
|
const res = await push({
|
|
collections: [
|
|
{ id: privateId, name: "Secret stash", model_count: 2, thumbnails: [], private: true },
|
|
{ id: printablesCollections[0].id, name: "Practical finds", model_count: 23, thumbnails: [], private: false },
|
|
],
|
|
likes: { model_count: 2, thumbnails: [] },
|
|
});
|
|
expect(res.status).toBe(200);
|
|
|
|
const list = await request(app).get("/api/provider-collections").query({ provider: "printables" }).set(auth());
|
|
expect(list.body.map((c: { name: string; private: boolean }) => [c.name, c.private])).toEqual([
|
|
["Liked models", true],
|
|
["Practical finds", false],
|
|
["Cool toys", false],
|
|
["Secret stash", true],
|
|
]);
|
|
expect(list.body[0]).toMatchObject({
|
|
id: `likes:${PRINTABLES_USER.handle.toLowerCase()}`,
|
|
url: likesUrl,
|
|
likes: true,
|
|
});
|
|
expect(list.body[1].likes).toBe(false);
|
|
});
|
|
|
|
it("finds the user's own likes by link, and nobody else's", async () => {
|
|
const own = await request(app).get("/api/provider-collections/lookup").query({ url: likesUrl }).set(auth());
|
|
expect(own.status).toBe(200);
|
|
expect(own.body.name).toBe("Liked models");
|
|
|
|
const other = await request(app)
|
|
.get("/api/provider-collections/lookup")
|
|
.query({ url: "https://www.printables.com/@someone_1/collections/liked" })
|
|
.set(auth());
|
|
expect(other.status).toBe(404);
|
|
});
|
|
|
|
it("waits for Grab to read a private collection, then imports it on the next visit", async () => {
|
|
const collectionId = await newCollection("Private");
|
|
const res = await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://www.printables.com/@${PRINTABLES_USER.handle}/collections/${privateId}` });
|
|
expect(res.body).toMatchObject({ importing: true, waiting: true });
|
|
await vi.waitFor(
|
|
async () =>
|
|
expect((await prisma.collectionSync.findUniqueOrThrow({ where: { collectionId } })).lastError).toBe(
|
|
WAITING_FOR_GRAB,
|
|
),
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
const wanted = await request(app).get("/api/provider-collections/printables/wanted").set(auth());
|
|
expect(wanted.body.keys).toContain(privateId);
|
|
|
|
const models = [newId(), newId()];
|
|
await push({ listings: { [privateId]: models } });
|
|
await vi.waitFor(
|
|
async () =>
|
|
expect(await queuedUrls(collectionId)).toEqual(
|
|
models.map((m) => `https://www.printables.com/model/${m}`).toSorted(),
|
|
),
|
|
{ timeout: 5000 },
|
|
);
|
|
});
|
|
|
|
it("reads a collection made public live again, not from what Grab read while it was private", async () => {
|
|
const id = newId();
|
|
await push({
|
|
collections: [{ id, name: "Was private", model_count: 1, thumbnails: [], private: true }],
|
|
listings: { [id]: [newId()] },
|
|
});
|
|
expect(Array.isArray(await pushedPrintablesModels(userId, id))).toBe(true);
|
|
|
|
await push({ collections: [{ id, name: "Was private", model_count: 1, thumbnails: [], private: false }] });
|
|
expect(await pushedPrintablesModels(userId, id)).toBeNull();
|
|
});
|
|
|
|
it("forgets the models Grab read of a collection that's no longer private", async () => {
|
|
const id = newId();
|
|
await push({
|
|
collections: [{ id, name: "Soon public", model_count: 1, thumbnails: [], private: true }],
|
|
listings: { [id]: [newId()] },
|
|
});
|
|
await push({ collections: [{ id, name: "Soon public", model_count: 1, thumbnails: [], private: false }] });
|
|
expect(await prisma.providerListing.count({ where: { userId, key: id } })).toBe(0);
|
|
});
|
|
|
|
it("refuses a push for a Printables account Printables doesn't know", async () => {
|
|
const res = await request(app)
|
|
.put("/api/provider-collections/printables/push")
|
|
.set(auth())
|
|
.send({ user: { id: PRINTABLES_USER.id, handle: "someone_else" }, collections: [] });
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
it("asks Grab for only a few private collections per visit", async () => {
|
|
const ids = [newId(), newId(), newId(), newId()];
|
|
await push({
|
|
collections: ids.map((id) => ({ id, name: `Private ${id}`, model_count: 1, thumbnails: [], private: true })),
|
|
});
|
|
for (const id of ids) {
|
|
const collectionId = await newCollection(`Capped ${id}`);
|
|
await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://www.printables.com/@${PRINTABLES_USER.handle}/collections/${id}` });
|
|
}
|
|
const wanted = await request(app).get("/api/provider-collections/printables/wanted").set(auth());
|
|
expect(wanted.body.keys.filter((key: string) => ids.includes(key))).toHaveLength(3);
|
|
});
|
|
|
|
it("takes only a few private collections' models per push", async () => {
|
|
const listings = Object.fromEntries(Array.from({ length: 11 }, () => [newId(), [newId()]]));
|
|
expect((await push({ listings })).status).toBe(400);
|
|
});
|
|
|
|
it("refuses to sync someone else's Printables likes", async () => {
|
|
const collectionId = await newCollection("Their likes");
|
|
const res = await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: "https://www.printables.com/@someone_1/collections/liked" });
|
|
expect(res.status).toBe(400);
|
|
const viaGrab = await request(app)
|
|
.post("/api/collection-sync")
|
|
.set(auth())
|
|
.send({ url: "https://www.printables.com/@someone_1/collections/liked", known_ids: [] });
|
|
expect(viaGrab.status).toBe(400);
|
|
expect(await prisma.collectionSync.count({ where: { userId, externalId: "likes:someone_1" } })).toBe(0);
|
|
});
|
|
|
|
it("doesn't take a typed-in Printables username as proof whose likes they are", async () => {
|
|
await request(app)
|
|
.put("/api/provider-collections/accounts/printables")
|
|
.set(auth())
|
|
.send({ value: `@${OTHER_PRINTABLES_USER.handle}` });
|
|
const collectionId = await newCollection("Typed likes");
|
|
const res = await request(app)
|
|
.post(`/api/collection/${collectionId}/sync`)
|
|
.set(auth())
|
|
.send({ url: `https://www.printables.com/@${OTHER_PRINTABLES_USER.handle}/collections/liked` });
|
|
expect(res.status).toBe(400);
|
|
// Back to the account Grab pushed, for the tests after this one.
|
|
await push({});
|
|
});
|
|
|
|
it("turns Grab's Printables pushes off when the user forgets their account, until they give one again", async () => {
|
|
const forget = await request(app)
|
|
.put("/api/provider-collections/accounts/printables")
|
|
.set(auth())
|
|
.send({ value: null });
|
|
expect(forget.body.printables.account).toBeNull();
|
|
expect(await prisma.providerListing.count({ where: { userId, provider: "printables" } })).toBe(0);
|
|
expect((await request(app).get("/api/provider-collections/printables/wanted").set(auth())).status).toBe(409);
|
|
expect((await push({ collections: [] })).status).toBe(409);
|
|
// The private collection synced earlier can't be told from a deleted one now: it pauses.
|
|
const synced = await prisma.collectionSync.findFirstOrThrow({ where: { userId, externalId: privateId } });
|
|
expect(await syncCollection(synced.id)).toBe("error");
|
|
expect((await prisma.collectionSync.findUniqueOrThrow({ where: { id: synced.id } })).lastError).toBe(
|
|
PRINTABLES_FORGOTTEN,
|
|
);
|
|
|
|
await request(app)
|
|
.put("/api/provider-collections/accounts/printables")
|
|
.set(auth())
|
|
.send({ value: `@${PRINTABLES_USER.handle}` });
|
|
expect((await push({ likes: { model_count: 0, thumbnails: [] } })).status).toBe(200);
|
|
});
|
|
|
|
it("syncs the user's likes from the ids Grab read", async () => {
|
|
const collectionId = await newCollection("Likes");
|
|
const res = await request(app).post(`/api/collection/${collectionId}/sync`).set(auth()).send({ url: likesUrl });
|
|
expect(res.body).toMatchObject({ importing: true, waiting: true });
|
|
|
|
const wanted = await request(app).get("/api/provider-collections/printables/wanted").set(auth());
|
|
expect(wanted.body.keys).toContain("likes");
|
|
|
|
await push({ likes: { model_count: 2, thumbnails: [], ids: likedIds } });
|
|
await vi.waitFor(
|
|
async () =>
|
|
expect(await queuedUrls(collectionId)).toEqual(
|
|
likedIds.map((m) => `https://www.printables.com/model/${m}`).toSorted(),
|
|
),
|
|
{ timeout: 5000 },
|
|
);
|
|
});
|
|
});
|