Merge branch 'main' into 18842-arv-mount-disk-config
[arvados.git] / sdk / go / arvados / vocabulary_test.go
index b2748c7be7f0c6df80248dd90e14be29d84f07ba..f31a4f984b36f7c70aa9987017d9596900c91173 100644 (file)
@@ -6,6 +6,8 @@ package arvados
 
 import (
        "encoding/json"
+       "regexp"
+       "strings"
 
        check "gopkg.in/check.v1"
 )
@@ -56,7 +58,7 @@ func (s *VocabularySuite) SetUpTest(c *check.C) {
                        },
                },
        }
-       err := s.testVoc.Validate()
+       _, err := s.testVoc.validate()
        c.Assert(err, check.IsNil)
 }
 
@@ -66,21 +68,122 @@ func (s *VocabularySuite) TestCheck(c *check.C) {
                strictVoc     bool
                props         string
                expectSuccess bool
+               errMatches    string
        }{
                // Check succeeds
-               {"Known key, known value", false, `{"IDTAGANIMALS":"IDVALANIMAL1"}`, true},
-               {"Unknown non-alias key on non-strict vocabulary", false, `{"foo":"bar"}`, true},
-               {"Known non-strict key, unknown non-alias value", false, `{"IDTAGANIMALS":"IDVALANIMAL3"}`, true},
-               {"Undefined but reserved key on strict vocabulary", true, `{"reservedKey":"bar"}`, true},
-               {"Known key, list of known values", false, `{"IDTAGANIMALS":["IDVALANIMAL1","IDVALANIMAL2"]}`, true},
-               {"Known non-strict key, list of unknown non-alias values", false, `{"IDTAGCOMMENT":["hello world","lorem ipsum"]}`, true},
+               {
+                       "Known key, known value",
+                       false,
+                       `{"IDTAGANIMALS":"IDVALANIMAL1"}`,
+                       true,
+                       "",
+               },
+               {
+                       "Unknown non-alias key on non-strict vocabulary",
+                       false,
+                       `{"foo":"bar"}`,
+                       true,
+                       "",
+               },
+               {
+                       "Known non-strict key, unknown non-alias value",
+                       false,
+                       `{"IDTAGANIMALS":"IDVALANIMAL3"}`,
+                       true,
+                       "",
+               },
+               {
+                       "Undefined but reserved key on strict vocabulary",
+                       true,
+                       `{"reservedKey":"bar"}`,
+                       true,
+                       "",
+               },
+               {
+                       "Known key, list of known values",
+                       false,
+                       `{"IDTAGANIMALS":["IDVALANIMAL1","IDVALANIMAL2"]}`,
+                       true,
+                       "",
+               },
+               {
+                       "Known non-strict key, list of unknown non-alias values",
+                       false,
+                       `{"IDTAGCOMMENT":["hello world","lorem ipsum"]}`,
+                       true,
+                       "",
+               },
                // Check fails
-               {"Unknown non-alias key on strict vocabulary", true, `{"foo":"bar"}`, false},
-               {"Known non-strict key, known value alias", false, `{"IDTAGANIMALS":"Loxodonta"}`, false},
-               {"Known strict key, unknown non-alias value", false, `{"IDTAGIMPORTANCE":"Unimportant"}`, false},
-               {"Known strict key, known value alias", false, `{"IDTAGIMPORTANCE":"High"}`, false},
-               {"Known strict key, list of known alias values", false, `{"IDTAGIMPORTANCE":["Unimportant","High"]}`, false},
-               {"Known strict key, list of unknown non-alias values", false, `{"IDTAGIMPORTANCE":["foo","bar"]}`, false},
+               {
+                       "Known first key & value; known 2nd key, unknown 2nd value",
+                       false,
+                       `{"IDTAGANIMALS":"IDVALANIMAL1", "IDTAGIMPORTANCE": "blah blah"}`,
+                       false,
+                       "tag value.*is not valid for key.*",
+               },
+               {
+                       "Unknown non-alias key on strict vocabulary",
+                       true,
+                       `{"foo":"bar"}`,
+                       false,
+                       "tag key.*is not defined in the vocabulary",
+               },
+               {
+                       "Known non-strict key, known value alias",
+                       false,
+                       `{"IDTAGANIMALS":"Loxodonta"}`,
+                       false,
+                       "tag value.*for key.* is an alias, must be provided as.*",
+               },
+               {
+                       "Known strict key, unknown non-alias value",
+                       false,
+                       `{"IDTAGIMPORTANCE":"Unimportant"}`,
+                       false,
+                       "tag value.*is not valid for key.*",
+               },
+               {
+                       "Known strict key, lowercase value regarded as alias",
+                       false,
+                       `{"IDTAGIMPORTANCE":"idval1"}`,
+                       false,
+                       "tag value.*for key.* is an alias, must be provided as.*",
+               },
+               {
+                       "Known strict key, known value alias",
+                       false,
+                       `{"IDTAGIMPORTANCE":"High"}`,
+                       false,
+                       "tag value.* for key.*is an alias, must be provided as.*",
+               },
+               {
+                       "Known strict key, list of known alias values",
+                       false,
+                       `{"IDTAGIMPORTANCE":["High", "Low"]}`,
+                       false,
+                       "tag value.*for key.*is an alias, must be provided as.*",
+               },
+               {
+                       "Known strict key, list of unknown non-alias values",
+                       false,
+                       `{"IDTAGIMPORTANCE":["foo","bar"]}`,
+                       false,
+                       "tag value.*is not valid for key.*",
+               },
+               {
+                       "Invalid value type",
+                       false,
+                       `{"IDTAGANIMALS":1}`,
+                       false,
+                       "value type for tag key.* was.*, but expected a string or list of strings",
+               },
+               {
+                       "Value list of invalid type",
+                       false,
+                       `{"IDTAGANIMALS":[1]}`,
+                       false,
+                       "value list element type for tag key.* was.*, but expected a string",
+               },
        }
        for _, tt := range tests {
                c.Log(c.TestName()+" ", tt.name)
@@ -94,6 +197,7 @@ func (s *VocabularySuite) TestCheck(c *check.C) {
                        c.Assert(err, check.IsNil)
                } else {
                        c.Assert(err, check.NotNil)
+                       c.Assert(err.Error(), check.Matches, tt.errMatches)
                }
        }
 }
@@ -118,7 +222,8 @@ func (s *VocabularySuite) TestNewVocabulary(c *check.C) {
                                        "labels": [{"label": "Animal"}, {"label": "Creature"}],
                                        "values": {
                                                "IDVALANIMAL1":{"labels":[{"label":"Human"}, {"label":"Homo sapiens"}]},
-                                               "IDVALANIMAL2":{"labels":[{"label":"Elephant"}, {"label":"Loxodonta"}]}
+                                               "IDVALANIMAL2":{"labels":[{"label":"Elephant"}, {"label":"Loxodonta"}]},
+                                               "DOG":{"labels":[{"label":"Dog"}, {"label":"Canis lupus familiaris"}, {"label":"dOg"}]}
                                        }
                                }
                        }}`,
@@ -133,6 +238,8 @@ func (s *VocabularySuite) TestNewVocabulary(c *check.C) {
                                        "docker-image-repo-tag": true,
                                        "filters":               true,
                                        "container_request":     true,
+                                       "cwl_input":             true,
+                                       "cwl_output":            true,
                                },
                                StrictTags: false,
                                Tags: map[string]VocabularyTag{
@@ -146,20 +253,35 @@ func (s *VocabularySuite) TestNewVocabulary(c *check.C) {
                                                        "IDVALANIMAL2": {
                                                                Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
                                                        },
+                                                       "DOG": {
+                                                               Labels: []VocabularyLabel{{Label: "Dog"}, {Label: "Canis lupus familiaris"}, {Label: "dOg"}},
+                                                       },
                                                },
                                        },
                                },
                        },
                },
                {
-                       "Valid data, but uses reserved key",
+                       "Invalid JSON error with line & column numbers",
+                       `{"tags":{
+                               "aKey":{
+                                       "labels": [,{"label": "A label"}]
+                               }
+                       }}`,
+                       false, `invalid JSON format:.*\(line \d+, column \d+\)`, nil,
+               },
+               {
+                       "Invalid JSON with duplicate & reserved keys",
                        `{"tags":{
                                "type":{
                                        "strict": false,
-                                       "labels": [{"label": "Type"}]
+                                       "labels": [{"label": "Class", "label": "Type"}]
+                               },
+                               "type":{
+                                       "labels": []
                                }
                        }}`,
-                       false, "tag key.*is reserved", nil,
+                       false, "(?s).*duplicate JSON key \"tags.type.labels.0.label\"\nduplicate JSON key \"tags.type\"\ntag key \"type\" is reserved", nil,
                },
        }
 
@@ -182,17 +304,51 @@ func (s *VocabularySuite) TestValidationErrors(c *check.C) {
        tests := []struct {
                name       string
                voc        *Vocabulary
-               errMatches string
+               errMatches []string
        }{
                {
                        "Strict vocabulary, no keys",
                        &Vocabulary{
                                StrictTags: true,
                        },
-                       "vocabulary is strict but no tags are defined",
+                       []string{"vocabulary is strict but no tags are defined"},
                },
                {
-                       "Duplicated tag keys",
+                       "Collision between tag key and tag key label",
+                       &Vocabulary{
+                               StrictTags: false,
+                               Tags: map[string]VocabularyTag{
+                                       "IDTAGANIMALS": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
+                                       },
+                                       "IDTAGCOMMENT": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IDTAGANIMALS"}},
+                                       },
+                               },
+                       },
+                       nil, // Depending on how the map is sorted, this could be one of two errors
+               },
+               {
+                       "Collision between tag key and tag key label (case-insensitive)",
+                       &Vocabulary{
+                               StrictTags: false,
+                               Tags: map[string]VocabularyTag{
+                                       "IDTAGANIMALS": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
+                                       },
+                                       "IDTAGCOMMENT": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IdTagAnimals"}},
+                                       },
+                               },
+                       },
+                       nil, // Depending on how the map is sorted, this could be one of two errors
+               },
+               {
+                       "Collision between tag key labels",
                        &Vocabulary{
                                StrictTags: false,
                                Tags: map[string]VocabularyTag{
@@ -206,10 +362,52 @@ func (s *VocabularySuite) TestValidationErrors(c *check.C) {
                                        },
                                },
                        },
-                       "tag label.*for key.*already seen.*",
+                       []string{"(?s).*tag label.*for key.*already seen.*"},
+               },
+               {
+                       "Collision between tag value and tag value label",
+                       &Vocabulary{
+                               StrictTags: false,
+                               Tags: map[string]VocabularyTag{
+                                       "IDTAGANIMALS": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
+                                               Values: map[string]VocabularyTagValue{
+                                                       "IDVALANIMAL1": {
+                                                               Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
+                                                       },
+                                                       "IDVALANIMAL2": {
+                                                               Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDVALANIMAL1"}},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       nil, // Depending on how the map is sorted, this could be one of two errors
+               },
+               {
+                       "Collision between tag value and tag value label (case-insensitive)",
+                       &Vocabulary{
+                               StrictTags: false,
+                               Tags: map[string]VocabularyTag{
+                                       "IDTAGANIMALS": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
+                                               Values: map[string]VocabularyTagValue{
+                                                       "IDVALANIMAL1": {
+                                                               Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
+                                                       },
+                                                       "IDVALANIMAL2": {
+                                                               Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDValAnimal1"}},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       nil, // Depending on how the map is sorted, this could be one of two errors
                },
                {
-                       "Duplicated tag values",
+                       "Collision between tag value labels",
                        &Vocabulary{
                                StrictTags: false,
                                Tags: map[string]VocabularyTag{
@@ -227,10 +425,31 @@ func (s *VocabularySuite) TestValidationErrors(c *check.C) {
                                        },
                                },
                        },
-                       "tag value label.*for pair.*already seen.*",
+                       []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
                },
                {
-                       "Strict key, no values",
+                       "Collision between tag value labels (case-insensitive)",
+                       &Vocabulary{
+                               StrictTags: false,
+                               Tags: map[string]VocabularyTag{
+                                       "IDTAGANIMALS": {
+                                               Strict: false,
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
+                                               Values: map[string]VocabularyTagValue{
+                                                       "IDVALANIMAL1": {
+                                                               Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
+                                                       },
+                                                       "IDVALANIMAL2": {
+                                                               Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "mAMMAL"}},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
+               },
+               {
+                       "Strict tag key, with no values",
                        &Vocabulary{
                                StrictTags: false,
                                Tags: map[string]VocabularyTag{
@@ -240,13 +459,47 @@ func (s *VocabularySuite) TestValidationErrors(c *check.C) {
                                        },
                                },
                        },
-                       "tag key.*is configured as strict but doesn't provide values",
+                       []string{"(?s).*tag key.*is configured as strict but doesn't provide values"},
+               },
+               {
+                       "Multiple errors reported",
+                       &Vocabulary{
+                               StrictTags: false,
+                               Tags: map[string]VocabularyTag{
+                                       "IDTAGANIMALS": {
+                                               Strict: true,
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
+                                       },
+                                       "IDTAGSIZES": {
+                                               Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Size"}},
+                                       },
+                               },
+                       },
+                       []string{
+                               "(?s).*tag key.*is configured as strict but doesn't provide values.*",
+                               "(?s).*tag label.*for key.*already seen.*",
+                       },
                },
        }
        for _, tt := range tests {
                c.Log(c.TestName()+" ", tt.name)
-               err := tt.voc.Validate()
+               validationErrs, err := tt.voc.validate()
                c.Assert(err, check.NotNil)
-               c.Assert(err, check.ErrorMatches, tt.errMatches)
+               for _, errMatch := range tt.errMatches {
+                       seen := false
+                       for _, validationErr := range validationErrs {
+                               if regexp.MustCompile(errMatch).MatchString(validationErr) {
+                                       seen = true
+                                       break
+                               }
+                       }
+                       if len(validationErrs) == 0 {
+                               c.Assert(err, check.ErrorMatches, errMatch)
+                       } else {
+                               c.Assert(seen, check.Equals, true,
+                                       check.Commentf("Expected to see error matching %q:\n%s",
+                                               errMatch, strings.Join(validationErrs, "\n")))
+                       }
+               }
        }
 }