421 lines
11 KiB
Go
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)
|
|
}
|
|
}
|