From 71e15886ed875d1d97d50e0e42067c38d9264749 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 2 Dec 2025 12:59:18 +0000 Subject: [PATCH] test: Add isRequestAuthenticated tests for API package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive tests for the isRequestAuthenticated function covering: - Nil inputs (config, request, both) - Basic auth (valid, invalid password, invalid username, malformed base64, missing colon) - API token via X-API-Token header - API token via Bearer authorization header (case insensitive) - Invalid/empty/whitespace API tokens - No auth configured scenarios - Empty session cookie handling Coverage: isRequestAuthenticated 26.1% → 82.6% Coverage: API package 30.7% → 30.9% --- internal/api/router_test.go | 202 ++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/internal/api/router_test.go b/internal/api/router_test.go index 88ce463..d2fef7e 100644 --- a/internal/api/router_test.go +++ b/internal/api/router_test.go @@ -2,10 +2,12 @@ package api import ( "crypto/tls" + "encoding/base64" "net/http" "net/http/httptest" "testing" + "github.com/rcourtman/pulse-go-rewrite/internal/auth" "github.com/rcourtman/pulse-go-rewrite/internal/config" ) @@ -1075,3 +1077,203 @@ func TestResolvePublicURL_NilRequest(t *testing.T) { }) } } + +func TestIsRequestAuthenticated_NilInputs(t *testing.T) { + t.Run("nil config returns false", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + if isRequestAuthenticated(nil, req) { + t.Error("expected false for nil config") + } + }) + + t.Run("nil request returns false", func(t *testing.T) { + cfg := &config.Config{} + if isRequestAuthenticated(cfg, nil) { + t.Error("expected false for nil request") + } + }) + + t.Run("both nil returns false", func(t *testing.T) { + if isRequestAuthenticated(nil, nil) { + t.Error("expected false when both nil") + } + }) +} + +func TestIsRequestAuthenticated_BasicAuth(t *testing.T) { + password := "testPassword123" + hashedPass, err := auth.HashPassword(password) + if err != nil { + t.Fatalf("failed to hash password: %v", err) + } + + cfg := &config.Config{ + AuthUser: "admin", + AuthPass: hashedPass, + } + + t.Run("valid basic auth", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + creds := base64.StdEncoding.EncodeToString([]byte("admin:" + password)) + req.Header.Set("Authorization", "Basic "+creds) + + if !isRequestAuthenticated(cfg, req) { + t.Error("expected true for valid basic auth") + } + }) + + t.Run("invalid password", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + creds := base64.StdEncoding.EncodeToString([]byte("admin:wrongpassword")) + req.Header.Set("Authorization", "Basic "+creds) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for invalid password") + } + }) + + t.Run("invalid username", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + creds := base64.StdEncoding.EncodeToString([]byte("wronguser:" + password)) + req.Header.Set("Authorization", "Basic "+creds) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for invalid username") + } + }) + + t.Run("malformed base64", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("Authorization", "Basic notbase64!!!") + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for malformed base64") + } + }) + + t.Run("missing colon in credentials", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + creds := base64.StdEncoding.EncodeToString([]byte("nocolon")) + req.Header.Set("Authorization", "Basic "+creds) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for credentials without colon") + } + }) + + t.Run("no auth header", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for no auth header") + } + }) +} + +func TestIsRequestAuthenticated_APIToken(t *testing.T) { + // Create a valid API token record + rawToken := "test-api-token-12345" + record, err := config.NewAPITokenRecord(rawToken, "test-token", []string{"read"}) + if err != nil { + t.Fatalf("failed to create API token record: %v", err) + } + + cfg := &config.Config{ + APITokens: []config.APITokenRecord{*record}, + } + + t.Run("valid X-API-Token header", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("X-API-Token", rawToken) + + if !isRequestAuthenticated(cfg, req) { + t.Error("expected true for valid API token") + } + }) + + t.Run("valid Bearer token", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("Authorization", "Bearer "+rawToken) + + if !isRequestAuthenticated(cfg, req) { + t.Error("expected true for valid Bearer token") + } + }) + + t.Run("Bearer case insensitive", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("Authorization", "bearer "+rawToken) + + if !isRequestAuthenticated(cfg, req) { + t.Error("expected true for lowercase bearer") + } + }) + + t.Run("invalid API token", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("X-API-Token", "invalid-token") + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for invalid API token") + } + }) + + t.Run("empty API token", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("X-API-Token", "") + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for empty API token") + } + }) + + t.Run("whitespace only API token", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.Header.Set("X-API-Token", " ") + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for whitespace-only API token") + } + }) +} + +func TestIsRequestAuthenticated_NoAuthConfigured(t *testing.T) { + cfg := &config.Config{} + + t.Run("no auth configured returns false", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false when no auth is configured") + } + }) + + t.Run("basic auth header ignored when not configured", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + creds := base64.StdEncoding.EncodeToString([]byte("admin:password")) + req.Header.Set("Authorization", "Basic "+creds) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false when basic auth not configured") + } + }) +} + +func TestIsRequestAuthenticated_SessionCookie(t *testing.T) { + cfg := &config.Config{} + + t.Run("empty session cookie value returns false", func(t *testing.T) { + req := httptest.NewRequest("GET", "/api/test", nil) + req.AddCookie(&http.Cookie{ + Name: "pulse_session", + Value: "", + }) + + if isRequestAuthenticated(cfg, req) { + t.Error("expected false for empty session cookie value") + } + }) + + // Note: Testing valid session would require setting up the session store, + // which is tested elsewhere. Here we test the branch logic. +}