Add R2 bucket config tests

This commit is contained in:
thePR0M3TH3AN committed 2026-01-26 07:46:54 -05:00
1 parent 55e6ec0cdf
commit 84468cbed5
2 files changed
+185 -16

No files matched your search

+27 -16
View File
@@ -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);
});
});