package main import ( "encoding/json" "os" "path/filepath" "strings" "testing" ) // The module's manifest, read the way the catalogue reads it, for the manifest tests. The same in // every desktop module that carries it. type manifest struct { Module string `json:"module"` Version string `json:"version"` Capabilities []string `json:"capabilities"` Requires []string `json:"requires"` Claims []claim `json:"claims"` Seats []any `json:"seats"` Tools []string `json:"tools"` Environment *environment `json:"environment"` Shell []shellCode `json:"shell"` Resources []map[string]any `json:"resources"` Build struct { Artifacts []map[string]any `json:"artifacts"` } `json:"build"` } type claim struct { Name string `json:"name"` Scope string `json:"scope"` Serves []string `json:"serves"` } type environment struct { Variables map[string]string `json:"variables"` Path []map[string]any `json:"path"` } type shellCode struct { For string `json:"for"` Slot string `json:"slot"` Code string `json:"code"` } func readManifest(t *testing.T) manifest { t.Helper() raw, err := os.ReadFile(filepath.Join("..", "..", "module.json")) if err != nil { t.Fatal(err) } dec := json.NewDecoder(strings.NewReader(string(raw))) dec.DisallowUnknownFields() var m manifest if err := dec.Decode(&m); err != nil { t.Fatalf("module.json: %v", err) } return m } func (m manifest) resource(t *testing.T, id string) map[string]any { t.Helper() for _, r := range m.Resources { if r["id"] == id { return r } } t.Fatalf("no resource %q", id) return nil } func (m manifest) packages() (present, absent []string) { for _, r := range m.Resources { if r["type"] == "package" { if r["absent"] == true { absent = append(absent, r["package"].(string)) } else { present = append(present, r["package"].(string)) } } } return present, absent } // sameAsSource checks that a file resource's content is byte for byte the module's source file, so // the readable file in the repository is what the machine gets. func (m manifest) sameAsSource(t *testing.T, id, source string) { t.Helper() want, err := os.ReadFile(filepath.Join("..", "..", source)) if err != nil { t.Fatal(err) } r := m.resource(t, id) if r["type"] != "file" { t.Fatalf("%s is a %v, not a file", id, r["type"]) } if got, _ := r["content"].(string); got != string(want) { t.Fatalf("resource %s's content is not %s: edit the source and copy it into module.json", id, source) } if r["owner"] != "${machine:account}" && !strings.HasPrefix(r["path"].(string), "/etc/") { t.Fatalf("%s under the home is the account's", id) } } // checkTheToolsAgree checks that the manifest lists the module's own tools exactly, that the bundle // serves each seat verb the claims promise as ., and that the Go bundle is declared. func checkTheToolsAgree(t *testing.T, m manifest) { t.Helper() own, seat := map[string]bool{}, map[string]bool{} for _, tool := range tools() { if strings.Contains(tool.Name, ".") { seat[tool.Name] = true } else { own[tool.Name] = true } if strings.TrimSpace(tool.Description) == "" { t.Errorf("%s has no description", tool.Name) } } listed := map[string]bool{} for _, name := range m.Tools { listed[name] = true if !own[name] { t.Errorf("module.json lists %s, which the bundle does not serve", name) } } for name := range own { if !listed[name] { t.Errorf("the bundle serves %s, which module.json does not list", name) } if !strings.HasPrefix(name, strings.ReplaceAll(m.Module, "-", "_")+"_") { t.Errorf("%s is not prefixed with the module's name", name) } } promised := map[string]bool{} for _, c := range m.Claims { for _, verb := range c.Serves { promised[c.Name+"."+verb] = true if !seat[c.Name+"."+verb] { t.Errorf("the claim on %s promises %s, which the bundle does not serve", c.Name, verb) } } } for name := range seat { if !promised[name] { t.Errorf("the bundle serves %s, which no claim promises", name) } } var bundle map[string]any for _, a := range m.Build.Artifacts { if a["kind"] == "bundle" { bundle = a } } if bundle == nil || bundle["language"] != "go" || bundle["system"] != "arch" || bundle["from"] != "cmd/"+m.Module+"-tools" || bundle["binary"] != m.Module+"-tools" { t.Errorf("the Go tools bundle: %v", bundle) } } // checkNoSecretsOrInstallationNames refuses what a catalogue manifest must never carry. func checkNoSecretsOrInstallationNames(t *testing.T) { t.Helper() raw, err := os.ReadFile(filepath.Join("..", "..", "module.json")) if err != nil { t.Fatal(err) } s := strings.ToLower(string(raw)) for _, never := range []string{"/home/", "jochen", "g14", "shanks", "novox.be", "api_key", ".hal/", "greenclip daemon"} { if strings.Contains(s, never) { t.Errorf("module.json names %q", never) } } }