mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
- Refactor `S3UploadService` to accept dependencies in constructor - Move `resolveUploadIdentifier` to class method for mocking - Add comprehensive unit tests for `S3UploadService` covering event emitter, validation, and upload logic - Ensure `WebTorrent` dependency is mocked or polyfilled in test environment Co-authored-by: PR0M3TH3AN <[email protected]>
229 lines
7.7 KiB
JavaScript
229 lines
7.7 KiB
JavaScript
import "../test-helpers/setup-localstorage.mjs";
|
|
import { test, mock } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { S3UploadService } from "../../js/services/s3UploadService.js";
|
|
|
|
// Mock VIDEO_NOTE_ERROR_CODES constants if needed, but they are imported in the service.
|
|
// We can just rely on the strings we return from the mocked getVideoNoteErrorMessage.
|
|
|
|
function createMockDeps() {
|
|
return {
|
|
ensureS3SdkLoaded: mock.fn(async () => {}),
|
|
makeS3Client: mock.fn(() => ({})),
|
|
multipartUpload: mock.fn(async () => {}),
|
|
buildR2Key: mock.fn(() => "mock-key"),
|
|
buildS3ObjectUrl: mock.fn(() => "https://mock-url.com/file"),
|
|
getCorsOrigins: mock.fn(() => ["*"]),
|
|
prepareS3Connection: mock.fn(async (opts) => ({
|
|
...opts,
|
|
bucket: "mock-bucket",
|
|
publicBaseUrl: "https://mock-url.com",
|
|
})),
|
|
validateS3Connection: mock.fn((settings) => ({
|
|
...settings,
|
|
bucket: "mock-bucket",
|
|
endpoint: "https://s3.mock",
|
|
})),
|
|
userLogger: {
|
|
warn: mock.fn(),
|
|
error: mock.fn(),
|
|
},
|
|
buildStoragePointerValue: mock.fn(() => "mock-pointer"),
|
|
buildStoragePrefixFromKey: mock.fn(() => "mock-prefix"),
|
|
getVideoNoteErrorMessage: mock.fn((code) => `Error: ${code}`),
|
|
normalizeVideoNotePayload: mock.fn((payload) => ({
|
|
payload,
|
|
errors: [],
|
|
})),
|
|
calculateTorrentInfoHash: mock.fn(async () => "mock-info-hash"),
|
|
};
|
|
}
|
|
|
|
test("S3UploadService", async (t) => {
|
|
await t.test("constructor initializes listeners", () => {
|
|
const service = new S3UploadService(createMockDeps());
|
|
assert.ok(service.listeners instanceof Map);
|
|
});
|
|
|
|
await t.test("Event Emitter Logic", async (t) => {
|
|
const service = new S3UploadService(createMockDeps());
|
|
const handler = mock.fn();
|
|
|
|
// on
|
|
const unsubscribe = service.on("test-event", handler);
|
|
assert.ok(service.listeners.has("test-event"));
|
|
assert.strictEqual(service.listeners.get("test-event").size, 1);
|
|
|
|
// emit
|
|
service.emit("test-event", { foo: "bar" });
|
|
assert.strictEqual(handler.mock.callCount(), 1);
|
|
assert.deepStrictEqual(handler.mock.calls[0].arguments[0], { foo: "bar" });
|
|
|
|
// unsubscribe
|
|
unsubscribe();
|
|
assert.ok(!service.listeners.has("test-event"));
|
|
});
|
|
|
|
await t.test("emit handles listener errors safely", () => {
|
|
const deps = createMockDeps();
|
|
const service = new S3UploadService(deps);
|
|
const handler = mock.fn(() => {
|
|
throw new Error("oops");
|
|
});
|
|
|
|
service.on("error-event", handler);
|
|
service.emit("error-event", {});
|
|
|
|
assert.strictEqual(handler.mock.callCount(), 1);
|
|
assert.strictEqual(deps.userLogger.error.mock.callCount(), 1);
|
|
assert.match(
|
|
deps.userLogger.error.mock.calls[0].arguments[0],
|
|
/Listener error for/
|
|
);
|
|
});
|
|
|
|
await t.test("verifyConnection", async () => {
|
|
const deps = createMockDeps();
|
|
const service = new S3UploadService(deps);
|
|
const settings = { foo: "bar" };
|
|
|
|
const result = await service.verifyConnection({ settings });
|
|
|
|
assert.strictEqual(deps.validateS3Connection.mock.callCount(), 1);
|
|
assert.strictEqual(deps.prepareS3Connection.mock.callCount(), 1);
|
|
assert.strictEqual(result.bucket, "mock-bucket");
|
|
});
|
|
|
|
await t.test("prepareUpload", async () => {
|
|
const deps = createMockDeps();
|
|
const service = new S3UploadService(deps);
|
|
const settings = { foo: "bar" };
|
|
|
|
const result = await service.prepareUpload(settings);
|
|
|
|
assert.ok(result.settings);
|
|
assert.ok(result.bucketEntry);
|
|
assert.strictEqual(result.bucketEntry.bucket, "mock-bucket");
|
|
});
|
|
|
|
await t.test("uploadFile", async (t) => {
|
|
await t.test("validates parameters", async () => {
|
|
const service = new S3UploadService(createMockDeps());
|
|
await assert.rejects(
|
|
() => service.uploadFile({}),
|
|
/Missing required parameters/
|
|
);
|
|
});
|
|
|
|
await t.test("uploads successfully", async () => {
|
|
const deps = createMockDeps();
|
|
const service = new S3UploadService(deps);
|
|
const file = new File(["content"], "test.txt", { type: "text/plain" });
|
|
const options = {
|
|
file,
|
|
endpoint: "https://s3.mock",
|
|
region: "us-east-1",
|
|
accessKeyId: "key",
|
|
secretAccessKey: "secret",
|
|
bucket: "bucket",
|
|
key: "key",
|
|
};
|
|
|
|
const result = await service.uploadFile(options);
|
|
|
|
assert.strictEqual(deps.ensureS3SdkLoaded.mock.callCount(), 1);
|
|
assert.strictEqual(deps.makeS3Client.mock.callCount(), 1);
|
|
assert.strictEqual(deps.multipartUpload.mock.callCount(), 1);
|
|
assert.strictEqual(result.bucket, "bucket");
|
|
assert.strictEqual(result.key, "key");
|
|
});
|
|
});
|
|
|
|
await t.test("uploadVideo", async (t) => {
|
|
const validParams = {
|
|
npub: "npub123",
|
|
file: new File(["video"], "video.mp4", { type: "video/mp4" }),
|
|
metadata: { title: "My Video" },
|
|
publishVideoNote: mock.fn(async () => true),
|
|
};
|
|
|
|
await t.test("fails if npub missing", async () => {
|
|
const service = new S3UploadService(createMockDeps());
|
|
const result = await service.uploadVideo({ ...validParams, npub: "" });
|
|
assert.strictEqual(result, false);
|
|
});
|
|
|
|
await t.test("fails if title missing", async () => {
|
|
const service = new S3UploadService(createMockDeps());
|
|
const result = await service.uploadVideo({
|
|
...validParams,
|
|
metadata: { title: "" },
|
|
});
|
|
assert.strictEqual(result, false);
|
|
});
|
|
|
|
await t.test("fails if file missing", async () => {
|
|
const service = new S3UploadService(createMockDeps());
|
|
const result = await service.uploadVideo({ ...validParams, file: null });
|
|
assert.strictEqual(result, false);
|
|
});
|
|
|
|
await t.test("successful upload flow", async () => {
|
|
const deps = createMockDeps();
|
|
const service = new S3UploadService(deps);
|
|
|
|
// Spy on status updates
|
|
const statusHandler = mock.fn();
|
|
service.on("uploadStatus", statusHandler);
|
|
|
|
const result = await service.uploadVideo(validParams);
|
|
|
|
assert.strictEqual(result, true);
|
|
assert.strictEqual(deps.ensureS3SdkLoaded.mock.callCount(), 1);
|
|
assert.strictEqual(deps.makeS3Client.mock.callCount(), 1);
|
|
// multipartUpload called for video
|
|
assert.ok(deps.multipartUpload.mock.callCount() >= 1);
|
|
assert.strictEqual(deps.calculateTorrentInfoHash.mock.callCount(), 1);
|
|
assert.strictEqual(validParams.publishVideoNote.mock.callCount(), 1);
|
|
});
|
|
|
|
await t.test("uploads thumbnail if provided", async () => {
|
|
const deps = createMockDeps();
|
|
const service = new S3UploadService(deps);
|
|
const thumbnailFile = new File(["img"], "thumb.jpg", {
|
|
type: "image/jpeg",
|
|
});
|
|
|
|
await service.uploadVideo({ ...validParams, thumbnailFile });
|
|
|
|
// multipartUpload called for video and thumbnail
|
|
assert.ok(deps.multipartUpload.mock.callCount() >= 2);
|
|
});
|
|
|
|
await t.test("handles validation errors from normalizeVideoNotePayload", async () => {
|
|
const deps = createMockDeps();
|
|
deps.normalizeVideoNotePayload = mock.fn(() => ({
|
|
payload: null,
|
|
errors: ["INVALID_DATA"],
|
|
}));
|
|
const service = new S3UploadService(deps);
|
|
|
|
const result = await service.uploadVideo(validParams);
|
|
|
|
assert.strictEqual(result, false);
|
|
assert.strictEqual(deps.getVideoNoteErrorMessage.mock.callCount(), 1);
|
|
});
|
|
|
|
await t.test("handles upload exception", async () => {
|
|
const deps = createMockDeps();
|
|
deps.multipartUpload = mock.fn(async () => { throw new Error("Upload failed"); });
|
|
const service = new S3UploadService(deps);
|
|
|
|
const result = await service.uploadVideo(validParams);
|
|
|
|
assert.strictEqual(result, false);
|
|
assert.strictEqual(deps.userLogger.error.mock.callCount(), 1);
|
|
});
|
|
});
|
|
});
|