Files
sub2api/backend/internal/service/ops_system_log_service_test.go

244 lines
7.9 KiB
Go

package service
import (
"context"
"database/sql"
"errors"
"strings"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
)
func TestOpsServiceListSystemLogs_DefaultClampAndSuccess(t *testing.T) {
var gotFilter *OpsSystemLogFilter
repo := &opsRepoMock{
ListSystemLogsFn: func(ctx context.Context, filter *OpsSystemLogFilter) (*OpsSystemLogList, error) {
gotFilter = filter
return &OpsSystemLogList{
Logs: []*OpsSystemLog{{ID: 1, Level: "warn", Message: "x"}},
Total: 1,
Page: filter.Page,
PageSize: filter.PageSize,
}, nil
},
}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
out, err := svc.ListSystemLogs(context.Background(), &OpsSystemLogFilter{
Page: 0,
PageSize: 999,
})
if err != nil {
t.Fatalf("ListSystemLogs() error: %v", err)
}
if gotFilter == nil {
t.Fatalf("expected repository to receive filter")
}
if gotFilter.Page != 1 || gotFilter.PageSize != 200 {
t.Fatalf("filter normalized unexpectedly: page=%d pageSize=%d", gotFilter.Page, gotFilter.PageSize)
}
if out.Total != 1 || len(out.Logs) != 1 {
t.Fatalf("unexpected result: %+v", out)
}
}
func TestOpsServiceListSystemLogs_MonitoringDisabled(t *testing.T) {
svc := NewOpsService(
&opsRepoMock{},
nil,
&config.Config{Ops: config.OpsConfig{Enabled: false}},
nil, nil, nil, nil, nil, nil, nil, nil,
)
_, err := svc.ListSystemLogs(context.Background(), &OpsSystemLogFilter{})
if err == nil {
t.Fatalf("expected disabled error")
}
}
func TestOpsServiceListSystemLogs_NilRepoReturnsEmpty(t *testing.T) {
svc := NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
out, err := svc.ListSystemLogs(context.Background(), nil)
if err != nil {
t.Fatalf("ListSystemLogs() error: %v", err)
}
if out == nil || out.Page != 1 || out.PageSize != 50 || out.Total != 0 || len(out.Logs) != 0 {
t.Fatalf("unexpected nil-repo result: %+v", out)
}
}
func TestOpsServiceListSystemLogs_RepoErrorMapped(t *testing.T) {
repo := &opsRepoMock{
ListSystemLogsFn: func(ctx context.Context, filter *OpsSystemLogFilter) (*OpsSystemLogList, error) {
return nil, errors.New("db down")
},
}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
_, err := svc.ListSystemLogs(context.Background(), &OpsSystemLogFilter{})
if err == nil {
t.Fatalf("expected mapped internal error")
}
if !strings.Contains(err.Error(), "OPS_SYSTEM_LOG_LIST_FAILED") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestOpsServiceCleanupSystemLogs_SuccessAndAudit(t *testing.T) {
var audit *OpsSystemLogCleanupAudit
repo := &opsRepoMock{
DeleteSystemLogsFn: func(ctx context.Context, filter *OpsSystemLogCleanupFilter) (int64, error) {
return 3, nil
},
InsertSystemLogCleanupAuditFn: func(ctx context.Context, input *OpsSystemLogCleanupAudit) error {
audit = input
return nil
},
}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
userID := int64(7)
now := time.Now().UTC()
filter := &OpsSystemLogCleanupFilter{
StartTime: &now,
Level: "warn",
RequestID: "req-1",
ClientRequestID: "creq-1",
UserID: &userID,
Query: "timeout",
}
deleted, err := svc.CleanupSystemLogs(context.Background(), filter, 99)
if err != nil {
t.Fatalf("CleanupSystemLogs() error: %v", err)
}
if deleted != 3 {
t.Fatalf("deleted=%d, want 3", deleted)
}
if audit == nil {
t.Fatalf("expected cleanup audit")
}
if !strings.Contains(audit.Conditions, `"client_request_id":"creq-1"`) {
t.Fatalf("audit conditions should include client_request_id: %s", audit.Conditions)
}
if !strings.Contains(audit.Conditions, `"user_id":7`) {
t.Fatalf("audit conditions should include user_id: %s", audit.Conditions)
}
}
func TestOpsServiceCleanupSystemLogs_RepoUnavailableAndInvalidOperator(t *testing.T) {
svc := NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
if _, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{RequestID: "r"}, 1); err == nil {
t.Fatalf("expected repo unavailable error")
}
svc = NewOpsService(&opsRepoMock{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
if _, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{RequestID: "r"}, 0); err == nil {
t.Fatalf("expected invalid operator error")
}
}
func TestOpsServiceCleanupSystemLogs_FilterRequired(t *testing.T) {
repo := &opsRepoMock{
DeleteSystemLogsFn: func(ctx context.Context, filter *OpsSystemLogCleanupFilter) (int64, error) {
return 0, errors.New("cleanup requires at least one filter condition")
},
}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
_, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{}, 1)
if err == nil {
t.Fatalf("expected filter required error")
}
if !strings.Contains(strings.ToLower(err.Error()), "filter") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestOpsServiceCleanupSystemLogs_InvalidRange(t *testing.T) {
repo := &opsRepoMock{}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
start := time.Now().UTC()
end := start.Add(-time.Hour)
_, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{
StartTime: &start,
EndTime: &end,
}, 1)
if err == nil {
t.Fatalf("expected invalid range error")
}
}
func TestOpsServiceCleanupSystemLogs_NoRowsAndInternalError(t *testing.T) {
repo := &opsRepoMock{
DeleteSystemLogsFn: func(ctx context.Context, filter *OpsSystemLogCleanupFilter) (int64, error) {
return 0, sql.ErrNoRows
},
}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
deleted, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{
RequestID: "req-1",
}, 1)
if err != nil || deleted != 0 {
t.Fatalf("expected no rows shortcut, deleted=%d err=%v", deleted, err)
}
repo.DeleteSystemLogsFn = func(ctx context.Context, filter *OpsSystemLogCleanupFilter) (int64, error) {
return 0, errors.New("boom")
}
if _, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{
RequestID: "req-1",
}, 1); err == nil {
t.Fatalf("expected internal cleanup error")
}
}
func TestOpsServiceCleanupSystemLogs_AuditFailureIgnored(t *testing.T) {
repo := &opsRepoMock{
DeleteSystemLogsFn: func(ctx context.Context, filter *OpsSystemLogCleanupFilter) (int64, error) {
return 5, nil
},
InsertSystemLogCleanupAuditFn: func(ctx context.Context, input *OpsSystemLogCleanupAudit) error {
return errors.New("audit down")
},
}
svc := NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
deleted, err := svc.CleanupSystemLogs(context.Background(), &OpsSystemLogCleanupFilter{
RequestID: "r1",
}, 1)
if err != nil || deleted != 5 {
t.Fatalf("audit failure should not break cleanup, deleted=%d err=%v", deleted, err)
}
}
func TestMarshalSystemLogCleanupConditions_NilAndMarshalError(t *testing.T) {
if got := marshalSystemLogCleanupConditions(nil); got != "{}" {
t.Fatalf("nil filter should return {}, got %s", got)
}
now := time.Now().UTC()
userID := int64(1)
filter := &OpsSystemLogCleanupFilter{
StartTime: &now,
EndTime: &now,
UserID: &userID,
}
got := marshalSystemLogCleanupConditions(filter)
if !strings.Contains(got, `"start_time"`) || !strings.Contains(got, `"user_id":1`) {
t.Fatalf("unexpected marshal payload: %s", got)
}
}
func TestOpsServiceGetSystemLogSinkHealth(t *testing.T) {
svc := NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
health := svc.GetSystemLogSinkHealth()
if health.QueueCapacity != 0 || health.QueueDepth != 0 {
t.Fatalf("unexpected health for nil sink: %+v", health)
}
sink := NewOpsSystemLogSink(&opsRepoMock{})
svc = NewOpsService(&opsRepoMock{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, sink)
health = svc.GetSystemLogSinkHealth()
if health.QueueCapacity <= 0 {
t.Fatalf("expected non-zero queue capacity: %+v", health)
}
}