test(ops): 提升日志链路覆盖率并修复lint阻塞
This commit is contained in:
233
backend/internal/handler/admin/ops_system_log_handler_test.go
Normal file
233
backend/internal/handler/admin/ops_system_log_handler_test.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type responseEnvelope struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
func newOpsSystemLogTestRouter(handler *OpsHandler, withUser bool) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
if withUser {
|
||||
r.Use(func(c *gin.Context) {
|
||||
c.Set(string(middleware.ContextKeyUser), middleware.AuthSubject{UserID: 99})
|
||||
c.Next()
|
||||
})
|
||||
}
|
||||
r.GET("/logs", handler.ListSystemLogs)
|
||||
r.POST("/logs/cleanup", handler.CleanupSystemLogs)
|
||||
r.GET("/logs/health", handler.GetSystemLogIngestionHealth)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_ListUnavailable(t *testing.T) {
|
||||
h := NewOpsHandler(nil)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("status=%d, want 503", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_ListInvalidUserID(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs?user_id=abc", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status=%d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_ListInvalidAccountID(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs?account_id=-1", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status=%d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_ListMonitoringDisabled(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, &config.Config{
|
||||
Ops: config.OpsConfig{Enabled: false},
|
||||
}, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("status=%d, want 404", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_ListSuccess(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs?time_range=30m&page=1&page_size=20", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d, want 200", w.Code)
|
||||
}
|
||||
|
||||
var resp responseEnvelope
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("unmarshal response: %v", err)
|
||||
}
|
||||
if resp.Code != 0 {
|
||||
t.Fatalf("unexpected response code: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_CleanupUnauthorized(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"request_id":"r1"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status=%d, want 401", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_CleanupInvalidPayload(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, true)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{bad-json`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status=%d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_CleanupInvalidTime(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, true)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"start_time":"bad","request_id":"r1"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status=%d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_CleanupInvalidEndTime(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, true)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"end_time":"bad","request_id":"r1"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status=%d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_CleanupServiceUnavailable(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, true)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"request_id":"r1"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("status=%d, want 503", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_CleanupMonitoringDisabled(t *testing.T) {
|
||||
svc := service.NewOpsService(nil, nil, &config.Config{
|
||||
Ops: config.OpsConfig{Enabled: false},
|
||||
}, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, true)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"request_id":"r1"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("status=%d, want 404", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_Health(t *testing.T) {
|
||||
sink := service.NewOpsSystemLogSink(nil)
|
||||
svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sink)
|
||||
h := NewOpsHandler(svc)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs/health", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d, want 200", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpsSystemLogHandler_HealthUnavailableAndMonitoringDisabled(t *testing.T) {
|
||||
h := NewOpsHandler(nil)
|
||||
r := newOpsSystemLogTestRouter(h, false)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/logs/health", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("status=%d, want 503", w.Code)
|
||||
}
|
||||
|
||||
svc := service.NewOpsService(nil, nil, &config.Config{
|
||||
Ops: config.OpsConfig{Enabled: false},
|
||||
}, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h = NewOpsHandler(svc)
|
||||
r = newOpsSystemLogTestRouter(h, false)
|
||||
w = httptest.NewRecorder()
|
||||
req = httptest.NewRequest(http.MethodGet, "/logs/health", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("status=%d, want 404", w.Code)
|
||||
}
|
||||
}
|
||||
570
backend/internal/service/ops_log_runtime_test.go
Normal file
570
backend/internal/service/ops_log_runtime_test.go
Normal file
@@ -0,0 +1,570 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||
)
|
||||
|
||||
type runtimeSettingRepoStub struct {
|
||||
values map[string]string
|
||||
deleted map[string]bool
|
||||
setCalls int
|
||||
getValueFn func(key string) (string, error)
|
||||
setFn func(key, value string) error
|
||||
deleteFn func(key string) error
|
||||
}
|
||||
|
||||
func newRuntimeSettingRepoStub() *runtimeSettingRepoStub {
|
||||
return &runtimeSettingRepoStub{
|
||||
values: map[string]string{},
|
||||
deleted: map[string]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) Get(ctx context.Context, key string) (*Setting, error) {
|
||||
value, err := s.GetValue(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Setting{Key: key, Value: value}, nil
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) GetValue(_ context.Context, key string) (string, error) {
|
||||
if s.getValueFn != nil {
|
||||
return s.getValueFn(key)
|
||||
}
|
||||
value, ok := s.values[key]
|
||||
if !ok {
|
||||
return "", ErrSettingNotFound
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) Set(_ context.Context, key, value string) error {
|
||||
if s.setFn != nil {
|
||||
if err := s.setFn(key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.values[key] = value
|
||||
s.setCalls++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) GetMultiple(_ context.Context, keys []string) (map[string]string, error) {
|
||||
out := make(map[string]string, len(keys))
|
||||
for _, key := range keys {
|
||||
if value, ok := s.values[key]; ok {
|
||||
out[key] = value
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) SetMultiple(_ context.Context, settings map[string]string) error {
|
||||
for key, value := range settings {
|
||||
s.values[key] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) GetAll(_ context.Context) (map[string]string, error) {
|
||||
out := make(map[string]string, len(s.values))
|
||||
for key, value := range s.values {
|
||||
out[key] = value
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *runtimeSettingRepoStub) Delete(_ context.Context, key string) error {
|
||||
if s.deleteFn != nil {
|
||||
if err := s.deleteFn(key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, ok := s.values[key]; !ok {
|
||||
return ErrSettingNotFound
|
||||
}
|
||||
delete(s.values, key)
|
||||
s.deleted[key] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUpdateRuntimeLogConfig_InvalidConfigShouldNotApply(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "info",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := logger.Init(logger.InitOptions{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
ServiceName: "sub2api",
|
||||
Environment: "test",
|
||||
Output: logger.OutputOptions{
|
||||
ToStdout: true,
|
||||
ToFile: false,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("init logger: %v", err)
|
||||
}
|
||||
|
||||
_, err := svc.UpdateRuntimeLogConfig(context.Background(), &OpsRuntimeLogConfig{
|
||||
Level: "trace",
|
||||
EnableSampling: true,
|
||||
SamplingInitial: 100,
|
||||
SamplingNext: 100,
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
RetentionDays: 30,
|
||||
}, 1)
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error")
|
||||
}
|
||||
if logger.CurrentLevel() != "info" {
|
||||
t.Fatalf("logger level changed unexpectedly: %s", logger.CurrentLevel())
|
||||
}
|
||||
if repo.setCalls != 1 {
|
||||
// GetRuntimeLogConfig() 会在 key 缺失时写入默认值,此处应只有这一次持久化。
|
||||
t.Fatalf("unexpected set calls: %d", repo.setCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetRuntimeLogConfig_ShouldFallbackToBaseline(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
existing := &OpsRuntimeLogConfig{
|
||||
Level: "debug",
|
||||
EnableSampling: true,
|
||||
SamplingInitial: 50,
|
||||
SamplingNext: 50,
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
RetentionDays: 60,
|
||||
Source: "runtime_setting",
|
||||
}
|
||||
raw, _ := json.Marshal(existing)
|
||||
repo.values[SettingKeyOpsRuntimeLogConfig] = string(raw)
|
||||
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "warn",
|
||||
Caller: false,
|
||||
StacktraceLevel: "fatal",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
Ops: config.OpsConfig{
|
||||
Cleanup: config.OpsCleanupConfig{
|
||||
ErrorLogRetentionDays: 45,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := logger.Init(logger.InitOptions{
|
||||
Level: "debug",
|
||||
Format: "json",
|
||||
ServiceName: "sub2api",
|
||||
Environment: "test",
|
||||
Output: logger.OutputOptions{
|
||||
ToStdout: true,
|
||||
ToFile: false,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("init logger: %v", err)
|
||||
}
|
||||
|
||||
resetCfg, err := svc.ResetRuntimeLogConfig(context.Background(), 9)
|
||||
if err != nil {
|
||||
t.Fatalf("ResetRuntimeLogConfig() error: %v", err)
|
||||
}
|
||||
if resetCfg.Source != "baseline" {
|
||||
t.Fatalf("source = %q, want baseline", resetCfg.Source)
|
||||
}
|
||||
if resetCfg.Level != "warn" {
|
||||
t.Fatalf("level = %q, want warn", resetCfg.Level)
|
||||
}
|
||||
if resetCfg.RetentionDays != 45 {
|
||||
t.Fatalf("retention_days = %d, want 45", resetCfg.RetentionDays)
|
||||
}
|
||||
if logger.CurrentLevel() != "warn" {
|
||||
t.Fatalf("logger level = %q, want warn", logger.CurrentLevel())
|
||||
}
|
||||
if !repo.deleted[SettingKeyOpsRuntimeLogConfig] {
|
||||
t.Fatalf("runtime setting key should be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetRuntimeLogConfig_InvalidOperator(t *testing.T) {
|
||||
svc := &OpsService{settingRepo: newRuntimeSettingRepoStub()}
|
||||
_, err := svc.ResetRuntimeLogConfig(context.Background(), 0)
|
||||
if err == nil {
|
||||
t.Fatalf("expected invalid operator error")
|
||||
}
|
||||
if err.Error() != "invalid operator id" {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRuntimeLogConfig_InvalidJSONFallback(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
repo.values[SettingKeyOpsRuntimeLogConfig] = `{invalid-json}`
|
||||
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "warn",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := svc.GetRuntimeLogConfig(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("GetRuntimeLogConfig() error: %v", err)
|
||||
}
|
||||
if got.Level != "warn" {
|
||||
t.Fatalf("level = %q, want warn", got.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRuntimeLogConfig_PersistFailureRollback(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
oldCfg := &OpsRuntimeLogConfig{
|
||||
Level: "info",
|
||||
EnableSampling: false,
|
||||
SamplingInitial: 100,
|
||||
SamplingNext: 100,
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
RetentionDays: 30,
|
||||
}
|
||||
raw, _ := json.Marshal(oldCfg)
|
||||
repo.values[SettingKeyOpsRuntimeLogConfig] = string(raw)
|
||||
repo.setFn = func(key, value string) error {
|
||||
if key == SettingKeyOpsRuntimeLogConfig {
|
||||
return errors.New("db down")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "info",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := logger.Init(logger.InitOptions{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
ServiceName: "sub2api",
|
||||
Environment: "test",
|
||||
Output: logger.OutputOptions{
|
||||
ToStdout: true,
|
||||
ToFile: false,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("init logger: %v", err)
|
||||
}
|
||||
|
||||
_, err := svc.UpdateRuntimeLogConfig(context.Background(), &OpsRuntimeLogConfig{
|
||||
Level: "debug",
|
||||
EnableSampling: false,
|
||||
SamplingInitial: 100,
|
||||
SamplingNext: 100,
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
RetentionDays: 30,
|
||||
}, 5)
|
||||
if err == nil {
|
||||
t.Fatalf("expected persist error")
|
||||
}
|
||||
// Persist failure should rollback runtime level back to old effective level.
|
||||
if logger.CurrentLevel() != "info" {
|
||||
t.Fatalf("logger level should rollback to info, got %s", logger.CurrentLevel())
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRuntimeLogConfigOnStartup(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
cfgRaw := `{"level":"debug","enable_sampling":false,"sampling_initial":100,"sampling_thereafter":100,"caller":true,"stacktrace_level":"error","retention_days":30}`
|
||||
repo.values[SettingKeyOpsRuntimeLogConfig] = cfgRaw
|
||||
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "info",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := logger.Init(logger.InitOptions{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
ServiceName: "sub2api",
|
||||
Environment: "test",
|
||||
Output: logger.OutputOptions{
|
||||
ToStdout: true,
|
||||
ToFile: false,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("init logger: %v", err)
|
||||
}
|
||||
|
||||
svc.applyRuntimeLogConfigOnStartup(context.Background())
|
||||
if logger.CurrentLevel() != "debug" {
|
||||
t.Fatalf("expected startup apply debug, got %s", logger.CurrentLevel())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultNormalizeAndValidateRuntimeLogConfig(t *testing.T) {
|
||||
defaults := defaultOpsRuntimeLogConfig(&config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "DEBUG",
|
||||
Caller: false,
|
||||
StacktraceLevel: "FATAL",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: true,
|
||||
Initial: 50,
|
||||
Thereafter: 20,
|
||||
},
|
||||
},
|
||||
Ops: config.OpsConfig{
|
||||
Cleanup: config.OpsCleanupConfig{
|
||||
ErrorLogRetentionDays: 7,
|
||||
},
|
||||
},
|
||||
})
|
||||
if defaults.Level != "debug" || defaults.StacktraceLevel != "fatal" || defaults.RetentionDays != 7 {
|
||||
t.Fatalf("unexpected defaults: %+v", defaults)
|
||||
}
|
||||
|
||||
cfg := &OpsRuntimeLogConfig{
|
||||
Level: " ",
|
||||
EnableSampling: true,
|
||||
SamplingInitial: 0,
|
||||
SamplingNext: -1,
|
||||
Caller: true,
|
||||
StacktraceLevel: "",
|
||||
RetentionDays: 0,
|
||||
}
|
||||
normalizeOpsRuntimeLogConfig(cfg, defaults)
|
||||
if cfg.Level != "debug" || cfg.StacktraceLevel != "fatal" {
|
||||
t.Fatalf("normalize level/stacktrace failed: %+v", cfg)
|
||||
}
|
||||
if cfg.SamplingInitial != 50 || cfg.SamplingNext != 20 || cfg.RetentionDays != 7 {
|
||||
t.Fatalf("normalize numeric defaults failed: %+v", cfg)
|
||||
}
|
||||
if err := validateOpsRuntimeLogConfig(cfg); err != nil {
|
||||
t.Fatalf("validate normalized config should pass: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRuntimeLogConfigErrors(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
cfg *OpsRuntimeLogConfig
|
||||
}{
|
||||
{name: "nil", cfg: nil},
|
||||
{name: "bad level", cfg: &OpsRuntimeLogConfig{Level: "trace", StacktraceLevel: "error", SamplingInitial: 1, SamplingNext: 1, RetentionDays: 1}},
|
||||
{name: "bad stack", cfg: &OpsRuntimeLogConfig{Level: "info", StacktraceLevel: "warn", SamplingInitial: 1, SamplingNext: 1, RetentionDays: 1}},
|
||||
{name: "bad initial", cfg: &OpsRuntimeLogConfig{Level: "info", StacktraceLevel: "error", SamplingInitial: 0, SamplingNext: 1, RetentionDays: 1}},
|
||||
{name: "bad next", cfg: &OpsRuntimeLogConfig{Level: "info", StacktraceLevel: "error", SamplingInitial: 1, SamplingNext: 0, RetentionDays: 1}},
|
||||
{name: "bad retention", cfg: &OpsRuntimeLogConfig{Level: "info", StacktraceLevel: "error", SamplingInitial: 1, SamplingNext: 1, RetentionDays: 0}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := validateOpsRuntimeLogConfig(tc.cfg); err == nil {
|
||||
t.Fatalf("expected validation error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRuntimeLogConfigFallbackAndErrors(t *testing.T) {
|
||||
var nilSvc *OpsService
|
||||
cfg, err := nilSvc.GetRuntimeLogConfig(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("nil svc should fallback default: %v", err)
|
||||
}
|
||||
if cfg.Level != "info" {
|
||||
t.Fatalf("unexpected nil svc default level: %s", cfg.Level)
|
||||
}
|
||||
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
repo.getValueFn = func(key string) (string, error) {
|
||||
return "", errors.New("boom")
|
||||
}
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "warn",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := svc.GetRuntimeLogConfig(context.Background()); err == nil {
|
||||
t.Fatalf("expected get value error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRuntimeLogConfig_PreconditionErrors(t *testing.T) {
|
||||
svc := &OpsService{}
|
||||
if _, err := svc.UpdateRuntimeLogConfig(context.Background(), &OpsRuntimeLogConfig{}, 1); err == nil {
|
||||
t.Fatalf("expected setting repo not initialized")
|
||||
}
|
||||
|
||||
svc = &OpsService{settingRepo: newRuntimeSettingRepoStub()}
|
||||
if _, err := svc.UpdateRuntimeLogConfig(context.Background(), nil, 1); err == nil {
|
||||
t.Fatalf("expected invalid config")
|
||||
}
|
||||
if _, err := svc.UpdateRuntimeLogConfig(context.Background(), &OpsRuntimeLogConfig{
|
||||
Level: "info",
|
||||
StacktraceLevel: "error",
|
||||
SamplingInitial: 1,
|
||||
SamplingNext: 1,
|
||||
RetentionDays: 1,
|
||||
}, 0); err == nil {
|
||||
t.Fatalf("expected invalid operator")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRuntimeLogConfig_Success(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "info",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := logger.Init(logger.InitOptions{
|
||||
Level: "info",
|
||||
Format: "json",
|
||||
ServiceName: "sub2api",
|
||||
Environment: "test",
|
||||
Output: logger.OutputOptions{
|
||||
ToStdout: true,
|
||||
ToFile: false,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("init logger: %v", err)
|
||||
}
|
||||
|
||||
next, err := svc.UpdateRuntimeLogConfig(context.Background(), &OpsRuntimeLogConfig{
|
||||
Level: "debug",
|
||||
EnableSampling: false,
|
||||
SamplingInitial: 100,
|
||||
SamplingNext: 100,
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
RetentionDays: 30,
|
||||
}, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateRuntimeLogConfig() error: %v", err)
|
||||
}
|
||||
if next.Source != "runtime_setting" || next.UpdatedByUserID != 2 || next.UpdatedAt == "" {
|
||||
t.Fatalf("unexpected metadata: %+v", next)
|
||||
}
|
||||
if logger.CurrentLevel() != "debug" {
|
||||
t.Fatalf("expected applied level debug, got %s", logger.CurrentLevel())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetRuntimeLogConfig_IgnoreNotFoundDelete(t *testing.T) {
|
||||
repo := newRuntimeSettingRepoStub()
|
||||
repo.deleteFn = func(key string) error { return ErrSettingNotFound }
|
||||
svc := &OpsService{
|
||||
settingRepo: repo,
|
||||
cfg: &config.Config{
|
||||
Log: config.LogConfig{
|
||||
Level: "info",
|
||||
Caller: true,
|
||||
StacktraceLevel: "error",
|
||||
Sampling: config.LogSamplingConfig{
|
||||
Enabled: false,
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if _, err := svc.ResetRuntimeLogConfig(context.Background(), 1); err != nil {
|
||||
t.Fatalf("reset should ignore ErrSettingNotFound: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyRuntimeLogConfigHelpers(t *testing.T) {
|
||||
if err := applyOpsRuntimeLogConfig(nil); err == nil {
|
||||
t.Fatalf("expected nil config error")
|
||||
}
|
||||
|
||||
normalizeOpsRuntimeLogConfig(nil, &OpsRuntimeLogConfig{Level: "info"})
|
||||
normalizeOpsRuntimeLogConfig(&OpsRuntimeLogConfig{Level: "debug"}, nil)
|
||||
|
||||
var nilSvc *OpsService
|
||||
nilSvc.applyRuntimeLogConfigOnStartup(context.Background())
|
||||
}
|
||||
@@ -68,6 +68,20 @@ type OpsMetricThresholds struct {
|
||||
UpstreamErrorRatePercentMax *float64 `json:"upstream_error_rate_percent_max,omitempty"` // 上游错误率高于此值变红
|
||||
}
|
||||
|
||||
type OpsRuntimeLogConfig struct {
|
||||
Level string `json:"level"`
|
||||
EnableSampling bool `json:"enable_sampling"`
|
||||
SamplingInitial int `json:"sampling_initial"`
|
||||
SamplingNext int `json:"sampling_thereafter"`
|
||||
Caller bool `json:"caller"`
|
||||
StacktraceLevel string `json:"stacktrace_level"`
|
||||
RetentionDays int `json:"retention_days"`
|
||||
Source string `json:"source,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
UpdatedByUserID int64 `json:"updated_by_user_id,omitempty"`
|
||||
Extra map[string]any `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
type OpsAlertRuntimeSettings struct {
|
||||
EvaluationIntervalSeconds int `json:"evaluation_interval_seconds"`
|
||||
|
||||
|
||||
243
backend/internal/service/ops_system_log_service_test.go
Normal file
243
backend/internal/service/ops_system_log_service_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user