Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

117 lines
3.9 KiB
Go

package contracts
import (
"strings"
"testing"
"github.com/zzet/gortex/internal/graph"
)
// TestInlineWrappers_EnrichesConsumerSchema is the regression for the
// dashboard bug where wrapper-routed TypeScript consumers showed
// "REQUEST not declared" / "RESPONSE not declared" even though the
// caller annotated `const data: ToolResponse = await res.json()` and
// returned `Promise<string>`.
//
// Setup mirrors the real web/src/lib/api.ts:
// - serverFetch is the wrapper, registered as a consumer with a
// single-parameter path (`/${path}`) so seedWrappers picks it up
// - callTool calls serverFetch with a literal path "/v1/tools/${name}"
// and reads the response via `const data: ToolResponse = ...`
//
// After InlineWrappers runs, the inlined http::POST::/v1/tools/{p1}
// consumer contract MUST have request_type and response_type set.
func TestInlineWrappers_EnrichesConsumerSchema(t *testing.T) {
src := []byte(`async function serverFetch(path: string, options?: RequestInit): Promise<Response> {
const res = await fetch(` + "`" + `${SERVER_URL}${path}` + "`" + `, options)
return res
}
async function callTool(name: string, args: Record<string, unknown> = {}): Promise<string> {
const res = await serverFetch(` + "`" + `/v1/tools/${name}` + "`" + `, {
method: 'POST',
body: JSON.stringify({ arguments: args }),
})
const data: ToolResponse = await res.json()
return data.text || ''
}
`)
// Build the graph: caller (callTool) calls wrapper (serverFetch).
g := graph.New()
wrapperNode := &graph.Node{
ID: "web/src/lib/api.ts::serverFetch", Name: "serverFetch",
Kind: graph.KindFunction, FilePath: "web/src/lib/api.ts",
Language: "typescript", StartLine: 1, EndLine: 4,
}
callerNode := &graph.Node{
ID: "web/src/lib/api.ts::callTool", Name: "callTool",
Kind: graph.KindFunction, FilePath: "web/src/lib/api.ts",
Language: "typescript", StartLine: 6, EndLine: 13,
}
respTypeNode := &graph.Node{
ID: "web/src/lib/api.ts::ToolResponse", Name: "ToolResponse",
Kind: graph.KindType, FilePath: "web/src/lib/api.ts",
Language: "typescript", StartLine: 0, EndLine: 0,
}
g.AddNode(wrapperNode)
g.AddNode(callerNode)
g.AddNode(respTypeNode)
g.AddEdge(&graph.Edge{
From: callerNode.ID, To: wrapperNode.ID,
Kind: graph.EdgeCalls, FilePath: callerNode.FilePath, Line: 7,
})
// Seed the registry with the wrapper's parametric consumer
// contract (path "/{path}"). seedWrappers picks this up because
// the path matches isWrapperPath().
reg := NewRegistry()
reg.Add(Contract{
ID: "http::GET::/{path}", Type: ContractHTTP, Role: RoleConsumer,
SymbolID: wrapperNode.ID, FilePath: wrapperNode.FilePath, Line: 2,
Meta: map[string]any{"path": "/{path}", "method": "GET"},
})
// SourceReader: return the test source for both nodes (same file).
read := func(n *graph.Node) ([]byte, bool) {
if n.FilePath == "web/src/lib/api.ts" {
return src, true
}
return nil, false
}
added := InlineWrappers(reg, g, read)
if len(added) == 0 {
t.Fatal("InlineWrappers produced no contracts; expected the inlined callTool consumer")
}
// Locate the inlined contract for callTool.
var c *Contract
for i := range added {
if added[i].SymbolID == callerNode.ID {
c = &added[i]
break
}
}
if c == nil {
t.Fatalf("no inlined contract for callTool; got: %+v", added)
}
// Schema enrichment MUST have populated response_type from the
// `const data: ToolResponse = await res.json()` annotation.
rt, _ := c.Meta["response_type"].(string)
if rt == "" {
t.Errorf("response_type empty; want ToolResponse-resolved id, got Meta=%+v", c.Meta)
}
if !strings.Contains(rt, "ToolResponse") {
t.Errorf("response_type=%q; want a ToolResponse-resolved id", rt)
}
// schema_source should reflect that we extracted at least
// some schema info (not "none").
ss, _ := c.Meta["schema_source"].(string)
if ss == "none" || ss == "" {
t.Errorf("schema_source=%q; want extracted/partial", ss)
}
}