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) } }