Files
ds2api/internal/compat/go_compat_test.go

160 lines
4.6 KiB
Go

package compat
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"ds2api/internal/sse"
"ds2api/internal/util"
)
func TestGoCompatSSEFixtures(t *testing.T) {
files, err := filepath.Glob(compatPath("fixtures", "sse_chunks", "*.json"))
if err != nil {
t.Fatalf("glob fixtures failed: %v", err)
}
if len(files) == 0 {
t.Fatal("no sse fixtures found")
}
for _, fixturePath := range files {
name := trimExt(filepath.Base(fixturePath))
expectedPath := compatPath("expected", "sse_"+name+".json")
var fixture struct {
Chunk map[string]any `json:"chunk"`
ThinkingEnable bool `json:"thinking_enabled"`
CurrentType string `json:"current_type"`
}
mustLoadJSON(t, fixturePath, &fixture)
var expected struct {
Parts []map[string]any `json:"parts"`
Finished bool `json:"finished"`
NewType string `json:"new_type"`
}
mustLoadJSON(t, expectedPath, &expected)
parts, finished, newType := sse.ParseSSEChunkForContent(fixture.Chunk, fixture.ThinkingEnable, fixture.CurrentType)
gotParts := make([]map[string]any, 0, len(parts))
for _, p := range parts {
gotParts = append(gotParts, map[string]any{
"text": p.Text,
"type": p.Type,
})
}
if !reflect.DeepEqual(gotParts, expected.Parts) || finished != expected.Finished || newType != expected.NewType {
t.Fatalf("fixture %s mismatch:\n got parts=%#v finished=%v newType=%q\nwant parts=%#v finished=%v newType=%q",
name, gotParts, finished, newType, expected.Parts, expected.Finished, expected.NewType)
}
}
}
func TestGoCompatToolcallFixtures(t *testing.T) {
files, err := filepath.Glob(compatPath("fixtures", "toolcalls", "*.json"))
if err != nil {
t.Fatalf("glob toolcall fixtures failed: %v", err)
}
if len(files) == 0 {
t.Fatal("no toolcall fixtures found")
}
for _, fixturePath := range files {
name := trimExt(filepath.Base(fixturePath))
expectedPath := compatPath("expected", "toolcalls_"+name+".json")
var fixture struct {
Text string `json:"text"`
ToolNames []string `json:"tool_names"`
Mode string `json:"mode"`
}
mustLoadJSON(t, fixturePath, &fixture)
var expected struct {
Calls []util.ParsedToolCall `json:"calls"`
SawToolCallSyntax bool `json:"sawToolCallSyntax"`
RejectedByPolicy bool `json:"rejectedByPolicy"`
RejectedToolNames []string `json:"rejectedToolNames"`
}
mustLoadJSON(t, expectedPath, &expected)
var got util.ToolCallParseResult
switch strings.ToLower(strings.TrimSpace(fixture.Mode)) {
case "standalone":
got = util.ParseStandaloneToolCallsDetailed(fixture.Text, fixture.ToolNames)
default:
got = util.ParseToolCallsDetailed(fixture.Text, fixture.ToolNames)
}
if got.Calls == nil {
got.Calls = []util.ParsedToolCall{}
}
if got.RejectedToolNames == nil {
got.RejectedToolNames = []string{}
}
if !reflect.DeepEqual(got.Calls, expected.Calls) ||
got.SawToolCallSyntax != expected.SawToolCallSyntax ||
got.RejectedByPolicy != expected.RejectedByPolicy ||
!reflect.DeepEqual(got.RejectedToolNames, expected.RejectedToolNames) {
t.Fatalf("toolcall fixture %s mismatch:\n got=%#v\nwant=%#v", name, got, expected)
}
}
}
func TestGoCompatTokenFixtures(t *testing.T) {
var fixture struct {
Cases []struct {
Name string `json:"name"`
Text string `json:"text"`
} `json:"cases"`
}
mustLoadJSON(t, compatPath("fixtures", "token_cases.json"), &fixture)
var expected struct {
Cases []struct {
Name string `json:"name"`
Tokens int `json:"tokens"`
} `json:"cases"`
}
mustLoadJSON(t, compatPath("expected", "token_cases.json"), &expected)
expectByName := map[string]int{}
for _, c := range expected.Cases {
expectByName[c.Name] = c.Tokens
}
for _, c := range fixture.Cases {
want, ok := expectByName[c.Name]
if !ok {
t.Fatalf("missing expected token case: %s", c.Name)
}
got := util.EstimateTokens(c.Text)
if got != want {
t.Fatalf("token fixture %s mismatch: got=%d want=%d", c.Name, got, want)
}
}
}
func mustLoadJSON(t *testing.T, path string, out any) {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s failed: %v", path, err)
}
if err := json.Unmarshal(b, out); err != nil {
t.Fatalf("decode %s failed: %v", path, err)
}
}
func trimExt(name string) string {
if len(name) > 5 && name[len(name)-5:] == ".json" {
return name[:len(name)-5]
}
return name
}
func compatPath(parts ...string) string {
prefix := []string{"..", "..", "tests", "compat"}
return filepath.Join(append(prefix, parts...)...)
}