Monorepo for Tangled
1package oauth
2
3import (
4 "testing"
5)
6
7func TestAccountRegistry_AddAccount(t *testing.T) {
8 tests := []struct {
9 name string
10 initial []AccountInfo
11 addDid string
12 addHandle string
13 addSessionId string
14 wantErr error
15 wantLen int
16 wantSessionId string
17 }{
18 {
19 name: "add first account",
20 initial: []AccountInfo{},
21 addDid: "did:plc:abc123",
22 addHandle: "alice.bsky.social",
23 addSessionId: "session-1",
24 wantErr: nil,
25 wantLen: 1,
26 wantSessionId: "session-1",
27 },
28 {
29 name: "add second account",
30 initial: []AccountInfo{
31 {Did: "did:plc:abc123", Handle: "alice.bsky.social", SessionId: "session-1", AddedAt: 1000},
32 },
33 addDid: "did:plc:def456",
34 addHandle: "bob.bsky.social",
35 addSessionId: "session-2",
36 wantErr: nil,
37 wantLen: 2,
38 wantSessionId: "session-2",
39 },
40 {
41 name: "update existing account session",
42 initial: []AccountInfo{
43 {Did: "did:plc:abc123", Handle: "alice.bsky.social", SessionId: "old-session", AddedAt: 1000},
44 },
45 addDid: "did:plc:abc123",
46 addHandle: "alice.bsky.social",
47 addSessionId: "new-session",
48 wantErr: nil,
49 wantLen: 1,
50 wantSessionId: "new-session",
51 },
52 }
53
54 for _, tt := range tests {
55 t.Run(tt.name, func(t *testing.T) {
56 registry := &AccountRegistry{Accounts: tt.initial}
57 err := registry.AddAccount(tt.addDid, tt.addHandle, tt.addSessionId)
58
59 if err != tt.wantErr {
60 t.Errorf("AddAccount() error = %v, want %v", err, tt.wantErr)
61 }
62
63 if len(registry.Accounts) != tt.wantLen {
64 t.Errorf("AddAccount() len = %d, want %d", len(registry.Accounts), tt.wantLen)
65 }
66
67 found := registry.FindAccount(tt.addDid)
68 if found == nil {
69 t.Errorf("AddAccount() account not found after add")
70 return
71 }
72
73 if found.SessionId != tt.wantSessionId {
74 t.Errorf("AddAccount() sessionId = %s, want %s", found.SessionId, tt.wantSessionId)
75 }
76 })
77 }
78}
79
80func TestAccountRegistry_AddAccount_MaxLimit(t *testing.T) {
81 registry := &AccountRegistry{Accounts: make([]AccountInfo, 0, MaxAccounts)}
82
83 for i := range MaxAccounts {
84 err := registry.AddAccount("did:plc:user"+string(rune('a'+i)), "handle", "session")
85 if err != nil {
86 t.Fatalf("AddAccount() unexpected error on account %d: %v", i, err)
87 }
88 }
89
90 if len(registry.Accounts) != MaxAccounts {
91 t.Errorf("expected %d accounts, got %d", MaxAccounts, len(registry.Accounts))
92 }
93
94 err := registry.AddAccount("did:plc:overflow", "overflow", "session-overflow")
95 if err != ErrMaxAccountsReached {
96 t.Errorf("AddAccount() error = %v, want %v", err, ErrMaxAccountsReached)
97 }
98
99 if len(registry.Accounts) != MaxAccounts {
100 t.Errorf("account added despite max limit, got %d", len(registry.Accounts))
101 }
102}
103
104func TestAccountRegistry_RemoveAccount(t *testing.T) {
105 tests := []struct {
106 name string
107 initial []AccountInfo
108 removeDid string
109 wantLen int
110 wantDids []string
111 }{
112 {
113 name: "remove existing account",
114 initial: []AccountInfo{
115 {Did: "did:plc:abc123", Handle: "alice", SessionId: "s1"},
116 {Did: "did:plc:def456", Handle: "bob", SessionId: "s2"},
117 },
118 removeDid: "did:plc:abc123",
119 wantLen: 1,
120 wantDids: []string{"did:plc:def456"},
121 },
122 {
123 name: "remove non-existing account",
124 initial: []AccountInfo{
125 {Did: "did:plc:abc123", Handle: "alice", SessionId: "s1"},
126 },
127 removeDid: "did:plc:notfound",
128 wantLen: 1,
129 wantDids: []string{"did:plc:abc123"},
130 },
131 {
132 name: "remove last account",
133 initial: []AccountInfo{
134 {Did: "did:plc:abc123", Handle: "alice", SessionId: "s1"},
135 },
136 removeDid: "did:plc:abc123",
137 wantLen: 0,
138 wantDids: []string{},
139 },
140 {
141 name: "remove from empty registry",
142 initial: []AccountInfo{},
143 removeDid: "did:plc:abc123",
144 wantLen: 0,
145 wantDids: []string{},
146 },
147 }
148
149 for _, tt := range tests {
150 t.Run(tt.name, func(t *testing.T) {
151 registry := &AccountRegistry{Accounts: tt.initial}
152 registry.RemoveAccount(tt.removeDid)
153
154 if len(registry.Accounts) != tt.wantLen {
155 t.Errorf("RemoveAccount() len = %d, want %d", len(registry.Accounts), tt.wantLen)
156 }
157
158 for _, wantDid := range tt.wantDids {
159 if registry.FindAccount(wantDid) == nil {
160 t.Errorf("RemoveAccount() expected %s to remain", wantDid)
161 }
162 }
163
164 if registry.FindAccount(tt.removeDid) != nil && tt.wantLen < len(tt.initial) {
165 t.Errorf("RemoveAccount() %s should have been removed", tt.removeDid)
166 }
167 })
168 }
169}
170
171func TestAccountRegistry_FindAccount(t *testing.T) {
172 registry := &AccountRegistry{
173 Accounts: []AccountInfo{
174 {Did: "did:plc:first", Handle: "first", SessionId: "s1", AddedAt: 1000},
175 {Did: "did:plc:second", Handle: "second", SessionId: "s2", AddedAt: 2000},
176 {Did: "did:plc:third", Handle: "third", SessionId: "s3", AddedAt: 3000},
177 },
178 }
179
180 t.Run("find existing account", func(t *testing.T) {
181 found := registry.FindAccount("did:plc:second")
182 if found == nil {
183 t.Fatal("FindAccount() returned nil for existing account")
184 }
185 if found.Handle != "second" {
186 t.Errorf("FindAccount() handle = %s, want second", found.Handle)
187 }
188 if found.SessionId != "s2" {
189 t.Errorf("FindAccount() sessionId = %s, want s2", found.SessionId)
190 }
191 })
192
193 t.Run("find non-existing account", func(t *testing.T) {
194 found := registry.FindAccount("did:plc:notfound")
195 if found != nil {
196 t.Errorf("FindAccount() = %v, want nil", found)
197 }
198 })
199
200 t.Run("returned pointer is mutable", func(t *testing.T) {
201 found := registry.FindAccount("did:plc:first")
202 if found == nil {
203 t.Fatal("FindAccount() returned nil")
204 }
205 found.SessionId = "modified"
206
207 refetch := registry.FindAccount("did:plc:first")
208 if refetch.SessionId != "modified" {
209 t.Errorf("FindAccount() pointer not referencing original, got %s", refetch.SessionId)
210 }
211 })
212}
213
214func TestAccountRegistry_OtherAccounts(t *testing.T) {
215 registry := &AccountRegistry{
216 Accounts: []AccountInfo{
217 {Did: "did:plc:active", Handle: "active", SessionId: "s1"},
218 {Did: "did:plc:other1", Handle: "other1", SessionId: "s2"},
219 {Did: "did:plc:other2", Handle: "other2", SessionId: "s3"},
220 },
221 }
222
223 others := registry.OtherAccounts("did:plc:active")
224
225 if len(others) != 2 {
226 t.Errorf("OtherAccounts() len = %d, want 2", len(others))
227 }
228
229 for _, acc := range others {
230 if acc.Did == "did:plc:active" {
231 t.Errorf("OtherAccounts() should not include active account")
232 }
233 }
234
235 hasDid := func(did string) bool {
236 for _, acc := range others {
237 if acc.Did == did {
238 return true
239 }
240 }
241 return false
242 }
243
244 if !hasDid("did:plc:other1") || !hasDid("did:plc:other2") {
245 t.Errorf("OtherAccounts() missing expected accounts")
246 }
247}
248
249func TestMultiAccountUser_Did(t *testing.T) {
250 t.Run("with active user", func(t *testing.T) {
251 user := &MultiAccountUser{
252 Active: &User{Did: "did:plc:test", Pds: "https://bsky.social"},
253 }
254 if user.Did() != "did:plc:test" {
255 t.Errorf("Did() = %s, want did:plc:test", user.Did())
256 }
257 })
258
259 t.Run("with nil active", func(t *testing.T) {
260 user := &MultiAccountUser{Active: nil}
261 if user.Did() != "" {
262 t.Errorf("Did() = %s, want empty string", user.Did())
263 }
264 })
265}