package rest_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/explorer/backend/api/rest" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // setupTestServer creates a test server with a test database func setupTestServer(t *testing.T) (*rest.Server, *http.ServeMux) { // Use test database or in-memory database // For now, we'll use a mock approach db, err := setupTestDB(t) if err != nil { t.Skipf("Skipping test: database not available: %v", err) return nil, nil } server := rest.NewServer(db, 138) // ChainID 138 mux := http.NewServeMux() server.SetupRoutes(mux) return server, mux } // setupTestDB creates a test database connection. Returns (nil, nil) so unit tests // run without a real DB; handlers use requireDB(w) and return 503 when db is nil. // For integration tests with a DB, replace this with a real connection (e.g. testcontainers). func setupTestDB(t *testing.T) (*pgxpool.Pool, error) { return nil, nil } // TestHealthEndpoint tests the health check endpoint func TestHealthEndpoint(t *testing.T) { _, mux := setupTestServer(t) if mux == nil { t.Skip("setupTestServer skipped (no DB)") return } req := httptest.NewRequest("GET", "/health", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) // Without DB we get 503 degraded; with DB we get 200 assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) status, _ := response["status"].(string) assert.True(t, status == "healthy" || status == "degraded", "status=%s", status) } // TestListBlocks tests the blocks list endpoint func TestListBlocks(t *testing.T) { _, mux := setupTestServer(t) req := httptest.NewRequest("GET", "/api/v1/blocks?limit=10&page=1", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) // Without DB returns 503; with DB returns 200 or 500 assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) } // TestGetBlockByNumber tests getting a block by number func TestGetBlockByNumber(t *testing.T) { _, mux := setupTestServer(t) req := httptest.NewRequest("GET", "/api/v1/blocks/138/1000", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) // Without DB returns 503; with DB returns 200, 404, or 500 assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) } // TestListTransactions tests the transactions list endpoint func TestListTransactions(t *testing.T) { _, mux := setupTestServer(t) req := httptest.NewRequest("GET", "/api/v1/transactions?limit=10&page=1", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) } // TestGetTransactionByHash tests getting a transaction by hash func TestGetTransactionByHash(t *testing.T) { _, mux := setupTestServer(t) req := httptest.NewRequest("GET", "/api/v1/transactions/138/0x1234567890abcdef", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) } // TestSearchEndpoint tests the unified search endpoint func TestSearchEndpoint(t *testing.T) { _, mux := setupTestServer(t) testCases := []struct { name string query string wantCode int }{ {"block number", "?q=1000", http.StatusOK}, {"address", "?q=0x1234567890abcdef1234567890abcdef12345678", http.StatusOK}, {"transaction hash", "?q=0xabcdef1234567890abcdef1234567890abcdef12", http.StatusOK}, {"empty query", "?q=", http.StatusBadRequest}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest("GET", "/api/v1/search"+tc.query, nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.True(t, w.Code == tc.wantCode || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) }) } } // TestTrack1Endpoints tests Track 1 (public) endpoints func TestTrack1Endpoints(t *testing.T) { _, mux := setupTestServer(t) testCases := []struct { name string endpoint string method string }{ {"latest blocks", "/api/v1/track1/blocks/latest", "GET"}, {"latest transactions", "/api/v1/track1/txs/latest", "GET"}, {"bridge status", "/api/v1/track1/bridge/status", "GET"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(tc.method, tc.endpoint, nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) // Track 1 routes not registered in test mux (only SetupRoutes), so 404 is ok; with full setup 200/500 assert.True(t, w.Code == http.StatusOK || w.Code == http.StatusNotFound || w.Code == http.StatusInternalServerError, "code=%d", w.Code) }) } } // TestCORSHeaders tests CORS headers are present func TestCORSHeaders(t *testing.T) { _, mux := setupTestServer(t) req := httptest.NewRequest("GET", "/health", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) // Check for CORS headers (if implemented) // This is a placeholder - actual implementation may vary assert.NotNil(t, w.Header()) } // TestErrorHandling tests error responses func TestErrorHandling(t *testing.T) { _, mux := setupTestServer(t) // Test invalid block number req := httptest.NewRequest("GET", "/api/v1/blocks/138/invalid", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.True(t, w.Code >= http.StatusBadRequest) var errorResponse map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &errorResponse) if err == nil { assert.NotNil(t, errorResponse["error"]) } } // TestPagination tests pagination parameters func TestPagination(t *testing.T) { _, mux := setupTestServer(t) testCases := []struct { name string query string wantCode int }{ {"valid pagination", "?limit=10&page=1", http.StatusOK}, {"large limit", "?limit=1000&page=1", http.StatusOK}, // Should be capped {"invalid page", "?limit=10&page=0", http.StatusBadRequest}, {"negative limit", "?limit=-10&page=1", http.StatusBadRequest}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest("GET", "/api/v1/blocks"+tc.query, nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.True(t, w.Code == tc.wantCode || w.Code == http.StatusInternalServerError || w.Code == http.StatusServiceUnavailable, "code=%d", w.Code) }) } } func TestAIContextEndpoint(t *testing.T) { _, mux := setupTestServer(t) req := httptest.NewRequest("GET", "/api/v1/ai/context?q=cUSDT+bridge", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.NotNil(t, response["context"]) } func TestAIChatEndpointRequiresOpenAIKey(t *testing.T) { _, mux := setupTestServer(t) body := bytes.NewBufferString(`{"messages":[{"role":"user","content":"What is live on Chain 138?"}]}`) req := httptest.NewRequest("POST", "/api/v1/ai/chat", body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() mux.ServeHTTP(w, req) assert.Equal(t, http.StatusServiceUnavailable, w.Code) } // TestRequestTimeout tests request timeout handling func TestRequestTimeout(t *testing.T) { // This would test timeout behavior // Implementation depends on timeout middleware t.Skip("Requires timeout middleware implementation") } // BenchmarkListBlocks benchmarks the blocks list endpoint func BenchmarkListBlocks(b *testing.B) { _, mux := setupTestServer(&testing.T{}) req := httptest.NewRequest("GET", "/api/v1/blocks?limit=10&page=1", nil) b.ResetTimer() for i := 0; i < b.N; i++ { w := httptest.NewRecorder() mux.ServeHTTP(w, req) } }