fix unit tests and error messages

This commit is contained in:
Elijah Duffy
2025-06-04 14:03:53 -07:00
parent c3c8a2cafc
commit 76d6216a9c
3 changed files with 137 additions and 117 deletions

View File

@@ -87,6 +87,9 @@ func (app *Lifecycle) WithLogger(logger *slog.Logger) *Lifecycle {
// Logger returns the logger for the lifecycle. // Logger returns the logger for the lifecycle.
func (app *Lifecycle) Logger() *slog.Logger { func (app *Lifecycle) Logger() *slog.Logger {
if app == nil {
panic("lifecycle is nil, cannot get logger")
}
if app.opts.Logger == nil { if app.opts.Logger == nil {
app.opts.Logger = slog.Default() app.opts.Logger = slog.Default()
} }
@@ -182,12 +185,16 @@ func (app *Lifecycle) require(logger *slog.Logger, unique bool, modules ...*Modu
// Check if the module has already been set up // Check if the module has already been set up
if _, ok := app.setupTracker[mod.name]; ok { if _, ok := app.setupTracker[mod.name]; ok {
if unique { if unique {
return fmt.Errorf("module %s is already set up, cannot require it again", mod) return fmt.Errorf("module %s is already set up, cannot require again", mod)
} }
app.Logger().Warn("module already set up, ignoring", "module", mod) app.Logger().Warn("module already set up, ignoring", "module", mod)
// Mark duplicate module as loaded
mod.loaded = true mod.loaded = true
mod.lifecycle = app
mod.logger = logger mod.logger = logger
continue continue
} }

View File

@@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"log/slog" "log/slog"
"os" "os"
@@ -46,6 +47,27 @@ func TestNewLifecycle(t *testing.T) {
}, "expected panic on duplicate module names") }, "expected panic on duplicate module names")
} }
func TestLifecycleToFromContext(t *testing.T) {
assert := assert.New(t)
// Create a Lifecycle instance
lc := NewLifecycle()
// Add the Lifecycle to the context
ctx := LifecycleToContext(context.Background(), lc)
// Retrieve the Lifecycle from the context
retrievedLC := LifecycleFromContext(ctx)
assert.NotNil(retrievedLC, "expected Lifecycle to be retrieved from context")
assert.Equal(lc, retrievedLC, "expected retrieved Lifecycle to match original")
// Test with nil Lifecycle
nilCtx := LifecycleToContext(context.Background(), nil)
retrievedNilLC := LifecycleFromContext(nilCtx)
assert.Nil(retrievedNilLC, "expected nil Lifecycle to return nil from context")
}
func TestLifecycle_WithOpts(t *testing.T) { func TestLifecycle_WithOpts(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
@@ -65,6 +87,10 @@ func TestLifecycle_WithLogger(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
lc.WithLogger(logger) lc.WithLogger(logger)
assert.Equal(logger, lc.Logger(), "expected Lifecycle to have the provided logger") assert.Equal(logger, lc.Logger(), "expected Lifecycle to have the provided logger")
assert.PanicsWithValue("logger cannot be nil", func() {
lc.WithLogger(nil)
}, "expected panic when setting nil logger")
} }
func TestLifecycle_Logger(t *testing.T) { func TestLifecycle_Logger(t *testing.T) {
@@ -77,6 +103,12 @@ func TestLifecycle_Logger(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
lc.opts.Logger = logger lc.opts.Logger = logger
assert.Equal(logger, lc.Logger()) assert.Equal(logger, lc.Logger())
// Test with nil Lifecycle
assert.PanicsWithValue("lifecycle is nil, cannot get logger", func() {
var nilLC *Lifecycle
nilLC.Logger()
}, "expected panic when calling Logger on nil Lifecycle")
} }
func TestLifecycle_Setup(t *testing.T) { func TestLifecycle_Setup(t *testing.T) {
@@ -225,15 +257,17 @@ func TestLifecycle_Teardown(t *testing.T) {
} }
func TestLifecycle_Require(t *testing.T) { func TestLifecycle_Require(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle() lc := NewLifecycle()
mod := NewModule("module1", ModuleOpts{
Setup: func(m *Module) error { return nil }, // Test that duplicate modules are allowed
}) mod1 := NewModule("module1", ModuleOpts{})
err := lc.Require(mod) mod2 := NewModule("module1", ModuleOpts{})
assert.NoError(t, err, "expected Require to succeed") err := lc.Require(mod1, mod2)
assert.Contains(t, lc.setupTracker, mod.name, "expected module to be marked as set up") assert.NoError(err, "expected Require to succeed with duplicate modules")
assert.Nil(t, mod.logger, "expected module logger to be nil") assert.Len(lc.modules, 1, "expected only one instance of the module to be added")
assert.Equal(t, lc.Logger(), mod.Logger(), "expected module logger to match lifecycle logger") assert.Equal(lc.Logger(), mod1.Logger(), "expected module logger to match lifecycle logger")
} }
func TestLifecycle_RequireUnique(t *testing.T) { func TestLifecycle_RequireUnique(t *testing.T) {
@@ -241,56 +275,77 @@ func TestLifecycle_RequireUnique(t *testing.T) {
lc := NewLifecycle() lc := NewLifecycle()
mod := NewModule("module1", ModuleOpts{}) mod := NewModule("module1", ModuleOpts{})
err := lc.RequireUnique(mod) err := lc.RequireUnique(mod, mod)
assert.NoError(err, "expected RequireL to succeed")
assert.Nil(mod.logger, "expected module logger to be nil")
// Requiring the same module again should fail
err = lc.RequireUnique(mod)
assert.Error(err, "expected RequireUnique to fail on duplicate module") assert.Error(err, "expected RequireUnique to fail on duplicate module")
} }
func TestLifecycle_RequireL(t *testing.T) { func TestLifecycle_RequireL(t *testing.T) {
assert := assert.New(t)
// Create a logger for the test cases // Create a logger for the test cases
logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
lc := NewLifecycle()
// Test that duplicate modules are allowed with RequireL
mod1 := NewModule("module1", ModuleOpts{})
mod2 := NewModule("module1", ModuleOpts{})
err := lc.RequireL(logger, mod1, mod2)
assert.NoError(err, "expected RequireL to succeed with duplicate modules")
assert.Len(lc.modules, 1, "expected only one instance of the module to be added")
assert.Equal(logger, mod1.Logger(), "expected module logger to match provided logger")
// Test with nil logger
err = lc.RequireL(nil, mod1)
assert.Error(err, "expected RequireL to fail with nil logger")
}
func TestLifecycle_RequireUniqueL(t *testing.T) {
assert := assert.New(t)
// Create a logger for the test cases
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
lc := NewLifecycle()
// Test that duplicate modules are not allowed with RequireUniqueL
mod1 := NewModule("module1", ModuleOpts{})
mod2 := NewModule("module1", ModuleOpts{})
err := lc.RequireUniqueL(logger, mod1, mod2)
assert.Error(err, "expected RequireUniqueL to fail on duplicate module")
assert.Contains(err.Error(), "cannot require again", "expected error message to match")
// Test with nil logger
err = lc.RequireUniqueL(nil, mod1)
assert.Error(err, "expected RequireUniqueL to fail with nil logger")
}
func TestLifecycle_require(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
modules []*Module logger *slog.Logger
existingModules []*Module unique bool
expectedErr string modules []*Module
expectedErr string
length int
}{ }{
{ {
name: "no modules", name: "no modules",
expectedErr: "no modules to require", expectedErr: "no modules to require",
}, },
{ {
name: "nil module", name: "nil module",
modules: []*Module{nil},
expectedErr: "is nil",
},
{
name: "basic module",
modules: []*Module{ modules: []*Module{
NewModule("testModule", ModuleOpts{ NewModule("", ModuleOpts{}),
Setup: func(m *Module) error { nil,
if m.name != "testModule" {
return errors.New("module name mismatch")
}
return nil
},
}),
}, },
expectedErr: "module 1 is nil",
}, },
{ {
name: "several modules", name: "basic modules",
modules: []*Module{ modules: []*Module{
NewModule("module1", ModuleOpts{ NewModule("module1", ModuleOpts{}),
Setup: func(m *Module) error { return nil }, NewModule("module2", ModuleOpts{}),
}),
NewModule("module2", ModuleOpts{
Setup: func(m *Module) error { return nil },
}),
}, },
}, },
{ {
@@ -300,21 +355,7 @@ func TestLifecycle_RequireL(t *testing.T) {
Setup: func(m *Module) error { return errors.New("setup failed") }, Setup: func(m *Module) error { return errors.New("setup failed") },
}), }),
}, },
expectedErr: "setup failed", expectedErr: "error setting up required module",
},
{
name: "module with dependency",
modules: []*Module{
NewModule("dependentModule", ModuleOpts{
Setup: func(m *Module) error { return nil },
Depends: []string{"dependencyModule"},
}),
},
existingModules: []*Module{
NewModule("dependencyModule", ModuleOpts{
Setup: func(m *Module) error { return nil },
}),
},
}, },
{ {
name: "duplicate module names", name: "duplicate module names",
@@ -322,6 +363,16 @@ func TestLifecycle_RequireL(t *testing.T) {
NewModule("duplicateModule", ModuleOpts{}), NewModule("duplicateModule", ModuleOpts{}),
NewModule("duplicateModule", ModuleOpts{}), NewModule("duplicateModule", ModuleOpts{}),
}, },
length: 1, // Expect only one instance to be added
},
{
name: "duplicate module names with unique",
unique: true,
modules: []*Module{
NewModule("duplicateModule", ModuleOpts{}),
NewModule("duplicateModule", ModuleOpts{}),
},
expectedErr: "cannot require again",
}, },
} }
@@ -329,75 +380,34 @@ func TestLifecycle_RequireL(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
lc := NewLifecycle(tc.existingModules...) lc := NewLifecycle()
err := lc.require(tc.logger, tc.unique, tc.modules...)
err := lc.RequireL(logger, tc.modules...)
if tc.expectedErr == "" { if tc.expectedErr == "" {
assert.NoError(err, "expected RequireL to succeed") assert.NoError(err, "expected require to succeed")
for i, mod := range tc.modules {
expectedLength := tc.length
if expectedLength == 0 {
expectedLength = len(tc.modules)
}
assert.Equal(expectedLength, len(lc.modules), "expected all modules to be added to lifecycle")
for _, mod := range tc.modules {
if mod != nil { if mod != nil {
_, ok := lc.setupTracker[mod.name] _, ok := lc.setupTracker[mod.name]
assert.Truef(ok, "expected module %d %s to be marked as set up", i, mod) assert.Truef(ok, "expected module %s to be marked as set up", mod)
assert.Truef(mod.loaded, "expected module %d %s to be loaded", i, mod) assert.Truef(mod.loaded, "expected module %s to be loaded", mod)
assert.Equalf(logger, mod.logger, "expected module %d %s logger to match", i, mod) assert.Equalf(lc, mod.lifecycle, "expected module %s lifecycle to match", mod)
if tc.logger != nil {
assert.Equalf(tc.logger, mod.Logger(), "expected module %s logger to match", mod)
} else {
assert.Equalf(lc.Logger(), mod.Logger(), "expected module %s logger to match lifecycle logger", mod)
}
} }
} }
} else { } else {
assert.Error(err, "expected RequireL to fail") assert.Error(err, "expected require to fail")
assert.Contains(err.Error(), tc.expectedErr, "expected error message to match")
}
})
}
}
func TestLifecycle_RequireUniqueL(t *testing.T) {
// Create a logger for the test cases
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
cases := []struct {
name string
modules []*Module
existingModules []string
expectedErr string
}{
{
name: "no existing modules",
modules: []*Module{
NewModule("module1", ModuleOpts{}),
},
},
{
name: "module already set up",
modules: []*Module{
NewModule("module1", ModuleOpts{}),
NewModule("module2", ModuleOpts{}),
},
existingModules: []string{"module1"},
expectedErr: "module [module1] is already set up, cannot require it again",
},
{
name: "nil module",
modules: []*Module{nil},
expectedErr: "is nil",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle()
// Fake existing modules
for _, modName := range tc.existingModules {
lc.setupTracker[modName] = 0 // Mark as set up
}
err := lc.RequireUniqueL(logger, tc.modules...)
if tc.expectedErr == "" {
assert.NoError(err, "expected RequireUniqueL to succeed")
} else {
assert.Error(err, "expected RequireUniqueL to fail")
assert.Contains(err.Error(), tc.expectedErr, "expected error message to match") assert.Contains(err.Error(), tc.expectedErr, "expected error message to match")
} }
}) })

View File

@@ -59,6 +59,9 @@ func (s *Module) Logger() *slog.Logger {
if s.logger != nil { if s.logger != nil {
return s.logger return s.logger
} }
if s.lifecycle == nil {
panic("module has no lifecycle, cannot get logger")
}
return s.lifecycle.Logger() return s.lifecycle.Logger()
} }