mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-15 23:22:59 +08:00
188 lines
6.7 KiB
Go
188 lines
6.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCodexContextWindowSyncRoundTrip(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
fields string
|
|
wantWindow int64
|
|
wantMax int64
|
|
}{
|
|
{"distinct windows", `"context_window":272000,"max_context_window":872000`, 272000, 872000},
|
|
{"equal windows", `"context_window":272000,"max_context_window":272000`, 272000, 272000},
|
|
{"default only", `"context_window":272000`, 272000, 272000},
|
|
{"maximum only", `"max_context_window":872000`, 872000, 872000},
|
|
{"registry style limit", `"limit":{"context":128000}`, 128000, 128000},
|
|
{"zero maximum", `"context_window":272000,"max_context_window":0`, 272000, 272000},
|
|
{"negative maximum", `"context_window":272000,"max_context_window":-1`, 272000, 272000},
|
|
{"maximum below default", `"context_window":272000,"max_context_window":128000`, 128000, 128000},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
account := codexContextWindowAccount(t, 1, `"context_window":64000`)
|
|
upstream := &httpUpstreamRecorder{resp: &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: http.Header{"Content-Type": []string{"application/json"}},
|
|
Body: io.NopCloser(strings.NewReader(fmt.Sprintf(`{"models":[{
|
|
"slug":"gpt-6-astra","reasoning":true,
|
|
"supported_reasoning_levels":[{"effort":"high"}],
|
|
"input_modalities":["text","image"],%s
|
|
}]}`, tc.fields))),
|
|
}}
|
|
repo := &upstreamModelMetadataRepoStub{}
|
|
syncer := &AccountTestService{accountRepo: repo, httpUpstream: upstream, cfg: upstreamModelSyncTestConfig()}
|
|
catalog, err := syncer.SyncUpstreamModelCatalog(context.Background(), &account)
|
|
require.NoError(t, err)
|
|
require.Empty(t, catalog.Warnings)
|
|
require.Len(t, upstream.requests, 1)
|
|
require.Equal(t, account.ID, repo.accountID)
|
|
|
|
// Reload the JSON representation persisted in account.extra, replacing
|
|
// the legacy snapshot so the test exercises resync as well as parsing.
|
|
persisted, err := json.Marshal(repo.updates)
|
|
require.NoError(t, err)
|
|
account.Extra = nil
|
|
require.NoError(t, json.Unmarshal(persisted, &account.Extra))
|
|
model := codexContextWindowManifest(t, []Account{account})
|
|
require.EqualValues(t, tc.wantWindow, model["context_window"])
|
|
require.EqualValues(t, tc.wantMax, model["max_context_window"])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCodexContextWindowAccountIntersection(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
accounts []string
|
|
wantWindow int64
|
|
wantMax int64
|
|
}{
|
|
{
|
|
"independent minima",
|
|
[]string{`"context_window":272000,"max_context_window":872000`, `"context_window":300000,"max_context_window":512000`},
|
|
272000, 512000,
|
|
},
|
|
{
|
|
"legacy snapshot caps maximum",
|
|
[]string{`"context_window":272000,"max_context_window":872000`, `"context_window":300000`},
|
|
272000, 300000,
|
|
},
|
|
{
|
|
"legacy snapshot alone",
|
|
[]string{`"context_window":272000`},
|
|
272000, 272000,
|
|
},
|
|
{
|
|
"invalid maximum uses known default",
|
|
[]string{`"context_window":272000,"max_context_window":872000`, `"context_window":300000,"max_context_window":-1`},
|
|
272000, 300000,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
accounts := make([]Account, len(tc.accounts))
|
|
for i, fields := range tc.accounts {
|
|
accounts[i] = codexContextWindowAccount(t, int64(i+1), fields)
|
|
}
|
|
for range 2 {
|
|
model := codexContextWindowManifest(t, accounts)
|
|
require.EqualValues(t, tc.wantWindow, model["context_window"])
|
|
require.EqualValues(t, tc.wantMax, model["max_context_window"])
|
|
for i, j := 0, len(accounts)-1; i < j; i, j = i+1, j-1 {
|
|
accounts[i], accounts[j] = accounts[j], accounts[i]
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCodexContextWindowRegistryEnrichment(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
fields string
|
|
wantWindow int64
|
|
wantMax int64
|
|
}{
|
|
{"preserve upstream limits", `"context_window":272000,"max_context_window":872000`, 272000, 872000},
|
|
{"upstream default remains ceiling", `"context_window":272000`, 272000, 272000},
|
|
{"registry supplies missing limits", `"description":"Model without context metadata"`, 1050000, 1050000},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
account := codexContextWindowAccount(t, 1, `"context_window":64000`)
|
|
account.Type = AccountTypeAPIKey
|
|
account.Credentials["api_key"] = "test-key"
|
|
account.Credentials["base_url"] = "https://provider.example/v1"
|
|
upstream := &httpUpstreamRecorder{responses: []*http.Response{
|
|
{
|
|
StatusCode: http.StatusOK,
|
|
Body: io.NopCloser(strings.NewReader(fmt.Sprintf(
|
|
`{"data":[{"id":"gpt-6-astra",%s}]}`, tc.fields))),
|
|
},
|
|
{
|
|
StatusCode: http.StatusOK,
|
|
Body: io.NopCloser(strings.NewReader(`{"provider":{
|
|
"id":"provider","api":"https://provider.example/v1","models":{
|
|
"gpt-6-astra":{"id":"gpt-6-astra","reasoning":true,
|
|
"reasoning_options":[{"type":"effort","values":["high"]}],
|
|
"modalities":{"input":["text","image"]},"limit":{"context":1050000}}
|
|
}
|
|
}}`)),
|
|
},
|
|
}}
|
|
syncer := &AccountTestService{
|
|
accountRepo: &upstreamModelMetadataRepoStub{}, httpUpstream: upstream, cfg: upstreamModelSyncTestConfig(),
|
|
}
|
|
catalog, err := syncer.SyncUpstreamModelCatalog(context.Background(), &account)
|
|
require.NoError(t, err)
|
|
require.Empty(t, catalog.Warnings)
|
|
require.Len(t, upstream.requests, 2)
|
|
model := codexContextWindowManifest(t, []Account{account})
|
|
require.EqualValues(t, tc.wantWindow, model["context_window"])
|
|
require.EqualValues(t, tc.wantMax, model["max_context_window"])
|
|
})
|
|
}
|
|
}
|
|
|
|
func codexContextWindowAccount(t *testing.T, id int64, fields string) Account {
|
|
t.Helper()
|
|
account := Account{
|
|
ID: id, Platform: PlatformOpenAI, Type: AccountTypeOAuth,
|
|
Credentials: map[string]any{
|
|
"access_token": "test-token",
|
|
"model_mapping": map[string]any{"gpt-6-astra": "gpt-6-astra"},
|
|
},
|
|
}
|
|
err := json.Unmarshal([]byte(fmt.Sprintf(`{"upstream_model_metadata":{
|
|
"source":"upstream","models":{"gpt-6-astra":{
|
|
"id":"gpt-6-astra","reasoning":true,
|
|
"supported_reasoning_levels":["high"],"input_modalities":["text","image"],%s
|
|
}}
|
|
}}`, fields)), &account.Extra)
|
|
require.NoError(t, err)
|
|
return account
|
|
}
|
|
|
|
func codexContextWindowManifest(t *testing.T, accounts []Account) map[string]any {
|
|
t.Helper()
|
|
const groupID int64 = 7042
|
|
svc := &GatewayService{accountRepo: codexModelsVisibilityAccountRepo{byGroup: map[int64][]Account{
|
|
groupID: accounts,
|
|
}}}
|
|
body, err := svc.BuildCodexModelsManifestForGroup(
|
|
context.Background(), &Group{ID: groupID, Platform: PlatformOpenAI}, "", []string{"gpt-6-astra"},
|
|
)
|
|
require.NoError(t, err)
|
|
models := decodeCodexManifestModels(t, body)
|
|
require.Len(t, models, 1)
|
|
return models[0]
|
|
}
|