533db535f1
build / build (push) Successful in 1m12s
按已确认需求提供独立安全模型配置、真实探测、正式字典字段评估与原问题译中,持久批次支持只补空白、暂停恢复、有限重试、幂等和逐字段依据。覆盖固定规则、本地语言、组织关联与主表有效看板共用治理侧读模型,保留源生产和冻结历史。 验证:完整 make verify 退出0,702项Go与329项前端通过;默认118项外部/隔离集成明确跳过,另跑完整镜像及组织隔离PG竞态。12并发、输入漂移、过期租约、暂停/恢复及有限重试通过。用户授权的百炼 deepseek-v4.1-flash 完成真实后台与浏览器评估/翻译;552行、16列、11块看板及完整PNG、独立配置页测试/保存、重开恢复和零JS异常通过。仅非业务样本外发,自动处理关闭,原payload不变。 上线前治理库已做导出快照原生备份并真实恢复,60表行数与17个QA表内容哈希一致;不修改DobotAI生产或隧道。本提交时尚未部署,线上验收须独立完成。
57 lines
2.5 KiB
Go
57 lines
2.5 KiB
Go
package qaopsmirror
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"knowledge-governance/internal/qaopsai"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAISettingsUseRealSyntheticProbeEncryptedStorageAndExplicitKeyActions(t *testing.T) {
|
|
pool := mirrorFixture(t)
|
|
ctx := context.Background()
|
|
keyFile := filepath.Join(t.TempDir(), "master-key")
|
|
if err := os.WriteFile(keyFile, []byte(strings.Repeat("a", 32)), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
calls := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
if r.Header.Get("Authorization") != "Bearer fixture-only" {
|
|
t.Error("wrong key used")
|
|
}
|
|
_, _ = w.Write([]byte(`{"model":"fixture","choices":[{"message":{"content":"{\"ok\":true}"},"finish_reason":"stop"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
store := qaopsai.NewStore(pool, "fixture-ai-settings", qaopsai.Options{KeyFile: keyFile, AllowPrivate: true})
|
|
first, err := store.SaveSettings(ctx, qaopsai.SaveSettings{BaseURL: server.URL + "/v1", Model: "fixture", KeyAction: "replace", APIKey: "fixture-only"}, "fixture-admin")
|
|
if err != nil || !first.KeyConfigured || first.Verification != "verified" || first.Revision != 1 || calls != 1 {
|
|
t.Fatalf("save must perform real probe: settings=%+v err=%v calls=%d", first, err, calls)
|
|
}
|
|
raw, _ := json.Marshal(first)
|
|
if strings.Contains(string(raw), "fixture-only") {
|
|
t.Fatal("public settings leaked key")
|
|
}
|
|
var sealed []byte
|
|
if err = pool.QueryRow(ctx, `SELECT key_ciphertext FROM qa_ops_ai_settings WHERE tenant_key=$1`, "fixture-ai-settings").Scan(&sealed); err != nil || len(sealed) == 0 || strings.Contains(string(sealed), "fixture-only") {
|
|
t.Fatal("key not encrypted", err)
|
|
}
|
|
kept, err := store.SaveSettings(ctx, qaopsai.SaveSettings{BaseRevision: first.Revision, BaseURL: first.BaseURL, Model: first.Model, KeyAction: "keep"}, "fixture-admin")
|
|
if err != nil || !kept.KeyConfigured || calls != 2 {
|
|
t.Fatal("blank keep did not reuse saved key through probe", err)
|
|
}
|
|
cleared, err := store.SaveSettings(ctx, qaopsai.SaveSettings{BaseRevision: kept.Revision, BaseURL: kept.BaseURL, Model: kept.Model, KeyAction: "clear"}, "fixture-admin")
|
|
if err != nil || cleared.KeyConfigured || cleared.Verification != "not_configured" || calls != 2 {
|
|
t.Fatal("clear called provider or retained active key", err)
|
|
}
|
|
got, err := store.GetSettings(ctx)
|
|
if err != nil || got.Revision != cleared.Revision || got.AutomaticEvaluation || got.AutomaticTranslation {
|
|
t.Fatal("settings not read back or save started automatic processing", err)
|
|
}
|
|
}
|