package admin import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/pkg/logger" "github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" ) type testSettingRepo struct { values map[string]string } func newTestSettingRepo() *testSettingRepo { return &testSettingRepo{values: map[string]string{}} } func (s *testSettingRepo) Get(ctx context.Context, key string) (*service.Setting, error) { v, err := s.GetValue(ctx, key) if err != nil { return nil, err } return &service.Setting{Key: key, Value: v}, nil } func (s *testSettingRepo) GetValue(ctx context.Context, key string) (string, error) { v, ok := s.values[key] if !ok { return "", service.ErrSettingNotFound } return v, nil } func (s *testSettingRepo) Set(ctx context.Context, key, value string) error { s.values[key] = value return nil } func (s *testSettingRepo) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) { out := make(map[string]string, len(keys)) for _, k := range keys { if v, ok := s.values[k]; ok { out[k] = v } } return out, nil } func (s *testSettingRepo) SetMultiple(ctx context.Context, settings map[string]string) error { for k, v := range settings { s.values[k] = v } return nil } func (s *testSettingRepo) GetAll(ctx context.Context) (map[string]string, error) { out := make(map[string]string, len(s.values)) for k, v := range s.values { out[k] = v } return out, nil } func (s *testSettingRepo) Delete(ctx context.Context, key string) error { delete(s.values, key) return nil } func newOpsRuntimeRouter(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: 7}) c.Next() }) } r.GET("/runtime/logging", handler.GetRuntimeLogConfig) r.PUT("/runtime/logging", handler.UpdateRuntimeLogConfig) r.POST("/runtime/logging/reset", handler.ResetRuntimeLogConfig) return r } func newRuntimeOpsService(t *testing.T) *service.OpsService { t.Helper() if err := logger.Init(logger.InitOptions{ Level: "info", Format: "json", ServiceName: "sub2api", Environment: "test", Output: logger.OutputOptions{ ToStdout: false, ToFile: false, }, }); err != nil { t.Fatalf("init logger: %v", err) } settingRepo := newTestSettingRepo() cfg := &config.Config{ Ops: config.OpsConfig{Enabled: true}, Log: config.LogConfig{ Level: "info", Caller: true, StacktraceLevel: "error", Sampling: config.LogSamplingConfig{ Enabled: false, Initial: 100, Thereafter: 100, }, }, } return service.NewOpsService(nil, settingRepo, cfg, nil, nil, nil, nil, nil, nil, nil, nil) } func TestOpsRuntimeLoggingHandler_GetConfig(t *testing.T) { h := NewOpsHandler(newRuntimeOpsService(t)) r := newOpsRuntimeRouter(h, false) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/runtime/logging", nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status=%d, want 200", w.Code) } } func TestOpsRuntimeLoggingHandler_UpdateUnauthorized(t *testing.T) { h := NewOpsHandler(newRuntimeOpsService(t)) r := newOpsRuntimeRouter(h, false) body := `{"level":"debug","enable_sampling":false,"sampling_initial":100,"sampling_thereafter":100,"caller":true,"stacktrace_level":"error","retention_days":30}` w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPut, "/runtime/logging", bytes.NewBufferString(body)) 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 TestOpsRuntimeLoggingHandler_UpdateAndResetSuccess(t *testing.T) { h := NewOpsHandler(newRuntimeOpsService(t)) r := newOpsRuntimeRouter(h, true) payload := map[string]any{ "level": "debug", "enable_sampling": false, "sampling_initial": 100, "sampling_thereafter": 100, "caller": true, "stacktrace_level": "error", "retention_days": 30, } raw, _ := json.Marshal(payload) w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPut, "/runtime/logging", bytes.NewReader(raw)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("update status=%d, want 200, body=%s", w.Code, w.Body.String()) } w = httptest.NewRecorder() req = httptest.NewRequest(http.MethodPost, "/runtime/logging/reset", nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("reset status=%d, want 200, body=%s", w.Code, w.Body.String()) } }