aboutsummaryrefslogtreecommitdiff
path: root/ref/store/memory/batch_test.go
blob: 518dc7b97e50fbd11b88b29ddb5fc5998677890e (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package memory_test

import (
	"errors"
	"testing"

	"lindenii.org/go/furgit/object/id"
	"lindenii.org/go/furgit/ref"
	"lindenii.org/go/furgit/ref/store"
	"lindenii.org/go/furgit/ref/store/memory"
)

func TestBatchRejectsDuplicateResolvedTargetAndAppliesRemainder(t *testing.T) {
	t.Parallel()

	for _, objectFormat := range id.SupportedObjectFormats() {
		t.Run(objectFormat.String(), func(t *testing.T) {
			t.Parallel()

			m := memory.New(objectFormat)
			mainID := objectFormat.Sum([]byte("main"))
			devID := objectFormat.Sum([]byte("dev"))
			nextMainID := objectFormat.Sum([]byte("next-main"))
			nextDevID := objectFormat.Sum([]byte("next-dev"))
			aliasID := objectFormat.Sum([]byte("alias"))

			seed(t, m, func(tx store.Transaction) {
				err := tx.Create("refs/heads/main", mainID)
				if err != nil {
					t.Fatalf("Create(main): %v", err)
				}

				err = tx.Create("refs/heads/dev", devID)
				if err != nil {
					t.Fatalf("Create(dev): %v", err)
				}

				err = tx.CreateSymbolic("refs/heads/alias", "refs/heads/main")
				if err != nil {
					t.Fatalf("CreateSymbolic(alias): %v", err)
				}
			})

			batch, err := m.BeginBatch()
			if err != nil {
				t.Fatalf("BeginBatch: %v", err)
			}

			err = batch.Update("refs/heads/main", nextMainID, mainID)
			if err != nil {
				t.Fatalf("Update(main): %v", err)
			}

			// Updates the symbolic alias in deref mode,
			// which resolves to refs/heads/main
			// and therefore duplicates the first operation.
			err = batch.Update("refs/heads/alias", aliasID, mainID)
			if err != nil {
				t.Fatalf("Update(alias): %v", err)
			}

			err = batch.Update("refs/heads/dev", nextDevID, devID)
			if err != nil {
				t.Fatalf("Update(dev): %v", err)
			}

			results, err := batch.Apply()
			if err != nil {
				t.Fatalf("Apply: %v", err)
			}

			if len(results) != 3 {
				t.Fatalf("len(results) = %d, want 3", len(results))
			}

			if results[0].Status != store.BatchStatusApplied {
				t.Fatalf("results[0].Status = %v, want applied", results[0].Status)
			}

			if results[1].Status != store.BatchStatusRejected {
				t.Fatalf("results[1].Status = %v, want rejected", results[1].Status)
			}

			if !errors.Is(results[1].Error, store.ErrDuplicateUpdate) {
				t.Fatalf("results[1].Error = %v, want ErrDuplicateUpdate", results[1].Error)
			}

			if results[2].Status != store.BatchStatusApplied {
				t.Fatalf("results[2].Status = %v, want applied", results[2].Status)
			}

			if got := resolveDirect(t, m, "refs/heads/main").ID; got != nextMainID {
				t.Fatalf("main after batch = %v, want %v", got, nextMainID)
			}

			if got := resolveDirect(t, m, "refs/heads/dev").ID; got != nextDevID {
				t.Fatalf("dev after batch = %v, want %v", got, nextDevID)
			}

			resolved, err := m.Resolve("refs/heads/alias")
			if err != nil {
				t.Fatalf("Resolve(alias): %v", err)
			}

			symbolic, ok := resolved.(ref.Symbolic)
			if !ok {
				t.Fatalf("Resolve(alias) = %T, want ref.Symbolic", resolved)
			}

			if symbolic.Target != "refs/heads/main" {
				t.Fatalf("alias target = %q, want refs/heads/main", symbolic.Target)
			}
		})
	}
}