WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)

Add shared test helpers and refactor session tests

- Create tests/helpers/mock-data.ts with reusable mock session data
- Refactor session tests to use shared mock data
- Move mock keyring storage to module scope for cleaner access
- Remove biome-ignore suppressions by using proper types

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

markbennett.ca 5d08f90f ccebce98

verified
+66 -88
+38
tests/helpers/mock-data.ts
··· 1 + import type { AtpSessionData } from '@atproto/api'; 2 + import type { SessionMetadata } from '../../src/lib/session.js'; 3 + 4 + /** 5 + * Shared mock session data for tests 6 + */ 7 + export const mockSessionData: AtpSessionData = { 8 + did: 'did:plc:test123', 9 + handle: 'user.bsky.social', 10 + email: 'user@example.com', 11 + emailConfirmed: true, 12 + active: true, 13 + accessJwt: 'mock-access-token', 14 + refreshJwt: 'mock-refresh-token', 15 + }; 16 + 17 + /** 18 + * Alternative mock session with different DID 19 + */ 20 + export const mockSessionData2: AtpSessionData = { 21 + did: 'did:plc:test456', 22 + handle: 'another.bsky.social', 23 + email: 'another@example.com', 24 + emailConfirmed: true, 25 + active: true, 26 + accessJwt: 'mock-access-token-2', 27 + refreshJwt: 'mock-refresh-token-2', 28 + }; 29 + 30 + /** 31 + * Mock session metadata 32 + */ 33 + export const mockSessionMetadata: SessionMetadata = { 34 + handle: 'user.bsky.social', 35 + did: 'did:plc:test123', 36 + pds: 'https://bsky.social', 37 + lastUsed: '2024-01-01T00:00:00.000Z', 38 + };
+28 -88
tests/lib/session.test.ts
··· 1 1 import type { AtpSessionData } from '@atproto/api'; 2 2 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 3 3 import { 4 - type SessionMetadata, 5 4 clearCurrentSessionMetadata, 6 5 deleteSession, 7 6 getCurrentSessionMetadata, ··· 9 8 saveCurrentSessionMetadata, 10 9 saveSession, 11 10 } from '../../src/lib/session.js'; 11 + import { mockSessionData, mockSessionData2, mockSessionMetadata } from '../helpers/mock-data.js'; 12 12 13 13 // Mock @napi-rs/keyring 14 - vi.mock('@napi-rs/keyring', () => { 15 - const mockStorage = new Map<string, string>(); 14 + const mockKeyringStorage = new Map<string, string>(); 16 15 16 + vi.mock('@napi-rs/keyring', () => { 17 17 return { 18 18 AsyncEntry: vi.fn().mockImplementation((service: string, account: string) => { 19 19 const key = `${service}:${account}`; 20 20 21 21 return { 22 22 setPassword: vi.fn().mockImplementation(async (password: string) => { 23 - mockStorage.set(key, password); 23 + mockKeyringStorage.set(key, password); 24 24 }), 25 25 getPassword: vi.fn().mockImplementation(async () => { 26 - return mockStorage.get(key) || null; 26 + return mockKeyringStorage.get(key) || null; 27 27 }), 28 28 deleteCredential: vi.fn().mockImplementation(async () => { 29 - return mockStorage.delete(key); 29 + return mockKeyringStorage.delete(key); 30 30 }), 31 31 }; 32 32 }), 33 - // Export the storage for test access 34 - __mockStorage: mockStorage, 35 33 }; 36 34 }); 37 35 38 36 describe('Session Management', () => { 39 - beforeEach(async () => { 37 + beforeEach(() => { 40 38 // Clear mock storage before each test 41 - // biome-ignore lint/suspicious/noExplicitAny: accessing mock-specific property 42 - const keyring = (await import('@napi-rs/keyring')) as any; 43 - if (keyring.__mockStorage instanceof Map) { 44 - keyring.__mockStorage.clear(); 45 - } 39 + mockKeyringStorage.clear(); 46 40 vi.clearAllMocks(); 47 41 }); 48 42 49 - afterEach(async () => { 43 + afterEach(() => { 50 44 // Clean up after each test 51 - // biome-ignore lint/suspicious/noExplicitAny: accessing mock-specific property 52 - const keyring = (await import('@napi-rs/keyring')) as any; 53 - if (keyring.__mockStorage instanceof Map) { 54 - keyring.__mockStorage.clear(); 55 - } 45 + mockKeyringStorage.clear(); 56 46 }); 57 47 58 48 describe('saveSession', () => { 59 49 it('should save session to keychain using DID', async () => { 60 - const sessionData: AtpSessionData = { 61 - did: 'did:plc:test123', 62 - handle: 'user.bsky.social', 63 - email: 'user@example.com', 64 - emailConfirmed: true, 65 - active: true, 66 - accessJwt: 'token123', 67 - refreshJwt: 'refresh123', 68 - }; 69 - 70 - await saveSession(sessionData); 50 + await saveSession(mockSessionData); 71 51 72 52 // Verify session was stored 73 - const loaded = await loadSession('did:plc:test123'); 74 - expect(loaded).toEqual(sessionData); 53 + const loaded = await loadSession(mockSessionData.did); 54 + expect(loaded).toEqual(mockSessionData); 75 55 }); 76 56 77 57 it('should save and retrieve session with all required fields', async () => { 78 - const sessionData: AtpSessionData = { 79 - did: 'did:plc:test456', 80 - handle: 'user.bsky.social', 81 - email: 'user@example.com', 82 - emailConfirmed: true, 83 - active: true, 84 - accessJwt: 'token123', 85 - refreshJwt: 'refresh123', 86 - }; 87 - 88 - await saveSession(sessionData); 58 + await saveSession(mockSessionData2); 89 59 90 60 // Verify session was stored (keyed by DID) 91 - const loaded = await loadSession('did:plc:test456'); 92 - expect(loaded).toEqual(sessionData); 61 + const loaded = await loadSession(mockSessionData2.did); 62 + expect(loaded).toEqual(mockSessionData2); 93 63 }); 94 64 95 65 it('should throw error if session has no DID or handle', async () => { ··· 107 77 108 78 describe('loadSession', () => { 109 79 it('should load session from keychain', async () => { 110 - const sessionData: AtpSessionData = { 111 - did: 'did:plc:test123', 112 - handle: 'user.bsky.social', 113 - active: true, 114 - accessJwt: 'token123', 115 - refreshJwt: 'refresh123', 116 - }; 80 + await saveSession(mockSessionData); 81 + const result = await loadSession(mockSessionData.did); 117 82 118 - await saveSession(sessionData); 119 - const result = await loadSession('did:plc:test123'); 120 - 121 - expect(result).toEqual(sessionData); 83 + expect(result).toEqual(mockSessionData); 122 84 }); 123 85 124 86 it('should return null when session not found', async () => { ··· 129 91 130 92 describe('deleteSession', () => { 131 93 it('should delete session from keychain', async () => { 132 - const sessionData: AtpSessionData = { 133 - did: 'did:plc:test123', 134 - handle: 'user.bsky.social', 135 - active: true, 136 - accessJwt: 'token123', 137 - refreshJwt: 'refresh123', 138 - }; 139 - 140 - await saveSession(sessionData); 94 + await saveSession(mockSessionData); 141 95 142 96 // Verify session exists 143 - let loaded = await loadSession('did:plc:test123'); 144 - expect(loaded).toEqual(sessionData); 97 + let loaded = await loadSession(mockSessionData.did); 98 + expect(loaded).toEqual(mockSessionData); 145 99 146 100 // Delete session 147 - const deleted = await deleteSession('did:plc:test123'); 101 + const deleted = await deleteSession(mockSessionData.did); 148 102 expect(deleted).toBe(true); 149 103 150 104 // Verify session no longer exists 151 - loaded = await loadSession('did:plc:test123'); 105 + loaded = await loadSession(mockSessionData.did); 152 106 expect(loaded).toBeNull(); 153 107 }); 154 108 ··· 160 114 161 115 describe('session metadata', () => { 162 116 it('should save and load current session metadata', async () => { 163 - const metadata: SessionMetadata = { 164 - handle: 'user.bsky.social', 165 - did: 'did:plc:test123', 166 - pds: 'https://bsky.social', 167 - lastUsed: new Date().toISOString(), 168 - }; 169 - 170 - await saveCurrentSessionMetadata(metadata); 117 + await saveCurrentSessionMetadata(mockSessionMetadata); 171 118 const result = await getCurrentSessionMetadata(); 172 119 173 - expect(result).toEqual(metadata); 120 + expect(result).toEqual(mockSessionMetadata); 174 121 }); 175 122 176 123 it('should return null when no metadata exists', async () => { ··· 179 126 }); 180 127 181 128 it('should clear current session metadata', async () => { 182 - const metadata: SessionMetadata = { 183 - handle: 'user.bsky.social', 184 - did: 'did:plc:test123', 185 - pds: 'https://bsky.social', 186 - lastUsed: new Date().toISOString(), 187 - }; 188 - 189 - await saveCurrentSessionMetadata(metadata); 129 + await saveCurrentSessionMetadata(mockSessionMetadata); 190 130 191 131 // Verify metadata exists 192 132 let result = await getCurrentSessionMetadata(); 193 - expect(result).toEqual(metadata); 133 + expect(result).toEqual(mockSessionMetadata); 194 134 195 135 // Clear metadata 196 136 await clearCurrentSessionMetadata();