Files
Havline/internal/terminal/audit_test.go
T
mrlee 302248a907
CI / verify (push) Canceled after 0s
chore(release): 发布 v2.0.0
- 内置 SSH 终端(/terminal):复用已保存的 SSH 密码/私钥与独立 known_hosts 校验
- 终端二次认证、会话审计(/terminal/audits)、录像审计与常用命令
- DDNS 服务商名称与自定义 IP,新增迁移 037/038/039
- CHANGELOG 与 VERSION 更新至 2.0.0
2026-10-16 20:18:04 +08:00

89 lines
2.7 KiB
Go

package terminal
import (
"context"
"path/filepath"
"testing"
"time"
"github.com/havline/havline/internal/db"
)
// TestAuditStoreRoundTrip:写入的审计能按时间倒序读回,并按保留期清理。
func TestAuditStoreRoundTrip(t *testing.T) {
conn, err := db.Open(filepath.Join(t.TempDir(), "audit.db"))
if err != nil {
t.Fatalf("打开数据库失败:%v", err)
}
defer conn.Close()
if err := db.Migrate(context.Background(), conn, filepath.Join("..", "..", "migrations")); err != nil {
t.Fatalf("迁移失败:%v", err)
}
store := NewAuditStore(conn)
ctx := context.Background()
now := time.Now().UTC()
entries := []AuditEntry{
{
SessionID: "s1", ServerID: 1, Target: "东京 VPS · root@10.0.0.1",
User: "admin", ClientIP: "192.168.1.2",
StartedAt: now.Add(-2 * time.Minute).Format(time.RFC3339),
EndedAt: now.Add(-time.Minute).Format(time.RFC3339),
DurationMS: 60_000, Reason: "用户关闭", BytesIn: 12, BytesOut: 4096,
},
{
SessionID: "s2", ServerID: 2, Target: "洛杉矶 VPS · root@10.0.0.2",
User: "admin", ClientIP: "10.1.1.1",
StartedAt: now.Format(time.RFC3339),
EndedAt: now.Add(time.Second).Format(time.RFC3339),
DurationMS: 1_000, Reason: "空闲超时",
},
}
for _, entry := range entries {
if err := store.WriteAudit(ctx, entry); err != nil {
t.Fatalf("写入审计失败:%v", err)
}
}
got, err := store.ListAudits(ctx, 10)
if err != nil {
t.Fatalf("读取审计失败:%v", err)
}
if len(got) != 2 {
t.Fatalf("期望 2 条审计,实际 %d 条", len(got))
}
// 按写入顺序倒序:最近结束的 s2 在前
if got[0].SessionID != "s2" || got[1].SessionID != "s1" {
t.Fatalf("排序不符:%+v", got)
}
if got[1].Reason != "用户关闭" || got[1].BytesOut != 4096 || got[1].ClientIP != "192.168.1.2" {
t.Fatalf("字段回读不符:%+v", got[1])
}
// 保留期外的记录会被清理
if _, err := conn.ExecContext(ctx,
`UPDATE terminal_audit_logs SET ended_at = ? WHERE session_id = 's1'`,
now.Add(-AuditRetention-time.Hour).Format(time.RFC3339)); err != nil {
t.Fatalf("准备过期数据失败:%v", err)
}
deleted, err := store.PruneAudits(ctx)
if err != nil {
t.Fatalf("清理失败:%v", err)
}
if deleted != 1 {
t.Fatalf("期望清理 1 条,实际 %d 条", deleted)
}
remaining, err := store.ListAudits(ctx, 10)
if err != nil {
t.Fatalf("读取审计失败:%v", err)
}
if len(remaining) != 1 || remaining[0].SessionID != "s2" {
t.Fatalf("清理结果不符:%+v", remaining)
}
// limit 传 0 时回落到默认值,不应报错
if _, err := store.ListAudits(ctx, 0); err != nil {
t.Fatalf("默认 limit 读取失败:%v", err)
}
}