package middleware import ( "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/casera-api/internal/config" "github.com/treytartt/casera-api/internal/models" ) func TestGetAuthUser_NilContext_ReturnsNil(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // No user set in context user := GetAuthUser(c) assert.Nil(t, user) } func TestGetAuthUser_WrongType_ReturnsNil(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) // Set wrong type in context — should NOT panic c.Set(AuthUserKey, "not-a-user") user := GetAuthUser(c) assert.Nil(t, user) } func TestGetAuthUser_ValidUser_ReturnsUser(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) expected := &models.User{Username: "testuser"} c.Set(AuthUserKey, expected) user := GetAuthUser(c) require.NotNil(t, user) assert.Equal(t, "testuser", user.Username) } func TestMustGetAuthUser_Nil_Returns401(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) user, err := MustGetAuthUser(c) assert.Nil(t, user) assert.Error(t, err) } func TestMustGetAuthUser_WrongType_Returns401(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) c.Set(AuthUserKey, 12345) user, err := MustGetAuthUser(c) assert.Nil(t, user) assert.Error(t, err) } func TestTokenTruncation_ShortToken_NoPanic(t *testing.T) { // Ensure truncateToken does not panic on short tokens assert.NotPanics(t, func() { result := truncateToken("ab") assert.Equal(t, "ab...", result) }) } func TestTokenTruncation_EmptyToken_NoPanic(t *testing.T) { assert.NotPanics(t, func() { result := truncateToken("") assert.Equal(t, "...", result) }) } func TestTokenTruncation_LongToken_Truncated(t *testing.T) { result := truncateToken("abcdefghijklmnop") assert.Equal(t, "abcdefgh...", result) } func TestAdminAuth_QueryParamToken_Rejected(t *testing.T) { // SEC-20: Admin JWT via query parameter must be rejected. // Tokens in URLs leak into server logs and browser history. cfg := &config.Config{ Security: config.SecurityConfig{SecretKey: "test-secret"}, } mw := AdminAuthMiddleware(cfg, nil) handler := mw(func(c echo.Context) error { return c.String(http.StatusOK, "should not reach here") }) e := echo.New() // Request with token only in query param, no Authorization header req := httptest.NewRequest(http.MethodGet, "/admin/test?token=some-jwt-token", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) err := handler(c) assert.NoError(t, err) // handler writes JSON directly, no Echo error assert.Equal(t, http.StatusUnauthorized, rec.Code, "query param token must be rejected") assert.Contains(t, rec.Body.String(), "Authorization required") }