修复审计日志字段格式与列表标识并补充回归测试

This commit is contained in:
xuans 2026-09-24 23:30:49 +08:00
parent fcad812c85
commit c4136cd8f3
3 changed files with 63 additions and 8 deletions

View File

@ -332,3 +332,57 @@ func TestIssueDomainSanitizesDeploymentReportWithoutTaskRepository(t *testing.T)
t.Fatalf("直接证书申请响应未返回稳定部署错误摘要:%s", response.Body.String())
}
}
func TestAuditLogResponseUsesDashboardFieldNames(t *testing.T) {
server, _, auditRepo, closeDB := newTaskAPITestServer(t, nil)
defer closeDB()
ctx := context.Background()
createdAt := time.Date(2026, 9, 24, 12, 0, 0, 0, time.UTC)
for _, status := range []string{"success", "error"} {
if err := auditRepo.CreateAuditLog(ctx, repository.AuditLog{
Action: "login", ResourceType: "auth", ResourceID: "admin",
Status: status, Message: "login", CreatedAt: createdAt,
}); err != nil {
t.Fatal(err)
}
}
response := httptest.NewRecorder()
serveAdminHTTP(server, response, httptest.NewRequest(http.MethodGet, "/api/v1/audit-logs?limit=2", nil))
if response.Code != http.StatusOK {
t.Fatal(response.Code, response.Body.String())
}
var payload struct {
Items []map[string]json.RawMessage `json:"items"`
}
if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
t.Fatal(err)
}
if len(payload.Items) != 2 {
t.Fatal(response.Body.String())
}
var previousID int64
for i, item := range payload.Items {
for _, field := range []string{"id", "action", "resource_type", "resource_id", "status", "message", "created_at"} {
if _, ok := item[field]; !ok {
t.Fatal(field, response.Body.String())
}
}
if len(item) != 7 {
t.Fatal(response.Body.String())
}
var id int64
if err := json.Unmarshal(item["id"], &id); err != nil || id <= 0 || (i > 0 && id >= previousID) {
t.Fatal(id, previousID, err)
}
previousID = id
for field, expected := range map[string]string{
"action": "login", "resource_type": "auth", "resource_id": "admin",
"status": []string{"error", "success"}[i], "message": "login", "created_at": createdAt.Format(time.RFC3339),
} {
var actual string
if err := json.Unmarshal(item[field], &actual); err != nil || actual != expected {
t.Fatal(field, actual, expected, err)
}
}
}
}

View File

@ -18,7 +18,7 @@ func (r *SQLAuditRepository) ListAuditLogs(ctx context.Context, limit int) ([]Au
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := r.db.QueryContext(ctx, r.query(`SELECT action, resource_type, resource_id, status, message, created_at FROM audit_logs ORDER BY created_at DESC, id DESC LIMIT `+fmt.Sprintf("%d", limit)))
rows, err := r.db.QueryContext(ctx, r.query(`SELECT id, action, resource_type, resource_id, status, message, created_at FROM audit_logs ORDER BY created_at DESC, id DESC LIMIT `+fmt.Sprintf("%d", limit)))
if err != nil {
return nil, fmt.Errorf("查询审计日志失败:%w", err)
}
@ -26,7 +26,7 @@ func (r *SQLAuditRepository) ListAuditLogs(ctx context.Context, limit int) ([]Au
logs := make([]AuditLog, 0)
for rows.Next() {
var item AuditLog
if err := rows.Scan(&item.Action, &item.ResourceType, &item.ResourceID, &item.Status, &item.Message, &item.CreatedAt); err != nil {
if err := rows.Scan(&item.ID, &item.Action, &item.ResourceType, &item.ResourceID, &item.Status, &item.Message, &item.CreatedAt); err != nil {
return nil, fmt.Errorf("读取审计日志失败:%w", err)
}
item.CreatedAt = item.CreatedAt.UTC()

View File

@ -10,12 +10,13 @@ import (
)
type AuditLog struct {
Action string
ResourceType string
ResourceID string
Status string
Message string
CreatedAt time.Time
ID int64 `json:"id"`
Action string `json:"action"`
ResourceType string `json:"resource_type"`
ResourceID string `json:"resource_id"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"created_at"`
}
type AuditRepository interface {