Files
notarius/internal/modules/dnd/chunk/scenes/schema_test.go

180 lines
4.8 KiB
Go

package scenes
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/santhosh-tekuri/jsonschema/v6"
)
func TestLoadResponseSchemaForScenes(t *testing.T) {
schema, err := loadResponseSchema()
if err != nil {
t.Fatalf("loadResponseSchema() error = %v, want nil", err)
}
if schema.Key != ResponseSchemaKey {
t.Fatalf("schema.Key = %q, want %q", schema.Key, ResponseSchemaKey)
}
if schema.ID != ResponseSchemaID {
t.Fatalf("schema.ID = %q, want %q", schema.ID, ResponseSchemaID)
}
if schema.Version != ResponseSchemaVersion {
t.Fatalf("schema.Version = %q, want %q", schema.Version, ResponseSchemaVersion)
}
if schema.Name != ResponseSchemaName {
t.Fatalf("schema.Name = %q, want %q", schema.Name, ResponseSchemaName)
}
if !strings.HasPrefix(schema.SHA256, "sha256:") {
t.Fatalf("schema.SHA256 = %q, want sha256 prefix", schema.SHA256)
}
if !json.Valid(schema.JSONSchema) {
t.Fatalf("schema.JSONSchema is invalid JSON: %s", schema.JSONSchema)
}
}
func TestResponseSchemaValidatesMinimalSceneResponses(t *testing.T) {
schema, err := loadResponseSchema()
if err != nil {
t.Fatalf("loadResponseSchema() 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 err := validateJSONSchema(validJSON, schema.JSONSchema); err != nil {
t.Fatalf("valid scene response rejected: %v", err)
}
tests := []struct {
name string
mutate func(map[string]any)
}{
{
name: "missing scenes",
mutate: func(response map[string]any) {
delete(response, "scenes")
},
},
{
name: "empty scenes",
mutate: func(response map[string]any) {
response["scenes"] = []any{}
},
},
{
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: "non-integer endpoint",
mutate: func(response map[string]any) {
response["scenes"].([]any)[0].(map[string]any)["start_unit_id"] = 1.5
},
},
{
name: "unknown top-level field",
mutate: func(response map[string]any) {
response["boundary_caveats"] = []any{}
},
},
{
name: "unknown scene field",
mutate: func(response map[string]any) {
response["scenes"].([]any)[0].(map[string]any)["short_title"] = "Old contract"
},
},
}
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")
}
})
}
}
func TestResponseStructAcceptsIntegerBoundaries(t *testing.T) {
raw := []byte(`{"scenes":[{"start_unit_id":1,"end_unit_id":3}]}`)
var response chunkResponse
if err := json.Unmarshal(raw, &response); err != nil {
t.Fatalf("Unmarshal() error = %v, want nil", err)
}
if got := response.Scenes[0].StartUnitID.String(); got != "1" {
t.Fatalf("StartUnitID = %q, want 1", got)
}
if got := response.Scenes[0].EndUnitID.String(); got != "3" {
t.Fatalf("EndUnitID = %q, want 3", got)
}
}
func TestResponseSchemaJSONIsMutationSafe(t *testing.T) {
first, err := loadResponseSchema()
if err != nil {
t.Fatalf("loadResponseSchema() error = %v, want nil", err)
}
first.JSONSchema[0] = '['
second, err := loadResponseSchema()
if err != nil {
t.Fatalf("loadResponseSchema() error = %v, want nil", err)
}
if !json.Valid(second.JSONSchema) {
t.Fatalf("schema JSON was mutated: %s", second.JSONSchema)
}
if len(second.JSONSchema) > 0 && second.JSONSchema[0] == '[' {
t.Fatal("schema JSON did not use defensive copy")
}
}
func validSceneSchemaResponse() map[string]any {
return map[string]any{
"scenes": []any{
map[string]any{
"start_unit_id": 1,
"end_unit_id": 3,
},
},
}
}
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)
}