mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
655 lines
17 KiB
Go
655 lines
17 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package core_test
|
|
|
|
import (
|
|
"Wavelet/core"
|
|
"Wavelet/core/extpoints"
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"testing/fstest"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// appMockDriver is a test driver tracking its start/stop lifecycle.
|
|
type appMockDriver struct {
|
|
mu sync.Mutex
|
|
driverType core.DriverType
|
|
startCalled bool
|
|
stopCalled bool
|
|
startErr error
|
|
stopErr error
|
|
}
|
|
|
|
func newAppMockDriver(dt core.DriverType) *appMockDriver {
|
|
return &appMockDriver{driverType: dt}
|
|
}
|
|
|
|
func (m *appMockDriver) Type() core.DriverType {
|
|
return m.driverType
|
|
}
|
|
|
|
func (m *appMockDriver) Start(_ context.Context) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.startErr != nil {
|
|
return m.startErr
|
|
}
|
|
m.startCalled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *appMockDriver) Stop(_ context.Context) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.stopErr != nil {
|
|
return m.stopErr
|
|
}
|
|
m.stopCalled = true
|
|
return nil
|
|
}
|
|
|
|
func (m *appMockDriver) isStarted() bool {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.startCalled
|
|
}
|
|
|
|
func (m *appMockDriver) isStopped() bool {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.stopCalled
|
|
}
|
|
|
|
// appMockPlugin is a test plugin.
|
|
type appMockPlugin struct {
|
|
name string
|
|
applyFn func(ctx *core.Context) error
|
|
}
|
|
|
|
func (p *appMockPlugin) Name() string {
|
|
return p.name
|
|
}
|
|
|
|
func (p *appMockPlugin) Apply(ctx *core.Context) error {
|
|
if p.applyFn != nil {
|
|
return p.applyFn(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestAppNewAndConfiguration(t *testing.T) {
|
|
customCtx := core.NewContext(context.Background())
|
|
p1 := &appMockPlugin{name: "plugin1"}
|
|
p2 := &appMockPlugin{name: "plugin2"}
|
|
|
|
app := core.NewApp(
|
|
core.WithContext(customCtx),
|
|
core.WithProfile(core.ProfileAPI),
|
|
core.WithPlugins(p1, p2),
|
|
core.WithShutdownTimeout(5*time.Second),
|
|
)
|
|
|
|
assert.Equal(t, customCtx, app.Context())
|
|
assert.Equal(t, core.ProfileAPI, app.Profile())
|
|
assert.Len(t, app.Plugins(), 2)
|
|
|
|
retrieved, ok := app.Plugin("plugin1")
|
|
assert.True(t, ok)
|
|
assert.Equal(t, p1, retrieved)
|
|
|
|
_, ok = app.Plugin("non_existent")
|
|
assert.False(t, ok)
|
|
|
|
// Update existing plugin in-place
|
|
p1Updated := &appMockPlugin{name: "plugin1"}
|
|
app.Use(p1Updated, nil)
|
|
assert.Len(t, app.Plugins(), 2)
|
|
retrieved, ok = app.Plugin("plugin1")
|
|
assert.True(t, ok)
|
|
assert.Equal(t, p1Updated, retrieved)
|
|
|
|
// Test SetProfile
|
|
app.SetProfile(core.ProfileWorker)
|
|
assert.Equal(t, core.ProfileWorker, app.Profile())
|
|
}
|
|
|
|
func TestAppProfileDispatch(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
profile core.Profile
|
|
expectedHTTP bool
|
|
expectedWorker bool
|
|
expectedCron bool
|
|
expectedCustom bool
|
|
}{
|
|
{
|
|
name: "ProfileAPI only starts HTTP driver",
|
|
profile: core.ProfileAPI,
|
|
expectedHTTP: true,
|
|
expectedWorker: false,
|
|
expectedCron: false,
|
|
expectedCustom: false,
|
|
},
|
|
{
|
|
name: "ProfileWorker only starts Worker driver",
|
|
profile: core.ProfileWorker,
|
|
expectedHTTP: false,
|
|
expectedWorker: true,
|
|
expectedCron: false,
|
|
expectedCustom: false,
|
|
},
|
|
{
|
|
name: "ProfileSchedule only starts Schedule driver",
|
|
profile: core.ProfileSchedule,
|
|
expectedHTTP: false,
|
|
expectedWorker: false,
|
|
expectedCron: true,
|
|
expectedCustom: false,
|
|
},
|
|
{
|
|
name: "Profile 'scheduler' alias starts Schedule driver",
|
|
profile: core.Profile("scheduler"),
|
|
expectedHTTP: false,
|
|
expectedWorker: false,
|
|
expectedCron: true,
|
|
expectedCustom: false,
|
|
},
|
|
{
|
|
name: "ProfileAll starts all drivers",
|
|
profile: core.ProfileAll,
|
|
expectedHTTP: true,
|
|
expectedWorker: true,
|
|
expectedCron: true,
|
|
expectedCustom: true,
|
|
},
|
|
{
|
|
name: "Custom profile starts custom driver",
|
|
profile: core.Profile("custom_rpc"),
|
|
expectedHTTP: false,
|
|
expectedWorker: false,
|
|
expectedCron: false,
|
|
expectedCustom: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
httpD := newAppMockDriver(core.DriverTypeHTTP)
|
|
workerD := newAppMockDriver(core.DriverTypeWorker)
|
|
cronD := newAppMockDriver(core.DriverTypeScheduler)
|
|
customD := newAppMockDriver(core.DriverType("custom_rpc"))
|
|
|
|
p := &appMockPlugin{
|
|
name: "drivers_plugin",
|
|
applyFn: func(ctx *core.Context) error {
|
|
_ = ctx.RegisterDriver(httpD)
|
|
_ = ctx.RegisterDriver(workerD)
|
|
_ = ctx.RegisterDriver(cronD)
|
|
_ = ctx.RegisterDriver(customD)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
app := core.NewApp(
|
|
core.WithProfile(tt.profile),
|
|
core.WithPlugins(p),
|
|
)
|
|
|
|
err := app.Start(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.expectedHTTP, httpD.isStarted(), "HTTP driver start mismatch")
|
|
assert.Equal(t, tt.expectedWorker, workerD.isStarted(), "Worker driver start mismatch")
|
|
assert.Equal(t, tt.expectedCron, cronD.isStarted(), "Cron driver start mismatch")
|
|
assert.Equal(t, tt.expectedCustom, customD.isStarted(), "Custom driver start mismatch")
|
|
|
|
err = app.Stop(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAppLifecycleStartStop(t *testing.T) {
|
|
var stopOrder []string
|
|
var stopOrderMu sync.Mutex
|
|
|
|
httpD := newAppMockDriver(core.DriverTypeHTTP)
|
|
workerD := newAppMockDriver(core.DriverTypeWorker)
|
|
|
|
httpD.stopErr = nil
|
|
workerD.stopErr = nil
|
|
|
|
// Wrap stop to record order
|
|
origHttpStop := httpD.Stop
|
|
_ = origHttpStop
|
|
|
|
p := &appMockPlugin{
|
|
name: "test_plugin",
|
|
applyFn: func(ctx *core.Context) error {
|
|
_ = ctx.RegisterDriver(httpD)
|
|
_ = ctx.RegisterDriver(workerD)
|
|
|
|
ctx.OnDispose(func() error {
|
|
stopOrderMu.Lock()
|
|
stopOrder = append(stopOrder, "ctx_disposer")
|
|
stopOrderMu.Unlock()
|
|
return nil
|
|
})
|
|
return nil
|
|
},
|
|
}
|
|
|
|
app := core.NewApp(
|
|
core.WithProfile(core.ProfileAll),
|
|
core.WithPlugins(p),
|
|
)
|
|
|
|
var readyReceived, stoppingReceived, stoppedReceived bool
|
|
app.Context().Events().On("app:ready", func() {
|
|
readyReceived = true
|
|
})
|
|
app.Context().Events().On("app:stopping", func() {
|
|
stoppingReceived = true
|
|
})
|
|
app.Context().Events().On("app:stopped", func() {
|
|
stoppedReceived = true
|
|
})
|
|
|
|
err := app.Start(context.Background())
|
|
require.NoError(t, err)
|
|
assert.True(t, app.IsRunning())
|
|
assert.Len(t, app.StartedDrivers(), 2)
|
|
assert.True(t, readyReceived)
|
|
|
|
err = app.Stop(context.Background())
|
|
require.NoError(t, err)
|
|
assert.False(t, app.IsRunning())
|
|
assert.Empty(t, app.StartedDrivers())
|
|
assert.True(t, stoppingReceived)
|
|
assert.True(t, stoppedReceived)
|
|
|
|
assert.True(t, httpD.isStopped())
|
|
assert.True(t, workerD.isStopped())
|
|
assert.True(t, app.Context().IsDisposed())
|
|
|
|
stopOrderMu.Lock()
|
|
assert.Contains(t, stopOrder, "ctx_disposer")
|
|
stopOrderMu.Unlock()
|
|
}
|
|
|
|
func TestAppStartDriverFailureRollback(t *testing.T) {
|
|
driver1 := newAppMockDriver(core.DriverTypeHTTP)
|
|
driver2 := newAppMockDriver(core.DriverTypeWorker)
|
|
driver2.startErr = errors.New("worker listen port conflict")
|
|
driver3 := newAppMockDriver(core.DriverTypeScheduler)
|
|
|
|
p := &appMockPlugin{
|
|
name: "fail_driver_plugin",
|
|
applyFn: func(ctx *core.Context) error {
|
|
_ = ctx.RegisterDriver(driver1)
|
|
_ = ctx.RegisterDriver(driver2)
|
|
_ = ctx.RegisterDriver(driver3)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
app := core.NewApp(
|
|
core.WithProfile(core.ProfileAll),
|
|
core.WithPlugins(p),
|
|
)
|
|
|
|
err := app.Start(context.Background())
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "worker listen port conflict")
|
|
assert.False(t, app.IsRunning())
|
|
|
|
// Driver 1 was started then rolled back (stopped)
|
|
assert.True(t, driver1.isStarted())
|
|
assert.True(t, driver1.isStopped())
|
|
|
|
// Driver 3 was never started
|
|
assert.False(t, driver3.isStarted())
|
|
}
|
|
|
|
func TestAppMigrationEngineExecution(t *testing.T) {
|
|
var migratedEntries []extpoints.MigrationEntry
|
|
runner := core.MigrationRunner(func(ctx *core.Context, entries []extpoints.MigrationEntry) error {
|
|
migratedEntries = entries
|
|
return nil
|
|
})
|
|
|
|
sqlFS := fstest.MapFS{
|
|
"migrations/001_init.sql": &fstest.MapFile{Data: []byte("CREATE TABLE users(id int);")},
|
|
}
|
|
|
|
p := &appMockPlugin{
|
|
name: "auth",
|
|
applyFn: func(ctx *core.Context) error {
|
|
ctx.Migrations().Register("auth", sqlFS)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
app := core.NewApp(
|
|
core.WithProfile(core.ProfileAll),
|
|
core.WithPlugins(p),
|
|
core.WithMigrationRunner(runner),
|
|
)
|
|
|
|
err := app.Start(context.Background())
|
|
require.NoError(t, err)
|
|
defer func() { _ = app.Stop(context.Background()) }()
|
|
|
|
require.Len(t, migratedEntries, 1)
|
|
assert.Equal(t, "auth", migratedEntries[0].PluginID)
|
|
}
|
|
|
|
func TestAppMigrationEngineFromIoCContainer(t *testing.T) {
|
|
var executed bool
|
|
runner := core.MigrationRunner(func(ctx *core.Context, entries []extpoints.MigrationEntry) error {
|
|
executed = true
|
|
return nil
|
|
})
|
|
|
|
sqlFS := fstest.MapFS{
|
|
"migrations/001_init.sql": &fstest.MapFile{Data: []byte("CREATE TABLE logs(id int);")},
|
|
}
|
|
|
|
p := &appMockPlugin{
|
|
name: "logstore",
|
|
applyFn: func(ctx *core.Context) error {
|
|
ctx.Migrations().Register("logstore", sqlFS)
|
|
core.Provide[core.MigrationEngine](ctx, runner)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
app := core.NewApp(
|
|
core.WithProfile(core.ProfileAll),
|
|
core.WithPlugins(p),
|
|
)
|
|
|
|
err := app.Start(context.Background())
|
|
require.NoError(t, err)
|
|
defer func() { _ = app.Stop(context.Background()) }()
|
|
|
|
assert.True(t, executed)
|
|
}
|
|
|
|
func TestAppRunContextCancellation(t *testing.T) {
|
|
d := newAppMockDriver(core.DriverTypeHTTP)
|
|
p := &appMockPlugin{
|
|
name: "http_plugin",
|
|
applyFn: func(ctx *core.Context) error {
|
|
return ctx.RegisterDriver(d)
|
|
},
|
|
}
|
|
|
|
app := core.NewApp(
|
|
core.WithProfile(core.ProfileAPI),
|
|
core.WithPlugins(p),
|
|
core.WithShutdownTimeout(1*time.Second),
|
|
)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
errCh <- app.Run(ctx)
|
|
}()
|
|
|
|
// Wait for app and driver to become ready
|
|
assert.Eventually(t, func() bool {
|
|
return app.IsRunning() && d.isStarted()
|
|
}, 2*time.Second, 10*time.Millisecond)
|
|
|
|
cancel()
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
assert.NoError(t, err)
|
|
assert.False(t, app.IsRunning())
|
|
assert.True(t, d.isStopped())
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("app.Run did not terminate upon context cancellation")
|
|
}
|
|
}
|
|
|
|
func TestAppExecuteCLI(t *testing.T) {
|
|
// Test CLI argument parsing logic
|
|
tests := []struct {
|
|
args []string
|
|
expectedProfile core.Profile
|
|
}{
|
|
{args: []string{"api"}, expectedProfile: core.ProfileAPI},
|
|
{args: []string{"worker"}, expectedProfile: core.ProfileWorker},
|
|
{args: []string{"scheduler"}, expectedProfile: core.ProfileSchedule},
|
|
{args: []string{"schedule"}, expectedProfile: core.ProfileSchedule},
|
|
{args: []string{"all"}, expectedProfile: core.ProfileAll},
|
|
{args: []string{"--profile=worker"}, expectedProfile: core.ProfileWorker},
|
|
{args: []string{"-p=api"}, expectedProfile: core.ProfileAPI},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.args[0], func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel immediately
|
|
|
|
// Use custom root context to control cancellation
|
|
customApp := core.NewApp(core.WithContext(core.NewContext(ctx)))
|
|
_ = customApp.ExecuteCLI(tt.args...)
|
|
assert.Equal(t, tt.expectedProfile, customApp.Profile())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAppIdempotencyAndErrorStates(t *testing.T) {
|
|
app := core.NewApp()
|
|
|
|
// Double start returns error
|
|
err := app.Start(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
err = app.Start(context.Background())
|
|
assert.ErrorIs(t, err, core.ErrAppRunning)
|
|
|
|
// Stop clears running state
|
|
err = app.Stop(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Double stop succeeds
|
|
err = app.Stop(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Plugin apply failure
|
|
failPlugin := &appMockPlugin{
|
|
name: "failing_plugin",
|
|
applyFn: func(ctx *core.Context) error {
|
|
return errors.New("plugin init boom")
|
|
},
|
|
}
|
|
app2 := core.NewApp(core.WithPlugins(failPlugin))
|
|
err = app2.Start(context.Background())
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "plugin init boom")
|
|
assert.False(t, app2.IsRunning())
|
|
|
|
// Migration failure
|
|
migFailRunner := core.MigrationRunner(func(ctx *core.Context, entries []extpoints.MigrationEntry) error {
|
|
return errors.New("sql migrate error")
|
|
})
|
|
sqlFS := fstest.MapFS{
|
|
"migrations/001.sql": &fstest.MapFile{Data: []byte("...")},
|
|
}
|
|
migPlugin := &appMockPlugin{
|
|
name: "db_plugin",
|
|
applyFn: func(ctx *core.Context) error {
|
|
ctx.Migrations().Register("db_plugin", sqlFS)
|
|
return nil
|
|
},
|
|
}
|
|
app3 := core.NewApp(
|
|
core.WithPlugins(migPlugin),
|
|
core.WithMigrationRunner(migFailRunner),
|
|
)
|
|
err = app3.Start(context.Background())
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "sql migrate error")
|
|
assert.False(t, app3.IsRunning())
|
|
}
|
|
|
|
// newGateSource builds a configuration source whose only key decides the test gates.
|
|
func newGateSource(enabled bool) *mapSource {
|
|
return &mapSource{
|
|
values: map[string]any{"gate.enabled": enabled},
|
|
env: map[string]string{},
|
|
}
|
|
}
|
|
|
|
func TestAppPrepareResolvesThenGatesDuringReconcile(t *testing.T) {
|
|
primary := &gatedPlugin{name: "cache", enabled: true}
|
|
fallback := &gatedPlugin{name: "cache_memory", enabled: false}
|
|
|
|
app := core.NewApp(core.WithConfigSource(newGateSource(true)))
|
|
app.Use(primary, fallback)
|
|
require.NoError(t, app.Prepare())
|
|
|
|
cacheFiber, ok := app.Fiber("cache")
|
|
require.True(t, ok)
|
|
require.Equal(t, core.FiberPending, cacheFiber.State(), "Prepare only builds the resolution barrier")
|
|
assert.True(t, app.Context().Config().Resolved())
|
|
|
|
require.NoError(t, app.Reconcile())
|
|
|
|
assert.Equal(t, core.FiberActive, cacheFiber.State())
|
|
|
|
memoryFiber, ok := app.Fiber("cache_memory")
|
|
require.True(t, ok)
|
|
assert.Equal(t, core.FiberSkipped, memoryFiber.State())
|
|
assert.False(t, fallback.applied, "the gated-out provider must never reach Apply")
|
|
}
|
|
|
|
func TestAppGatesPluginsMountedAfterPrepare(t *testing.T) {
|
|
app := core.NewApp(core.WithConfigSource(newGateSource(true)))
|
|
require.NoError(t, app.Prepare())
|
|
|
|
late := &gatedPlugin{name: "cache_memory", enabled: false}
|
|
app.Use(late)
|
|
require.NoError(t, app.Reconcile())
|
|
|
|
fiber, ok := app.Fiber("cache_memory")
|
|
require.True(t, ok)
|
|
assert.Equal(t, core.FiberSkipped, fiber.State(),
|
|
"plugins mounted after Prepare must still be gated")
|
|
}
|
|
|
|
func TestAppApplyPluginsGatesImplicitly(t *testing.T) {
|
|
app := core.NewApp(core.WithConfigSource(newGateSource(false)))
|
|
app.Use(&gatedPlugin{name: "cache", enabled: true})
|
|
|
|
require.NoError(t, app.ApplyPlugins())
|
|
|
|
fiber, ok := app.Fiber("cache")
|
|
require.True(t, ok)
|
|
assert.Equal(t, core.FiberSkipped, fiber.State(),
|
|
"ApplyPlugins must resolve and gate without an explicit Prepare call")
|
|
}
|
|
|
|
func TestAppPrepareReportsConfigurationErrors(t *testing.T) {
|
|
src := &mapSource{
|
|
values: map[string]any{"gate.enabled": "yes"},
|
|
env: map[string]string{},
|
|
}
|
|
app := core.NewApp(core.WithConfigSource(src))
|
|
app.Use(&gatedPlugin{name: "cache", enabled: true})
|
|
|
|
err := app.Prepare()
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "gate.enabled")
|
|
}
|
|
|
|
func TestAppGatedPluginWithoutConfigSourceFailsFast(t *testing.T) {
|
|
app := core.NewApp()
|
|
app.Use(&gatedPlugin{name: "cache", enabled: true})
|
|
|
|
err := app.ApplyPlugins()
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "cache")
|
|
assert.Contains(t, err.Error(), "ConfigSource")
|
|
}
|
|
|
|
func TestAppSetShutdownTimeoutIgnoresNonPositive(t *testing.T) {
|
|
app := core.NewApp()
|
|
|
|
app.SetShutdownTimeout(0)
|
|
assert.Equal(t, 10*time.Second, app.ShutdownTimeout(), "zero must not shrink the kernel fallback")
|
|
|
|
app.SetShutdownTimeout(45 * time.Second)
|
|
assert.Equal(t, 45*time.Second, app.ShutdownTimeout())
|
|
}
|
|
|
|
func TestWithMigrationBaselineVisibleAfterPrepare(t *testing.T) {
|
|
var called bool
|
|
fn := func(*core.Context) error {
|
|
called = true
|
|
return nil
|
|
}
|
|
|
|
app := core.NewApp(core.WithMigrationBaseline(fn))
|
|
require.Nil(t, app.Context().MigrationBaseline(), "baseline must be copied during Prepare")
|
|
|
|
require.NoError(t, app.Prepare())
|
|
|
|
got := app.Context().MigrationBaseline()
|
|
require.NotNil(t, got, "Prepare must copy the baseline onto the root Context")
|
|
require.NoError(t, got(app.Context()))
|
|
assert.True(t, called)
|
|
}
|
|
|
|
func TestWithMigrationBaselineRunsBeforeEngineMigrate(t *testing.T) {
|
|
var order []string
|
|
engine := core.MigrationRunner(func(ctx *core.Context, _ []extpoints.MigrationEntry) error {
|
|
order = append(order, "engine")
|
|
if ctx.MigrationBaseline() == nil {
|
|
t.Fatal("baseline must be visible on context inside Migrate")
|
|
}
|
|
return ctx.MigrationBaseline()(ctx)
|
|
})
|
|
|
|
sqlFS := fstest.MapFS{
|
|
"migrations/001_init.sql": &fstest.MapFile{Data: []byte("-- +goose Up\nSELECT 1;\n")},
|
|
}
|
|
app := core.NewApp(
|
|
core.WithMigrationEngine(engine),
|
|
core.WithMigrationBaseline(func(*core.Context) error {
|
|
order = append(order, "baseline")
|
|
return nil
|
|
}),
|
|
core.WithPlugins(&appMockPlugin{
|
|
name: "t",
|
|
applyFn: func(ctx *core.Context) error {
|
|
ctx.Migrations().Register("t", sqlFS)
|
|
return nil
|
|
},
|
|
}),
|
|
)
|
|
|
|
require.NoError(t, app.Start(context.Background()))
|
|
defer func() { _ = app.Stop(context.Background()) }()
|
|
|
|
assert.Equal(t, []string{"engine", "baseline"}, order)
|
|
}
|
|
|
|
func TestWithMigrationBaselineNilByDefault(t *testing.T) {
|
|
app := core.NewApp()
|
|
require.NoError(t, app.Prepare())
|
|
assert.Nil(t, app.Context().MigrationBaseline())
|
|
}
|