Upload images to your PDS and get instant CDN URLs via images.blue
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}