~james/core

core/web/src/lib/auth.svelte.test.ts -rw-r--r-- 3.7 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import type { Did } from "@atcute/lexicons/syntax";
import type { Session } from "@atcute/oauth-browser-client";
import { finalizeAuthorization, getSession } from "@atcute/oauth-browser-client";
import { flushSync } from "svelte";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createAuth, type Auth } from "#lib/auth.svelte.ts";

vi.mock("@atcute/oauth-browser-client", async (importOriginal) => {
	const mod = await importOriginal<Record<string, unknown>>();
	class MockOAuthUserAgent {
		readonly sub: string;
		getSession = vi.fn(async () => this.session);
		signOut = vi.fn(async () => {});
		constructor(readonly session: { info: { sub: string } }) {
			this.sub = session.info.sub;
		}
	}
	return {
		...mod,
		OAuthUserAgent: MockOAuthUserAgent,
		configureOAuth: vi.fn(),
		finalizeAuthorization: vi.fn(),
		getSession: vi.fn(),
		listStoredSessions: vi.fn(() => []),
		deleteStoredSession: vi.fn()
	};
});

const mockedFinalize = vi.mocked(finalizeAuthorization);
const mockedGetSession = vi.mocked(getSession);

const alice = "did:plc:alice" as Did;
const bob = "did:plc:bob" as Did;
const liveSession = (did: Did): Session => ({ info: { sub: did } }) as unknown as Session;
const response = { code: "c", state: "sid", iss: "https://pds.example" };

const watch = (read: () => unknown) => {
	let runs = 0;
	const stop = $effect.root(() => {
		$effect(() => {
			read();
			runs++;
		});
	});
	flushSync();
	return {
		get runs() {
			flushSync();
			return runs;
		},
		stop
	};
};

const settle = (auth: Auth) => vi.waitFor(() => expect(auth.state.kind).toBe("authenticated"));

beforeEach(() => {
	vi.clearAllMocks();
	localStorage.clear();
	vi.stubGlobal(
		"fetch",
		vi.fn(async (input: URL) => {
			const did = new URL(input).searchParams.get("identifier");
			return Response.json({ did, handle: `${did?.slice(8)}.example` });
		})
	);
});

afterEach(() => {
	vi.unstubAllGlobals();
});

describe("auth readers", () => {
	it("don't rerun while the same account finishes signing in", async () => {
		mockedFinalize.mockResolvedValue({ session: liveSession(alice), state: null });
		const auth = createAuth("http://bobbin.example", { did: alice, handle: "alice.example" });
		const did = watch(() => auth.currentDid);
		const agent = watch(() => auth.agent);

		await auth.completePendingSignIn("#state=sid&code=c");
		await settle(auth);

		expect(did.runs).toBe(1);
		// once with no agent yet, once when it arrives
		expect(agent.runs).toBe(2);
		did.stop();
		agent.stop();
	});

	it("rerun when the account changes or signs out", async () => {
		mockedFinalize.mockResolvedValueOnce({ session: liveSession(alice), state: null });
		const auth = createAuth("http://bobbin.example", null);
		await auth.completeAuthorization(alice, response);
		await settle(auth);
		const did = watch(() => auth.currentDid);

		mockedFinalize.mockResolvedValueOnce({ session: liveSession(bob), state: null });
		await auth.completeAuthorization(bob, response);
		await settle(auth);
		expect(auth.currentDid).toBe(bob);
		expect(did.runs).toBe(2);

		await auth.signOut();
		expect(auth.currentDid).toBeNull();
		expect(did.runs).toBe(3);
		did.stop();
	});

	it("don't rerun an effect that asked agentFor for an agent", async () => {
		mockedGetSession.mockResolvedValue(liveSession(alice));
		const auth = createAuth("http://bobbin.example", null);
		let pending: Promise<unknown> = Promise.resolve();
		const effect = watch(() => {
			pending = auth.agentFor(alice, { relogin: false });
		});
		await pending;

		mockedFinalize.mockResolvedValue({ session: liveSession(bob), state: null });
		await auth.completeAuthorization(bob, response);
		await settle(auth);

		expect(effect.runs).toBe(1);
		effect.stop();
	});
});