1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
13 check "gopkg.in/check.v1"
16 type VocabularySuite struct {
20 var _ = check.Suite(&VocabularySuite{})
22 func (s *VocabularySuite) SetUpTest(c *check.C) {
23 s.testVoc = &Vocabulary{
24 reservedTagKeys: map[string]bool{
28 Tags: map[string]VocabularyTag{
31 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
32 Values: map[string]VocabularyTagValue{
34 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Homo sapiens"}},
37 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
43 Labels: []VocabularyLabel{{Label: "Importance"}, {Label: "Priority"}},
44 Values: map[string]VocabularyTagValue{
46 Labels: []VocabularyLabel{{Label: "Low"}, {Label: "Low priority"}},
49 Labels: []VocabularyLabel{{Label: "Medium"}, {Label: "Medium priority"}},
52 Labels: []VocabularyLabel{{Label: "High"}, {Label: "High priority"}},
58 Labels: []VocabularyLabel{{Label: "Comment"}},
62 _, err := s.testVoc.validate()
63 c.Assert(err, check.IsNil)
66 func (s *VocabularySuite) TestCheck(c *check.C) {
76 "Known key, known value",
78 `{"IDTAGANIMALS":"IDVALANIMAL1"}`,
83 "Unknown non-alias key on non-strict vocabulary",
90 "Known non-strict key, unknown non-alias value",
92 `{"IDTAGANIMALS":"IDVALANIMAL3"}`,
97 "Undefined but reserved key on strict vocabulary",
99 `{"reservedKey":"bar"}`,
104 "Known key, list of known values",
106 `{"IDTAGANIMALS":["IDVALANIMAL1","IDVALANIMAL2"]}`,
111 "Known non-strict key, list of unknown non-alias values",
113 `{"IDTAGCOMMENT":["hello world","lorem ipsum"]}`,
119 "Known first key & value; known 2nd key, unknown 2nd value",
121 `{"IDTAGANIMALS":"IDVALANIMAL1", "IDTAGIMPORTANCE": "blah blah"}`,
123 "tag value.*is not valid for key.*",
126 "Unknown non-alias key on strict vocabulary",
130 "tag key.*is not defined in the vocabulary",
133 "Known non-strict key, known value alias",
135 `{"IDTAGANIMALS":"Loxodonta"}`,
137 "tag value.*for key.* is an alias, must be provided as.*",
140 "Known strict key, unknown non-alias value",
142 `{"IDTAGIMPORTANCE":"Unimportant"}`,
144 "tag value.*is not valid for key.*",
147 "Known strict key, lowercase value regarded as alias",
149 `{"IDTAGIMPORTANCE":"idval1"}`,
151 "tag value.*for key.* is an alias, must be provided as.*",
154 "Known strict key, known value alias",
156 `{"IDTAGIMPORTANCE":"High"}`,
158 "tag value.* for key.*is an alias, must be provided as.*",
161 "Known strict key, list of known alias values",
163 `{"IDTAGIMPORTANCE":["High", "Low"]}`,
165 "tag value.*for key.*is an alias, must be provided as.*",
168 "Known strict key, list of unknown non-alias values",
170 `{"IDTAGIMPORTANCE":["foo","bar"]}`,
172 "tag value.*is not valid for key.*",
175 "Invalid value type",
177 `{"IDTAGANIMALS":1}`,
179 "value type for tag key.* was.*, but expected a string or list of strings",
182 "Value list of invalid type",
184 `{"IDTAGANIMALS":[1]}`,
186 "value list element type for tag key.* was.*, but expected a string",
189 for _, tt := range tests {
190 c.Log(c.TestName()+" ", tt.name)
191 s.testVoc.StrictTags = tt.strictVoc
193 var data map[string]interface{}
194 err := json.Unmarshal([]byte(tt.props), &data)
195 c.Assert(err, check.IsNil)
196 err = s.testVoc.Check(data)
197 if tt.expectSuccess {
198 c.Assert(err, check.IsNil)
200 c.Assert(err, check.NotNil)
201 c.Assert(err.Error(), check.Matches, tt.errMatches)
206 func (s *VocabularySuite) TestNewVocabulary(c *check.C) {
214 {"Empty data", "", true, "", &Vocabulary{}},
215 {"Invalid JSON", "foo", false, "invalid JSON format.*", nil},
216 {"Valid, empty JSON", "{}", false, ".*doesn't match Vocabulary format.*", nil},
217 {"Valid JSON, wrong data", `{"foo":"bar"}`, false, ".*doesn't match Vocabulary format.*", nil},
219 "Simple valid example",
223 "labels": [{"label": "Animal"}, {"label": "Creature"}],
225 "IDVALANIMAL1":{"labels":[{"label":"Human"}, {"label":"Homo sapiens"}]},
226 "IDVALANIMAL2":{"labels":[{"label":"Elephant"}, {"label":"Loxodonta"}]},
227 "DOG":{"labels":[{"label":"Dog"}, {"label":"Canis lupus familiaris"}, {"label":"dOg"}]}
233 reservedTagKeys: map[string]bool{
234 "container_request": true,
235 "container_uuid": true,
238 "docker-image-repo-tag": true,
241 "image_timestamp": true,
242 "template_uuid": true,
247 Tags: map[string]VocabularyTag{
250 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
251 Values: map[string]VocabularyTagValue{
253 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Homo sapiens"}},
256 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
259 Labels: []VocabularyLabel{{Label: "Dog"}, {Label: "Canis lupus familiaris"}, {Label: "dOg"}},
267 "Invalid JSON error with line & column numbers",
270 "labels": [,{"label": "A label"}]
273 false, `invalid JSON format:.*\(line \d+, column \d+\)`, nil,
276 "Invalid JSON with duplicate & reserved keys",
280 "labels": [{"label": "Class", "label": "Type"}]
286 false, "(?s).*duplicate JSON key \"tags.type.labels.0.label\"\nduplicate JSON key \"tags.type\"\ntag key \"type\" is reserved", nil,
290 for _, tt := range tests {
291 c.Log(c.TestName()+" ", tt.name)
292 voc, err := NewVocabulary([]byte(tt.data), []string{})
294 c.Assert(err, check.IsNil)
296 c.Assert(err, check.NotNil)
297 if tt.errMatches != "" {
298 c.Assert(err, check.ErrorMatches, tt.errMatches)
301 c.Assert(voc, check.DeepEquals, tt.expect)
305 func (s *VocabularySuite) TestValidSystemProperties(c *check.C) {
306 s.testVoc.StrictTags = true
307 properties := map[string]interface{}{
308 "arv:gitBranch": "main",
312 c.Check(s.testVoc.Check(properties), check.IsNil)
315 func (s *VocabularySuite) TestSystemPropertiesFirstCharacterAlphabetic(c *check.C) {
316 s.testVoc.StrictTags = true
317 properties := map[string]interface{}{"arv:": "value"}
318 c.Check(s.testVoc.Check(properties), check.NotNil)
319 // If we expand the list of allowed characters in the future, these lists
320 // may need adjustment to match.
321 for _, prefix := range []string{" ", ".", "_", "-", "1"} {
322 for _, suffix := range []string{"", "invalid"} {
323 key := fmt.Sprintf("arv:%s%s", prefix, suffix)
324 properties := map[string]interface{}{key: "value"}
325 c.Check(s.testVoc.Check(properties), check.NotNil)
330 func (s *VocabularySuite) TestSystemPropertiesPrefixTypo(c *check.C) {
331 s.testVoc.StrictTags = true
332 for _, key := range []string{
338 properties := map[string]interface{}{key: "value"}
339 c.Check(s.testVoc.Check(properties), check.NotNil)
343 func (s *VocabularySuite) TestValidationErrors(c *check.C) {
350 "Strict vocabulary, no keys",
354 []string{"vocabulary is strict but no tags are defined"},
357 "Collision between tag key and tag key label",
360 Tags: map[string]VocabularyTag{
363 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
367 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IDTAGANIMALS"}},
371 nil, // Depending on how the map is sorted, this could be one of two errors
374 "Collision between tag key and tag key label (case-insensitive)",
377 Tags: map[string]VocabularyTag{
380 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
384 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IdTagAnimals"}},
388 nil, // Depending on how the map is sorted, this could be one of two errors
391 "Collision between tag key labels",
394 Tags: map[string]VocabularyTag{
397 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
401 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "Animal"}},
405 []string{"(?s).*tag label.*for key.*already seen.*"},
408 "Collision between tag value and tag value label",
411 Tags: map[string]VocabularyTag{
414 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
415 Values: map[string]VocabularyTagValue{
417 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
420 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDVALANIMAL1"}},
426 nil, // Depending on how the map is sorted, this could be one of two errors
429 "Collision between tag value and tag value label (case-insensitive)",
432 Tags: map[string]VocabularyTag{
435 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
436 Values: map[string]VocabularyTagValue{
438 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
441 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDValAnimal1"}},
447 nil, // Depending on how the map is sorted, this could be one of two errors
450 "Collision between tag value labels",
453 Tags: map[string]VocabularyTag{
456 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
457 Values: map[string]VocabularyTagValue{
459 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
462 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Mammal"}},
468 []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
471 "Collision between tag value labels (case-insensitive)",
474 Tags: map[string]VocabularyTag{
477 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
478 Values: map[string]VocabularyTagValue{
480 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
483 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "mAMMAL"}},
489 []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
492 "Strict tag key, with no values",
495 Tags: map[string]VocabularyTag{
498 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
502 []string{"(?s).*tag key.*is configured as strict but doesn't provide values"},
505 "Multiple errors reported",
508 Tags: map[string]VocabularyTag{
511 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
514 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Size"}},
519 "(?s).*tag key.*is configured as strict but doesn't provide values.*",
520 "(?s).*tag label.*for key.*already seen.*",
524 for _, tt := range tests {
525 c.Log(c.TestName()+" ", tt.name)
526 validationErrs, err := tt.voc.validate()
527 c.Assert(err, check.NotNil)
528 for _, errMatch := range tt.errMatches {
530 for _, validationErr := range validationErrs {
531 if regexp.MustCompile(errMatch).MatchString(validationErr) {
536 if len(validationErrs) == 0 {
537 c.Assert(err, check.ErrorMatches, errMatch)
539 c.Assert(seen, check.Equals, true,
540 check.Commentf("Expected to see error matching %q:\n%s",
541 errMatch, strings.Join(validationErrs, "\n")))