Moonshark/core/routers/staticrouter_test.go
2025-03-13 16:43:08 -05:00

421 lines
11 KiB
Go

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": "<html>Home</html>",
"about.html": "<html>About</html>",
"api/index.json": `{"version": "1.0"}`,
"users/index.html": "<html>Users</html>",
"users/123/profile.html": "<html>User Profile</html>",
"posts/hello-world/comments.html": "<html>Post Comments</html>",
"docs/v1/api.html": "<html>API Docs</html>",
"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("<html>New</html>"), 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)
}
}