137 lines
3.5 KiB
Go
137 lines
3.5 KiB
Go
package admission
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
// HTTPTestRunner implements TestRunner by making real HTTP requests
|
|
type HTTPTestRunner struct {
|
|
client *http.Client
|
|
now func() time.Time
|
|
}
|
|
|
|
// NewHTTPTestRunner creates a runner that makes real HTTP calls
|
|
func NewHTTPTestRunner() *HTTPTestRunner {
|
|
return &HTTPTestRunner{
|
|
client: &http.Client{
|
|
Timeout: 60 * time.Second,
|
|
},
|
|
now: func() time.Time { return time.Now().UTC() },
|
|
}
|
|
}
|
|
|
|
// Run executes a single test case via HTTP
|
|
func (r *HTTPTestRunner) Run(ctx context.Context, tc TestCase) TestCaseResult {
|
|
// Allow mock mode for local verification without real API keys
|
|
if os.Getenv("ADMISSION_TEST_MOCK") == "1" {
|
|
return TestCaseResult{Passed: true, StatusCode: 200, LatencyMs: 1}
|
|
}
|
|
var body io.Reader
|
|
if tc.Body != "" {
|
|
body = bytes.NewBufferString(tc.Body)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, tc.Method, tc.Endpoint, body)
|
|
if err != nil {
|
|
return TestCaseResult{Error: err.Error()}
|
|
}
|
|
|
|
for k, v := range tc.Headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
if req.Header.Get("Content-Type") == "" {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
|
|
start := time.Now()
|
|
resp, err := r.client.Do(req)
|
|
latencyMs := int(time.Since(start).Milliseconds())
|
|
|
|
if err != nil {
|
|
return TestCaseResult{
|
|
Error: err.Error(),
|
|
LatencyMs: latencyMs,
|
|
}
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response (up to 4KB for validation)
|
|
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
|
|
passed := resp.StatusCode >= 200 && resp.StatusCode < 300
|
|
|
|
return TestCaseResult{
|
|
Passed: passed,
|
|
StatusCode: resp.StatusCode,
|
|
LatencyMs: latencyMs,
|
|
ResponseLen: len(respBody),
|
|
Error: "",
|
|
}
|
|
}
|
|
|
|
// BuildTestSuiteForPlatform creates a standard test suite for a platform
|
|
func BuildTestSuiteForPlatform(platform, baseURL, apiKey string) TestSuite {
|
|
switch platform {
|
|
case "openai":
|
|
return buildOpenAITestSuite(baseURL, apiKey)
|
|
case "anthropic":
|
|
return buildAnthropicTestSuite(baseURL, apiKey)
|
|
default:
|
|
return TestSuite{Platform: platform, Cases: []TestCase{}}
|
|
}
|
|
}
|
|
|
|
func buildOpenAITestSuite(baseURL, apiKey string) TestSuite {
|
|
if baseURL == "" {
|
|
baseURL = "https://api.openai.com"
|
|
}
|
|
endpoint := baseURL + "/v1/models"
|
|
return TestSuite{
|
|
Platform: "openai",
|
|
Cases: []TestCase{
|
|
{
|
|
ID: "openai-models-list",
|
|
Name: "List Models",
|
|
Endpoint: endpoint,
|
|
Method: http.MethodGet,
|
|
Headers: map[string]string{"Authorization": "Bearer " + apiKey},
|
|
TimeoutSecs: 30,
|
|
},
|
|
{
|
|
ID: "openai-chat-completion",
|
|
Name: "Chat Completion",
|
|
Endpoint: baseURL + "/v1/chat/completions",
|
|
Method: http.MethodPost,
|
|
Headers: map[string]string{"Authorization": "Bearer " + apiKey, "Content-Type": "application/json"},
|
|
Body: `{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hello"}],"max_tokens":10}`,
|
|
TimeoutSecs: 30,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func buildAnthropicTestSuite(baseURL, apiKey string) TestSuite {
|
|
if baseURL == "" {
|
|
baseURL = "https://api.anthropic.com"
|
|
}
|
|
return TestSuite{
|
|
Platform: "anthropic",
|
|
Cases: []TestCase{
|
|
{
|
|
ID: "anthropic-messages",
|
|
Name: "Claude Messages",
|
|
Endpoint: baseURL + "/v1/messages",
|
|
Method: http.MethodPost,
|
|
Headers: map[string]string{"x-api-key": apiKey, "anthropic-version": "2023-06-01", "Content-Type": "application/json"},
|
|
Body: `{"model":"claude-3-5-haiku-20241022","messages":[{"role":"user","content":"hello"}],"max_tokens":10}`,
|
|
TimeoutSecs: 30,
|
|
},
|
|
},
|
|
}
|
|
}
|