package routers import ( "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "git.sharkk.net/Sky/Moonshark/core/logger" ) func setupStaticFiles(t *testing.T) (string, func()) { // Create a temporary directory tempDir, err := os.MkdirTemp("", "staticrouter-test") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } // Create file structure files := map[string]string{ "index.html": "Home", "about.html": "About", "api/index.json": `{"version": "1.0"}`, "users/index.html": "Users", "users/123/profile.html": "User Profile", "posts/hello-world/comments.html": "Post Comments", "docs/v1/api.html": "API Docs", "styles.css": "body { color: red; }", "script.js": "function test() { return true; }", } for path, content := range files { filePath := filepath.Join(tempDir, path) // Create directories err := os.MkdirAll(filepath.Dir(filePath), 0755) if err != nil { t.Fatalf("Failed to create directory %s: %v", filepath.Dir(filePath), err) } // Create file err = os.WriteFile(filePath, []byte(content), 0644) if err != nil { t.Fatalf("Failed to create file %s: %v", filePath, err) } } // Return cleanup function cleanup := func() { os.RemoveAll(tempDir) } return tempDir, cleanup } func TestStaticRouterInitialization(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } if router == nil { t.Fatal("Router is nil") } // Test configuration methods router.SetMaxItems(200) router.SetMaxItemSize(2 << 20) // 2MB router.SetTotalCapacity(50 << 20) // 50MB // These methods shouldn't fail, though we can't verify internal state directly } func TestStaticRouteMatching(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } tests := []struct { path string wantFound bool }{ {"/static/index.html", true}, {"/static/about.html", true}, {"/static/api/index.json", true}, {"/static/users/index.html", true}, {"/static/users/123/profile.html", true}, {"/static/posts/hello-world/comments.html", true}, {"/static/docs/v1/api.html", true}, {"/static/styles.css", true}, {"/static/script.js", true}, // Non-existent routes {"/static/nonexistent.html", false}, {"/static/api/nonexistent.json", false}, // Routes without prefix {"/index.html", false}, {"/styles.css", false}, } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { _, found := router.Match(tt.path) if found != tt.wantFound { t.Errorf("Match() found = %v, want %v", found, tt.wantFound) } }) } // Test with different prefix router.SetURLPrefix("/assets") // Should now match with new prefix _, found := router.Match("/assets/index.html") if !found { t.Errorf("Match() should find file with new prefix") } // Should not match with old prefix _, found = router.Match("/static/index.html") if found { t.Errorf("Match() should not find file with old prefix") } } func TestStaticRefresh(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } // Add a new file newFilePath := filepath.Join(rootDir, "new.html") err = os.WriteFile(newFilePath, []byte("New"), 0644) if err != nil { t.Fatalf("Failed to create file: %v", err) } // File should be found with proper prefix _, found := router.Match("/static/new.html") if !found { t.Errorf("New file should be found immediately") } // Test refresh clears cache err = router.Refresh() if err != nil { t.Fatalf("Failed to refresh router: %v", err) } // File should still be found after refresh _, found = router.Match("/static/new.html") if !found { t.Errorf("File should still be found after refresh") } } func TestStaticRouterHTTP(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } // Enable debug logging for coverage router.EnableDebugLog() // Create a request to get a CSS file - first request will be uncached req, err := http.NewRequest("GET", "/static/styles.css", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } req.Header.Set("Accept-Encoding", "gzip") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, recorder.Code) } // Second request should be cached recorder = httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status %d, got %d for cached request", http.StatusOK, recorder.Code) } // Verify content is gzipped if recorder.Header().Get("Content-Encoding") != "gzip" { t.Errorf("Expected gzip encoding, got %s", recorder.Header().Get("Content-Encoding")) } // Verify content type is correct if !strings.Contains(recorder.Header().Get("Content-Type"), "text/css") { t.Errorf("Expected text/css content type, got %s", recorder.Header().Get("Content-Type")) } // Test with If-Modified-Since header req.Header.Set("If-Modified-Since", recorder.Header().Get("Last-Modified")) recorder = httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusNotModified { t.Errorf("Expected status %d, got %d for If-Modified-Since", http.StatusNotModified, recorder.Code) } // Test request without gzip support req.Header.Del("Accept-Encoding") req.Header.Del("If-Modified-Since") // Ensure we don't get a 304 recorder = httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status %d, got %d for non-gzip request", http.StatusOK, recorder.Code) } if recorder.Header().Get("Content-Encoding") == "gzip" { t.Errorf("Should not have gzip encoding for non-gzip request") } // Test request to non-existing file req, _ = http.NewRequest("GET", "/static/nonexistent.css", nil) recorder = httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusNotFound { t.Errorf("Expected status %d, got %d for nonexistent file", http.StatusNotFound, recorder.Code) } // Test request without prefix req, _ = http.NewRequest("GET", "/styles.css", nil) recorder = httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusNotFound { t.Errorf("Expected status %d, got %d for request without prefix", http.StatusNotFound, recorder.Code) } // Test with custom prefix router.SetURLPrefix("/assets") req, _ = http.NewRequest("GET", "/assets/styles.css", nil) req.Header.Set("Accept-Encoding", "gzip") recorder = httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status %d, got %d with custom prefix", http.StatusOK, recorder.Code) } // Disable debug logging for coverage router.DisableDebugLog() } func TestStaticRouterPreload(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } // Enable debug logging for coverage router.EnableDebugLog() // Preload files router.PreloadCommonFiles() // Request CSS file which should be preloaded req, err := http.NewRequest("GET", "/static/styles.css", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } req.Header.Set("Accept-Encoding", "gzip") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, recorder.Code) } // Should be served from cache if recorder.Header().Get("Content-Encoding") != "gzip" { t.Errorf("Expected gzip encoding after preload, got %s", recorder.Header().Get("Content-Encoding")) } } func TestStaticRouterStats(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } // Preload files to populate cache router.PreloadCommonFiles() // Wait a bit for preloading to complete // This is a bit of a hack but necessary for the async nature of preloading for i := 0; i < 100; i++ { stats := router.GetStats() items, ok := stats["items"].(int) if ok && items > 0 { break } // Small sleep to avoid tight loop http.NewRequest("GET", "/styles.css", nil) // Just to waste a little time } // Get stats stats := router.GetStats() // Verify stats exist if stats["items"] == nil { t.Errorf("Expected items stat to exist") } if stats["maxItems"] == nil { t.Errorf("Expected maxItems stat to exist") } if stats["currentSize"] == nil { t.Errorf("Expected currentSize stat to exist") } if stats["totalCapacity"] == nil { t.Errorf("Expected totalCapacity stat to exist") } if stats["usagePercent"] == nil { t.Errorf("Expected usagePercent stat to exist") } } func TestStaticRouterLargeFile(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() // Create a large file (2MB) that should exceed default max item size largeFilePath := filepath.Join(rootDir, "large.bin") largeContent := make([]byte, 2<<20) // 2MB of zeros for i := range largeContent { largeContent[i] = byte(i % 256) // Fill with pattern to prevent compression } err := os.WriteFile(largeFilePath, largeContent, 0644) if err != nil { t.Fatalf("Failed to create large file: %v", err) } router, err := NewStaticRouter(rootDir) if err != nil { t.Fatalf("Failed to create router: %v", err) } // Request large file with proper prefix req, err := http.NewRequest("GET", "/static/large.bin", nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } req.Header.Set("Accept-Encoding", "gzip") recorder := httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status %d, got %d", http.StatusOK, recorder.Code) } // Should not be served from cache (too large) if recorder.Header().Get("Content-Encoding") == "gzip" { t.Errorf("Large file should not be served with gzip from cache") } // Verify content length is correct if recorder.Body.Len() != len(largeContent) { t.Errorf("Expected body length %d, got %d", len(largeContent), recorder.Body.Len()) } } func TestStaticRouterWithLogger(t *testing.T) { rootDir, cleanup := setupStaticFiles(t) defer cleanup() // Create a test logger log := logger.New(logger.LevelDebug, false) // Create router with custom logger router, err := NewStaticRouterWithLogger(rootDir, log) if err != nil { t.Fatalf("Failed to create router with logger: %v", err) } router.EnableDebugLog() // Basic test with explicit file req, _ := http.NewRequest("GET", "/static/styles.css", nil) recorder := httptest.NewRecorder() router.ServeHTTP(recorder, req) if recorder.Code != http.StatusOK { t.Errorf("Expected status OK, got %d", recorder.Code) } }