test(memory): 拆分调优写端点测试(原文件 524 行 > AGENTS §10 的 500 行上限)

守卫用例(非法值不落盘 / 坏文件不覆盖 / 同值不重写 / 热生效两种结果)移到
admin_memory_tuning_guard_test.go,helper 与读侧用例留在 admin_memory_tuning_test.go;
口径不变,两个文件都 ≤300 行。
This commit is contained in:
AskaEth committed 2026-10-13 21:59:18 +08:00
1 parent eddf921229
commit 27ee0a8149
2 files changed
+263 -245

No files matched your search

@@ -0,0 +1,263 @@
package gateway
import (
"context"
"errors"
"net/http"
"os"
"testing"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
)
// 本文件是记忆整理「面板调优」写端点的**守卫用例**(helper 与读侧用例见
// admin_memory_tuning_test.go):三态里最要紧的那两条(非法值绝不落盘、坏文件绝不
// 覆盖)与热生效的两种结果(成功 / 如实报失败)都在这里逐条钉死。
//
// 拆成两个文件只为守 AGENTS §10 的文件长度上限,口径与另一半完全同源。
// TestAdminMemoryTidyTuningWrite_InvalidValueNeverTouchesDisk 三态之三(本文件的核心
// 证据):任何非法输入都 `400 invalid_payload`,且**盘上一个字节都不动**。
func TestAdminMemoryTidyTuningWrite_InvalidValueNeverTouchesDisk(t *testing.T) {
t.Parallel()
cases := []struct{ name, body string }{
{"字符串冒充整数", `{"intervalSec": "3600"}`},
{"小数不是整数", `{"tempTtlSec": 1.5}`},
{"周期为 0(会让整理空转)", `{"intervalSec": 0}`},
{"周期为负", `{"quietIntervalSec": -60}`},
{"钟点越上界", `{"quietStartHour": 24}`},
{"钟点越下界", `{"quietEndHour": -1}`},
{"钟点不是数字", `{"quietEndHour": "07"}`},
{"开关不是布尔", `{"ingestGate": "yes"}`},
{"未知键(拼错的 quietEnd)", `{"quietEnd": 7}`},
{"请求体不是对象", `[1, 2]`},
{"请求体是 null", `null`},
{"请求体是空串", ``},
{"合法键与非法键同时提交", `{"intervalSec": 3600, "autoApply": "on"}`},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
env := newTuneEnv(t, Deps{})
before := env.seed(t, `{
"intervalSec": 86400,
"quietStartHour": 23,
"quietEndHour": 7,
"t1Judge": false
}`)
resp, body := env.put(t, env.token(t, auth.RoleOwner), tc.body)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("status = %d, want 400 (body=%s)", resp.StatusCode, body)
}
assertErrorCode(t, body, "invalid_payload")
if after := env.bytes(t); string(after) != string(before) {
t.Fatalf("非法输入改动了盘上内容:\nbefore=%s\nafter=%s", before, after)
}
})
}
// 盘上**没有**文件时,非法输入也不得创建它(「先落盘再校验」同样是落盘)。
t.Run("文件不存在时也不创建", func(t *testing.T) {
env := newTuneEnv(t, Deps{})
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": 0}`)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("status = %d, want 400 (body=%s)", resp.StatusCode, body)
}
if _, err := os.Stat(env.path()); !os.IsNotExist(err) {
t.Fatalf("非法输入创建了文件:err=%v", err)
}
})
}
// TestAdminMemoryTidyTuningWrite_NullClearsKey 清除哨兵:`null` 删掉该键(回到下一档
// 兜底),同文件其它键不受影响。
func TestAdminMemoryTidyTuningWrite_NullClearsKey(t *testing.T) {
t.Parallel()
env := newTuneEnv(t, Deps{})
env.seed(t, `{"intervalSec": 3600, "quietIntervalSec": 7200, "t1Judge": false}`)
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": null}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("clear status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
view := env.view(t, body)
if _, ok := view.Values["intervalSec"]; ok {
t.Fatalf("清除后不该再回显 intervalSec:%#v", view.Values)
}
if got := view.Values["quietIntervalSec"]; got != float64(7200) {
t.Fatalf("清除一个键影响了别的键:%#v", view.Values)
}
doc := env.diskDoc(t)
if _, ok := doc["intervalSec"]; ok {
t.Fatalf("盘上仍有 intervalSec:%s", string(env.bytes(t)))
}
if _, ok := doc["t1Judge"]; !ok {
t.Fatalf("清除一个键抹掉了别的键:%s", string(env.bytes(t)))
}
}
// TestAdminMemoryTidyTuningWrite_RefusesCorruptFile 盘上那份运行期读不走时,**读与写
// 都**拒绝(`409 tuning_corrupt`)且绝不覆盖 —— 否则面板会说「已保存」而运行期仍在
// 整体回落 `.env` 并记 WARN。
func TestAdminMemoryTidyTuningWrite_RefusesCorruptFile(t *testing.T) {
t.Parallel()
for _, tc := range []struct{ name, body string }{
{"类型写错", `{"intervalSec": "3600"}`},
{"根本不是 JSON", `not json at all`},
{"顶层不是对象", `[1, 2, 3]`},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
env := newTuneEnv(t, Deps{})
before := env.seed(t, tc.body)
tok := env.token(t, auth.RoleOwner)
resp, body := env.put(t, tok, `{"intervalSec": 3600}`)
if resp.StatusCode != http.StatusConflict {
t.Fatalf("write status = %d, want 409 (body=%s)", resp.StatusCode, body)
}
assertErrorCode(t, body, "tuning_corrupt")
if string(env.bytes(t)) != string(before) {
t.Fatalf("坏文件被覆盖了:%s", string(env.bytes(t)))
}
// 读侧同样如实报坏,而不是回一份空配置假装「没配过」。
rresp, rbody := env.get(t, tok)
if rresp.StatusCode != http.StatusConflict {
t.Fatalf("read status = %d, want 409 (body=%s)", rresp.StatusCode, rbody)
}
assertErrorCode(t, rbody, "tuning_corrupt")
})
}
}
// TestAdminMemoryTidyTuningWrite_SameValueDoesNotRewrite 「保存同样的值」不该在盘上留
// 一次无意义的改写(内容逐字节相同 ⇒ 不落盘、mtime 不动)。
func TestAdminMemoryTidyTuningWrite_SameValueDoesNotRewrite(t *testing.T) {
t.Parallel()
env := newTuneEnv(t, Deps{})
env.seed(t, `{"intervalSec": 3600}`)
past := time.Now().Add(-time.Hour)
if err := os.Chtimes(env.path(), past, past); err != nil {
t.Fatalf("chtimes: %v", err)
}
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": 3600}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
info, err := os.Stat(env.path())
if err != nil {
t.Fatalf("stat: %v", err)
}
if !info.ModTime().Equal(past) {
t.Fatalf("同值保存重写了文件(mtime %v ⇒ %v)", past, info.ModTime())
}
if string(env.bytes(t)) != `{"intervalSec": 3600}` {
t.Fatalf("盘上内容变了:%s", string(env.bytes(t)))
}
}
// fakeTuningTidy 既是整理服务的只读出口,也是「可热生效」的实现(记录收到的值)。
type fakeTuningTidy struct {
fakeTidyDryRunner
gotBase *memory.TidyConfig
gotTuning *runtimeconfig.MemoryTidyTuning
err error
}
func (f *fakeTuningTidy) ApplyPanelTuning(base memory.TidyConfig, panel runtimeconfig.MemoryTidyTuning) error {
f.gotBase = &base
f.gotTuning = &panel
return f.err
}
// fakeTuningMemories 同时实现 MemoryStore 与「入库侧可热生效」。
type fakeTuningMemories struct {
got *runtimeconfig.MemoryTidyTuning
err error
}
func (f *fakeTuningMemories) List(context.Context) ([]memory.MemoryItem, error) {
return []memory.MemoryItem{}, nil
}
func (f *fakeTuningMemories) ApplyIngestPanelTuning(panel runtimeconfig.MemoryTidyTuning) error {
f.got = &panel
return f.err
}
// TestAdminMemoryTidyTuningWrite_HotAppliesAndAudits 接线了热生效实现时:保存后当场把
// **新值**交给运行期(hot=true),并落一条审计 `memory.tidy.tuning`/ok。
func TestAdminMemoryTidyTuningWrite_HotAppliesAndAudits(t *testing.T) {
t.Parallel()
tidy := &fakeTuningTidy{}
mems := &fakeTuningMemories{}
env := newTuneEnv(t, Deps{MemoryTidy: tidy, Memories: mems})
env.seed(t, `{"intervalSec": 86400}`)
resp, body := env.put(t, env.token(t, auth.RoleAdmin), `{"intervalSec": 10800, "tempTtlSec": 259200}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
view := env.view(t, body)
if !view.Hot || view.HotError != "" {
t.Fatalf("hot/hotError = %v/%q, want true/空", view.Hot, view.HotError)
}
if tidy.gotTuning == nil || tidy.gotTuning.IntervalSec == nil || *tidy.gotTuning.IntervalSec != 10800 {
t.Fatalf("整理档没收到面板值:%#v", tidy.gotTuning)
}
if mems.got == nil || mems.got.TempTtlSec == nil || *mems.got.TempTtlSec != 259200 {
t.Fatalf("入库侧没收到面板值:%#v", mems.got)
}
// base 是 `.env` 层(进程生效配置投影),不是面板值本身。
if tidy.gotBase == nil || tidy.gotBase.IntervalSec != 86400 {
t.Fatalf("base 不是 .env 层:%#v", tidy.gotBase)
}
recs, err := env.srv.obs.audit.List(context.Background(), 50)
if err != nil {
t.Fatalf("list audit: %v", err)
}
found := false
for _, rec := range recs {
if rec.What == memoryTuningAuditAction && rec.Result == "ok" {
found = true
}
}
if !found {
t.Fatalf("审计链里没有 %s/ok 的记录:%#v", memoryTuningAuditAction, recs)
}
}
// TestAdminMemoryTidyTuningWrite_HotFailureIsHonest 热生效实现报错时:**值照旧留在盘上**
// (不丢),但响应如实 `hot:false` + 原因,绝不说成「已生效」。
func TestAdminMemoryTidyTuningWrite_HotFailureIsHonest(t *testing.T) {
t.Parallel()
tidy := &fakeTuningTidy{err: errors.New("boom")}
env := newTuneEnv(t, Deps{MemoryTidy: tidy})
env.seed(t, `{"intervalSec": 86400}`)
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": 10800}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
view := env.view(t, body)
if view.Hot || view.HotError == "" {
t.Fatalf("hot/hotError = %v/%q, want false/非空", view.Hot, view.HotError)
}
if got := view.Values["intervalSec"]; got != float64(10800) {
t.Fatalf("热生效失败不该丢值:%#v", view.Values)
}
}
// TestAdminMemoryTidyTuning_UnavailableWithoutDataDir 数据目录未接线时如实报「没有落点」,
// 绝不猜一个目录去写(端点据此回 500 tuning_unavailable)。
func TestAdminMemoryTidyTuning_UnavailableWithoutDataDir(t *testing.T) {
t.Parallel()
srv, err := New(cfgDefault(), Deps{
Auth: auth.New(config.AuthConfig{AdminUser: "a", AdminPass: "b", JWTSecret: testSecret}),
})
if err != nil {
t.Fatalf("new server: %v", err)
}
if _, ok := srv.memoryTidyTuningPath(); ok {
t.Fatal("没有数据目录时不该解析出落点")
}
}
@@ -1,9 +1,7 @@
package gateway
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"os"
@@ -14,7 +12,6 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
)
// 本文件锁定记忆整理「面板调优」读写端点(`GET|PUT /api/v1/admin/memories/tidy/tuning`)
@@ -280,245 +277,3 @@ func TestAdminMemoryTidyTuningWrite_AbsentKeyLeavesBytesUntouched(t *testing.T)
t.Fatalf("partial patch 结果 = %s", string(env.bytes(t)))
}
}
// TestAdminMemoryTidyTuningWrite_InvalidValueNeverTouchesDisk 三态之三(本文件的核心
// 证据):任何非法输入都 `400 invalid_payload`,且**盘上一个字节都不动**。
func TestAdminMemoryTidyTuningWrite_InvalidValueNeverTouchesDisk(t *testing.T) {
t.Parallel()
cases := []struct{ name, body string }{
{"字符串冒充整数", `{"intervalSec": "3600"}`},
{"小数不是整数", `{"tempTtlSec": 1.5}`},
{"周期为 0(会让整理空转)", `{"intervalSec": 0}`},
{"周期为负", `{"quietIntervalSec": -60}`},
{"钟点越上界", `{"quietStartHour": 24}`},
{"钟点越下界", `{"quietEndHour": -1}`},
{"钟点不是数字", `{"quietEndHour": "07"}`},
{"开关不是布尔", `{"ingestGate": "yes"}`},
{"未知键(拼错的 quietEnd)", `{"quietEnd": 7}`},
{"请求体不是对象", `[1, 2]`},
{"请求体是 null", `null`},
{"请求体是空串", ``},
{"合法键与非法键同时提交", `{"intervalSec": 3600, "autoApply": "on"}`},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
env := newTuneEnv(t, Deps{})
before := env.seed(t, `{
"intervalSec": 86400,
"quietStartHour": 23,
"quietEndHour": 7,
"t1Judge": false
}`)
resp, body := env.put(t, env.token(t, auth.RoleOwner), tc.body)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("status = %d, want 400 (body=%s)", resp.StatusCode, body)
}
assertErrorCode(t, body, "invalid_payload")
if after := env.bytes(t); string(after) != string(before) {
t.Fatalf("非法输入改动了盘上内容:\nbefore=%s\nafter=%s", before, after)
}
})
}
// 盘上**没有**文件时,非法输入也不得创建它(「先落盘再校验」同样是落盘)。
t.Run("文件不存在时也不创建", func(t *testing.T) {
env := newTuneEnv(t, Deps{})
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": 0}`)
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("status = %d, want 400 (body=%s)", resp.StatusCode, body)
}
if _, err := os.Stat(env.path()); !os.IsNotExist(err) {
t.Fatalf("非法输入创建了文件:err=%v", err)
}
})
}
// TestAdminMemoryTidyTuningWrite_NullClearsKey 清除哨兵:`null` 删掉该键(回到下一档
// 兜底),同文件其它键不受影响。
func TestAdminMemoryTidyTuningWrite_NullClearsKey(t *testing.T) {
t.Parallel()
env := newTuneEnv(t, Deps{})
env.seed(t, `{"intervalSec": 3600, "quietIntervalSec": 7200, "t1Judge": false}`)
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": null}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("clear status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
view := env.view(t, body)
if _, ok := view.Values["intervalSec"]; ok {
t.Fatalf("清除后不该再回显 intervalSec:%#v", view.Values)
}
if got := view.Values["quietIntervalSec"]; got != float64(7200) {
t.Fatalf("清除一个键影响了别的键:%#v", view.Values)
}
doc := env.diskDoc(t)
if _, ok := doc["intervalSec"]; ok {
t.Fatalf("盘上仍有 intervalSec:%s", string(env.bytes(t)))
}
if _, ok := doc["t1Judge"]; !ok {
t.Fatalf("清除一个键抹掉了别的键:%s", string(env.bytes(t)))
}
}
// TestAdminMemoryTidyTuningWrite_RefusesCorruptFile 盘上那份运行期读不走时,**读与写
// 都**拒绝(`409 tuning_corrupt`)且绝不覆盖 —— 否则面板会说「已保存」而运行期仍在
// 整体回落 `.env` 并记 WARN。
func TestAdminMemoryTidyTuningWrite_RefusesCorruptFile(t *testing.T) {
t.Parallel()
for _, tc := range []struct{ name, body string }{
{"类型写错", `{"intervalSec": "3600"}`},
{"根本不是 JSON", `not json at all`},
{"顶层不是对象", `[1, 2, 3]`},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
env := newTuneEnv(t, Deps{})
before := env.seed(t, tc.body)
tok := env.token(t, auth.RoleOwner)
resp, body := env.put(t, tok, `{"intervalSec": 3600}`)
if resp.StatusCode != http.StatusConflict {
t.Fatalf("write status = %d, want 409 (body=%s)", resp.StatusCode, body)
}
assertErrorCode(t, body, "tuning_corrupt")
if string(env.bytes(t)) != string(before) {
t.Fatalf("坏文件被覆盖了:%s", string(env.bytes(t)))
}
// 读侧同样如实报坏,而不是回一份空配置假装「没配过」。
rresp, rbody := env.get(t, tok)
if rresp.StatusCode != http.StatusConflict {
t.Fatalf("read status = %d, want 409 (body=%s)", rresp.StatusCode, rbody)
}
assertErrorCode(t, rbody, "tuning_corrupt")
})
}
}
// TestAdminMemoryTidyTuningWrite_SameValueDoesNotRewrite 「保存同样的值」不该在盘上留
// 一次无意义的改写(内容逐字节相同 ⇒ 不落盘、mtime 不动)。
func TestAdminMemoryTidyTuningWrite_SameValueDoesNotRewrite(t *testing.T) {
t.Parallel()
env := newTuneEnv(t, Deps{})
env.seed(t, `{"intervalSec": 3600}`)
past := time.Now().Add(-time.Hour)
if err := os.Chtimes(env.path(), past, past); err != nil {
t.Fatalf("chtimes: %v", err)
}
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": 3600}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
info, err := os.Stat(env.path())
if err != nil {
t.Fatalf("stat: %v", err)
}
if !info.ModTime().Equal(past) {
t.Fatalf("同值保存重写了文件(mtime %v ⇒ %v)", past, info.ModTime())
}
if string(env.bytes(t)) != `{"intervalSec": 3600}` {
t.Fatalf("盘上内容变了:%s", string(env.bytes(t)))
}
}
// fakeTuningTidy 既是整理服务的只读出口,也是「可热生效」的实现(记录收到的值)。
type fakeTuningTidy struct {
fakeTidyDryRunner
gotBase *memory.TidyConfig
gotTuning *runtimeconfig.MemoryTidyTuning
err error
}
func (f *fakeTuningTidy) ApplyPanelTuning(base memory.TidyConfig, panel runtimeconfig.MemoryTidyTuning) error {
f.gotBase = &base
f.gotTuning = &panel
return f.err
}
// fakeTuningMemories 同时实现 MemoryStore 与「入库侧可热生效」。
type fakeTuningMemories struct {
got *runtimeconfig.MemoryTidyTuning
err error
}
func (f *fakeTuningMemories) List(context.Context) ([]memory.MemoryItem, error) {
return []memory.MemoryItem{}, nil
}
func (f *fakeTuningMemories) ApplyIngestPanelTuning(panel runtimeconfig.MemoryTidyTuning) error {
f.got = &panel
return f.err
}
// TestAdminMemoryTidyTuningWrite_HotAppliesAndAudits 接线了热生效实现时:保存后当场把
// **新值**交给运行期(hot=true),并落一条审计 `memory.tidy.tuning`/ok。
func TestAdminMemoryTidyTuningWrite_HotAppliesAndAudits(t *testing.T) {
t.Parallel()
tidy := &fakeTuningTidy{}
mems := &fakeTuningMemories{}
env := newTuneEnv(t, Deps{MemoryTidy: tidy, Memories: mems})
env.seed(t, `{"intervalSec": 86400}`)
resp, body := env.put(t, env.token(t, auth.RoleAdmin), `{"intervalSec": 10800, "tempTtlSec": 259200}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
view := env.view(t, body)
if !view.Hot || view.HotError != "" {
t.Fatalf("hot/hotError = %v/%q, want true/空", view.Hot, view.HotError)
}
if tidy.gotTuning == nil || tidy.gotTuning.IntervalSec == nil || *tidy.gotTuning.IntervalSec != 10800 {
t.Fatalf("整理档没收到面板值:%#v", tidy.gotTuning)
}
if mems.got == nil || mems.got.TempTtlSec == nil || *mems.got.TempTtlSec != 259200 {
t.Fatalf("入库侧没收到面板值:%#v", mems.got)
}
// base 是 `.env` 层(进程生效配置投影),不是面板值本身。
if tidy.gotBase == nil || tidy.gotBase.IntervalSec != 86400 {
t.Fatalf("base 不是 .env 层:%#v", tidy.gotBase)
}
recs, err := env.srv.obs.audit.List(context.Background(), 50)
if err != nil {
t.Fatalf("list audit: %v", err)
}
found := false
for _, rec := range recs {
if rec.What == memoryTuningAuditAction && rec.Result == "ok" {
found = true
}
}
if !found {
t.Fatalf("审计链里没有 %s/ok 的记录:%#v", memoryTuningAuditAction, recs)
}
}
// TestAdminMemoryTidyTuningWrite_HotFailureIsHonest 热生效实现报错时:**值照旧留在盘上**
// (不丢),但响应如实 `hot:false` + 原因,绝不说成「已生效」。
func TestAdminMemoryTidyTuningWrite_HotFailureIsHonest(t *testing.T) {
t.Parallel()
tidy := &fakeTuningTidy{err: errors.New("boom")}
env := newTuneEnv(t, Deps{MemoryTidy: tidy})
env.seed(t, `{"intervalSec": 86400}`)
resp, body := env.put(t, env.token(t, auth.RoleOwner), `{"intervalSec": 10800}`)
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200 (body=%s)", resp.StatusCode, body)
}
view := env.view(t, body)
if view.Hot || view.HotError == "" {
t.Fatalf("hot/hotError = %v/%q, want false/非空", view.Hot, view.HotError)
}
if got := view.Values["intervalSec"]; got != float64(10800) {
t.Fatalf("热生效失败不该丢值:%#v", view.Values)
}
}
// TestAdminMemoryTidyTuning_UnavailableWithoutDataDir 数据目录未接线时如实报「没有落点」,
// 绝不猜一个目录去写(端点据此回 500 tuning_unavailable)。
func TestAdminMemoryTidyTuning_UnavailableWithoutDataDir(t *testing.T) {
t.Parallel()
srv, err := New(cfgDefault(), Deps{
Auth: auth.New(config.AuthConfig{AdminUser: "a", AdminPass: "b", JWTSecret: testSecret}),
})
if err != nil {
t.Fatalf("new server: %v", err)
}
if _, ok := srv.memoryTidyTuningPath(); ok {
t.Fatal("没有数据目录时不该解析出落点")
}
}