iop/apps/edge/internal/openai/stream_gate_ingress_test.go
toki d182eafeff feat: stream evidence gate core - full implementation
- Stream evidence gate routing in edge config and runtime
- Ingress snapshot and allocation
- Recovery coordinator and plan
- Runtime contract for gate filters
- OpenAI-compatible request rebuilder and stream gate dispatcher
- Comprehensive tests for all new components
- Updated contracts and roadmap milestones
2026-07-26 20:48:53 +09:00

139 lines
4.6 KiB
Go

package openai
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"iop/packages/go/config"
"iop/packages/go/streamgate"
)
func TestReadOpenAIIngressBodyBoundary(t *testing.T) {
for _, tc := range []struct {
name string
size int
wantErr bool
}{
{name: "limit minus one", size: 7},
{name: "exact limit", size: 8},
{name: "limit plus one", size: 9, wantErr: true},
} {
t.Run(tc.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", bytes.NewReader(bytes.Repeat([]byte("x"), tc.size)))
body, err := readOpenAIIngressBody(recorder, request, 8)
if tc.wantErr {
if !errors.Is(err, errOpenAIIngressTooLarge) {
t.Fatalf("error = %v, want ingress-too-large", err)
}
return
}
if err != nil || len(body) != tc.size {
t.Fatalf("body len/error = %d/%v, want %d/nil", len(body), err, tc.size)
}
})
}
}
func TestOpenAIIngressSnapshotSharedBackingAndRelease(t *testing.T) {
body := []byte(`{"model":"m","messages":[]}`)
ingress, err := buildOpenAIIngressSnapshot(int64(len(body)), body, json.RawMessage(body))
if err != nil {
t.Fatalf("buildOpenAIIngressSnapshot: %v", err)
}
accessor, err := ingress.accessor()
if err != nil {
t.Fatalf("accessor: %v", err)
}
if accessor.RetainedCount() != 1 || accessor.RetainedBytes() != int64(len(body)) {
t.Fatalf("retained count/bytes = %d/%d", accessor.RetainedCount(), accessor.RetainedBytes())
}
ingress.Close()
ingress.Close()
if !ingress.isClosed() {
t.Fatal("ingress snapshot was not released")
}
}
func TestOpenAIIngressTypedViewOverflow(t *testing.T) {
body := []byte(`{"model":"m"}`)
_, err := buildOpenAIIngressSnapshot(int64(len(body)), body, struct {
Model string `json:"model"`
Extra string `json:"extra"`
}{Model: "m", Extra: "typed-view-expansion"})
if !errors.Is(err, streamgate.ErrIngressSnapshotLimitExceeded) {
t.Fatalf("error = %v, want typed-view overflow", err)
}
}
func TestOpenAIIngressExcludesAuthorization(t *testing.T) {
body := []byte(`{"model":"m","messages":[]}`)
ingress, err := buildOpenAIIngressSnapshot(1024, body, json.RawMessage(body))
if err != nil {
t.Fatalf("buildOpenAIIngressSnapshot: %v", err)
}
defer ingress.Close()
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
request.Header.Set("X-Provider-Token", "secret-never-snapshot")
canonical, _ := ingress.canonicalBody()
semantic, _ := ingress.semanticBody()
if bytes.Contains(canonical, []byte("secret-never-snapshot")) || bytes.Contains(semantic, []byte("secret-never-snapshot")) {
t.Fatal("provider auth leaked into ingress snapshot")
}
}
func TestChatIngressOverflowDoesNotDispatch(t *testing.T) {
service := &fakeRunService{}
server := NewServer(config.EdgeOpenAIConf{
Adapter: "ollama",
Target: "model",
StreamEvidenceGate: config.StreamEvidenceGateConf{
MaxIngressSnapshotBytes: 32,
},
}, service, nil)
body := `{"model":"model","messages":[{"role":"user","content":"` + strings.Repeat("x", 64) + `"}]}`
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body))
recorder := httptest.NewRecorder()
server.handleChatCompletions(recorder, request)
if recorder.Code != http.StatusRequestEntityTooLarge {
t.Fatalf("status = %d, want 413; body=%s", recorder.Code, recorder.Body.String())
}
if len(service.reqsSnapshot()) != 0 {
t.Fatal("overflow request reached provider dispatch")
}
if strings.Contains(recorder.Body.String(), "32") || strings.Contains(recorder.Body.String(), "streamgate") {
t.Fatalf("error leaked internal limit details: %s", recorder.Body.String())
}
}
func TestChatIngressTypedOverflowDoesNotDispatch(t *testing.T) {
body := `{"model":"model", "messages":[{"role":"user","content":"hello"}]}`
service := &fakeRunService{}
server := NewServer(config.EdgeOpenAIConf{
Adapter: "ollama",
Target: "model",
StreamEvidenceGate: config.StreamEvidenceGateConf{
// The canonical body fits exactly. Its normalized typed view differs,
// so the combined ledger must fail before identity/admission.
MaxIngressSnapshotBytes: len(body),
},
}, service, nil)
request := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body))
recorder := httptest.NewRecorder()
server.handleChatCompletions(recorder, request)
if recorder.Code != http.StatusRequestEntityTooLarge {
t.Fatalf("status = %d, want 413; body=%s", recorder.Code, recorder.Body.String())
}
if len(service.reqsSnapshot()) != 0 {
t.Fatal("typed-view overflow reached provider dispatch")
}
}