summaryrefslogtreecommitdiffstats
path: root/modules/git/foreachref/parser_test.go
blob: 7a37ced3565cfd01d866d92d8f751625a98f11de (plain)
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package foreachref_test

import (
	"errors"
	"fmt"
	"io"
	"strings"
	"testing"

	"code.gitea.io/gitea/modules/git/foreachref"
	"code.gitea.io/gitea/modules/json"

	"github.com/stretchr/testify/require"
)

type refSlice = []map[string]string

func TestParser(t *testing.T) {
	tests := []struct {
		name string

		givenFormat foreachref.Format
		givenInput  io.Reader

		wantRefs    refSlice
		wantErr     bool
		expectedErr error
	}{
		// this would, for example, be the result when running `git
		// for-each-ref refs/tags` on a repo without tags.
		{
			name: "no references on empty input",

			givenFormat: foreachref.NewFormat("refname:short"),
			givenInput:  strings.NewReader(``),

			wantRefs: []map[string]string{},
		},

		// note: `git for-each-ref` will add a newline between every
		// reference (in addition to the ref-delimiter we've chosen)
		{
			name: "single field requested, single reference in output",

			givenFormat: foreachref.NewFormat("refname:short"),
			givenInput:  strings.NewReader("refname:short v0.0.1\x00\x00" + "\n"),

			wantRefs: []map[string]string{
				{"refname:short": "v0.0.1"},
			},
		},
		{
			name: "single field requested, multiple references in output",

			givenFormat: foreachref.NewFormat("refname:short"),
			givenInput: strings.NewReader(
				"refname:short v0.0.1\x00\x00" + "\n" +
					"refname:short v0.0.2\x00\x00" + "\n" +
					"refname:short v0.0.3\x00\x00" + "\n"),

			wantRefs: []map[string]string{
				{"refname:short": "v0.0.1"},
				{"refname:short": "v0.0.2"},
				{"refname:short": "v0.0.3"},
			},
		},

		{
			name: "multiple fields requested for each reference",

			givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"),
			givenInput: strings.NewReader(

				"refname:short v0.0.1\x00objecttype commit\x00objectname 7b2c5ac9fc04fc5efafb60700713d4fa609b777b\x00\x00" + "\n" +
					"refname:short v0.0.2\x00objecttype commit\x00objectname a1f051bc3eba734da4772d60e2d677f47cf93ef4\x00\x00" + "\n" +
					"refname:short v0.0.3\x00objecttype commit\x00objectname ef82de70bb3f60c65fb8eebacbb2d122ef517385\x00\x00" + "\n",
			),

			wantRefs: []map[string]string{
				{
					"refname:short": "v0.0.1",
					"objecttype":    "commit",
					"objectname":    "7b2c5ac9fc04fc5efafb60700713d4fa609b777b",
				},
				{
					"refname:short": "v0.0.2",
					"objecttype":    "commit",
					"objectname":    "a1f051bc3eba734da4772d60e2d677f47cf93ef4",
				},
				{
					"refname:short": "v0.0.3",
					"objecttype":    "commit",
					"objectname":    "ef82de70bb3f60c65fb8eebacbb2d122ef517385",
				},
			},
		},

		{
			name: "must handle multi-line fields such as 'content'",

			givenFormat: foreachref.NewFormat("refname:short", "contents", "author"),
			givenInput: strings.NewReader(
				"refname:short v0.0.1\x00contents Create new buffer if not present yet (#549)\n\nFixes a nil dereference when ProcessFoo is used\nwith multiple commands.\x00author Foo Bar <foo@bar.com> 1507832733 +0200\x00\x00" + "\n" +
					"refname:short v0.0.2\x00contents Update CI config (#651)\n\n\x00author John Doe <john.doe@foo.com> 1521643174 +0000\x00\x00" + "\n" +
					"refname:short v0.0.3\x00contents Fixed code sample for bash completion (#687)\n\n\x00author Foo Baz <foo@baz.com> 1524836750 +0200\x00\x00" + "\n",
			),

			wantRefs: []map[string]string{
				{
					"refname:short": "v0.0.1",
					"contents":      "Create new buffer if not present yet (#549)\n\nFixes a nil dereference when ProcessFoo is used\nwith multiple commands.",
					"author":        "Foo Bar <foo@bar.com> 1507832733 +0200",
				},
				{
					"refname:short": "v0.0.2",
					"contents":      "Update CI config (#651)",
					"author":        "John Doe <john.doe@foo.com> 1521643174 +0000",
				},
				{
					"refname:short": "v0.0.3",
					"contents":      "Fixed code sample for bash completion (#687)",
					"author":        "Foo Baz <foo@baz.com> 1524836750 +0200",
				},
			},
		},

		{
			name: "must handle fields without values",

			givenFormat: foreachref.NewFormat("refname:short", "object", "objecttype"),
			givenInput: strings.NewReader(
				"refname:short v0.0.1\x00object \x00objecttype commit\x00\x00" + "\n" +
					"refname:short v0.0.2\x00object \x00objecttype commit\x00\x00" + "\n" +
					"refname:short v0.0.3\x00object \x00objecttype commit\x00\x00" + "\n",
			),

			wantRefs: []map[string]string{
				{
					"refname:short": "v0.0.1",
					"object":        "",
					"objecttype":    "commit",
				},
				{
					"refname:short": "v0.0.2",
					"object":        "",
					"objecttype":    "commit",
				},
				{
					"refname:short": "v0.0.3",
					"object":        "",
					"objecttype":    "commit",
				},
			},
		},

		{
			name: "must fail when the number of fields in the input doesn't match expected format",

			givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"),
			givenInput: strings.NewReader(
				"refname:short v0.0.1\x00objecttype commit\x00\x00" + "\n" +
					"refname:short v0.0.2\x00objecttype commit\x00\x00" + "\n" +
					"refname:short v0.0.3\x00objecttype commit\x00\x00" + "\n",
			),

			wantErr:     true,
			expectedErr: errors.New("unexpected number of reference fields: wanted 2, was 3"),
		},

		{
			name: "must fail input fields don't match expected format",

			givenFormat: foreachref.NewFormat("refname:short", "objectname"),
			givenInput: strings.NewReader(
				"refname:short v0.0.1\x00objecttype commit\x00\x00" + "\n" +
					"refname:short v0.0.2\x00objecttype commit\x00\x00" + "\n" +
					"refname:short v0.0.3\x00objecttype commit\x00\x00" + "\n",
			),

			wantErr:     true,
			expectedErr: errors.New("unexpected field name at position 1: wanted: 'objectname', was: 'objecttype'"),
		},
	}

	for _, test := range tests {
		tc := test // don't close over loop variable
		t.Run(tc.name, func(t *testing.T) {
			parser := tc.givenFormat.Parser(tc.givenInput)

			//
			// parse references from input
			//
			gotRefs := make([]map[string]string, 0)
			for {
				ref := parser.Next()
				if ref == nil {
					break
				}
				gotRefs = append(gotRefs, ref)
			}
			err := parser.Err()

			//
			// verify expectations
			//
			if tc.wantErr {
				require.Error(t, err)
				require.EqualError(t, err, tc.expectedErr.Error())
			} else {
				require.NoError(t, err, "for-each-ref parser unexpectedly failed with: %v", err)
				require.Equal(t, tc.wantRefs, gotRefs, "for-each-ref parser produced unexpected reference set. wanted: %v, got: %v", pretty(tc.wantRefs), pretty(gotRefs))
			}
		})
	}
}

func pretty(v any) string {
	data, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		// shouldn't happen
		panic(fmt.Sprintf("json-marshalling failed: %v", err))
	}
	return string(data)
}