mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-12 20:27:30 +00:00
test: 按模块导出接口重写黑盒判据(批次 5:vector / systemd / recovery)
3 个文件全部在 `<module>/test/`,源码目录仍零 `_test.go`。
共 17 条判据,只调导出接口,输入 → 可观察输出。
## internal/memory/vector —— 纯数学 + Store
TestCosineSimilarity 稀疏向量:同向/正交/零向量
TestDenseCosine 稠密向量:三种基本情形 + 非单位向量
TestStoreInsertSearchRemove Insert → Search 命中 → Remove → 不再命中
**变异第一版没红**:`DenseCosine` 去掉归一化后,我给的 `{1,0}`
用例点积恰好还是 1 ⇒ 空变异。补 `{4,4} / {2,2}`(不归一化得 16,
归一化得 1)后才真红。**变异必须真的改变被测行为**,这条按纪律记下。
## internal/systemd —— State / NewMigratePlan
TestStateInSync 相同/等价/空/不同路径
TestStateDescribe 一致→空;不一致→含两路径与后果
TestSyncCommand 不一致时给出可执行命令
TestNewMigratePlanNormalizesPaths 路径 Clean;脚本内联新旧路径 + shebang
TestRestartCommand 命令含给定地址
不读单元文件、不执行脚本、不碰系统状态。
## internal/recovery —— 文件往返
TestTaskRoundTrip SaveTask → LoadTask 全字段 + Plugins()
TestTaskPathsAreStable TaskPath/ResultPath 稳定且互异
TestResultRoundTrip SaveResult → LoadResult 结论字段
TestLoadTaskMissingFileReportsError
TestSortSteps 输出可用
## 变异验证(改回旧实现必须红)
vector 不归一化 → ★ 非单位同向 = 16,期望 1
systemd InSync 恒 true → ★ 真不一致: InSync=true
recovery 丢 PluginList → ★ PluginList 丢失:[]
全部还原后复跑全绿。
## 验证
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
python3 .github/scripts/check-ci-gates.py PASS
go test ./... -count=1 20 个测试包 / 100 包无测试 / 0 FAIL
3 个新包 -race 全绿
源码目录 _test.go 0
This commit is contained in:
87
internal/recovery/test/task_contract_test.go
Normal file
87
internal/recovery/test/task_contract_test.go
Normal file
@ -0,0 +1,87 @@
|
||||
package test
|
||||
|
||||
// internal/recovery 的黑盒判据:SaveTask/LoadTask/SaveResult/LoadResult,
|
||||
// 输入 → 输出(文件往返)。不读实现、不碰内部字段。
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/recovery"
|
||||
)
|
||||
|
||||
// TestTaskRoundTrip:SaveTask → LoadTask 得回全部字段。
|
||||
func TestTaskRoundTrip(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "nested", "task.json")
|
||||
in := &recovery.Task{
|
||||
Attempt: 3, MaxAttempts: 5, AttemptTimeout: "120s",
|
||||
TriggerPrompt: "检查网络", PluginList: []string{"cmd", "files"},
|
||||
}
|
||||
if err := recovery.SaveTask(path, in); err != nil {
|
||||
t.Fatalf("SaveTask: %v", err)
|
||||
}
|
||||
out, err := recovery.LoadTask(path)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadTask: %v", err)
|
||||
}
|
||||
if out.Attempt != 3 || out.MaxAttempts != 5 || out.AttemptTimeout != "120s" {
|
||||
t.Fatalf("★ 往返后字段丢失:%+v", out)
|
||||
}
|
||||
if len(out.PluginList) != 2 || out.PluginList[0] != "cmd" {
|
||||
t.Fatalf("★ PluginList 丢失:%v", out.PluginList)
|
||||
}
|
||||
if got := out.Plugins(); len(got) != 2 {
|
||||
t.Fatalf("★ Plugins() = %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTaskPathsAreStable:TaskPath/ResultPath 由 dataDir 决定且互不相同。
|
||||
func TestTaskPathsAreStable(t *testing.T) {
|
||||
dir := "/data/contract"
|
||||
a := recovery.TaskPath(dir)
|
||||
b := recovery.TaskPath(dir)
|
||||
if a != b || a == "" {
|
||||
t.Fatalf("★ TaskPath 不稳定:%q vs %q", a, b)
|
||||
}
|
||||
if recovery.ResultPath(dir) == a {
|
||||
t.Fatal("★ TaskPath 与 ResultPath 相同")
|
||||
}
|
||||
}
|
||||
|
||||
// TestResultRoundTrip:SaveResult → LoadResult 得回结论字段。
|
||||
func TestResultRoundTrip(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "result.json")
|
||||
in := &recovery.Result{
|
||||
Attempt: 2, Success: true, Cause: "network", NetworkOK: true,
|
||||
RestoredNet: []string{"/etc/resolv.conf"}, ConfigRestored: true,
|
||||
QuickChatOK: true, Steps: []string{"check", "restore"},
|
||||
Message: "已恢复", Timestamp: time.Now(),
|
||||
}
|
||||
if err := recovery.SaveResult(path, in); err != nil {
|
||||
t.Fatalf("SaveResult: %v", err)
|
||||
}
|
||||
out, err := recovery.LoadResult(path)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadResult: %v", err)
|
||||
}
|
||||
if !out.Success || out.Cause != "network" || !out.ConfigRestored || !out.QuickChatOK {
|
||||
t.Fatalf("★ 往返后结论丢失:%+v", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadTaskMissingFileReportsError:读不存在的文件必须报错,不静默给空值。
|
||||
func TestLoadTaskMissingFileReportsError(t *testing.T) {
|
||||
if _, err := recovery.LoadTask(filepath.Join(t.TempDir(), "nope.json")); err == nil {
|
||||
t.Fatal("★ 读不存在的任务文件却返回成功")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSortSteps:步骤排序输出可用(不钉死具体顺序以外的内部结构)。
|
||||
func TestSortSteps(t *testing.T) {
|
||||
in := []string{"c", "a", "b"}
|
||||
out := recovery.SortSteps(in)
|
||||
if len(out) != 3 {
|
||||
t.Fatalf("★ SortSteps = %v", out)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user