118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
createBrowserUploadCancellationChannel,
|
|
type UploadCancellationBroadcastFacade,
|
|
} from "../../src/adapters/browser-transfer/resumable-upload/upload-cancellation-channel.ts";
|
|
|
|
type MessageListener = (
|
|
event: Readonly<{ data: unknown }>,
|
|
) => void;
|
|
|
|
function createBroadcastHarness() {
|
|
const listeners: Array<Set<MessageListener>> = [];
|
|
const closes: Array<ReturnType<typeof vi.fn>> = [];
|
|
|
|
function createChannel(): UploadCancellationBroadcastFacade {
|
|
const ownIndex = listeners.length;
|
|
const ownListeners = new Set<MessageListener>();
|
|
const close = vi.fn();
|
|
listeners.push(ownListeners);
|
|
closes.push(close);
|
|
return {
|
|
postMessage(message) {
|
|
for (const [index, peerListeners] of listeners.entries()) {
|
|
if (index === ownIndex) continue;
|
|
for (const listener of [...peerListeners]) {
|
|
listener({ data: structuredClone(message) });
|
|
}
|
|
}
|
|
},
|
|
addEventListener(_type, listener) {
|
|
ownListeners.add(listener);
|
|
},
|
|
removeEventListener(_type, listener) {
|
|
ownListeners.delete(listener);
|
|
},
|
|
close,
|
|
};
|
|
}
|
|
|
|
return {
|
|
createChannel,
|
|
emit(index: number, data: unknown) {
|
|
for (const listener of [...(listeners[index] ?? [])]) {
|
|
listener({ data });
|
|
}
|
|
},
|
|
listenerCount(index: number) {
|
|
return listeners[index]?.size ?? 0;
|
|
},
|
|
closes,
|
|
};
|
|
}
|
|
|
|
describe("browser upload cancellation channel", () => {
|
|
it("delivers only the closed v1 wire message to another context", () => {
|
|
const harness = createBroadcastHarness();
|
|
const first = createBrowserUploadCancellationChannel({
|
|
channelName: "product-upload-cancel-v1",
|
|
createChannel: harness.createChannel,
|
|
});
|
|
const second = createBrowserUploadCancellationChannel({
|
|
channelName: "product-upload-cancel-v1",
|
|
createChannel: harness.createChannel,
|
|
});
|
|
expect(first).toBeDefined();
|
|
expect(second).toBeDefined();
|
|
if (!first || !second) return;
|
|
|
|
const received = vi.fn();
|
|
const release = second.subscribe(received);
|
|
expect(first.publish("upload_key_cross_context")).toBe(true);
|
|
expect(received).toHaveBeenCalledOnce();
|
|
expect(received).toHaveBeenCalledWith(
|
|
"upload_key_cross_context",
|
|
);
|
|
|
|
for (const malformed of [
|
|
null,
|
|
{
|
|
protocol: "RESUMABLE_UPLOAD_CANCEL_V0",
|
|
uploadKey: "upload_key_cross_context",
|
|
},
|
|
{
|
|
protocol: "RESUMABLE_UPLOAD_CANCEL_V1",
|
|
uploadKey: "short",
|
|
},
|
|
{
|
|
protocol: "RESUMABLE_UPLOAD_CANCEL_V1",
|
|
uploadKey: "upload_key_cross_context",
|
|
extra: true,
|
|
},
|
|
]) {
|
|
harness.emit(1, malformed);
|
|
}
|
|
expect(received).toHaveBeenCalledTimes(1);
|
|
|
|
release();
|
|
second.close();
|
|
expect(harness.listenerCount(1)).toBe(0);
|
|
expect(harness.closes[1]).toHaveBeenCalledOnce();
|
|
expect(second.publish("upload_key_cross_context")).toBe(false);
|
|
first.close();
|
|
});
|
|
|
|
it("fails closed when the host is unsupported and rejects invalid namespaces", () => {
|
|
expect(
|
|
createBrowserUploadCancellationChannel({ host: {} }),
|
|
).toBeUndefined();
|
|
expect(() =>
|
|
createBrowserUploadCancellationChannel({
|
|
channelName: "invalid channel name",
|
|
host: {},
|
|
}),
|
|
).toThrow(/channel name/u);
|
|
});
|
|
});
|