Files
cpa-plugin/internal/plugin/management_test.go
T

606 lines
29 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package plugin
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
"time"
)
func managementCall(t *testing.T, app *App, method, path string) ManagementResponse {
return managementCallBody(t, app, method, path, nil)
}
func managementCallBody(t *testing.T, app *App, method, path string, body []byte) ManagementResponse {
return managementCallRequest(t, app, ManagementRequest{Method: method, Path: path, Body: body})
}
func managementCallRequest(t *testing.T, app *App, requestValue ManagementRequest) ManagementResponse {
t.Helper()
request, err := json.Marshal(requestValue)
if err != nil {
t.Fatal(err)
}
raw, err := app.HandleMethod(MethodManagementHandle, request)
if err != nil {
t.Fatal(err)
}
var envelope Envelope
if err := json.Unmarshal(raw, &envelope); err != nil {
t.Fatal(err)
}
var response ManagementResponse
if err := json.Unmarshal(envelope.Result, &response); err != nil {
t.Fatal(err)
}
return response
}
func TestManagementRegistrationDeclaresUsageAPIAndUI(t *testing.T) {
raw, err := NewApp().HandleMethod(MethodManagementRegister, nil)
if err != nil {
t.Fatal(err)
}
var envelope Envelope
if err := json.Unmarshal(raw, &envelope); err != nil {
t.Fatal(err)
}
var registration ManagementRegistrationResponse
if err := json.Unmarshal(envelope.Result, &registration); err != nil {
t.Fatal(err)
}
if len(registration.Routes) != 18 || registration.Routes[0].Path != managementBase+routeUsage || registration.Routes[1].Path != managementBase+routeUsageSummary || registration.Routes[2].Path != managementBase+routePrices {
t.Fatalf("unexpected management routes: %+v", registration.Routes)
}
if len(registration.Resources) != 1 || registration.Resources[0].Path != resourceBase+resourceUI {
t.Fatalf("unexpected resource routes: %+v", registration.Resources)
}
}
func TestReadOnlyResourceReturnsRealDataWithoutSecrets(t *testing.T) {
app := NewApp()
defer app.Shutdown()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
keys := managementCallRequest(t, app, ManagementRequest{
Method: http.MethodGet,
Path: resourceBase + resourceUI,
Query: url.Values{"view": {"keys"}},
})
if keys.StatusCode != http.StatusOK || !strings.Contains(string(keys.Body), `"masked_secret":"00******0000"`) {
t.Fatalf("unexpected read-only keys: status=%d body=%s", keys.StatusCode, keys.Body)
}
if strings.Contains(string(keys.Body), `"secret":`) {
t.Fatalf("read-only response exposed a complete secret: %s", keys.Body)
}
usageRaw, _ := json.Marshal(UsageRecord{Provider: "openai", Model: "gpt-test", APIKey: "000000", RequestedAt: time.Now(), Detail: UsageDetail{TotalTokens: 1}})
if _, err := app.HandleMethod(MethodUsageHandle, usageRaw); err != nil {
t.Fatal(err)
}
usage := managementCallRequest(t, app, ManagementRequest{
Method: http.MethodGet,
Path: resourceBase + resourceUI,
Query: url.Values{"view": {"usage"}},
})
if usage.StatusCode != http.StatusOK || strings.Contains(string(usage.Body), `"api_key":"000000"`) {
t.Fatalf("read-only usage exposed a historical key: status=%d body=%s", usage.StatusCode, usage.Body)
}
invalid := managementCallRequest(t, app, ManagementRequest{
Method: http.MethodGet,
Path: resourceBase + resourceUI,
Query: url.Values{"view": {"missing"}},
})
if invalid.StatusCode != http.StatusBadRequest || !strings.Contains(string(invalid.Body), `"code":"invalid_view"`) {
t.Fatalf("unexpected invalid view response: status=%d body=%s", invalid.StatusCode, invalid.Body)
}
}
func TestModelsDevCatalogRequiresExplicitImportAndConfirmedRefresh(t *testing.T) {
catalogBody := `{"providers":{"openai":{"id":"openai","name":"OpenAI","models":{"gpt-5.6-sol":{"id":"gpt-5.6-sol","name":"GPT-5.6 Sol","cost":{"input":5,"output":30,"cache_read":0.5,"cache_write":6.25}}}},"free":{"id":"free","name":"Free","models":{"placeholder":{"id":"placeholder","cost":{"input":0,"output":0}}}}}}`
serverStatus := http.StatusOK
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) {
response.WriteHeader(serverStatus)
_, _ = response.Write([]byte(catalogBody))
}))
defer server.Close()
directory := t.TempDir()
config := fmt.Sprintf("database_path: %q\nmodels_dev_url: %q\nmodels_dev_cache_path: %q\nenabled: true\ncodex_only: false\n",
filepath.Join(directory, "usage.db"), server.URL, filepath.Join(directory, "catalog.json"))
app := NewApp()
defer app.Shutdown()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, config)); err != nil {
t.Fatal(err)
}
status := managementCallRequest(t, app, ManagementRequest{Method: http.MethodGet, Path: managementBase + routeCatalog, Query: url.Values{"q": {"gpt"}}})
if status.StatusCode != http.StatusOK || !strings.Contains(string(status.Body), `"loaded":false`) {
t.Fatalf("catalog status=%d body=%s", status.StatusCode, status.Body)
}
refresh := managementCall(t, app, http.MethodPost, managementBase+routeCatalogRefresh)
if refresh.StatusCode != http.StatusOK || !strings.Contains(string(refresh.Body), `"changed":0`) {
t.Fatalf("initial refresh status=%d body=%s", refresh.StatusCode, refresh.Body)
}
search := managementCallRequest(t, app, ManagementRequest{Method: http.MethodGet, Path: managementBase + routeCatalog, Query: url.Values{"q": {"gpt-5.6"}}})
if search.StatusCode != http.StatusOK || !strings.Contains(string(search.Body), `"id":"openai/gpt-5.6-sol"`) || strings.Contains(string(search.Body), "placeholder") {
t.Fatalf("catalog search status=%d body=%s", search.StatusCode, search.Body)
}
imported := managementCallBody(t, app, http.MethodPost, managementBase+routePriceImport, []byte(`{"model":"deepseek-v4-flash","catalog_id":"openai/gpt-5.6-sol"}`))
if imported.StatusCode != http.StatusOK || !strings.Contains(string(imported.Body), `"input_per_1m":"5"`) || !strings.Contains(string(imported.Body), `"kind":"models.dev"`) {
t.Fatalf("catalog import status=%d body=%s", imported.StatusCode, imported.Body)
}
catalogBody = strings.Replace(catalogBody, `"input":5`, `"input":6`, 1)
preview := managementCall(t, app, http.MethodPost, managementBase+routeCatalogRefresh)
if preview.StatusCode != http.StatusOK || !strings.Contains(string(preview.Body), `"changed":1`) || !strings.Contains(string(preview.Body), `"input_per_1m":"6"`) {
t.Fatalf("refresh preview status=%d body=%s", preview.StatusCode, preview.Body)
}
pricesBefore := managementCall(t, app, http.MethodGet, managementBase+routePrices)
if !strings.Contains(string(pricesBefore.Body), `"input_per_1m":"5"`) {
t.Fatalf("preview silently changed local price: %s", pricesBefore.Body)
}
var previewPayload struct {
Catalog struct {
Revision string `json:"revision"`
} `json:"catalog"`
}
if err := json.Unmarshal(preview.Body, &previewPayload); err != nil {
t.Fatal(err)
}
applyBody, _ := json.Marshal(map[string]any{"revision": previewPayload.Catalog.Revision, "models": []string{"deepseek-v4-flash"}})
applied := managementCallBody(t, app, http.MethodPost, managementBase+routeCatalogApply, applyBody)
if applied.StatusCode != http.StatusOK || !strings.Contains(string(applied.Body), `"updated":1`) {
t.Fatalf("catalog apply status=%d body=%s", applied.StatusCode, applied.Body)
}
pricesAfter := managementCall(t, app, http.MethodGet, managementBase+routePrices)
if !strings.Contains(string(pricesAfter.Body), `"input_per_1m":"6"`) {
t.Fatalf("confirmed price was not applied: %s", pricesAfter.Body)
}
manual := []byte(`{"model":"deepseek-v4-flash","base":{"input_per_1m":"7","cache_read_per_1m":"0.5","cache_write_per_1m":"6.25","output_per_1m":"30"},"fast_pricing_enabled":false,"fast_multiplier":"2.5"}`)
if response := managementCallBody(t, app, http.MethodPut, managementBase+routePrices, manual); response.StatusCode != http.StatusOK || !strings.Contains(string(response.Body), `"kind":"manual"`) {
t.Fatalf("manual save status=%d body=%s", response.StatusCode, response.Body)
}
catalogBody = strings.Replace(catalogBody, `"input":6`, `"input":8`, 1)
manualPreview := managementCall(t, app, http.MethodPost, managementBase+routeCatalogRefresh)
if manualPreview.StatusCode != http.StatusOK || !strings.Contains(string(manualPreview.Body), `"changed":0`) {
t.Fatalf("manual price followed catalog: status=%d body=%s", manualPreview.StatusCode, manualPreview.Body)
}
serverStatus = http.StatusBadGateway
failed := managementCall(t, app, http.MethodPost, managementBase+routeCatalogRefresh)
if failed.StatusCode != http.StatusBadGateway {
t.Fatalf("failed refresh status=%d body=%s", failed.StatusCode, failed.Body)
}
lastGood := managementCallRequest(t, app, ManagementRequest{Method: http.MethodGet, Path: managementBase + routeCatalog, Query: url.Values{"q": {"gpt"}}})
if lastGood.StatusCode != http.StatusOK || !strings.Contains(string(lastGood.Body), `"loaded":true`) || !strings.Contains(string(lastGood.Body), `"input_per_1m":"8"`) {
t.Fatalf("last-known-good catalog unavailable: status=%d body=%s", lastGood.StatusCode, lastGood.Body)
}
}
func TestUsageManagementSupportsServerPaginationFiltersAndSummary(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
base := time.Date(2026, 8, 15, 8, 0, 0, 0, time.UTC)
for index := 0; index < 205; index++ {
record := UsageRecord{Provider: "openai", Model: fmt.Sprintf("model-%d", index%2), AuthID: fmt.Sprintf("auth-%d", index%2), RequestedAt: base.Add(time.Duration(index) * time.Second), Failed: index%10 == 0, Detail: UsageDetail{TotalTokens: int64(index + 1)}}
raw, _ := json.Marshal(record)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
}
response := managementCallRequest(t, app, ManagementRequest{Method: http.MethodGet, Path: managementBase + routeUsage, Query: url.Values{"page": {"2"}, "page_size": {"100"}, "model": {"model-1"}}})
if response.StatusCode != http.StatusOK {
t.Fatalf("usage query status=%d body=%s", response.StatusCode, response.Body)
}
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if payload.Pagination.Total != 102 || payload.Pagination.Page != 2 || payload.Pagination.TotalPages != 2 || len(payload.Records) != 2 {
t.Fatalf("unexpected paginated response: %+v", payload.Pagination)
}
if payload.Records[0].Model != "model-1" || payload.Records[1].Model != "model-1" {
t.Fatalf("filter leaked records: %+v", payload.Records)
}
invalid := managementCallRequest(t, app, ManagementRequest{Method: http.MethodGet, Path: managementBase + routeUsage, Query: url.Values{"page_size": {"101"}}})
if invalid.StatusCode != http.StatusBadRequest {
t.Fatalf("invalid page size status=%d", invalid.StatusCode)
}
summary := managementCall(t, app, http.MethodGet, managementBase+routeUsageSummary)
if summary.StatusCode != http.StatusOK || !strings.Contains(string(summary.Body), `"users"`) || !strings.Contains(string(summary.Body), `"days"`) {
t.Fatalf("unexpected summary: status=%d body=%s", summary.StatusCode, summary.Body)
}
}
func TestFormatMicrosSupportsNegativeBalances(t *testing.T) {
if got := formatMicros(-125_000); got != "-0.125" {
t.Fatalf("formatMicros(-125000)=%q", got)
}
}
func TestPriceManagementCalculatesUsageWithLongContextAndFastPolicy(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: true\n"))); err != nil {
t.Fatal(err)
}
priceBody := []byte(`{
"model":"gpt-5.6-sol",
"base":{"input_per_1m":"2.5","cache_read_per_1m":"0.25","cache_write_per_1m":"3.125","output_per_1m":"15"},
"long_context":{"threshold_input_tokens":272000,"comparison":"gt","input_per_1m":"5","cache_read_per_1m":"0.5","cache_write_per_1m":"6.25","output_per_1m":"22.5"},
"fast_pricing_enabled":true,
"fast_multiplier":"2.5"
}`)
response := managementCallBody(t, app, http.MethodPut, managementBase+routePrices, priceBody)
if response.StatusCode != http.StatusOK {
t.Fatalf("put price status = %d, body = %s", response.StatusCode, response.Body)
}
record := UsageRecord{
Provider: "codex", Model: "gpt-5.6-sol", ServiceTier: "priority", RequestedAt: time.Now(),
Detail: UsageDetail{InputTokens: 272_001, OutputTokens: 10_000, TotalTokens: 282_001},
}
raw, _ := json.Marshal(record)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
usageResponse := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(usageResponse.Body, &payload); err != nil {
t.Fatal(err)
}
got := payload.Records[0]
if !got.CostAvailable || got.CostUSD == nil || *got.CostUSD != 3.962513 || got.PriceTier != "long_context" || !got.FastRequested || !got.FastPricingApplied {
t.Fatalf("unexpected priced usage: %+v", got)
}
pricesResponse := managementCall(t, app, http.MethodGet, managementBase+routePrices)
if pricesResponse.StatusCode != http.StatusOK || !strings.Contains(string(pricesResponse.Body), `"fast_multiplier":"2.5"`) {
t.Fatalf("unexpected prices response: %d %s", pricesResponse.StatusCode, pricesResponse.Body)
}
}
func TestFastRequestCanUseStandardPricing(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: true\n"))); err != nil {
t.Fatal(err)
}
priceBody := []byte(`{"model":"gpt-5.6-sol","base":{"input_per_1m":"2.5","cache_read_per_1m":"0.25","cache_write_per_1m":"3.125","output_per_1m":"15"},"fast_pricing_enabled":false,"fast_multiplier":"2.5"}`)
if response := managementCallBody(t, app, http.MethodPut, managementBase+routePrices, priceBody); response.StatusCode != http.StatusOK {
t.Fatalf("put price status = %d, body = %s", response.StatusCode, response.Body)
}
record := UsageRecord{Provider: "codex", Model: "gpt-5.6-sol", ServiceTier: "fast", RequestedAt: time.Now(), Detail: UsageDetail{InputTokens: 100_000, TotalTokens: 100_000}}
raw, _ := json.Marshal(record)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
got := payload.Records[0]
if got.CostUSD == nil || *got.CostUSD != 0.25 || !got.FastRequested || got.FastPricingApplied {
t.Fatalf("unexpected standard-priced Fast usage: %+v", got)
}
}
func TestCanceledRequestWithoutUsageRemainsVisible(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
startedAt := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
completion := RequestCompletion{
RequestID: "request-canceled", TraceID: "trace-canceled", SourceFormat: "openai-response",
Model: "deepseek-v4-flash", RequestedModel: "deepseek-flash", Stream: true,
Outcome: RequestCompletionCanceled, StatusCode: 499, Error: "context canceled",
StartedAt: startedAt, CompletedAt: startedAt.Add(2 * time.Second),
Metadata: map[string]any{"request_path": "/v1/responses"},
}
raw, _ := json.Marshal(completion)
if _, err := app.HandleMethod(MethodRequestComplete, raw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if len(payload.Records) != 1 {
t.Fatalf("records = %d, want 1", len(payload.Records))
}
got := payload.Records[0]
if got.RequestID != "request-canceled" || got.TraceID != "trace-canceled" || got.Outcome != "canceled" || !got.Failed || got.StatusCode != 499 || got.RequestType != "SSE" || got.CostAvailable {
t.Fatalf("unexpected canceled request: %+v", got)
}
}
func TestCompactUsageIsPricedAndUsesJSONMetrics(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
priceBody := []byte(`{"model":"deepseek-v4-flash","base":{"input_per_1m":"2.5","cache_read_per_1m":"0.25","cache_write_per_1m":"3.125","output_per_1m":"15"},"fast_pricing_enabled":true,"fast_multiplier":"2.5"}`)
if response := managementCallBody(t, app, http.MethodPut, managementBase+routePrices, priceBody); response.StatusCode != http.StatusOK {
t.Fatalf("put price status = %d, body = %s", response.StatusCode, response.Body)
}
startedAt := time.Now()
record := UsageRecord{
Provider: "openai", Model: "deepseek-v4-flash", RequestedAt: startedAt,
Latency: time.Second, TTFT: 200 * time.Millisecond,
Detail: UsageDetail{InputTokens: 1_000, OutputTokens: 100, TotalTokens: 1_100},
}
raw, _ := json.Marshal(record)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
completionRaw, _ := json.Marshal(RequestCompletion{
RequestID: "compact-success", Model: "deepseek-v4-flash", Outcome: RequestCompletionSucceeded,
StartedAt: startedAt, CompletedAt: startedAt.Add(time.Second),
Metadata: map[string]any{"request_path": "/v1/responses/compact"},
})
if _, err := app.HandleMethod(MethodRequestComplete, completionRaw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if len(payload.Records) != 1 {
t.Fatalf("records = %d, want 1", len(payload.Records))
}
got := payload.Records[0]
if got.RequestType != "JSON" || got.Endpoint != "/v1/responses/compact" || got.TTFTMilliseconds != 0 || got.SpeedTPS != nil {
t.Fatalf("unexpected compact metadata: %+v", got)
}
if !got.CostAvailable || got.CostUSD == nil || *got.CostUSD != 0.004 {
t.Fatalf("unexpected compact cost: %+v", got)
}
}
func TestCompactFailureAndCancellationRemainVisible(t *testing.T) {
for _, test := range []struct {
name string
outcome RequestCompletionOutcome
statusCode int
}{
{name: "failed", outcome: RequestCompletionFailed, statusCode: 500},
{name: "canceled", outcome: RequestCompletionCanceled, statusCode: 499},
} {
t.Run(test.name, func(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
startedAt := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
completion := RequestCompletion{
RequestID: "compact-" + test.name, Model: "deepseek-v4-flash", Stream: false,
Outcome: test.outcome, StatusCode: test.statusCode, Error: test.name,
StartedAt: startedAt, CompletedAt: startedAt.Add(time.Second),
Metadata: map[string]any{"request_path": "/v1/responses/compact"},
}
raw, _ := json.Marshal(completion)
if _, err := app.HandleMethod(MethodRequestComplete, raw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if len(payload.Records) != 1 {
t.Fatalf("records = %d, want 1", len(payload.Records))
}
got := payload.Records[0]
if got.Outcome != string(test.outcome) || !got.Failed || got.StatusCode != test.statusCode || got.RequestType != "JSON" || got.Endpoint != "/v1/responses/compact" || got.SpeedTPS != nil || got.CostAvailable {
t.Fatalf("unexpected compact terminal record: %+v", got)
}
})
}
}
func TestUsageAndLifecycleMergeInEitherOrderAndDeduplicateUsage(t *testing.T) {
for _, usageFirst := range []bool{true, false} {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
startedAt := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
usage := UsageRecord{Provider: "openai", Model: "deepseek-v4-flash", RequestedAt: startedAt,
Detail: UsageDetail{InputTokens: 10, TotalTokens: 10}}
completion := RequestCompletion{
RequestID: "request-1", TraceID: "trace-1", Model: "deepseek-v4-flash",
Outcome: RequestCompletionFailed, StatusCode: 500, Error: "upstream failed",
StartedAt: startedAt, CompletedAt: startedAt.Add(time.Second),
}
usageRaw, _ := json.Marshal(usage)
completionRaw, _ := json.Marshal(completion)
calls := []struct {
method string
raw []byte
}{{MethodUsageHandle, usageRaw}, {MethodRequestComplete, completionRaw}}
if !usageFirst {
calls[0], calls[1] = calls[1], calls[0]
}
for _, call := range calls {
if _, err := app.HandleMethod(call.method, call.raw); err != nil {
t.Fatal(err)
}
}
// 重复的 Usage 回调不能生成第二条执行记录。
if _, err := app.HandleMethod(MethodUsageHandle, usageRaw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if len(payload.Records) != 1 || payload.Records[0].RequestID != "request-1" || payload.Records[0].ExecutionID != "" || payload.Records[0].Outcome != "failed" || payload.Records[0].StatusCode != 500 {
t.Fatalf("usageFirst=%v records=%+v", usageFirst, payload.Records)
}
}
}
func TestDistinctContractUsageEventsRemainSeparate(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil {
t.Fatal(err)
}
startedAt := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
for index, failed := range []bool{true, false} {
usage := UsageRecord{
Provider: "openai", Model: "deepseek-v4-flash", RequestedAt: startedAt.Add(time.Duration(index) * time.Second),
Failed: failed, Detail: UsageDetail{InputTokens: int64(10 + index), TotalTokens: int64(10 + index)},
}
raw, _ := json.Marshal(usage)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
}
completion := RequestCompletion{RequestID: "request-retry", Model: "deepseek-v4-flash", Outcome: RequestCompletionSucceeded, StartedAt: startedAt, CompletedAt: startedAt.Add(2 * time.Second)}
raw, _ := json.Marshal(completion)
if _, err := app.HandleMethod(MethodRequestComplete, raw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if len(payload.Records) != 2 || payload.Records[0].ExecutionID != "" || payload.Records[0].RequestID != "" || payload.Records[0].Failed || payload.Records[1].ExecutionID != "" || payload.Records[1].RequestID != "request-retry" || !payload.Records[1].Failed {
t.Fatalf("unexpected usage records: %+v", payload.Records)
}
}
func TestUsageManagementResponseContainsDisplayFields(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: true\n"))); err != nil {
t.Fatal(err)
}
startedAt := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
record := UsageRecord{
Provider: "codex",
APIKey: "test-key",
Model: "gpt-5.5",
ReasoningEffort: "high",
ServiceTier: "priority",
ExecutorType: "CodexExecutor",
RequestedAt: startedAt,
Latency: 1500 * time.Millisecond,
TTFT: 250 * time.Millisecond,
Detail: UsageDetail{
InputTokens: 10,
OutputTokens: 5,
TotalTokens: 15,
CachedTokens: 4,
CacheReadTokens: 4,
CacheCreationTokens: 1,
ReasoningTokens: 2,
},
}
raw, _ := json.Marshal(record)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
completionRaw, _ := json.Marshal(RequestCompletion{
RequestID: "request-display", TraceID: "trace-display", Model: "gpt-5.5", Stream: true,
Outcome: RequestCompletionSucceeded, StartedAt: startedAt, CompletedAt: startedAt.Add(1500 * time.Millisecond),
Metadata: map[string]any{"request_path": "/v1/responses"},
})
if _, err := app.HandleMethod(MethodRequestComplete, completionRaw); err != nil {
t.Fatal(err)
}
response := managementCall(t, app, http.MethodGet, managementBase+routeUsage)
if response.StatusCode != http.StatusOK {
t.Fatalf("status = %d, body = %s", response.StatusCode, response.Body)
}
var payload usageListResponse
if err := json.Unmarshal(response.Body, &payload); err != nil {
t.Fatal(err)
}
if len(payload.Records) != 1 {
t.Fatalf("records = %d, want 1", len(payload.Records))
}
got := payload.Records[0]
if got.APIKey != "test-key" || got.Model != "gpt-5.5" || got.ReasoningEffort != "high" || got.ServiceTier != "priority" || got.ExecutorType != "CodexExecutor" {
t.Fatalf("unexpected usage identity fields: %+v", got)
}
if got.RequestID != "request-display" || got.ExecutionID != "" || got.TraceID != "trace-display" || got.RequestType != "SSE" || got.Endpoint != "/v1/responses" || got.ClientIP != "" {
t.Fatalf("unexpected request metadata: %+v", got)
}
if got.TotalTokens != 15 || got.TTFTMilliseconds != 250 || got.CacheReadTokens != 4 || got.CacheWriteTokens != 1 {
t.Fatalf("unexpected usage counters: %+v", got)
}
if got.SpeedTPS == nil || *got.SpeedTPS != 4 || got.CacheRate == nil || *got.CacheRate != 40 {
t.Fatalf("unexpected derived display fields: %+v", got)
}
if got.KeyAlias != "" || got.CostUSD != nil || got.CostAvailable {
t.Fatalf("unexpected usage item: %+v", got)
}
}
func TestUsageResourceServesTablePage(t *testing.T) {
response := managementCall(t, NewApp(), http.MethodGet, resourceBase+resourceUI)
page := string(response.Body)
if response.StatusCode != http.StatusOK || !strings.Contains(page, "请求明细") {
t.Fatalf("unexpected UI response: status=%d", response.StatusCode)
}
for _, column := range []string{"Key / 别名", "推理强度", "生成速度", "缓存写入", "总成本", "客户端 IP"} {
if !strings.Contains(page, column) {
t.Fatalf("UI does not contain column %q", column)
}
}
if !strings.Contains(page, `return "compact"`) || !strings.Contains(page, "isCompactEndpoint(record.endpoint)") {
t.Fatal("UI does not contain compact display rules")
}
for _, feature := range []string{"创建 Key", "指定账号", "允许模型", "永久归档"} {
if !strings.Contains(page, feature) {
t.Fatalf("UI does not contain managed access feature %q", feature)
}
}
for _, feature := range []string{`<body class="read-only">`, `<input id="key" type="password" autocomplete="off" aria-label="管理密钥">`, `id="page-buttons"`, "const PAGE_SIZE = 100", `id="user-usage"`, `id="daily-chart"`, `id="usage-filter-panel" class="usage-filter-panel"`, `id="usage-from"`, `id="usage-request-id"`, `id="apply-usage-filters"`, "SUMMARY_API", "READONLY_API", "dataFetch", "managementAuthorized", `overflow-y: hidden`, `id="editor-quota"`, `id="editor-reset-period"`, `id="billing-ledger"`, "deepseek-*", "请求结束后按实际费用扣款", `id="key-management" class="surface admin-only"`, `class="price-section admin-only"`, `id="price-editor-content" class="hidden"`, `row.setAttribute("role", "button")`, `id="long-price-section"`, "syncLongSectionVisibility"} {
if !strings.Contains(page, feature) {
t.Fatalf("UI does not contain workspace feature %q", feature)
}
}
if strings.Contains(page, "<script src=") || strings.Contains(page, "<link rel=\"stylesheet\" href=") {
t.Fatal("UI unexpectedly depends on external assets")
}
for _, removed := range []string{"下游 Keys", "最近用量记录", "显示 102 条", "最多展示", "1100 /", "一个 Key 对应一个用户", "查看请求结果、实际上游", "维护模型基础价格", "点击“管理”修改访问、路由与额度", "由 CPA 调度", `copy.textContent = "复制"`, "测试模式", `data-mode="demo"`, "DEMO_STORE_KEY", "demoState", "mode-button", "测试目录 · 不访问 models.dev", `id="access-mode"`, `placeholder="留空时只读"`, `label for="key"`, "密钥无效 · 只读", `id="include-archived"`, `id="reload-keys"`} {
if strings.Contains(page, removed) {
t.Fatalf("UI still contains removed description %q", removed)
}
}
for _, feature := range []string{`minlength="6"`, `maskedSecret(key.secret)`, `key.masked_secret`, `className = "credential-copy"`, `return "自由选择"`, `body.classList.toggle("read-only"`} {
if !strings.Contains(page, feature) {
t.Fatalf("UI does not contain compact Key display feature %q", feature)
}
}
if strings.Index(page, `id="page-buttons"`) > strings.Index(page, `id="headers"`) {
t.Fatal("UI pagination controls are not above the usage table")
}
for _, feature := range []string{`class="surface price-panel price-layout"`, `id="new-price"`, "基础价格 · $ / 1M Token", "长上下文价格", "Fast 价格", "删除 ${model} 的真实价格配置", `id="catalog-query"`, `id="refresh-catalog"`, "PRICE_IMPORT_API", "发现 ${changed.length} 个已关联价格发生变化"} {
if !strings.Contains(page, feature) {
t.Fatalf("UI does not contain redesigned pricing feature %q", feature)
}
}
if strings.Contains(page, "scrollIntoView") {
t.Fatal("UI pagination still changes the document scroll position")
}
}