mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add R2 bucket config tests
This commit is contained in:
1 parent
55e6ec0cdf
commit
84468cbed5
2 files changed
+185
-16
No files matched your search
+27
-16
@@ -114,9 +114,20 @@ function safeDecodeNpub(npub) {
|
||||
}
|
||||
|
||||
class R2Service {
|
||||
constructor() {
|
||||
constructor({
|
||||
makeR2Client: makeR2ClientOverride,
|
||||
multipartUpload: multipartUploadOverride,
|
||||
ensureBucketExists: ensureBucketExistsOverride,
|
||||
ensureBucketCors: ensureBucketCorsOverride,
|
||||
deleteObject: deleteObjectOverride,
|
||||
} = {}) {
|
||||
this.listeners = new Map();
|
||||
this.cloudflareSettings = null;
|
||||
this.makeR2Client = makeR2ClientOverride || makeR2Client;
|
||||
this.multipartUpload = multipartUploadOverride || multipartUpload;
|
||||
this.ensureBucketExists = ensureBucketExistsOverride || ensureBucketExists;
|
||||
this.ensureBucketCors = ensureBucketCorsOverride || ensureBucketCors;
|
||||
this.deleteObject = deleteObjectOverride || deleteObject;
|
||||
}
|
||||
|
||||
on(event, handler) {
|
||||
@@ -481,7 +492,7 @@ class R2Service {
|
||||
// We attempt to ensure the bucket exists and CORS is set up using the S3 keys if possible.
|
||||
if (accessKeyId && secretAccessKey) {
|
||||
try {
|
||||
const s3 = makeR2Client({
|
||||
const s3 = this.makeR2Client({
|
||||
accountId,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
@@ -491,7 +502,7 @@ class R2Service {
|
||||
|
||||
// Attempt to auto-create the bucket (requires Admin keys, but harmless if fails)
|
||||
try {
|
||||
await ensureBucketExists({
|
||||
await this.ensureBucketExists({
|
||||
s3,
|
||||
bucket: bucketName,
|
||||
region: settings.region,
|
||||
@@ -506,7 +517,7 @@ class R2Service {
|
||||
}
|
||||
|
||||
if (corsOrigins.length > 0) {
|
||||
await ensureBucketCors({
|
||||
await this.ensureBucketCors({
|
||||
s3,
|
||||
bucket: bucketName,
|
||||
origins: corsOrigins,
|
||||
@@ -586,7 +597,7 @@ class R2Service {
|
||||
|
||||
try {
|
||||
// 1. Initialize S3
|
||||
const s3 = makeR2Client({
|
||||
const s3 = this.makeR2Client({
|
||||
accountId,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
@@ -596,7 +607,7 @@ class R2Service {
|
||||
|
||||
// 2. Ensure bucket (best effort)
|
||||
try {
|
||||
await ensureBucketExists({
|
||||
await this.ensureBucketExists({
|
||||
s3,
|
||||
bucket: bucketName,
|
||||
region: settings.region,
|
||||
@@ -610,7 +621,7 @@ class R2Service {
|
||||
try {
|
||||
const corsOrigins = this.getCorsOrigins();
|
||||
if (corsOrigins.length > 0) {
|
||||
await ensureBucketCors({
|
||||
await this.ensureBucketCors({
|
||||
s3,
|
||||
bucket: bucketName,
|
||||
origins: corsOrigins,
|
||||
@@ -623,7 +634,7 @@ class R2Service {
|
||||
|
||||
// 4. Upload Test File
|
||||
const file = new File([verifyContent], "verify.txt", { type: "text/plain" });
|
||||
await multipartUpload({
|
||||
await this.multipartUpload({
|
||||
s3,
|
||||
bucket: bucketName,
|
||||
key: verifyKey,
|
||||
@@ -641,7 +652,7 @@ class R2Service {
|
||||
|
||||
if (!response.ok) {
|
||||
// Cleanup attempt
|
||||
try { await deleteObject({ s3, bucket: bucketName, key: verifyKey }); } catch (e) {}
|
||||
try { await this.deleteObject({ s3, bucket: bucketName, key: verifyKey }); } catch (e) {}
|
||||
|
||||
if (response.status === 404) {
|
||||
return { success: false, error: "File not found. Check your Public Bucket URL." };
|
||||
@@ -652,7 +663,7 @@ class R2Service {
|
||||
const text = await response.text();
|
||||
|
||||
// Cleanup
|
||||
try { await deleteObject({ s3, bucket: bucketName, key: verifyKey }); } catch (e) {}
|
||||
try { await this.deleteObject({ s3, bucket: bucketName, key: verifyKey }); } catch (e) {}
|
||||
|
||||
if (text.trim() !== verifyContent) {
|
||||
return { success: false, error: "Content mismatch. URL might be pointing elsewhere." };
|
||||
@@ -881,7 +892,7 @@ class R2Service {
|
||||
};
|
||||
|
||||
try {
|
||||
const s3 = makeR2Client({
|
||||
const s3 = this.makeR2Client({
|
||||
accountId,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
@@ -895,7 +906,7 @@ class R2Service {
|
||||
const thumbKey = key.replace(/\.[^/.]+$/, "") + `.thumb.${thumbExt}`;
|
||||
|
||||
try {
|
||||
await multipartUpload({
|
||||
await this.multipartUpload({
|
||||
s3,
|
||||
bucket: bucketEntry.bucket,
|
||||
key: thumbKey,
|
||||
@@ -915,7 +926,7 @@ class R2Service {
|
||||
|
||||
this.setCloudflareUploadStatus(statusMessage, "info");
|
||||
|
||||
await multipartUpload({
|
||||
await this.multipartUpload({
|
||||
s3,
|
||||
bucket: bucketEntry.bucket,
|
||||
key,
|
||||
@@ -935,7 +946,7 @@ class R2Service {
|
||||
this.setCloudflareUploadStatus("Uploading torrent metadata...", "info");
|
||||
const torrentKey = buildTorrentKey();
|
||||
try {
|
||||
await multipartUpload({
|
||||
await this.multipartUpload({
|
||||
s3,
|
||||
bucket: bucketEntry.bucket,
|
||||
key: torrentKey,
|
||||
@@ -1101,7 +1112,7 @@ class R2Service {
|
||||
const useR2 = provider === "cloudflare_r2" || Boolean(accountId);
|
||||
|
||||
if (useR2) {
|
||||
s3 = makeR2Client({
|
||||
s3 = this.makeR2Client({
|
||||
accountId,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
@@ -1119,7 +1130,7 @@ class R2Service {
|
||||
});
|
||||
}
|
||||
|
||||
await multipartUpload({
|
||||
await this.multipartUpload({
|
||||
s3,
|
||||
bucket,
|
||||
key,
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import r2ServiceDefault from "../../../js/services/r2Service.js";
|
||||
import storageService from "../../../js/services/storageService.js";
|
||||
import { sanitizeBucketName } from "../../../js/storage/r2-mgmt.js";
|
||||
|
||||
const R2Service = r2ServiceDefault.constructor;
|
||||
const npub = "npub1r2buckettest";
|
||||
const metaBucket = "r2-meta-bucket";
|
||||
const sanitizedBucket = sanitizeBucketName(npub);
|
||||
|
||||
let originalWindow;
|
||||
let originalFile;
|
||||
let originalFetch;
|
||||
let originalSetTimeout;
|
||||
|
||||
beforeEach(() => {
|
||||
originalWindow = globalThis.window;
|
||||
originalFile = globalThis.File;
|
||||
originalFetch = globalThis.fetch;
|
||||
originalSetTimeout = globalThis.setTimeout;
|
||||
|
||||
globalThis.window = {
|
||||
location: { origin: "" },
|
||||
NostrTools: {
|
||||
nip19: {
|
||||
decode: () => ({ type: "npub", data: "pubkey123" }),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
globalThis.File = class TestFile {
|
||||
constructor(parts, name, options = {}) {
|
||||
this.parts = parts;
|
||||
this.name = name;
|
||||
this.type = options.type;
|
||||
}
|
||||
};
|
||||
|
||||
globalThis.fetch = async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text: async () => "bitvid-verification",
|
||||
});
|
||||
|
||||
globalThis.setTimeout = (fn) => {
|
||||
fn();
|
||||
return 0;
|
||||
};
|
||||
|
||||
mock.method(storageService, "listConnections", async () => [
|
||||
{
|
||||
id: "conn-1",
|
||||
provider: "cloudflare_r2",
|
||||
meta: { defaultForUploads: true },
|
||||
},
|
||||
]);
|
||||
mock.method(storageService, "isUnlocked", () => true);
|
||||
mock.method(storageService, "getConnection", async () => ({
|
||||
provider: "cloudflare_r2",
|
||||
accountId: "acct-123",
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
endpoint: "https://r2.example.com",
|
||||
region: "auto",
|
||||
meta: {
|
||||
bucket: metaBucket,
|
||||
accountId: "acct-123",
|
||||
publicBaseUrl: "https://cdn.example.com",
|
||||
},
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restoreAll();
|
||||
globalThis.window = originalWindow;
|
||||
globalThis.File = originalFile;
|
||||
globalThis.fetch = originalFetch;
|
||||
globalThis.setTimeout = originalSetTimeout;
|
||||
});
|
||||
|
||||
describe("R2Service bucket selection", () => {
|
||||
it("uses meta.bucket for ensureBucketExists and multipartUpload", async () => {
|
||||
const bucketCalls = { ensure: [], multipart: [] };
|
||||
|
||||
const ensureBucketExists = mock.fn(async ({ bucket }) => {
|
||||
bucketCalls.ensure.push(bucket);
|
||||
});
|
||||
const multipartUpload = mock.fn(async ({ bucket }) => {
|
||||
bucketCalls.multipart.push(bucket);
|
||||
});
|
||||
|
||||
const service = new R2Service({
|
||||
makeR2Client: mock.fn(() => ({})),
|
||||
ensureBucketExists,
|
||||
ensureBucketCors: mock.fn(async () => {}),
|
||||
multipartUpload,
|
||||
deleteObject: mock.fn(async () => {}),
|
||||
});
|
||||
|
||||
await service.prepareUpload(npub);
|
||||
|
||||
assert.ok(bucketCalls.ensure.length > 0);
|
||||
for (const bucket of bucketCalls.ensure) {
|
||||
assert.strictEqual(bucket, metaBucket);
|
||||
assert.notStrictEqual(bucket, sanitizedBucket);
|
||||
}
|
||||
|
||||
await service.handleCloudflareUploadSubmit({
|
||||
npub,
|
||||
file: { name: "video.mp4", type: "video/mp4" },
|
||||
metadata: { title: "Test upload" },
|
||||
publishVideoNote: mock.fn(async () => true),
|
||||
});
|
||||
|
||||
assert.ok(bucketCalls.multipart.length > 0);
|
||||
for (const bucket of bucketCalls.multipart) {
|
||||
assert.strictEqual(bucket, metaBucket);
|
||||
assert.notStrictEqual(bucket, sanitizedBucket);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses configured meta.bucket when verifying public access", async () => {
|
||||
const bucketCalls = { ensure: [], multipart: [] };
|
||||
|
||||
const ensureBucketExists = mock.fn(async ({ bucket }) => {
|
||||
bucketCalls.ensure.push(bucket);
|
||||
});
|
||||
const multipartUpload = mock.fn(async ({ bucket }) => {
|
||||
bucketCalls.multipart.push(bucket);
|
||||
});
|
||||
|
||||
const service = new R2Service({
|
||||
makeR2Client: mock.fn(() => ({})),
|
||||
ensureBucketExists,
|
||||
ensureBucketCors: mock.fn(async () => {}),
|
||||
multipartUpload,
|
||||
deleteObject: mock.fn(async () => {}),
|
||||
});
|
||||
const result = await service.verifyPublicAccess({
|
||||
npub,
|
||||
settings: {
|
||||
accountId: "acct-123",
|
||||
accessKeyId: "access-key",
|
||||
secretAccessKey: "secret-key",
|
||||
baseDomain: "https://cdn.example.com",
|
||||
meta: { bucket: metaBucket },
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(result.success, true);
|
||||
assert.ok(bucketCalls.ensure.length > 0);
|
||||
assert.strictEqual(bucketCalls.ensure[0], metaBucket);
|
||||
assert.ok(bucketCalls.multipart.length > 0);
|
||||
assert.strictEqual(bucketCalls.multipart[0], metaBucket);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user