180 lines
4.8 KiB
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)
|
|
}
|