feat: restore functional option pattern for New()

New() returns Result, accepts CoreOption functionals.
Restores v0.3.3 service registration contract:
- WithService(factory func(*Core) Result) — service factory receives Core
- WithOptions(Options) — key-value configuration
- WithServiceLock() — immutable after construction

Services registered in New() form the application conclave with
shared IPC access. Each Core instance has its own bus scope.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-24 16:23:33 +00:00
parent 927f830be4
commit c45b22849f
19 changed files with 255 additions and 162 deletions

View file

@ -10,18 +10,18 @@ import (
// --- App ---
func TestApp_Good(t *testing.T) {
c := New(Options{{Key: "name", Value: "myapp"}})
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
assert.Equal(t, "myapp", c.App().Name)
}
func TestApp_Empty_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotNil(t, c.App())
assert.Equal(t, "", c.App().Name)
}
func TestApp_Runtime_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.App().Runtime = &struct{ Name string }{Name: "wails"}
assert.NotNil(t, c.App().Runtime)
}

View file

@ -11,23 +11,23 @@ import (
// --- Cli Surface ---
func TestCli_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotNil(t, c.Cli())
}
func TestCli_Banner_Good(t *testing.T) {
c := New(Options{{Key: "name", Value: "myapp"}})
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
assert.Equal(t, "myapp", c.Cli().Banner())
}
func TestCli_SetBanner_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Cli().SetBanner(func(_ *Cli) string { return "Custom Banner" })
assert.Equal(t, "Custom Banner", c.Cli().Banner())
}
func TestCli_Run_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
executed := false
c.Command("hello", Command{Action: func(_ Options) Result {
executed = true
@ -40,7 +40,7 @@ func TestCli_Run_Good(t *testing.T) {
}
func TestCli_Run_Nested_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
executed := false
c.Command("deploy/to/homelab", Command{Action: func(_ Options) Result {
executed = true
@ -52,7 +52,7 @@ func TestCli_Run_Nested_Good(t *testing.T) {
}
func TestCli_Run_WithFlags_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
var received Options
c.Command("serve", Command{Action: func(opts Options) Result {
received = opts
@ -64,20 +64,20 @@ func TestCli_Run_WithFlags_Good(t *testing.T) {
}
func TestCli_Run_NoCommand_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Cli().Run()
assert.False(t, r.OK)
}
func TestCli_PrintHelp_Good(t *testing.T) {
c := New(Options{{Key: "name", Value: "myapp"}})
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
c.Command("serve", Command{Action: func(_ Options) Result { return Result{OK: true} }})
c.Cli().PrintHelp()
}
func TestCli_SetOutput_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
var buf bytes.Buffer
c.Cli().SetOutput(&buf)
c.Cli().Print("hello %s", "world")

View file

@ -10,7 +10,7 @@ import (
// --- Command DTO ---
func TestCommand_Register_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Command("deploy", Command{Action: func(_ Options) Result {
return Result{Value: "deployed", OK: true}
}})
@ -18,7 +18,7 @@ func TestCommand_Register_Good(t *testing.T) {
}
func TestCommand_Get_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
r := c.Command("deploy")
assert.True(t, r.OK)
@ -26,13 +26,13 @@ func TestCommand_Get_Good(t *testing.T) {
}
func TestCommand_Get_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Command("nonexistent")
assert.False(t, r.OK)
}
func TestCommand_Run_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("greet", Command{Action: func(opts Options) Result {
return Result{Value: Concat("hello ", opts.String("name")), OK: true}
}})
@ -43,7 +43,7 @@ func TestCommand_Run_Good(t *testing.T) {
}
func TestCommand_Run_NoAction_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("empty", Command{Description: "no action"})
cmd := c.Command("empty").Value.(*Command)
r := cmd.Run(Options{})
@ -53,7 +53,7 @@ func TestCommand_Run_NoAction_Good(t *testing.T) {
// --- Nested Commands ---
func TestCommand_Nested_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("deploy/to/homelab", Command{Action: func(_ Options) Result {
return Result{Value: "deployed to homelab", OK: true}
}})
@ -67,7 +67,7 @@ func TestCommand_Nested_Good(t *testing.T) {
}
func TestCommand_Paths_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
c.Command("serve", Command{Action: func(_ Options) Result { return Result{OK: true} }})
c.Command("deploy/to/homelab", Command{Action: func(_ Options) Result { return Result{OK: true} }})
@ -82,21 +82,21 @@ func TestCommand_Paths_Good(t *testing.T) {
// --- I18n Key Derivation ---
func TestCommand_I18nKey_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("deploy/to/homelab", Command{})
cmd := c.Command("deploy/to/homelab").Value.(*Command)
assert.Equal(t, "cmd.deploy.to.homelab.description", cmd.I18nKey())
}
func TestCommand_I18nKey_Custom_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("deploy", Command{Description: "custom.deploy.key"})
cmd := c.Command("deploy").Value.(*Command)
assert.Equal(t, "custom.deploy.key", cmd.I18nKey())
}
func TestCommand_I18nKey_Simple_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("serve", Command{})
cmd := c.Command("serve").Value.(*Command)
assert.Equal(t, "cmd.serve.description", cmd.I18nKey())
@ -105,7 +105,7 @@ func TestCommand_I18nKey_Simple_Good(t *testing.T) {
// --- Lifecycle ---
func TestCommand_Lifecycle_NoImpl_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("serve", Command{Action: func(_ Options) Result {
return Result{Value: "running", OK: true}
}})
@ -153,7 +153,7 @@ func (l *testLifecycle) Signal(sig string) Result {
}
func TestCommand_Lifecycle_WithImpl_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
lc := &testLifecycle{}
c.Command("daemon", Command{Lifecycle: lc})
cmd := c.Command("daemon").Value.(*Command)
@ -177,14 +177,14 @@ func TestCommand_Lifecycle_WithImpl_Good(t *testing.T) {
}
func TestCommand_Duplicate_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
r := c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
assert.False(t, r.OK)
}
func TestCommand_InvalidPath_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.False(t, c.Command("/leading", Command{}).OK)
assert.False(t, c.Command("trailing/", Command{}).OK)
assert.False(t, c.Command("double//slash", Command{}).OK)
@ -193,7 +193,7 @@ func TestCommand_InvalidPath_Bad(t *testing.T) {
// --- Cli Run with Lifecycle ---
func TestCli_Run_Lifecycle_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
lc := &testLifecycle{}
c.Command("serve", Command{Lifecycle: lc})
r := c.Cli().Run("serve")
@ -202,7 +202,7 @@ func TestCli_Run_Lifecycle_Good(t *testing.T) {
}
func TestCli_Run_NoActionNoLifecycle_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Command("empty", Command{})
r := c.Cli().Run("empty")
assert.False(t, r.OK)
@ -211,7 +211,7 @@ func TestCli_Run_NoActionNoLifecycle_Bad(t *testing.T) {
// --- Empty path ---
func TestCommand_EmptyPath_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Command("", Command{})
assert.False(t, r.OK)
}

View file

@ -10,7 +10,7 @@ import (
// --- Config ---
func TestConfig_SetGet_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Config().Set("api_url", "https://api.lthn.ai")
c.Config().Set("max_agents", 5)
@ -20,14 +20,14 @@ func TestConfig_SetGet_Good(t *testing.T) {
}
func TestConfig_Get_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Config().Get("missing")
assert.False(t, r.OK)
assert.Nil(t, r.Value)
}
func TestConfig_TypedAccessors_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Config().Set("url", "https://lthn.ai")
c.Config().Set("port", 8080)
c.Config().Set("debug", true)
@ -38,7 +38,7 @@ func TestConfig_TypedAccessors_Good(t *testing.T) {
}
func TestConfig_TypedAccessors_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
// Missing keys return zero values
assert.Equal(t, "", c.Config().String("missing"))
assert.Equal(t, 0, c.Config().Int("missing"))
@ -48,7 +48,7 @@ func TestConfig_TypedAccessors_Bad(t *testing.T) {
// --- Feature Flags ---
func TestConfig_Features_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Config().Enable("dark-mode")
c.Config().Enable("beta")
@ -58,7 +58,7 @@ func TestConfig_Features_Good(t *testing.T) {
}
func TestConfig_Features_Disable_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Config().Enable("feature")
assert.True(t, c.Config().Enabled("feature"))
@ -67,14 +67,14 @@ func TestConfig_Features_Disable_Good(t *testing.T) {
}
func TestConfig_Features_CaseSensitive(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Config().Enable("Feature")
assert.True(t, c.Config().Enabled("Feature"))
assert.False(t, c.Config().Enabled("feature"))
}
func TestConfig_EnabledFeatures_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Config().Enable("a")
c.Config().Enable("b")
c.Config().Enable("c")

View file

@ -66,12 +66,28 @@ type ActionTaskCompleted struct {
// --- Constructor ---
// New creates a Core instance.
// CoreOption is a functional option applied during Core construction.
// Returns Result — if !OK, New() stops and returns the error.
//
// c := core.New(core.Options{
// {Key: "name", Value: "myapp"},
// })
func New(opts ...Options) *Core {
// core.New(
// core.WithService(agentic.Register),
// core.WithService(monitor.Register),
// core.WithServiceLock(),
// )
type CoreOption func(*Core) Result
// New initialises a Core instance by applying options in order.
// Services registered here form the application conclave — they share
// IPC access and participate in the lifecycle (ServiceStartup/ServiceShutdown).
//
// r := core.New(
// core.WithOptions(core.Options{{Key: "name", Value: "myapp"}}),
// core.WithService(auth.Register),
// core.WithServiceLock(),
// )
// if !r.OK { log.Fatal(r.Value) }
// c := r.Value.(*Core)
func New(opts ...CoreOption) Result {
c := &Core{
app: &App{},
data: &Data{},
@ -88,19 +104,54 @@ func New(opts ...Options) *Core {
commands: &commandRegistry{commands: make(map[string]*Command)},
}
c.context, c.cancel = context.WithCancel(context.Background())
c.cli = &Cli{core: c}
if len(opts) > 0 {
cp := make(Options, len(opts[0]))
copy(cp, opts[0])
c.options = &cp
name := cp.String("name")
if name != "" {
c.app.Name = name
for _, opt := range opts {
if r := opt(c); !r.OK {
return r
}
}
// Init Cli surface with Core reference
c.cli = &Cli{core: c}
return c
return Result{c, true}
}
// WithOptions applies key-value configuration to Core.
//
// core.WithOptions(core.Options{{Key: "name", Value: "myapp"}})
func WithOptions(opts Options) CoreOption {
return func(c *Core) Result {
c.options = &opts
if name := opts.String("name"); name != "" {
c.app.Name = name
}
return Result{OK: true}
}
}
// WithService registers a service via its factory function.
// The factory receives *Core so the service can wire IPC handlers
// and access other subsystems during construction.
// Service name is auto-discovered from the package path.
// If the service implements HandleIPCEvents, it is auto-registered.
//
// core.WithService(agentic.Register)
// core.WithService(display.Register(nil))
func WithService(factory func(*Core) Result) CoreOption {
return func(c *Core) Result {
return factory(c)
}
}
// WithServiceLock prevents further service registration after construction.
//
// core.New(
// core.WithService(auth.Register),
// core.WithServiceLock(),
// )
func WithServiceLock() CoreOption {
return func(c *Core) Result {
c.LockEnable()
c.LockApply()
return Result{OK: true}
}
}

View file

@ -1,6 +1,7 @@
package core_test
import (
"context"
"testing"
. "dappco.re/go/core"
@ -10,26 +11,63 @@ import (
// --- New ---
func TestNew_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotNil(t, c)
}
func TestNew_WithOptions_Good(t *testing.T) {
c := New(Options{{Key: "name", Value: "myapp"}})
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
assert.NotNil(t, c)
assert.Equal(t, "myapp", c.App().Name)
}
func TestNew_WithOptions_Bad(t *testing.T) {
// Empty options — should still create a valid Core
c := New(Options{})
c := New(WithOptions(Options{})).Value.(*Core)
assert.NotNil(t, c)
}
func TestNew_WithService_Good(t *testing.T) {
started := false
r := New(
WithOptions(Options{{Key: "name", Value: "myapp"}}),
WithService(func(c *Core) Result {
c.Service("test", Service{
OnStart: func() Result { started = true; return Result{OK: true} },
})
return Result{OK: true}
}),
)
assert.True(t, r.OK)
c := r.Value.(*Core)
svc := c.Service("test")
assert.True(t, svc.OK)
c.ServiceStartup(context.Background(), nil)
assert.True(t, started)
}
func TestNew_WithServiceLock_Good(t *testing.T) {
r := New(
WithService(func(c *Core) Result {
c.Service("allowed", Service{})
return Result{OK: true}
}),
WithServiceLock(),
)
assert.True(t, r.OK)
c := r.Value.(*Core)
// Registration after lock should fail
reg := c.Service("blocked", Service{})
assert.False(t, reg.OK)
}
// --- Accessors ---
func TestAccessors_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotNil(t, c.App())
assert.NotNil(t, c.Data())
assert.NotNil(t, c.Drive())
@ -44,11 +82,11 @@ func TestAccessors_Good(t *testing.T) {
}
func TestOptions_Accessor_Good(t *testing.T) {
c := New(Options{
c := New(WithOptions(Options{
{Key: "name", Value: "testapp"},
{Key: "port", Value: 8080},
{Key: "debug", Value: true},
})
})).Value.(*Core)
opts := c.Options()
assert.NotNil(t, opts)
assert.Equal(t, "testapp", opts.String("name"))
@ -57,7 +95,7 @@ func TestOptions_Accessor_Good(t *testing.T) {
}
func TestOptions_Accessor_Nil(t *testing.T) {
c := New()
c := New().Value.(*Core)
// No options passed — Options() returns nil
assert.Nil(t, c.Options())
}
@ -65,7 +103,7 @@ func TestOptions_Accessor_Nil(t *testing.T) {
// --- Core Error/Log Helpers ---
func TestCore_LogError_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
cause := assert.AnError
r := c.LogError(cause, "test.Operation", "something broke")
assert.False(t, r.OK)
@ -75,7 +113,7 @@ func TestCore_LogError_Good(t *testing.T) {
}
func TestCore_LogWarn_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.LogWarn(assert.AnError, "test.Operation", "heads up")
assert.False(t, r.OK)
_, ok := r.Value.(error)
@ -83,14 +121,14 @@ func TestCore_LogWarn_Good(t *testing.T) {
}
func TestCore_Must_Ugly(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.Panics(t, func() {
c.Must(assert.AnError, "test.Operation", "fatal")
})
}
func TestCore_Must_Nil_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotPanics(t, func() {
c.Must(nil, "test.Operation", "no error")
})

View file

@ -15,7 +15,7 @@ var testFS embed.FS
// --- Data (Embedded Content Mounts) ---
func TestData_New_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().New(Options{
{Key: "name", Value: "test"},
{Key: "source", Value: testFS},
@ -26,7 +26,7 @@ func TestData_New_Good(t *testing.T) {
}
func TestData_New_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().New(Options{{Key: "source", Value: testFS}})
assert.False(t, r.OK)
@ -39,7 +39,7 @@ func TestData_New_Bad(t *testing.T) {
}
func TestData_ReadString_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
r := c.Data().ReadString("app/test.txt")
assert.True(t, r.OK)
@ -47,13 +47,13 @@ func TestData_ReadString_Good(t *testing.T) {
}
func TestData_ReadString_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().ReadString("nonexistent/file.txt")
assert.False(t, r.OK)
}
func TestData_ReadFile_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
r := c.Data().ReadFile("app/test.txt")
assert.True(t, r.OK)
@ -61,7 +61,7 @@ func TestData_ReadFile_Good(t *testing.T) {
}
func TestData_Get_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "brain"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
gr := c.Data().Get("brain")
assert.True(t, gr.OK)
@ -76,13 +76,13 @@ func TestData_Get_Good(t *testing.T) {
}
func TestData_Get_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().Get("nonexistent")
assert.False(t, r.OK)
}
func TestData_Mounts_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "a"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
c.Data().New(Options{{Key: "name", Value: "b"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
mounts := c.Data().Mounts()
@ -90,26 +90,26 @@ func TestData_Mounts_Good(t *testing.T) {
}
func TestEmbed_Legacy_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
assert.NotNil(t, c.Embed())
}
func TestData_List_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}})
r := c.Data().List("app/testdata")
assert.True(t, r.OK)
}
func TestData_List_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().List("nonexistent/path")
assert.False(t, r.OK)
}
func TestData_ListNames_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}})
r := c.Data().ListNames("app/testdata")
assert.True(t, r.OK)
@ -117,14 +117,14 @@ func TestData_ListNames_Good(t *testing.T) {
}
func TestData_Extract_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}})
r := c.Data().Extract("app/testdata", t.TempDir(), nil)
assert.True(t, r.OK)
}
func TestData_Extract_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().Extract("nonexistent/path", t.TempDir(), nil)
assert.False(t, r.OK)
}

View file

@ -10,7 +10,7 @@ import (
// --- Drive (Transport Handles) ---
func TestDrive_New_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Drive().New(Options{
{Key: "name", Value: "api"},
{Key: "transport", Value: "https://api.lthn.ai"},
@ -21,7 +21,7 @@ func TestDrive_New_Good(t *testing.T) {
}
func TestDrive_New_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
// Missing name
r := c.Drive().New(Options{
{Key: "transport", Value: "https://api.lthn.ai"},
@ -30,7 +30,7 @@ func TestDrive_New_Bad(t *testing.T) {
}
func TestDrive_Get_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Drive().New(Options{
{Key: "name", Value: "ssh"},
{Key: "transport", Value: "ssh://claude@10.69.69.165"},
@ -42,20 +42,20 @@ func TestDrive_Get_Good(t *testing.T) {
}
func TestDrive_Get_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Drive().Get("nonexistent")
assert.False(t, r.OK)
}
func TestDrive_Has_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Drive().New(Options{{Key: "name", Value: "mcp"}, {Key: "transport", Value: "mcp://mcp.lthn.sh"}})
assert.True(t, c.Drive().Has("mcp"))
assert.False(t, c.Drive().Has("missing"))
}
func TestDrive_Names_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Drive().New(Options{{Key: "name", Value: "api"}, {Key: "transport", Value: "https://api.lthn.ai"}})
c.Drive().New(Options{{Key: "name", Value: "ssh"}, {Key: "transport", Value: "ssh://claude@10.69.69.165"}})
c.Drive().New(Options{{Key: "name", Value: "mcp"}, {Key: "transport", Value: "mcp://mcp.lthn.sh"}})
@ -67,7 +67,7 @@ func TestDrive_Names_Good(t *testing.T) {
}
func TestDrive_OptionsPreserved_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Drive().New(Options{
{Key: "name", Value: "api"},
{Key: "transport", Value: "https://api.lthn.ai"},

View file

@ -102,7 +102,7 @@ func TestFormatStackTrace_Good(t *testing.T) {
// --- ErrorLog ---
func TestErrorLog_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
cause := errors.New("boom")
r := c.Log().Error(cause, "test.Operation", "something broke")
assert.False(t, r.OK)
@ -110,27 +110,27 @@ func TestErrorLog_Good(t *testing.T) {
}
func TestErrorLog_Nil_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Log().Error(nil, "test.Operation", "no error")
assert.True(t, r.OK)
}
func TestErrorLog_Warn_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
cause := errors.New("warning")
r := c.Log().Warn(cause, "test.Operation", "heads up")
assert.False(t, r.OK)
}
func TestErrorLog_Must_Ugly(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.Panics(t, func() {
c.Log().Must(errors.New("fatal"), "test.Operation", "must fail")
})
}
func TestErrorLog_Must_Nil_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotPanics(t, func() {
c.Log().Must(nil, "test.Operation", "no error")
})
@ -139,7 +139,7 @@ func TestErrorLog_Must_Nil_Good(t *testing.T) {
// --- ErrorPanic ---
func TestErrorPanic_Recover_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
// Should not panic — Recover catches it
assert.NotPanics(t, func() {
defer c.Error().Recover()
@ -148,7 +148,7 @@ func TestErrorPanic_Recover_Good(t *testing.T) {
}
func TestErrorPanic_SafeGo_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
done := make(chan bool, 1)
c.Error().SafeGo(func() {
done <- true
@ -157,7 +157,7 @@ func TestErrorPanic_SafeGo_Good(t *testing.T) {
}
func TestErrorPanic_SafeGo_Panic_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
done := make(chan bool, 1)
c.Error().SafeGo(func() {
defer func() { done <- true }()
@ -202,7 +202,7 @@ func TestErrorPanic_Reports_Good(t *testing.T) {
path := dir + "/crashes.json"
// Create ErrorPanic with file output
c := New()
c := New().Value.(*Core)
// Access internals via a crash that writes to file
// Since ErrorPanic fields are unexported, we test via Recover
_ = c
@ -221,7 +221,7 @@ func TestErrorPanic_CrashFile_Good(t *testing.T) {
// error handling that writes crash reports
// For now, test that Reports handles missing file gracefully
c := New()
c := New().Value.(*Core)
r := c.Error().Reports(5)
assert.False(t, r.OK)
assert.Nil(t, r.Value)
@ -260,13 +260,13 @@ func TestWrap_PreservesCode_Good(t *testing.T) {
}
func TestErrorLog_Warn_Nil_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.LogWarn(nil, "op", "msg")
assert.True(t, r.OK)
}
func TestErrorLog_Error_Nil_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.LogError(nil, "op", "msg")
assert.True(t, r.OK)
}

View file

@ -15,7 +15,7 @@ import (
func TestFs_WriteRead_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "test.txt")
assert.True(t, c.Fs().Write(path, "hello core").OK)
@ -26,21 +26,21 @@ func TestFs_WriteRead_Good(t *testing.T) {
}
func TestFs_Read_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Fs().Read("/nonexistent/path/to/file.txt")
assert.False(t, r.OK)
}
func TestFs_EnsureDir_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "sub", "dir")
assert.True(t, c.Fs().EnsureDir(path).OK)
assert.True(t, c.Fs().IsDir(path))
}
func TestFs_IsDir_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
dir := t.TempDir()
assert.True(t, c.Fs().IsDir(dir))
assert.False(t, c.Fs().IsDir(filepath.Join(dir, "nonexistent")))
@ -49,7 +49,7 @@ func TestFs_IsDir_Good(t *testing.T) {
func TestFs_IsFile_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "test.txt")
c.Fs().Write(path, "data")
assert.True(t, c.Fs().IsFile(path))
@ -59,7 +59,7 @@ func TestFs_IsFile_Good(t *testing.T) {
func TestFs_Exists_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "exists.txt")
c.Fs().Write(path, "yes")
assert.True(t, c.Fs().Exists(path))
@ -69,7 +69,7 @@ func TestFs_Exists_Good(t *testing.T) {
func TestFs_List_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
c.Fs().Write(filepath.Join(dir, "a.txt"), "a")
c.Fs().Write(filepath.Join(dir, "b.txt"), "b")
r := c.Fs().List(dir)
@ -79,7 +79,7 @@ func TestFs_List_Good(t *testing.T) {
func TestFs_Stat_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "stat.txt")
c.Fs().Write(path, "data")
r := c.Fs().Stat(path)
@ -89,7 +89,7 @@ func TestFs_Stat_Good(t *testing.T) {
func TestFs_Open_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "open.txt")
c.Fs().Write(path, "content")
r := c.Fs().Open(path)
@ -99,7 +99,7 @@ func TestFs_Open_Good(t *testing.T) {
func TestFs_Create_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "sub", "created.txt")
r := c.Fs().Create(path)
assert.True(t, r.OK)
@ -112,7 +112,7 @@ func TestFs_Create_Good(t *testing.T) {
func TestFs_Append_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "append.txt")
c.Fs().Write(path, "first")
r := c.Fs().Append(path)
@ -126,7 +126,7 @@ func TestFs_Append_Good(t *testing.T) {
func TestFs_ReadStream_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "stream.txt")
c.Fs().Write(path, "streamed")
r := c.Fs().ReadStream(path)
@ -136,7 +136,7 @@ func TestFs_ReadStream_Good(t *testing.T) {
func TestFs_WriteStream_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "sub", "ws.txt")
r := c.Fs().WriteStream(path)
assert.True(t, r.OK)
@ -147,7 +147,7 @@ func TestFs_WriteStream_Good(t *testing.T) {
func TestFs_Delete_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "delete.txt")
c.Fs().Write(path, "gone")
assert.True(t, c.Fs().Delete(path).OK)
@ -156,7 +156,7 @@ func TestFs_Delete_Good(t *testing.T) {
func TestFs_DeleteAll_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
sub := filepath.Join(dir, "deep", "nested")
c.Fs().EnsureDir(sub)
c.Fs().Write(filepath.Join(sub, "file.txt"), "data")
@ -166,7 +166,7 @@ func TestFs_DeleteAll_Good(t *testing.T) {
func TestFs_Rename_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
old := filepath.Join(dir, "old.txt")
nw := filepath.Join(dir, "new.txt")
c.Fs().Write(old, "data")
@ -177,7 +177,7 @@ func TestFs_Rename_Good(t *testing.T) {
func TestFs_WriteMode_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "secret.txt")
assert.True(t, c.Fs().WriteMode(path, "secret", 0600).OK)
r := c.Fs().Stat(path)
@ -213,39 +213,39 @@ func TestFs_ZeroValue_List_Good(t *testing.T) {
}
func TestFs_Exists_NotFound_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.False(t, c.Fs().Exists("/nonexistent/path/xyz"))
}
// --- Fs path/validatePath edge cases ---
func TestFs_Read_EmptyPath_Ugly(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Fs().Read("")
assert.False(t, r.OK)
}
func TestFs_Write_EmptyPath_Ugly(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Fs().Write("", "data")
assert.False(t, r.OK)
}
func TestFs_Delete_Protected_Ugly(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Fs().Delete("/")
assert.False(t, r.OK)
}
func TestFs_DeleteAll_Protected_Ugly(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Fs().DeleteAll("/")
assert.False(t, r.OK)
}
func TestFs_ReadStream_WriteStream_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c := New().Value.(*Core)
path := filepath.Join(dir, "stream.txt")
c.Fs().Write(path, "streamed")

View file

@ -10,12 +10,12 @@ import (
// --- I18n ---
func TestI18n_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotNil(t, c.I18n())
}
func TestI18n_AddLocales_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Data().New(Options{
{Key: "name", Value: "lang"},
{Key: "source", Value: testFS},
@ -30,7 +30,7 @@ func TestI18n_AddLocales_Good(t *testing.T) {
}
func TestI18n_Locales_Empty_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.I18n().Locales()
assert.True(t, r.OK)
assert.Empty(t, r.Value.([]*Embed))
@ -39,7 +39,7 @@ func TestI18n_Locales_Empty_Good(t *testing.T) {
// --- Translator (no translator registered) ---
func TestI18n_Translate_NoTranslator_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
// Without a translator, Translate returns the key as-is
r := c.I18n().Translate("greeting.hello")
assert.True(t, r.OK)
@ -47,24 +47,24 @@ func TestI18n_Translate_NoTranslator_Good(t *testing.T) {
}
func TestI18n_SetLanguage_NoTranslator_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.I18n().SetLanguage("de")
assert.True(t, r.OK) // no-op without translator
}
func TestI18n_Language_NoTranslator_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.Equal(t, "en", c.I18n().Language())
}
func TestI18n_AvailableLanguages_NoTranslator_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
langs := c.I18n().AvailableLanguages()
assert.Equal(t, []string{"en"}, langs)
}
func TestI18n_Translator_Nil_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.False(t, c.I18n().Translator().OK)
}
@ -82,7 +82,7 @@ func (m *mockTranslator) Language() string { return m.lang }
func (m *mockTranslator) AvailableLanguages() []string { return []string{"en", "de", "fr"} }
func TestI18n_WithTranslator_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
tr := &mockTranslator{lang: "en"}
c.I18n().SetTranslator(tr)

View file

@ -89,7 +89,7 @@ func TestEnv_Unknown(t *testing.T) {
}
func TestEnv_CoreInstance(t *testing.T) {
c := core.New()
c := core.New().Value.(*core.Core)
assert.Equal(t, core.Env("OS"), c.Env("OS"))
assert.Equal(t, core.Env("DIR_HOME"), c.Env("DIR_HOME"))
}

View file

@ -12,7 +12,7 @@ import (
type testMessage struct{ payload string }
func TestAction_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
var received Message
c.RegisterAction(func(_ *Core, msg Message) Result {
received = msg
@ -24,7 +24,7 @@ func TestAction_Good(t *testing.T) {
}
func TestAction_Multiple_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
count := 0
handler := func(_ *Core, _ Message) Result { count++; return Result{OK: true} }
c.RegisterActions(handler, handler, handler)
@ -33,7 +33,7 @@ func TestAction_Multiple_Good(t *testing.T) {
}
func TestAction_None_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
// No handlers registered — should succeed
r := c.ACTION(nil)
assert.True(t, r.OK)
@ -42,7 +42,7 @@ func TestAction_None_Good(t *testing.T) {
// --- IPC: Queries ---
func TestQuery_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.RegisterQuery(func(_ *Core, q Query) Result {
if q == "ping" {
return Result{Value: "pong", OK: true}
@ -55,7 +55,7 @@ func TestQuery_Good(t *testing.T) {
}
func TestQuery_Unhandled_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.RegisterQuery(func(_ *Core, q Query) Result {
return Result{}
})
@ -64,7 +64,7 @@ func TestQuery_Unhandled_Good(t *testing.T) {
}
func TestQueryAll_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.RegisterQuery(func(_ *Core, _ Query) Result {
return Result{Value: "a", OK: true}
})
@ -82,7 +82,7 @@ func TestQueryAll_Good(t *testing.T) {
// --- IPC: Tasks ---
func TestPerform_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.RegisterTask(func(_ *Core, t Task) Result {
if t == "compute" {
return Result{Value: 42, OK: true}

View file

@ -8,28 +8,28 @@ import (
)
func TestLock_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
lock := c.Lock("test")
assert.NotNil(t, lock)
assert.NotNil(t, lock.Mutex)
}
func TestLock_SameName_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
l1 := c.Lock("shared")
l2 := c.Lock("shared")
assert.Equal(t, l1, l2)
}
func TestLock_DifferentName_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
l1 := c.Lock("a")
l2 := c.Lock("b")
assert.NotEqual(t, l1, l2)
}
func TestLockEnable_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Service("early", Service{})
c.LockEnable()
c.LockApply()
@ -39,7 +39,7 @@ func TestLockEnable_Good(t *testing.T) {
}
func TestStartables_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Service("s", Service{OnStart: func() Result { return Result{OK: true} }})
r := c.Startables()
assert.True(t, r.OK)
@ -47,7 +47,7 @@ func TestStartables_Good(t *testing.T) {
}
func TestStoppables_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Service("s", Service{OnStop: func() Result { return Result{OK: true} }})
r := c.Stoppables()
assert.True(t, r.OK)

View file

@ -54,7 +54,7 @@ func TestLog_LevelString_Good(t *testing.T) {
}
func TestLog_CoreLog_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
assert.NotNil(t, c.Log())
}

View file

@ -106,7 +106,11 @@ type ServiceFactory func() Result
// NewWithFactories creates a Runtime with the provided service factories.
func NewWithFactories(app any, factories map[string]ServiceFactory) Result {
c := New(Options{{Key: "name", Value: "core"}})
r := New(WithOptions(Options{{Key: "name", Value: "core"}}))
if !r.OK {
return r
}
c := r.Value.(*Core)
c.app.Runtime = app
names := slices.Sorted(maps.Keys(factories))

View file

@ -16,7 +16,7 @@ type testOpts struct {
}
func TestServiceRuntime_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
opts := testOpts{URL: "https://api.lthn.ai", Timeout: 30}
rt := NewServiceRuntime(c, opts)
@ -102,7 +102,7 @@ func TestRuntime_ServiceShutdown_NilCore_Good(t *testing.T) {
func TestCore_ServiceShutdown_Good(t *testing.T) {
stopped := false
c := New()
c := New().Value.(*Core)
c.Service("test", Service{
OnStart: func() Result { return Result{OK: true} },
OnStop: func() Result { stopped = true; return Result{OK: true} },
@ -114,7 +114,7 @@ func TestCore_ServiceShutdown_Good(t *testing.T) {
}
func TestCore_Context_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.ServiceStartup(context.Background(), nil)
assert.NotNil(t, c.Context())
c.ServiceShutdown(context.Background())

View file

@ -10,26 +10,26 @@ import (
// --- Service Registration ---
func TestService_Register_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Service("auth", Service{})
assert.True(t, r.OK)
}
func TestService_Register_Duplicate_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Service("auth", Service{})
r := c.Service("auth", Service{})
assert.False(t, r.OK)
}
func TestService_Register_Empty_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Service("", Service{})
assert.False(t, r.OK)
}
func TestService_Get_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Service("brain", Service{OnStart: func() Result { return Result{OK: true} }})
r := c.Service("brain")
assert.True(t, r.OK)
@ -37,13 +37,13 @@ func TestService_Get_Good(t *testing.T) {
}
func TestService_Get_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
r := c.Service("nonexistent")
assert.False(t, r.OK)
}
func TestService_Names_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.Service("a", Service{})
c.Service("b", Service{})
names := c.Services()
@ -55,7 +55,7 @@ func TestService_Names_Good(t *testing.T) {
// --- Service Lifecycle ---
func TestService_Lifecycle_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
started := false
stopped := false
c.Service("lifecycle", Service{

View file

@ -13,7 +13,7 @@ import (
// --- PerformAsync ---
func TestPerformAsync_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
var mu sync.Mutex
var result string
@ -37,7 +37,7 @@ func TestPerformAsync_Good(t *testing.T) {
}
func TestPerformAsync_Progress_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.RegisterTask(func(_ *Core, task Task) Result {
return Result{OK: true}
})
@ -48,7 +48,7 @@ func TestPerformAsync_Progress_Good(t *testing.T) {
}
func TestPerformAsync_Completion_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
completed := make(chan ActionTaskCompleted, 1)
c.RegisterTask(func(_ *Core, task Task) Result {
@ -73,7 +73,7 @@ func TestPerformAsync_Completion_Good(t *testing.T) {
}
func TestPerformAsync_NoHandler_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
completed := make(chan ActionTaskCompleted, 1)
c.RegisterAction(func(_ *Core, msg Message) Result {
@ -94,7 +94,7 @@ func TestPerformAsync_NoHandler_Good(t *testing.T) {
}
func TestPerformAsync_AfterShutdown_Bad(t *testing.T) {
c := New()
c := New().Value.(*Core)
c.ServiceStartup(context.Background(), nil)
c.ServiceShutdown(context.Background())
@ -105,7 +105,7 @@ func TestPerformAsync_AfterShutdown_Bad(t *testing.T) {
// --- RegisterAction + RegisterActions ---
func TestRegisterAction_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
called := false
c.RegisterAction(func(_ *Core, _ Message) Result {
called = true
@ -116,7 +116,7 @@ func TestRegisterAction_Good(t *testing.T) {
}
func TestRegisterActions_Good(t *testing.T) {
c := New()
c := New().Value.(*Core)
count := 0
h := func(_ *Core, _ Message) Result { count++; return Result{OK: true} }
c.RegisterActions(h, h)