mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-21 05:57:53 +00:00
test: integration suite with real Traefik, redis-shared limits, streaming assertions
Adds integration/ (separate module): docker compose stack with real Traefik v3.5 + localPlugins, mock openai/anthropic/gemini/tool providers, shared Redis, and a raw-TCP streaming timing probe. 6/7 tests pass; streaming incrementality fails on a confirmed external yaegi/Traefik bug (see task-15-report.md), not a plugin defect. Fixes 4 real Yaegi-interpretation bugs surfaced by running the plugin under real Traefik for the first time (none caught by go test or yaegi-check, which never exercises chat/embeddings/streaming/Redis): - .traefik.yml: add basePkg (Traefik derives the yaegi binding identifier from the module path, not the package's own declared name; without this override the plugin never loads at all). - registry.go: resolveAgainst's multi-value return panicked yaegi on an inline string concatenation; assign to a local first. - routes_unified.go: errors.As panicked yaegi on interpreted pointer error types; use plain type assertions (both types are always returned bare, never wrapped). - sse.go: readSSE's dispatch closure hit the same multi-value-assignment panic as registry.go; split into separate statements. - resp.go: respErr as a defined string type was indistinguishable from a plain string in a yaegi type assertion, misreading every successful Redis SELECT reply as failed and silently disabling rate limiting; changed to a struct.
This commit is contained in:
@@ -78,7 +78,19 @@ func readSSE(r io.Reader, fn func(sseEvent) error) error {
|
||||
joined := strings.Join(dataLines, "\n")
|
||||
fire := hasData && joined != ""
|
||||
ev := sseEvent{event: event, data: []byte(joined)}
|
||||
event, dataLines, hasData = "", nil, false
|
||||
// Three separate assignments, not one "event, dataLines, hasData =
|
||||
// \"\", nil, false" multi-value statement: yaegi v0.16.1 panics
|
||||
// ("reflect: New(nil)") interpreting a 3-way assignment mixing a
|
||||
// string, a nil slice, and a bool literal in one statement — the
|
||||
// same class of bug as resolveAgainst's local-var fix (registry.go),
|
||||
// just in assignment form instead of a return tuple. The identical
|
||||
// values assigned one statement at a time do not trigger it.
|
||||
// Verified empirically under real Traefik (Task 15's integration
|
||||
// suite, the SSE streaming path); tools/yaegi-check never exercises
|
||||
// a live streaming response.
|
||||
event = ""
|
||||
dataLines = nil
|
||||
hasData = false
|
||||
if !fire {
|
||||
return nil
|
||||
}
|
||||
@@ -134,6 +146,25 @@ type sseWriter struct {
|
||||
// need not implement http.Flusher — the Gateway's statusTrackingWriter
|
||||
// always does, since it delegates — but writeData/writeDone tolerate its
|
||||
// absence by skipping the flush.
|
||||
//
|
||||
// KNOWN LIMITATION under real Traefik (yaegi v0.16.1), confirmed by
|
||||
// Task 15's integration suite: the w.(http.Flusher) assertion below
|
||||
// always reports false, because Yaegi wraps any http.ResponseWriter
|
||||
// argument crossing from Traefik's compiled dispatcher into this
|
||||
// interpreted package in a synthetic type scoped to exactly the
|
||||
// http.ResponseWriter method set — Header/Write/WriteHeader — dropping
|
||||
// any other interface the real writer satisfies. The same holds one
|
||||
// layer down: statusTrackingWriter.Flush's own w.rw.(http.Flusher)
|
||||
// check, and even http.NewResponseController(w.rw).Flush(), fail the
|
||||
// same way, all verified empirically. Net effect: streamed responses
|
||||
// (chat completions, MCP/A2A proxying via flushWriter below) are
|
||||
// correct in content but arrive as one batch when the handler returns,
|
||||
// not incrementally, when this plugin is loaded via localPlugins —
|
||||
// unlike compiled Go, where this exact code streams correctly (see the
|
||||
// unit tests). Confirmed as an external, still-open Yaegi/Traefik bug,
|
||||
// not a defect in this code: traefik/yaegi#1600 and, with the
|
||||
// "confirmed bug" label from Traefik's own maintainers,
|
||||
// traefik/traefik#10269. No code-level workaround is known.
|
||||
func newSSEWriter(w http.ResponseWriter) *sseWriter {
|
||||
h := w.Header()
|
||||
h.Set("Content-Type", "text/event-stream")
|
||||
|
||||
Reference in New Issue
Block a user