mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
- Add helper text to the S3 storage configuration form to guide users on Endpoint URL format. - Add 'Force Path Style' checkbox to S3 storage configuration to support MinIO and other S3-compatible providers that require it. - Update `ProfileModalController` to toggle visibility of these new fields based on the selected provider. - Update `ProfileModalController` to save and load the `forcePathStyle` setting. - Update `tests/ui/storageService.test.mjs` to verify persistence of `forcePathStyle`.
170 lines
6.4 KiB
JavaScript
170 lines
6.4 KiB
JavaScript
import { test, describe, it, before, after, beforeEach } from "node:test";
|
|
import assert from "node:assert";
|
|
import { createUiDom } from "./helpers/jsdom-test-helpers.mjs";
|
|
|
|
// We need to import storageService AFTER we setup globals like indexedDB and crypto
|
|
// So we will use dynamic import in the before() hook.
|
|
let storageService;
|
|
let PROVIDERS;
|
|
|
|
describe("StorageService", () => {
|
|
let dom;
|
|
const mockPubkey = "0000000000000000000000000000000000000000000000000000000000000001";
|
|
|
|
// Mock Signer (NIP-04 style for simplicity)
|
|
// In a real app, this does ECIES. Here we just reverse string or something reversible for test speed/simplicity
|
|
// OR strictly obey the contract: returns Promise<hexString>
|
|
const mockSigner = {
|
|
encrypt: async (pubkey, plaintext) => {
|
|
// Fake encryption: prefix "enc:" + hex(plaintext)
|
|
return "enc:" + Buffer.from(plaintext).toString("hex");
|
|
},
|
|
decrypt: async (pubkey, ciphertext) => {
|
|
// Fake decryption
|
|
if (!ciphertext.startsWith("enc:")) throw new Error("Bad ciphertext");
|
|
const hex = ciphertext.slice(4);
|
|
return Buffer.from(hex, "hex").toString("utf8");
|
|
},
|
|
// Add NIP-04 aliases just in case
|
|
nip04Encrypt: async (pk, pt) => "enc:" + Buffer.from(pt).toString("hex"),
|
|
nip04Decrypt: async (pk, ct) => {
|
|
if (!ct.startsWith("enc:")) throw new Error("Bad ciphertext");
|
|
return Buffer.from(ct.slice(4), "hex").toString("utf8");
|
|
}
|
|
};
|
|
|
|
before(async () => {
|
|
dom = createUiDom();
|
|
|
|
// Polyfill Globals
|
|
global.window = dom.window;
|
|
|
|
// Use fake-indexeddb for reliable testing
|
|
const { indexedDB, IDBKeyRange } = await import("fake-indexeddb");
|
|
global.indexedDB = indexedDB;
|
|
globalThis.indexedDB = indexedDB;
|
|
global.IDBKeyRange = IDBKeyRange;
|
|
globalThis.IDBKeyRange = IDBKeyRange;
|
|
|
|
// Ensure crypto is available (Node's or Window's)
|
|
if (!global.crypto) {
|
|
const cryptoModule = await import("node:crypto");
|
|
global.crypto = global.window.crypto || cryptoModule.webcrypto;
|
|
}
|
|
// StorageService uses crypto.subtle
|
|
if (!global.crypto.subtle) {
|
|
// If JSDOM crypto is partial, fallback to Node
|
|
const cryptoModule = await import("node:crypto");
|
|
global.crypto = cryptoModule.webcrypto;
|
|
}
|
|
|
|
// Now import the service (it instantiates 'storageService' singleton on load)
|
|
const module = await import("../../js/services/storageService.js");
|
|
storageService = module.default;
|
|
PROVIDERS = module.PROVIDERS;
|
|
});
|
|
|
|
after(() => {
|
|
if (dom) dom.cleanup();
|
|
});
|
|
|
|
// Since storageService is a singleton, state might persist.
|
|
// We should try to reset it or use different pubkeys.
|
|
// The service uses IndexedDB. We can try to delete the DB between tests if needed,
|
|
// or just rely on unique pubkeys or connection IDs.
|
|
// For unit tests, clearing the DB is cleaner.
|
|
beforeEach(async () => {
|
|
// Optional: Clear DB store if possible.
|
|
// Since `storageService` doesn't expose a clear method, we'll just use unique IDs/pubkeys or rely on overwrites.
|
|
// But we can reset the in-memory masterKeys map.
|
|
if (storageService.masterKeys) storageService.masterKeys.clear();
|
|
});
|
|
|
|
it("should unlock storage with a valid signer", async () => {
|
|
assert.strictEqual(storageService.isUnlocked(mockPubkey), false, "Should be locked initially");
|
|
|
|
await storageService.unlock(mockPubkey, { signer: mockSigner });
|
|
|
|
assert.strictEqual(storageService.isUnlocked(mockPubkey), true, "Should be unlocked");
|
|
});
|
|
|
|
it("should save and retrieve an R2 connection", async () => {
|
|
// Ensure unlocked
|
|
if (!storageService.isUnlocked(mockPubkey)) {
|
|
await storageService.unlock(mockPubkey, { signer: mockSigner });
|
|
}
|
|
|
|
const connectionId = "r2-test-1";
|
|
const payload = {
|
|
provider: PROVIDERS.R2,
|
|
accountId: "acc123",
|
|
accessKeyId: "key123",
|
|
secretAccessKey: "secret123"
|
|
};
|
|
const meta = {
|
|
label: "My R2",
|
|
baseDomain: "https://pub.r2.dev"
|
|
};
|
|
|
|
await storageService.saveConnection(mockPubkey, connectionId, payload, meta);
|
|
|
|
const retrieved = await storageService.getConnection(mockPubkey, connectionId);
|
|
|
|
assert.ok(retrieved, "Should return connection");
|
|
assert.strictEqual(retrieved.provider, PROVIDERS.R2);
|
|
assert.strictEqual(retrieved.accountId, "acc123");
|
|
assert.strictEqual(retrieved.accessKeyId, "key123");
|
|
assert.strictEqual(retrieved.meta.label, "My R2");
|
|
assert.strictEqual(retrieved.meta.baseDomain, "https://pub.r2.dev");
|
|
|
|
// Ensure secret didn't leak into meta or something unintended, but main point is it came back
|
|
assert.strictEqual(retrieved.secretAccessKey, "secret123");
|
|
});
|
|
|
|
it("should save and retrieve a Generic S3 connection with extra fields", async () => {
|
|
// Verify Generic S3 Payload (endpoint, region, bucket)
|
|
if (!storageService.isUnlocked(mockPubkey)) {
|
|
await storageService.unlock(mockPubkey, { signer: mockSigner });
|
|
}
|
|
|
|
const connectionId = "s3-generic-1";
|
|
const payload = {
|
|
provider: PROVIDERS.GENERIC || "generic_s3",
|
|
endpoint: "https://minio.local:9000",
|
|
region: "us-east-1",
|
|
bucket: "my-bucket",
|
|
accessKeyId: "minio-key",
|
|
secretAccessKey: "minio-secret",
|
|
forcePathStyle: true
|
|
};
|
|
const meta = {
|
|
label: "Local Minio",
|
|
defaultForUploads: true
|
|
};
|
|
|
|
await storageService.saveConnection(mockPubkey, connectionId, payload, meta);
|
|
|
|
const retrieved = await storageService.getConnection(mockPubkey, connectionId);
|
|
|
|
assert.ok(retrieved, "Should return connection");
|
|
assert.strictEqual(retrieved.provider, "generic_s3");
|
|
assert.strictEqual(retrieved.endpoint, "https://minio.local:9000", "Endpoint should be preserved");
|
|
assert.strictEqual(retrieved.region, "us-east-1", "Region should be preserved");
|
|
assert.strictEqual(retrieved.bucket, "my-bucket", "Bucket should be preserved");
|
|
assert.strictEqual(retrieved.accessKeyId, "minio-key");
|
|
assert.strictEqual(retrieved.forcePathStyle, true, "ForcePathStyle should be preserved");
|
|
assert.strictEqual(retrieved.meta.defaultForUploads, true);
|
|
});
|
|
|
|
it("should fail to get connection if locked", async () => {
|
|
storageService.lock(mockPubkey);
|
|
assert.strictEqual(storageService.isUnlocked(mockPubkey), false);
|
|
|
|
await assert.rejects(
|
|
async () => await storageService.getConnection(mockPubkey, "r2-test-1"),
|
|
/Storage is locked/,
|
|
"Should throw if locked"
|
|
);
|
|
});
|
|
});
|