CI / verify (push) Canceled after 0s
- 内置 SSH 终端(/terminal):复用已保存的 SSH 密码/私钥与独立 known_hosts 校验 - 终端二次认证、会话审计(/terminal/audits)、录像审计与常用命令 - DDNS 服务商名称与自定义 IP,新增迁移 037/038/039 - CHANGELOG 与 VERSION 更新至 2.0.0
89 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|