package register import ( "encoding/json" "io/fs" "sort" "strconv" "strings" "testing" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" ) func TestRegisteredResponseSchemasRequireEveryObjectProperty(t *testing.T) { assets := llm.NewAssetRegistry() if err := Register(completeRegistries(), assets); err != nil { t.Fatal(err) } schemaFS, err := assets.SchemaFS() if err != nil { t.Fatal(err) } count := 0 err = fs.WalkDir(schemaFS, ".", func(path string, entry fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } if entry.IsDir() || !strings.HasSuffix(path, ".json") { return nil } content, err := fs.ReadFile(schemaFS, path) if err != nil { return err } var schema any if err := json.Unmarshal(content, &schema); err != nil { t.Errorf("decode response schema %q: %v", path, err) return nil } count++ assertRequiredObjectProperties(t, path, "$", schema) return nil }) if err != nil { t.Fatal(err) } if count == 0 { t.Fatal("no registered response schemas found") } } func assertRequiredObjectProperties(t *testing.T, schemaPath, nodePath string, value any) { t.Helper() switch node := value.(type) { case map[string]any: if properties, ok := node["properties"].(map[string]any); ok { required := make(map[string]struct{}) if values, ok := node["required"].([]any); ok { for _, value := range values { if name, ok := value.(string); ok { required[name] = struct{}{} } } } propertyNames := make([]string, 0, len(properties)) for name := range properties { propertyNames = append(propertyNames, name) } sort.Strings(propertyNames) for _, name := range propertyNames { if _, ok := required[name]; !ok { t.Errorf("response schema %q object %s declares property %q without requiring it", schemaPath, nodePath, name) } } } for name, child := range node { assertRequiredObjectProperties(t, schemaPath, nodePath+"."+name, child) } case []any: for index, child := range node { assertRequiredObjectProperties(t, schemaPath, nodePath+"["+strconv.Itoa(index)+"]", child) } } }