mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
This change adds comprehensive unit and integration tests to cover: 1. `signerKeyManager.js`: Verification of key derivation from signatures and passphrases. 2. `storageService.js`: Tests for saving, retrieving, and decrypting connections, specifically verifying that generic S3 payloads (endpoint, region, bucket) are preserved alongside R2 payloads. 3. `UploadModal.js`: Integration tests mocking the storage service to verify that R2 connection details correctly map to the UI input fields and that the "Unlock" flow updates the UI state. It also introduces `fake-indexeddb` as a dev dependency to enable reliable testing of IndexedDB interactions in the Node/JSDOM test environment.
168 lines
6.3 KiB
JavaScript
168 lines
6.3 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"
|
|
};
|
|
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.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"
|
|
);
|
|
});
|
|
});
|