Files
bitvid/tests/authService.test.mjs
T

209 lines
7.3 KiB
JavaScript

import "./test-helpers/setup-localstorage.mjs";
import { test, describe, beforeEach, afterEach, mock } from "node:test";
import assert from "node:assert/strict";
import AuthService from "../js/services/authService.js";
import {
getPubkey,
setPubkey,
getCurrentUserNpub,
setCurrentUserNpub,
} from "../js/state/appState.js";
import {
getSavedProfiles,
mutateSavedProfiles,
getActiveProfilePubkey,
setActiveProfilePubkey,
} from "../js/state/cache.js";
// Mock dependencies
const mockNostrClient = {
pubkey: null,
logout: mock.fn(),
pool: { list: mock.fn(async () => []) },
relays: [],
handleEvent: mock.fn(),
};
const mockUserBlocks = {
loadBlocks: mock.fn(async () => true),
reset: mock.fn(),
};
const mockRelayManager = {
loadRelayList: mock.fn(async () => true),
reset: mock.fn(),
};
const mockAccessControl = {
isLockdownActive: mock.fn(() => false),
isAdminEditor: mock.fn(() => false),
canAccess: mock.fn(() => true),
isBlacklisted: mock.fn(() => false),
};
const mockLogger = mock.fn();
const mockAuthProvider = {
login: mock.fn(async () => ({ pubkey: "0000000000000000000000000000000000000000000000000000000000000001", authType: "nip07" })),
};
const mockGetAuthProvider = mock.fn((id) => mockAuthProvider);
describe("AuthService Coverage", () => {
let authService;
beforeEach(() => {
// Reset global state
setPubkey(null);
setCurrentUserNpub(null);
setActiveProfilePubkey(null, { persist: false });
mutateSavedProfiles(() => [], { persist: false, persistActive: false });
// Reset mocks
mockNostrClient.logout.mock.resetCalls();
mockUserBlocks.loadBlocks.mock.resetCalls();
mockUserBlocks.reset.mock.resetCalls();
mockRelayManager.loadRelayList.mock.resetCalls();
mockRelayManager.reset.mock.resetCalls();
mockAccessControl.isLockdownActive.mock.resetCalls();
mockAuthProvider.login.mock.resetCalls();
authService = new AuthService({
nostrClient: mockNostrClient,
userBlocks: mockUserBlocks,
relayManager: mockRelayManager,
logger: mockLogger,
accessControl: mockAccessControl,
getAuthProvider: mockGetAuthProvider,
});
});
describe("login", () => {
test("successfully logs in with valid pubkey", async () => {
const pubkey = "0000000000000000000000000000000000000000000000000000000000000001";
const result = await authService.login(pubkey);
assert.equal(getPubkey(), pubkey);
assert.equal(getActiveProfilePubkey(), pubkey);
assert.equal(result.pubkey, pubkey);
const saved = getSavedProfiles();
assert.equal(saved.length, 1);
assert.equal(saved[0].pubkey, pubkey);
});
test("updates global state and calls post-login hooks", async () => {
const pubkey = "0000000000000000000000000000000000000000000000000000000000000002";
await authService.login(pubkey);
assert.strictEqual(mockRelayManager.loadRelayList.mock.callCount(), 1);
// Blocks are deferred by default in login flow, so loadBlocks should not be called immediately
assert.strictEqual(mockUserBlocks.loadBlocks.mock.callCount(), 0);
});
test("throws if lockdown is active and user is not admin", async () => {
mockAccessControl.isLockdownActive.mock.mockImplementation(() => true);
mockAccessControl.isAdminEditor.mock.mockImplementation(() => false);
await assert.rejects(
async () => await authService.login("0000000000000000000000000000000000000000000000000000000000000003"),
{ code: "site-lockdown" }
);
});
test("allows login if lockdown is active but user is admin", async () => {
mockAccessControl.isLockdownActive.mock.mockImplementation(() => true);
mockAccessControl.isAdminEditor.mock.mockImplementation(() => true);
const pubkey = "0000000000000000000000000000000000000000000000000000000000000004";
await authService.login(pubkey);
assert.equal(getPubkey(), pubkey);
});
});
describe("logout", () => {
test("clears state and calls reset on dependencies", async () => {
// Setup initial state
const pubkey = "0000000000000000000000000000000000000000000000000000000000000005";
await authService.login(pubkey);
await authService.logout();
assert.equal(getPubkey(), null);
assert.equal(getActiveProfilePubkey(), null);
assert.strictEqual(mockNostrClient.logout.mock.callCount(), 1);
assert.strictEqual(mockUserBlocks.reset.mock.callCount(), 1);
assert.strictEqual(mockRelayManager.reset.mock.callCount(), 1);
});
});
describe("requestLogin", () => {
test("uses provider to login", async () => {
const pubkey = "0000000000000000000000000000000000000000000000000000000000000001";
mockAuthProvider.login.mock.mockImplementation(async () => ({ pubkey, authType: "nip07" }));
const result = await authService.requestLogin({ providerId: "nip07" });
assert.equal(result.pubkey, pubkey);
assert.strictEqual(mockAuthProvider.login.mock.callCount(), 1);
// requestLogin calls login() internally if autoApply is true (default)
assert.equal(getPubkey(), pubkey);
});
test("does not call login if autoApply is false", async () => {
const pubkey = "0000000000000000000000000000000000000000000000000000000000000001";
mockAuthProvider.login.mock.mockImplementation(async () => ({ pubkey, authType: "nip07" }));
await authService.requestLogin({ providerId: "nip07", autoApply: false });
assert.equal(getPubkey(), null);
});
});
describe("switchProfile", () => {
test("switches to an existing profile", async () => {
const pubkey1 = "0000000000000000000000000000000000000000000000000000000000000001";
const pubkey2 = "0000000000000000000000000000000000000000000000000000000000000002";
await authService.login(pubkey1);
// Reset mocks to clear login calls
mockRelayManager.loadRelayList.mock.resetCalls();
// Mock login for switch
mockAuthProvider.login.mock.mockImplementation(async () => ({ pubkey: pubkey2, authType: "nip07" }));
await authService.switchProfile(pubkey2, { providerId: "nip07" });
assert.equal(getPubkey(), pubkey2);
assert.equal(getActiveProfilePubkey(), pubkey2);
});
test("reorders saved profiles on switch", async () => {
const pubkey1 = "0000000000000000000000000000000000000000000000000000000000000001";
const pubkey2 = "0000000000000000000000000000000000000000000000000000000000000002";
await authService.login(pubkey1);
await authService.login(pubkey2);
// At this point pubkey2 is active and likely last or first in savedProfiles depending on implementation.
// mutateSavedProfiles pushes to end if new.
// So [pubkey1, pubkey2].
let saved = getSavedProfiles();
assert.equal(saved.length, 2);
assert.equal(saved[1].pubkey, pubkey2);
// Switch back to pubkey1
mockAuthProvider.login.mock.mockImplementation(async () => ({ pubkey: pubkey1, authType: "nip07" }));
await authService.switchProfile(pubkey1);
saved = getSavedProfiles();
// Should be moved to front?
// Code says: if (index > 0) { const [moved] = draft.splice(index, 1); draft.unshift(moved); }
// So yes, moved to front.
assert.equal(saved[0].pubkey, pubkey1);
});
});
});