go/tests/core_lifecycle_test.go
Snider 8199727537 feat: restructure Core as unified struct with DTO pattern
Complete architectural overhaul of pkg/core:
- All subsystem types renamed to idiomatic Go (no stutter)
- Core struct: App, Embed, Fs, Config, ErrPan, ErrLog, Cli, Service, Lock, Ipc, I18n
- Exports consolidated in core.go, contracts/options in contract.go
- Service() unified get/register: c.Service(), c.Service("name"), c.Service("name", svc)
- Lock() named mutex map: c.Lock("srv"), c.Lock("ipc")
- Error system: Err/ErrLog/ErrPan + Log/LogErr/LogPan (shared ErrSink interface)
- CoreCommand with optional description (i18n resolves from command path)
- Tests moved to tests/ directory (black-box package core_test)
- Removed: ServiceFor/MustServiceFor, global instance, Display/Workspace/Crypt interfaces
- New files: app.go, fs.go, ipc.go, lock.go, i18n.go, task.go, runtime.go, contract.go

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 09:12:29 +00:00

165 lines
3.7 KiB
Go

package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
type MockStartable struct {
started bool
err error
}
func (m *MockStartable) OnStartup(ctx context.Context) error {
m.started = true
return m.err
}
type MockStoppable struct {
stopped bool
err error
}
func (m *MockStoppable) OnShutdown(ctx context.Context) error {
m.stopped = true
return m.err
}
type MockLifecycle struct {
MockStartable
MockStoppable
}
func TestCore_LifecycleInterfaces(t *testing.T) {
c, err := New()
assert.NoError(t, err)
startable := &MockStartable{}
stoppable := &MockStoppable{}
lifecycle := &MockLifecycle{}
// Register services
err = c.RegisterService("startable", startable)
assert.NoError(t, err)
err = c.RegisterService("stoppable", stoppable)
assert.NoError(t, err)
err = c.RegisterService("lifecycle", lifecycle)
assert.NoError(t, err)
// Startup
err = c.ServiceStartup(context.Background(), nil)
assert.NoError(t, err)
assert.True(t, startable.started)
assert.True(t, lifecycle.started)
assert.False(t, stoppable.stopped)
// Shutdown
err = c.ServiceShutdown(context.Background())
assert.NoError(t, err)
assert.True(t, stoppable.stopped)
assert.True(t, lifecycle.stopped)
}
type MockLifecycleWithLog struct {
id string
log *[]string
}
func (m *MockLifecycleWithLog) OnStartup(ctx context.Context) error {
*m.log = append(*m.log, "start-"+m.id)
return nil
}
func (m *MockLifecycleWithLog) OnShutdown(ctx context.Context) error {
*m.log = append(*m.log, "stop-"+m.id)
return nil
}
func TestCore_LifecycleOrder(t *testing.T) {
c, err := New()
assert.NoError(t, err)
var callOrder []string
s1 := &MockLifecycleWithLog{id: "1", log: &callOrder}
s2 := &MockLifecycleWithLog{id: "2", log: &callOrder}
err = c.RegisterService("s1", s1)
assert.NoError(t, err)
err = c.RegisterService("s2", s2)
assert.NoError(t, err)
// Startup
err = c.ServiceStartup(context.Background(), nil)
assert.NoError(t, err)
assert.Equal(t, []string{"start-1", "start-2"}, callOrder)
// Reset log
callOrder = nil
// Shutdown
err = c.ServiceShutdown(context.Background())
assert.NoError(t, err)
assert.Equal(t, []string{"stop-2", "stop-1"}, callOrder)
}
func TestCore_LifecycleErrors(t *testing.T) {
c, err := New()
assert.NoError(t, err)
s1 := &MockStartable{err: assert.AnError}
s2 := &MockStoppable{err: assert.AnError}
_ = c.RegisterService("s1", s1)
_ = c.RegisterService("s2", s2)
err = c.ServiceStartup(context.Background(), nil)
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
err = c.ServiceShutdown(context.Background())
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestCore_LifecycleErrors_Aggregated(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// Register action that fails
c.RegisterAction(func(c *Core, msg Message) error {
if _, ok := msg.(ActionServiceStartup); ok {
return errors.New("startup action error")
}
if _, ok := msg.(ActionServiceShutdown); ok {
return errors.New("shutdown action error")
}
return nil
})
// Register service that fails
s1 := &MockStartable{err: errors.New("startup service error")}
s2 := &MockStoppable{err: errors.New("shutdown service error")}
err = c.RegisterService("s1", s1)
assert.NoError(t, err)
err = c.RegisterService("s2", s2)
assert.NoError(t, err)
// Startup
err = c.ServiceStartup(context.Background(), nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "startup action error")
assert.Contains(t, err.Error(), "startup service error")
// Shutdown
err = c.ServiceShutdown(context.Background())
assert.Error(t, err)
assert.Contains(t, err.Error(), "shutdown action error")
assert.Contains(t, err.Error(), "shutdown service error")
}