1package constraints
2
3import (
4	"testing"
5
6	"github.com/davecgh/go-spew/spew"
7	"github.com/go-test/deep"
8)
9
10func TestParse(t *testing.T) {
11	tests := []struct {
12		Input   string
13		Want    UnionSpec
14		WantErr string
15	}{
16		{
17			"",
18			nil,
19			"empty specification",
20		},
21		{
22			"1",
23			UnionSpec{
24				IntersectionSpec{
25					SelectionSpec{
26						Operator: OpEqual,
27						Boundary: VersionSpec{
28							Major: NumConstraint{Num: 1},
29							Minor: NumConstraint{Num: 0},
30							Patch: NumConstraint{Num: 0},
31						},
32					},
33				},
34			},
35			"",
36		},
37		{
38			"1.1",
39			UnionSpec{
40				IntersectionSpec{
41					SelectionSpec{
42						Operator: OpEqual,
43						Boundary: VersionSpec{
44							Major: NumConstraint{Num: 1},
45							Minor: NumConstraint{Num: 1},
46							Patch: NumConstraint{Num: 0},
47						},
48					},
49				},
50			},
51			"",
52		},
53		{
54			"1.1.1",
55			UnionSpec{
56				IntersectionSpec{
57					SelectionSpec{
58						Operator: OpEqual,
59						Boundary: VersionSpec{
60							Major: NumConstraint{Num: 1},
61							Minor: NumConstraint{Num: 1},
62							Patch: NumConstraint{Num: 1},
63						},
64					},
65				},
66			},
67			"",
68		},
69		{
70			"1.0.0.0",
71			nil,
72			"too many numbered portions; only three are allowed (major, minor, patch)",
73		},
74		{
75			"v1.0.0",
76			nil,
77			`a "v" prefix should not be used when specifying versions`,
78		},
79		{
80			"1.0.0-beta2",
81			UnionSpec{
82				IntersectionSpec{
83					SelectionSpec{
84						Operator: OpEqual,
85						Boundary: VersionSpec{
86							Major:      NumConstraint{Num: 1},
87							Minor:      NumConstraint{Num: 0},
88							Patch:      NumConstraint{Num: 0},
89							Prerelease: "beta2",
90						},
91					},
92				},
93			},
94			"",
95		},
96		{
97			"1.0-beta2",
98			UnionSpec{
99				IntersectionSpec{
100					SelectionSpec{
101						Operator: OpEqual,
102						Boundary: VersionSpec{
103							Major:      NumConstraint{Num: 1},
104							Minor:      NumConstraint{Num: 0},
105							Patch:      NumConstraint{Num: 0}, // implied by the prerelease tag to ensure constraint consistency
106							Prerelease: "beta2",
107						},
108					},
109				},
110			},
111			"",
112		},
113		{
114			"1.0.0-beta.2",
115			UnionSpec{
116				IntersectionSpec{
117					SelectionSpec{
118						Operator: OpEqual,
119						Boundary: VersionSpec{
120							Major:      NumConstraint{Num: 1},
121							Minor:      NumConstraint{Num: 0},
122							Patch:      NumConstraint{Num: 0},
123							Prerelease: "beta.2",
124						},
125					},
126				},
127			},
128			"",
129		},
130		{
131			"1.0.0+foo",
132			UnionSpec{
133				IntersectionSpec{
134					SelectionSpec{
135						Operator: OpEqual,
136						Boundary: VersionSpec{
137							Major:    NumConstraint{Num: 1},
138							Minor:    NumConstraint{Num: 0},
139							Patch:    NumConstraint{Num: 0},
140							Metadata: "foo",
141						},
142					},
143				},
144			},
145			"",
146		},
147		{
148			"1.0.0+foo.bar",
149			UnionSpec{
150				IntersectionSpec{
151					SelectionSpec{
152						Operator: OpEqual,
153						Boundary: VersionSpec{
154							Major:    NumConstraint{Num: 1},
155							Minor:    NumConstraint{Num: 0},
156							Patch:    NumConstraint{Num: 0},
157							Metadata: "foo.bar",
158						},
159					},
160				},
161			},
162			"",
163		},
164		{
165			"1.0.0-beta1+foo.bar",
166			UnionSpec{
167				IntersectionSpec{
168					SelectionSpec{
169						Operator: OpEqual,
170						Boundary: VersionSpec{
171							Major:      NumConstraint{Num: 1},
172							Minor:      NumConstraint{Num: 0},
173							Patch:      NumConstraint{Num: 0},
174							Prerelease: "beta1",
175							Metadata:   "foo.bar",
176						},
177					},
178				},
179			},
180			"",
181		},
182		{
183			">1.1.1",
184			UnionSpec{
185				IntersectionSpec{
186					SelectionSpec{
187						Operator: OpGreaterThan,
188						Boundary: VersionSpec{
189							Major: NumConstraint{Num: 1},
190							Minor: NumConstraint{Num: 1},
191							Patch: NumConstraint{Num: 1},
192						},
193					},
194				},
195			},
196			``,
197		},
198		{
199			">2.*.*",
200			UnionSpec{
201				IntersectionSpec{
202					SelectionSpec{
203						Operator: OpGreaterThanOrEqual,
204						Boundary: VersionSpec{
205							Major: NumConstraint{Num: 3},
206							Minor: NumConstraint{Num: 0},
207							Patch: NumConstraint{Num: 0},
208						},
209					},
210				},
211			},
212			``,
213		},
214		{
215			">=1.1.1",
216			UnionSpec{
217				IntersectionSpec{
218					SelectionSpec{
219						Operator: OpGreaterThanOrEqual,
220						Boundary: VersionSpec{
221							Major: NumConstraint{Num: 1},
222							Minor: NumConstraint{Num: 1},
223							Patch: NumConstraint{Num: 1},
224						},
225					},
226				},
227			},
228			``,
229		},
230		{
231			">=2.*.*",
232			UnionSpec{
233				IntersectionSpec{
234					SelectionSpec{
235						Operator: OpGreaterThanOrEqual,
236						Boundary: VersionSpec{
237							Major: NumConstraint{Num: 2},
238							Minor: NumConstraint{Num: 0},
239							Patch: NumConstraint{Num: 0},
240						},
241					},
242				},
243			},
244			``,
245		},
246		{
247			"=>1.1.1",
248			nil,
249			`invalid constraint operator "=>"; did you mean ">="?`,
250		},
251		{
252			"<1.1.1",
253			UnionSpec{
254				IntersectionSpec{
255					SelectionSpec{
256						Operator: OpLessThan,
257						Boundary: VersionSpec{
258							Major: NumConstraint{Num: 1},
259							Minor: NumConstraint{Num: 1},
260							Patch: NumConstraint{Num: 1},
261						},
262					},
263				},
264			},
265			``,
266		},
267		{
268			"<2.*.*",
269			UnionSpec{
270				IntersectionSpec{
271					SelectionSpec{
272						Operator: OpLessThan,
273						Boundary: VersionSpec{
274							Major: NumConstraint{Num: 2},
275							Minor: NumConstraint{Num: 0},
276							Patch: NumConstraint{Num: 0},
277						},
278					},
279				},
280			},
281			``,
282		},
283		{
284			"<=1.1.1",
285			UnionSpec{
286				IntersectionSpec{
287					SelectionSpec{
288						Operator: OpLessThanOrEqual,
289						Boundary: VersionSpec{
290							Major: NumConstraint{Num: 1},
291							Minor: NumConstraint{Num: 1},
292							Patch: NumConstraint{Num: 1},
293						},
294					},
295				},
296			},
297			``,
298		},
299		{
300			"<=2.*.*",
301			UnionSpec{
302				IntersectionSpec{
303					SelectionSpec{
304						Operator: OpLessThan,
305						Boundary: VersionSpec{
306							Major: NumConstraint{Num: 3},
307							Minor: NumConstraint{Num: 0},
308							Patch: NumConstraint{Num: 0},
309						},
310					},
311				},
312			},
313			``,
314		},
315		{
316			"=<1.1.1",
317			nil,
318			`invalid constraint operator "=<"; did you mean "<="?`,
319		},
320		{
321			"~1.1.1",
322			UnionSpec{
323				IntersectionSpec{
324					SelectionSpec{
325						Operator: OpGreaterThanOrEqualPatchOnly,
326						Boundary: VersionSpec{
327							Major: NumConstraint{Num: 1},
328							Minor: NumConstraint{Num: 1},
329							Patch: NumConstraint{Num: 1},
330						},
331					},
332				},
333			},
334			``,
335		},
336		{
337			"~1.1",
338			UnionSpec{
339				IntersectionSpec{
340					SelectionSpec{
341						Operator: OpGreaterThanOrEqualPatchOnly,
342						Boundary: VersionSpec{
343							Major: NumConstraint{Num: 1},
344							Minor: NumConstraint{Num: 1},
345							Patch: NumConstraint{Num: 0},
346						},
347					},
348				},
349			},
350			``,
351		},
352		{
353			"~1",
354			UnionSpec{
355				IntersectionSpec{
356					SelectionSpec{
357						Operator: OpGreaterThanOrEqualMinorOnly,
358						Boundary: VersionSpec{
359							Major: NumConstraint{Num: 1},
360							Minor: NumConstraint{Num: 0},
361							Patch: NumConstraint{Num: 0},
362						},
363					},
364				},
365			},
366			``,
367		},
368		{
369			"^1.1.1",
370			UnionSpec{
371				IntersectionSpec{
372					SelectionSpec{
373						Operator: OpGreaterThanOrEqualMinorOnly,
374						Boundary: VersionSpec{
375							Major: NumConstraint{Num: 1},
376							Minor: NumConstraint{Num: 1},
377							Patch: NumConstraint{Num: 1},
378						},
379					},
380				},
381			},
382			``,
383		},
384		{
385			"^1.1",
386			UnionSpec{
387				IntersectionSpec{
388					SelectionSpec{
389						Operator: OpGreaterThanOrEqualMinorOnly,
390						Boundary: VersionSpec{
391							Major: NumConstraint{Num: 1},
392							Minor: NumConstraint{Num: 1},
393							Patch: NumConstraint{Num: 0},
394						},
395					},
396				},
397			},
398			``,
399		},
400		{
401			"^0.1",
402			UnionSpec{
403				IntersectionSpec{
404					SelectionSpec{
405						Operator: OpGreaterThanOrEqualPatchOnly,
406						Boundary: VersionSpec{
407							Major: NumConstraint{Num: 0},
408							Minor: NumConstraint{Num: 1},
409							Patch: NumConstraint{Num: 0},
410						},
411					},
412				},
413			},
414			``,
415		},
416		{
417			"^1",
418			UnionSpec{
419				IntersectionSpec{
420					SelectionSpec{
421						Operator: OpGreaterThanOrEqualMinorOnly,
422						Boundary: VersionSpec{
423							Major: NumConstraint{Num: 1},
424							Minor: NumConstraint{Num: 0},
425							Patch: NumConstraint{Num: 0},
426						},
427					},
428				},
429			},
430			``,
431		},
432		{
433			"=1.1.1",
434			UnionSpec{
435				IntersectionSpec{
436					SelectionSpec{
437						Operator: OpEqual,
438						Boundary: VersionSpec{
439							Major: NumConstraint{Num: 1},
440							Minor: NumConstraint{Num: 1},
441							Patch: NumConstraint{Num: 1},
442						},
443					},
444				},
445			},
446			``,
447		},
448		{
449			"!1.1.1",
450			UnionSpec{
451				IntersectionSpec{
452					SelectionSpec{
453						Operator: OpNotEqual,
454						Boundary: VersionSpec{
455							Major: NumConstraint{Num: 1},
456							Minor: NumConstraint{Num: 1},
457							Patch: NumConstraint{Num: 1},
458						},
459					},
460				},
461			},
462			``,
463		},
464		{
465			"1.*.*",
466			UnionSpec{
467				IntersectionSpec{
468					SelectionSpec{
469						Operator: OpMatch,
470						Boundary: VersionSpec{
471							Major: NumConstraint{Num: 1},
472							Minor: NumConstraint{Unconstrained: true},
473							Patch: NumConstraint{Unconstrained: true},
474						},
475					},
476				},
477			},
478			``,
479		},
480		{
481			"=1.*.*",
482			UnionSpec{
483				IntersectionSpec{
484					SelectionSpec{
485						Operator: OpEqual,
486						Boundary: VersionSpec{
487							Major: NumConstraint{Num: 1},
488							Minor: NumConstraint{Num: 0},
489							Patch: NumConstraint{Num: 0},
490						},
491					},
492				},
493			},
494			``,
495		},
496		{
497			"1.0.x",
498			UnionSpec{
499				IntersectionSpec{
500					SelectionSpec{
501						Operator: OpMatch,
502						Boundary: VersionSpec{
503							Major: NumConstraint{Num: 1},
504							Minor: NumConstraint{Num: 0},
505							Patch: NumConstraint{Unconstrained: true},
506						},
507					},
508				},
509			},
510			``,
511		},
512		{
513			"1.0.0 - 2.0.0",
514			UnionSpec{
515				IntersectionSpec{
516					SelectionSpec{
517						Operator: OpGreaterThanOrEqual,
518						Boundary: VersionSpec{
519							Major: NumConstraint{Num: 1},
520							Minor: NumConstraint{Num: 0},
521							Patch: NumConstraint{Num: 0},
522						},
523					},
524					SelectionSpec{
525						Operator: OpLessThanOrEqual,
526						Boundary: VersionSpec{
527							Major: NumConstraint{Num: 2},
528							Minor: NumConstraint{Num: 0},
529							Patch: NumConstraint{Num: 0},
530						},
531					},
532				},
533			},
534			``,
535		},
536		{
537			"1.*.* - 2.*.*",
538			UnionSpec{
539				IntersectionSpec{
540					SelectionSpec{
541						Operator: OpGreaterThanOrEqual,
542						Boundary: VersionSpec{
543							Major: NumConstraint{Num: 1},
544							Minor: NumConstraint{Num: 0},
545							Patch: NumConstraint{Num: 0},
546						},
547					},
548					SelectionSpec{
549						Operator: OpLessThan,
550						Boundary: VersionSpec{
551							Major: NumConstraint{Num: 3},
552							Minor: NumConstraint{Num: 0},
553							Patch: NumConstraint{Num: 0},
554						},
555					},
556				},
557			},
558			``,
559		},
560		{
561			">1.0.0 - 2.0.0",
562			nil,
563			`lower bound of range specified with "-" operator must be an exact version`,
564		},
565		{
566			"1.0.0 - >2.0.0",
567			nil,
568			`upper bound of range specified with "-" operator must be an exact version`,
569		},
570		{
571			">=1.0.0 <2.0.0",
572			UnionSpec{
573				IntersectionSpec{
574					SelectionSpec{
575						Operator: OpGreaterThanOrEqual,
576						Boundary: VersionSpec{
577							Major: NumConstraint{Num: 1},
578							Minor: NumConstraint{Num: 0},
579							Patch: NumConstraint{Num: 0},
580						},
581					},
582					SelectionSpec{
583						Operator: OpLessThan,
584						Boundary: VersionSpec{
585							Major: NumConstraint{Num: 2},
586							Minor: NumConstraint{Num: 0},
587							Patch: NumConstraint{Num: 0},
588						},
589					},
590				},
591			},
592			``,
593		},
594		{
595			">=1.0 <2 || 2.0-beta.1",
596			UnionSpec{
597				IntersectionSpec{
598					SelectionSpec{
599						Operator: OpGreaterThanOrEqual,
600						Boundary: VersionSpec{
601							Major: NumConstraint{Num: 1},
602							Minor: NumConstraint{Num: 0},
603							Patch: NumConstraint{Num: 0},
604						},
605					},
606					SelectionSpec{
607						Operator: OpLessThan,
608						Boundary: VersionSpec{
609							Major: NumConstraint{Num: 2},
610							Minor: NumConstraint{Num: 0},
611							Patch: NumConstraint{Num: 0},
612						},
613					},
614				},
615				IntersectionSpec{
616					SelectionSpec{
617						Operator: OpEqual,
618						Boundary: VersionSpec{
619							Major:      NumConstraint{Num: 2},
620							Minor:      NumConstraint{Num: 0},
621							Patch:      NumConstraint{Num: 0},
622							Prerelease: "beta.1",
623						},
624					},
625				},
626			},
627			``,
628		},
629		{
630			"1.0.0, 2.0.0",
631			nil,
632			`commas are not needed to separate version selections; separate with spaces instead`,
633		},
634		{
635			"= 1.1.1",
636			nil,
637			`no spaces allowed after operator "="`,
638		},
639		{
640			"=  1.1.1",
641			nil,
642			`no spaces allowed after operator "="`,
643		},
644		{
645			"garbage",
646			nil,
647			`the sequence "garbage" is not valid`,
648		},
649		{
650			"&1.1.0",
651			nil,
652			`invalid constraint operator "&"`,
653		},
654	}
655
656	for _, test := range tests {
657		t.Run(test.Input, func(t *testing.T) {
658			got, err := Parse(test.Input)
659			var gotErr string
660			if err != nil {
661				gotErr = err.Error()
662			}
663			if gotErr != test.WantErr {
664				t.Errorf("wrong error\ngot:  %s\nwant: %s", gotErr, test.WantErr)
665				return
666			}
667			if err != nil {
668				return
669			}
670
671			t.Logf("got: %s", spew.Sdump(got))
672			t.Logf("want: %s", spew.Sdump(test.Want))
673
674			for _, problem := range deep.Equal(got, test.Want) {
675				t.Error(problem)
676			}
677		})
678	}
679}
680