package contract import ( "bytes" "encoding/json" "os" "path/filepath" "testing" "git.ipao.vip/rogee/go-sip/contracts" "github.com/santhosh-tekuri/jsonschema/v6" ) func TestV1RuntimeSchemaResolvesOnlyEmbeddedContracts(t *testing.T) { const version = contracts.SourceCommit schema, err := schemaFromBundle(version, "mq.schema.json") if err != nil { t.Fatal(err) } for _, name := range []string{"command-query", "call-query-result", "ai-config-result", "call-execute", "event-recording-uploaded"} { raw, err := contracts.Files.ReadFile("upstream/" + version + "/examples/" + name + ".json") if err != nil { t.Fatal(err) } value, err := jsonschema.UnmarshalJSON(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } if err := schema.Validate(value); err != nil { t.Fatalf("%s: %v", name, err) } } loader := bundleLoader{version: version} for _, address := range []string{"https://example.invalid/mq.schema.json", "file:///etc/passwd", "https://go-sip.local/contracts/other/mq.schema.json"} { if _, err := loader.Load(address); err == nil { t.Fatalf("external/cross-version schema accepted: %s", address) } } again, err := schemaFromBundle(version, "mq.schema.json") if err != nil || again != schema { t.Fatal("immutable compiled schema not reused") } } func TestBundleSchemaRejectsInvalidResources(t *testing.T) { for _, name := range []string{"../mq.schema.json", "", "missing.schema.json"} { if _, err := schemaFromBundle(contracts.SourceCommit, name); err == nil { t.Fatalf("invalid resource accepted: %q", name) } } } func TestProjectLocalConfigurationSchemasValidatePositivesAndRejectNegatives(t *testing.T) { read := func(name string) []byte { t.Helper() body, err := os.ReadFile(filepath.Join("..", "..", "docs", "contracts", "examples", name)) if err != nil { t.Fatal(err) } return body } if err := ValidateLocalConfigRead(read("config-read-task-v0.1.json")); err != nil { t.Fatalf("valid task config: %v", err) } var task map[string]any if err := json.Unmarshal(read("config-read-task-v0.1.json"), &task); err != nil { t.Fatal(err) } delete(task, "name") delete(task, "group_id") withoutDisplayFields, err := json.Marshal(task) if err != nil { t.Fatal(err) } if err := ValidateLocalConfigRead(withoutDisplayFields); err != nil { t.Fatalf("task without optional display fields: %v", err) } task["name"] = 123 withInvalidDisplayField, err := json.Marshal(task) if err != nil { t.Fatal(err) } if err := ValidateLocalConfigRead(withInvalidDisplayField); err == nil { t.Fatal("invalid optional task name accepted") } if err := ValidateLocalConfigRead(read("config-read-invalid-extra-property-v0.1.json")); err == nil { t.Fatal("config-read schema accepted an additional property") } } func TestV04LocalSchemasValidateExamples(t *testing.T) { for _, tt := range []struct { name string validate func([]byte) error valid bool }{ {"task-discovery-snapshot-page1-v0.4.json", ValidateLocalTaskDiscoveryV04, true}, {"task-discovery-snapshot-page2-v0.4.json", ValidateLocalTaskDiscoveryV04, true}, {"task-discovery-changes-v0.4.json", ValidateLocalTaskDiscoveryV04, true}, {"task-discovery-error-v0.4.json", ValidateLocalTaskDiscoveryV04, true}, {"task-discovery-invalid-v0.4.json", ValidateLocalTaskDiscoveryV04, false}, {"task-control-pause-v0.4.json", ValidateLocalTaskControlV04, true}, {"task-control-resume-v0.4.json", ValidateLocalTaskControlV04, true}, {"task-control-stop-v0.4.json", ValidateLocalTaskControlV04, true}, {"task-control-invalid-drain-v0.4.json", ValidateLocalTaskControlV04, false}, {"task-control-invalid-expiry-v0.4.json", ValidateLocalTaskControlV04, false}, {"call-execute-old-v0.4.json", ValidateLocalCallExecuteV04, true}, {"call-execute-recent-v0.4.json", ValidateLocalCallExecuteV04, true}, {"call-execute-altcallee-v0.4.json", ValidateLocalCallExecuteV04, true}, {"call-execute-invalid-expiry-v0.4.json", ValidateLocalCallExecuteV04, false}, } { t.Run(tt.name, func(t *testing.T) { body, err := os.ReadFile(filepath.Join("..", "..", "docs", "contracts", "examples", tt.name)) if err != nil { t.Fatal(err) } err = tt.validate(body) if (err == nil) != tt.valid { t.Fatalf("valid=%v, err=%v", tt.valid, err) } }) } }