master
go 1,060 lines 23.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package charttpl
4
5 import (
6 "errors"
7 "testing"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"
11
12 metrixselector "github.com/netdata/netdata/go/plugins/pkg/metrix/selector"
13 )
14
15 func TestSpecValidateScenarios(t *testing.T) {
16 tests := map[string]struct {
17 spec Spec
18 wantErr bool
19 errLike string
20 }{
21 "valid nested groups with inherited metrics union": {
22 spec: Spec{
23 Version: VersionV1,
24 Groups: []Group{
25 {
26 Family: "Database",
27 Metrics: []string{"mysql_queries_total"},
28 Groups: []Group{
29 {
30 Family: "Throughput",
31 Charts: []Chart{
32 {
33 Title: "Queries",
34 Context: "queries_total",
35 Units: "queries/s",
36 Algorithm: "incremental",
37 Dimensions: []Dimension{
38 {
39 Selector: "mysql_queries_total",
40 Name: "total",
41 },
42 },
43 },
44 },
45 },
46 },
47 },
48 },
49 },
50 },
51 "requires groups in groups-only mode": {
52 spec: Spec{
53 Version: VersionV1,
54 },
55 wantErr: true,
56 errLike: "groups[] is required",
57 },
58 "fails when selector metric not visible in scope": {
59 spec: Spec{
60 Version: VersionV1,
61 Groups: []Group{
62 {
63 Family: "Database",
64 Charts: []Chart{
65 {
66 Title: "Queries",
67 Context: "queries_total",
68 Units: "queries/s",
69 Algorithm: "incremental",
70 Dimensions: []Dimension{
71 {
72 Selector: "mysql_queries_total",
73 Name: "total",
74 },
75 },
76 },
77 },
78 },
79 },
80 },
81 wantErr: true,
82 errLike: "not visible in current group scope",
83 },
84 "fails on duplicate by_labels tokens": {
85 spec: Spec{
86 Version: VersionV1,
87 Groups: []Group{
88 {
89 Family: "Database",
90 Metrics: []string{"mysql_queries_total"},
91 Charts: []Chart{
92 {
93 Title: "Queries",
94 Context: "queries_total",
95 Units: "queries/s",
96 Algorithm: "incremental",
97 Instances: &Instances{
98 ByLabels: []string{"*", "*"},
99 },
100 Dimensions: []Dimension{
101 {
102 Selector: "mysql_queries_total",
103 Name: "total",
104 },
105 },
106 },
107 },
108 },
109 },
110 },
111 wantErr: true,
112 errLike: "duplicate token",
113 },
114 "fails on duplicate chart_defaults by_labels tokens": {
115 spec: Spec{
116 Version: VersionV1,
117 Groups: []Group{
118 {
119 Family: "Database",
120 ChartDefaults: &ChartDefaults{
121 Instances: &Instances{
122 ByLabels: []string{"*", "*"},
123 },
124 },
125 Metrics: []string{"mysql_queries_total"},
126 Charts: []Chart{
127 {
128 Title: "Queries",
129 Context: "queries_total",
130 Units: "queries/s",
131 Dimensions: []Dimension{
132 {Selector: "mysql_queries_total", Name: "total"},
133 },
134 },
135 },
136 },
137 },
138 },
139 wantErr: true,
140 errLike: "chart_defaults.instances.by_labels",
141 },
142 "fails on empty metric name": {
143 spec: Spec{
144 Version: VersionV1,
145 Groups: []Group{
146 {
147 Family: "Database",
148 Metrics: []string{"mysql_queries_total", " "},
149 Charts: []Chart{
150 {
151 Title: "Queries",
152 Context: "queries_total",
153 Units: "queries/s",
154 Dimensions: []Dimension{
155 {Selector: "mysql_queries_total", Name: "total"},
156 },
157 },
158 },
159 },
160 },
161 },
162 wantErr: true,
163 errLike: "metric name must not be empty",
164 },
165 "fails on duplicate metric names in group": {
166 spec: Spec{
167 Version: VersionV1,
168 Groups: []Group{
169 {
170 Family: "Database",
171 Metrics: []string{"mysql_queries_total", "mysql_queries_total"},
172 Charts: []Chart{
173 {
174 Title: "Queries",
175 Context: "queries_total",
176 Units: "queries/s",
177 Dimensions: []Dimension{
178 {Selector: "mysql_queries_total", Name: "total"},
179 },
180 },
181 },
182 },
183 },
184 },
185 wantErr: true,
186 errLike: "duplicate metric",
187 },
188 "fails on empty group family": {
189 spec: Spec{
190 Version: VersionV1,
191 Groups: []Group{
192 {
193 Family: " ",
194 Metrics: []string{"mysql_queries_total"},
195 Charts: []Chart{
196 {
197 Title: "Queries",
198 Context: "queries_total",
199 Units: "queries/s",
200 Dimensions: []Dimension{
201 {Selector: "mysql_queries_total", Name: "total"},
202 },
203 },
204 },
205 },
206 },
207 },
208 wantErr: true,
209 errLike: "groups[0].family",
210 },
211 "fails on empty chart title": {
212 spec: Spec{
213 Version: VersionV1,
214 Groups: []Group{
215 {
216 Family: "Database",
217 Metrics: []string{"mysql_queries_total"},
218 Charts: []Chart{
219 {
220 Title: " ",
221 Context: "queries_total",
222 Units: "queries/s",
223 Dimensions: []Dimension{
224 {Selector: "mysql_queries_total", Name: "total"},
225 },
226 },
227 },
228 },
229 },
230 },
231 wantErr: true,
232 errLike: ".title",
233 },
234 "fails on empty chart context": {
235 spec: Spec{
236 Version: VersionV1,
237 Groups: []Group{
238 {
239 Family: "Database",
240 Metrics: []string{"mysql_queries_total"},
241 Charts: []Chart{
242 {
243 Title: "Queries",
244 Context: " ",
245 Units: "queries/s",
246 Dimensions: []Dimension{
247 {Selector: "mysql_queries_total", Name: "total"},
248 },
249 },
250 },
251 },
252 },
253 },
254 wantErr: true,
255 errLike: ".context",
256 },
257 "fails on empty chart units": {
258 spec: Spec{
259 Version: VersionV1,
260 Groups: []Group{
261 {
262 Family: "Database",
263 Metrics: []string{"mysql_queries_total"},
264 Charts: []Chart{
265 {
266 Title: "Queries",
267 Context: "queries_total",
268 Units: " ",
269 Dimensions: []Dimension{
270 {Selector: "mysql_queries_total", Name: "total"},
271 },
272 },
273 },
274 },
275 },
276 },
277 wantErr: true,
278 errLike: ".units",
279 },
280 "fails on invalid algorithm": {
281 spec: Spec{
282 Version: VersionV1,
283 Groups: []Group{
284 {
285 Family: "Database",
286 Metrics: []string{"mysql_queries_total"},
287 Charts: []Chart{
288 {
289 Title: "Queries",
290 Context: "queries_total",
291 Units: "queries/s",
292 Algorithm: "delta",
293 Dimensions: []Dimension{
294 {Selector: "mysql_queries_total", Name: "total"},
295 },
296 },
297 },
298 },
299 },
300 },
301 wantErr: true,
302 errLike: ".algorithm",
303 },
304 "fails on invalid chart type": {
305 spec: Spec{
306 Version: VersionV1,
307 Groups: []Group{
308 {
309 Family: "Database",
310 Metrics: []string{"mysql_queries_total"},
311 Charts: []Chart{
312 {
313 Title: "Queries",
314 Context: "queries_total",
315 Units: "queries/s",
316 Type: "bars",
317 Dimensions: []Dimension{
318 {Selector: "mysql_queries_total", Name: "total"},
319 },
320 },
321 },
322 },
323 },
324 },
325 wantErr: true,
326 errLike: ".type",
327 },
328 "fails on negative lifecycle max_instances": {
329 spec: Spec{
330 Version: VersionV1,
331 Groups: []Group{
332 {
333 Family: "Database",
334 Metrics: []string{"mysql_queries_total"},
335 Charts: []Chart{
336 {
337 Title: "Queries",
338 Context: "queries_total",
339 Units: "queries/s",
340 Lifecycle: &Lifecycle{
341 MaxInstances: -1,
342 },
343 Dimensions: []Dimension{
344 {Selector: "mysql_queries_total", Name: "total"},
345 },
346 },
347 },
348 },
349 },
350 },
351 wantErr: true,
352 errLike: "lifecycle.max_instances",
353 },
354 "fails on negative lifecycle expire_after_cycles": {
355 spec: Spec{
356 Version: VersionV1,
357 Groups: []Group{
358 {
359 Family: "Database",
360 Metrics: []string{"mysql_queries_total"},
361 Charts: []Chart{
362 {
363 Title: "Queries",
364 Context: "queries_total",
365 Units: "queries/s",
366 Lifecycle: &Lifecycle{
367 ExpireAfterCycles: -1,
368 },
369 Dimensions: []Dimension{
370 {Selector: "mysql_queries_total", Name: "total"},
371 },
372 },
373 },
374 },
375 },
376 },
377 wantErr: true,
378 errLike: "lifecycle.expire_after_cycles",
379 },
380 "fails on negative lifecycle dimensions max_dims": {
381 spec: Spec{
382 Version: VersionV1,
383 Groups: []Group{
384 {
385 Family: "Database",
386 Metrics: []string{"mysql_queries_total"},
387 Charts: []Chart{
388 {
389 Title: "Queries",
390 Context: "queries_total",
391 Units: "queries/s",
392 Lifecycle: &Lifecycle{
393 Dimensions: &DimensionLifecycle{
394 MaxDims: -1,
395 },
396 },
397 Dimensions: []Dimension{
398 {Selector: "mysql_queries_total", Name: "total"},
399 },
400 },
401 },
402 },
403 },
404 },
405 wantErr: true,
406 errLike: "lifecycle.dimensions.max_dims",
407 },
408 "fails on negative lifecycle dimensions expire_after_cycles": {
409 spec: Spec{
410 Version: VersionV1,
411 Groups: []Group{
412 {
413 Family: "Database",
414 Metrics: []string{"mysql_queries_total"},
415 Charts: []Chart{
416 {
417 Title: "Queries",
418 Context: "queries_total",
419 Units: "queries/s",
420 Lifecycle: &Lifecycle{
421 Dimensions: &DimensionLifecycle{
422 ExpireAfterCycles: -1,
423 },
424 },
425 Dimensions: []Dimension{
426 {Selector: "mysql_queries_total", Name: "total"},
427 },
428 },
429 },
430 },
431 },
432 },
433 wantErr: true,
434 errLike: "lifecycle.dimensions.expire_after_cycles",
435 },
436 "fails on empty dimensions": {
437 spec: Spec{
438 Version: VersionV1,
439 Groups: []Group{
440 {
441 Family: "Database",
442 Metrics: []string{"mysql_queries_total"},
443 Charts: []Chart{
444 {
445 Title: "Queries",
446 Context: "queries_total",
447 Units: "queries/s",
448 Dimensions: nil,
449 },
450 },
451 },
452 },
453 },
454 wantErr: true,
455 errLike: ".dimensions",
456 },
457 "fails on empty selector": {
458 spec: Spec{
459 Version: VersionV1,
460 Groups: []Group{
461 {
462 Family: "Database",
463 Metrics: []string{"mysql_queries_total"},
464 Charts: []Chart{
465 {
466 Title: "Queries",
467 Context: "queries_total",
468 Units: "queries/s",
469 Dimensions: []Dimension{
470 {Selector: " ", Name: "total"},
471 },
472 },
473 },
474 },
475 },
476 },
477 wantErr: true,
478 errLike: ".selector",
479 },
480 "fails on selector without explicit metric": {
481 spec: Spec{
482 Version: VersionV1,
483 Groups: []Group{
484 {
485 Family: "Database",
486 Metrics: []string{"mysql_queries_total"},
487 Charts: []Chart{
488 {
489 Title: "Queries",
490 Context: "queries_total",
491 Units: "queries/s",
492 Dimensions: []Dimension{
493 {Selector: `{role="primary"}`, Name: "total"},
494 },
495 },
496 },
497 },
498 },
499 },
500 wantErr: true,
501 errLike: "selector must include explicit metric name",
502 },
503 "fails on name and name_from_label together": {
504 spec: Spec{
505 Version: VersionV1,
506 Groups: []Group{
507 {
508 Family: "Database",
509 Metrics: []string{"mysql_queries_total"},
510 Charts: []Chart{
511 {
512 Title: "Queries",
513 Context: "queries_total",
514 Units: "queries/s",
515 Dimensions: []Dimension{
516 {Selector: "mysql_queries_total", Name: "total", NameFromLabel: "mode"},
517 },
518 },
519 },
520 },
521 },
522 },
523 wantErr: true,
524 errLike: "use either name or name_from_label",
525 },
526 "fails on duplicate static dimension names": {
527 spec: Spec{
528 Version: VersionV1,
529 Groups: []Group{
530 {
531 Family: "Database",
532 Metrics: []string{"mysql_queries_total", "mysql_questions_total"},
533 Charts: []Chart{
534 {
535 Title: "Queries",
536 Context: "queries_total",
537 Units: "queries/s",
538 Dimensions: []Dimension{
539 {Selector: "mysql_queries_total", Name: "total"},
540 {Selector: "mysql_questions_total", Name: "total"},
541 },
542 },
543 },
544 },
545 },
546 },
547 wantErr: true,
548 errLike: "duplicate dimension name",
549 },
550 "fails on instances empty token": {
551 spec: Spec{
552 Version: VersionV1,
553 Groups: []Group{
554 {
555 Family: "Database",
556 Metrics: []string{"mysql_queries_total"},
557 Charts: []Chart{
558 {
559 Title: "Queries",
560 Context: "queries_total",
561 Units: "queries/s",
562 Instances: &Instances{
563 ByLabels: []string{"* ", " "},
564 },
565 Dimensions: []Dimension{
566 {Selector: "mysql_queries_total", Name: "total"},
567 },
568 },
569 },
570 },
571 },
572 },
573 wantErr: true,
574 errLike: "must not be empty",
575 },
576 "fails on instances bare exclude token": {
577 spec: Spec{
578 Version: VersionV1,
579 Groups: []Group{
580 {
581 Family: "Database",
582 Metrics: []string{"mysql_queries_total"},
583 Charts: []Chart{
584 {
585 Title: "Queries",
586 Context: "queries_total",
587 Units: "queries/s",
588 Instances: &Instances{
589 ByLabels: []string{"!"},
590 },
591 Dimensions: []Dimension{
592 {Selector: "mysql_queries_total", Name: "total"},
593 },
594 },
595 },
596 },
597 },
598 },
599 wantErr: true,
600 errLike: "exclude token must include label key",
601 },
602 "fails on instances with empty by_labels list": {
603 spec: Spec{
604 Version: VersionV1,
605 Groups: []Group{
606 {
607 Family: "Database",
608 Metrics: []string{"mysql_queries_total"},
609 Charts: []Chart{
610 {
611 Title: "Queries",
612 Context: "queries_total",
613 Units: "queries/s",
614 Instances: &Instances{
615 ByLabels: []string{},
616 },
617 Dimensions: []Dimension{
618 {Selector: "mysql_queries_total", Name: "total"},
619 },
620 },
621 },
622 },
623 },
624 },
625 wantErr: true,
626 errLike: "instances.by_labels",
627 },
628 "fails on whitespace-only dimension name": {
629 spec: Spec{
630 Version: VersionV1,
631 Groups: []Group{
632 {
633 Family: "Database",
634 Metrics: []string{"mysql_queries_total"},
635 Charts: []Chart{
636 {
637 Title: "Queries",
638 Context: "queries_total",
639 Units: "queries/s",
640 Dimensions: []Dimension{
641 {Selector: "mysql_queries_total", Name: " "},
642 },
643 },
644 },
645 },
646 },
647 },
648 wantErr: true,
649 errLike: "name",
650 },
651 "fails on whitespace-only name_from_label": {
652 spec: Spec{
653 Version: VersionV1,
654 Groups: []Group{
655 {
656 Family: "Database",
657 Metrics: []string{"mysql_queries_total"},
658 Charts: []Chart{
659 {
660 Title: "Queries",
661 Context: "queries_total",
662 Units: "queries/s",
663 Dimensions: []Dimension{
664 {Selector: "mysql_queries_total", NameFromLabel: " "},
665 },
666 },
667 },
668 },
669 },
670 },
671 wantErr: true,
672 errLike: "name_from_label",
673 },
674 "fails on empty engine selector entry": {
675 spec: Spec{
676 Version: VersionV1,
677 Engine: &Engine{
678 Selector: &metrixselector.Expr{
679 Allow: []string{" "},
680 },
681 },
682 Groups: []Group{
683 {
684 Family: "Database",
685 Metrics: []string{"mysql_queries_total"},
686 Charts: []Chart{
687 {
688 Title: "Queries",
689 Context: "queries_total",
690 Units: "queries/s",
691 Dimensions: []Dimension{
692 {Selector: "mysql_queries_total", Name: "total"},
693 },
694 },
695 },
696 },
697 },
698 },
699 wantErr: true,
700 errLike: "engine.selector.allow[0]",
701 },
702 "fails on invalid engine autogen max_type_id_len": {
703 spec: Spec{
704 Version: VersionV1,
705 Engine: &Engine{
706 Autogen: &EngineAutogen{
707 Enabled: true,
708 MaxTypeIDLen: 3,
709 },
710 },
711 Groups: []Group{
712 {
713 Family: "Database",
714 Metrics: []string{"mysql_queries_total"},
715 Charts: []Chart{
716 {
717 Title: "Queries",
718 Context: "queries_total",
719 Units: "queries/s",
720 Dimensions: []Dimension{
721 {Selector: "mysql_queries_total", Name: "total"},
722 },
723 },
724 },
725 },
726 },
727 },
728 wantErr: true,
729 errLike: "engine.autogen.max_type_id_len",
730 },
731 "fails on empty engine deny selector entry": {
732 spec: Spec{
733 Version: VersionV1,
734 Engine: &Engine{
735 Selector: &metrixselector.Expr{
736 Deny: []string{" "},
737 },
738 },
739 Groups: []Group{
740 {
741 Family: "Database",
742 Metrics: []string{"mysql_queries_total"},
743 Charts: []Chart{
744 {
745 Title: "Queries",
746 Context: "queries_total",
747 Units: "queries/s",
748 Dimensions: []Dimension{
749 {Selector: "mysql_queries_total", Name: "total"},
750 },
751 },
752 },
753 },
754 },
755 },
756 wantErr: true,
757 errLike: "engine.selector.deny[0]",
758 },
759 "fails on negative engine autogen max_type_id_len": {
760 spec: Spec{
761 Version: VersionV1,
762 Engine: &Engine{
763 Autogen: &EngineAutogen{
764 MaxTypeIDLen: -1,
765 },
766 },
767 Groups: []Group{
768 {
769 Family: "Database",
770 Metrics: []string{"mysql_queries_total"},
771 Charts: []Chart{
772 {
773 Title: "Queries",
774 Context: "queries_total",
775 Units: "queries/s",
776 Dimensions: []Dimension{
777 {Selector: "mysql_queries_total", Name: "total"},
778 },
779 },
780 },
781 },
782 },
783 },
784 wantErr: true,
785 errLike: "engine.autogen.max_type_id_len",
786 },
787 }
788
789 for name, tc := range tests {
790 t.Run(name, func(t *testing.T) {
791 err := tc.spec.Validate()
792 if tc.wantErr {
793 require.Error(t, err)
794 if tc.errLike != "" {
795 assert.ErrorContains(t, err, tc.errLike)
796 }
797 return
798 }
799 require.NoError(t, err)
800 })
801 }
802 }
803
804 func TestSpecValidateNilAndVersion(t *testing.T) {
805 var spec *Spec
806 err := spec.Validate()
807 require.Error(t, err)
808 assert.ErrorContains(t, err, "nil spec")
809
810 spec = &Spec{
811 Version: "v2",
812 Groups: []Group{
813 {
814 Family: "Database",
815 Metrics: []string{"mysql_queries_total"},
816 Charts: []Chart{
817 {
818 Title: "Queries",
819 Context: "queries_total",
820 Units: "queries/s",
821 Dimensions: []Dimension{
822 {Selector: "mysql_queries_total", Name: "total"},
823 },
824 },
825 },
826 },
827 },
828 }
829 err = spec.Validate()
830 require.Error(t, err)
831 assert.ErrorContains(t, err, "expected \"v1\"")
832 }
833
834 func TestSpecValidateReportsAllSemanticErrors(t *testing.T) {
835 spec := Spec{
836 Version: VersionV1,
837 Groups: []Group{
838 {
839 Family: "Database",
840 ChartDefaults: &ChartDefaults{
841 LabelPromoted: []string{"cluster", " "},
842 },
843 Metrics: []string{"mysql_queries_total"},
844 Charts: []Chart{
845 {
846 Title: "Queries",
847 Context: "queries_total",
848 Units: "queries/s",
849 LabelPromoted: []string{"instance", ""},
850 Instances: &Instances{ByLabels: []string{"*", "*"}},
851 Dimensions: []Dimension{{Selector: "mysql_queries_total", Name: "total"}},
852 },
853 },
854 },
855 },
856 }
857
858 err := spec.Validate()
859 require.Error(t, err)
860 assert.True(t, errors.Is(err, errSemanticCheck))
861 assert.ErrorContains(t, err, "groups[0].chart_defaults.label_promotion[1]")
862 assert.ErrorContains(t, err, "groups[0].charts[0].label_promotion[1]")
863 assert.ErrorContains(t, err, "groups[0].charts[0].instances.by_labels[1]")
864 }
865
866 func TestSpecValidateRejectsEmptyLabelPromotionEntries(t *testing.T) {
867 tests := map[string]struct {
868 spec Spec
869 errLike string
870 }{
871 "chart label_promotion": {
872 spec: Spec{
873 Version: VersionV1,
874 Groups: []Group{
875 {
876 Family: "Database",
877 Metrics: []string{"mysql_queries_total"},
878 Charts: []Chart{
879 {
880 Title: "Queries",
881 Context: "queries_total",
882 Units: "queries/s",
883 LabelPromoted: []string{"cluster", " "},
884 Dimensions: []Dimension{{Selector: "mysql_queries_total", Name: "total"}},
885 },
886 },
887 },
888 },
889 },
890 errLike: "groups[0].charts[0].label_promotion[1]",
891 },
892 "chart_defaults label_promotion": {
893 spec: Spec{
894 Version: VersionV1,
895 Groups: []Group{
896 {
897 Family: "Database",
898 ChartDefaults: &ChartDefaults{
899 LabelPromoted: []string{"cluster", ""},
900 },
901 Metrics: []string{"mysql_queries_total"},
902 Charts: []Chart{
903 {
904 Title: "Queries",
905 Context: "queries_total",
906 Units: "queries/s",
907 Dimensions: []Dimension{{Selector: "mysql_queries_total", Name: "total"}},
908 },
909 },
910 },
911 },
912 },
913 errLike: "groups[0].chart_defaults.label_promotion[1]",
914 },
915 }
916
917 for name, tc := range tests {
918 t.Run(name, func(t *testing.T) {
919 err := tc.spec.Validate()
920 require.Error(t, err)
921 assert.ErrorContains(t, err, tc.errLike)
922 })
923 }
924 }
925
926 func TestSpecValidateRejectsMalformedExcludeTokens(t *testing.T) {
927 tests := map[string]struct {
928 spec Spec
929 errLike string
930 }{
931 "chart instances": {
932 spec: Spec{
933 Version: VersionV1,
934 Groups: []Group{
935 {
936 Family: "Database",
937 Metrics: []string{"mysql_queries_total"},
938 Charts: []Chart{
939 {
940 Title: "Queries",
941 Context: "queries_total",
942 Units: "queries/s",
943 Instances: &Instances{
944 ByLabels: []string{"*", "! host"},
945 },
946 Dimensions: []Dimension{
947 {Selector: "mysql_queries_total", Name: "total"},
948 },
949 },
950 },
951 },
952 },
953 },
954 errLike: "exclude token must use !label_key syntax",
955 },
956 "chart_defaults instances": {
957 spec: Spec{
958 Version: VersionV1,
959 Groups: []Group{
960 {
961 Family: "Database",
962 ChartDefaults: &ChartDefaults{
963 Instances: &Instances{
964 ByLabels: []string{"*", "! host"},
965 },
966 },
967 Metrics: []string{"mysql_queries_total"},
968 Charts: []Chart{
969 {
970 Title: "Queries",
971 Context: "queries_total",
972 Units: "queries/s",
973 Dimensions: []Dimension{
974 {Selector: "mysql_queries_total", Name: "total"},
975 },
976 },
977 },
978 },
979 },
980 },
981 errLike: "chart_defaults.instances.by_labels[1]",
982 },
983 }
984
985 for name, tc := range tests {
986 t.Run(name, func(t *testing.T) {
987 err := tc.spec.Validate()
988 require.Error(t, err)
989 assert.ErrorContains(t, err, tc.errLike)
990 })
991 }
992 }
993
994 func TestSpecValidateRejectsNegationOnlyInstances(t *testing.T) {
995 tests := map[string]struct {
996 spec Spec
997 errLike string
998 }{
999 "chart instances": {
1000 spec: Spec{
1001 Version: VersionV1,
1002 Groups: []Group{
1003 {
1004 Family: "Database",
1005 Metrics: []string{"mysql_queries_total"},
1006 Charts: []Chart{
1007 {
1008 Title: "Queries",
1009 Context: "queries_total",
1010 Units: "queries/s",
1011 Instances: &Instances{
1012 ByLabels: []string{"!host"},
1013 },
1014 Dimensions: []Dimension{
1015 {Selector: "mysql_queries_total", Name: "total"},
1016 },
1017 },
1018 },
1019 },
1020 },
1021 },
1022 errLike: "must include at least one positive selector",
1023 },
1024 "chart_defaults instances": {
1025 spec: Spec{
1026 Version: VersionV1,
1027 Groups: []Group{
1028 {
1029 Family: "Database",
1030 ChartDefaults: &ChartDefaults{
1031 Instances: &Instances{
1032 ByLabels: []string{"!host"},
1033 },
1034 },
1035 Metrics: []string{"mysql_queries_total"},
1036 Charts: []Chart{
1037 {
1038 Title: "Queries",
1039 Context: "queries_total",
1040 Units: "queries/s",
1041 Dimensions: []Dimension{
1042 {Selector: "mysql_queries_total", Name: "total"},
1043 },
1044 },
1045 },
1046 },
1047 },
1048 },
1049 errLike: "chart_defaults.instances.by_labels",
1050 },
1051 }
1052
1053 for name, tc := range tests {
1054 t.Run(name, func(t *testing.T) {
1055 err := tc.spec.Validate()
1056 require.Error(t, err)
1057 assert.ErrorContains(t, err, tc.errLike)
1058 })
1059 }
1060 }