Upload images to your PDS and get instant CDN URLs via images.blue
at main 318 lines 8.3 kB view raw
1package auth 2 3import ( 4 "context" 5 "errors" 6 "testing" 7 8 "github.com/bluesky-social/indigo/atproto/auth/oauth" 9 "github.com/bluesky-social/indigo/atproto/syntax" 10) 11 12// MockKeyring is an in-memory implementation of Keyring for testing. 13type MockKeyring struct { 14 data map[string]map[string]string // service -> key -> value 15} 16 17func NewMockKeyring() *MockKeyring { 18 return &MockKeyring{ 19 data: make(map[string]map[string]string), 20 } 21} 22 23var ErrNotFound = errors.New("secret not found in keyring") 24 25func (m *MockKeyring) Get(service, key string) (string, error) { 26 if svc, ok := m.data[service]; ok { 27 if val, ok := svc[key]; ok { 28 return val, nil 29 } 30 } 31 return "", ErrNotFound 32} 33 34func (m *MockKeyring) Set(service, key, value string) error { 35 if _, ok := m.data[service]; !ok { 36 m.data[service] = make(map[string]string) 37 } 38 m.data[service][key] = value 39 return nil 40} 41 42func (m *MockKeyring) Delete(service, key string) error { 43 if svc, ok := m.data[service]; ok { 44 delete(svc, key) 45 } 46 return nil 47} 48 49func TestSessionKey(t *testing.T) { 50 did, _ := syntax.ParseDID("did:plc:test123") 51 key := sessionKey(did, "session-abc") 52 expected := "session:did:plc:test123:session-abc" 53 if key != expected { 54 t.Errorf("sessionKey() = %q, want %q", key, expected) 55 } 56} 57 58func TestAuthRequestKey(t *testing.T) { 59 key := authRequestKey("state123") 60 expected := "auth-request:state123" 61 if key != expected { 62 t.Errorf("authRequestKey() = %q, want %q", key, expected) 63 } 64} 65 66func TestSaveAndGetSession(t *testing.T) { 67 mock := NewMockKeyring() 68 store := NewKeyringAuthStoreWithKeyring(mock) 69 ctx := context.Background() 70 71 did, _ := syntax.ParseDID("did:plc:testuser") 72 sess := oauth.ClientSessionData{ 73 AccountDID: did, 74 SessionID: "test-session-id", 75 } 76 77 // Save session 78 if err := store.SaveSession(ctx, sess); err != nil { 79 t.Fatalf("SaveSession() error = %v", err) 80 } 81 82 // Get session 83 retrieved, err := store.GetSession(ctx, did, "test-session-id") 84 if err != nil { 85 t.Fatalf("GetSession() error = %v", err) 86 } 87 88 if retrieved.AccountDID.String() != did.String() { 89 t.Errorf("GetSession() DID = %q, want %q", retrieved.AccountDID.String(), did.String()) 90 } 91 if retrieved.SessionID != "test-session-id" { 92 t.Errorf("GetSession() SessionID = %q, want %q", retrieved.SessionID, "test-session-id") 93 } 94} 95 96func TestDeleteSession(t *testing.T) { 97 mock := NewMockKeyring() 98 store := NewKeyringAuthStoreWithKeyring(mock) 99 ctx := context.Background() 100 101 did, _ := syntax.ParseDID("did:plc:testuser") 102 sess := oauth.ClientSessionData{ 103 AccountDID: did, 104 SessionID: "test-session-id", 105 } 106 107 // Save then delete 108 store.SaveSession(ctx, sess) 109 if err := store.DeleteSession(ctx, did, "test-session-id"); err != nil { 110 t.Fatalf("DeleteSession() error = %v", err) 111 } 112 113 // Should not be found 114 _, err := store.GetSession(ctx, did, "test-session-id") 115 if err == nil { 116 t.Error("GetSession() expected error after delete, got nil") 117 } 118} 119 120func TestSaveAndGetAuthRequestInfo(t *testing.T) { 121 mock := NewMockKeyring() 122 store := NewKeyringAuthStoreWithKeyring(mock) 123 ctx := context.Background() 124 125 info := oauth.AuthRequestData{ 126 State: "test-state-123", 127 } 128 129 // Save auth request 130 if err := store.SaveAuthRequestInfo(ctx, info); err != nil { 131 t.Fatalf("SaveAuthRequestInfo() error = %v", err) 132 } 133 134 // Get auth request 135 retrieved, err := store.GetAuthRequestInfo(ctx, "test-state-123") 136 if err != nil { 137 t.Fatalf("GetAuthRequestInfo() error = %v", err) 138 } 139 140 if retrieved.State != "test-state-123" { 141 t.Errorf("GetAuthRequestInfo() State = %q, want %q", retrieved.State, "test-state-123") 142 } 143 144 // Pending auth state should also be set 145 state, err := store.GetPendingAuthState() 146 if err != nil { 147 t.Fatalf("GetPendingAuthState() error = %v", err) 148 } 149 if state != "test-state-123" { 150 t.Errorf("GetPendingAuthState() = %q, want %q", state, "test-state-123") 151 } 152} 153 154func TestDeleteAuthRequestInfo(t *testing.T) { 155 mock := NewMockKeyring() 156 store := NewKeyringAuthStoreWithKeyring(mock) 157 ctx := context.Background() 158 159 info := oauth.AuthRequestData{ 160 State: "test-state-456", 161 } 162 163 store.SaveAuthRequestInfo(ctx, info) 164 if err := store.DeleteAuthRequestInfo(ctx, "test-state-456"); err != nil { 165 t.Fatalf("DeleteAuthRequestInfo() error = %v", err) 166 } 167 168 _, err := store.GetAuthRequestInfo(ctx, "test-state-456") 169 if err == nil { 170 t.Error("GetAuthRequestInfo() expected error after delete, got nil") 171 } 172} 173 174func TestClearPendingAuthState(t *testing.T) { 175 mock := NewMockKeyring() 176 store := NewKeyringAuthStoreWithKeyring(mock) 177 ctx := context.Background() 178 179 info := oauth.AuthRequestData{ 180 State: "test-state-789", 181 } 182 store.SaveAuthRequestInfo(ctx, info) 183 184 if err := store.ClearPendingAuthState(); err != nil { 185 t.Fatalf("ClearPendingAuthState() error = %v", err) 186 } 187 188 _, err := store.GetPendingAuthState() 189 if err == nil { 190 t.Error("GetPendingAuthState() expected error after clear, got nil") 191 } 192} 193 194func TestSetAndGetCurrentSession(t *testing.T) { 195 mock := NewMockKeyring() 196 store := NewKeyringAuthStoreWithKeyring(mock) 197 ctx := context.Background() 198 199 did, _ := syntax.ParseDID("did:plc:currentuser") 200 sess := oauth.ClientSessionData{ 201 AccountDID: did, 202 SessionID: "current-session-id", 203 } 204 205 // Save the actual session first 206 if err := store.SaveSession(ctx, sess); err != nil { 207 t.Fatalf("SaveSession() error = %v", err) 208 } 209 210 // Set as current session 211 if err := store.SetCurrentSession(ctx, &sess); err != nil { 212 t.Fatalf("SetCurrentSession() error = %v", err) 213 } 214 215 // Get current session 216 retrieved, err := store.GetCurrentSession(ctx) 217 if err != nil { 218 t.Fatalf("GetCurrentSession() error = %v", err) 219 } 220 221 if retrieved.AccountDID.String() != did.String() { 222 t.Errorf("GetCurrentSession() DID = %q, want %q", retrieved.AccountDID.String(), did.String()) 223 } 224 if retrieved.SessionID != "current-session-id" { 225 t.Errorf("GetCurrentSession() SessionID = %q, want %q", retrieved.SessionID, "current-session-id") 226 } 227} 228 229func TestClearCurrentSession(t *testing.T) { 230 mock := NewMockKeyring() 231 store := NewKeyringAuthStoreWithKeyring(mock) 232 ctx := context.Background() 233 234 did, _ := syntax.ParseDID("did:plc:currentuser") 235 sess := oauth.ClientSessionData{ 236 AccountDID: did, 237 SessionID: "current-session-id", 238 } 239 240 store.SaveSession(ctx, sess) 241 store.SetCurrentSession(ctx, &sess) 242 243 if err := store.ClearCurrentSession(); err != nil { 244 t.Fatalf("ClearCurrentSession() error = %v", err) 245 } 246 247 _, err := store.GetCurrentSession(ctx) 248 if err == nil { 249 t.Error("GetCurrentSession() expected error after clear, got nil") 250 } 251} 252 253func TestSetAndGetLoginIdentifier(t *testing.T) { 254 mock := NewMockKeyring() 255 store := NewKeyringAuthStoreWithKeyring(mock) 256 257 if err := store.SetLoginIdentifier("user.bsky.social"); err != nil { 258 t.Fatalf("SetLoginIdentifier() error = %v", err) 259 } 260 261 id, err := store.GetLoginIdentifier() 262 if err != nil { 263 t.Fatalf("GetLoginIdentifier() error = %v", err) 264 } 265 266 if id != "user.bsky.social" { 267 t.Errorf("GetLoginIdentifier() = %q, want %q", id, "user.bsky.social") 268 } 269} 270 271func TestClearLoginIdentifier(t *testing.T) { 272 mock := NewMockKeyring() 273 store := NewKeyringAuthStoreWithKeyring(mock) 274 275 store.SetLoginIdentifier("user.bsky.social") 276 277 if err := store.ClearLoginIdentifier(); err != nil { 278 t.Fatalf("ClearLoginIdentifier() error = %v", err) 279 } 280 281 _, err := store.GetLoginIdentifier() 282 if err == nil { 283 t.Error("GetLoginIdentifier() expected error after clear, got nil") 284 } 285} 286 287func TestGetSessionNotFound(t *testing.T) { 288 mock := NewMockKeyring() 289 store := NewKeyringAuthStoreWithKeyring(mock) 290 ctx := context.Background() 291 292 did, _ := syntax.ParseDID("did:plc:nonexistent") 293 _, err := store.GetSession(ctx, did, "no-such-session") 294 if err == nil { 295 t.Error("GetSession() expected error for non-existent session, got nil") 296 } 297} 298 299func TestGetCurrentSessionNotFound(t *testing.T) { 300 mock := NewMockKeyring() 301 store := NewKeyringAuthStoreWithKeyring(mock) 302 ctx := context.Background() 303 304 _, err := store.GetCurrentSession(ctx) 305 if err == nil { 306 t.Error("GetCurrentSession() expected error when no current session, got nil") 307 } 308} 309 310func TestGetLoginIdentifierNotFound(t *testing.T) { 311 mock := NewMockKeyring() 312 store := NewKeyringAuthStoreWithKeyring(mock) 313 314 _, err := store.GetLoginIdentifier() 315 if err == nil { 316 t.Error("GetLoginIdentifier() expected error when not set, got nil") 317 } 318}