Harden D&D scene description extraction
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"properties": {
|
||||
"scenes": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -97,6 +97,9 @@ func validate(value dnd.SceneDescriptionList) error {
|
||||
if value.Scenes == nil {
|
||||
return fmt.Errorf("scenes must be present")
|
||||
}
|
||||
if len(value.Scenes) == 0 {
|
||||
return fmt.Errorf("scenes must not be empty")
|
||||
}
|
||||
for index, scene := range value.Scenes {
|
||||
prefix := fmt.Sprintf("scenes[%d]", index)
|
||||
if strings.TrimSpace(scene.ID) == "" {
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||
@@ -86,6 +88,40 @@ func TestCodecOwnsDurableSchemaAndMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurableSchemaRequiresAtLeastOneScene(t *testing.T) {
|
||||
schemaDocument, err := jsonschema.UnmarshalJSON(bytes.NewReader(New().Schema().JSONSchema))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compiler := jsonschema.NewCompiler()
|
||||
if err := compiler.AddResource("schema.json", schemaDocument); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
compiled, err := compiler.Compile("schema.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
content []byte
|
||||
valid bool
|
||||
}{
|
||||
{name: "one scene", content: mustEncode(t, validList()), valid: true},
|
||||
{name: "empty scenes", content: []byte(`{"scenes":[]}`)},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
instance, err := jsonschema.UnmarshalJSON(bytes.NewReader(test.content))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = compiled.Validate(instance)
|
||||
if (err == nil) != test.valid {
|
||||
t.Fatalf("Validate() error = %v, want valid=%t", err, test.valid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodecStrictlyRejectsInvalidDurableBoundaries(t *testing.T) {
|
||||
validJSON := `{"scenes":[{"id":"chunk-000001","source_ref":{"source_id":"session","start_unit_id":1,"end_unit_id":1},"kind":"narrative","title":"Arrival","summary":"The party arrives."}]}`
|
||||
tests := []struct{ name, raw, want string }{
|
||||
@@ -95,6 +131,7 @@ func TestCodecStrictlyRejectsInvalidDurableBoundaries(t *testing.T) {
|
||||
{"unknown source field", strings.Replace(validJSON, `"end_unit_id":1`, `"end_unit_id":1,"unexpected":true`, 1), "unknown field"},
|
||||
{"trailing", `{"scenes":[]} {}`, "multiple JSON values"},
|
||||
{"missing scenes", `{}`, "scenes must be present"},
|
||||
{"empty scenes", `{"scenes":[]}`, "scenes must not be empty"},
|
||||
{"invalid kind", strings.Replace(validJSON, `"kind":"narrative"`, `"kind":"other"`, 1), "kind must be supported"},
|
||||
{"blank title", strings.Replace(validJSON, `"title":"Arrival"`, `"title":" "`, 1), "title must not be empty"},
|
||||
{"blank summary", strings.Replace(validJSON, `"summary":"The party arrives."`, `"summary":" "`, 1), "summary must not be empty"},
|
||||
@@ -138,6 +175,7 @@ func TestCodecRejectsRequiredApprovedValues(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"nil scenes", dnd.SceneDescriptionList{}, "scenes must be present"},
|
||||
{"empty scenes", dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{}}, "scenes must not be empty"},
|
||||
{"empty ID", mutate(validList(), func(value *dnd.SceneDescriptionList) { value.Scenes[0].ID = " " }), "id must not be empty"},
|
||||
{"unsupported kind", mutate(validList(), func(value *dnd.SceneDescriptionList) { value.Scenes[0].Kind = "unsupported" }), "kind must be supported"},
|
||||
{"empty title", mutate(validList(), func(value *dnd.SceneDescriptionList) { value.Scenes[0].Title = " " }), "title must not be empty"},
|
||||
@@ -155,6 +193,15 @@ func TestCodecRejectsRequiredApprovedValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustEncode(t *testing.T, value dnd.SceneDescriptionList) []byte {
|
||||
t.Helper()
|
||||
content, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func mutate(value dnd.SceneDescriptionList, change func(*dnd.SceneDescriptionList)) dnd.SceneDescriptionList {
|
||||
change(&value)
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user