mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(upload): surface an 'Analyzing' status during pre-upload file hashing
Computing the torrent info-hash (and the content-hash fallback) reads the entire file before the upload starts and before any progress is emitted, so on multi-GB videos there was a long silent pause that looked like a freeze. Both upload services now emit an 'Analyzing video…' status when a full-file hash is required (i.e. no valid info-hash was pre-supplied). Cloudflare-upload audit #2. Test: tests/services/s3-upload-analyzing-status.test.mjs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c1ae810776
commit
6b603cc21c
3 files changed
+106
No files matched your search
@@ -0,0 +1,82 @@
|
||||
// Scenario (SCN-upload-analyzing-status):
|
||||
// Computing the torrent info-hash (and the content-hash fallback) reads the
|
||||
// ENTIRE file before the upload starts, which on large videos is a long silent
|
||||
// pause that looks like a freeze (Cloudflare-upload audit #2).
|
||||
// Given an upload with NO usable info-hash (so the file must be hashed),
|
||||
// When uploadVideo runs,
|
||||
// Then an "Analyzing…" status is emitted before the upload; and when a valid
|
||||
// info-hash IS supplied (no hashing needed) that status is NOT emitted.
|
||||
//
|
||||
// Anti-cheat: asserts the externally observable uploadStatus events, with the
|
||||
// hashing dependency mocked so the assertion reflects the control-flow decision,
|
||||
// not the hash itself.
|
||||
|
||||
import "../test-helpers/setup-localstorage.mjs";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { S3UploadService } from "../../js/services/s3UploadService.js";
|
||||
|
||||
function makeDeps() {
|
||||
return {
|
||||
ensureS3SdkLoaded: async () => {},
|
||||
makeS3Client: () => ({}),
|
||||
multipartUpload: async () => {},
|
||||
buildR2Key: () => "u/npub/ns/video.mp4",
|
||||
computeStorageContentHash: async () => "sha256deadbeefcafe",
|
||||
buildS3ObjectUrl: () => "https://mock.example/file",
|
||||
getCorsOrigins: () => ["*"],
|
||||
prepareS3Connection: async (o) => ({ ...o, bucket: "b", publicBaseUrl: "https://mock.example" }),
|
||||
validateS3Connection: (s) => ({
|
||||
...s,
|
||||
endpoint: "https://s3.mock",
|
||||
region: "auto",
|
||||
accessKeyId: "k",
|
||||
secretAccessKey: "s",
|
||||
bucket: "b",
|
||||
forcePathStyle: false,
|
||||
publicBaseUrl: "https://mock.example",
|
||||
}),
|
||||
userLogger: { warn() {}, error() {} },
|
||||
buildStoragePointerValue: () => "ptr",
|
||||
buildStoragePrefixFromKey: () => "prefix",
|
||||
getVideoNoteErrorMessage: (c) => `Error: ${c}`,
|
||||
normalizeVideoNotePayload: (p) => ({ payload: p, errors: [] }),
|
||||
// Returns an INVALID hash, so the service treats it as "no info-hash".
|
||||
calculateTorrentInfoHash: async () => "not-a-real-hash",
|
||||
};
|
||||
}
|
||||
|
||||
const fakeFile = { name: "video.mp4", type: "video/mp4", size: 10, slice() { return this; } };
|
||||
|
||||
async function statusesFor({ infoHash } = {}) {
|
||||
const service = new S3UploadService(makeDeps());
|
||||
const messages = [];
|
||||
service.on("uploadStatus", (d) => messages.push(d.message));
|
||||
const published = await service.uploadVideo({
|
||||
npub: "npub1example",
|
||||
file: fakeFile,
|
||||
metadata: { title: "Test" },
|
||||
settings: { endpoint: "https://s3.mock", accessKeyId: "k", secretAccessKey: "s", bucket: "b" },
|
||||
infoHash,
|
||||
publishVideoNote: async () => true,
|
||||
});
|
||||
return { messages, published };
|
||||
}
|
||||
|
||||
test("emits an Analyzing status when the file must be hashed (no info-hash)", async () => {
|
||||
const { messages, published } = await statusesFor({ infoHash: "" });
|
||||
assert.equal(published, true, "upload should still succeed");
|
||||
assert.ok(
|
||||
messages.some((m) => /analyz/i.test(m)),
|
||||
`expected an 'Analyzing' status, got: ${JSON.stringify(messages)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test("does NOT emit Analyzing when a valid info-hash is already supplied", async () => {
|
||||
const { messages } = await statusesFor({ infoHash: "a".repeat(40) });
|
||||
assert.ok(
|
||||
!messages.some((m) => /analyz/i.test(m)),
|
||||
`expected no 'Analyzing' status, got: ${JSON.stringify(messages)}`,
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user