diff --git a/backend/internal/httpx/router.go b/backend/internal/httpx/router.go index 1a9ad0c..1640113 100644 --- a/backend/internal/httpx/router.go +++ b/backend/internal/httpx/router.go @@ -2,6 +2,7 @@ package httpx import ( "net/http" + "time" "github.com/gin-gonic/gin" "senlinai-agent/backend/internal/config" @@ -33,6 +34,9 @@ func newRouter(cfg config.Config, tokenVerifier func(string) (uint, error), regi c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) api := router.Group("/api") + api.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"timestamp_ms": time.Now().UnixMilli()}) + }) if tokenVerifier != nil { api.Use(auth.RequireUser(tokenVerifier)) } diff --git a/backend/internal/httpx/router_test.go b/backend/internal/httpx/router_test.go index 6903fb1..cb9834c 100644 --- a/backend/internal/httpx/router_test.go +++ b/backend/internal/httpx/router_test.go @@ -1,9 +1,11 @@ package httpx import ( + "encoding/json" "net/http" "net/http/httptest" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" @@ -32,6 +34,19 @@ func TestNewRouterRegistersFeatureRoutesUnderAPI(t *testing.T) { require.JSONEq(t, `{"pong":true}`, rec.Body.String()) } +func TestAPIStatusReturnsChangingTimestampMillisecondsWithoutAuth(t *testing.T) { + router := NewProtectedRouter(config.Config{Env: "test"}, func(token string) (uint, error) { + return 0, http.ErrNoCookie + }) + + first := getStatusTimestamp(t, router) + time.Sleep(2 * time.Millisecond) + second := getStatusTimestamp(t, router) + + require.Greater(t, first, int64(0)) + require.Greater(t, second, first) +} + func TestRouterAddsCORSHeadersForLocalWebClient(t *testing.T) { router := NewRouter(config.Config{Env: "test"}, testRegistrar{}) req := httptest.NewRequest(http.MethodGet, "/api/ping", nil) @@ -69,3 +84,18 @@ func (testRegistrar) Register(router gin.IRouter) { c.JSON(http.StatusOK, gin.H{"pong": true}) }) } + +func getStatusTimestamp(t *testing.T, router http.Handler) int64 { + t.Helper() + req := httptest.NewRequest(http.MethodGet, "/api/status", nil) + rec := httptest.NewRecorder() + + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + var payload struct { + TimestampMS int64 `json:"timestamp_ms"` + } + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload)) + return payload.TimestampMS +}