- 内置 SSH 终端(/terminal):复用已保存的 SSH 密码/私钥与独立 known_hosts 校验 - 终端二次认证、会话审计(/terminal/audits)、录像审计与常用命令 - DDNS 服务商名称与自定义 IP,新增迁移 037/038/039 - CHANGELOG 与 VERSION 更新至 2.0.0
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user