GetConfig 从不把 rock_configs.base_url 回写到客户端,main.go 又硬编码了 https://0666.pro,导致后台改上游地址「保存那一刻生效、进程重启后失效」, 数据库里的值被静默忽略且无任何提示。 - 新增 model.RockDefaultBaseURL 统一默认值来源 - 新增 RockService.ApplyConfigToClient,启动时把 DB 地址同步到客户端 - UpdateConfig 改用 client.SetBaseURL 立即生效,不再直接写字段 - RockClient.baseURL 原为裸字段,被 HTTP 请求线程与库存巡检 goroutine 并发 读写(数据竞争),改为 sync.RWMutex 保护并新增 BaseURL/SetBaseURL - ClaimTokens/RefreshToken/RedeemSkin 的 Origin/Referer 跟随配置地址 - 空值回落默认地址,末尾斜杠自动裁剪 为保持该提交可编译,同一次改动把 rock_service.go(1177 行)按域拆分为 8 个文件(最大 221 行):rock_service / rock_bindlink / rock_stock / rock_assign / rock_bindcheck / rock_redeem / rock_recover / rock_snapshot。 35 个声明块全部有归属,无遗漏无重复;迁移文件 012 受 checksum 保护故未改动。 验证:TestRockServiceBaseURLFromConfig 覆盖「改地址→立即生效→重启→仍生效」; 并发用例在 -race 下通过;全量测试通过。
124 lines
4.0 KiB
Go
124 lines
4.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
// TestRockServiceBaseURLFromConfig 锁定「后台配置的上游地址必须真正生效,并且能跨进程重启」。
|
|
//
|
|
// 回归背景:main.go 曾把 baseURL 硬编码进 NewRockClient,而 GetConfig 从
|
|
// 不把 rock_configs.base_url 回写到客户端。结果是后台改了地址在保存那一刻生效,
|
|
// 进程一重启就悄悄退回 0666.pro。
|
|
func TestRockServiceBaseURLFromConfig(t *testing.T) {
|
|
db := newRockTestDB(t)
|
|
ctx := context.Background()
|
|
|
|
client := NewRockClient(model.RockDefaultBaseURL)
|
|
svc := NewRockService(db, client)
|
|
|
|
// 首次读取会初始化配置,默认地址应与常量一致。
|
|
cfg, err := svc.GetConfig(ctx)
|
|
if err != nil {
|
|
t.Fatalf("读取 Rock 配置失败: %v", err)
|
|
}
|
|
if cfg.BaseURL != model.RockDefaultBaseURL {
|
|
t.Fatalf("初始 base_url 期望 %s,实际 %s", model.RockDefaultBaseURL, cfg.BaseURL)
|
|
}
|
|
|
|
// 后台改地址后必须立即生效,不需要重启。
|
|
custom := "https://rock-mirror.example.com"
|
|
if _, err := svc.UpdateConfig(ctx, UpdateRockConfigReq{BaseURL: &custom}); err != nil {
|
|
t.Fatalf("更新 Rock 配置失败: %v", err)
|
|
}
|
|
if got := client.BaseURL(); got != custom {
|
|
t.Fatalf("UpdateConfig 未立即生效:期望 %s,实际 %s", custom, got)
|
|
}
|
|
|
|
// 模拟进程重启:新的客户端 + 新的 service,读同一个数据库。
|
|
restarted := NewRockClient(model.RockDefaultBaseURL)
|
|
restartedSvc := NewRockService(db, restarted)
|
|
if got := restarted.BaseURL(); got != model.RockDefaultBaseURL {
|
|
t.Fatalf("同步前应保持构造时传入的地址,实际 %s", got)
|
|
}
|
|
if err := restartedSvc.ApplyConfigToClient(ctx); err != nil {
|
|
t.Fatalf("启动同步 Rock 配置失败: %v", err)
|
|
}
|
|
if got := restarted.BaseURL(); got != custom {
|
|
t.Fatalf("重启后未采用数据库中的上游地址:期望 %s,实际 %s", custom, got)
|
|
}
|
|
}
|
|
|
|
func TestRockClientSetBaseURL(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input string
|
|
want string
|
|
}{
|
|
{"空值回落到默认地址", "", model.RockDefaultBaseURL},
|
|
{"纯空白回落到默认地址", " ", model.RockDefaultBaseURL},
|
|
{"裁剪末尾斜杠", "https://a.example.com/", "https://a.example.com"},
|
|
{"裁剪多余空白与斜杠", " https://b.example.com// ", "https://b.example.com"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
client := NewRockClient(tc.input)
|
|
if got := client.BaseURL(); got != tc.want {
|
|
t.Fatalf("期望 %q,实际 %q", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRockClientOriginHeaderFollowsBaseURL 确认 Origin / Referer 跟随配置地址,
|
|
// 而不是始终写死上游域名。
|
|
func TestRockClientOriginHeaderFollowsBaseURL(t *testing.T) {
|
|
var gotOrigin, gotReferer string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotOrigin = r.Header.Get("Origin")
|
|
gotReferer = r.Header.Get("Referer")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"tokens":[{"id":1,"redeem_code":"ABC"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewRockClient(server.URL)
|
|
if _, err := client.ClaimTokens(context.Background(), "bearer", model.RockColorBlack, 1); err != nil {
|
|
t.Fatalf("claim 失败: %v", err)
|
|
}
|
|
|
|
if gotOrigin != server.URL {
|
|
t.Fatalf("Origin 期望 %s,实际 %s", server.URL, gotOrigin)
|
|
}
|
|
if want := server.URL + "/"; gotReferer != want {
|
|
t.Fatalf("Referer 期望 %s,实际 %s", want, gotReferer)
|
|
}
|
|
}
|
|
|
|
// TestRockClientBaseURLConcurrentAccess 在 -race 下验证读写 baseURL 无数据竞争。
|
|
func TestRockClientBaseURLConcurrentAccess(t *testing.T) {
|
|
client := NewRockClient(model.RockDefaultBaseURL)
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(done)
|
|
for i := 0; i < 200; i++ {
|
|
client.SetBaseURL("https://rock-mirror.example.com")
|
|
client.SetBaseURL(model.RockDefaultBaseURL)
|
|
}
|
|
}()
|
|
|
|
for i := 0; i < 200; i++ {
|
|
if got := client.BaseURL(); got == "" {
|
|
t.Error("BaseURL 不应为空")
|
|
break
|
|
}
|
|
}
|
|
<-done
|
|
}
|