feat: sync deployed v1.0.0-rc.38-custom.67
This commit is contained in:
1 parent
d2e03ab7e8
commit
ffc8cf56db
154 files changed
+39540
-20509
No files matched your search
@@ -2,6 +2,8 @@ package controller
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/pkg/billingexpr"
|
||||
@@ -11,10 +13,15 @@ import (
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -32,6 +39,10 @@ func TestDetectionProxyFlushesBeforeGenerationAndConsumesCredentialOnce(t *testi
|
||||
require.NoError(t, db.AutoMigrate(&model.Channel{}, &model.DetectionRun{}, &model.DetectionCall{}))
|
||||
release := make(chan struct{})
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, readErr := io.ReadAll(r.Body)
|
||||
require.NoError(t, readErr)
|
||||
assert.Equal(t, "medium", gjson.GetBytes(body, "reasoning.effort").String())
|
||||
|
||||
select {
|
||||
case <-release:
|
||||
case <-r.Context().Done():
|
||||
@@ -169,3 +180,209 @@ func TestDetectionLatestPerChannelStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectionMeowPaidRoundAndResume(t *testing.T) {
|
||||
oldRedis := common.RedisEnabled
|
||||
common.RedisEnabled = false
|
||||
t.Cleanup(func() { common.RedisEnabled = oldRedis })
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
conn, err := db.DB()
|
||||
require.NoError(t, err)
|
||||
conn.SetMaxOpenConns(1)
|
||||
old := model.DB
|
||||
model.DB = db
|
||||
t.Cleanup(func() { model.DB = old; _ = conn.Close() })
|
||||
require.NoError(t, db.AutoMigrate(&model.Channel{}, &model.DetectionRun{}, &model.DetectionCall{}, &model.User{}))
|
||||
data, err := os.ReadFile("../pkg/model_fingerprint/testdata/meow-reference.json")
|
||||
require.NoError(t, err)
|
||||
var fixtures []struct {
|
||||
Samples []fingerprint.Sample `json:"samples"`
|
||||
}
|
||||
require.NoError(t, common.Unmarshal(data, &fixtures))
|
||||
calls := 0
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Less(t, calls, 32)
|
||||
answer := fixtures[0].Samples[calls].Text
|
||||
calls++
|
||||
body, encodeErr := common.Marshal(map[string]any{"status": "completed", "usage": map[string]int{"input_tokens": 10, "output_tokens": 2}, "output": []any{map[string]any{"type": "message", "content": []any{map[string]string{"type": "output_text", "text": answer}}}}})
|
||||
require.NoError(t, encodeErr)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(body)
|
||||
}))
|
||||
defer upstream.Close()
|
||||
url := upstream.URL
|
||||
channel := model.Channel{Type: 1, Key: "test-only", BaseURL: &url, Status: common.ChannelStatusEnabled, Models: fingerprint.Model}
|
||||
require.NoError(t, db.Create(&channel).Error)
|
||||
owner := model.User{Username: "meow-test", Quota: 1000, Status: common.UserStatusEnabled}
|
||||
require.NoError(t, db.Create(&owner).Error)
|
||||
price, err := common.Marshal(service.DetectionPrice{Snapshot: billingexpr.BillingSnapshot{ExprString: "p+c", ExprHash: billingexpr.ExprHashString("p+c"), GroupRatio: 1, QuotaPerUnit: 500000, ExprVersion: 1}, Request: billingexpr.RequestInput{Body: []byte(`{"max_output_tokens":128,"reasoning":{"effort":"low"}}`)}})
|
||||
require.NoError(t, err)
|
||||
run := model.DetectionRun{ID: "meow-test", RequestKey: "meow-test", UserID: owner.Id, Kind: "benchmark", ChannelID: channel.Id, Status: "pending", Deadline: time.Now().Unix() + 60, Reserved: 320, Price: string(price)}
|
||||
require.NoError(t, model.CreateDetection(&run))
|
||||
processPaidFingerprint(context.Background(), run)
|
||||
require.NoError(t, db.First(&run, "id = ?", run.ID).Error)
|
||||
assert.Equal(t, "succeeded", run.Status)
|
||||
assert.Equal(t, "normal", run.Quality)
|
||||
assert.True(t, run.Settled)
|
||||
assert.Equal(t, 32, calls)
|
||||
assert.Equal(t, 192, run.Charged)
|
||||
require.NoError(t, db.First(&owner, owner.Id).Error)
|
||||
assert.Equal(t, 808, owner.Quota)
|
||||
processPaidFingerprint(context.Background(), run)
|
||||
assert.Equal(t, 32, calls, "resume must not resend or recharge")
|
||||
require.NoError(t, db.First(&owner, owner.Id).Error)
|
||||
assert.Equal(t, 808, owner.Quota)
|
||||
}
|
||||
|
||||
func TestDetectionModelTracePaidRoundAndResume(t *testing.T) {
|
||||
oldRedis := common.RedisEnabled
|
||||
common.RedisEnabled = false
|
||||
t.Cleanup(func() { common.RedisEnabled = oldRedis })
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
conn, err := db.DB()
|
||||
require.NoError(t, err)
|
||||
conn.SetMaxOpenConns(1)
|
||||
old := model.DB
|
||||
model.DB = db
|
||||
t.Cleanup(func() { model.DB = old; _ = conn.Close() })
|
||||
require.NoError(t, db.AutoMigrate(&model.Channel{}, &model.DetectionRun{}, &model.DetectionCall{}, &model.User{}))
|
||||
data, err := os.ReadFile("../pkg/model_fingerprint/testdata/modeltrace-reference.json")
|
||||
require.NoError(t, err)
|
||||
var fixtures []struct {
|
||||
Samples []fingerprint.Sample `json:"samples"`
|
||||
}
|
||||
require.NoError(t, common.Unmarshal(data, &fixtures))
|
||||
calls := 0
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Less(t, calls, 3)
|
||||
answer := fixtures[1].Samples[calls].Text
|
||||
calls++
|
||||
body, encodeErr := common.Marshal(map[string]any{"status": "completed", "usage": map[string]int{"input_tokens": 10, "output_tokens": 2}, "output": []any{map[string]any{"type": "message", "content": []any{map[string]string{"type": "output_text", "text": answer}}}}})
|
||||
require.NoError(t, encodeErr)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(body)
|
||||
}))
|
||||
defer upstream.Close()
|
||||
url := upstream.URL
|
||||
channel := model.Channel{Type: 1, Key: "test-only", BaseURL: &url, Status: common.ChannelStatusEnabled, Models: fingerprint.Model}
|
||||
require.NoError(t, db.Create(&channel).Error)
|
||||
owner := model.User{Username: "meow-test", Quota: 1000, Status: common.UserStatusEnabled}
|
||||
require.NoError(t, db.Create(&owner).Error)
|
||||
price, err := common.Marshal(service.DetectionPrice{Snapshot: billingexpr.BillingSnapshot{ExprString: "p+c", ExprHash: billingexpr.ExprHashString("p+c"), GroupRatio: 1, QuotaPerUnit: 500000, ExprVersion: 1}, Request: billingexpr.RequestInput{Body: []byte(`{"max_output_tokens":4096,"reasoning":{"effort":"low"}}`)}})
|
||||
require.NoError(t, err)
|
||||
run := model.DetectionRun{ID: "meow-test", RequestKey: "meow-test", UserID: owner.Id, Kind: "fingerprint", ChannelID: channel.Id, Status: "pending", Deadline: time.Now().Unix() + 60, Reserved: 320, Price: string(price)}
|
||||
require.NoError(t, model.CreateDetection(&run))
|
||||
processPaidFingerprint(context.Background(), run)
|
||||
require.NoError(t, db.First(&run, "id = ?", run.ID).Error)
|
||||
assert.Equal(t, "succeeded", run.Status)
|
||||
assert.Equal(t, "match", run.Quality)
|
||||
assert.True(t, run.Settled)
|
||||
assert.Equal(t, 3, calls)
|
||||
assert.Equal(t, 18, run.Charged)
|
||||
require.NoError(t, db.First(&owner, owner.Id).Error)
|
||||
assert.Equal(t, 982, owner.Quota)
|
||||
processPaidFingerprint(context.Background(), run)
|
||||
assert.Equal(t, 3, calls, "resume must not resend or recharge")
|
||||
require.NoError(t, db.First(&owner, owner.Id).Error)
|
||||
assert.Equal(t, 982, owner.Quota)
|
||||
}
|
||||
|
||||
func TestCandyAutomaticRoundAndResume(t *testing.T) {
|
||||
for _, dialect := range []string{"sqlite", "mysql", "postgres"} {
|
||||
t.Run(dialect, func(t *testing.T) {
|
||||
oldRedis := common.RedisEnabled
|
||||
common.RedisEnabled = false
|
||||
t.Cleanup(func() { common.RedisEnabled = oldRedis })
|
||||
var driver gorm.Dialector = sqlite.Open(":memory:")
|
||||
if dialect == "mysql" {
|
||||
if os.Getenv("TEST_MYSQL_DSN") == "" {
|
||||
t.Skip("TEST_MYSQL_DSN unset")
|
||||
}
|
||||
driver = mysql.Open(os.Getenv("TEST_MYSQL_DSN"))
|
||||
}
|
||||
if dialect == "postgres" {
|
||||
if os.Getenv("TEST_POSTGRES_DSN") == "" {
|
||||
t.Skip("TEST_POSTGRES_DSN unset")
|
||||
}
|
||||
driver = postgres.Open(os.Getenv("TEST_POSTGRES_DSN"))
|
||||
}
|
||||
db, err := gorm.Open(driver, &gorm.Config{NamingStrategy: schema.NamingStrategy{TablePrefix: fmt.Sprintf("candy_%d_", time.Now().UnixNano())}})
|
||||
require.NoError(t, err)
|
||||
conn, err := db.DB()
|
||||
require.NoError(t, err)
|
||||
conn.SetMaxOpenConns(1)
|
||||
oldType, oldLogType := common.MainDatabaseType(), common.LogDatabaseType()
|
||||
common.SetDatabaseTypes(common.DatabaseType(dialect), common.DatabaseType(dialect))
|
||||
t.Cleanup(func() { common.SetDatabaseTypes(oldType, oldLogType) })
|
||||
old := model.DB
|
||||
model.DB = db
|
||||
t.Cleanup(func() {
|
||||
for _, row := range []any{&model.Channel{}, &model.DetectionRun{}, &model.DetectionCall{}, &model.User{}, &model.DetectionSchedule{}, &model.SystemTask{}} {
|
||||
require.NoError(t, db.Migrator().DropTable(row))
|
||||
}
|
||||
model.DB = old
|
||||
_ = conn.Close()
|
||||
})
|
||||
require.NoError(t, db.AutoMigrate(&model.Channel{}, &model.DetectionRun{}, &model.DetectionCall{}, &model.User{}, &model.DetectionSchedule{}, &model.SystemTask{}))
|
||||
calls := 0
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Less(t, calls, 1)
|
||||
answer := "21"
|
||||
raw, readErr := io.ReadAll(r.Body)
|
||||
require.NoError(t, readErr)
|
||||
assert.Contains(t, gjson.GetBytes(raw, "input.0.content").String(), "圆形 7 9 8")
|
||||
assert.NotContains(t, gjson.GetBytes(raw, "input.0.content").String(), "21")
|
||||
calls++
|
||||
body, encodeErr := common.Marshal(map[string]any{"status": "completed", "usage": map[string]int{"input_tokens": 10, "output_tokens": 2}, "output": []any{map[string]any{"type": "message", "content": []any{map[string]string{"type": "output_text", "text": answer}}}}})
|
||||
require.NoError(t, encodeErr)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(body)
|
||||
}))
|
||||
defer upstream.Close()
|
||||
url := upstream.URL
|
||||
channel := model.Channel{Type: 1, Key: "test-only", BaseURL: &url, Status: common.ChannelStatusEnabled, Models: fingerprint.Model, Group: "candy-group"}
|
||||
require.NoError(t, db.Create(&channel).Error)
|
||||
owner := model.User{Username: "meow-test", Quota: 1000, Status: common.UserStatusEnabled}
|
||||
require.NoError(t, db.Create(&owner).Error)
|
||||
price, err := common.Marshal(service.DetectionPrice{Snapshot: billingexpr.BillingSnapshot{ExprString: "p+c", ExprHash: billingexpr.ExprHashString("p+c"), GroupRatio: 1, QuotaPerUnit: 500000, ExprVersion: 1}, Request: billingexpr.RequestInput{Body: []byte(`{"max_output_tokens":4096,"reasoning":{"effort":"low"}}`)}})
|
||||
require.NoError(t, err)
|
||||
run := model.DetectionRun{ID: "meow-test", RequestKey: "meow-test", Kind: "candy", CreatedAt: time.Now().Unix(), ChannelID: channel.Id, Status: "pending", Deadline: time.Now().Unix() + 60, Reserved: 320, Price: string(price)}
|
||||
require.NoError(t, model.CreateDetection(&run))
|
||||
require.NoError(t, model.EnsureDetectionSchedule(context.Background(), model.DetectionSchedule{ChannelID: channel.Id, Kind: "candy", IntervalMinutes: 30}))
|
||||
processDetection(context.Background(), run)
|
||||
require.NoError(t, db.First(&run, "id = ?", run.ID).Error)
|
||||
assert.Equal(t, "succeeded", run.Status)
|
||||
assert.Equal(t, "normal", run.Quality)
|
||||
assert.True(t, run.Settled)
|
||||
var clock model.DetectionSchedule
|
||||
require.NoError(t, db.First(&clock, "channel_id = ? AND kind = ?", channel.Id, "candy").Error)
|
||||
assert.Equal(t, "normal", clock.LastValid)
|
||||
assert.Greater(t, clock.NextDueAt, run.FinishedAt)
|
||||
assert.Equal(t, 1, calls)
|
||||
assert.Equal(t, 0, run.Charged, "automatic tests do not charge a customer")
|
||||
require.NoError(t, db.First(&owner, owner.Id).Error)
|
||||
assert.Equal(t, 1000, owner.Quota)
|
||||
processDetection(context.Background(), run)
|
||||
assert.Equal(t, 1, calls, "resume must not resend or recharge")
|
||||
require.NoError(t, db.First(&owner, owner.Id).Error)
|
||||
assert.Equal(t, 1000, owner.Quota)
|
||||
summaries, err := service.GroupDetectionSummaries(context.Background(), time.Now(), map[string]*service.AstraSamplingSummary{}, map[string]model.StatusPageGroup{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, summaries["candy-group"].Candy)
|
||||
assert.Equal(t, 1, summaries["candy-group"].Candy.Normal)
|
||||
assert.Equal(t, "21", summaries["candy-group"].Candy.History[0].Closest)
|
||||
assert.Zero(t, summaries["candy-group"].Benchmark.Normal)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandyAnswers(t *testing.T) {
|
||||
for _, tc := range []struct{ answer, quality string }{
|
||||
{"21", "normal"}, {" 21\n", "normal"}, {"28", "degraded"}, {"121", "degraded"}, {"21或28", "degraded"}, {"答案不是21", "degraded"}, {"", "degraded"},
|
||||
} {
|
||||
t.Run(tc.answer, func(t *testing.T) { assert.Equal(t, tc.quality, service.CandyQuality(tc.answer)) })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user