ops_system_log_handler_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. package admin
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. "github.com/Wei-Shaw/sub2api/internal/config"
  10. "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  11. "github.com/Wei-Shaw/sub2api/internal/service"
  12. "github.com/gin-gonic/gin"
  13. )
  14. type responseEnvelope struct {
  15. Code int `json:"code"`
  16. Message string `json:"message"`
  17. Data json.RawMessage `json:"data"`
  18. }
  19. type opsSystemLogCaptureRepo struct {
  20. service.OpsRepository
  21. listFilter *service.OpsSystemLogFilter
  22. cleanupFilter *service.OpsSystemLogCleanupFilter
  23. }
  24. func (r *opsSystemLogCaptureRepo) ListSystemLogs(_ context.Context, filter *service.OpsSystemLogFilter) (*service.OpsSystemLogList, error) {
  25. r.listFilter = filter
  26. return &service.OpsSystemLogList{Logs: []*service.OpsSystemLog{}, Page: filter.Page, PageSize: filter.PageSize}, nil
  27. }
  28. func (r *opsSystemLogCaptureRepo) DeleteSystemLogs(_ context.Context, filter *service.OpsSystemLogCleanupFilter) (int64, error) {
  29. r.cleanupFilter = filter
  30. return 1, nil
  31. }
  32. func (r *opsSystemLogCaptureRepo) InsertSystemLogCleanupAudit(_ context.Context, _ *service.OpsSystemLogCleanupAudit) error {
  33. return nil
  34. }
  35. func newOpsSystemLogTestRouter(handler *OpsHandler, withUser bool) *gin.Engine {
  36. gin.SetMode(gin.TestMode)
  37. r := gin.New()
  38. if withUser {
  39. r.Use(func(c *gin.Context) {
  40. c.Set(string(middleware.ContextKeyUser), middleware.AuthSubject{UserID: 99})
  41. c.Next()
  42. })
  43. }
  44. r.GET("/logs", handler.ListSystemLogs)
  45. r.POST("/logs/cleanup", handler.CleanupSystemLogs)
  46. r.GET("/logs/health", handler.GetSystemLogIngestionHealth)
  47. return r
  48. }
  49. func TestOpsSystemLogHandler_ListUnavailable(t *testing.T) {
  50. h := NewOpsHandler(nil)
  51. r := newOpsSystemLogTestRouter(h, false)
  52. w := httptest.NewRecorder()
  53. req := httptest.NewRequest(http.MethodGet, "/logs", nil)
  54. r.ServeHTTP(w, req)
  55. if w.Code != http.StatusServiceUnavailable {
  56. t.Fatalf("status=%d, want 503", w.Code)
  57. }
  58. }
  59. func TestOpsSystemLogHandler_ListInvalidUserID(t *testing.T) {
  60. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  61. h := NewOpsHandler(svc)
  62. r := newOpsSystemLogTestRouter(h, false)
  63. w := httptest.NewRecorder()
  64. req := httptest.NewRequest(http.MethodGet, "/logs?user_id=abc", nil)
  65. r.ServeHTTP(w, req)
  66. if w.Code != http.StatusBadRequest {
  67. t.Fatalf("status=%d, want 400", w.Code)
  68. }
  69. }
  70. func TestOpsSystemLogHandler_ListInvalidAccountID(t *testing.T) {
  71. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  72. h := NewOpsHandler(svc)
  73. r := newOpsSystemLogTestRouter(h, false)
  74. w := httptest.NewRecorder()
  75. req := httptest.NewRequest(http.MethodGet, "/logs?account_id=-1", nil)
  76. r.ServeHTTP(w, req)
  77. if w.Code != http.StatusBadRequest {
  78. t.Fatalf("status=%d, want 400", w.Code)
  79. }
  80. }
  81. func TestOpsSystemLogHandler_ListInvalidAPIKeyID(t *testing.T) {
  82. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  83. h := NewOpsHandler(svc)
  84. r := newOpsSystemLogTestRouter(h, false)
  85. w := httptest.NewRecorder()
  86. req := httptest.NewRequest(http.MethodGet, "/logs?api_key_id=abc", nil)
  87. r.ServeHTTP(w, req)
  88. if w.Code != http.StatusBadRequest {
  89. t.Fatalf("status=%d, want 400", w.Code)
  90. }
  91. }
  92. func TestOpsSystemLogHandler_ListMonitoringDisabled(t *testing.T) {
  93. svc := service.NewOpsService(nil, nil, &config.Config{
  94. Ops: config.OpsConfig{Enabled: false},
  95. }, nil, nil, nil, nil, nil, nil, nil, nil)
  96. h := NewOpsHandler(svc)
  97. r := newOpsSystemLogTestRouter(h, false)
  98. w := httptest.NewRecorder()
  99. req := httptest.NewRequest(http.MethodGet, "/logs", nil)
  100. r.ServeHTTP(w, req)
  101. if w.Code != http.StatusNotFound {
  102. t.Fatalf("status=%d, want 404", w.Code)
  103. }
  104. }
  105. func TestOpsSystemLogHandler_ListSuccess(t *testing.T) {
  106. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  107. h := NewOpsHandler(svc)
  108. r := newOpsSystemLogTestRouter(h, false)
  109. w := httptest.NewRecorder()
  110. req := httptest.NewRequest(http.MethodGet, "/logs?time_range=30m&page=1&page_size=20", nil)
  111. r.ServeHTTP(w, req)
  112. if w.Code != http.StatusOK {
  113. t.Fatalf("status=%d, want 200", w.Code)
  114. }
  115. var resp responseEnvelope
  116. if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
  117. t.Fatalf("unmarshal response: %v", err)
  118. }
  119. if resp.Code != 0 {
  120. t.Fatalf("unexpected response code: %+v", resp)
  121. }
  122. }
  123. func TestOpsSystemLogHandler_ListAcceptsHost(t *testing.T) {
  124. repo := &opsSystemLogCaptureRepo{}
  125. svc := service.NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  126. h := NewOpsHandler(svc)
  127. r := newOpsSystemLogTestRouter(h, false)
  128. w := httptest.NewRecorder()
  129. req := httptest.NewRequest(http.MethodGet, "/logs?host=api-node-1", nil)
  130. r.ServeHTTP(w, req)
  131. if w.Code != http.StatusOK {
  132. t.Fatalf("status=%d, want 200", w.Code)
  133. }
  134. if repo.listFilter == nil || repo.listFilter.Host != "api-node-1" {
  135. t.Fatalf("host filter = %+v, want api-node-1", repo.listFilter)
  136. }
  137. }
  138. func TestOpsSystemLogHandler_CleanupUnauthorized(t *testing.T) {
  139. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  140. h := NewOpsHandler(svc)
  141. r := newOpsSystemLogTestRouter(h, false)
  142. w := httptest.NewRecorder()
  143. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"request_id":"r1"}`))
  144. req.Header.Set("Content-Type", "application/json")
  145. r.ServeHTTP(w, req)
  146. if w.Code != http.StatusUnauthorized {
  147. t.Fatalf("status=%d, want 401", w.Code)
  148. }
  149. }
  150. func TestOpsSystemLogHandler_CleanupInvalidPayload(t *testing.T) {
  151. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  152. h := NewOpsHandler(svc)
  153. r := newOpsSystemLogTestRouter(h, true)
  154. w := httptest.NewRecorder()
  155. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{bad-json`))
  156. req.Header.Set("Content-Type", "application/json")
  157. r.ServeHTTP(w, req)
  158. if w.Code != http.StatusBadRequest {
  159. t.Fatalf("status=%d, want 400", w.Code)
  160. }
  161. }
  162. func TestOpsSystemLogHandler_CleanupInvalidTime(t *testing.T) {
  163. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  164. h := NewOpsHandler(svc)
  165. r := newOpsSystemLogTestRouter(h, true)
  166. w := httptest.NewRecorder()
  167. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"start_time":"bad","request_id":"r1"}`))
  168. req.Header.Set("Content-Type", "application/json")
  169. r.ServeHTTP(w, req)
  170. if w.Code != http.StatusBadRequest {
  171. t.Fatalf("status=%d, want 400", w.Code)
  172. }
  173. }
  174. func TestOpsSystemLogHandler_CleanupInvalidEndTime(t *testing.T) {
  175. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  176. h := NewOpsHandler(svc)
  177. r := newOpsSystemLogTestRouter(h, true)
  178. w := httptest.NewRecorder()
  179. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"end_time":"bad","request_id":"r1"}`))
  180. req.Header.Set("Content-Type", "application/json")
  181. r.ServeHTTP(w, req)
  182. if w.Code != http.StatusBadRequest {
  183. t.Fatalf("status=%d, want 400", w.Code)
  184. }
  185. }
  186. func TestOpsSystemLogHandler_CleanupServiceUnavailable(t *testing.T) {
  187. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  188. h := NewOpsHandler(svc)
  189. r := newOpsSystemLogTestRouter(h, true)
  190. w := httptest.NewRecorder()
  191. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"request_id":"r1"}`))
  192. req.Header.Set("Content-Type", "application/json")
  193. r.ServeHTTP(w, req)
  194. if w.Code != http.StatusServiceUnavailable {
  195. t.Fatalf("status=%d, want 503", w.Code)
  196. }
  197. }
  198. func TestOpsSystemLogHandler_CleanupAcceptsAPIKeyID(t *testing.T) {
  199. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  200. h := NewOpsHandler(svc)
  201. r := newOpsSystemLogTestRouter(h, true)
  202. w := httptest.NewRecorder()
  203. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"api_key_id":123}`))
  204. req.Header.Set("Content-Type", "application/json")
  205. r.ServeHTTP(w, req)
  206. if w.Code != http.StatusServiceUnavailable {
  207. t.Fatalf("status=%d, want 503", w.Code)
  208. }
  209. }
  210. func TestOpsSystemLogHandler_CleanupAcceptsHost(t *testing.T) {
  211. repo := &opsSystemLogCaptureRepo{}
  212. svc := service.NewOpsService(repo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  213. h := NewOpsHandler(svc)
  214. r := newOpsSystemLogTestRouter(h, true)
  215. w := httptest.NewRecorder()
  216. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"host":"api-node-1"}`))
  217. req.Header.Set("Content-Type", "application/json")
  218. r.ServeHTTP(w, req)
  219. if w.Code != http.StatusOK {
  220. t.Fatalf("status=%d, want 200", w.Code)
  221. }
  222. if repo.cleanupFilter == nil || repo.cleanupFilter.Host != "api-node-1" {
  223. t.Fatalf("host filter = %+v, want api-node-1", repo.cleanupFilter)
  224. }
  225. }
  226. func TestOpsSystemLogHandler_CleanupInvalidAPIKeyID(t *testing.T) {
  227. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
  228. h := NewOpsHandler(svc)
  229. r := newOpsSystemLogTestRouter(h, true)
  230. w := httptest.NewRecorder()
  231. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"api_key_id":0}`))
  232. req.Header.Set("Content-Type", "application/json")
  233. r.ServeHTTP(w, req)
  234. if w.Code != http.StatusBadRequest {
  235. t.Fatalf("status=%d, want 400", w.Code)
  236. }
  237. }
  238. func TestOpsSystemLogHandler_CleanupMonitoringDisabled(t *testing.T) {
  239. svc := service.NewOpsService(nil, nil, &config.Config{
  240. Ops: config.OpsConfig{Enabled: false},
  241. }, nil, nil, nil, nil, nil, nil, nil, nil)
  242. h := NewOpsHandler(svc)
  243. r := newOpsSystemLogTestRouter(h, true)
  244. w := httptest.NewRecorder()
  245. req := httptest.NewRequest(http.MethodPost, "/logs/cleanup", bytes.NewBufferString(`{"request_id":"r1"}`))
  246. req.Header.Set("Content-Type", "application/json")
  247. r.ServeHTTP(w, req)
  248. if w.Code != http.StatusNotFound {
  249. t.Fatalf("status=%d, want 404", w.Code)
  250. }
  251. }
  252. func TestOpsSystemLogHandler_Health(t *testing.T) {
  253. sink := service.NewOpsSystemLogSink(nil)
  254. svc := service.NewOpsService(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sink)
  255. h := NewOpsHandler(svc)
  256. r := newOpsSystemLogTestRouter(h, false)
  257. w := httptest.NewRecorder()
  258. req := httptest.NewRequest(http.MethodGet, "/logs/health", nil)
  259. r.ServeHTTP(w, req)
  260. if w.Code != http.StatusOK {
  261. t.Fatalf("status=%d, want 200", w.Code)
  262. }
  263. }
  264. func TestOpsSystemLogHandler_HealthUnavailableAndMonitoringDisabled(t *testing.T) {
  265. h := NewOpsHandler(nil)
  266. r := newOpsSystemLogTestRouter(h, false)
  267. w := httptest.NewRecorder()
  268. req := httptest.NewRequest(http.MethodGet, "/logs/health", nil)
  269. r.ServeHTTP(w, req)
  270. if w.Code != http.StatusServiceUnavailable {
  271. t.Fatalf("status=%d, want 503", w.Code)
  272. }
  273. svc := service.NewOpsService(nil, nil, &config.Config{
  274. Ops: config.OpsConfig{Enabled: false},
  275. }, nil, nil, nil, nil, nil, nil, nil, nil)
  276. h = NewOpsHandler(svc)
  277. r = newOpsSystemLogTestRouter(h, false)
  278. w = httptest.NewRecorder()
  279. req = httptest.NewRequest(http.MethodGet, "/logs/health", nil)
  280. r.ServeHTTP(w, req)
  281. if w.Code != http.StatusNotFound {
  282. t.Fatalf("status=%d, want 404", w.Code)
  283. }
  284. }