|
| 1 | +package inventory |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "strings" |
| 6 | + "testing" |
| 7 | + |
| 8 | + "github.com/spf13/cobra" |
| 9 | + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
| 10 | +) |
| 11 | + |
| 12 | +func obj(name string, labels map[string]string, spec, status map[string]interface{}) unstructured.Unstructured { |
| 13 | + o := map[string]interface{}{"metadata": map[string]interface{}{"name": name}} |
| 14 | + if labels != nil { |
| 15 | + l := map[string]interface{}{} |
| 16 | + for k, v := range labels { |
| 17 | + l[k] = v |
| 18 | + } |
| 19 | + o["metadata"].(map[string]interface{})["labels"] = l |
| 20 | + } |
| 21 | + if spec != nil { |
| 22 | + o["spec"] = spec |
| 23 | + } |
| 24 | + if status != nil { |
| 25 | + o["status"] = status |
| 26 | + } |
| 27 | + return unstructured.Unstructured{Object: o} |
| 28 | +} |
| 29 | + |
| 30 | +func readyCond(s string) map[string]interface{} { |
| 31 | + return map[string]interface{}{ |
| 32 | + "conditions": []interface{}{map[string]interface{}{"type": "Ready", "status": s}}, |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +func TestStrAndIntStr(t *testing.T) { |
| 37 | + u := obj("n", nil, map[string]interface{}{ |
| 38 | + "hardware": map[string]interface{}{"cpuArchitecture": "arm64", "cpuCores": int64(96)}, |
| 39 | + }, nil) |
| 40 | + if got := str(u, "spec", "hardware", "cpuArchitecture"); got != "arm64" { |
| 41 | + t.Errorf("str arch = %q, want arm64", got) |
| 42 | + } |
| 43 | + if got := intStr(u, "spec", "hardware", "cpuCores"); got != "96" { |
| 44 | + t.Errorf("intStr cpu = %q, want 96", got) |
| 45 | + } |
| 46 | + if got := str(u, "spec", "missing"); got != none { |
| 47 | + t.Errorf("str missing = %q, want %s", got, none) |
| 48 | + } |
| 49 | + if got := intStr(u, "spec", "missing"); got != none { |
| 50 | + t.Errorf("intStr missing = %q, want %s", got, none) |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +func TestReady(t *testing.T) { |
| 55 | + cases := map[string]struct { |
| 56 | + status map[string]interface{} |
| 57 | + want string |
| 58 | + }{ |
| 59 | + "true": {readyCond("True"), "True"}, |
| 60 | + "false": {readyCond("False"), "False"}, |
| 61 | + "none": {nil, none}, |
| 62 | + "noReady": {map[string]interface{}{"conditions": []interface{}{map[string]interface{}{"type": "Accepted", "status": "True"}}}, none}, |
| 63 | + } |
| 64 | + for name, tc := range cases { |
| 65 | + t.Run(name, func(t *testing.T) { |
| 66 | + if got := ready(obj("x", nil, nil, tc.status)); got != tc.want { |
| 67 | + t.Errorf("ready = %q, want %q", got, tc.want) |
| 68 | + } |
| 69 | + }) |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +func TestSitesViewRow(t *testing.T) { |
| 74 | + u := obj("us-central-2a", map[string]string{labelRegion: "us-central-2"}, map[string]interface{}{ |
| 75 | + "regionRef": map[string]interface{}{"name": "us-central-2"}, |
| 76 | + "providerRef": map[string]interface{}{"name": "netactuate"}, |
| 77 | + "type": "Edge", |
| 78 | + }, readyCond("True")) |
| 79 | + got := sitesView.row(u) |
| 80 | + want := []any{"us-central-2a", "us-central-2", "netactuate", "Edge", "True"} |
| 81 | + if len(got) != len(want) { |
| 82 | + t.Fatalf("row len = %d, want %d", len(got), len(want)) |
| 83 | + } |
| 84 | + for i := range want { |
| 85 | + if got[i] != want[i] { |
| 86 | + t.Errorf("col %d = %v, want %v", i, got[i], want[i]) |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +func TestFilterItemsPredicate(t *testing.T) { |
| 92 | + list := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{ |
| 93 | + obj("a", nil, map[string]interface{}{"providerRef": map[string]interface{}{"name": "vultr"}}, nil), |
| 94 | + obj("b", nil, map[string]interface{}{"providerRef": map[string]interface{}{"name": "netactuate"}}, nil), |
| 95 | + }} |
| 96 | + filterItems(list, []func(u unstructured.Unstructured) bool{ |
| 97 | + func(u unstructured.Unstructured) bool { return str(u, "spec", "providerRef", "name") == "netactuate" }, |
| 98 | + }) |
| 99 | + if len(list.Items) != 1 || list.Items[0].GetName() != "b" { |
| 100 | + t.Fatalf("filterItems kept %v, want [b]", names(list)) |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +func TestFilterItemsNoPredicateKeepsAll(t *testing.T) { |
| 105 | + list := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{obj("a", nil, nil, nil), obj("b", nil, nil, nil)}} |
| 106 | + filterItems(list, nil) |
| 107 | + if len(list.Items) != 2 { |
| 108 | + t.Fatalf("filterItems dropped items without predicate: %v", names(list)) |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +func TestPrintTree(t *testing.T) { |
| 113 | + regions := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{ |
| 114 | + obj("us-central-2", nil, nil, nil), |
| 115 | + obj("eu-west-1", nil, nil, nil), |
| 116 | + }} |
| 117 | + sites := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{ |
| 118 | + obj("us-central-2a", nil, map[string]interface{}{"regionRef": map[string]interface{}{"name": "us-central-2"}}, nil), |
| 119 | + }} |
| 120 | + clusters := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{ |
| 121 | + obj("edge-1", map[string]string{labelRegion: "us-central-2"}, nil, nil), |
| 122 | + }} |
| 123 | + nodes := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{ |
| 124 | + obj("node-1", nil, map[string]interface{}{"siteRef": map[string]interface{}{"name": "us-central-2a"}}, nil), |
| 125 | + }} |
| 126 | + |
| 127 | + var buf bytes.Buffer |
| 128 | + printTree(&buf, "", regions, sites, clusters, nodes) |
| 129 | + out := buf.String() |
| 130 | + for _, want := range []string{"us-central-2", "eu-west-1", "clusters: edge-1", " us-central-2a", " node-1"} { |
| 131 | + if !strings.Contains(out, want) { |
| 132 | + t.Errorf("tree output missing %q\n%s", want, out) |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + buf.Reset() |
| 137 | + printTree(&buf, "us-central-2", regions, sites, clusters, nodes) |
| 138 | + if strings.Contains(buf.String(), "eu-west-1") { |
| 139 | + t.Errorf("--region filter leaked other region:\n%s", buf.String()) |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +func TestTallyAndUnion(t *testing.T) { |
| 144 | + items := []unstructured.Unstructured{ |
| 145 | + obj("a", nil, map[string]interface{}{"regionRef": map[string]interface{}{"name": "r1"}}, nil), |
| 146 | + obj("b", nil, map[string]interface{}{"regionRef": map[string]interface{}{"name": "r1"}}, nil), |
| 147 | + obj("c", nil, map[string]interface{}{"regionRef": map[string]interface{}{"name": "r2"}}, nil), |
| 148 | + } |
| 149 | + got := tally(items, func(u unstructured.Unstructured) string { return str(u, "spec", "regionRef", "name") }) |
| 150 | + if got["r1"] != 2 || got["r2"] != 1 { |
| 151 | + t.Errorf("tally = %v, want r1:2 r2:1", got) |
| 152 | + } |
| 153 | + u := union(map[string]int{"r1": 1}, map[string]int{"r2": 1, "r1": 3}) |
| 154 | + if strings.Join(u, ",") != "r1,r2" { |
| 155 | + t.Errorf("union = %v, want [r1 r2] sorted", u) |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +func TestRenderJSONYAMLUnstructured(t *testing.T) { |
| 160 | + list := &unstructured.UnstructuredList{} |
| 161 | + list.SetAPIVersion("inventory.miloapis.com/v1alpha1") |
| 162 | + list.SetKind("SiteList") |
| 163 | + list.Items = []unstructured.Unstructured{obj("s1", nil, map[string]interface{}{"type": "Edge"}, nil)} |
| 164 | + for _, f := range []string{"json", "yaml"} { |
| 165 | + var buf bytes.Buffer |
| 166 | + c := &cobra.Command{} |
| 167 | + c.SetOut(&buf) |
| 168 | + if err := render(c, f, list, sitesView.headers, sitesView.row); err != nil { |
| 169 | + t.Fatalf("%s render err: %v", f, err) |
| 170 | + } |
| 171 | + if !strings.Contains(buf.String(), "s1") { |
| 172 | + t.Errorf("%s output missing name s1:\n%s", f, buf.String()) |
| 173 | + } |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +func TestRenderInvalidFormat(t *testing.T) { |
| 178 | + c := &cobra.Command{} |
| 179 | + c.SetOut(&bytes.Buffer{}) |
| 180 | + if err := render(c, "xml", &unstructured.UnstructuredList{}, nil, nil); err == nil { |
| 181 | + t.Fatal("render with invalid format should error") |
| 182 | + } |
| 183 | +} |
| 184 | + |
| 185 | +func names(list *unstructured.UnstructuredList) []string { |
| 186 | + var out []string |
| 187 | + for _, i := range list.Items { |
| 188 | + out = append(out, i.GetName()) |
| 189 | + } |
| 190 | + return out |
| 191 | +} |
0 commit comments