2026-08-09 16:00:01 +00:00
package semanticreconcile
import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
"github.com/santhosh-tekuri/jsonschema/v6"
)
func TestResponseSchemaMetadataAndOwnership ( t * testing . T ) {
schema , err := LoadResponseSchema ( )
if err != nil {
t . Fatal ( err )
}
if schema . Key != ResponseSchemaKey || schema . ID != ResponseSchemaID || schema . Version != SchemaVersion || schema . Name != ResponseSchemaName {
t . Fatalf ( "schema metadata = %#v" , schema )
}
if ! json . Valid ( schema . JSONSchema ) || ! strings . HasPrefix ( schema . SHA256 , "sha256:" ) {
t . Fatalf ( "schema content metadata = %#v" , schema )
}
first := append ( [ ] byte ( nil ) , schema . JSONSchema ... )
schema . JSONSchema [ 0 ] = '['
loadedAgain , err := LoadResponseSchema ( )
if err != nil {
t . Fatal ( err )
}
if ! bytes . Equal ( loadedAgain . JSONSchema , first ) || ! json . Valid ( loadedAgain . JSONSchema ) {
t . Fatal ( "LoadResponseSchema() exposed shared schema content" )
}
}
func TestResponseSchemaAcceptsOnlyTheIntegerProposalShape ( t * testing . T ) {
schema , err := LoadResponseSchema ( )
if err != nil {
t . Fatal ( err )
}
tests := [ ] struct {
name string
value any
valid bool
} {
{ name : "empty proposal" , value : map [ string ] any { "duplicate_groups" : [ ] any { } } , valid : true } ,
2026-08-29 03:16:24 +00:00
{ name : "valid group" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2 } , "canonical_candidate_number" : 1 } } } , valid : true } ,
{ name : "semantic canonical mismatch" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2 } , "canonical_candidate_number" : 3 } } } , valid : true } ,
2026-08-09 16:00:01 +00:00
{ name : "missing proposal" , value : map [ string ] any { } , valid : false } ,
{ name : "unknown top-level field" , value : map [ string ] any { "duplicate_groups" : [ ] any { } , "extra" : true } , valid : false } ,
2026-08-29 03:16:24 +00:00
{ name : "missing members" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "canonical_candidate_number" : 1 } } } , valid : false } ,
{ name : "missing canonical" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2 } } } } , valid : false } ,
{ name : "unknown group field" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2 } , "canonical_candidate_number" : 1 , "name" : "replacement" } } } , valid : false } ,
{ name : "former candidate ID fields" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_ids" : [ ] any { 1 , 2 } , "canonical_candidate_id" : 1 } } } , valid : false } ,
{ name : "too few members" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 } , "canonical_candidate_number" : 1 } } } , valid : false } ,
{ name : "semantic repeated members" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 1 } , "canonical_candidate_number" : 1 } } } , valid : true } ,
{ name : "zero member" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 0 , 1 } , "canonical_candidate_number" : 1 } } } , valid : false } ,
{ name : "negative member" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { - 1 , 1 } , "canonical_candidate_number" : 1 } } } , valid : false } ,
{ name : "non-integer member" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2.5 } , "canonical_candidate_number" : 1 } } } , valid : false } ,
{ name : "zero canonical" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2 } , "canonical_candidate_number" : 0 } } } , valid : false } ,
{ name : "contextual selectors" , value : map [ string ] any { "duplicate_groups" : [ ] any { map [ string ] any { "candidate_numbers" : [ ] any { 1 , 2 } , "canonical_candidate_number" : 1 , "source_refs" : [ ] any { } } } } , valid : false } ,
2026-08-09 16:00:01 +00:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
content , err := json . Marshal ( test . value )
if err != nil {
t . Fatal ( err )
}
err = validateAgainstSchema ( content , schema . JSONSchema )
if ( err == nil ) != test . valid {
t . Fatalf ( "schema validation error = %v, want valid=%t" , err , test . valid )
}
} )
}
2026-08-29 03:16:24 +00:00
content := [ ] byte ( ` { "duplicate_groups":[ { "candidate_numbers":[1,2],"canonical_candidate_number":2}]} ` )
2026-08-09 16:00:01 +00:00
if err := validateAgainstSchema ( content , schema . JSONSchema ) ; err != nil {
t . Fatal ( err )
}
var response ProposalResponse
if err := json . Unmarshal ( content , & response ) ; err != nil {
t . Fatal ( err )
}
2026-08-29 03:16:24 +00:00
want := ProposalResponse { DuplicateGroups : [ ] DuplicateGroup { { CandidateNumbers : [ ] int { 1 , 2 } , CanonicalCandidateNumber : 2 } } }
2026-08-09 16:00:01 +00:00
if ! reflect . DeepEqual ( response , want ) {
t . Fatalf ( "decoded response = %#v, want %#v" , response , want )
}
}
func validateAgainstSchema ( instanceContent , schemaContent [ ] byte ) error {
instance , err := jsonschema . UnmarshalJSON ( bytes . NewReader ( instanceContent ) )
if err != nil {
return err
}
document , err := jsonschema . UnmarshalJSON ( bytes . NewReader ( schemaContent ) )
if err != nil {
return err
}
compiler := jsonschema . NewCompiler ( )
if err := compiler . AddResource ( "schema.json" , document ) ; err != nil {
return err
}
compiled , err := compiler . Compile ( "schema.json" )
if err != nil {
return err
}
return compiled . Validate ( instance )
}