Files

92 lines
3.8 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package contract
import (
"encoding/json"
"reflect"
"strings"
"testing"
)
func TestNormalizeResultSpecCanonicalizesAndCopies(t *testing.T) {
in := &ResultSpec{
Outcomes: []ResultOutcome{ResultOutcomeFailure, ResultOutcomeSuccess},
DataSchema: json.RawMessage(`{ "properties": {"items":{"type":"array","description":"Result records","items":{"type":"object"}}}, "type":"object" }`),
SensitivePaths: []string{"items.secret", "credential"},
}
got, err := NormalizeResultSpec(in, "dev.list")
if err != nil {
t.Fatalf("NormalizeResultSpec() error = %v", err)
}
if want := []ResultOutcome{ResultOutcomeSuccess, ResultOutcomeFailure}; !reflect.DeepEqual(got.Outcomes, want) {
t.Fatalf("outcomes = %#v, want %#v", got.Outcomes, want)
}
if string(got.DataSchema) != `{"properties":{"items":{"type":"array","description":"Result records","items":{"type":"object"}}},"type":"object"}` {
t.Fatalf("data_schema = %s", got.DataSchema)
}
if want := []string{"credential", "items.secret"}; !reflect.DeepEqual(got.SensitivePaths, want) {
t.Fatalf("sensitive_paths = %#v, want %#v", got.SensitivePaths, want)
}
in.Outcomes[0] = ResultOutcomePending
in.DataSchema[0] = '['
in.SensitivePaths[0] = "changed"
if got.Outcomes[0] != ResultOutcomeSuccess || got.DataSchema[0] != '{' || got.SensitivePaths[0] != "credential" {
t.Fatalf("normalized result aliases input: %#v", got)
}
}
func TestNormalizeResultSpecRejectsInvalidContractsDeterministically(t *testing.T) {
valid := func() *ResultSpec {
return &ResultSpec{Outcomes: []ResultOutcome{ResultOutcomeSuccess}, DataSchema: json.RawMessage(`{"type":"object"}`)}
}
tests := []struct {
name string
edit func(*ResultSpec)
want string
}{
{"no outcomes", func(r *ResultSpec) { r.Outcomes = nil }, "no outcomes"},
{"unknown outcome", func(r *ResultSpec) { r.Outcomes = []ResultOutcome{"ok"} }, "unknown outcome"},
{"duplicate outcome", func(r *ResultSpec) { r.Outcomes = []ResultOutcome{ResultOutcomeSuccess, ResultOutcomeSuccess} }, "duplicate outcome"},
{"schema array", func(r *ResultSpec) { r.DataSchema = json.RawMessage(`[]`) }, "data_schema: must be one JSON object"},
{"multiple schemas", func(r *ResultSpec) { r.DataSchema = json.RawMessage(`{} {}`) }, "data_schema: must be one JSON object"},
{"missing property description", func(r *ResultSpec) {
r.DataSchema = json.RawMessage(`{"type":"object","properties":{"id":{"type":"string"}}}`)
}, "properties.id requires description"},
{"unsafe path", func(r *ResultSpec) { r.SensitivePaths = []string{"$.token"} }, "unsafe segment"},
{"duplicate path", func(r *ResultSpec) { r.SensitivePaths = []string{"token", " token "} }, "duplicate sensitive path"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
spec := valid()
test.edit(spec)
_, err := NormalizeResultSpec(spec, "dev.test")
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("error = %v, want %q", err, test.want)
}
})
}
}
func TestNormalizePaginationSpecUsesFrameworkMetaPaths(t *testing.T) {
got, err := NormalizePaginationSpec(&PaginationSpec{Kind: PaginationKindCursor, CursorParameter: "--cursor"}, "dev.list")
if err != nil {
t.Fatal(err)
}
if got.CursorParameter != "cursor" || got.MetaPath != PaginationMetaPath || got.EndpointExhaustedPath != PaginationExhaustedPath || got.NextTokenPath != PaginationNextTokenPath {
t.Fatalf("pagination = %#v", got)
}
for _, spec := range []*PaginationSpec{
{Kind: "offset", CursorParameter: "cursor"},
{Kind: PaginationKindCursor},
{Kind: PaginationKindCursor, CursorParameter: "cursor", MetaPath: "data.pagination"},
} {
if _, err := NormalizePaginationSpec(spec, "dev.list"); err == nil {
t.Fatalf("invalid pagination accepted: %#v", spec)
}
}
}