Framework-agnostic OAuth integration for AT Protocol (Bluesky) applications.
at main 138 lines 4.3 kB view raw
1import { assertEquals, assertRejects } from "@std/assert"; 2import { MemoryStorage } from "@tijs/atproto-storage"; 3import { NetworkError, SessionError } from "@tijs/oauth-client-deno"; 4import { OAuthSessions } from "./sessions.ts"; 5import type { OAuthClientInterface, SessionInterface } from "./types.ts"; 6import { noopLogger } from "./types.ts"; 7 8/** Create a fake session for testing */ 9function fakeSession(did: string): SessionInterface { 10 return { 11 did, 12 accessToken: "test-access-token", 13 pdsUrl: "https://pds.example.com", 14 timeUntilExpiry: 3600000, 15 makeRequest: () => Promise.resolve(new Response()), 16 toJSON: () => ({ did, accessToken: "test-access-token" }), 17 }; 18} 19 20/** Create a mock OAuth client */ 21function mockOAuthClient( 22 restoreFn: (sessionId: string) => Promise<SessionInterface | null>, 23): OAuthClientInterface { 24 return { 25 authorize: () => Promise.resolve(new URL("https://auth.example.com")), 26 callback: () => 27 Promise.resolve({ session: fakeSession("did:plc:test"), state: null }), 28 restore: restoreFn, 29 }; 30} 31 32Deno.test("getOAuthSession - returns session on success", async () => { 33 const session = fakeSession("did:plc:abc"); 34 const sessions = new OAuthSessions({ 35 oauthClient: mockOAuthClient(() => Promise.resolve(session)), 36 storage: new MemoryStorage(), 37 sessionTtl: 3600, 38 logger: noopLogger, 39 }); 40 41 const result = await sessions.getOAuthSession("did:plc:abc"); 42 assertEquals(result?.did, "did:plc:abc"); 43}); 44 45Deno.test("getOAuthSession - returns null when restore returns null", async () => { 46 const sessions = new OAuthSessions({ 47 oauthClient: mockOAuthClient(() => Promise.resolve(null)), 48 storage: new MemoryStorage(), 49 sessionTtl: 3600, 50 logger: noopLogger, 51 }); 52 53 const result = await sessions.getOAuthSession("did:plc:abc"); 54 assertEquals(result, null); 55}); 56 57Deno.test("getOAuthSession - returns null on SessionError (corrupt session)", async () => { 58 const storage = new MemoryStorage(); 59 await storage.set("session:did:plc:abc", { corrupt: "data" }); 60 61 const sessions = new OAuthSessions({ 62 oauthClient: mockOAuthClient(() => { 63 throw new SessionError("Failed to restore session: did:plc:abc"); 64 }), 65 storage, 66 sessionTtl: 3600, 67 logger: noopLogger, 68 }); 69 70 const result = await sessions.getOAuthSession("did:plc:abc"); 71 assertEquals(result, null); 72 73 // Should have cleaned up the dead session from storage 74 const stored = await storage.get("session:did:plc:abc"); 75 assertEquals(stored, null); 76}); 77 78Deno.test("getOAuthSession - returns null on token expiry errors", async () => { 79 const storage = new MemoryStorage(); 80 await storage.set("session:did:plc:abc", { expired: true }); 81 82 const sessions = new OAuthSessions({ 83 oauthClient: mockOAuthClient(() => { 84 throw new Error("Refresh token has expired"); 85 }), 86 storage, 87 sessionTtl: 3600, 88 logger: noopLogger, 89 }); 90 91 const result = await sessions.getOAuthSession("did:plc:abc"); 92 assertEquals(result, null); 93 94 // Should have cleaned up the dead session from storage 95 const stored = await storage.get("session:did:plc:abc"); 96 assertEquals(stored, null); 97}); 98 99Deno.test("getOAuthSession - re-throws NetworkError (transient)", async () => { 100 const sessions = new OAuthSessions({ 101 oauthClient: mockOAuthClient(() => { 102 throw new NetworkError("Connection refused"); 103 }), 104 storage: new MemoryStorage(), 105 sessionTtl: 3600, 106 logger: noopLogger, 107 }); 108 109 await assertRejects( 110 () => sessions.getOAuthSession("did:plc:abc"), 111 NetworkError, 112 ); 113}); 114 115Deno.test("getOAuthSession - cleanup failure does not prevent null return", async () => { 116 // Storage that fails on delete 117 const storage = new MemoryStorage(); 118 const originalDelete = storage.delete.bind(storage); 119 storage.delete = () => { 120 throw new Error("Storage delete failed"); 121 }; 122 123 const sessions = new OAuthSessions({ 124 oauthClient: mockOAuthClient(() => { 125 throw new SessionError("Failed to restore session: did:plc:abc"); 126 }), 127 storage, 128 sessionTtl: 3600, 129 logger: noopLogger, 130 }); 131 132 // Should still return null even if cleanup fails 133 const result = await sessions.getOAuthSession("did:plc:abc"); 134 assertEquals(result, null); 135 136 // Restore delete for cleanup 137 storage.delete = originalDelete; 138});