dynamic registration of routes, model, migrations and initializers.
This commit is contained in:
@@ -10,5 +10,4 @@ func init() {
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
//registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ import "bamort/registry"
|
||||
// init self-registers the character module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/characters/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
//registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
+23
-45
@@ -1,19 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "bamort/appsystem"
|
||||
_ "bamort/character"
|
||||
"bamort/config"
|
||||
"bamort/database"
|
||||
_ "bamort/equipment"
|
||||
_ "bamort/gsmaster"
|
||||
_ "bamort/importer"
|
||||
"bamort/logger"
|
||||
_ "bamort/maintenance"
|
||||
"bamort/pdfrender"
|
||||
_ "bamort/pdfrender"
|
||||
"bamort/registry"
|
||||
"bamort/router"
|
||||
|
||||
// Blank imports trigger each module's init(), which self-registers
|
||||
// routes and migrations with the central registry.
|
||||
_ "bamort/appsystem"
|
||||
_ "bamort/character"
|
||||
_ "bamort/equipment"
|
||||
_ "bamort/gamesystem"
|
||||
_ "bamort/gsmaster"
|
||||
_ "bamort/importer"
|
||||
_ "bamort/maintenance"
|
||||
_ "bamort/models"
|
||||
_ "bamort/pdfrender"
|
||||
_ "bamort/transfer"
|
||||
_ "bamort/user"
|
||||
|
||||
@@ -62,23 +66,18 @@ func main() {
|
||||
database.ConnectDatabase()
|
||||
logger.Info("Datenbankverbindung erfolgreich")
|
||||
|
||||
/*
|
||||
// Populate initial misc lookup data
|
||||
logger.Debug("Initialisiere Misc-Lookup-Daten...")
|
||||
if err := gsmaster.PopulateMiscLookupData(); err != nil {
|
||||
logger.Warn("Fehler beim Initialisieren der Misc-Lookup-Daten: %s", err.Error())
|
||||
} else {
|
||||
logger.Info("Misc-Lookup-Daten erfolgreich initialisiert")
|
||||
}
|
||||
*/
|
||||
|
||||
// Initialize PDF templates
|
||||
logger.Debug("Initialisiere PDF-Templates...")
|
||||
if err := pdfrender.InitializeTemplates("/app/default_templates", cfg.TemplatesDir); err != nil {
|
||||
logger.Warn("Fehler beim Initialisieren der Templates: %s", err.Error())
|
||||
} else {
|
||||
logger.Info("PDF-Templates erfolgreich initialisiert")
|
||||
// Run all registered migrations
|
||||
logger.Debug("Führe Datenbankmigrationen aus...")
|
||||
if err := registry.RunAllMigrations(database.DB); err != nil {
|
||||
logger.Error("Fehler bei Datenbankmigrationen: %s", err.Error())
|
||||
panic(err)
|
||||
}
|
||||
logger.Info("Datenbankmigrationen erfolgreich")
|
||||
|
||||
// Run all registered initializers (post-migration startup tasks)
|
||||
logger.Debug("Führe Initialisierer aus...")
|
||||
registry.RunAllInitializers(database.DB)
|
||||
logger.Info("Initialisierer erfolgreich ausgeführt")
|
||||
|
||||
r := gin.Default()
|
||||
router.SetupGin(r)
|
||||
@@ -93,27 +92,6 @@ func main() {
|
||||
|
||||
logger.Info("API-Routen erfolgreich registriert")
|
||||
|
||||
/*
|
||||
// Routes registrieren
|
||||
logger.Debug("Registriere API-Routen...")
|
||||
protected := router.BaseRouterGrp(r)
|
||||
// Register your module routes
|
||||
user.RegisterRoutes(protected)
|
||||
gsmaster.RegisterRoutes(protected)
|
||||
character.RegisterRoutes(protected)
|
||||
equipment.RegisterRoutes(protected)
|
||||
maintenance.RegisterRoutes(protected)
|
||||
importer.RegisterRoutes(protected)
|
||||
pdfrender.RegisterRoutes(protected)
|
||||
transfer.RegisterRoutes(protected)
|
||||
appsystem.RegisterRoutes(protected)
|
||||
|
||||
// Register public routes (no authentication)
|
||||
pdfrender.RegisterPublicRoutes(r)
|
||||
appsystem.RegisterPublicRoutes(r)
|
||||
*/
|
||||
logger.Info("API-Routen erfolgreich registriert")
|
||||
|
||||
// Server starten
|
||||
serverAddress := cfg.GetServerAddress()
|
||||
logger.Info("Server startet auf Adresse: %s", serverAddress)
|
||||
|
||||
@@ -2,13 +2,15 @@ package database
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
// init self-registers the database module with the central registry.
|
||||
// main.go imports this package directly, which triggers this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/database/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
|
||||
// Database schema versioning migration.
|
||||
registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ package equipment
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// init self-registers the equipment module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/equipment/*, /api/weapons/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
//registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ package gamesystem
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// init self-registers the gamesystem module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/gamesystem/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
|
||||
// Database migration for GameSystem model.
|
||||
registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ package gsmaster
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// init self-registers the gsmaster module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/gsmaster/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
//registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package importer
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// init self-registers the importer module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/importer/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ import "bamort/registry"
|
||||
// init self-registers the maintenance module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/maintenance/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes.
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
//registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,9 @@ package models
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// init self-registers the models module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
//registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
//registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
// Core domain model migrations.
|
||||
registry.RegisterMigration(MigrateStructure)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
package pdfrender
|
||||
|
||||
import "bamort/registry"
|
||||
import (
|
||||
"bamort/config"
|
||||
"bamort/logger"
|
||||
"bamort/registry"
|
||||
)
|
||||
|
||||
// init self-registers the character module with the central registry.
|
||||
// init self-registers the pdfrender module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/version, /api/systeminfo).
|
||||
// Protected API routes (/api/pdf/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Public routes (/api/public/version, /api/public/systeminfo).
|
||||
// Public routes (/api/pdf/file/*).
|
||||
registry.RegisterPublicRoutes(RegisterPublicRoutes)
|
||||
//registry.RegisterMigration(MigrateStructure)
|
||||
|
||||
cfg := config.Cfg
|
||||
logger.Debug("Initialisiere PDF-Templates...")
|
||||
if err := InitializeTemplates("/app/default_templates", cfg.TemplatesDir); err != nil {
|
||||
logger.Warn("Fehler beim Initialisieren der Templates: %s", err.Error())
|
||||
} else {
|
||||
logger.Info("PDF-Templates erfolgreich initialisiert")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,18 @@ type BaseRouteFunc func(r *gin.Engine)
|
||||
// AuthMiddlewareProvider returns a gin.HandlerFunc used for JWT authentication.
|
||||
type AuthMiddlewareProvider func() gin.HandlerFunc
|
||||
|
||||
// InitializerFunc is called once after DB is connected and all migrations have run.
|
||||
// Use it to load persisted settings into in-memory configuration.
|
||||
type InitializerFunc func(db *gorm.DB)
|
||||
|
||||
var (
|
||||
routeFuncs []RouteFunc
|
||||
publicRouteFuncs []PublicRouteFunc
|
||||
baseRouteFuncs []BaseRouteFunc
|
||||
migrateFuncs []MigrateFunc
|
||||
initializerFuncs []InitializerFunc
|
||||
authProvider AuthMiddlewareProvider
|
||||
modelInstances []interface{}
|
||||
)
|
||||
|
||||
// RegisterRoutes adds a module's protected route registrar to the registry.
|
||||
@@ -54,6 +60,24 @@ func RegisterMigration(fn MigrateFunc) {
|
||||
migrateFuncs = append(migrateFuncs, fn)
|
||||
}
|
||||
|
||||
// RegisterInitializer adds a startup function that is called once after all
|
||||
// migrations have run. Use it to load persisted settings into config.
|
||||
func RegisterInitializer(fn InitializerFunc) {
|
||||
initializerFuncs = append(initializerFuncs, fn)
|
||||
}
|
||||
|
||||
// RegisterModel adds a GORM model instance to the central model registry.
|
||||
// Each module should register its model instances (e.g. MyModel{}) to allow
|
||||
// cross-database operations such as test snapshot creation.
|
||||
func RegisterModel(model interface{}) {
|
||||
modelInstances = append(modelInstances, model)
|
||||
}
|
||||
|
||||
// GetModels returns all registered GORM model instances.
|
||||
func GetModels() []interface{} {
|
||||
return modelInstances
|
||||
}
|
||||
|
||||
// SetAuthMiddleware sets the authentication middleware provider.
|
||||
// Only one module (user) should call this.
|
||||
func SetAuthMiddleware(fn AuthMiddlewareProvider) {
|
||||
@@ -99,3 +123,11 @@ func RunAllMigrations(db *gorm.DB) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunAllInitializers calls every registered initializer with the connected DB.
|
||||
// Invoke this once after RunAllMigrations.
|
||||
func RunAllInitializers(db *gorm.DB) {
|
||||
for _, fn := range initializerFuncs {
|
||||
fn(db)
|
||||
}
|
||||
}
|
||||
|
||||
+241
-159
@@ -1,38 +1,40 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// resetRegistry clears all registered functions so each test starts with a
|
||||
// clean slate. Only callable from within the registry package (_test.go).
|
||||
func resetRegistry() {
|
||||
routeFuncs = nil
|
||||
publicRouteFuncs = nil
|
||||
baseRouteFuncs = nil
|
||||
migrateFuncs = nil
|
||||
authProvider = nil
|
||||
routeFuncs = nil
|
||||
publicRouteFuncs = nil
|
||||
baseRouteFuncs = nil
|
||||
migrateFuncs = nil
|
||||
initializerFuncs = nil
|
||||
authProvider = nil
|
||||
modelInstances = nil
|
||||
}
|
||||
|
||||
func newTestEngine() *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
return gin.New()
|
||||
gin.SetMode(gin.TestMode)
|
||||
return gin.New()
|
||||
}
|
||||
|
||||
func newTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
return db
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
return db
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -40,39 +42,39 @@ return db
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestRegisterRoutes_SingleFunc_IsCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
called := false
|
||||
RegisterRoutes(func(r *gin.RouterGroup) {
|
||||
called = true
|
||||
r.GET("/ping", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
})
|
||||
called := false
|
||||
RegisterRoutes(func(r *gin.RouterGroup) {
|
||||
called = true
|
||||
r.GET("/ping", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
})
|
||||
|
||||
engine := newTestEngine()
|
||||
RunAllRoutes(engine.Group("/api"))
|
||||
engine := newTestEngine()
|
||||
RunAllRoutes(engine.Group("/api"))
|
||||
|
||||
assert.True(t, called)
|
||||
assert.True(t, called)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/api/ping", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/api/ping", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestRegisterRoutes_MultipleFuncs_AllCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
count := 0
|
||||
for i := 0; i < 3; i++ {
|
||||
RegisterRoutes(func(r *gin.RouterGroup) { count++ })
|
||||
}
|
||||
RunAllRoutes(newTestEngine().Group("/api"))
|
||||
assert.Equal(t, 3, count)
|
||||
count := 0
|
||||
for i := 0; i < 3; i++ {
|
||||
RegisterRoutes(func(r *gin.RouterGroup) { count++ })
|
||||
}
|
||||
RunAllRoutes(newTestEngine().Group("/api"))
|
||||
assert.Equal(t, 3, count)
|
||||
}
|
||||
|
||||
func TestRunAllRoutes_NoFuncs_DoesNotPanic(t *testing.T) {
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllRoutes(newTestEngine().Group("/api")) })
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllRoutes(newTestEngine().Group("/api")) })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -80,28 +82,28 @@ assert.NotPanics(t, func() { RunAllRoutes(newTestEngine().Group("/api")) })
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestRegisterPublicRoutes_SingleFunc_IsCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
called := false
|
||||
RegisterPublicRoutes(func(r *gin.Engine) {
|
||||
called = true
|
||||
r.GET("/health", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
})
|
||||
called := false
|
||||
RegisterPublicRoutes(func(r *gin.Engine) {
|
||||
called = true
|
||||
r.GET("/health", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
})
|
||||
|
||||
engine := newTestEngine()
|
||||
RunAllPublicRoutes(engine)
|
||||
engine := newTestEngine()
|
||||
RunAllPublicRoutes(engine)
|
||||
|
||||
assert.True(t, called)
|
||||
assert.True(t, called)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/health", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/health", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestRunAllPublicRoutes_NoFuncs_DoesNotPanic(t *testing.T) {
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllPublicRoutes(newTestEngine()) })
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllPublicRoutes(newTestEngine()) })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -109,28 +111,28 @@ assert.NotPanics(t, func() { RunAllPublicRoutes(newTestEngine()) })
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestRegisterBaseRoutes_SingleFunc_IsCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
called := false
|
||||
RegisterBaseRoutes(func(r *gin.Engine) {
|
||||
called = true
|
||||
r.POST("/login", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
})
|
||||
called := false
|
||||
RegisterBaseRoutes(func(r *gin.Engine) {
|
||||
called = true
|
||||
r.POST("/login", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
})
|
||||
|
||||
engine := newTestEngine()
|
||||
RunAllBaseRoutes(engine)
|
||||
engine := newTestEngine()
|
||||
RunAllBaseRoutes(engine)
|
||||
|
||||
assert.True(t, called)
|
||||
assert.True(t, called)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodPost, "/login", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodPost, "/login", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestRunAllBaseRoutes_NoFuncs_DoesNotPanic(t *testing.T) {
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllBaseRoutes(newTestEngine()) })
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllBaseRoutes(newTestEngine()) })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -138,64 +140,64 @@ assert.NotPanics(t, func() { RunAllBaseRoutes(newTestEngine()) })
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestGetAuthMiddleware_NoProvider_ReturnsPassThrough(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
mw := GetAuthMiddleware()
|
||||
require.NotNil(t, mw)
|
||||
mw := GetAuthMiddleware()
|
||||
require.NotNil(t, mw)
|
||||
|
||||
engine := newTestEngine()
|
||||
engine.Use(mw)
|
||||
engine.GET("/test", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
engine := newTestEngine()
|
||||
engine.Use(mw)
|
||||
engine.GET("/test", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code, "pass-through should not block requests")
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code, "pass-through should not block requests")
|
||||
}
|
||||
|
||||
func TestSetAuthMiddleware_ProviderIsUsed(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
providerCalled := false
|
||||
SetAuthMiddleware(func() gin.HandlerFunc {
|
||||
providerCalled = true
|
||||
return func(c *gin.Context) {
|
||||
c.Header("X-Auth", "ok")
|
||||
c.Next()
|
||||
}
|
||||
})
|
||||
providerCalled := false
|
||||
SetAuthMiddleware(func() gin.HandlerFunc {
|
||||
providerCalled = true
|
||||
return func(c *gin.Context) {
|
||||
c.Header("X-Auth", "ok")
|
||||
c.Next()
|
||||
}
|
||||
})
|
||||
|
||||
mw := GetAuthMiddleware()
|
||||
assert.True(t, providerCalled)
|
||||
mw := GetAuthMiddleware()
|
||||
assert.True(t, providerCalled)
|
||||
|
||||
engine := newTestEngine()
|
||||
engine.Use(mw)
|
||||
engine.GET("/secure", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
engine := newTestEngine()
|
||||
engine.Use(mw)
|
||||
engine.GET("/secure", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/secure", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, "ok", w.Header().Get("X-Auth"))
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/secure", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, "ok", w.Header().Get("X-Auth"))
|
||||
}
|
||||
|
||||
func TestSetAuthMiddleware_OverwritesPreviousProvider(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
SetAuthMiddleware(func() gin.HandlerFunc {
|
||||
return func(c *gin.Context) { c.Header("X-Version", "first"); c.Next() }
|
||||
})
|
||||
SetAuthMiddleware(func() gin.HandlerFunc {
|
||||
return func(c *gin.Context) { c.Header("X-Version", "second"); c.Next() }
|
||||
})
|
||||
SetAuthMiddleware(func() gin.HandlerFunc {
|
||||
return func(c *gin.Context) { c.Header("X-Version", "first"); c.Next() }
|
||||
})
|
||||
SetAuthMiddleware(func() gin.HandlerFunc {
|
||||
return func(c *gin.Context) { c.Header("X-Version", "second"); c.Next() }
|
||||
})
|
||||
|
||||
engine := newTestEngine()
|
||||
engine.Use(GetAuthMiddleware())
|
||||
engine.GET("/v", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
engine := newTestEngine()
|
||||
engine.Use(GetAuthMiddleware())
|
||||
engine.GET("/v", func(c *gin.Context) { c.Status(http.StatusOK) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/v", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, "second", w.Header().Get("X-Version"), "last registration wins")
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/v", nil)
|
||||
engine.ServeHTTP(w, req)
|
||||
assert.Equal(t, "second", w.Header().Get("X-Version"), "last registration wins")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -203,75 +205,155 @@ assert.Equal(t, "second", w.Header().Get("X-Version"), "last registration wins")
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestRunAllMigrations_NoFuncs_ReturnsNil(t *testing.T) {
|
||||
resetRegistry()
|
||||
assert.NoError(t, RunAllMigrations(newTestDB(t)))
|
||||
resetRegistry()
|
||||
assert.NoError(t, RunAllMigrations(newTestDB(t)))
|
||||
}
|
||||
|
||||
func TestRunAllMigrations_SingleFunc_IsCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
called := false
|
||||
RegisterMigration(func(d ...*gorm.DB) error {
|
||||
called = true
|
||||
return nil
|
||||
})
|
||||
called := false
|
||||
RegisterMigration(func(d ...*gorm.DB) error {
|
||||
called = true
|
||||
return nil
|
||||
})
|
||||
|
||||
require.NoError(t, RunAllMigrations(newTestDB(t)))
|
||||
assert.True(t, called)
|
||||
require.NoError(t, RunAllMigrations(newTestDB(t)))
|
||||
assert.True(t, called)
|
||||
}
|
||||
|
||||
func TestRunAllMigrations_ReceivesCorrectDB(t *testing.T) {
|
||||
resetRegistry()
|
||||
db := newTestDB(t)
|
||||
resetRegistry()
|
||||
db := newTestDB(t)
|
||||
|
||||
var received *gorm.DB
|
||||
RegisterMigration(func(d ...*gorm.DB) error {
|
||||
if len(d) > 0 {
|
||||
received = d[0]
|
||||
}
|
||||
return nil
|
||||
})
|
||||
var received *gorm.DB
|
||||
RegisterMigration(func(d ...*gorm.DB) error {
|
||||
if len(d) > 0 {
|
||||
received = d[0]
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
require.NoError(t, RunAllMigrations(db))
|
||||
assert.Same(t, db, received, "RunAllMigrations should pass the provided DB instance")
|
||||
require.NoError(t, RunAllMigrations(db))
|
||||
assert.Same(t, db, received, "RunAllMigrations should pass the provided DB instance")
|
||||
}
|
||||
|
||||
func TestRunAllMigrations_MultipleFuncs_AllCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
count := 0
|
||||
for i := 0; i < 3; i++ {
|
||||
RegisterMigration(func(d ...*gorm.DB) error { count++; return nil })
|
||||
}
|
||||
require.NoError(t, RunAllMigrations(newTestDB(t)))
|
||||
assert.Equal(t, 3, count)
|
||||
count := 0
|
||||
for i := 0; i < 3; i++ {
|
||||
RegisterMigration(func(d ...*gorm.DB) error { count++; return nil })
|
||||
}
|
||||
require.NoError(t, RunAllMigrations(newTestDB(t)))
|
||||
assert.Equal(t, 3, count)
|
||||
}
|
||||
|
||||
func TestRunAllMigrations_FirstErrorAbortsChain(t *testing.T) {
|
||||
resetRegistry()
|
||||
resetRegistry()
|
||||
|
||||
secondCalled := false
|
||||
RegisterMigration(func(d ...*gorm.DB) error { return errors.New("migration failed") })
|
||||
RegisterMigration(func(d ...*gorm.DB) error { secondCalled = true; return nil })
|
||||
secondCalled := false
|
||||
RegisterMigration(func(d ...*gorm.DB) error { return errors.New("migration failed") })
|
||||
RegisterMigration(func(d ...*gorm.DB) error { secondCalled = true; return nil })
|
||||
|
||||
err := RunAllMigrations(newTestDB(t))
|
||||
assert.Error(t, err)
|
||||
assert.False(t, secondCalled, "subsequent migrations must not run after an error")
|
||||
err := RunAllMigrations(newTestDB(t))
|
||||
assert.Error(t, err)
|
||||
assert.False(t, secondCalled, "subsequent migrations must not run after an error")
|
||||
}
|
||||
|
||||
func TestRunAllMigrations_AutoMigratesModel(t *testing.T) {
|
||||
resetRegistry()
|
||||
db := newTestDB(t)
|
||||
resetRegistry()
|
||||
db := newTestDB(t)
|
||||
|
||||
type SampleModel struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
type SampleModel struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
}
|
||||
|
||||
RegisterMigration(func(d ...*gorm.DB) error {
|
||||
return d[0].AutoMigrate(&SampleModel{})
|
||||
})
|
||||
|
||||
require.NoError(t, RunAllMigrations(db))
|
||||
assert.True(t, db.Migrator().HasTable(&SampleModel{}), "table should exist after migration")
|
||||
}
|
||||
|
||||
RegisterMigration(func(d ...*gorm.DB) error {
|
||||
return d[0].AutoMigrate(&SampleModel{})
|
||||
})
|
||||
// ---------------------------------------------------------------------------
|
||||
// RegisterInitializer / RunAllInitializers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
require.NoError(t, RunAllMigrations(db))
|
||||
assert.True(t, db.Migrator().HasTable(&SampleModel{}), "table should exist after migration")
|
||||
func TestRunAllInitializers_NoFuncs_DoesNotPanic(t *testing.T) {
|
||||
resetRegistry()
|
||||
assert.NotPanics(t, func() { RunAllInitializers(newTestDB(t)) })
|
||||
}
|
||||
|
||||
func TestRegisterInitializer_SingleFunc_IsCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
called := false
|
||||
RegisterInitializer(func(db *gorm.DB) {
|
||||
called = true
|
||||
})
|
||||
|
||||
RunAllInitializers(newTestDB(t))
|
||||
assert.True(t, called)
|
||||
}
|
||||
|
||||
func TestRunAllInitializers_ReceivesCorrectDB(t *testing.T) {
|
||||
resetRegistry()
|
||||
db := newTestDB(t)
|
||||
|
||||
var received *gorm.DB
|
||||
RegisterInitializer(func(d *gorm.DB) {
|
||||
received = d
|
||||
})
|
||||
|
||||
RunAllInitializers(db)
|
||||
assert.Same(t, db, received, "RunAllInitializers should pass the provided DB instance")
|
||||
}
|
||||
|
||||
func TestRunAllInitializers_MultipleFuncs_AllCalled(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
count := 0
|
||||
for i := 0; i < 3; i++ {
|
||||
RegisterInitializer(func(db *gorm.DB) { count++ })
|
||||
}
|
||||
RunAllInitializers(newTestDB(t))
|
||||
assert.Equal(t, 3, count)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RegisterModel / GetModels
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestGetModels_NoModels_ReturnsNil(t *testing.T) {
|
||||
resetRegistry()
|
||||
assert.Nil(t, GetModels())
|
||||
}
|
||||
|
||||
func TestRegisterModel_SingleModel_IsReturned(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
type Foo struct{ Name string }
|
||||
RegisterModel(Foo{})
|
||||
|
||||
models := GetModels()
|
||||
require.Len(t, models, 1)
|
||||
assert.IsType(t, Foo{}, models[0])
|
||||
}
|
||||
|
||||
func TestRegisterModel_MultipleModels_AllReturned(t *testing.T) {
|
||||
resetRegistry()
|
||||
|
||||
type A struct{ X int }
|
||||
type B struct{ Y string }
|
||||
|
||||
RegisterModel(A{})
|
||||
RegisterModel(B{})
|
||||
|
||||
models := GetModels()
|
||||
require.Len(t, models, 2)
|
||||
assert.IsType(t, A{}, models[0])
|
||||
assert.IsType(t, B{}, models[1])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package transfer
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the transfer module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Protected API routes (/api/transfer/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package user
|
||||
|
||||
import "bamort/registry"
|
||||
|
||||
// init self-registers the user module with the central registry.
|
||||
// main.go blank-imports this package to trigger this function.
|
||||
func init() {
|
||||
// Auth middleware for JWT authentication.
|
||||
registry.SetAuthMiddleware(AuthMiddleware)
|
||||
|
||||
// Base routes (login, register, password-reset) — no auth required.
|
||||
registry.RegisterBaseRoutes(RegisterBaseRoutes)
|
||||
|
||||
// Protected API routes (/api/user/*, /api/users/*).
|
||||
registry.RegisterRoutes(RegisterRoutes)
|
||||
|
||||
// Database migration for User model.
|
||||
registry.RegisterMigration(MigrateStructure)
|
||||
|
||||
// Register GORM model for cross-DB operations.
|
||||
registry.RegisterModel(User{})
|
||||
}
|
||||
@@ -4,6 +4,19 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterBaseRoutes registers unauthenticated routes (login, register, password-reset).
|
||||
func RegisterBaseRoutes(r *gin.Engine) {
|
||||
r.POST("/login", LoginUser)
|
||||
r.POST("/register", RegisterUser)
|
||||
|
||||
pwReset := r.Group("/password-reset")
|
||||
{
|
||||
pwReset.POST("/request", RequestPasswordReset)
|
||||
pwReset.GET("/validate/:token", ValidateResetToken)
|
||||
pwReset.POST("/reset", ResetPassword)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes registers user-related routes
|
||||
func RegisterRoutes(r *gin.RouterGroup) {
|
||||
userGroup := r.Group("/user")
|
||||
|
||||
Reference in New Issue
Block a user