mirror of
https://github.com/Alvin-Zilverstand/return-youtube-dislike.git
synced 2026-10-28 07:40:27 +01:00
121 lines
4.8 KiB
JavaScript
121 lines
4.8 KiB
JavaScript
|
|
const { AttributedRuntimeTrafficLedger, attributeRuntimeRequest } = require("../e2e/live/live-youtube-driver");
|
||
|
|
|
||
|
|
const SELECTED_EXTENSION_ID = "abcdefghijklmnopabcdefghijklmnop";
|
||
|
|
const EXPECTED_BUILD_ID = "0123456789abcdef0123456789abcdef";
|
||
|
|
|
||
|
|
function createEventContext(pages = []) {
|
||
|
|
const listeners = new Map();
|
||
|
|
return {
|
||
|
|
context: {
|
||
|
|
off: jest.fn((event, listener) => listeners.get(event)?.delete(listener)),
|
||
|
|
on: jest.fn((event, listener) => {
|
||
|
|
if (!listeners.has(event)) listeners.set(event, new Set());
|
||
|
|
listeners.get(event).add(listener);
|
||
|
|
}),
|
||
|
|
pages: jest.fn(() => pages),
|
||
|
|
},
|
||
|
|
emit(event, value) {
|
||
|
|
listeners.get(event)?.forEach((listener) => listener(value));
|
||
|
|
},
|
||
|
|
listeners,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function pageRequest(page, { method, pathname, videoId }) {
|
||
|
|
return {
|
||
|
|
frame: () => ({ page: () => page }),
|
||
|
|
method: () => method,
|
||
|
|
postDataJSON: () => ({ userId: "A".repeat(36), value: -1, videoId }),
|
||
|
|
url: () => `https://returnyoutubedislikeapi.com${pathname}${pathname === "/votes" ? `?videoId=${videoId}` : ""}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async function advanceFakeTime(milliseconds) {
|
||
|
|
for (let elapsed = 0; elapsed < milliseconds; elapsed += 25) {
|
||
|
|
jest.advanceTimersByTime(Math.min(25, milliseconds - elapsed));
|
||
|
|
await Promise.resolve();
|
||
|
|
await Promise.resolve();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("run-wide attributed runtime traffic ledger", () => {
|
||
|
|
afterEach(() => {
|
||
|
|
jest.useRealTimers();
|
||
|
|
});
|
||
|
|
|
||
|
|
test.each([
|
||
|
|
["late vote-data GET", { method: "GET", pathname: "/votes" }],
|
||
|
|
["late interaction POST", { method: "POST", pathname: "/interact/vote" }],
|
||
|
|
])("rejects a %s emitted after the stage listener detached", async (_label, requestOptions) => {
|
||
|
|
jest.useFakeTimers({ doNotFake: ["performance"] });
|
||
|
|
const page = {};
|
||
|
|
const harness = createEventContext([page]);
|
||
|
|
const ledger = new AttributedRuntimeTrafficLedger(harness.context, page, (request) =>
|
||
|
|
attributeRuntimeRequest(request, page, "userscript"),
|
||
|
|
);
|
||
|
|
const detachedStageListener = jest.fn();
|
||
|
|
harness.context.on("request", detachedStageListener);
|
||
|
|
harness.context.off("request", detachedStageListener);
|
||
|
|
const request = pageRequest(page, { ...requestOptions, videoId: "abcdefghijk" });
|
||
|
|
|
||
|
|
const audit = ledger.assertNoUnclaimed({ label: "detached stage", quietMs: 100, timeoutMs: 500 });
|
||
|
|
const rejection = expect(audit).rejects.toThrow(/escaped every active audit/);
|
||
|
|
setTimeout(() => harness.emit("request", request), 50);
|
||
|
|
await advanceFakeTime(250);
|
||
|
|
await rejection;
|
||
|
|
|
||
|
|
expect(detachedStageListener).not.toHaveBeenCalled();
|
||
|
|
expect(ledger.records).toEqual([
|
||
|
|
expect.objectContaining({
|
||
|
|
method: requestOptions.method,
|
||
|
|
pathname: requestOptions.pathname,
|
||
|
|
videoId: "abcdefghijk",
|
||
|
|
}),
|
||
|
|
]);
|
||
|
|
ledger.stop();
|
||
|
|
});
|
||
|
|
|
||
|
|
test("accepts delayed traffic only when an active stage claims that exact request", async () => {
|
||
|
|
jest.useFakeTimers({ doNotFake: ["performance"] });
|
||
|
|
const page = {};
|
||
|
|
const harness = createEventContext([page]);
|
||
|
|
const ledger = new AttributedRuntimeTrafficLedger(harness.context, page, (request) =>
|
||
|
|
attributeRuntimeRequest(request, page, "userscript"),
|
||
|
|
);
|
||
|
|
const request = pageRequest(page, { method: "GET", pathname: "/votes", videoId: "abcdefghijk" });
|
||
|
|
harness.context.on("request", (candidate) => ledger.claim(candidate, "exact-votes-stage"));
|
||
|
|
|
||
|
|
const audit = ledger.assertNoUnclaimed({ label: "claimed stage", quietMs: 100, timeoutMs: 500 });
|
||
|
|
setTimeout(() => harness.emit("request", request), 50);
|
||
|
|
await advanceFakeTime(250);
|
||
|
|
|
||
|
|
await expect(audit).resolves.toEqual({ attributedRequests: 1, label: "claimed stage" });
|
||
|
|
ledger.stop();
|
||
|
|
});
|
||
|
|
|
||
|
|
test("retains same-video other-tab ambiguity from request time after that tab navigates away", async () => {
|
||
|
|
const page = { url: () => "https://www.youtube.com/watch?v=abcdefghijk" };
|
||
|
|
let otherUrl = "https://www.youtube.com/shorts/abcdefghijk";
|
||
|
|
const otherPage = { url: () => otherUrl };
|
||
|
|
const harness = createEventContext([page, otherPage]);
|
||
|
|
const ledger = new AttributedRuntimeTrafficLedger(harness.context, page, (request) =>
|
||
|
|
attributeRuntimeRequest(request, page, "extension", SELECTED_EXTENSION_ID, EXPECTED_BUILD_ID),
|
||
|
|
);
|
||
|
|
const request = {
|
||
|
|
frame: () => {
|
||
|
|
throw new Error("Service Worker requests do not have a frame");
|
||
|
|
},
|
||
|
|
method: () => "GET",
|
||
|
|
serviceWorker: () => ({ url: () => `chrome-extension://${SELECTED_EXTENSION_ID}/ryd.background.js` }),
|
||
|
|
url: () => "https://returnyoutubedislikeapi.com/votes?videoId=abcdefghijk",
|
||
|
|
};
|
||
|
|
|
||
|
|
harness.emit("request", request);
|
||
|
|
expect(ledger.claim(request, "exact-votes-stage")).toBe(true);
|
||
|
|
otherUrl = "https://www.youtube.com/watch?v=lmnopqrstuv";
|
||
|
|
|
||
|
|
await expect(ledger.assertNoUnclaimed({ quietMs: 0 })).rejects.toThrow(/same video open at send time/);
|
||
|
|
ledger.stop();
|
||
|
|
});
|
||
|
|
});
|