package inventory import ( "bytes" "encoding/json" "fmt" "reflect" "slices" "testing" ) // aNodesPackages is the resources of a node whose modules declare packages: one package two modules declare present, one // declared absent, and a resource that is no package. func aNodesPackages() ([]map[string]any, map[string]string) { return []map[string]any{ {"id": "web.pkg-curl", "type": "package", "package": "curl"}, {"id": "backup.pkg-curl", "type": "package", "package": "curl"}, {"id": "web.pkg-nano", "type": "package", "package": "nano", "absent": true}, {"id": "web.conf", "type": "file", "path": "/etc/web.conf"}, }, map[string]string{"web.pkg-curl": "web", "backup.pkg-curl": "backup", "web.pkg-nano": "web", "web.conf": "web"} } // 1840/AC5: the declared-packages mirror's value for a node names each package with the modules that declare it and whether // they declare it absent, and the same declaration is the same bytes however its resources are ordered, so a pass over an // unchanged node writes nothing. func TestTheDeclaredPackagesValueNamesEachPackageWithItsModules(t *testing.T) { resources, owner := aNodesPackages() got, err := DeclaredPackagesValue("one", resources, owner) if err != nil { t.Fatal(err) } want := `{"node":"one","packages":[{"package":"curl","modules":["backup","web"],"absent":false},` + `{"package":"nano","modules":["web"],"absent":true}]}` if string(got) != want { t.Errorf("the value is\n%s\nwant\n%s", got, want) } reversed := slices.Clone(resources) slices.Reverse(reversed) again, err := DeclaredPackagesValue("one", reversed, owner) if err != nil { t.Fatal(err) } if !bytes.Equal(got, again) { t.Errorf("the same declaration in another order is other bytes:\n%s\n%s", got, again) } if got := jsonFields(reflect.TypeOf(DeclaredPackagesMirrored{})); !slices.Equal(got, []string{"node", "packages"}) { t.Errorf("the value carries %v", got) } } // A node declaring no package has a value that says so, an empty list and not null. func TestANodeDeclaringNoPackageHasAnEmptyList(t *testing.T) { got, err := DeclaredPackagesValue("one", nil, nil) if err != nil || string(got) != `{"node":"one","packages":[]}` { t.Errorf("the value is %s (%v)", got, err) } } // 1840/S3: a node's value stays under the 64 KiB a mirror's key holds at far more packages than a node declares: 600 // packages, each declared by four modules, is a few times what the largest node declares today. func TestTheDeclaredPackagesValueOfABigNodeFitsAKey(t *testing.T) { var resources []map[string]any owner := map[string]string{} for p := 0; p < 600; p++ { for m := 0; m < 4; m++ { id := fmt.Sprintf("module-%d.pkg-%d", m, p) resources = append(resources, map[string]any{"id": id, "type": "package", "package": fmt.Sprintf("package-%d", p)}) owner[id] = fmt.Sprintf("module-%d", m) } } got, err := DeclaredPackagesValue("one", resources, owner) if err != nil { t.Fatal(err) } var v DeclaredPackagesMirrored if err := json.Unmarshal(got, &v); err != nil || len(v.Packages) != 600 { t.Fatalf("the value holds %d packages (%v)", len(v.Packages), err) } if len(got) >= 64<<10 { t.Errorf("the value is %d bytes, over the 64 KiB a key holds", len(got)) } }