19466: Fix typo and test with cwl_ properties
[arvados.git] / sdk / go / arvados / vocabulary_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "encoding/json"
9         "regexp"
10         "strings"
11
12         check "gopkg.in/check.v1"
13 )
14
15 type VocabularySuite struct {
16         testVoc *Vocabulary
17 }
18
19 var _ = check.Suite(&VocabularySuite{})
20
21 func (s *VocabularySuite) SetUpTest(c *check.C) {
22         s.testVoc = &Vocabulary{
23                 reservedTagKeys: map[string]bool{
24                         "reservedKey": true,
25                 },
26                 StrictTags: false,
27                 Tags: map[string]VocabularyTag{
28                         "IDTAGANIMALS": {
29                                 Strict: false,
30                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
31                                 Values: map[string]VocabularyTagValue{
32                                         "IDVALANIMAL1": {
33                                                 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Homo sapiens"}},
34                                         },
35                                         "IDVALANIMAL2": {
36                                                 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
37                                         },
38                                 },
39                         },
40                         "IDTAGIMPORTANCE": {
41                                 Strict: true,
42                                 Labels: []VocabularyLabel{{Label: "Importance"}, {Label: "Priority"}},
43                                 Values: map[string]VocabularyTagValue{
44                                         "IDVAL3": {
45                                                 Labels: []VocabularyLabel{{Label: "Low"}, {Label: "Low priority"}},
46                                         },
47                                         "IDVAL2": {
48                                                 Labels: []VocabularyLabel{{Label: "Medium"}, {Label: "Medium priority"}},
49                                         },
50                                         "IDVAL1": {
51                                                 Labels: []VocabularyLabel{{Label: "High"}, {Label: "High priority"}},
52                                         },
53                                 },
54                         },
55                         "IDTAGCOMMENT": {
56                                 Strict: false,
57                                 Labels: []VocabularyLabel{{Label: "Comment"}},
58                         },
59                 },
60         }
61         _, err := s.testVoc.validate()
62         c.Assert(err, check.IsNil)
63 }
64
65 func (s *VocabularySuite) TestCheck(c *check.C) {
66         tests := []struct {
67                 name          string
68                 strictVoc     bool
69                 props         string
70                 expectSuccess bool
71                 errMatches    string
72         }{
73                 // Check succeeds
74                 {
75                         "Known key, known value",
76                         false,
77                         `{"IDTAGANIMALS":"IDVALANIMAL1"}`,
78                         true,
79                         "",
80                 },
81                 {
82                         "Unknown non-alias key on non-strict vocabulary",
83                         false,
84                         `{"foo":"bar"}`,
85                         true,
86                         "",
87                 },
88                 {
89                         "Known non-strict key, unknown non-alias value",
90                         false,
91                         `{"IDTAGANIMALS":"IDVALANIMAL3"}`,
92                         true,
93                         "",
94                 },
95                 {
96                         "Undefined but reserved key on strict vocabulary",
97                         true,
98                         `{"reservedKey":"bar"}`,
99                         true,
100                         "",
101                 },
102                 {
103                         "Known key, list of known values",
104                         false,
105                         `{"IDTAGANIMALS":["IDVALANIMAL1","IDVALANIMAL2"]}`,
106                         true,
107                         "",
108                 },
109                 {
110                         "Known non-strict key, list of unknown non-alias values",
111                         false,
112                         `{"IDTAGCOMMENT":["hello world","lorem ipsum"]}`,
113                         true,
114                         "",
115                 },
116                 // Check fails
117                 {
118                         "Known first key & value; known 2nd key, unknown 2nd value",
119                         false,
120                         `{"IDTAGANIMALS":"IDVALANIMAL1", "IDTAGIMPORTANCE": "blah blah"}`,
121                         false,
122                         "tag value.*is not valid for key.*",
123                 },
124                 {
125                         "Unknown non-alias key on strict vocabulary",
126                         true,
127                         `{"foo":"bar"}`,
128                         false,
129                         "tag key.*is not defined in the vocabulary",
130                 },
131                 {
132                         "Known non-strict key, known value alias",
133                         false,
134                         `{"IDTAGANIMALS":"Loxodonta"}`,
135                         false,
136                         "tag value.*for key.* is an alias, must be provided as.*",
137                 },
138                 {
139                         "Known strict key, unknown non-alias value",
140                         false,
141                         `{"IDTAGIMPORTANCE":"Unimportant"}`,
142                         false,
143                         "tag value.*is not valid for key.*",
144                 },
145                 {
146                         "Known strict key, lowercase value regarded as alias",
147                         false,
148                         `{"IDTAGIMPORTANCE":"idval1"}`,
149                         false,
150                         "tag value.*for key.* is an alias, must be provided as.*",
151                 },
152                 {
153                         "Known strict key, known value alias",
154                         false,
155                         `{"IDTAGIMPORTANCE":"High"}`,
156                         false,
157                         "tag value.* for key.*is an alias, must be provided as.*",
158                 },
159                 {
160                         "Known strict key, list of known alias values",
161                         false,
162                         `{"IDTAGIMPORTANCE":["High", "Low"]}`,
163                         false,
164                         "tag value.*for key.*is an alias, must be provided as.*",
165                 },
166                 {
167                         "Known strict key, list of unknown non-alias values",
168                         false,
169                         `{"IDTAGIMPORTANCE":["foo","bar"]}`,
170                         false,
171                         "tag value.*is not valid for key.*",
172                 },
173                 {
174                         "Invalid value type",
175                         false,
176                         `{"IDTAGANIMALS":1}`,
177                         false,
178                         "value type for tag key.* was.*, but expected a string or list of strings",
179                 },
180                 {
181                         "Value list of invalid type",
182                         false,
183                         `{"IDTAGANIMALS":[1]}`,
184                         false,
185                         "value list element type for tag key.* was.*, but expected a string",
186                 },
187         }
188         for _, tt := range tests {
189                 c.Log(c.TestName()+" ", tt.name)
190                 s.testVoc.StrictTags = tt.strictVoc
191
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)
198                 } else {
199                         c.Assert(err, check.NotNil)
200                         c.Assert(err.Error(), check.Matches, tt.errMatches)
201                 }
202         }
203 }
204
205 func (s *VocabularySuite) TestNewVocabulary(c *check.C) {
206         tests := []struct {
207                 name       string
208                 data       string
209                 isValid    bool
210                 errMatches string
211                 expect     *Vocabulary
212         }{
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},
217                 {
218                         "Simple valid example",
219                         `{"tags":{
220                                 "IDTAGANIMALS":{
221                                         "strict": false,
222                                         "labels": [{"label": "Animal"}, {"label": "Creature"}],
223                                         "values": {
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"}]}
227                                         }
228                                 }
229                         }}`,
230                         true, "",
231                         &Vocabulary{
232                                 reservedTagKeys: map[string]bool{
233                                         "type":                  true,
234                                         "template_uuid":         true,
235                                         "groups":                true,
236                                         "username":              true,
237                                         "image_timestamp":       true,
238                                         "docker-image-repo-tag": true,
239                                         "filters":               true,
240                                         "container_request":     true,
241                                         "cwl_input":             true,
242                                         "cwl_output":            true,
243                                 },
244                                 StrictTags: false,
245                                 Tags: map[string]VocabularyTag{
246                                         "IDTAGANIMALS": {
247                                                 Strict: false,
248                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
249                                                 Values: map[string]VocabularyTagValue{
250                                                         "IDVALANIMAL1": {
251                                                                 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Homo sapiens"}},
252                                                         },
253                                                         "IDVALANIMAL2": {
254                                                                 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Loxodonta"}},
255                                                         },
256                                                         "DOG": {
257                                                                 Labels: []VocabularyLabel{{Label: "Dog"}, {Label: "Canis lupus familiaris"}, {Label: "dOg"}},
258                                                         },
259                                                 },
260                                         },
261                                 },
262                         },
263                 },
264                 {
265                         "Invalid JSON error with line & column numbers",
266                         `{"tags":{
267                                 "aKey":{
268                                         "labels": [,{"label": "A label"}]
269                                 }
270                         }}`,
271                         false, `invalid JSON format:.*\(line \d+, column \d+\)`, nil,
272                 },
273                 {
274                         "Invalid JSON with duplicate & reserved keys",
275                         `{"tags":{
276                                 "type":{
277                                         "strict": false,
278                                         "labels": [{"label": "Class", "label": "Type"}]
279                                 },
280                                 "type":{
281                                         "labels": []
282                                 }
283                         }}`,
284                         false, "(?s).*duplicate JSON key \"tags.type.labels.0.label\"\nduplicate JSON key \"tags.type\"\ntag key \"type\" is reserved", nil,
285                 },
286         }
287
288         for _, tt := range tests {
289                 c.Log(c.TestName()+" ", tt.name)
290                 voc, err := NewVocabulary([]byte(tt.data), []string{})
291                 if tt.isValid {
292                         c.Assert(err, check.IsNil)
293                 } else {
294                         c.Assert(err, check.NotNil)
295                         if tt.errMatches != "" {
296                                 c.Assert(err, check.ErrorMatches, tt.errMatches)
297                         }
298                 }
299                 c.Assert(voc, check.DeepEquals, tt.expect)
300         }
301 }
302
303 func (s *VocabularySuite) TestValidationErrors(c *check.C) {
304         tests := []struct {
305                 name       string
306                 voc        *Vocabulary
307                 errMatches []string
308         }{
309                 {
310                         "Strict vocabulary, no keys",
311                         &Vocabulary{
312                                 StrictTags: true,
313                         },
314                         []string{"vocabulary is strict but no tags are defined"},
315                 },
316                 {
317                         "Collision between tag key and tag key label",
318                         &Vocabulary{
319                                 StrictTags: false,
320                                 Tags: map[string]VocabularyTag{
321                                         "IDTAGANIMALS": {
322                                                 Strict: false,
323                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
324                                         },
325                                         "IDTAGCOMMENT": {
326                                                 Strict: false,
327                                                 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IDTAGANIMALS"}},
328                                         },
329                                 },
330                         },
331                         nil, // Depending on how the map is sorted, this could be one of two errors
332                 },
333                 {
334                         "Collision between tag key and tag key label (case-insensitive)",
335                         &Vocabulary{
336                                 StrictTags: false,
337                                 Tags: map[string]VocabularyTag{
338                                         "IDTAGANIMALS": {
339                                                 Strict: false,
340                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
341                                         },
342                                         "IDTAGCOMMENT": {
343                                                 Strict: false,
344                                                 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "IdTagAnimals"}},
345                                         },
346                                 },
347                         },
348                         nil, // Depending on how the map is sorted, this could be one of two errors
349                 },
350                 {
351                         "Collision between tag key labels",
352                         &Vocabulary{
353                                 StrictTags: false,
354                                 Tags: map[string]VocabularyTag{
355                                         "IDTAGANIMALS": {
356                                                 Strict: false,
357                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
358                                         },
359                                         "IDTAGCOMMENT": {
360                                                 Strict: false,
361                                                 Labels: []VocabularyLabel{{Label: "Comment"}, {Label: "Animal"}},
362                                         },
363                                 },
364                         },
365                         []string{"(?s).*tag label.*for key.*already seen.*"},
366                 },
367                 {
368                         "Collision between tag value and tag value label",
369                         &Vocabulary{
370                                 StrictTags: false,
371                                 Tags: map[string]VocabularyTag{
372                                         "IDTAGANIMALS": {
373                                                 Strict: false,
374                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
375                                                 Values: map[string]VocabularyTagValue{
376                                                         "IDVALANIMAL1": {
377                                                                 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
378                                                         },
379                                                         "IDVALANIMAL2": {
380                                                                 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDVALANIMAL1"}},
381                                                         },
382                                                 },
383                                         },
384                                 },
385                         },
386                         nil, // Depending on how the map is sorted, this could be one of two errors
387                 },
388                 {
389                         "Collision between tag value and tag value label (case-insensitive)",
390                         &Vocabulary{
391                                 StrictTags: false,
392                                 Tags: map[string]VocabularyTag{
393                                         "IDTAGANIMALS": {
394                                                 Strict: false,
395                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
396                                                 Values: map[string]VocabularyTagValue{
397                                                         "IDVALANIMAL1": {
398                                                                 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
399                                                         },
400                                                         "IDVALANIMAL2": {
401                                                                 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "IDValAnimal1"}},
402                                                         },
403                                                 },
404                                         },
405                                 },
406                         },
407                         nil, // Depending on how the map is sorted, this could be one of two errors
408                 },
409                 {
410                         "Collision between tag value labels",
411                         &Vocabulary{
412                                 StrictTags: false,
413                                 Tags: map[string]VocabularyTag{
414                                         "IDTAGANIMALS": {
415                                                 Strict: false,
416                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
417                                                 Values: map[string]VocabularyTagValue{
418                                                         "IDVALANIMAL1": {
419                                                                 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
420                                                         },
421                                                         "IDVALANIMAL2": {
422                                                                 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "Mammal"}},
423                                                         },
424                                                 },
425                                         },
426                                 },
427                         },
428                         []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
429                 },
430                 {
431                         "Collision between tag value labels (case-insensitive)",
432                         &Vocabulary{
433                                 StrictTags: false,
434                                 Tags: map[string]VocabularyTag{
435                                         "IDTAGANIMALS": {
436                                                 Strict: false,
437                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
438                                                 Values: map[string]VocabularyTagValue{
439                                                         "IDVALANIMAL1": {
440                                                                 Labels: []VocabularyLabel{{Label: "Human"}, {Label: "Mammal"}},
441                                                         },
442                                                         "IDVALANIMAL2": {
443                                                                 Labels: []VocabularyLabel{{Label: "Elephant"}, {Label: "mAMMAL"}},
444                                                         },
445                                                 },
446                                         },
447                                 },
448                         },
449                         []string{"(?s).*tag value label.*for pair.*already seen.*on value.*"},
450                 },
451                 {
452                         "Strict tag key, with no values",
453                         &Vocabulary{
454                                 StrictTags: false,
455                                 Tags: map[string]VocabularyTag{
456                                         "IDTAGANIMALS": {
457                                                 Strict: true,
458                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
459                                         },
460                                 },
461                         },
462                         []string{"(?s).*tag key.*is configured as strict but doesn't provide values"},
463                 },
464                 {
465                         "Multiple errors reported",
466                         &Vocabulary{
467                                 StrictTags: false,
468                                 Tags: map[string]VocabularyTag{
469                                         "IDTAGANIMALS": {
470                                                 Strict: true,
471                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Creature"}},
472                                         },
473                                         "IDTAGSIZES": {
474                                                 Labels: []VocabularyLabel{{Label: "Animal"}, {Label: "Size"}},
475                                         },
476                                 },
477                         },
478                         []string{
479                                 "(?s).*tag key.*is configured as strict but doesn't provide values.*",
480                                 "(?s).*tag label.*for key.*already seen.*",
481                         },
482                 },
483         }
484         for _, tt := range tests {
485                 c.Log(c.TestName()+" ", tt.name)
486                 validationErrs, err := tt.voc.validate()
487                 c.Assert(err, check.NotNil)
488                 for _, errMatch := range tt.errMatches {
489                         seen := false
490                         for _, validationErr := range validationErrs {
491                                 if regexp.MustCompile(errMatch).MatchString(validationErr) {
492                                         seen = true
493                                         break
494                                 }
495                         }
496                         if len(validationErrs) == 0 {
497                                 c.Assert(err, check.ErrorMatches, errMatch)
498                         } else {
499                                 c.Assert(seen, check.Equals, true,
500                                         check.Commentf("Expected to see error matching %q:\n%s",
501                                                 errMatch, strings.Join(validationErrs, "\n")))
502                         }
503                 }
504         }
505 }