Files
gohoarder/pkg/app/handlers_auth_test.go
T

286 lines
7.4 KiB
Go

package app
import (
"bytes"
"encoding/json"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/lukaszraczylo/gohoarder/pkg/auth"
"github.com/stretchr/testify/suite"
)
type AuthHandlersTestSuite struct {
suite.Suite
app *fiber.App
appInst *App
authManager *auth.Manager
}
func (s *AuthHandlersTestSuite) SetupTest() {
// Create auth manager
s.authManager = auth.New()
// Create app instance
s.appInst = &App{
authManager: s.authManager,
}
// Create Fiber app
s.app = fiber.New()
// Register routes
s.app.Post("/api/admin/keys", s.appInst.handleGenerateAPIKey)
s.app.Get("/api/admin/keys", s.appInst.handleListAPIKeys)
s.app.Delete("/api/admin/keys/:key_id", s.appInst.handleRevokeAPIKey)
}
func TestAuthHandlersTestSuite(t *testing.T) {
suite.Run(t, new(AuthHandlersTestSuite))
}
func (s *AuthHandlersTestSuite) TestHandleGenerateAPIKey() {
tests := []struct {
requestBody map[string]string
name string
expectedStatus int
expectedRole string
expectKey bool
}{
{
name: "generate read-only key",
requestBody: map[string]string{
"role": "readonly",
"name": "test-readonly-key",
},
expectedStatus: 201,
expectedRole: "readonly",
expectKey: true,
},
{
name: "generate read-write key",
requestBody: map[string]string{
"role": "readwrite",
"name": "test-readwrite-key",
},
expectedStatus: 201,
expectedRole: "readwrite",
expectKey: true,
},
{
name: "generate admin key",
requestBody: map[string]string{
"role": "admin",
"name": "test-admin-key",
},
expectedStatus: 201,
expectedRole: "admin",
expectKey: true,
},
{
name: "invalid role",
requestBody: map[string]string{
"role": "invalid-role",
"name": "test-key",
},
expectedStatus: 400,
expectKey: false,
},
{
name: "missing role defaults to readonly",
requestBody: map[string]string{
"name": "test-key-default-role",
},
expectedStatus: 201,
expectedRole: "readonly", // Role defaults to readonly when not specified
expectKey: true,
},
{
name: "missing name",
requestBody: map[string]string{
"role": "read-only",
},
expectedStatus: 400,
expectKey: false,
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
bodyBytes, err := json.Marshal(tt.requestBody)
s.Require().NoError(err)
req := httptest.NewRequest("POST", "/api/admin/keys", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(tt.expectedStatus, resp.StatusCode)
if tt.expectKey {
var result struct {
Key string `json:"key"`
KeyID string `json:"key_id"`
Role string `json:"role"`
Name string `json:"name"`
Message string `json:"message"`
}
err = json.NewDecoder(resp.Body).Decode(&result)
s.NoError(err)
s.NotEmpty(result.Key)
s.NotEmpty(result.KeyID)
s.Equal(tt.expectedRole, result.Role)
s.Equal(tt.requestBody["name"], result.Name)
}
})
}
}
func (s *AuthHandlersTestSuite) TestHandleListAPIKeys() {
// Generate some test keys first
s.authManager.GenerateAPIKey("test-key-1", auth.RoleReadOnly, nil)
s.authManager.GenerateAPIKey("test-key-2", auth.RoleReadWrite, nil)
s.authManager.GenerateAPIKey("test-key-3", auth.RoleAdmin, nil)
req := httptest.NewRequest("GET", "/api/admin/keys", nil)
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(200, resp.StatusCode)
var result struct {
Keys []map[string]interface{} `json:"keys"`
Total int `json:"total"`
}
err = json.NewDecoder(resp.Body).Decode(&result)
s.NoError(err)
s.GreaterOrEqual(result.Total, 3)
// Verify keys don't include the actual key value
for _, key := range result.Keys {
s.NotEmpty(key["id"])
s.NotEmpty(key["role"])
s.NotEmpty(key["name"])
s.NotEmpty(key["created_at"])
}
}
func (s *AuthHandlersTestSuite) TestHandleRevokeAPIKey() {
// Generate a test key
keyInfo, _, _ := s.authManager.GenerateAPIKey("test-revoke-key", auth.RoleReadOnly, nil)
tests := []struct {
name string
keyID string
expectedStatus int
}{
{
name: "revoke existing key",
keyID: keyInfo.ID,
expectedStatus: 200,
},
{
name: "revoke non-existent key",
keyID: "non-existent-key-id",
expectedStatus: 404,
},
}
for _, tt := range tests {
s.Run(tt.name, func() {
req := httptest.NewRequest("DELETE", "/api/admin/keys/"+tt.keyID, nil)
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(tt.expectedStatus, resp.StatusCode)
if tt.expectedStatus == 200 {
var result map[string]string
err = json.NewDecoder(resp.Body).Decode(&result)
s.NoError(err)
s.Contains(result, "message")
}
})
}
}
func (s *AuthHandlersTestSuite) TestHandleGenerateAPIKeyInvalidJSON() {
req := httptest.NewRequest("POST", "/api/admin/keys", bytes.NewReader([]byte("invalid json")))
req.Header.Set("Content-Type", "application/json")
resp, err := s.app.Test(req, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(400, resp.StatusCode)
}
func (s *AuthHandlersTestSuite) TestGenerateAndRevokeKeyFlow() {
// Generate a key
bodyBytes, _ := json.Marshal(map[string]string{
"role": "readonly",
"name": "integration-test-key",
})
req1 := httptest.NewRequest("POST", "/api/admin/keys", bytes.NewReader(bodyBytes))
req1.Header.Set("Content-Type", "application/json")
resp1, err := s.app.Test(req1, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(201, resp1.StatusCode)
var createResult struct {
Key string `json:"key"`
KeyID string `json:"key_id"`
}
err = json.NewDecoder(resp1.Body).Decode(&createResult)
s.Require().NoError(err)
keyID := createResult.KeyID
// List keys - should include our new key
req2 := httptest.NewRequest("GET", "/api/admin/keys", nil)
resp2, err := s.app.Test(req2, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(200, resp2.StatusCode)
var listResult struct {
Keys []map[string]interface{} `json:"keys"`
Total int `json:"total"`
}
err = json.NewDecoder(resp2.Body).Decode(&listResult)
s.Require().NoError(err)
found := false
for _, key := range listResult.Keys {
if key["id"].(string) == keyID {
found = true
break
}
}
s.True(found, "newly created key should be in the list")
// Revoke the key
req3 := httptest.NewRequest("DELETE", "/api/admin/keys/"+keyID, nil)
resp3, err := s.app.Test(req3, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(200, resp3.StatusCode)
// List keys again - should not include the revoked key
req4 := httptest.NewRequest("GET", "/api/admin/keys", nil)
resp4, err := s.app.Test(req4, 5000) // 5 second timeout for CI environments
s.Require().NoError(err)
s.Equal(200, resp4.StatusCode)
var listResult2 struct {
Keys []map[string]interface{} `json:"keys"`
Total int `json:"total"`
}
err = json.NewDecoder(resp4.Body).Decode(&listResult2)
s.Require().NoError(err)
found = false
for _, key := range listResult2.Keys {
if key["id"].(string) == keyID {
found = true
break
}
}
s.False(found, "revoked key should not be in the list")
}