fix(integration): green gate with tracked streaming skip, gemini coverage, robust probe

Addresses coordinator review of the Task 15 integration suite: 1 critical
ruling, 5 important, 3 folded. make integration now exits 0.

- integration_test.go: TestStreamingIncrementalityProbe restructured —
  content assertion (5 deltas + [DONE], in order) always runs; the raw-TCP
  timing assertion (unchanged, >=3 gaps >=100ms) is gated behind
  INTEGRATION_STREAMING=1 with t.Skipf naming traefik/traefik#10269,
  verified to still genuinely fail when re-armed (not silently disabled).
- Makefile: integration-up/integration-wait moved from prerequisites into
  the integration recipe body as go test ./... -count=1
ok  	github.com/lukaszraczylo/traefik-llmgateway	6.065s sub-invocations with per-stage
  status capture, so integration-down always runs even when bring-up or
  the health wait fails; verified with a synthetic failure. Added
  .NOTPARALLEL (the integration targets are stateful).
- translate_gemini.go: fixed the same yaegi multi-value-assignment panic
  shape as the prior sse.go fix, on the live max_tokens path. Added a
  gemini chat completion assertion to integration_test.go (renamed
  TestUnifiedChatAllProviders) so this path now runs under real Yaegi.
- providers.go, translate_anthropic.go: documented the never-wrap
  contract on providerHTTPError/translateError (routes_unified.go asserts
  their concrete type directly; errors.As panics under yaegi on these).
- integration_test.go: TestCrossReplicaLimits now FLUSHALLs Redis first,
  for idempotence under integration-keep.
- mock/main.go: probe's header-terminator search now accumulates a
  rolling buffer across reads instead of only checking the latest one.
- Folded: hot-reload deadline 10s -> 20s; dynamic.yml's real-upstream
  baseUrl is now a template (dynamic.yml.tmpl) rendered via
  INTEGRATION_REAL_BASEURL (default https://llmgw.example.com), not a
  hardcoded-only hostname.

See task-15-report.md's addendum for full verification detail.
This commit is contained in:
2026-08-20 13:46:45 +01:00
parent 7939c3e74b
commit 730583d98e
8 changed files with 201 additions and 28 deletions
+16 -5
View File
@@ -341,6 +341,7 @@ func runProbe(target, apiKey, model string) int {
}
var arrivals []time.Time
var headerBuf []byte
buf := make([]byte, 4096)
headerDone := false
for {
@@ -348,12 +349,22 @@ func runProbe(target, apiKey, model string) int {
if n > 0 {
chunk := buf[:n]
if !headerDone {
if idx := bytes.Index(chunk, []byte("\r\n\r\n")); idx >= 0 {
headerDone = true
chunk = chunk[idx+4:]
} else {
chunk = nil
// The "\r\n\r\n" header/body terminator is not guaranteed to
// land inside a single Read: accumulate every read into a
// rolling buffer and search that, not just the latest
// chunk, so a terminator split across two reads is still
// found instead of silently discarding both reads' bytes.
headerBuf = append(headerBuf, chunk...)
idx := bytes.Index(headerBuf, []byte("\r\n\r\n"))
if idx < 0 {
if readErr != nil {
break
}
continue
}
headerDone = true
chunk = headerBuf[idx+4:]
headerBuf = nil
}
if len(chunk) > 0 && bytes.Contains(chunk, []byte("data:")) {
arrivals = append(arrivals, time.Now())