package extpoints_test import ( "context" "testing" "testing/fstest" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/Rain-kl/Wavelet/core" "github.com/Rain-kl/Wavelet/core/extpoints" ) func TestRouterExtension(t *testing.T) { r := extpoints.NewRouterRegistry() require.NotNil(t, r) mGlobal := "global_middleware" r.Use(mGlobal) assert.Equal(t, []any{mGlobal}, r.Middlewares()) // Test root methods hRoot := "root_handler" r.GET("/", hRoot) r.POST("/root_post", hRoot) r.PUT("/root_put", hRoot) r.DELETE("/root_del", hRoot) r.PATCH("/root_patch", hRoot) r.HEAD("/root_head", hRoot) r.OPTIONS("/root_opt", hRoot) anyRootDefs := r.Any("/root_any", hRoot) assert.Len(t, anyRootDefs, 7) // Group and Group.Use mAPI := "api_middleware" api := r.Group("/api/v1", mAPI) api.Use("api_extra_middleware") assert.Len(t, api.Middlewares(), 2) hList := "list_orders_handler" hCreate := "create_order_handler" api.GET("/orders", hList) api.POST("/orders", hCreate) mAdmin := "admin_middleware" admin := api.Group("admin", mAdmin) hUserGet := "get_user_handler" hUserPut := "put_user_handler" hUserDel := "del_user_handler" hUserPatch := "patch_user_handler" hUserHead := "head_user_handler" hUserOptions := "options_user_handler" admin.GET("/users/:id", hUserGet) admin.PUT("/users/:id", hUserPut) admin.DELETE("/users/:id", hUserDel) admin.PATCH("/users/:id", hUserPatch) admin.HEAD("/users/:id", hUserHead) admin.OPTIONS("/users/:id", hUserOptions) hCustom := "custom_handler" admin.Handle("CUSTOM", "/custom", hCustom) hAny := "any_handler" anyRoutes := admin.Any("/all", hAny) assert.NotEmpty(t, anyRoutes) // Group.Routes() returns root routes assert.Equal(t, r.Routes(), admin.Routes()) routes := r.Routes() // Verify route paths and middlewares var foundOrderGet bool var foundUserPut bool for _, route := range routes { if route.Method == "GET" && route.Path == "/api/v1/orders" { foundOrderGet = true assert.Equal(t, []any{mGlobal, mAPI, "api_extra_middleware"}, route.Middlewares) assert.Equal(t, []any{hList}, route.Handlers) } if route.Method == "PUT" && route.Path == "/api/v1/admin/users/:id" { foundUserPut = true assert.Equal(t, []any{mGlobal, mAPI, "api_extra_middleware", mAdmin}, route.Middlewares) assert.Equal(t, []any{hUserPut}, route.Handlers) } } assert.True(t, foundOrderGet) assert.True(t, foundUserPut) } func TestMigrationExtension(t *testing.T) { m := extpoints.NewMigrationRegistry() require.NotNil(t, m) fs1 := fstest.MapFS{ "migrations/001_init.sql": &fstest.MapFile{Data: []byte("CREATE TABLE t1(id int);")}, } fs2 := fstest.MapFS{ "custom/001_order.sql": &fstest.MapFile{Data: []byte("CREATE TABLE t2(id int);")}, } m.Register("auth", fs1) m.Register("order", fs2, "custom") // Update existing entry fs1Updated := fstest.MapFS{ "migrations/002_update.sql": &fstest.MapFile{Data: []byte("ALTER TABLE t1 ADD col int;")}, } m.Register("auth", fs1Updated, "") entries := m.Entries() require.Len(t, entries, 2) assert.Equal(t, "auth", entries[0].PluginID) assert.Equal(t, "migrations", entries[0].Dir) assert.Equal(t, "order", entries[1].PluginID) assert.Equal(t, "custom", entries[1].Dir) authEntry, ok := m.Get("auth") assert.True(t, ok) assert.Equal(t, "auth", authEntry.PluginID) _, ok = m.Get("non_existent") assert.False(t, ok) } func TestTaskExtension(t *testing.T) { tr := extpoints.NewTaskRegistry() require.NotNil(t, tr) handler := func(ctx context.Context, payload []byte) error { return nil } tr.Register("order:cancel_timeout", handler, extpoints.WithTaskConcurrency(5), extpoints.WithTaskRetry(3), extpoints.WithTaskTimeout(10*time.Second), extpoints.WithTaskMetadata("queue", "critical"), nil, // test nil option ) // Re-register to test update tr.Register("order:cancel_timeout", handler, extpoints.WithTaskConcurrency(10), extpoints.WithTaskMetadata("queue", "high"), ) tasks := tr.Tasks() require.Len(t, tasks, 1) assert.Equal(t, "order:cancel_timeout", tasks[0].Pattern) assert.Equal(t, 10, tasks[0].Concurrency) assert.Equal(t, "high", tasks[0].Metadata["queue"]) task, ok := tr.Get("order:cancel_timeout") assert.True(t, ok) assert.Equal(t, "order:cancel_timeout", task.Pattern) _, ok = tr.Get("unknown") assert.False(t, ok) } func TestScheduleExtension(t *testing.T) { sr := extpoints.NewScheduleRegistry() require.NotNil(t, sr) type ReportPayload struct { Type string `json:"type"` } sr.RegisterCron("0 2 * * *", "report:daily_summary", ReportPayload{Type: "daily"}) sr.Register("@every 1h", "cleanup:expired_sessions", nil, extpoints.WithScheduleOption("retry", 2), nil, // test nil option ) // Re-register to test update sr.RegisterCron("0 3 * * *", "report:daily_summary", ReportPayload{Type: "all"}) schedules := sr.Schedules() require.Len(t, schedules, 2) assert.Equal(t, "0 3 * * *", schedules[0].Spec) assert.Equal(t, "report:daily_summary", schedules[0].TaskType) assert.Equal(t, ReportPayload{Type: "all"}, schedules[0].Payload) assert.Equal(t, "@every 1h", schedules[1].Spec) assert.Equal(t, "cleanup:expired_sessions", schedules[1].TaskType) assert.Equal(t, 2, schedules[1].Options["retry"]) sched, ok := sr.Get("report:daily_summary") assert.True(t, ok) assert.Equal(t, "0 3 * * *", sched.Spec) _, ok = sr.Get("unknown") assert.False(t, ok) } func TestSettingExtension(t *testing.T) { sr := extpoints.NewSettingRegistry() require.NotNil(t, sr) assert.Panics(t, func() { sr.Register(extpoints.SettingSchema{}) // empty key panics }) sr.Register(extpoints.SettingSchema{ Key: "order.auto_cancel_mins", Default: 15, Description: "Order auto cancellation timeout in minutes", Category: "order", Public: true, }) // Re-register to test update sr.Register(extpoints.SettingSchema{ Key: "order.auto_cancel_mins", Default: 30, Description: "Updated timeout", }) sr.Register(extpoints.SettingSchema{ Key: "auth.jwt_secret", Default: "default-secret", Description: "JWT secret key", Category: "auth", ReadOnly: true, }) schemas := sr.Schemas() require.Len(t, schemas, 2) schema, ok := sr.Get("order.auto_cancel_mins") assert.True(t, ok) assert.Equal(t, 30, schema.Default) _, ok = sr.Get("unknown") assert.False(t, ok) } func TestContextExtensionPointsIntegration(t *testing.T) { ctx := core.NewContext(context.Background()) require.NotNil(t, ctx.Events()) require.NotNil(t, ctx.Router()) require.NotNil(t, ctx.Migrations()) require.NotNil(t, ctx.Tasks()) require.NotNil(t, ctx.Task()) require.NotNil(t, ctx.Schedules()) require.NotNil(t, ctx.Schedule()) require.NotNil(t, ctx.Settings()) require.NotNil(t, ctx.Setting()) // Register from child context and verify shared application registry child := ctx.Fork() child.Router().GET("/ping", "pong_handler") child.Task().Register("sample:task", "handler") child.Schedule().RegisterCron("@hourly", "sample:cron", nil) child.Settings().Register(extpoints.SettingSchema{ Key: "app.name", Default: "Wavelet", }) assert.Len(t, ctx.Router().Routes(), 1) assert.Len(t, ctx.Tasks().Tasks(), 1) assert.Len(t, ctx.Schedules().Schedules(), 1) assert.Len(t, ctx.Settings().Schemas(), 1) // Child and root events var eventReceived bool child.Events().On("app:ready", func() { eventReceived = true }) err := ctx.Events().Emit(context.Background(), "app:ready", nil) assert.NoError(t, err) assert.True(t, eventReceived) }