Files
bitvid/tests/ui/storageService.test.mjs
T
google-labs-jules[bot]andPR0M3TH3AN 7fdcecfc64 chore(ai): CI health fixes (test isolation)
Refactored tests to prevent global pollution. Cleaned up global variables in after hooks.
- tests/ui/uploadModal-integration.test.mjs: Save and restore global window, document, fetch, etc.
- tests/ui/storageService.test.mjs: Save and restore global indexedDB, crypto, etc.
- Added daily agent report.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-16 02:54:16 +00:00

199 lines
7.9 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";
const originalGlobals = {};
// 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 () => {
originalGlobals.window = global.window;
originalGlobals.indexedDB = global.indexedDB;
originalGlobals.globalThisIndexedDB = globalThis.indexedDB;
originalGlobals.IDBKeyRange = global.IDBKeyRange;
originalGlobals.globalThisIDBKeyRange = globalThis.IDBKeyRange;
originalGlobals.crypto = global.crypto;
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();
if ('window' in originalGlobals) global.window = originalGlobals.window; else delete global.window;
if ('indexedDB' in originalGlobals) global.indexedDB = originalGlobals.indexedDB; else delete global.indexedDB;
if ('globalThisIndexedDB' in originalGlobals) globalThis.indexedDB = originalGlobals.globalThisIndexedDB; else delete globalThis.indexedDB;
if ('IDBKeyRange' in originalGlobals) global.IDBKeyRange = originalGlobals.IDBKeyRange; else delete global.IDBKeyRange;
if ('globalThisIDBKeyRange' in originalGlobals) globalThis.IDBKeyRange = originalGlobals.globalThisIDBKeyRange; else delete globalThis.IDBKeyRange;
if ('crypto' in originalGlobals) {
if (global.crypto !== originalGlobals.crypto) {
try {
global.crypto = originalGlobals.crypto;
} catch (e) {
// If read-only/getter, use defineProperty
Object.defineProperty(global, 'crypto', { value: originalGlobals.crypto, writable: true, configurable: true });
}
}
} else {
delete global.crypto;
}
});
// 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"
);
});
});