Rewrite brittle validation and schema tests
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
package scenes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||
)
|
||||
|
||||
func TestLoadResponseSchemaForScenes(t *testing.T) {
|
||||
@@ -31,62 +35,83 @@ func TestLoadResponseSchemaForScenes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseSchemaShapeUsesSourceUnitBoundaries(t *testing.T) {
|
||||
func TestResponseSchemaValidatesSceneResponses(t *testing.T) {
|
||||
schema, err := loadResponseSchema()
|
||||
if err != nil {
|
||||
t.Fatalf("loadResponseSchema() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(schema.JSONSchema, &decoded); err != nil {
|
||||
t.Fatalf("Unmarshal() error = %v, want nil", err)
|
||||
valid := validSceneSchemaResponse()
|
||||
validJSON, err := json.Marshal(valid)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal(validSceneSchemaResponse()) error = %v, want nil", err)
|
||||
}
|
||||
if decoded["$id"] != ResponseSchemaID {
|
||||
t.Fatalf("$id = %#v, want %q", decoded["$id"], ResponseSchemaID)
|
||||
}
|
||||
if decoded["additionalProperties"] != false {
|
||||
t.Fatalf("additionalProperties = %#v, want false", decoded["additionalProperties"])
|
||||
if err := validateJSONSchema(validJSON, schema.JSONSchema); err != nil {
|
||||
t.Fatalf("valid scene response rejected: %v", err)
|
||||
}
|
||||
|
||||
properties := decoded["properties"].(map[string]any)
|
||||
if _, ok := properties["artifact_type"]; ok {
|
||||
t.Fatal("schema includes artifact_type, want only scene response fields")
|
||||
}
|
||||
if _, ok := properties["session_scope"]; ok {
|
||||
t.Fatal("schema includes session_scope, want no session wrapper")
|
||||
tests := []struct {
|
||||
name string
|
||||
mutate func(map[string]any)
|
||||
}{
|
||||
{
|
||||
name: "obsolete segment boundaries",
|
||||
mutate: func(response map[string]any) {
|
||||
scene := response["scenes"].([]any)[0].(map[string]any)
|
||||
scene["start_segment_id"] = 1
|
||||
scene["end_segment_id"] = 2
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-positive start unit",
|
||||
mutate: func(response map[string]any) {
|
||||
response["scenes"].([]any)[0].(map[string]any)["start_unit_id"] = 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-positive end unit",
|
||||
mutate: func(response map[string]any) {
|
||||
response["scenes"].([]any)[0].(map[string]any)["end_unit_id"] = 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid primary mode",
|
||||
mutate: func(response map[string]any) {
|
||||
response["scenes"].([]any)[0].(map[string]any)["primary_mode"] = "Unknown"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid boundary confidence",
|
||||
mutate: func(response map[string]any) {
|
||||
response["scenes"].([]any)[0].(map[string]any)["boundary_confidence"] = "Unknown"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty boundary caveat",
|
||||
mutate: func(response map[string]any) {
|
||||
response["boundary_caveats"] = []any{""}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown property",
|
||||
mutate: func(response map[string]any) {
|
||||
response["unexpected"] = true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sceneProperties := properties["scenes"].(map[string]any)["items"].(map[string]any)["properties"].(map[string]any)
|
||||
for _, field := range []string{"scene_id", "start_segment_id", "end_segment_id"} {
|
||||
if _, ok := sceneProperties[field]; ok {
|
||||
t.Fatalf("scene schema includes old field %q", field)
|
||||
}
|
||||
}
|
||||
for _, field := range []string{"start_unit_id", "end_unit_id"} {
|
||||
property := sceneProperties[field].(map[string]any)
|
||||
if property["type"] != "integer" {
|
||||
t.Fatalf("%s type = %#v, want integer", field, property["type"])
|
||||
}
|
||||
if property["minimum"] != float64(1) {
|
||||
t.Fatalf("%s minimum = %#v, want 1", field, property["minimum"])
|
||||
}
|
||||
}
|
||||
|
||||
modeEnum := sceneProperties["primary_mode"].(map[string]any)["enum"].([]any)
|
||||
if !sameStrings(modeEnum, []string{"Recap", "Discussion", "Combat", "Narrative"}) {
|
||||
t.Fatalf("primary_mode enum = %#v, want Recap/Discussion/Combat/Narrative", modeEnum)
|
||||
}
|
||||
confidenceEnum := sceneProperties["boundary_confidence"].(map[string]any)["enum"].([]any)
|
||||
if !sameStrings(confidenceEnum, []string{"High", "Medium", "Low"}) {
|
||||
t.Fatalf("boundary_confidence enum = %#v, want High/Medium/Low", confidenceEnum)
|
||||
}
|
||||
|
||||
boundaryCaveatItems := decoded["properties"].(map[string]any)["boundary_caveats"].(map[string]any)["items"].(map[string]any)
|
||||
if boundaryCaveatItems["type"] != "string" {
|
||||
t.Fatalf("boundary_caveats.items.type = %#v, want string", boundaryCaveatItems["type"])
|
||||
}
|
||||
if boundaryCaveatItems["minLength"] != float64(1) {
|
||||
t.Fatalf("boundary_caveats.items.minLength = %#v, want 1", boundaryCaveatItems["minLength"])
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
response := validSceneSchemaResponse()
|
||||
tt.mutate(response)
|
||||
content, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal() error = %v, want nil", err)
|
||||
}
|
||||
if err := validateJSONSchema(content, schema.JSONSchema); err == nil {
|
||||
t.Fatal("validateJSONSchema() error = nil, want rejected response")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,14 +163,40 @@ func TestResponseSchemaJSONIsMutationSafe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func sameStrings(got []any, want []string) bool {
|
||||
if len(got) != len(want) {
|
||||
return false
|
||||
func validSceneSchemaResponse() map[string]any {
|
||||
return map[string]any{
|
||||
"scenes": []any{
|
||||
map[string]any{
|
||||
"start_unit_id": 1,
|
||||
"end_unit_id": 3,
|
||||
"short_title": "Ambush",
|
||||
"primary_mode": "Combat",
|
||||
"main_participants": []any{"Aria"},
|
||||
"summary": "The party fights.",
|
||||
"boundary_note": "Combat starts and resolves.",
|
||||
"boundary_confidence": "High",
|
||||
},
|
||||
},
|
||||
"boundary_caveats": []any{},
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateJSONSchema(instanceContent, schemaContent []byte) error {
|
||||
instance, err := jsonschema.UnmarshalJSON(bytes.NewReader(instanceContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse instance: %w", err)
|
||||
}
|
||||
schemaDocument, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse schema: %w", err)
|
||||
}
|
||||
compiler := jsonschema.NewCompiler()
|
||||
if err := compiler.AddResource("schema.json", schemaDocument); err != nil {
|
||||
return fmt.Errorf("load schema: %w", err)
|
||||
}
|
||||
schema, err := compiler.Compile("schema.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("compile schema: %w", err)
|
||||
}
|
||||
return schema.Validate(instance)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user