130 lines
4.0 KiB
Go
130 lines
4.0 KiB
Go
// Package contract is deliberately framework-free: production Wails wiring
|
|
// must not change LineUp's envelope/state semantics.
|
|
package contract
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
type fixture struct {
|
|
Contract string `json:"contract"`
|
|
Cases []testCase `json:"cases"`
|
|
}
|
|
|
|
type testCase struct {
|
|
ID string `json:"id"`
|
|
Expected expected `json:"expected"`
|
|
Envelopes []envelope `json:"envelopes"`
|
|
Transitions []transition `json:"transitions"`
|
|
}
|
|
|
|
type expected struct {
|
|
ItemKind string `json:"item_kind"`
|
|
Fallback string `json:"fallback"`
|
|
KernelToolStatus string `json:"kernel_tool_status"`
|
|
}
|
|
|
|
type transition struct {
|
|
AfterEnvelope int `json:"after_envelope"`
|
|
Kind string `json:"kind"`
|
|
CallID string `json:"call_id"`
|
|
}
|
|
|
|
type envelope struct {
|
|
Version int `json:"v"`
|
|
Type string `json:"type"`
|
|
Payload map[string]interface{} `json:"payload"`
|
|
}
|
|
|
|
func TestSharedLineUpV1GoldenContract(t *testing.T) {
|
|
bytes, err := os.ReadFile(filepath.Join("..", "..", "tauri", "src", "runtime", "golden", "lineup-v1.json"))
|
|
if err != nil {
|
|
t.Fatalf("read shared fixture: %v", err)
|
|
}
|
|
var spec fixture
|
|
if err := json.Unmarshal(bytes, &spec); err != nil {
|
|
t.Fatalf("parse shared fixture: %v", err)
|
|
}
|
|
if spec.Contract != "lineup-v1-golden-1" || len(spec.Cases) != 6 {
|
|
t.Fatalf("unexpected fixture declaration: %q (%d cases)", spec.Contract, len(spec.Cases))
|
|
}
|
|
for _, c := range spec.Cases {
|
|
t.Run(c.ID, func(t *testing.T) {
|
|
toolStates := map[string]string{}
|
|
kind, fallback := "", ""
|
|
for index, wire := range c.Envelopes {
|
|
kind, fallback = project(wire)
|
|
if kind == "tool-call" {
|
|
toolStates[stringValue(wire.Payload["call_id"])] = "pending"
|
|
}
|
|
for _, action := range c.Transitions {
|
|
if action.AfterEnvelope == index && action.Kind == "submit_tool_call" && toolStates[action.CallID] == "pending" {
|
|
toolStates[action.CallID] = "submitted"
|
|
}
|
|
}
|
|
if kind == "tool-result" {
|
|
callID := stringValue(wire.Payload["call_id"])
|
|
if toolStates[callID] == "submitted" {
|
|
toolStates[callID] = stringValue(wire.Payload["status"])
|
|
}
|
|
}
|
|
}
|
|
if kind != c.Expected.ItemKind || fallback != c.Expected.Fallback {
|
|
t.Fatalf("got kind=%q fallback=%q; want kind=%q fallback=%q", kind, fallback, c.Expected.ItemKind, c.Expected.Fallback)
|
|
}
|
|
if c.Expected.KernelToolStatus != "" {
|
|
ok := false
|
|
for _, state := range toolStates {
|
|
ok = ok || state == c.Expected.KernelToolStatus
|
|
}
|
|
if !ok {
|
|
t.Fatalf("tool state never reached %q: %#v", c.Expected.KernelToolStatus, toolStates)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func project(wire envelope) (kind, fallback string) {
|
|
if wire.Version != 1 {
|
|
return "fallback", "unsupported_version"
|
|
}
|
|
switch wire.Type {
|
|
case "lineup.v1.tool.call":
|
|
if stringValue(wire.Payload["call_id"]) == "" || stringValue(wire.Payload["tool"]) == "" {
|
|
return "fallback", "invalid_payload"
|
|
}
|
|
return "tool-call", ""
|
|
case "lineup.v1.tool.result":
|
|
if stringValue(wire.Payload["call_id"]) == "" || stringValue(wire.Payload["status"]) == "" {
|
|
return "fallback", "invalid_payload"
|
|
}
|
|
return "tool-result", ""
|
|
case "lineup.v1.ui.open", "lineup.v1.ui.patch", "lineup.v1.ui.close":
|
|
for _, forbidden := range []string{"bundle", "bundle_id", "bundle_url", "url", "html", "css", "javascript", "js", "script", "source"} {
|
|
if _, exists := wire.Payload[forbidden]; exists {
|
|
return "fallback", "invalid_payload"
|
|
}
|
|
}
|
|
return "surface", ""
|
|
case "lineup.v1.app.call":
|
|
if stringValue(wire.Payload["call_id"]) == "" || stringValue(wire.Payload["capability"]) == "" || stringValue(wire.Payload["reason"]) == "" || stringValue(wire.Payload["expires_at"]) == "" {
|
|
return "fallback", "invalid_payload"
|
|
}
|
|
if _, ok := wire.Payload["arguments"].(map[string]interface{}); !ok {
|
|
return "fallback", "invalid_payload"
|
|
}
|
|
return "app-call", ""
|
|
default:
|
|
return "fallback", "unsupported_type"
|
|
}
|
|
}
|
|
|
|
func stringValue(value interface{}) string {
|
|
text, _ := value.(string)
|
|
return text
|
|
}
|