1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
12 check "gopkg.in/check.v1"
15 type VocabularySuite struct {
19 var _ = check.Suite(&VocabularySuite{})
21 func (s *VocabularySuite) SetUpTest(c *check.C) {
22 s.testVoc = &Vocabulary{
23 reservedTagKeys: map[string]bool{
27 Tags: map[string]VocabularyTag{
30 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
31 Values: map[string]VocabularyTagValue{
33 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Homo sapiens"}},
36 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
42 Labels: []VocabularyLabel{{Label: "Importance"}, {Label: "Priority"}},
43 Values: map[string]VocabularyTagValue{
45 Labels: []VocabularyLabel{{Label: "Low"}, {Label: "Low priority"}},
48 Labels: []VocabularyLabel{{Label: "Medium"}, {Label: "Medium priority"}},
51 Labels: []VocabularyLabel{{Label: "High"}, {Label: "High priority"}},
57 Labels: []VocabularyLabel{{Label: "Comment"}},
61 _, err := s.testVoc.validate()
62 c.Assert(err, check.IsNil)
65 func (s *VocabularySuite) TestCheck(c *check.C) {
75 "Known key, known value",
77 `{"IDTAGANIMALS":"IDVALANIMAL1"}`,
82 "Unknown non-alias key on non-strict vocabulary",
89 "Known non-strict key, unknown non-alias value",
91 `{"IDTAGANIMALS":"IDVALANIMAL3"}`,
96 "Undefined but reserved key on strict vocabulary",
98 `{"reservedKey":"bar"}`,
103 "Known key, list of known values",
105 `{"IDTAGANIMALS":["IDVALANIMAL1","IDVALANIMAL2"]}`,
110 "Known non-strict key, list of unknown non-alias values",
112 `{"IDTAGCOMMENT":["hello world","lorem ipsum"]}`,
118 "Known first key & value; known 2nd key, unknown 2nd value",
120 `{"IDTAGANIMALS":"IDVALANIMAL1", "IDTAGIMPORTANCE": "blah blah"}`,
122 "tag value.*is not valid for key.*",
125 "Unknown non-alias key on strict vocabulary",
129 "tag key.*is not defined in the vocabulary",
132 "Known non-strict key, known value alias",
134 `{"IDTAGANIMALS":"Loxodonta"}`,
136 "tag value.*for key.* is an alias, must be provided as.*",
139 "Known strict key, unknown non-alias value",
141 `{"IDTAGIMPORTANCE":"Unimportant"}`,
143 "tag value.*is not valid for key.*",
146 "Known strict key, lowercase value regarded as alias",
148 `{"IDTAGIMPORTANCE":"idval1"}`,
150 "tag value.*for key.* is an alias, must be provided as.*",
153 "Known strict key, known value alias",
155 `{"IDTAGIMPORTANCE":"High"}`,
157 "tag value.* for key.*is an alias, must be provided as.*",
160 "Known strict key, list of known alias values",
162 `{"IDTAGIMPORTANCE":["High", "Low"]}`,
164 "tag value.*for key.*is an alias, must be provided as.*",
167 "Known strict key, list of unknown non-alias values",
169 `{"IDTAGIMPORTANCE":["foo","bar"]}`,
171 "tag value.*is not valid for key.*",
174 "Invalid value type",
176 `{"IDTAGANIMALS":1}`,
178 "value type for tag key.* was.*, but expected a string or list of strings",
181 "Value list of invalid type",
183 `{"IDTAGANIMALS":[1]}`,
185 "value list element type for tag key.* was.*, but expected a string",
188 for _, tt := range tests {
189 c.Log(c.TestName()+" ", tt.name)
190 s.testVoc.StrictTags = tt.strictVoc
192 var data map[string]interface{}
193 err := json.Unmarshal([]byte(tt.props), &data)
194 c.Assert(err, check.IsNil)
195 err = s.testVoc.Check(data)
196 if tt.expectSuccess {
197 c.Assert(err, check.IsNil)
199 c.Assert(err, check.NotNil)
200 c.Assert(err.Error(), check.Matches, tt.errMatches)
205 func (s *VocabularySuite) TestNewVocabulary(c *check.C) {
213 {"Empty data", "", true, "", &Vocabulary{}},
214 {"Invalid JSON", "foo", false, "invalid JSON format.*", nil},
215 {"Valid, empty JSON", "{}", false, ".*doesn't match Vocabulary format.*", nil},
216 {"Valid JSON, wrong data", `{"foo":"bar"}`, false, ".*doesn't match Vocabulary format.*", nil},
218 "Simple valid example",
222 "labels": [{"label": "Animal"}, {"label": "Creature"}],
224 "IDVALANIMAL1":{"labels":[{"label":"Human"}, {"label":"Homo sapiens"}]},
225 "IDVALANIMAL2":{"labels":[{"label":"Elephant"}, {"label":"Loxodonta"}]},
226 "DOG":{"labels":[{"label":"Dog"}, {"label":"Canis lupus familiaris"}, {"label":"dOg"}]}
232 reservedTagKeys: map[string]bool{
233 "container_request": true,
234 "container_uuid": true,
238 "docker-image-repo-tag": true,
241 "image_timestamp": true,
242 "template_uuid": true,
245 "workflowName": true,
248 Tags: map[string]VocabularyTag{
251 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
252 Values: map[string]VocabularyTagValue{
254 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Homo sapiens"}},
257 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
260 Labels: []VocabularyLabel{{Label: "Dog"}, {Label: "Canis lupus familiaris"}, {Label: "dOg"}},
268 "Invalid JSON error with line & column numbers",
271 "labels": [,{"label": "A label"}]
274 false, `invalid JSON format:.*\(line \d+, column \d+\)`, nil,
277 "Invalid JSON with duplicate & reserved keys",
281 "labels": [{"label": "Class", "label": "Type"}]
287 false, "(?s).*duplicate JSON key \"tags.type.labels.0.label\"\nduplicate JSON key \"tags.type\"\ntag key \"type\" is reserved", nil,
291 for _, tt := range tests {
292 c.Log(c.TestName()+" ", tt.name)
293 voc, err := NewVocabulary([]byte(tt.data), []string{})
295 c.Assert(err, check.IsNil)
297 c.Assert(err, check.NotNil)
298 if tt.errMatches != "" {
299 c.Assert(err, check.ErrorMatches, tt.errMatches)
302 c.Assert(voc, check.DeepEquals, tt.expect)
306 func (s *VocabularySuite) TestValidSystemProperties(c *check.C) {
307 s.testVoc.StrictTags = true
308 properties := map[string]interface{}{
309 "arv:gitBranch": "main",
313 c.Check(s.testVoc.Check(properties), check.IsNil)
316 func (s *VocabularySuite) TestSystemPropertiesPrefixTypo(c *check.C) {
317 s.testVoc.StrictTags = true
318 for _, key := range []string{
319 // Extra characters in prefix
332 properties := map[string]interface{}{key: "value"}
333 c.Check(s.testVoc.Check(properties), check.NotNil)
337 func (s *VocabularySuite) TestValidationErrors(c *check.C) {
344 "Strict vocabulary, no keys",
348 []string{"vocabulary is strict but no tags are defined"},
351 "Collision between tag key and tag key label",
354 Tags: map[string]VocabularyTag{
357 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
361 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IDTAGANIMALS"}},
365 nil, // Depending on how the map is sorted, this could be one of two errors
368 "Collision between tag key and tag key label (case-insensitive)",
371 Tags: map[string]VocabularyTag{
374 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
378 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IdTagAnimals"}},
382 nil, // Depending on how the map is sorted, this could be one of two errors
385 "Collision between tag key labels",
388 Tags: map[string]VocabularyTag{
391 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
395 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "Animal"}},
399 []string{"(?s).*tag label.*for key.*already seen.*"},
402 "Collision between tag value and tag value label",
405 Tags: map[string]VocabularyTag{
408 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
409 Values: map[string]VocabularyTagValue{
411 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
414 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDVALANIMAL1"}},
420 nil, // Depending on how the map is sorted, this could be one of two errors
423 "Collision between tag value and tag value label (case-insensitive)",
426 Tags: map[string]VocabularyTag{
429 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
430 Values: map[string]VocabularyTagValue{
432 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
435 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDValAnimal1"}},
441 nil, // Depending on how the map is sorted, this could be one of two errors
444 "Collision between tag value labels",
447 Tags: map[string]VocabularyTag{
450 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
451 Values: map[string]VocabularyTagValue{
453 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
456 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Mammal"}},
462 []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
465 "Collision between tag value labels (case-insensitive)",
468 Tags: map[string]VocabularyTag{
471 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
472 Values: map[string]VocabularyTagValue{
474 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
477 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "mAMMAL"}},
483 []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
486 "Strict tag key, with no values",
489 Tags: map[string]VocabularyTag{
492 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
496 []string{"(?s).*tag key.*is configured as strict but doesn't provide values"},
499 "Multiple errors reported",
502 Tags: map[string]VocabularyTag{
505 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
508 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Size"}},
513 "(?s).*tag key.*is configured as strict but doesn't provide values.*",
514 "(?s).*tag label.*for key.*already seen.*",
518 for _, tt := range tests {
519 c.Log(c.TestName()+" ", tt.name)
520 validationErrs, err := tt.voc.validate()
521 c.Assert(err, check.NotNil)
522 for _, errMatch := range tt.errMatches {
524 for _, validationErr := range validationErrs {
525 if regexp.MustCompile(errMatch).MatchString(validationErr) {
530 if len(validationErrs) == 0 {
531 c.Assert(err, check.ErrorMatches, errMatch)
533 c.Assert(seen, check.Equals, true,
534 check.Commentf("Expected to see error matching %q:\n%s",
535 errMatch, strings.Join(validationErrs, "\n")))