@@ -11,6 +11,8 @@ import (
|
||||
"oci-portal/internal/aiwire"
|
||||
"oci-portal/internal/model"
|
||||
"oci-portal/internal/oci"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TestSpeechBodyNormalize 断言 TTS 请求体校验与 language 缺省注入。
|
||||
@@ -220,3 +222,71 @@ func TestAiModerations(t *testing.T) {
|
||||
t.Errorf("ghost 分组 err = %v, want ErrAiNoChannel", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCleanupTableBatchesOverflow 锁定超限清理的固定批次行为:
|
||||
// 溢出量大于单批上限时分多轮删完,且始终删最旧的行。
|
||||
func TestCleanupTableBatchesOverflow(t *testing.T) {
|
||||
gw, _ := newTestGateway(t, &fakeClient{})
|
||||
old := cleanupBatch
|
||||
cleanupBatch = 3
|
||||
t.Cleanup(func() { cleanupBatch = old })
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := gw.db.Create(&model.AiCallLog{Endpoint: "chat"}).Error; err != nil {
|
||||
t.Fatalf("seed %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
// maxRows=2:溢出 8 行,需 3 轮批次(3+3+2)
|
||||
gw.cleanupTable(context.Background(), &model.AiCallLog{}, 24*365*time.Hour, 2, "test")
|
||||
var rest []model.AiCallLog
|
||||
if err := gw.db.Order("id").Find(&rest).Error; err != nil {
|
||||
t.Fatalf("load rest: %v", err)
|
||||
}
|
||||
if len(rest) != 2 {
|
||||
t.Fatalf("remaining = %d, want 2", len(rest))
|
||||
}
|
||||
for _, row := range rest {
|
||||
if row.ID <= 8 {
|
||||
t.Errorf("row %d survived, want oldest deleted first", row.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogContentRechecksKeyInsideTx 锁定「立即关闭」语义:写入事务内回读密钥,
|
||||
// 窗口已关、密钥停用或已删时,鉴权期的旧快照不得再落敏感正文。
|
||||
func TestLogContentRechecksKeyInsideTx(t *testing.T) {
|
||||
gw, _ := newTestGateway(t, &fakeClient{})
|
||||
if err := gw.db.AutoMigrate(&model.AiContentLog{}); err != nil {
|
||||
t.Fatalf("migrate content log: %v", err)
|
||||
}
|
||||
future := time.Now().Add(time.Hour)
|
||||
key := model.AiKey{Name: "k", KeyHash: "h", Enabled: true, ContentLogUntil: &future}
|
||||
call := model.AiCallLog{Endpoint: "chat"}
|
||||
if err := gw.db.Create(&key).Error; err != nil {
|
||||
t.Fatalf("seed key: %v", err)
|
||||
}
|
||||
if err := gw.db.Create(&call).Error; err != nil {
|
||||
t.Fatalf("seed call: %v", err)
|
||||
}
|
||||
countIs := func(want int64, note string) {
|
||||
t.Helper()
|
||||
var n int64
|
||||
if err := gw.db.Model(&model.AiContentLog{}).Count(&n).Error; err != nil || n != want {
|
||||
t.Fatalf("%s: count=%d (%v), want %d", note, n, err, want)
|
||||
}
|
||||
}
|
||||
gw.LogContent(model.AiContentLog{CallLogID: call.ID, KeyID: key.ID, RequestBody: "prompt"})
|
||||
countIs(1, "窗口开启时应写入")
|
||||
// 管理员立即关闭窗口:在途请求携带的旧快照不得再写
|
||||
if err := gw.db.Model(&model.AiKey{}).Where("id = ?", key.ID).
|
||||
Update("content_log_until", gorm.Expr("NULL")).Error; err != nil {
|
||||
t.Fatalf("close window: %v", err)
|
||||
}
|
||||
gw.LogContent(model.AiContentLog{CallLogID: call.ID, KeyID: key.ID, RequestBody: "late"})
|
||||
countIs(1, "窗口关闭后不得写入")
|
||||
// 密钥删除后同样拒写
|
||||
if err := gw.db.Delete(&model.AiKey{}, key.ID).Error; err != nil {
|
||||
t.Fatalf("delete key: %v", err)
|
||||
}
|
||||
gw.LogContent(model.AiContentLog{CallLogID: call.ID, KeyID: key.ID, RequestBody: "orphan"})
|
||||
countIs(1, "密钥已删后不得写入")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user