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'") }) }