add server api get and update service control endpoints
This commit is contained in:
@@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/utl/cache"
|
"acc-server-manager/local/utl/cache"
|
||||||
|
"acc-server-manager/local/utl/configs"
|
||||||
"acc-server-manager/local/utl/db"
|
"acc-server-manager/local/utl/db"
|
||||||
|
"acc-server-manager/local/utl/jwt"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
"acc-server-manager/local/utl/server"
|
"acc-server-manager/local/utl/server"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,10 +12,12 @@ import (
|
|||||||
|
|
||||||
"go.uber.org/dig"
|
"go.uber.org/dig"
|
||||||
|
|
||||||
_ "acc-server-manager/docs"
|
_ "acc-server-manager/swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
configs.Init()
|
||||||
|
jwt.Init()
|
||||||
// Initialize new logging system
|
// Initialize new logging system
|
||||||
if err := logging.InitializeLogging(); err != nil {
|
if err := logging.InitializeLogging(); err != nil {
|
||||||
fmt.Printf("Failed to initialize logging system: %v\n", err)
|
fmt.Printf("Failed to initialize logging system: %v\n", err)
|
||||||
|
|||||||
27
cmd/api/swagger.go
Normal file
27
cmd/api/swagger.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Package main ACC Server Manager API
|
||||||
|
//
|
||||||
|
// @title ACC Server Manager API
|
||||||
|
// @version 1.0
|
||||||
|
// @description API for managing Assetto Corsa Competizione dedicated servers
|
||||||
|
//
|
||||||
|
// @contact.name ACC Server Manager Support
|
||||||
|
// @contact.url https://github.com/yourusername/acc-server-manager
|
||||||
|
//
|
||||||
|
// @license.name MIT
|
||||||
|
// @license.url https://opensource.org/licenses/MIT
|
||||||
|
//
|
||||||
|
// @host localhost:3000
|
||||||
|
// @BasePath /api/v1
|
||||||
|
// @schemes http https
|
||||||
|
//
|
||||||
|
// @securityDefinitions.apikey BearerAuth
|
||||||
|
// @in header
|
||||||
|
// @name Authorization
|
||||||
|
// @description Type "Bearer" followed by a space and JWT token.
|
||||||
|
//
|
||||||
|
// @externalDocs.description OpenAPI
|
||||||
|
// @externalDocs.url https://swagger.io/resources/open-api/
|
||||||
|
package main
|
||||||
|
|
||||||
|
// This file exists solely for Swagger documentation generation.
|
||||||
|
// Run: swag init -g cmd/api/swagger.go -o docs/
|
||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/controller"
|
"acc-server-manager/local/controller"
|
||||||
|
"acc-server-manager/local/middleware"
|
||||||
"acc-server-manager/local/utl/common"
|
"acc-server-manager/local/utl/common"
|
||||||
"acc-server-manager/local/utl/configs"
|
"acc-server-manager/local/utl/configs"
|
||||||
"acc-server-manager/local/utl/logging"
|
"acc-server-manager/local/utl/logging"
|
||||||
@@ -29,8 +30,12 @@ func Init(di *dig.Container, app *fiber.App) {
|
|||||||
Lookup: groups.Group("/lookup"),
|
Lookup: groups.Group("/lookup"),
|
||||||
StateHistory: serverIdGroup.Group("/state-history"),
|
StateHistory: serverIdGroup.Group("/state-history"),
|
||||||
Membership: groups.Group("/membership"),
|
Membership: groups.Group("/membership"),
|
||||||
|
System: groups.Group("/system"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessKeyMiddleware := middleware.NewAccessKeyMiddleware()
|
||||||
|
routeGroups.Api.Use(accessKeyMiddleware.Authenticate)
|
||||||
|
|
||||||
err := di.Provide(func() *common.RouteGroups {
|
err := di.Provide(func() *common.RouteGroups {
|
||||||
return routeGroups
|
return routeGroups
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"acc-server-manager/local/middleware"
|
|
||||||
"acc-server-manager/local/service"
|
|
||||||
"acc-server-manager/local/utl/common"
|
|
||||||
"acc-server-manager/local/utl/error_handler"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ApiController struct {
|
|
||||||
service *service.ApiService
|
|
||||||
errorHandler *error_handler.ControllerErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApiController
|
|
||||||
// Initializes ApiController.
|
|
||||||
//
|
|
||||||
// Args:
|
|
||||||
// *services.ApiService: API service
|
|
||||||
// *Fiber.RouterGroup: Fiber Router Group
|
|
||||||
// Returns:
|
|
||||||
// *ApiController: Controller for "api" interactions
|
|
||||||
func NewApiController(as *service.ApiService, routeGroups *common.RouteGroups, auth *middleware.AuthMiddleware) *ApiController {
|
|
||||||
ac := &ApiController{
|
|
||||||
service: as,
|
|
||||||
errorHandler: error_handler.NewControllerErrorHandler(),
|
|
||||||
}
|
|
||||||
|
|
||||||
apiGroup := routeGroups.Api
|
|
||||||
apiGroup.Use(auth.Authenticate)
|
|
||||||
apiGroup.Get("/", ac.getFirst)
|
|
||||||
apiGroup.Get("/:service", ac.getStatus)
|
|
||||||
apiGroup.Post("/start", ac.startServer)
|
|
||||||
apiGroup.Post("/stop", ac.stopServer)
|
|
||||||
apiGroup.Post("/restart", ac.restartServer)
|
|
||||||
|
|
||||||
return ac
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFirst returns API
|
|
||||||
//
|
|
||||||
// @Summary Return API
|
|
||||||
// @Description Return API
|
|
||||||
// @Tags api
|
|
||||||
// @Success 200 {array} string
|
|
||||||
// @Router /v1/api [get]
|
|
||||||
func (ac *ApiController) getFirst(c *fiber.Ctx) error {
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStatus returns service status
|
|
||||||
//
|
|
||||||
// @Summary Return service status
|
|
||||||
// @Description Returns service status
|
|
||||||
// @Param service path string true "required"
|
|
||||||
// @Tags api
|
|
||||||
// @Success 200 {array} string
|
|
||||||
// @Router /v1/api/{service} [get]
|
|
||||||
func (ac *ApiController) getStatus(c *fiber.Ctx) error {
|
|
||||||
service := c.Params("service")
|
|
||||||
if service == "" {
|
|
||||||
serverId := c.Params("service")
|
|
||||||
if _, err := uuid.Parse(serverId); err != nil {
|
|
||||||
return ac.errorHandler.HandleUUIDError(c, "server ID")
|
|
||||||
}
|
|
||||||
c.Locals("serverId", serverId)
|
|
||||||
} else {
|
|
||||||
c.Locals("service", service)
|
|
||||||
}
|
|
||||||
apiModel, err := ac.service.GetStatus(c)
|
|
||||||
if err != nil {
|
|
||||||
return ac.errorHandler.HandleServiceError(c, err)
|
|
||||||
}
|
|
||||||
return c.SendString(string(apiModel))
|
|
||||||
}
|
|
||||||
|
|
||||||
// startServer starts service
|
|
||||||
//
|
|
||||||
// @Summary Start service
|
|
||||||
// @Description Starts service
|
|
||||||
// @Param name body string true "required"
|
|
||||||
// @Tags api
|
|
||||||
// @Success 200 {array} string
|
|
||||||
// @Router /v1/api/start [post]
|
|
||||||
func (ac *ApiController) startServer(c *fiber.Ctx) error {
|
|
||||||
model := new(Service)
|
|
||||||
if err := c.BodyParser(model); err != nil {
|
|
||||||
return ac.errorHandler.HandleParsingError(c, err)
|
|
||||||
}
|
|
||||||
c.Locals("service", model.Name)
|
|
||||||
c.Locals("serverId", model.ServerId)
|
|
||||||
apiModel, err := ac.service.ApiStartServer(c)
|
|
||||||
if err != nil {
|
|
||||||
return ac.errorHandler.HandleServiceError(c, err)
|
|
||||||
}
|
|
||||||
return c.SendString(apiModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopServer stops service
|
|
||||||
//
|
|
||||||
// @Summary Stop service
|
|
||||||
// @Description Stops service
|
|
||||||
// @Param name body string true "required"
|
|
||||||
// @Tags api
|
|
||||||
// @Success 200 {array} string
|
|
||||||
// @Router /v1/api/stop [post]
|
|
||||||
func (ac *ApiController) stopServer(c *fiber.Ctx) error {
|
|
||||||
model := new(Service)
|
|
||||||
if err := c.BodyParser(model); err != nil {
|
|
||||||
return ac.errorHandler.HandleParsingError(c, err)
|
|
||||||
}
|
|
||||||
c.Locals("service", model.Name)
|
|
||||||
c.Locals("serverId", model.ServerId)
|
|
||||||
apiModel, err := ac.service.ApiStopServer(c)
|
|
||||||
if err != nil {
|
|
||||||
return ac.errorHandler.HandleServiceError(c, err)
|
|
||||||
}
|
|
||||||
return c.SendString(apiModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// restartServer returns API
|
|
||||||
//
|
|
||||||
// @Summary Restart service
|
|
||||||
// @Description Restarts service
|
|
||||||
// @Param name body string true "required"
|
|
||||||
// @Tags api
|
|
||||||
// @Success 200 {array} string
|
|
||||||
// @Router /v1/api/restart [post]
|
|
||||||
func (ac *ApiController) restartServer(c *fiber.Ctx) error {
|
|
||||||
model := new(Service)
|
|
||||||
if err := c.BodyParser(model); err != nil {
|
|
||||||
return ac.errorHandler.HandleParsingError(c, err)
|
|
||||||
}
|
|
||||||
c.Locals("service", model.Name)
|
|
||||||
c.Locals("serverId", model.ServerId)
|
|
||||||
apiModel, err := ac.service.ApiRestartServer(c)
|
|
||||||
if err != nil {
|
|
||||||
return ac.errorHandler.HandleServiceError(c, err)
|
|
||||||
}
|
|
||||||
return c.SendString(apiModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
Name string `json:"name" xml:"name" form:"name"`
|
|
||||||
ServerId string `json:"serverId" xml:"serverId" form:"serverId"`
|
|
||||||
}
|
|
||||||
@@ -31,10 +31,28 @@ func NewServerController(ss *service.ServerService, routeGroups *common.RouteGro
|
|||||||
serverRoutes.Post("/", auth.HasPermission(model.ServerCreate), ac.CreateServer)
|
serverRoutes.Post("/", auth.HasPermission(model.ServerCreate), ac.CreateServer)
|
||||||
serverRoutes.Put("/:id", auth.HasPermission(model.ServerUpdate), ac.UpdateServer)
|
serverRoutes.Put("/:id", auth.HasPermission(model.ServerUpdate), ac.UpdateServer)
|
||||||
serverRoutes.Delete("/:id", auth.HasPermission(model.ServerDelete), ac.DeleteServer)
|
serverRoutes.Delete("/:id", auth.HasPermission(model.ServerDelete), ac.DeleteServer)
|
||||||
|
|
||||||
|
apiServerRoutes := routeGroups.Api.Group("/server")
|
||||||
|
apiServerRoutes.Get("/", auth.HasPermission(model.ServerView), ac.GetAllApi)
|
||||||
return ac
|
return ac
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll returns Servers
|
// GetAll returns Servers
|
||||||
|
func (ac *ServerController) GetAllApi(c *fiber.Ctx) error {
|
||||||
|
var filter model.ServerFilter
|
||||||
|
if err := common.ParseQueryFilter(c, &filter); err != nil {
|
||||||
|
return ac.errorHandler.HandleValidationError(c, err, "query_filter")
|
||||||
|
}
|
||||||
|
ServerModel, err := ac.service.GetAll(c, &filter)
|
||||||
|
if err != nil {
|
||||||
|
return ac.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
var apiServers []model.ServerAPI
|
||||||
|
for _, server := range *ServerModel {
|
||||||
|
apiServers = append(apiServers, *server.ToServerAPI())
|
||||||
|
}
|
||||||
|
return c.JSON(apiServers)
|
||||||
|
}
|
||||||
func (ac *ServerController) GetAll(c *fiber.Ctx) error {
|
func (ac *ServerController) GetAll(c *fiber.Ctx) error {
|
||||||
var filter model.ServerFilter
|
var filter model.ServerFilter
|
||||||
if err := common.ParseQueryFilter(c, &filter); err != nil {
|
if err := common.ParseQueryFilter(c, &filter); err != nil {
|
||||||
|
|||||||
148
local/controller/service_control.go
Normal file
148
local/controller/service_control.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/middleware"
|
||||||
|
"acc-server-manager/local/service"
|
||||||
|
"acc-server-manager/local/utl/common"
|
||||||
|
"acc-server-manager/local/utl/error_handler"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceControlController struct {
|
||||||
|
service *service.ServiceControlService
|
||||||
|
errorHandler *error_handler.ControllerErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceControlController
|
||||||
|
// Initializes ServiceControlController.
|
||||||
|
//
|
||||||
|
// Args:
|
||||||
|
// *services.ServiceControlService: Service control service
|
||||||
|
// *Fiber.RouterGroup: Fiber Router Group
|
||||||
|
// Returns:
|
||||||
|
// *ServiceControlController: Controller for service control interactions
|
||||||
|
func NewServiceControlController(as *service.ServiceControlService, routeGroups *common.RouteGroups, auth *middleware.AuthMiddleware) *ServiceControlController {
|
||||||
|
ac := &ServiceControlController{
|
||||||
|
service: as,
|
||||||
|
errorHandler: error_handler.NewControllerErrorHandler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceRoutes := routeGroups.Server.Group("/service")
|
||||||
|
serviceRoutes.Get("/:service", ac.getStatus)
|
||||||
|
serviceRoutes.Post("/start", ac.startServer)
|
||||||
|
serviceRoutes.Post("/stop", ac.stopServer)
|
||||||
|
serviceRoutes.Post("/restart", ac.restartServer)
|
||||||
|
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStatus returns service status
|
||||||
|
//
|
||||||
|
// @Summary Get service status
|
||||||
|
// @Description Get the current status of a Windows service
|
||||||
|
// @Tags Service Control
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param service path string true "Service name"
|
||||||
|
// @Success 200 {object} object{status=string,state=string} "Service status information"
|
||||||
|
// @Failure 400 {object} error_handler.ErrorResponse "Invalid service name"
|
||||||
|
// @Failure 401 {object} error_handler.ErrorResponse "Unauthorized"
|
||||||
|
// @Failure 404 {object} error_handler.ErrorResponse "Service not found"
|
||||||
|
// @Failure 500 {object} error_handler.ErrorResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /v1/service-control/{service} [get]
|
||||||
|
func (ac *ServiceControlController) getStatus(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
c.Locals("serverId", id)
|
||||||
|
apiModel, err := ac.service.GetStatus(c)
|
||||||
|
if err != nil {
|
||||||
|
return ac.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
return c.SendString(string(apiModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// startServer starts service
|
||||||
|
//
|
||||||
|
// @Summary Start a Windows service
|
||||||
|
// @Description Start a stopped Windows service for an ACC server
|
||||||
|
// @Tags Service Control
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param service body object{name=string} true "Service name to start"
|
||||||
|
// @Success 200 {object} object{message=string} "Service started successfully"
|
||||||
|
// @Failure 400 {object} error_handler.ErrorResponse "Invalid request body"
|
||||||
|
// @Failure 401 {object} error_handler.ErrorResponse "Unauthorized"
|
||||||
|
// @Failure 403 {object} error_handler.ErrorResponse "Insufficient permissions"
|
||||||
|
// @Failure 404 {object} error_handler.ErrorResponse "Service not found"
|
||||||
|
// @Failure 409 {object} error_handler.ErrorResponse "Service already running"
|
||||||
|
// @Failure 500 {object} error_handler.ErrorResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /v1/service-control/start [post]
|
||||||
|
func (ac *ServiceControlController) startServer(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
c.Locals("serverId", id)
|
||||||
|
apiModel, err := ac.service.ServiceControlStartServer(c)
|
||||||
|
if err != nil {
|
||||||
|
return ac.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
return c.SendString(apiModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopServer stops service
|
||||||
|
//
|
||||||
|
// @Summary Stop a Windows service
|
||||||
|
// @Description Stop a running Windows service for an ACC server
|
||||||
|
// @Tags Service Control
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param service body object{name=string} true "Service name to stop"
|
||||||
|
// @Success 200 {object} object{message=string} "Service stopped successfully"
|
||||||
|
// @Failure 400 {object} error_handler.ErrorResponse "Invalid request body"
|
||||||
|
// @Failure 401 {object} error_handler.ErrorResponse "Unauthorized"
|
||||||
|
// @Failure 403 {object} error_handler.ErrorResponse "Insufficient permissions"
|
||||||
|
// @Failure 404 {object} error_handler.ErrorResponse "Service not found"
|
||||||
|
// @Failure 409 {object} error_handler.ErrorResponse "Service already stopped"
|
||||||
|
// @Failure 500 {object} error_handler.ErrorResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /v1/service-control/stop [post]
|
||||||
|
func (ac *ServiceControlController) stopServer(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
c.Locals("serverId", id)
|
||||||
|
apiModel, err := ac.service.ServiceControlStopServer(c)
|
||||||
|
if err != nil {
|
||||||
|
return ac.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
return c.SendString(apiModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// restartServer restarts service
|
||||||
|
//
|
||||||
|
// @Summary Restart a Windows service
|
||||||
|
// @Description Stop and start a Windows service for an ACC server
|
||||||
|
// @Tags Service Control
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param service body object{name=string} true "Service name to restart"
|
||||||
|
// @Success 200 {object} object{message=string} "Service restarted successfully"
|
||||||
|
// @Failure 400 {object} error_handler.ErrorResponse "Invalid request body"
|
||||||
|
// @Failure 401 {object} error_handler.ErrorResponse "Unauthorized"
|
||||||
|
// @Failure 403 {object} error_handler.ErrorResponse "Insufficient permissions"
|
||||||
|
// @Failure 404 {object} error_handler.ErrorResponse "Service not found"
|
||||||
|
// @Failure 500 {object} error_handler.ErrorResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /v1/service-control/restart [post]
|
||||||
|
func (ac *ServiceControlController) restartServer(c *fiber.Ctx) error {
|
||||||
|
id := c.Params("id")
|
||||||
|
c.Locals("serverId", id)
|
||||||
|
apiModel, err := ac.service.ServiceControlRestartServer(c)
|
||||||
|
if err != nil {
|
||||||
|
return ac.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
return c.SendString(apiModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Name string `json:"name" xml:"name" form:"name"`
|
||||||
|
ServerId string `json:"serverId" xml:"serverId" form:"serverId"`
|
||||||
|
}
|
||||||
38
local/controller/system.go
Normal file
38
local/controller/system.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/utl/common"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemController struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSystemController
|
||||||
|
// Initializes SystemController.
|
||||||
|
//
|
||||||
|
// Args:
|
||||||
|
// *services.SystemService: Service control service
|
||||||
|
// *Fiber.RouterGroup: Fiber Router Group
|
||||||
|
// Returns:
|
||||||
|
// *SystemController: Controller for service control interactions
|
||||||
|
func NewSystemController(routeGroups *common.RouteGroups) *SystemController {
|
||||||
|
ac := &SystemController{}
|
||||||
|
|
||||||
|
apiGroup := routeGroups.System
|
||||||
|
apiGroup.Get("/health", ac.getFirst)
|
||||||
|
|
||||||
|
return ac
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFirst returns service control status
|
||||||
|
//
|
||||||
|
// @Summary Return service control status
|
||||||
|
// @Description Return service control status
|
||||||
|
// @Tags service-control
|
||||||
|
// @Success 200 {array} string
|
||||||
|
// @Router /v1/service-control [get]
|
||||||
|
func (ac *SystemController) getFirst(c *fiber.Ctx) error {
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
59
local/middleware/access_key.go
Normal file
59
local/middleware/access_key.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/utl/configs"
|
||||||
|
"acc-server-manager/local/utl/logging"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessKeyMiddleware provides authentication and permission middleware.
|
||||||
|
type AccessKeyMiddleware struct {
|
||||||
|
userInfo CachedUserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccessKeyMiddleware creates a new AccessKeyMiddleware.
|
||||||
|
func NewAccessKeyMiddleware() *AccessKeyMiddleware {
|
||||||
|
auth := &AccessKeyMiddleware{
|
||||||
|
userInfo: CachedUserInfo{UserID: uuid.New().String(), Username: "access_key", RoleName: "Admin", Permissions: make(map[string]bool), CachedAt: time.Now()},
|
||||||
|
}
|
||||||
|
return auth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate is a middleware for JWT authentication with enhanced security.
|
||||||
|
func (m *AccessKeyMiddleware) Authenticate(ctx *fiber.Ctx) error {
|
||||||
|
// Log authentication attempt
|
||||||
|
ip := ctx.IP()
|
||||||
|
userAgent := ctx.Get("User-Agent")
|
||||||
|
|
||||||
|
authHeader := ctx.Get("Access-Key")
|
||||||
|
if authHeader == "" {
|
||||||
|
logging.Error("Authentication failed: missing Access-Key header from IP %s", ip)
|
||||||
|
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "Missing or malformed JWT",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authHeader) < 10 || len(authHeader) > 2048 {
|
||||||
|
logging.Error("Authentication failed: invalid token length from IP %s", ip)
|
||||||
|
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "Invalid or expired JWT",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if authHeader != configs.AccessKey {
|
||||||
|
logging.Error("Authentication failed: invalid token from IP %s, User-Agent: %s", ip, userAgent)
|
||||||
|
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
"error": "Invalid or expired JWT",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Locals("userID", m.userInfo.UserID)
|
||||||
|
ctx.Locals("userInfo", m.userInfo)
|
||||||
|
ctx.Locals("authTime", time.Now())
|
||||||
|
|
||||||
|
logging.InfoWithContext("AUTH", "User %s authenticated successfully from IP %s", m.userInfo.UserID, ip)
|
||||||
|
return ctx.Next()
|
||||||
|
}
|
||||||
@@ -35,9 +35,9 @@ type ConfigFilter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApiFilter defines filtering options for Api queries
|
// ApiFilter defines filtering options for Api queries
|
||||||
type ApiFilter struct {
|
type ServiceControlFilter struct {
|
||||||
BaseFilter
|
BaseFilter
|
||||||
Api string `query:"api"`
|
ServiceControl string `query:"serviceControl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MembershipFilter defines filtering options for User queries
|
// MembershipFilter defines filtering options for User queries
|
||||||
|
|||||||
@@ -16,6 +16,25 @@ const (
|
|||||||
ServiceNamePrefix = "ACC-Server"
|
ServiceNamePrefix = "ACC-Server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Server represents an ACC server instance
|
||||||
|
type ServerAPI struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status ServiceStatus `json:"status"`
|
||||||
|
State *ServerState `json:"state"`
|
||||||
|
PlayerCount int `json:"playerCount"`
|
||||||
|
Track string `json:"track"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ToServerAPI() *ServerAPI {
|
||||||
|
return &ServerAPI{
|
||||||
|
Name: s.Name,
|
||||||
|
Status: s.Status,
|
||||||
|
State: s.State,
|
||||||
|
PlayerCount: s.State.PlayerCount,
|
||||||
|
Track: s.State.Track,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Server represents an ACC server instance
|
// Server represents an ACC server instance
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||||
|
|||||||
@@ -104,6 +104,6 @@ func (s ServiceStatus) Value() (driver.Value, error) {
|
|||||||
return s.String(), nil
|
return s.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiModel struct {
|
type ServiceControlModel struct {
|
||||||
Api string `json:"api"`
|
ServiceControl string `json:"serviceControl"`
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"acc-server-manager/local/model"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ApiRepository struct {
|
|
||||||
*BaseRepository[model.ApiModel, model.ApiFilter]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewApiRepository(db *gorm.DB) *ApiRepository {
|
|
||||||
return &ApiRepository{
|
|
||||||
BaseRepository: NewBaseRepository[model.ApiModel, model.ApiFilter](db, model.ApiModel{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
// Args:
|
// Args:
|
||||||
// *dig.Container: Dig Container
|
// *dig.Container: Dig Container
|
||||||
func InitializeRepositories(c *dig.Container) {
|
func InitializeRepositories(c *dig.Container) {
|
||||||
c.Provide(NewApiRepository)
|
c.Provide(NewServiceControlRepository)
|
||||||
c.Provide(NewStateHistoryRepository)
|
c.Provide(NewStateHistoryRepository)
|
||||||
c.Provide(NewServerRepository)
|
c.Provide(NewServerRepository)
|
||||||
c.Provide(NewConfigRepository)
|
c.Provide(NewConfigRepository)
|
||||||
|
|||||||
17
local/repository/service_control.go
Normal file
17
local/repository/service_control.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceControlRepository struct {
|
||||||
|
*BaseRepository[model.ServiceControlModel, model.ServiceControlFilter]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceControlRepository(db *gorm.DB) *ServiceControlRepository {
|
||||||
|
return &ServiceControlRepository{
|
||||||
|
BaseRepository: NewBaseRepository[model.ServiceControlModel, model.ServiceControlFilter](db, model.ServiceControlModel{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ const (
|
|||||||
type ServerService struct {
|
type ServerService struct {
|
||||||
repository *repository.ServerRepository
|
repository *repository.ServerRepository
|
||||||
stateHistoryRepo *repository.StateHistoryRepository
|
stateHistoryRepo *repository.StateHistoryRepository
|
||||||
apiService *ApiService
|
apiService *ServiceControlService
|
||||||
configService *ConfigService
|
configService *ConfigService
|
||||||
steamService *SteamService
|
steamService *SteamService
|
||||||
windowsService *WindowsService
|
windowsService *WindowsService
|
||||||
@@ -63,7 +63,7 @@ func (s *ServerService) ensureLogTailing(server *model.Server, instance *trackin
|
|||||||
func NewServerService(
|
func NewServerService(
|
||||||
repository *repository.ServerRepository,
|
repository *repository.ServerRepository,
|
||||||
stateHistoryRepo *repository.StateHistoryRepository,
|
stateHistoryRepo *repository.StateHistoryRepository,
|
||||||
apiService *ApiService,
|
apiService *ServiceControlService,
|
||||||
configService *ConfigService,
|
configService *ConfigService,
|
||||||
steamService *SteamService,
|
steamService *SteamService,
|
||||||
windowsService *WindowsService,
|
windowsService *WindowsService,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func InitializeServices(c *dig.Container) {
|
|||||||
// Provide services
|
// Provide services
|
||||||
c.Provide(NewServerService)
|
c.Provide(NewServerService)
|
||||||
c.Provide(NewStateHistoryService)
|
c.Provide(NewStateHistoryService)
|
||||||
c.Provide(NewApiService)
|
c.Provide(NewServiceControlService)
|
||||||
c.Provide(NewConfigService)
|
c.Provide(NewConfigService)
|
||||||
c.Provide(NewLookupService)
|
c.Provide(NewLookupService)
|
||||||
c.Provide(NewSteamService)
|
c.Provide(NewSteamService)
|
||||||
@@ -29,7 +29,7 @@ func InitializeServices(c *dig.Container) {
|
|||||||
c.Provide(NewMembershipService)
|
c.Provide(NewMembershipService)
|
||||||
|
|
||||||
logging.Debug("Initializing service dependencies")
|
logging.Debug("Initializing service dependencies")
|
||||||
err := c.Invoke(func(server *ServerService, api *ApiService, config *ConfigService) {
|
err := c.Invoke(func(server *ServerService, api *ServiceControlService, config *ConfigService) {
|
||||||
logging.Debug("Setting up service cross-references")
|
logging.Debug("Setting up service cross-references")
|
||||||
api.SetServerService(server)
|
api.SetServerService(server)
|
||||||
config.SetServerService(server)
|
config.SetServerService(server)
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApiService struct {
|
type ServiceControlService struct {
|
||||||
repository *repository.ApiRepository
|
repository *repository.ServiceControlRepository
|
||||||
serverRepository *repository.ServerRepository
|
serverRepository *repository.ServerRepository
|
||||||
serverService *ServerService
|
serverService *ServerService
|
||||||
statusCache *model.ServerStatusCache
|
statusCache *model.ServerStatusCache
|
||||||
windowsService *WindowsService
|
windowsService *WindowsService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApiService(repository *repository.ApiRepository,
|
func NewServiceControlService(repository *repository.ServiceControlRepository,
|
||||||
serverRepository *repository.ServerRepository) *ApiService {
|
serverRepository *repository.ServerRepository) *ServiceControlService {
|
||||||
return &ApiService{
|
return &ServiceControlService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
serverRepository: serverRepository,
|
serverRepository: serverRepository,
|
||||||
statusCache: model.NewServerStatusCache(model.CacheConfig{
|
statusCache: model.NewServerStatusCache(model.CacheConfig{
|
||||||
@@ -32,11 +32,11 @@ func NewApiService(repository *repository.ApiRepository,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) SetServerService(serverService *ServerService) {
|
func (as *ServiceControlService) SetServerService(serverService *ServerService) {
|
||||||
as.serverService = serverService
|
as.serverService = serverService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) GetStatus(ctx *fiber.Ctx) (string, error) {
|
func (as *ServiceControlService) GetStatus(ctx *fiber.Ctx) (string, error) {
|
||||||
serviceName, err := as.GetServiceName(ctx)
|
serviceName, err := as.GetServiceName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -59,7 +59,7 @@ func (as *ApiService) GetStatus(ctx *fiber.Ctx) (string, error) {
|
|||||||
return status.String(), nil
|
return status.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) ApiStartServer(ctx *fiber.Ctx) (string, error) {
|
func (as *ServiceControlService) ServiceControlStartServer(ctx *fiber.Ctx) (string, error) {
|
||||||
serviceName, err := as.GetServiceName(ctx)
|
serviceName, err := as.GetServiceName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -83,7 +83,7 @@ func (as *ApiService) ApiStartServer(ctx *fiber.Ctx) (string, error) {
|
|||||||
return status.String(), nil
|
return status.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) ApiStopServer(ctx *fiber.Ctx) (string, error) {
|
func (as *ServiceControlService) ServiceControlStopServer(ctx *fiber.Ctx) (string, error) {
|
||||||
serviceName, err := as.GetServiceName(ctx)
|
serviceName, err := as.GetServiceName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -107,7 +107,7 @@ func (as *ApiService) ApiStopServer(ctx *fiber.Ctx) (string, error) {
|
|||||||
return status.String(), nil
|
return status.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
|
func (as *ServiceControlService) ServiceControlRestartServer(ctx *fiber.Ctx) (string, error) {
|
||||||
serviceName, err := as.GetServiceName(ctx)
|
serviceName, err := as.GetServiceName(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -131,12 +131,12 @@ func (as *ApiService) ApiRestartServer(ctx *fiber.Ctx) (string, error) {
|
|||||||
return status.String(), nil
|
return status.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) StatusServer(serviceName string) (string, error) {
|
func (as *ServiceControlService) StatusServer(serviceName string) (string, error) {
|
||||||
return as.windowsService.Status(context.Background(), serviceName)
|
return as.windowsService.Status(context.Background(), serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCachedStatus gets the cached status for a service name without requiring fiber context
|
// GetCachedStatus gets the cached status for a service name without requiring fiber context
|
||||||
func (as *ApiService) GetCachedStatus(serviceName string) (string, error) {
|
func (as *ServiceControlService) GetCachedStatus(serviceName string) (string, error) {
|
||||||
// Try to get status from cache
|
// Try to get status from cache
|
||||||
if status, shouldCheck := as.statusCache.GetStatus(serviceName); !shouldCheck {
|
if status, shouldCheck := as.statusCache.GetStatus(serviceName); !shouldCheck {
|
||||||
return status.String(), nil
|
return status.String(), nil
|
||||||
@@ -154,7 +154,7 @@ func (as *ApiService) GetCachedStatus(serviceName string) (string, error) {
|
|||||||
return status.String(), nil
|
return status.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) StartServer(serviceName string) (string, error) {
|
func (as *ServiceControlService) StartServer(serviceName string) (string, error) {
|
||||||
status, err := as.windowsService.Start(context.Background(), serviceName)
|
status, err := as.windowsService.Start(context.Background(), serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -168,7 +168,7 @@ func (as *ApiService) StartServer(serviceName string) (string, error) {
|
|||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) StopServer(serviceName string) (string, error) {
|
func (as *ServiceControlService) StopServer(serviceName string) (string, error) {
|
||||||
status, err := as.windowsService.Stop(context.Background(), serviceName)
|
status, err := as.windowsService.Stop(context.Background(), serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -183,7 +183,7 @@ func (as *ApiService) StopServer(serviceName string) (string, error) {
|
|||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) RestartServer(serviceName string) (string, error) {
|
func (as *ServiceControlService) RestartServer(serviceName string) (string, error) {
|
||||||
status, err := as.windowsService.Restart(context.Background(), serviceName)
|
status, err := as.windowsService.Restart(context.Background(), serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -197,7 +197,7 @@ func (as *ApiService) RestartServer(serviceName string) (string, error) {
|
|||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (as *ApiService) GetServiceName(ctx *fiber.Ctx) (string, error) {
|
func (as *ServiceControlService) GetServiceName(ctx *fiber.Ctx) (string, error) {
|
||||||
var server *model.Server
|
var server *model.Server
|
||||||
var err error
|
var err error
|
||||||
serviceName, ok := ctx.Locals("service").(string)
|
serviceName, ok := ctx.Locals("service").(string)
|
||||||
@@ -24,6 +24,7 @@ type RouteGroups struct {
|
|||||||
Lookup fiber.Router
|
Lookup fiber.Router
|
||||||
StateHistory fiber.Router
|
StateHistory fiber.Router
|
||||||
Membership fiber.Router
|
Membership fiber.Router
|
||||||
|
System fiber.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckError(err error) {
|
func CheckError(err error) {
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ var (
|
|||||||
Secret string
|
Secret string
|
||||||
SecretCode string
|
SecretCode string
|
||||||
EncryptionKey string
|
EncryptionKey string
|
||||||
|
AccessKey string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func Init() {
|
||||||
godotenv.Load()
|
godotenv.Load()
|
||||||
// Fail fast if critical environment variables are missing
|
// Fail fast if critical environment variables are missing
|
||||||
Secret = getEnvRequired("APP_SECRET")
|
Secret = getEnvRequired("APP_SECRET")
|
||||||
SecretCode = getEnvRequired("APP_SECRET_CODE")
|
SecretCode = getEnvRequired("APP_SECRET_CODE")
|
||||||
EncryptionKey = getEnvRequired("ENCRYPTION_KEY")
|
EncryptionKey = getEnvRequired("ENCRYPTION_KEY")
|
||||||
|
AccessKey = getEnvRequired("ACCESS_KEY")
|
||||||
|
|
||||||
if len(EncryptionKey) != 32 {
|
if len(EncryptionKey) != 32 {
|
||||||
log.Fatal("ENCRYPTION_KEY must be exactly 32 bytes long for AES-256")
|
log.Fatal("ENCRYPTION_KEY must be exactly 32 bytes long for AES-256")
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func Migrate(db *gorm.DB) {
|
|||||||
|
|
||||||
// Run GORM AutoMigrate for all models
|
// Run GORM AutoMigrate for all models
|
||||||
err := db.AutoMigrate(
|
err := db.AutoMigrate(
|
||||||
&model.ApiModel{},
|
&model.ServiceControlModel{},
|
||||||
&model.Config{},
|
&model.Config{},
|
||||||
&model.Track{},
|
&model.Track{},
|
||||||
&model.CarModel{},
|
&model.CarModel{},
|
||||||
@@ -55,7 +55,7 @@ func Migrate(db *gorm.DB) {
|
|||||||
// Don't panic, just log the error as custom migrations may have handled this
|
// Don't panic, just log the error as custom migrations may have handled this
|
||||||
}
|
}
|
||||||
|
|
||||||
db.FirstOrCreate(&model.ApiModel{Api: "Works"})
|
db.FirstOrCreate(&model.ServiceControlModel{ServiceControl: "Works"})
|
||||||
|
|
||||||
Seed(db)
|
Seed(db)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type Claims struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init initializes the JWT secret key from environment variable
|
// init initializes the JWT secret key from environment variable
|
||||||
func init() {
|
func Init() {
|
||||||
jwtSecret := os.Getenv("JWT_SECRET")
|
jwtSecret := os.Getenv("JWT_SECRET")
|
||||||
if jwtSecret == "" {
|
if jwtSecret == "" {
|
||||||
log.Fatal("JWT_SECRET environment variable is required and cannot be empty")
|
log.Fatal("JWT_SECRET environment variable is required and cannot be empty")
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package tests
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"acc-server-manager/local/model"
|
"acc-server-manager/local/model"
|
||||||
|
"acc-server-manager/local/utl/configs"
|
||||||
|
"acc-server-manager/local/utl/jwt"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -45,8 +47,12 @@ func SetTestEnv() {
|
|||||||
os.Setenv("APP_SECRET_CODE", "test-code-for-testing-123456789012")
|
os.Setenv("APP_SECRET_CODE", "test-code-for-testing-123456789012")
|
||||||
os.Setenv("ENCRYPTION_KEY", "12345678901234567890123456789012")
|
os.Setenv("ENCRYPTION_KEY", "12345678901234567890123456789012")
|
||||||
os.Setenv("JWT_SECRET", "test-jwt-secret-key-for-testing-123456789012345678901234567890")
|
os.Setenv("JWT_SECRET", "test-jwt-secret-key-for-testing-123456789012345678901234567890")
|
||||||
|
os.Setenv("ACCESS_KEY", "test-access-key-for-testing")
|
||||||
// Set test-specific environment variables
|
// Set test-specific environment variables
|
||||||
os.Setenv("TESTING_ENV", "true") // Used to bypass
|
os.Setenv("TESTING_ENV", "true") // Used to bypass
|
||||||
|
|
||||||
|
configs.Init()
|
||||||
|
jwt.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTestHelper creates a new test helper with in-memory database
|
// NewTestHelper creates a new test helper with in-memory database
|
||||||
|
|||||||
@@ -1,547 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"acc-server-manager/local/controller"
|
|
||||||
"acc-server-manager/local/model"
|
|
||||||
"acc-server-manager/local/service"
|
|
||||||
"acc-server-manager/local/utl/common"
|
|
||||||
"acc-server-manager/tests"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigController_GetConfig_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Setup expected response
|
|
||||||
expectedConfig := &model.Configuration{
|
|
||||||
UdpPort: model.IntString(9231),
|
|
||||||
TcpPort: model.IntString(9232),
|
|
||||||
MaxConnections: model.IntString(30),
|
|
||||||
LanDiscovery: model.IntString(1),
|
|
||||||
RegisterToLobby: model.IntString(1),
|
|
||||||
ConfigVersion: model.IntString(1),
|
|
||||||
}
|
|
||||||
mockConfigService.getConfigResponse = expectedConfig
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
serverID := uuid.New().String()
|
|
||||||
req := httptest.NewRequest("GET", "/config/configuration.json", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response model.Configuration
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, expectedConfig.UdpPort, response.UdpPort)
|
|
||||||
tests.AssertEqual(t, expectedConfig.TcpPort, response.TcpPort)
|
|
||||||
tests.AssertEqual(t, expectedConfig.MaxConnections, response.MaxConnections)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_GetConfig_Unauthorized(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/config/configuration.json", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication failure
|
|
||||||
mockAuth.authenticated = false
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_GetConfig_ServiceError(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Setup service error
|
|
||||||
mockConfigService.shouldFailGet = true
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/config/configuration.json", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 500, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_UpdateConfig_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Setup expected response
|
|
||||||
expectedConfig := &model.Config{
|
|
||||||
ID: uuid.New(),
|
|
||||||
ServerID: uuid.New(),
|
|
||||||
ConfigFile: "configuration.json",
|
|
||||||
OldConfig: `{"udpPort": "9231"}`,
|
|
||||||
NewConfig: `{"udpPort": "9999"}`,
|
|
||||||
}
|
|
||||||
mockConfigService.updateConfigResponse = expectedConfig
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config/:id"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
updateData := map[string]interface{}{
|
|
||||||
"udpPort": "9999",
|
|
||||||
"tcpPort": "10000",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(updateData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
serverID := uuid.New().String()
|
|
||||||
req := httptest.NewRequest("PUT", "/config/"+serverID+"/configuration.json", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response model.Config
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, expectedConfig.ConfigFile, response.ConfigFile)
|
|
||||||
tests.AssertEqual(t, expectedConfig.OldConfig, response.OldConfig)
|
|
||||||
tests.AssertEqual(t, expectedConfig.NewConfig, response.NewConfig)
|
|
||||||
|
|
||||||
// Verify service was called with correct data
|
|
||||||
tests.AssertEqual(t, true, mockConfigService.updateConfigCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_UpdateConfig_WithRestart(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Setup expected response
|
|
||||||
expectedConfig := &model.Config{
|
|
||||||
ID: uuid.New(),
|
|
||||||
ServerID: uuid.New(),
|
|
||||||
ConfigFile: "configuration.json",
|
|
||||||
OldConfig: `{"udpPort": "9231"}`,
|
|
||||||
NewConfig: `{"udpPort": "9999"}`,
|
|
||||||
}
|
|
||||||
mockConfigService.updateConfigResponse = expectedConfig
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config/:id"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
updateData := map[string]interface{}{
|
|
||||||
"udpPort": "9999",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(updateData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request with restart parameter
|
|
||||||
serverID := uuid.New().String()
|
|
||||||
req := httptest.NewRequest("PUT", "/config/"+serverID+"/configuration.json?restart=true", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify both services were called
|
|
||||||
tests.AssertEqual(t, true, mockConfigService.updateConfigCalled)
|
|
||||||
tests.AssertEqual(t, true, mockApiService.restartServerCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_UpdateConfig_InvalidUUID(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config/:id"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
updateData := map[string]interface{}{
|
|
||||||
"udpPort": "9999",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(updateData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request with invalid UUID
|
|
||||||
req := httptest.NewRequest("PUT", "/config/invalid-uuid/configuration.json", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 400, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was not called
|
|
||||||
tests.AssertEqual(t, false, mockConfigService.updateConfigCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_UpdateConfig_InvalidJSON(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config/:id"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Create test request with invalid JSON
|
|
||||||
serverID := uuid.New().String()
|
|
||||||
req := httptest.NewRequest("PUT", "/config/"+serverID+"/configuration.json", bytes.NewReader([]byte("invalid json")))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 400, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was not called
|
|
||||||
tests.AssertEqual(t, false, mockConfigService.updateConfigCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_UpdateConfig_ServiceError(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Setup service error
|
|
||||||
mockConfigService.shouldFailUpdate = true
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config/:id"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
updateData := map[string]interface{}{
|
|
||||||
"udpPort": "9999",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(updateData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
serverID := uuid.New().String()
|
|
||||||
req := httptest.NewRequest("PUT", "/config/"+serverID+"/configuration.json", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 500, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was called
|
|
||||||
tests.AssertEqual(t, true, mockConfigService.updateConfigCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigController_GetConfigs_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock services
|
|
||||||
mockConfigService := &MockConfigService{}
|
|
||||||
mockApiService := &MockApiService{}
|
|
||||||
|
|
||||||
// Setup expected response
|
|
||||||
expectedConfigs := &model.Configurations{
|
|
||||||
Configuration: model.Configuration{
|
|
||||||
UdpPort: model.IntString(9231),
|
|
||||||
TcpPort: model.IntString(9232),
|
|
||||||
},
|
|
||||||
Settings: model.ServerSettings{
|
|
||||||
ServerName: "Test Server",
|
|
||||||
},
|
|
||||||
Event: model.EventConfig{
|
|
||||||
Track: "spa",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
mockConfigService.getConfigsResponse = expectedConfigs
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Config: app.Group("/config"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewConfigController(mockConfigService, routeGroups, mockApiService, mockAuth)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/config/", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Mock authentication
|
|
||||||
mockAuth.authenticated = true
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response model.Configurations
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, expectedConfigs.Configuration.UdpPort, response.Configuration.UdpPort)
|
|
||||||
tests.AssertEqual(t, expectedConfigs.Settings.ServerName, response.Settings.ServerName)
|
|
||||||
tests.AssertEqual(t, expectedConfigs.Event.Track, response.Event.Track)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockConfigService implements the ConfigService interface for testing
|
|
||||||
type MockConfigService struct {
|
|
||||||
getConfigResponse interface{}
|
|
||||||
getConfigsResponse *model.Configurations
|
|
||||||
updateConfigResponse *model.Config
|
|
||||||
shouldFailGet bool
|
|
||||||
shouldFailUpdate bool
|
|
||||||
getConfigCalled bool
|
|
||||||
getConfigsCalled bool
|
|
||||||
updateConfigCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) GetConfig(c *fiber.Ctx) (interface{}, error) {
|
|
||||||
m.getConfigCalled = true
|
|
||||||
if m.shouldFailGet {
|
|
||||||
return nil, tests.ErrorForTesting("service error")
|
|
||||||
}
|
|
||||||
return m.getConfigResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) GetConfigs(c *fiber.Ctx) (*model.Configurations, error) {
|
|
||||||
m.getConfigsCalled = true
|
|
||||||
if m.shouldFailGet {
|
|
||||||
return nil, tests.ErrorForTesting("service error")
|
|
||||||
}
|
|
||||||
return m.getConfigsResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) UpdateConfig(c *fiber.Ctx, body *map[string]interface{}) (*model.Config, error) {
|
|
||||||
m.updateConfigCalled = true
|
|
||||||
if m.shouldFailUpdate {
|
|
||||||
return nil, tests.ErrorForTesting("service error")
|
|
||||||
}
|
|
||||||
return m.updateConfigResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Additional methods that might be needed by the service interface
|
|
||||||
func (m *MockConfigService) LoadConfigs(server *model.Server) (*model.Configurations, error) {
|
|
||||||
return m.getConfigsResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) GetConfiguration(server *model.Server) (*model.Configuration, error) {
|
|
||||||
if config, ok := m.getConfigResponse.(*model.Configuration); ok {
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
return nil, tests.ErrorForTesting("type assertion failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) GetEventConfig(server *model.Server) (*model.EventConfig, error) {
|
|
||||||
if config, ok := m.getConfigResponse.(*model.EventConfig); ok {
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
return nil, tests.ErrorForTesting("type assertion failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) SaveConfiguration(server *model.Server, config *model.Configuration) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockConfigService) SetServerService(serverService *service.ServerService) {
|
|
||||||
// Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockApiService implements the ApiService interface for testing
|
|
||||||
type MockApiService struct {
|
|
||||||
restartServerCalled bool
|
|
||||||
shouldFailRestart bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockApiService) ApiRestartServer(c *fiber.Ctx) (interface{}, error) {
|
|
||||||
m.restartServerCalled = true
|
|
||||||
if m.shouldFailRestart {
|
|
||||||
return nil, tests.ErrorForTesting("restart failed")
|
|
||||||
}
|
|
||||||
return fiber.Map{"message": "server restarted"}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockAuthMiddleware implements the AuthMiddleware interface for testing
|
|
||||||
type MockAuthMiddleware struct {
|
|
||||||
authenticated bool
|
|
||||||
hasPermission bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAuthMiddleware) Authenticate(c *fiber.Ctx) error {
|
|
||||||
if !m.authenticated {
|
|
||||||
return c.Status(401).JSON(fiber.Map{"error": "Unauthorized"})
|
|
||||||
}
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAuthMiddleware) HasPermission(permission string) fiber.Handler {
|
|
||||||
return func(c *fiber.Ctx) error {
|
|
||||||
if !m.authenticated {
|
|
||||||
return c.Status(401).JSON(fiber.Map{"error": "Unauthorized"})
|
|
||||||
}
|
|
||||||
if !m.hasPermission {
|
|
||||||
return c.Status(403).JSON(fiber.Map{"error": "Forbidden"})
|
|
||||||
}
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAuthMiddleware) AuthRateLimit() fiber.Handler {
|
|
||||||
return func(c *fiber.Ctx) error {
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAuthMiddleware) RequireHTTPS() fiber.Handler {
|
|
||||||
return func(c *fiber.Ctx) error {
|
|
||||||
return c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAuthMiddleware) InvalidateUserPermissions(userID string) {
|
|
||||||
// Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAuthMiddleware) InvalidateAllUserPermissions() {
|
|
||||||
// Mock implementation
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestController_JSONParsing_Success(t *testing.T) {
|
func TestController_JSONParsing_Success(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test basic JSON parsing functionality
|
// Test basic JSON parsing functionality
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -55,6 +60,11 @@ func TestController_JSONParsing_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_JSONParsing_InvalidJSON(t *testing.T) {
|
func TestController_JSONParsing_InvalidJSON(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test handling of invalid JSON
|
// Test handling of invalid JSON
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -87,6 +97,11 @@ func TestController_JSONParsing_InvalidJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_UUIDValidation_Success(t *testing.T) {
|
func TestController_UUIDValidation_Success(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test UUID parameter validation
|
// Test UUID parameter validation
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -123,6 +138,11 @@ func TestController_UUIDValidation_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_UUIDValidation_InvalidUUID(t *testing.T) {
|
func TestController_UUIDValidation_InvalidUUID(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test handling of invalid UUID
|
// Test handling of invalid UUID
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -157,6 +177,11 @@ func TestController_UUIDValidation_InvalidUUID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_QueryParameters_Success(t *testing.T) {
|
func TestController_QueryParameters_Success(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test query parameter handling
|
// Test query parameter handling
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -194,6 +219,11 @@ func TestController_QueryParameters_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_HTTPMethods_Success(t *testing.T) {
|
func TestController_HTTPMethods_Success(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test different HTTP methods
|
// Test different HTTP methods
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -249,6 +279,11 @@ func TestController_HTTPMethods_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_ErrorHandling_StatusCodes(t *testing.T) {
|
func TestController_ErrorHandling_StatusCodes(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test different error status codes
|
// Test different error status codes
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -293,6 +328,11 @@ func TestController_ErrorHandling_StatusCodes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_ConfigurationModel_JSONSerialization(t *testing.T) {
|
func TestController_ConfigurationModel_JSONSerialization(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test Configuration model JSON serialization
|
// Test Configuration model JSON serialization
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -333,6 +373,11 @@ func TestController_ConfigurationModel_JSONSerialization(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_UserModel_JSONSerialization(t *testing.T) {
|
func TestController_UserModel_JSONSerialization(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test User model JSON serialization (password should be hidden)
|
// Test User model JSON serialization (password should be hidden)
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
@@ -370,6 +415,11 @@ func TestController_UserModel_JSONSerialization(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestController_MiddlewareChaining_Success(t *testing.T) {
|
func TestController_MiddlewareChaining_Success(t *testing.T) {
|
||||||
|
// Setup environment and test helper
|
||||||
|
tests.SetTestEnv()
|
||||||
|
helper := tests.NewTestHelper(t)
|
||||||
|
defer helper.Cleanup()
|
||||||
|
|
||||||
// Test middleware chaining
|
// Test middleware chaining
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
||||||
|
|||||||
@@ -1,598 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"acc-server-manager/local/controller"
|
|
||||||
"acc-server-manager/local/model"
|
|
||||||
"acc-server-manager/local/service"
|
|
||||||
"acc-server-manager/local/utl/common"
|
|
||||||
"acc-server-manager/tests"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMembershipController_Login_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
loginResponse: "mock-jwt-token-12345",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
loginData := map[string]string{
|
|
||||||
"username": "testuser",
|
|
||||||
"password": "password123",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(loginData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("POST", "/auth/login", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response map[string]string
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, "mock-jwt-token-12345", response["token"])
|
|
||||||
tests.AssertEqual(t, true, mockMembershipService.loginCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_Login_InvalidCredentials(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service with login failure
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
shouldFailLogin: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
loginData := map[string]string{
|
|
||||||
"username": "baduser",
|
|
||||||
"password": "wrongpassword",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(loginData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("POST", "/auth/login", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was called
|
|
||||||
tests.AssertEqual(t, true, mockMembershipService.loginCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_Login_InvalidJSON(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Create test request with invalid JSON
|
|
||||||
req := httptest.NewRequest("POST", "/auth/login", bytes.NewReader([]byte("invalid json")))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 400, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was not called
|
|
||||||
tests.AssertEqual(t, false, mockMembershipService.loginCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_CreateUser_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create expected user response
|
|
||||||
expectedUser := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "newuser",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
createUserResponse: expectedUser,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{authenticated: true}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
createUserData := map[string]string{
|
|
||||||
"username": "newuser",
|
|
||||||
"password": "password123",
|
|
||||||
"role": "User",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(createUserData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("POST", "/membership/", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response model.User
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, expectedUser.ID, response.ID)
|
|
||||||
tests.AssertEqual(t, expectedUser.Username, response.Username)
|
|
||||||
tests.AssertEqual(t, true, mockMembershipService.createUserCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_CreateUser_Unauthorized(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{authenticated: false}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
createUserData := map[string]string{
|
|
||||||
"username": "newuser",
|
|
||||||
"password": "password123",
|
|
||||||
"role": "User",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(createUserData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("POST", "/membership/", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was not called
|
|
||||||
tests.AssertEqual(t, false, mockMembershipService.createUserCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_CreateUser_Forbidden(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{
|
|
||||||
authenticated: true,
|
|
||||||
hasPermission: false, // User doesn't have MembershipCreate permission
|
|
||||||
}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Prepare request body
|
|
||||||
createUserData := map[string]string{
|
|
||||||
"username": "newuser",
|
|
||||||
"password": "password123",
|
|
||||||
"role": "User",
|
|
||||||
}
|
|
||||||
bodyBytes, err := json.Marshal(createUserData)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("POST", "/membership/", bytes.NewReader(bodyBytes))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 403, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was not called
|
|
||||||
tests.AssertEqual(t, false, mockMembershipService.createUserCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_ListUsers_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create expected users response
|
|
||||||
expectedUsers := []*model.User{
|
|
||||||
{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "user1",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "user2",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
listUsersResponse: expectedUsers,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{
|
|
||||||
authenticated: true,
|
|
||||||
hasPermission: true,
|
|
||||||
}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/membership/", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response []*model.User
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, 2, len(response))
|
|
||||||
tests.AssertEqual(t, expectedUsers[0].Username, response[0].Username)
|
|
||||||
tests.AssertEqual(t, expectedUsers[1].Username, response[1].Username)
|
|
||||||
tests.AssertEqual(t, true, mockMembershipService.listUsersCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_GetUser_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create expected user response
|
|
||||||
userID := uuid.New()
|
|
||||||
expectedUser := &model.User{
|
|
||||||
ID: userID,
|
|
||||||
Username: "testuser",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
getUserResponse: expectedUser,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{
|
|
||||||
authenticated: true,
|
|
||||||
hasPermission: true,
|
|
||||||
}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/membership/"+userID.String(), nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response model.User
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, expectedUser.ID, response.ID)
|
|
||||||
tests.AssertEqual(t, expectedUser.Username, response.Username)
|
|
||||||
tests.AssertEqual(t, true, mockMembershipService.getUserCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_GetUser_InvalidUUID(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{
|
|
||||||
authenticated: true,
|
|
||||||
hasPermission: true,
|
|
||||||
}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Create test request with invalid UUID
|
|
||||||
req := httptest.NewRequest("GET", "/membership/invalid-uuid", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 400, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was not called
|
|
||||||
tests.AssertEqual(t, false, mockMembershipService.getUserCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_DeleteUser_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{
|
|
||||||
authenticated: true,
|
|
||||||
hasPermission: true,
|
|
||||||
}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
userID := uuid.New().String()
|
|
||||||
req := httptest.NewRequest("DELETE", "/membership/"+userID, nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Verify service was called
|
|
||||||
tests.AssertEqual(t, true, mockMembershipService.deleteUserCalled)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMembershipController_GetMe_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create expected user response
|
|
||||||
expectedUser := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "currentuser",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
getUserWithPermissionsResponse: expectedUser,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Fiber app with controller
|
|
||||||
app := fiber.New()
|
|
||||||
routeGroups := &common.RouteGroups{
|
|
||||||
Auth: app.Group("/auth"),
|
|
||||||
Membership: app.Group("/membership"),
|
|
||||||
}
|
|
||||||
mockAuth := &MockAuthMiddleware{authenticated: true}
|
|
||||||
controller.NewMembershipController(mockMembershipService, mockAuth, routeGroups)
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/auth/me", nil)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
|
|
||||||
// Parse response
|
|
||||||
var response model.User
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
err = json.Unmarshal(body, &response)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Verify response
|
|
||||||
tests.AssertEqual(t, expectedUser.ID, response.ID)
|
|
||||||
tests.AssertEqual(t, expectedUser.Username, response.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockMembershipService implements the MembershipService interface for testing
|
|
||||||
type MockMembershipService struct {
|
|
||||||
loginResponse string
|
|
||||||
createUserResponse *model.User
|
|
||||||
listUsersResponse []*model.User
|
|
||||||
getUserResponse *model.User
|
|
||||||
getUserWithPermissionsResponse *model.User
|
|
||||||
getRolesResponse []*model.Role
|
|
||||||
shouldFailLogin bool
|
|
||||||
shouldFailCreateUser bool
|
|
||||||
shouldFailListUsers bool
|
|
||||||
shouldFailGetUser bool
|
|
||||||
shouldFailGetUserWithPermissions bool
|
|
||||||
shouldFailDeleteUser bool
|
|
||||||
shouldFailUpdateUser bool
|
|
||||||
shouldFailGetRoles bool
|
|
||||||
loginCalled bool
|
|
||||||
createUserCalled bool
|
|
||||||
listUsersCalled bool
|
|
||||||
getUserCalled bool
|
|
||||||
getUserWithPermissionsCalled bool
|
|
||||||
deleteUserCalled bool
|
|
||||||
updateUserCalled bool
|
|
||||||
getRolesCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) Login(ctx context.Context, username, password string) (string, error) {
|
|
||||||
m.loginCalled = true
|
|
||||||
if m.shouldFailLogin {
|
|
||||||
return "", tests.ErrorForTesting("invalid credentials")
|
|
||||||
}
|
|
||||||
return m.loginResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) CreateUser(ctx context.Context, username, password, roleName string) (*model.User, error) {
|
|
||||||
m.createUserCalled = true
|
|
||||||
if m.shouldFailCreateUser {
|
|
||||||
return nil, tests.ErrorForTesting("failed to create user")
|
|
||||||
}
|
|
||||||
return m.createUserResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) ListUsers(ctx context.Context) ([]*model.User, error) {
|
|
||||||
m.listUsersCalled = true
|
|
||||||
if m.shouldFailListUsers {
|
|
||||||
return nil, tests.ErrorForTesting("failed to list users")
|
|
||||||
}
|
|
||||||
return m.listUsersResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) GetUser(ctx context.Context, userID uuid.UUID) (*model.User, error) {
|
|
||||||
m.getUserCalled = true
|
|
||||||
if m.shouldFailGetUser {
|
|
||||||
return nil, tests.ErrorForTesting("user not found")
|
|
||||||
}
|
|
||||||
return m.getUserResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) GetUserWithPermissions(ctx context.Context, userID string) (*model.User, error) {
|
|
||||||
m.getUserWithPermissionsCalled = true
|
|
||||||
if m.shouldFailGetUserWithPermissions {
|
|
||||||
return nil, tests.ErrorForTesting("user not found")
|
|
||||||
}
|
|
||||||
return m.getUserWithPermissionsResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) DeleteUser(ctx context.Context, userID uuid.UUID) error {
|
|
||||||
m.deleteUserCalled = true
|
|
||||||
if m.shouldFailDeleteUser {
|
|
||||||
return tests.ErrorForTesting("failed to delete user")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) UpdateUser(ctx context.Context, userID uuid.UUID, updates map[string]interface{}) (*model.User, error) {
|
|
||||||
m.updateUserCalled = true
|
|
||||||
if m.shouldFailUpdateUser {
|
|
||||||
return nil, tests.ErrorForTesting("failed to update user")
|
|
||||||
}
|
|
||||||
return m.getUserResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) GetRoles(ctx context.Context) ([]*model.Role, error) {
|
|
||||||
m.getRolesCalled = true
|
|
||||||
if m.shouldFailGetRoles {
|
|
||||||
return nil, tests.ErrorForTesting("failed to get roles")
|
|
||||||
}
|
|
||||||
return m.getRolesResponse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) SetCacheInvalidator(invalidator service.CacheInvalidator) {
|
|
||||||
// Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) SetupInitialData(ctx context.Context) error {
|
|
||||||
// Mock implementation - no-op for testing
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,684 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"acc-server-manager/local/middleware"
|
|
||||||
"acc-server-manager/local/model"
|
|
||||||
"acc-server-manager/local/service"
|
|
||||||
"acc-server-manager/local/utl/cache"
|
|
||||||
"acc-server-manager/local/utl/jwt"
|
|
||||||
"acc-server-manager/tests"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
jwtLib "github.com/golang-jwt/jwt/v4"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAuthMiddleware_Authenticate_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "User",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "read", Description: "Read permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with valid token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_Authenticate_MissingToken(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request without token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_Authenticate_InvalidToken(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with invalid token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer invalid-token")
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_Authenticate_MalformedHeader(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
header string
|
|
||||||
}{
|
|
||||||
{"Missing Bearer", "invalid-token"},
|
|
||||||
{"Extra parts", "Bearer token1 token2"},
|
|
||||||
{"Wrong prefix", "Basic token"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", tc.header)
|
|
||||||
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_Authenticate_ExpiredToken(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate token with very short expiration (simulate expired token)
|
|
||||||
claims := &jwt.Claims{
|
|
||||||
UserID: user.ID.String(),
|
|
||||||
RegisteredClaims: jwtLib.RegisteredClaims{
|
|
||||||
ExpiresAt: jwtLib.NewNumericDate(time.Now().Add(-1 * time.Hour)), // Expired
|
|
||||||
},
|
|
||||||
}
|
|
||||||
token := jwtLib.NewWithClaims(jwtLib.SigningMethodHS256, claims)
|
|
||||||
tokenString, err := token.SignedString(jwt.SecretKey)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with expired token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+tokenString)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_HasPermission_Success(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user with permissions
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "User",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "read", Description: "Read permission"},
|
|
||||||
{Name: "write", Description: "Write permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Use(authMiddleware.HasPermission("read"))
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with valid token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_HasPermission_Forbidden(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user without required permission
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "User",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "read", Description: "Read permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Use(authMiddleware.HasPermission("admin")) // User doesn't have admin permission
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with valid token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 403, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_HasPermission_SuperAdmin(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user with Super Admin role
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "superadmin",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "Super Admin",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "basic", Description: "Basic permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Use(authMiddleware.HasPermission("any-permission")) // Super Admin has all permissions
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with valid token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_HasPermission_Admin(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user with Admin role
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "admin",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "Admin",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "basic", Description: "Basic permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Use(authMiddleware.HasPermission("any-permission")) // Admin has all permissions
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with valid token
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_HasPermission_NoUserInContext(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
mockMembershipService := &MockMembershipService{}
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Create Fiber app for testing (skip authentication middleware)
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.HasPermission("read")) // No user in context
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_UserCaching(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "User",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "read", Description: "Read permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service that tracks calls
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
getUserCallCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// First request - should call database
|
|
||||||
resp1, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp1.StatusCode)
|
|
||||||
tests.AssertEqual(t, 1, mockMembershipService.getUserCallCount)
|
|
||||||
|
|
||||||
// Second request - should use cache
|
|
||||||
resp2, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp2.StatusCode)
|
|
||||||
tests.AssertEqual(t, 1, mockMembershipService.getUserCallCount) // Should still be 1 (cached)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_CacheInvalidation(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
Role: model.Role{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "User",
|
|
||||||
Permissions: []model.Permission{
|
|
||||||
{Name: "read", Description: "Read permission"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{
|
|
||||||
user.ID.String(): user,
|
|
||||||
},
|
|
||||||
getUserCallCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// First request - should call database
|
|
||||||
resp1, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp1.StatusCode)
|
|
||||||
tests.AssertEqual(t, 1, mockMembershipService.getUserCallCount)
|
|
||||||
|
|
||||||
// Invalidate cache
|
|
||||||
authMiddleware.InvalidateUserPermissions(user.ID.String())
|
|
||||||
|
|
||||||
// Second request - should call database again due to cache invalidation
|
|
||||||
resp2, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 200, resp2.StatusCode)
|
|
||||||
tests.AssertEqual(t, 2, mockMembershipService.getUserCallCount) // Should be 2 (cache invalidated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthMiddleware_UserNotFound(t *testing.T) {
|
|
||||||
// Setup
|
|
||||||
helper := tests.NewTestHelper(t)
|
|
||||||
defer helper.Cleanup()
|
|
||||||
|
|
||||||
// Create test user for token generation
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "password123",
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mock membership service without the user (user not found scenario)
|
|
||||||
mockMembershipService := &MockMembershipService{
|
|
||||||
users: map[string]*model.User{}, // Empty - user not found
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache and auth middleware
|
|
||||||
cache := cache.NewInMemoryCache()
|
|
||||||
authMiddleware := middleware.NewAuthMiddleware(mockMembershipService, cache)
|
|
||||||
|
|
||||||
// Generate valid JWT for non-existent user
|
|
||||||
token, err := jwt.GenerateToken(user)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
|
|
||||||
// Create Fiber app for testing
|
|
||||||
app := fiber.New()
|
|
||||||
app.Use(authMiddleware.Authenticate)
|
|
||||||
app.Get("/test", func(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(fiber.Map{"message": "success"})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create test request with valid token but non-existent user
|
|
||||||
req := httptest.NewRequest("GET", "/test", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
// Execute request
|
|
||||||
resp, err := app.Test(req)
|
|
||||||
tests.AssertNoError(t, err)
|
|
||||||
tests.AssertEqual(t, 401, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockMembershipService implements the MembershipService interface for testing
|
|
||||||
type MockMembershipService struct {
|
|
||||||
users map[string]*model.User
|
|
||||||
getUserCallCount int
|
|
||||||
shouldFailGet bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) GetUserWithPermissions(ctx context.Context, userID string) (*model.User, error) {
|
|
||||||
m.getUserCallCount++
|
|
||||||
if m.shouldFailGet {
|
|
||||||
return nil, errors.New("database error")
|
|
||||||
}
|
|
||||||
user, exists := m.users[userID]
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.New("user not found")
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) SetCacheInvalidator(invalidator service.CacheInvalidator) {
|
|
||||||
// Mock implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) Login(ctx context.Context, username, password string) (string, error) {
|
|
||||||
for _, user := range m.users {
|
|
||||||
if user.Username == username {
|
|
||||||
if err := user.VerifyPassword(password); err == nil {
|
|
||||||
return jwt.GenerateToken(user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("invalid credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) CreateUser(ctx context.Context, username, password, roleName string) (*model.User, error) {
|
|
||||||
user := &model.User{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
RoleID: uuid.New(),
|
|
||||||
}
|
|
||||||
m.users[user.ID.String()] = user
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) ListUsers(ctx context.Context) ([]*model.User, error) {
|
|
||||||
users := make([]*model.User, 0, len(m.users))
|
|
||||||
for _, user := range m.users {
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) GetUser(ctx context.Context, userID uuid.UUID) (*model.User, error) {
|
|
||||||
user, exists := m.users[userID.String()]
|
|
||||||
if !exists {
|
|
||||||
return nil, errors.New("user not found")
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockMembershipService) SetShouldFailGet(shouldFail bool) {
|
|
||||||
m.shouldFailGet = shouldFail
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user