mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
255 lines
6.6 KiB
Go
255 lines
6.6 KiB
Go
package common
|
|
|
|
import "testing"
|
|
|
|
func TestSanitizeSchemaForGemini_DereferencesRefsAndFlattensUnions(t *testing.T) {
|
|
schema := map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"parent": map[string]any{
|
|
"anyOf": []any{
|
|
map[string]any{"$ref": "#/$defs/pageParent"},
|
|
map[string]any{"$ref": "#/$defs/databaseParent"},
|
|
},
|
|
},
|
|
"icon": map[string]any{
|
|
"anyOf": []any{
|
|
map[string]any{"$ref": "#/$defs/emoji"},
|
|
map[string]any{"type": "null"},
|
|
},
|
|
},
|
|
"data": map[string]any{
|
|
"$ref": "#/$defs/dataPayload",
|
|
},
|
|
},
|
|
"required": []any{"parent", "icon", "missing"},
|
|
"$defs": map[string]any{
|
|
"pageParent": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"page_id": map[string]any{
|
|
"type": "string",
|
|
},
|
|
},
|
|
"required": []any{"page_id"},
|
|
},
|
|
"databaseParent": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"database_id": map[string]any{
|
|
"type": "string",
|
|
},
|
|
},
|
|
"required": []any{"database_id"},
|
|
},
|
|
"emoji": map[string]any{
|
|
"type": "string",
|
|
"pattern": "^:[a-z_]+:$",
|
|
},
|
|
"dataPayload": map[string]any{
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": map[string]any{
|
|
"name": map[string]any{
|
|
"type": "string",
|
|
"minLength": 1,
|
|
},
|
|
"count": map[string]any{
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
},
|
|
},
|
|
"required": []any{"name"},
|
|
},
|
|
},
|
|
}
|
|
|
|
got := SanitizeSchemaForGemini(schema)
|
|
assertSchemaKeyAbsent(t, got, "$defs")
|
|
assertSchemaKeyAbsent(t, got, "$ref")
|
|
assertSchemaKeyAbsent(t, got, "anyOf")
|
|
assertSchemaKeyAbsent(t, got, "oneOf")
|
|
assertSchemaKeyAbsent(t, got, "allOf")
|
|
assertSchemaKeyAbsent(t, got, "additionalProperties")
|
|
assertSchemaKeyAbsent(t, got, "pattern")
|
|
assertSchemaKeyAbsent(t, got, "minLength")
|
|
assertSchemaKeyAbsent(t, got, "minimum")
|
|
|
|
if got["type"] != "object" {
|
|
t.Fatalf("top-level type = %#v, want object", got["type"])
|
|
}
|
|
|
|
props, ok := got["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("properties = %#v, want map", got["properties"])
|
|
}
|
|
|
|
parent, ok := props["parent"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("parent schema = %#v, want map", props["parent"])
|
|
}
|
|
if parent["type"] != "object" {
|
|
t.Fatalf("parent.type = %#v, want object", parent["type"])
|
|
}
|
|
parentProps, ok := parent["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("parent.properties = %#v, want map", parent["properties"])
|
|
}
|
|
if _, found := parentProps["page_id"]; !found {
|
|
t.Fatalf("parent.properties missing page_id: %#v", parentProps)
|
|
}
|
|
if _, found := parentProps["database_id"]; !found {
|
|
t.Fatalf("parent.properties missing database_id: %#v", parentProps)
|
|
}
|
|
if _, hasRequired := parent["required"]; hasRequired {
|
|
t.Fatalf("parent.required = %#v, want omitted for merged anyOf branches", parent["required"])
|
|
}
|
|
|
|
icon, ok := props["icon"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("icon schema = %#v, want map", props["icon"])
|
|
}
|
|
if icon["type"] != "string" {
|
|
t.Fatalf("icon.type = %#v, want string", icon["type"])
|
|
}
|
|
|
|
data, ok := props["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("data schema = %#v, want map", props["data"])
|
|
}
|
|
if data["type"] != "object" {
|
|
t.Fatalf("data.type = %#v, want object", data["type"])
|
|
}
|
|
dataProps, ok := data["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("data.properties = %#v, want map", data["properties"])
|
|
}
|
|
if _, found := dataProps["name"]; !found {
|
|
t.Fatalf("data.properties missing name: %#v", dataProps)
|
|
}
|
|
if _, found := dataProps["count"]; !found {
|
|
t.Fatalf("data.properties missing count: %#v", dataProps)
|
|
}
|
|
|
|
required, ok := got["required"].([]string)
|
|
if !ok {
|
|
t.Fatalf("required = %#v, want []string", got["required"])
|
|
}
|
|
if len(required) != 2 || required[0] != "parent" || required[1] != "icon" {
|
|
t.Fatalf("required = %#v, want [parent icon]", required)
|
|
}
|
|
}
|
|
|
|
func TestSanitizeSchemaForGemini_MergesAllOfAndFiltersRequired(t *testing.T) {
|
|
schema := map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"payload": map[string]any{
|
|
"allOf": []any{
|
|
map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"id": map[string]any{
|
|
"type": "string",
|
|
},
|
|
},
|
|
"required": []any{"id"},
|
|
},
|
|
map[string]any{
|
|
"properties": map[string]any{
|
|
"name": map[string]any{
|
|
"type": "string",
|
|
},
|
|
"count": map[string]any{
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
},
|
|
},
|
|
"required": []any{"name", "missing"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
got := SanitizeSchemaForGemini(schema)
|
|
props := got["properties"].(map[string]any)
|
|
payload := props["payload"].(map[string]any)
|
|
|
|
if payload["type"] != "object" {
|
|
t.Fatalf("payload.type = %#v, want object", payload["type"])
|
|
}
|
|
payloadProps, ok := payload["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("payload.properties = %#v, want map", payload["properties"])
|
|
}
|
|
for _, key := range []string{"id", "name", "count"} {
|
|
if _, found := payloadProps[key]; !found {
|
|
t.Fatalf("payload.properties missing %q: %#v", key, payloadProps)
|
|
}
|
|
}
|
|
|
|
required, ok := payload["required"].([]string)
|
|
if !ok {
|
|
t.Fatalf("payload.required = %#v, want []string", payload["required"])
|
|
}
|
|
if len(required) != 2 || required[0] != "id" || required[1] != "name" {
|
|
t.Fatalf("payload.required = %#v, want [id name]", required)
|
|
}
|
|
|
|
assertSchemaKeyAbsent(t, payload, "allOf")
|
|
assertSchemaKeyAbsent(t, payload, "minimum")
|
|
}
|
|
|
|
func TestSanitizeSchemaForGemini_HandlesRecursiveRefs(t *testing.T) {
|
|
schema := map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"tree": map[string]any{
|
|
"$ref": "#/$defs/node",
|
|
},
|
|
},
|
|
"$defs": map[string]any{
|
|
"node": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"name": map[string]any{
|
|
"type": "string",
|
|
},
|
|
"child": map[string]any{
|
|
"$ref": "#/$defs/node",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
got := SanitizeSchemaForGemini(schema)
|
|
props := got["properties"].(map[string]any)
|
|
tree := props["tree"].(map[string]any)
|
|
if tree["type"] != "object" {
|
|
t.Fatalf("tree.type = %#v, want object", tree["type"])
|
|
}
|
|
assertSchemaKeyAbsent(t, tree, "$ref")
|
|
}
|
|
|
|
func assertSchemaKeyAbsent(t *testing.T, value any, key string) {
|
|
t.Helper()
|
|
|
|
switch typed := value.(type) {
|
|
case map[string]any:
|
|
if _, found := typed[key]; found {
|
|
t.Fatalf("schema still contains key %q: %#v", key, typed)
|
|
}
|
|
for _, nested := range typed {
|
|
assertSchemaKeyAbsent(t, nested, key)
|
|
}
|
|
case []any:
|
|
for _, nested := range typed {
|
|
assertSchemaKeyAbsent(t, nested, key)
|
|
}
|
|
case []string:
|
|
return
|
|
}
|
|
}
|