test(player): pin the stop URL's ids, the beacon's settle, the kept point's edges
The stop-URL contract passed with the two ids swapped and read the backend from the working directory; nothing called a real beacon's onSettled or its library pass-through; the kept-point rule was held by its constants only.
This commit is contained in:
@@ -454,3 +454,31 @@ describe("uploadDownload's wire shape", () => {
|
||||
expect(FakeXHR.last.headers["X-Upload-Display-Name"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// The library player re-reads its listings once its closing point is on the server: it cancelled
|
||||
// their poll so an older answer could not put the point back, and only this callback brings them
|
||||
// back. A beacon that never called it, or a slice that dropped it, left the lists stale with every
|
||||
// test green.
|
||||
describe("a beacon's settle callback", () => {
|
||||
it("runs once the request is answered, and once it has failed", async () => {
|
||||
const fetch = vi
|
||||
.spyOn(globalThis, "fetch")
|
||||
.mockResolvedValueOnce(new Response(null, { status: 204 }))
|
||||
.mockRejectedValueOnce(new TypeError("offline"));
|
||||
const answered = vi.fn();
|
||||
const failed = vi.fn();
|
||||
beacon("/api/a", {}, answered);
|
||||
beacon("/api/b", {}, failed);
|
||||
await vi.waitFor(() =>
|
||||
expect([answered.mock.calls.length, failed.mock.calls.length]).toEqual([1, 1]),
|
||||
);
|
||||
expect(fetch.mock.calls.map(([url]) => url).slice(-2)).toEqual(["/api/a", "/api/b"]);
|
||||
});
|
||||
|
||||
it("is passed on by the library progress beacon", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(null, { status: 204 }));
|
||||
const settled = vi.fn();
|
||||
downloadsApi.saveDownloadProgressBeacon(7, 125.4, 600, settled);
|
||||
await vi.waitFor(() => expect(settled).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { resolve } from "node:path";
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import { api } from "../api";
|
||||
|
||||
@@ -10,10 +10,9 @@ import { api } from "../api";
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
function serverRoute(): RegExp {
|
||||
// From the frontend root (vitest's cwd): this file runs under jsdom — `api` boots i18n, which reads
|
||||
// localStorage — and there `import.meta.url` is not a file URL `readFileSync` takes.
|
||||
// Anchored to this file, not the working directory, so the test holds wherever vitest starts.
|
||||
const source = readFileSync(
|
||||
join(process.cwd(), "../backend/app/routes/plex/playback.py"),
|
||||
resolve(__dirname, "../../../../backend/app/routes/plex/playback.py"),
|
||||
"utf8",
|
||||
);
|
||||
const prefix = /APIRouter\(prefix="([^"]+)"/.exec(source)?.[1];
|
||||
@@ -21,7 +20,9 @@ function serverRoute(): RegExp {
|
||||
// If either is missing the route was renamed or moved — re-point this test, the rule stands.
|
||||
expect(prefix, "router prefix").toBeDefined();
|
||||
expect(path, "the session stop route").toBeDefined();
|
||||
const pattern = (prefix! + path!).replace(/\{[^}]+\}/g, "[^/]+");
|
||||
// Each path parameter becomes a group of its own NAME: two ids that swapped places still match a
|
||||
// pattern of anonymous wildcards, and the server then finds no session to stop.
|
||||
const pattern = (prefix! + path!).replace(/\{(\w+)\}/g, "(?<$1>[^/]+)");
|
||||
return new RegExp(`^${pattern}$`);
|
||||
}
|
||||
|
||||
@@ -29,8 +30,6 @@ it("sends the session stop to the route the server serves it on", () => {
|
||||
const fetch = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(null));
|
||||
api.plexStopSession("12345", "a1b2c3");
|
||||
const url = String(fetch.mock.calls[0]![0]);
|
||||
expect(url).toMatch(serverRoute());
|
||||
expect(url).toContain("/12345/");
|
||||
expect(url).toContain("/a1b2c3/");
|
||||
expect(url.match(serverRoute())?.groups).toEqual({ rating_key: "12345", session_id: "a1b2c3" });
|
||||
expect(fetch.mock.calls[0]![1]).toMatchObject({ method: "POST", keepalive: true });
|
||||
});
|
||||
|
||||
@@ -154,6 +154,18 @@ describe("keptPosition (the server's rule)", () => {
|
||||
expect(keptPosition(2995, 3000)).toBe(0);
|
||||
expect(keptPosition(120, 0)).toBe(120); // an unknown length cannot be "near the end"
|
||||
});
|
||||
|
||||
// The edges themselves, one second either side: a `>=` that became `>` on one side only moves a
|
||||
// rule both sides must apply alike, and the constants alone (resume.contract.test.ts) would not see
|
||||
// it. backend/tests/test_library_progress.py holds the server to the same rows.
|
||||
it.each([
|
||||
[4, 3000, 0],
|
||||
[5, 3000, 5],
|
||||
[2990, 3000, 2990],
|
||||
[2991, 3000, 0],
|
||||
])("at %s s of %s keeps %s", (position, duration, kept) => {
|
||||
expect(keptPosition(position, duration)).toBe(kept);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a shared link's point", () => {
|
||||
|
||||
Reference in New Issue
Block a user