mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 12:52:26 +00:00
feat(my-videos): bucket object listing for reconciliation (Phase 2, step 2)
Adds the bucket-enumeration primitive the My Videos tab needs to find orphan and
missing files:
- s3 layer: ListObjectsV2Command wired into the SDK wrapper + a paginated
listObjects({ s3, bucket, prefix, maxKeys }) helper (follows continuation
tokens, bounded by maxKeys).
- r2Service.listVideoStorageObjects({ npub|pubkey }) — unlock-gated, lists under
the user's own u/<npub>/ prefix; returns { ok, reason, keys }. Body lives in
r2ServiceHelpers (with a shared buildProviderClient) so r2Service stays under
its file-size budget; ensureS3SdkLoaded is injectable via ctx for testing.
Tests + mutation-verified: never lists while locked (storage-locked, no call),
no-connection / missing-bucket gating, and unlocked listing is scoped to the
user's own u/<npub>/ prefix (empty-prefix mutation fails). Removing the lock gate
or widening the prefix each fail a scenario.
Build + full lint green (r2Service back under limit).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
bbb8dd0fd8
commit
513f75e4da
6 files changed
+233
-2
No files matched your search
@@ -0,0 +1,83 @@
|
||||
// listVideoStorageObjects backs the My Videos tab's bucket reconciliation. It is
|
||||
// UNLOCK-GATED — it must never attempt a bucket listing without credentials —
|
||||
// and when unlocked it must list under the user's own prefix (u/<npub>/) so it
|
||||
// can't enumerate (or later act on) anything outside the user's space.
|
||||
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { listVideoStorageObjects } from "../js/services/r2ServiceHelpers.js";
|
||||
|
||||
const NPUB = "npub1exampleexampleexample";
|
||||
|
||||
function makeCtx(overrides = {}) {
|
||||
const calls = { listObjects: [] };
|
||||
const ctx = {
|
||||
resolveConnection: async () => null,
|
||||
ensureS3SdkLoaded: async () => {},
|
||||
makeR2Client: () => ({}),
|
||||
makeS3Client: () => ({}),
|
||||
listObjects: async (args) => {
|
||||
calls.listObjects.push(args);
|
||||
return ["u/npub1example/a/v.mp4", "u/npub1example/b/v.mp4"];
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
return { ctx, calls };
|
||||
}
|
||||
|
||||
const UNLOCKED = {
|
||||
provider: "cloudflare_r2",
|
||||
accountId: "acc",
|
||||
bucket: "bkt",
|
||||
accessKeyId: "AK",
|
||||
secretAccessKey: "SK",
|
||||
};
|
||||
|
||||
test("locked storage never lists (returns storage-locked)", async () => {
|
||||
const { ctx, calls } = makeCtx();
|
||||
const result = await listVideoStorageObjects(ctx, {
|
||||
npub: NPUB,
|
||||
credentials: { ...UNLOCKED, accessKeyId: "", secretAccessKey: "" },
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.reason, "storage-locked");
|
||||
assert.equal(calls.listObjects.length, 0, "must not attempt a listing while locked");
|
||||
});
|
||||
|
||||
test("no resolvable connection returns no-connection", async () => {
|
||||
const { ctx, calls } = makeCtx();
|
||||
const result = await listVideoStorageObjects(ctx, { npub: NPUB });
|
||||
assert.equal(result.reason, "no-connection");
|
||||
assert.equal(calls.listObjects.length, 0);
|
||||
});
|
||||
|
||||
test("missing bucket is reported and not listed", async () => {
|
||||
const { ctx, calls } = makeCtx();
|
||||
const result = await listVideoStorageObjects(ctx, {
|
||||
npub: NPUB,
|
||||
credentials: { ...UNLOCKED, bucket: "" },
|
||||
});
|
||||
assert.equal(result.reason, "missing-bucket");
|
||||
assert.equal(calls.listObjects.length, 0);
|
||||
});
|
||||
|
||||
test("unlocked storage lists under the user's own u/<npub>/ prefix", async () => {
|
||||
const { ctx, calls } = makeCtx();
|
||||
const result = await listVideoStorageObjects(ctx, {
|
||||
npub: NPUB,
|
||||
credentials: UNLOCKED,
|
||||
});
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(calls.listObjects.length, 1);
|
||||
assert.equal(
|
||||
calls.listObjects[0].prefix,
|
||||
`u/${NPUB}/`,
|
||||
"must scope the listing to the user's own prefix",
|
||||
);
|
||||
assert.equal(calls.listObjects[0].bucket, "bkt");
|
||||
assert.deepEqual(result.keys, [
|
||||
"u/npub1example/a/v.mp4",
|
||||
"u/npub1example/b/v.mp4",
|
||||
]);
|
||||
});
|
||||
Reference in new issue
Block a user