1package constraints
2
3import (
4	"testing"
5
6	"github.com/go-test/deep"
7)
8
9func TestParseRubyStyle(t *testing.T) {
10	tests := []struct {
11		Input   string
12		Want    SelectionSpec
13		WantErr string
14	}{
15		{
16			"",
17			SelectionSpec{},
18			"empty specification",
19		},
20		{
21			"1",
22			SelectionSpec{
23				Operator: OpEqual,
24				Boundary: VersionSpec{
25					Major: NumConstraint{Num: 1},
26					Minor: NumConstraint{Unconstrained: true},
27					Patch: NumConstraint{Unconstrained: true},
28				},
29			},
30			"",
31		},
32		{
33			"1.1",
34			SelectionSpec{
35				Operator: OpEqual,
36				Boundary: VersionSpec{
37					Major: NumConstraint{Num: 1},
38					Minor: NumConstraint{Num: 1},
39					Patch: NumConstraint{Unconstrained: true},
40				},
41			},
42			"",
43		},
44		{
45			"1.1.1",
46			SelectionSpec{
47				Operator: OpEqual,
48				Boundary: VersionSpec{
49					Major: NumConstraint{Num: 1},
50					Minor: NumConstraint{Num: 1},
51					Patch: NumConstraint{Num: 1},
52				},
53			},
54			"",
55		},
56		{
57			"1.0.0.0",
58			SelectionSpec{},
59			"too many numbered portions; only three are allowed (major, minor, patch)",
60		},
61		{
62			"v1.0.0",
63			SelectionSpec{},
64			`a "v" prefix should not be used`,
65		},
66		{
67			"1.0.0-beta2",
68			SelectionSpec{
69				Operator: OpEqual,
70				Boundary: VersionSpec{
71					Major:      NumConstraint{Num: 1},
72					Minor:      NumConstraint{Num: 0},
73					Patch:      NumConstraint{Num: 0},
74					Prerelease: "beta2",
75				},
76			},
77			"",
78		},
79		{
80			"1.0-beta2",
81			SelectionSpec{
82				Operator: OpEqual,
83				Boundary: VersionSpec{
84					Major:      NumConstraint{Num: 1},
85					Minor:      NumConstraint{Num: 0},
86					Patch:      NumConstraint{Num: 0}, // implied by the prerelease tag to ensure constraint consistency
87					Prerelease: "beta2",
88				},
89			},
90			"",
91		},
92		{
93			"1.0.0-beta.2",
94			SelectionSpec{
95				Operator: OpEqual,
96				Boundary: VersionSpec{
97					Major:      NumConstraint{Num: 1},
98					Minor:      NumConstraint{Num: 0},
99					Patch:      NumConstraint{Num: 0},
100					Prerelease: "beta.2",
101				},
102			},
103			"",
104		},
105		{
106			"1.0.0+foo",
107			SelectionSpec{
108				Operator: OpEqual,
109				Boundary: VersionSpec{
110					Major:    NumConstraint{Num: 1},
111					Minor:    NumConstraint{Num: 0},
112					Patch:    NumConstraint{Num: 0},
113					Metadata: "foo",
114				},
115			},
116			"",
117		},
118		{
119			"1.0.0+foo.bar",
120			SelectionSpec{
121				Operator: OpEqual,
122				Boundary: VersionSpec{
123					Major:    NumConstraint{Num: 1},
124					Minor:    NumConstraint{Num: 0},
125					Patch:    NumConstraint{Num: 0},
126					Metadata: "foo.bar",
127				},
128			},
129			"",
130		},
131		{
132			"1.0.0-beta1+foo.bar",
133			SelectionSpec{
134				Operator: OpEqual,
135				Boundary: VersionSpec{
136					Major:      NumConstraint{Num: 1},
137					Minor:      NumConstraint{Num: 0},
138					Patch:      NumConstraint{Num: 0},
139					Prerelease: "beta1",
140					Metadata:   "foo.bar",
141				},
142			},
143			"",
144		},
145		{
146			"> 1.1.1",
147			SelectionSpec{
148				Operator: OpGreaterThan,
149				Boundary: VersionSpec{
150					Major: NumConstraint{Num: 1},
151					Minor: NumConstraint{Num: 1},
152					Patch: NumConstraint{Num: 1},
153				},
154			},
155			``,
156		},
157		{
158			">1.1.1",
159			SelectionSpec{
160				Operator: OpGreaterThan,
161				Boundary: VersionSpec{
162					Major: NumConstraint{Num: 1},
163					Minor: NumConstraint{Num: 1},
164					Patch: NumConstraint{Num: 1},
165				},
166			},
167			``,
168		},
169		{
170			">= 1.1.1",
171			SelectionSpec{
172				Operator: OpGreaterThanOrEqual,
173				Boundary: VersionSpec{
174					Major: NumConstraint{Num: 1},
175					Minor: NumConstraint{Num: 1},
176					Patch: NumConstraint{Num: 1},
177				},
178			},
179			``,
180		},
181		{
182			">=1.1.1",
183			SelectionSpec{
184				Operator: OpGreaterThanOrEqual,
185				Boundary: VersionSpec{
186					Major: NumConstraint{Num: 1},
187					Minor: NumConstraint{Num: 1},
188					Patch: NumConstraint{Num: 1},
189				},
190			},
191			``,
192		},
193		{
194			"=> 1.1.1",
195			SelectionSpec{},
196			`invalid constraint operator "=>"; did you mean ">="?`,
197		},
198		{
199			"=>1.1.1",
200			SelectionSpec{},
201			`invalid constraint operator "=>"; did you mean ">="?`,
202		},
203		{
204			"< 1.1.1",
205			SelectionSpec{
206				Operator: OpLessThan,
207				Boundary: VersionSpec{
208					Major: NumConstraint{Num: 1},
209					Minor: NumConstraint{Num: 1},
210					Patch: NumConstraint{Num: 1},
211				},
212			},
213			``,
214		},
215		{
216			"<= 1.1.1",
217			SelectionSpec{
218				Operator: OpLessThanOrEqual,
219				Boundary: VersionSpec{
220					Major: NumConstraint{Num: 1},
221					Minor: NumConstraint{Num: 1},
222					Patch: NumConstraint{Num: 1},
223				},
224			},
225			``,
226		},
227		{
228			"=< 1.1.1",
229			SelectionSpec{},
230			`invalid constraint operator "=<"; did you mean "<="?`,
231		},
232		{
233			"~> 1.1.1",
234			SelectionSpec{
235				Operator: OpGreaterThanOrEqualPatchOnly,
236				Boundary: VersionSpec{
237					Major: NumConstraint{Num: 1},
238					Minor: NumConstraint{Num: 1},
239					Patch: NumConstraint{Num: 1},
240				},
241			},
242			``,
243		},
244		{
245			"~> 1.1",
246			SelectionSpec{
247				Operator: OpGreaterThanOrEqualMinorOnly,
248				Boundary: VersionSpec{
249					Major: NumConstraint{Num: 1},
250					Minor: NumConstraint{Num: 1},
251					Patch: NumConstraint{Unconstrained: true},
252				},
253			},
254			``,
255		},
256		{
257			"~> 1",
258			SelectionSpec{
259				Operator: OpGreaterThanOrEqualMinorOnly,
260				Boundary: VersionSpec{
261					Major: NumConstraint{Num: 1},
262					Minor: NumConstraint{Unconstrained: true},
263					Patch: NumConstraint{Unconstrained: true},
264				},
265			},
266			``,
267		},
268		{
269			"= 1.1.1",
270			SelectionSpec{
271				Operator: OpEqual,
272				Boundary: VersionSpec{
273					Major: NumConstraint{Num: 1},
274					Minor: NumConstraint{Num: 1},
275					Patch: NumConstraint{Num: 1},
276				},
277			},
278			``,
279		},
280		{
281			"!= 1.1.1",
282			SelectionSpec{
283				Operator: OpNotEqual,
284				Boundary: VersionSpec{
285					Major: NumConstraint{Num: 1},
286					Minor: NumConstraint{Num: 1},
287					Patch: NumConstraint{Num: 1},
288				},
289			},
290			``,
291		},
292		{
293			"=  1.1.1",
294			SelectionSpec{},
295			`only one space is expected after the operator "="`,
296		},
297		{
298			"garbage",
299			SelectionSpec{},
300			`invalid characters "garbage"`,
301		},
302		{
303			"& 1.1.0",
304			SelectionSpec{},
305			`invalid constraint operator "&"`,
306		},
307		{
308			"1.*.*",
309			SelectionSpec{},
310			`can't use wildcard for minor number; omit segments that should be unconstrained`,
311		},
312		{
313			"1.0.x",
314			SelectionSpec{},
315			`can't use wildcard for patch number; omit segments that should be unconstrained`,
316		},
317		{
318			"1.0 || 2.0",
319			SelectionSpec{},
320			`only one constraint may be specified`,
321		},
322		{
323			"1.0.0, 2.0.0",
324			SelectionSpec{},
325			`only one constraint may be specified`,
326		},
327		{
328			"1.0.0 - 2.0.0",
329			SelectionSpec{},
330			`range constraints are not supported`,
331		},
332	}
333
334	for _, test := range tests {
335		t.Run(test.Input, func(t *testing.T) {
336			got, err := ParseRubyStyle(test.Input)
337			var gotErr string
338			if err != nil {
339				gotErr = err.Error()
340			}
341			if gotErr != test.WantErr {
342				t.Errorf("wrong error\ngot:  %s\nwant: %s", gotErr, test.WantErr)
343				return
344			}
345			if err != nil {
346				return
347			}
348
349			for _, problem := range deep.Equal(got, test.Want) {
350				t.Error(problem)
351			}
352		})
353	}
354}
355
356func TestParseRubyStyleMulti(t *testing.T) {
357	tests := []struct {
358		Input   string
359		Want    IntersectionSpec
360		WantErr string
361	}{
362		{
363			"",
364			nil,
365			"",
366		},
367		{
368			"1.1.1",
369			IntersectionSpec{
370				SelectionSpec{
371					Operator: OpEqual,
372					Boundary: VersionSpec{
373						Major: NumConstraint{Num: 1},
374						Minor: NumConstraint{Num: 1},
375						Patch: NumConstraint{Num: 1},
376					},
377				},
378			},
379			"",
380		},
381		{
382			"~> v1.1.1",
383			nil,
384			`a "v" prefix should not be used`,
385		},
386		{
387			">= 1.0, < 2",
388			IntersectionSpec{
389				SelectionSpec{
390					Operator: OpGreaterThanOrEqual,
391					Boundary: VersionSpec{
392						Major: NumConstraint{Num: 1},
393						Minor: NumConstraint{Num: 0},
394						Patch: NumConstraint{Unconstrained: true},
395					},
396				},
397				SelectionSpec{
398					Operator: OpLessThan,
399					Boundary: VersionSpec{
400						Major: NumConstraint{Num: 2},
401						Minor: NumConstraint{Unconstrained: true},
402						Patch: NumConstraint{Unconstrained: true},
403					},
404				},
405			},
406			"",
407		},
408		{
409			">= 1.0 < 2",
410			nil,
411			`missing comma after ">= 1.0"`,
412		},
413	}
414
415	for _, test := range tests {
416		t.Run(test.Input, func(t *testing.T) {
417			got, err := ParseRubyStyleMulti(test.Input)
418			var gotErr string
419			if err != nil {
420				gotErr = err.Error()
421			}
422			if gotErr != test.WantErr {
423				t.Errorf("wrong error\ngot:  %s\nwant: %s", gotErr, test.WantErr)
424				return
425			}
426			if err != nil {
427				return
428			}
429
430			for _, problem := range deep.Equal(got, test.Want) {
431				t.Error(problem)
432			}
433		})
434	}
435}
436