1package identity
2
3import (
4 "bytes"
5 "encoding/json"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/git-bug/git-bug/entity"
11 "github.com/git-bug/git-bug/repository"
12 "github.com/git-bug/git-bug/util/lamport"
13 "github.com/git-bug/git-bug/util/timestamp"
14)
15
16type mockIdentity struct {
17 name string
18 login string
19 email string
20}
21
22func (m *mockIdentity) Name() string {
23 return m.name
24}
25
26func (m *mockIdentity) Login() string {
27 return m.login
28}
29
30func (m *mockIdentity) Email() string {
31 return m.email
32}
33
34func (m *mockIdentity) DisplayName() string {
35 return m.name
36}
37
38func (m *mockIdentity) AvatarUrl() string {
39 return ""
40}
41
42func (m *mockIdentity) Keys() []*Key {
43 return nil
44}
45
46func (m *mockIdentity) SigningKey(repo repository.RepoKeyring) (*Key, error) {
47 return nil, nil
48}
49
50func (m *mockIdentity) ValidKeysAtTime(clockName string, time lamport.Time) []*Key {
51 return nil
52}
53
54func (m *mockIdentity) LastModification() timestamp.Timestamp {
55 return 0
56}
57
58func (m *mockIdentity) LastModificationLamports() map[string]lamport.Time {
59 return nil
60}
61
62func (m *mockIdentity) IsProtected() bool {
63 return false
64}
65
66func (m *mockIdentity) Validate() error {
67 return nil
68}
69
70func (m *mockIdentity) NeedCommit() bool {
71 return false
72}
73
74func (m *mockIdentity) Id() entity.Id {
75 return ""
76}
77
78// identitiesEqual compares two identities by their versions
79func identitiesEqual(left, right *Identity) bool {
80 if left == nil && right == nil {
81 return true
82 }
83 if left == nil || right == nil {
84 return false
85 }
86
87 if len(left.versions) != len(right.versions) {
88 return false
89 }
90
91 for i, lv := range left.versions {
92 rv := right.versions[i]
93 if !versionsEqual(lv, rv) {
94 return false
95 }
96 }
97
98 return true
99}
100
101// versionsEqual compares two versions, comparing keys by their public key fingerprints
102func versionsEqual(left, right *version) bool {
103 if left == nil && right == nil {
104 return true
105 }
106 if left == nil || right == nil {
107 return false
108 }
109
110 // Compare basic fields
111 if left.name != right.name || left.email != right.email ||
112 left.login != right.login || left.avatarURL != right.avatarURL ||
113 left.unixTime != right.unixTime {
114 return false
115 }
116
117 // Compare times
118 if len(left.times) != len(right.times) {
119 return false
120 }
121 for k, tv := range left.times {
122 ov, ok := right.times[k]
123 if !ok || tv != ov {
124 return false
125 }
126 }
127
128 // Compare keys by fingerprint
129 if len(left.keys) != len(right.keys) {
130 return false
131 }
132 for i, k := range left.keys {
133 if !keysEqual(k, right.keys[i]) {
134 return false
135 }
136 }
137
138 // Compare nonce
139 if len(left.nonce) != len(right.nonce) {
140 return false
141 }
142 for i, n := range left.nonce {
143 if n != right.nonce[i] {
144 return false
145 }
146 }
147
148 // Compare metadata
149 if len(left.metadata) != len(right.metadata) {
150 return false
151 }
152 for k, vm := range left.metadata {
153 om, ok := right.metadata[k]
154 if !ok || vm != om {
155 return false
156 }
157 }
158
159 // Don't compare id and commitHash as they're derived fields
160 return true
161}
162
163// keysEqual compares two keys by their public key fingerprint
164func keysEqual(left, right *Key) bool {
165 if left == nil && right == nil {
166 return true
167 }
168 if left == nil || right == nil {
169 return false
170 }
171 if left.public == nil && right.public == nil {
172 return true
173 }
174 if left.public == nil || right.public == nil {
175 return false
176 }
177 return bytes.Equal(left.public.Fingerprint[:], right.public.Fingerprint[:])
178}
179
180// Test the commit and load of an Identity with multiple versions
181func TestIdentityCommitLoad(t *testing.T) {
182 repo := makeIdentityTestRepo(t)
183
184 // single version
185
186 identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
187 require.NoError(t, err)
188
189 idBeforeCommit := identity.Id()
190
191 err = identity.Commit(repo)
192 require.NoError(t, err)
193
194 commitsAreSet(t, identity)
195 require.NotEmpty(t, identity.Id())
196 require.Equal(t, idBeforeCommit, identity.Id())
197 require.Equal(t, idBeforeCommit, identity.versions[0].Id())
198
199 loaded, err := ReadLocal(repo, identity.Id())
200 require.NoError(t, err)
201 commitsAreSet(t, loaded)
202 require.True(t, identitiesEqual(identity, loaded), "loaded identity should equal original (comparing by key fingerprint)")
203
204 // multiple versions
205 testIdentity := &mockIdentity{name: "René Descartes", email: "rene.descartes@example.com"}
206 identity, err = NewIdentityFull(repo, "René Descartes", "rene.descartes@example.com", "", "", []*Key{generatePublicKey(testIdentity)})
207 require.NoError(t, err)
208
209 idBeforeCommit = identity.Id()
210
211 err = identity.Mutate(repo, func(orig *Mutator) {
212 orig.Keys = []*Key{generatePublicKey(testIdentity)}
213 })
214 require.NoError(t, err)
215
216 err = identity.Mutate(repo, func(orig *Mutator) {
217 orig.Keys = []*Key{generatePublicKey(testIdentity)}
218 })
219 require.NoError(t, err)
220
221 require.Equal(t, idBeforeCommit, identity.Id())
222
223 err = identity.Commit(repo)
224 require.NoError(t, err)
225
226 commitsAreSet(t, identity)
227 require.NotEmpty(t, identity.Id())
228 require.Equal(t, idBeforeCommit, identity.Id())
229 require.Equal(t, idBeforeCommit, identity.versions[0].Id())
230
231 loaded, err = ReadLocal(repo, identity.Id())
232 require.NoError(t, err)
233 commitsAreSet(t, loaded)
234 require.True(t, identitiesEqual(identity, loaded), "loaded identity should equal original (comparing by key fingerprint)")
235
236 // add more version
237
238 err = identity.Mutate(repo, func(orig *Mutator) {
239 orig.Email = "rene@descartes.com"
240 orig.Keys = []*Key{generatePublicKey(testIdentity)}
241 })
242 require.NoError(t, err)
243
244 err = identity.Mutate(repo, func(orig *Mutator) {
245 orig.Email = "rene@descartes.com"
246 orig.Keys = []*Key{generatePublicKey(testIdentity), generatePublicKey(testIdentity)}
247 })
248 require.NoError(t, err)
249
250 err = identity.Commit(repo)
251 require.NoError(t, err)
252
253 commitsAreSet(t, identity)
254 require.NotEmpty(t, identity.Id())
255 require.Equal(t, idBeforeCommit, identity.Id())
256 require.Equal(t, idBeforeCommit, identity.versions[0].Id())
257
258 loaded, err = ReadLocal(repo, identity.Id())
259 require.NoError(t, err)
260 commitsAreSet(t, loaded)
261 require.True(t, identitiesEqual(identity, loaded), "loaded identity should equal original (comparing by key fingerprint)")
262}
263
264func TestIdentityMutate(t *testing.T) {
265 repo := makeIdentityTestRepo(t)
266
267 identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
268 require.NoError(t, err)
269
270 require.Len(t, identity.versions, 1)
271
272 err = identity.Mutate(repo, func(orig *Mutator) {
273 orig.Email = "rene@descartes.fr"
274 orig.Name = "René"
275 orig.Login = "rene"
276 })
277 require.NoError(t, err)
278
279 require.Len(t, identity.versions, 2)
280 require.Equal(t, identity.Email(), "rene@descartes.fr")
281 require.Equal(t, identity.Name(), "René")
282 require.Equal(t, identity.Login(), "rene")
283}
284
285func commitsAreSet(t *testing.T, identity *Identity) {
286 for _, version := range identity.versions {
287 require.NotEmpty(t, version.commitHash)
288 }
289}
290
291// Test that the correct crypto keys are returned for a given lamport time
292func TestIdentity_ValidKeysAtTime(t *testing.T) {
293 testIdentity := &mockIdentity{name: "Test User", email: "test@example.com"}
294 pubKeyA := generatePublicKey(testIdentity)
295 pubKeyB := generatePublicKey(testIdentity)
296 pubKeyC := generatePublicKey(testIdentity)
297 pubKeyD := generatePublicKey(testIdentity)
298 pubKeyE := generatePublicKey(testIdentity)
299
300 identity := Identity{
301 versions: []*version{
302 {
303 times: map[string]lamport.Time{"foo": 100},
304 keys: []*Key{pubKeyA},
305 },
306 {
307 times: map[string]lamport.Time{"foo": 200},
308 keys: []*Key{pubKeyB},
309 },
310 {
311 times: map[string]lamport.Time{"foo": 201},
312 keys: []*Key{pubKeyC},
313 },
314 {
315 times: map[string]lamport.Time{"foo": 201},
316 keys: []*Key{pubKeyD},
317 },
318 {
319 times: map[string]lamport.Time{"foo": 300},
320 keys: []*Key{pubKeyE},
321 },
322 },
323 }
324
325 require.Nil(t, identity.ValidKeysAtTime("foo", 10))
326 require.Equal(t, identity.ValidKeysAtTime("foo", 100), []*Key{pubKeyA})
327 require.Equal(t, identity.ValidKeysAtTime("foo", 140), []*Key{pubKeyA})
328 require.Equal(t, identity.ValidKeysAtTime("foo", 200), []*Key{pubKeyB})
329 require.Equal(t, identity.ValidKeysAtTime("foo", 201), []*Key{pubKeyD})
330 require.Equal(t, identity.ValidKeysAtTime("foo", 202), []*Key{pubKeyD})
331 require.Equal(t, identity.ValidKeysAtTime("foo", 300), []*Key{pubKeyE})
332 require.Equal(t, identity.ValidKeysAtTime("foo", 3000), []*Key{pubKeyE})
333}
334
335// Test the immutable or mutable metadata search
336func TestMetadata(t *testing.T) {
337 repo := makeIdentityTestRepo(t)
338
339 identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
340 require.NoError(t, err)
341
342 identity.SetMetadata("key1", "value1")
343 assertHasKeyValue(t, identity.ImmutableMetadata(), "key1", "value1")
344 assertHasKeyValue(t, identity.MutableMetadata(), "key1", "value1")
345
346 err = identity.Commit(repo)
347 require.NoError(t, err)
348
349 assertHasKeyValue(t, identity.ImmutableMetadata(), "key1", "value1")
350 assertHasKeyValue(t, identity.MutableMetadata(), "key1", "value1")
351
352 // try override
353 err = identity.Mutate(repo, func(orig *Mutator) {
354 orig.Email = "rene@descartes.fr"
355 })
356 require.NoError(t, err)
357
358 identity.SetMetadata("key1", "value2")
359 assertHasKeyValue(t, identity.ImmutableMetadata(), "key1", "value1")
360 assertHasKeyValue(t, identity.MutableMetadata(), "key1", "value2")
361
362 err = identity.Commit(repo)
363 require.NoError(t, err)
364
365 // reload
366 loaded, err := ReadLocal(repo, identity.Id())
367 require.NoError(t, err)
368
369 assertHasKeyValue(t, loaded.ImmutableMetadata(), "key1", "value1")
370 assertHasKeyValue(t, loaded.MutableMetadata(), "key1", "value2")
371
372 // set metadata after commit
373 versionCount := len(identity.versions)
374 identity.SetMetadata("foo", "bar")
375 require.True(t, identity.NeedCommit())
376 require.Len(t, identity.versions, versionCount+1)
377
378 err = identity.Commit(repo)
379 require.NoError(t, err)
380 require.Len(t, identity.versions, versionCount+1)
381}
382
383func assertHasKeyValue(t *testing.T, metadata map[string]string, key, value string) {
384 val, ok := metadata[key]
385 require.True(t, ok)
386 require.Equal(t, val, value)
387}
388
389func TestJSON(t *testing.T) {
390 repo := makeIdentityTestRepo(t)
391
392 identity, err := NewIdentity(repo, "René Descartes", "rene.descartes@example.com")
393 require.NoError(t, err)
394
395 // commit to make sure we have an Id
396 err = identity.Commit(repo)
397 require.NoError(t, err)
398 require.NotEmpty(t, identity.Id())
399
400 // serialize
401 data, err := json.Marshal(identity)
402 require.NoError(t, err)
403
404 // deserialize, got a IdentityStub with the same id
405 var i Interface
406 i, err = UnmarshalJSON(data)
407 require.NoError(t, err)
408 require.Equal(t, identity.Id(), i.Id())
409
410 // make sure we can load the identity properly
411 i, err = ReadLocal(repo, i.Id())
412 require.NoError(t, err)
413}
414
415func TestIdentityRemove(t *testing.T) {
416 repo := repository.CreateGoGitTestRepo(t, false)
417 remoteA := repository.CreateGoGitTestRepo(t, true)
418 remoteB := repository.CreateGoGitTestRepo(t, true)
419
420 err := repo.AddRemote("remoteA", remoteA.GetLocalRemote())
421 require.NoError(t, err)
422
423 err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
424 require.NoError(t, err)
425
426 // generate an identity for testing
427 rene, err := NewIdentity(repo, "René Descartes", "rene@descartes.fr")
428 require.NoError(t, err)
429
430 err = rene.Commit(repo)
431 require.NoError(t, err)
432
433 _, err = Push(repo, "remoteA")
434 require.NoError(t, err)
435
436 _, err = Push(repo, "remoteB")
437 require.NoError(t, err)
438
439 _, err = Fetch(repo, "remoteA")
440 require.NoError(t, err)
441
442 _, err = Fetch(repo, "remoteB")
443 require.NoError(t, err)
444
445 err = Remove(repo, rene.Id())
446 require.NoError(t, err)
447
448 _, err = ReadLocal(repo, rene.Id())
449 require.ErrorAs(t, entity.ErrNotFound{}, err)
450
451 _, err = ReadRemote(repo, "remoteA", string(rene.Id()))
452 require.ErrorAs(t, entity.ErrNotFound{}, err)
453
454 _, err = ReadRemote(repo, "remoteB", string(rene.Id()))
455 require.ErrorAs(t, entity.ErrNotFound{}, err)
456
457 ids, err := ListLocalIds(repo)
458 require.NoError(t, err)
459 require.Len(t, ids, 0)
460}