package server import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" "git.maze.io/ham/hamview/schema" ) // setupTestServer creates an Echo instance with routes configured and an in-memory database func setupTestServer(t *testing.T) (*echo.Echo, *Server) { t.Helper() logger := logrus.New() logger.SetLevel(logrus.WarnLevel) // Initialize in-memory SQLite database if err := schema.Open("sqlite3", ":memory:"); err != nil { t.Fatalf("Failed to open test database: %v", err) } // Create server instance server := &Server{ listen: "127.0.0.1:0", logger: logger, } // Setup Echo with routes e := echo.New() e.HideBanner = true setupRoutes(server, e) return e, server } // teardownTestServer cleans up test resources func teardownTestServer(t *testing.T) { t.Helper() // Close database connection if needed // schema.Close() // Add this method to schema package if needed } // TestRadiosEndpoints tests all radio-related endpoints func TestRadiosEndpoints(t *testing.T) { e, _ := setupTestServer(t) defer teardownTestServer(t) t.Run("GET /api/v1/radios", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/radios", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var radios []*schema.Radio if err := json.Unmarshal(rec.Body.Bytes(), &radios); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) t.Run("GET /api/v1/radios/:protocol", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/radios/aprs", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var radios []*schema.Radio if err := json.Unmarshal(rec.Body.Bytes(), &radios); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) } // TestAPRSEndpoints tests all APRS-related endpoints func TestAPRSEndpoints(t *testing.T) { e, _ := setupTestServer(t) defer teardownTestServer(t) t.Run("GET /api/v1/aprs/packets", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/aprs/packets", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var packets []*schema.APRSPacket if err := json.Unmarshal(rec.Body.Bytes(), &packets); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) t.Run("GET /api/v1/aprs/packets filters", func(t *testing.T) { testCases := []string{ "?src=OE1ABC", "?dst=APRS", "?limit=200", } for _, query := range testCases { req := httptest.NewRequest(http.MethodGet, "/api/v1/aprs/packets"+query, nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK && rec.Code != http.StatusInternalServerError { t.Errorf("Expected status %d or %d, got %d", http.StatusOK, http.StatusInternalServerError, rec.Code) } } }) } // TestMeshCoreEndpoints tests all MeshCore-related endpoints func TestMeshCoreEndpoints(t *testing.T) { e, _ := setupTestServer(t) defer teardownTestServer(t) t.Run("GET /api/v1/meshcore", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var stats map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &stats); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) t.Run("GET /api/v1/meshcore/groups", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/groups", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var groups []any if err := json.Unmarshal(rec.Body.Bytes(), &groups); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) t.Run("GET /api/v1/meshcore/nodes", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var nodes []*schema.MeshCoreNode if err := json.Unmarshal(rec.Body.Bytes(), &nodes); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) t.Run("GET /api/v1/meshcore/nodes?type=chat", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes?type=chat", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var nodes []*schema.MeshCoreNode if err := json.Unmarshal(rec.Body.Bytes(), &nodes); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) t.Run("GET /api/v1/meshcore/packets", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/packets", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var packets []*schema.MeshCorePacket if err := json.Unmarshal(rec.Body.Bytes(), &packets); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } }) } // TestMeshCoreNodesCloseTo tests the close-to endpoint func TestMeshCoreNodesCloseTo(t *testing.T) { e, _ := setupTestServer(t) defer teardownTestServer(t) // First, insert a test node if needed // This is a placeholder - you'll need actual test data setup t.Run("GET /api/v1/meshcore/nodes/close-to/:publickey", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes/close-to/test_key", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) // May return 404 or error if no data exists, which is fine for skeleton test if rec.Code != http.StatusOK && rec.Code != http.StatusNotFound && rec.Code != http.StatusInternalServerError { t.Errorf("Expected status %d, %d, or %d, got %d", http.StatusOK, http.StatusNotFound, http.StatusInternalServerError, rec.Code) } }) t.Run("GET /api/v1/meshcore/nodes/close-to/:publickey with radius", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/nodes/close-to/test_key?radius=50000", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) // May return 404 or error if no data exists, which is fine for skeleton test if rec.Code != http.StatusOK && rec.Code != http.StatusNotFound && rec.Code != http.StatusInternalServerError { t.Errorf("Expected status %d, %d, or %d, got %d", http.StatusOK, http.StatusNotFound, http.StatusInternalServerError, rec.Code) } }) } // TestMeshCorePacketsWithFilters tests packet endpoint with various query parameters func TestMeshCorePacketsWithFilters(t *testing.T) { e, _ := setupTestServer(t) defer teardownTestServer(t) testCases := []struct { name string queryParam string }{ {"With hash", "?hash=test_hash"}, {"With type", "?type=1"}, {"With type and channel_hash", "?type=1&channel_hash=test_channel"}, {"No filters", ""}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/packets"+tc.queryParam, nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK && rec.Code != http.StatusInternalServerError { t.Errorf("Expected status %d or %d, got %d", http.StatusOK, http.StatusInternalServerError, rec.Code) } }) } } // BenchmarkGetRadios benchmarks the radios endpoint func BenchmarkGetRadios(b *testing.B) { logger := logrus.New() logger.SetLevel(logrus.ErrorLevel) if err := schema.Open("sqlite3", ":memory:"); err != nil { b.Fatalf("Failed to open test database: %v", err) } server := &Server{ listen: "127.0.0.1:0", logger: logger, } e := echo.New() e.HideBanner = true setupRoutes(server, e) req := httptest.NewRequest(http.MethodGet, "/api/v1/radios", nil) b.ResetTimer() for i := 0; i < b.N; i++ { rec := httptest.NewRecorder() e.ServeHTTP(rec, req) } } // BenchmarkGetMeshCorePackets benchmarks the packets endpoint func BenchmarkGetMeshCorePackets(b *testing.B) { logger := logrus.New() logger.SetLevel(logrus.ErrorLevel) if err := schema.Open("sqlite3", ":memory:"); err != nil { b.Fatalf("Failed to open test database: %v", err) } server := &Server{ listen: "127.0.0.1:0", logger: logger, } e := echo.New() e.HideBanner = true setupRoutes(server, e) req := httptest.NewRequest(http.MethodGet, "/api/v1/meshcore/packets", nil) b.ResetTimer() for i := 0; i < b.N; i++ { rec := httptest.NewRecorder() e.ServeHTTP(rec, req) } } // Example test showing how to populate test data func TestWithTestData(t *testing.T) { e, _ := setupTestServer(t) defer teardownTestServer(t) // Example: Insert test data ctx := context.Background() // Example radio insertion (adapt based on your schema package) // radio := &schema.Radio{ // ID: "test-radio-1", // Protocol: "aprs", // Name: "Test Radio", // } // if err := schema.InsertRadio(ctx, radio); err != nil { // t.Fatalf("Failed to insert test radio: %v", err) // } t.Run("GET /api/v1/radios with data", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/radios", nil) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, rec.Code) } var radios []*schema.Radio if err := json.Unmarshal(rec.Body.Bytes(), &radios); err != nil { t.Errorf("Failed to unmarshal response: %v", err) } // Add assertions about the data // if len(radios) != 1 { // t.Errorf("Expected 1 radio, got %d", len(radios)) // } }) _ = ctx // Use ctx to avoid unused variable error }