tests(11): make uploadModal-reset zombie-callback test deterministic (correct mock boundary)

The "guard against zombie callbacks" subtest passed its asserts but leaked async
activity after the test ended (node:test flags that as failure). Root cause: it
mocked modal.generateTorrentMetadata / resolveUploadIdentifier, but
handleVideoSelection drives the upload through this.mediaUploader.uploadVideo —
a DIFFERENT object with its own helpers — so the mediaUploader still ran real
createTorrentMetadata()/hashing on a synthetic {name:"video.mp4"} file ("invalid
input type" async) and captureVideoMetadata() ran URL.createObjectURL(file)
("must be an instance of Blob"). Not intermittent — a consistent wrong-boundary mock.

Fix: mock at the real DI boundary — modal.mediaUploader.uploadVideo = () =>
delayedUpload (controllable pending promise) — and stub captureVideoMetadata.
Hermetic, deterministic (3/3 exit 0, # pass 3). Mutation-verified: breaking the
videoUploadId zombie guard makes the test fail. Production code untouched. See
TEST_INTEGRITY.md.

NOTE: the runner is fail-fast, so the full suite surfaces pre-existing failures one
at a time. tests/unit/hashChangeHandler.test.mjs is next (consistent AssertionError
on "loads default view if hash is empty" — likely the same stale-default bug the
sibling tests/app/hash-change-handler.test.mjs already had when TRENDING became the
logged-out default). Enumerating full scope next.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-05 11:29:36 -04:00
1 parent a326d04330
commit 567a12c758
3 files changed
+68 -16

No files matched your search

+14 -12
View File
@@ -151,17 +151,9 @@ describe("UploadModal Reset Logic", () => {
let resolveUpload;
const delayedUpload = new Promise(r => { resolveUpload = r; });
const slowR2Service = {
prepareUpload: async () => ({
settings: {},
bucketEntry: { bucket: 'test', publicBaseUrl: 'https://test.com' }
}),
uploadFile: () => delayedUpload // Return promise that waits
};
const modal = new UploadModal({
storageService: mockStorageService,
r2Service: slowR2Service,
r2Service: mockR2Service,
getCurrentPubkey: () => "pk1",
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
@@ -175,9 +167,19 @@ describe("UploadModal Reset Logic", () => {
modal.activeProvider = "cloudflare_r2";
modal.activeCredentials = { accountId: 'acc', accessKeyId: 'key', secretAccessKey: 'sec' };
// Mock generateTorrentMetadata BEFORE calling handleVideoSelection
modal.generateTorrentMetadata = async () => ({ hasValidInfoHash: false });
modal.resolveUploadIdentifier = async () => "mock-identifier";
// handleVideoSelection() drives the upload through this.mediaUploader.uploadVideo
// — NOT the modal's own generate/resolve helpers — so mock at THAT boundary.
// Returning the controllable pending promise lets us reset mid-upload and then
// release a "zombie" completion. This isolates the unit under test (the modal's
// videoUploadId versioning guard) from real webtorrent/S3, which is what the
// test intends. Previously the test mocked modal.generateTorrentMetadata /
// resolveUploadIdentifier (the wrong object), so the mediaUploader still ran
// real createTorrentMetadata()/hashing on a synthetic file — throwing "invalid
// input type" as async activity after the test ended.
modal.mediaUploader.uploadVideo = () => delayedUpload;
// captureVideoMetadata() does URL.createObjectURL(file), which needs a real
// Blob; stub it so the synthetic file below doesn't throw an async Blob error.
modal.captureVideoMetadata = async () => {};
// Start upload
const file = { name: "video.mp4" };