identity_test.go

  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}