Files
Jay Brown 17fc813823 Merged in feature/mutable-metadata1 (pull request #221)
M1, M2 and M3 complete

* M1, M2 and M3 complete

* review changes

* docs

* docs
2026-04-16 23:11:26 +00:00

190 lines
7.6 KiB
Go

package customschema_test
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/require"
"queryorchestration/internal/customschema"
)
// TestValidateSchemaDefinition exercises every rule listed in Plan
// Section 6.2 / Tracking Section 1.2 against the SchemaValidator. Every
// reject-case subtest is expected to FAIL against the current stub
// (which returns nil unconditionally) and PASS once the real
// implementation lands. Every accept-case subtest must pass against
// both the stub and the real implementation.
func TestValidateSchemaDefinition(t *testing.T) {
v := &customschema.SchemaValidator{}
// -------------------------------------------------------------------
// Accept-case subtests. These will "pass" against the stub and must
// remain green under the real implementation.
// -------------------------------------------------------------------
t.Run("valid_minimal_object_accepted", func(t *testing.T) {
schema := json.RawMessage(`{"type":"object","additionalProperties":false}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.NoError(t, err, "minimal valid object schema must be accepted")
})
t.Run("valid_object_with_properties_accepted", func(t *testing.T) {
schema := json.RawMessage(`{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":100},"count":{"type":"integer","minimum":0}}}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.NoError(t, err, "valid object schema with typed properties must be accepted")
})
t.Run("additional_properties_true_accepted", func(t *testing.T) {
// The rule is that additionalProperties must be *present*, not that
// it must be false. Explicit `true` must also be accepted.
schema := json.RawMessage(`{"type":"object","additionalProperties":true}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.NoError(t, err, "explicit additionalProperties=true must be accepted")
})
t.Run("at_max_size_accepted", func(t *testing.T) {
// Construct a schema whose serialized length is exactly
// MaxSchemaDefinitionBytes. This proves the size rule is strict
// greater-than, not >=.
//
// Fixed scaffold (63 bytes):
// {"type":"object","additionalProperties":false,"description":""}
// Padding lives inside the description string.
const scaffoldLen = 63
padLen := customschema.MaxSchemaDefinitionBytes - scaffoldLen
pad := strings.Repeat("a", padLen)
raw := `{"type":"object","additionalProperties":false,"description":"` + pad + `"}`
require.Equal(t, customschema.MaxSchemaDefinitionBytes, len(raw),
"test setup: payload must be exactly MaxSchemaDefinitionBytes bytes")
schema := json.RawMessage(raw)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.NoError(t, err, "schema exactly at MaxSchemaDefinitionBytes must be accepted")
})
// -------------------------------------------------------------------
// Reject-case subtests. These MUST FAIL against the stub (which
// returns nil) and MUST PASS against the real implementation.
// Expected-substring contract is documented inline so the golang
// engineer knows what error text to emit.
// -------------------------------------------------------------------
t.Run("invalid_json_rejected", func(t *testing.T) {
// Contract: error message must mention "invalid" (the golang
// engineer should wrap the json.Unmarshal error with a prefix
// containing the word "invalid", e.g. "invalid JSON: ...").
schema := json.RawMessage(`{this is not json`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "malformed JSON must be rejected")
require.Contains(t, strings.ToLower(err.Error()), "invalid",
"error message should mention 'invalid'")
})
t.Run("meta_validation_failure_rejected", func(t *testing.T) {
// Contract: error message must mention "type". The santhosh-tekuri
// library reports meta-validation failures against the draft
// 2020-12 meta-schema, and "type: nonsense" fails the enum check
// on the "type" keyword -- the word "type" is the most
// discriminating substring the golang engineer can reliably
// surface from the library's error output.
schema := json.RawMessage(`{"type":"nonsense"}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "schema failing JSON Schema meta-validation must be rejected")
require.Contains(t, strings.ToLower(err.Error()), "type",
"error message should mention 'type'")
})
t.Run("root_type_not_object_rejected", func(t *testing.T) {
// Contract: error message must mention "object" (Plan 6.2 rule 3
// is explicit that root type must be "object").
schema := json.RawMessage(`{"type":"array","items":{"type":"string"}}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "non-object root type must be rejected")
require.Contains(t, strings.ToLower(err.Error()), "object",
"error message should mention 'object'")
})
t.Run("root_type_missing_rejected", func(t *testing.T) {
// Contract: error message must mention "type". Omitted root
// `type` fails Plan 6.2 rule 3 (root type must equal "object")
// regardless of JSON Schema's own permissiveness.
schema := json.RawMessage(`{"additionalProperties":false}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "missing root type must be rejected")
require.Contains(t, strings.ToLower(err.Error()), "type",
"error message should mention 'type'")
})
t.Run("additional_properties_sub_schema_rejected", func(t *testing.T) {
// Contract: only boolean true/false is accepted. A sub-schema object
// like {"type":"string"} must be rejected with an error mentioning
// "additionalProperties". Plan Section 6.2 rule 4 is explicitly binary.
schema := json.RawMessage(`{"type":"object","additionalProperties":{"type":"string"}}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "sub-schema additionalProperties must be rejected")
require.Contains(t, err.Error(), "additionalProperties",
"error message should mention 'additionalProperties'")
})
t.Run("additional_properties_missing_rejected", func(t *testing.T) {
// Contract: error message must mention "additionalProperties"
// (Plan 6.2 rule 4 is explicit that this key must be present).
schema := json.RawMessage(`{"type":"object","properties":{"foo":{"type":"string"}}}`)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "missing root additionalProperties must be rejected")
require.Contains(t, err.Error(), "additionalProperties",
"error message should mention 'additionalProperties'")
})
t.Run("oversize_rejected", func(t *testing.T) {
// Contract: error message must mention "size" (Plan 6.2 rule 5:
// Size must be <= MaxSchemaDefinitionBytes).
const scaffoldLen = 63
// One byte past the limit.
padLen := customschema.MaxSchemaDefinitionBytes - scaffoldLen + 1
pad := strings.Repeat("a", padLen)
raw := `{"type":"object","additionalProperties":false,"description":"` + pad + `"}`
require.Equal(t, customschema.MaxSchemaDefinitionBytes+1, len(raw),
"test setup: payload must be exactly MaxSchemaDefinitionBytes+1 bytes")
schema := json.RawMessage(raw)
err := v.ValidateSchemaDefinition(schema)
t.Logf("stub err = %v", err)
require.Error(t, err, "schema larger than MaxSchemaDefinitionBytes must be rejected")
require.Contains(t, strings.ToLower(err.Error()), "size",
"error message should mention 'size'")
})
}