mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #1999 from PR0M3TH3AN/codex/add-test-stubs-for-permission-scenarios
Add tests for permission deferral during NIP-07 login and list loads
This commit is contained in:
4 files changed
+324
No files matched your search
@@ -221,6 +221,90 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"load defers permission-required decrypts until explicitly enabled",
|
||||
{ concurrency: false },
|
||||
async () => {
|
||||
const pubkey = "e".repeat(64);
|
||||
|
||||
const relayUrls = ["wss://relay-permissions.example"];
|
||||
const originalRelayEntries = relayManager.getEntries();
|
||||
relayManager.setEntries(relayUrls.map(url => ({ url, mode: "both" })), { allowEmpty: false, updateClient: false });
|
||||
|
||||
const originalFetchIncremental = nostrClient.fetchListIncrementally;
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
|
||||
? [...nostrClient.writeRelays]
|
||||
: nostrClient.writeRelays;
|
||||
const originalWindowNostr = window.nostr;
|
||||
|
||||
const event = {
|
||||
id: "pref-permission",
|
||||
created_at: 400,
|
||||
pubkey,
|
||||
content: "cipher-permission",
|
||||
tags: [["encrypted", "nip04"]],
|
||||
};
|
||||
|
||||
let fetchCalls = 0;
|
||||
nostrClient.fetchListIncrementally = async () => {
|
||||
fetchCalls += 1;
|
||||
return fetchCalls === 1 ? [event] : [];
|
||||
};
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.writeRelays = relayUrls;
|
||||
|
||||
const decryptCalls = [];
|
||||
window.nostr = {
|
||||
nip04: {
|
||||
decrypt: async () => {
|
||||
decryptCalls.push("nip04");
|
||||
return JSON.stringify({
|
||||
version: 1,
|
||||
interests: ["Late"],
|
||||
disinterests: [],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
clearActiveSigner();
|
||||
|
||||
try {
|
||||
await hashtagPreferences.load(pubkey, { allowPermissionPrompt: false });
|
||||
|
||||
assert.equal(
|
||||
decryptCalls.length,
|
||||
0,
|
||||
"decryption should be skipped when permissions are deferred",
|
||||
);
|
||||
assert.equal(
|
||||
hashtagPreferences.lastLoadError?.code,
|
||||
"hashtag-preferences-permission-required",
|
||||
"permission-required errors should be captured for deferred prompts",
|
||||
);
|
||||
assert.deepEqual(hashtagPreferences.getInterests(), []);
|
||||
|
||||
await hashtagPreferences.load(pubkey, { allowPermissionPrompt: true });
|
||||
|
||||
assert.equal(
|
||||
decryptCalls.length,
|
||||
1,
|
||||
"explicit permission prompts should retry decryption",
|
||||
);
|
||||
assert.deepEqual(hashtagPreferences.getInterests(), ["late"]);
|
||||
} finally {
|
||||
relayManager.setEntries(originalRelayEntries, { allowEmpty: true, updateClient: false });
|
||||
nostrClient.fetchListIncrementally = originalFetchIncremental;
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.writeRelays = originalWriteRelays;
|
||||
window.nostr = originalWindowNostr;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"interest and disinterest lists remain exclusive",
|
||||
{ concurrency: false },
|
||||
|
||||
@@ -456,6 +456,42 @@ test("NIP-07 login quickly retries when a permission payload stalls", async () =
|
||||
}
|
||||
});
|
||||
|
||||
test("NIP-07 login does not wait for deferred permission grants", async () => {
|
||||
clearStoredPermissions();
|
||||
const env = setupLoginEnvironment();
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
let completionPromise;
|
||||
|
||||
nostrClient.ensureExtensionPermissions = async () => {
|
||||
completionPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("permission denied")), 300);
|
||||
});
|
||||
return { ok: true, completionPromise };
|
||||
};
|
||||
|
||||
try {
|
||||
const start = Date.now();
|
||||
const result = await nip07Provider.login({ nostrClient });
|
||||
const duration = Date.now() - start;
|
||||
assert.equal(result.pubkey, HEX_PUBKEY);
|
||||
assert.ok(
|
||||
duration < 200,
|
||||
`login should resolve before deferred permission grants (duration: ${duration}ms)`,
|
||||
);
|
||||
assert.ok(completionPromise, "completionPromise should be captured");
|
||||
await assert.rejects(
|
||||
completionPromise,
|
||||
/permission denied/,
|
||||
"deferred permission grants should still reject",
|
||||
);
|
||||
} finally {
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
env.restore();
|
||||
nostrClient.logout();
|
||||
clearStoredPermissions();
|
||||
}
|
||||
});
|
||||
|
||||
test("NIP-07 login surfaces enable permission errors", async () => {
|
||||
clearStoredPermissions();
|
||||
const env = setupLoginEnvironment({
|
||||
|
||||
@@ -541,6 +541,114 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"loadSubscriptions retries permission-required decrypts only when enabled",
|
||||
async () => {
|
||||
const SubscriptionsManager = subscriptions.constructor;
|
||||
const manager = new SubscriptionsManager();
|
||||
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
|
||||
? [...nostrClient.writeRelays]
|
||||
: nostrClient.writeRelays;
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalFetchIncremental = nostrClient.fetchListIncrementally;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
const originalEnsureSubscription = manager.ensureSubscriptionListSubscription;
|
||||
|
||||
const hadWindow = typeof globalThis.window !== "undefined";
|
||||
if (!hadWindow) {
|
||||
globalThis.window = {};
|
||||
}
|
||||
const originalWindowNostr = globalThis.window.nostr;
|
||||
|
||||
const relayUrls = ["wss://relay-permission.example"];
|
||||
nostrClient.relays = relayUrls;
|
||||
nostrClient.writeRelays = relayUrls;
|
||||
|
||||
const originalRelayEntries = relayManager.getEntries();
|
||||
relayManager.setEntries(relayUrls.map(url => ({ url, mode: "both" })), { allowEmpty: false, updateClient: false });
|
||||
|
||||
const actor = "f".repeat(64);
|
||||
const event = {
|
||||
id: "event-permission",
|
||||
created_at: 100,
|
||||
pubkey: actor,
|
||||
content: "cipher-permission",
|
||||
tags: [["encrypted", "nip04"]],
|
||||
};
|
||||
|
||||
nostrClient.pool = {};
|
||||
nostrClient.fetchListIncrementally = async () => [event];
|
||||
nostrClient.ensureExtensionPermissions = async () => ({ ok: true });
|
||||
manager.ensureSubscriptionListSubscription = () => null;
|
||||
clearActiveSigner();
|
||||
|
||||
const decryptCalls = [];
|
||||
manager.decryptSubscriptionEvent = async (_event, _pubkey, options) => {
|
||||
decryptCalls.push(options);
|
||||
if (!options?.allowPermissionPrompt) {
|
||||
const error = new Error("permissions required");
|
||||
error.code = "subscriptions-permission-required";
|
||||
return { ok: false, error };
|
||||
}
|
||||
return { ok: true, plaintext: JSON.stringify([["p", "pub-allowed"]]) };
|
||||
};
|
||||
|
||||
try {
|
||||
await manager.updateFromRelays(actor, { allowPermissionPrompt: false });
|
||||
|
||||
assert.equal(
|
||||
decryptCalls.length,
|
||||
1,
|
||||
"should attempt decryption once without retry",
|
||||
);
|
||||
assert.equal(
|
||||
manager.subscribedPubkeys.size,
|
||||
0,
|
||||
"subscriptions should remain empty when permissions are deferred",
|
||||
);
|
||||
|
||||
await manager.updateFromRelays(actor, { allowPermissionPrompt: true });
|
||||
|
||||
assert.equal(
|
||||
decryptCalls.length,
|
||||
2,
|
||||
"explicit permission prompts should trigger a retry",
|
||||
);
|
||||
assert.deepEqual(
|
||||
Array.from(manager.subscribedPubkeys),
|
||||
["pub-allowed"],
|
||||
"retry with permission prompts should populate subscriptions",
|
||||
);
|
||||
} finally {
|
||||
relayManager.setEntries(originalRelayEntries, { allowEmpty: false, updateClient: false });
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.writeRelays = originalWriteRelays;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.fetchListIncrementally = originalFetchIncremental;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
manager.ensureSubscriptionListSubscription = originalEnsureSubscription;
|
||||
setActiveSigner(originalSigner);
|
||||
if (typeof originalWindowNostr === "undefined") {
|
||||
delete globalThis.window.nostr;
|
||||
} else {
|
||||
globalThis.window.nostr = originalWindowNostr;
|
||||
}
|
||||
if (!hadWindow) {
|
||||
delete globalThis.window;
|
||||
}
|
||||
localStorage.clear();
|
||||
profileCache.memoryCache.clear();
|
||||
profileCache.activePubkey = null;
|
||||
clearActiveSigner();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"showSubscriptionVideos waits for nostrService warm-up and refreshes after updates",
|
||||
async () => {
|
||||
|
||||
@@ -695,6 +695,102 @@ await (async () => {
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const actor = "9".repeat(64);
|
||||
const relays = ["wss://relay-login-mode.example"];
|
||||
|
||||
const originalRelayEntries = relayManager.getEntries();
|
||||
relayManager.setEntries(relays.map(url => ({ url, mode: "both" })), { allowEmpty: false, updateClient: false });
|
||||
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalWriteRelays = Array.isArray(nostrClient.writeRelays)
|
||||
? [...nostrClient.writeRelays]
|
||||
: nostrClient.writeRelays;
|
||||
const originalEnsurePermissions = nostrClient.ensureExtensionPermissions;
|
||||
const originalSigner = getActiveSigner();
|
||||
const originalNostr = window.nostr;
|
||||
const originalBlocked = new Set(userBlocks.blockedPubkeys);
|
||||
const originalBlockEventId = userBlocks.blockEventId;
|
||||
const originalBlockEventCreatedAt = userBlocks.blockEventCreatedAt;
|
||||
const originalMuteEventId = userBlocks.muteEventId;
|
||||
const originalMuteEventCreatedAt = userBlocks.muteEventCreatedAt;
|
||||
const originalLoaded = userBlocks.loaded;
|
||||
|
||||
let permissionRequests = 0;
|
||||
nostrClient.ensureExtensionPermissions = async () => {
|
||||
permissionRequests += 1;
|
||||
return { ok: true };
|
||||
};
|
||||
|
||||
nostrClient.relays = relays;
|
||||
nostrClient.writeRelays = relays;
|
||||
nostrClient.pool = {
|
||||
list: async () => [
|
||||
{
|
||||
id: "event-login",
|
||||
kind: 10000,
|
||||
created_at: 100,
|
||||
pubkey: actor,
|
||||
content: "cipher-login",
|
||||
tags: [["encrypted", "nip04"]],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const statusEvents = [];
|
||||
const unsubscribeStatus = userBlocks.on(USER_BLOCK_EVENTS.STATUS, (detail) => {
|
||||
statusEvents.push(detail);
|
||||
});
|
||||
|
||||
clearActiveSigner();
|
||||
window.nostr = undefined;
|
||||
|
||||
try {
|
||||
const start = Date.now();
|
||||
await userBlocks.loadBlocks(actor, { allowPermissionPrompt: false });
|
||||
const duration = Date.now() - start;
|
||||
|
||||
assert.equal(
|
||||
permissionRequests,
|
||||
0,
|
||||
"login-mode load should not request extension permissions",
|
||||
);
|
||||
assert.ok(
|
||||
duration < 200,
|
||||
`login-mode load should settle quickly (duration: ${duration}ms)`,
|
||||
);
|
||||
|
||||
const permissionStatus = statusEvents.find(
|
||||
(detail) => detail?.status === "permission-required",
|
||||
);
|
||||
assert.ok(permissionStatus, "login-mode load should emit permission-required status");
|
||||
assert.equal(permissionStatus.allowPermissionPrompt, false);
|
||||
} finally {
|
||||
if (typeof unsubscribeStatus === "function") {
|
||||
unsubscribeStatus();
|
||||
}
|
||||
relayManager.setEntries(originalRelayEntries, { allowEmpty: false, updateClient: false });
|
||||
userBlocks.blockedPubkeys = originalBlocked;
|
||||
userBlocks.blockEventId = originalBlockEventId;
|
||||
userBlocks.blockEventCreatedAt = originalBlockEventCreatedAt;
|
||||
userBlocks.muteEventId = originalMuteEventId;
|
||||
userBlocks.muteEventCreatedAt = originalMuteEventCreatedAt;
|
||||
userBlocks.loaded = originalLoaded;
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.writeRelays = originalWriteRelays;
|
||||
nostrClient.ensureExtensionPermissions = originalEnsurePermissions;
|
||||
window.nostr = originalNostr;
|
||||
clearActiveSigner();
|
||||
if (originalSigner) {
|
||||
setActiveSigner(originalSigner);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
await (async () => {
|
||||
const actor = "1".repeat(64);
|
||||
const blocked = "2".repeat(64);
|
||||
|
||||
Reference in new issue
Block a user