test: rewrite test suite for AX primitives API

164 tests, 41.3% coverage. Tests written against the public API only
(external test package, no _test.go in pkg/core/).

Covers: New(Options), Data, Drive, Config, Service, Error, IPC,
Fs, Cli, Lock, Array, Log, App, Runtime, Task.

Fixes: NewCommand now inits flagset, New() wires Cli root command.

Old tests removed — they referenced With*, RegisterService, and
other patterns that no longer exist.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-20 08:42:38 +00:00
parent f51c748f49
commit 1ca010e1fb
31 changed files with 1671 additions and 1600 deletions

View file

@ -45,6 +45,9 @@ func NewCommand(name string, description ...string) *Command {
sliceSeparator: make(map[string]string),
}
// Init flagset so flags can be added before Run
result.setParentCommandPath("")
return result
}

View file

@ -95,5 +95,10 @@ func New(opts ...Options) *Core {
}
}
// Init Cli root command from app name
c.cli.rootCommand = NewCommand(c.app.Name)
c.cli.rootCommand.setParentCommandPath("")
c.cli.rootCommand.setApp(c.cli)
return c
}

40
tests/app_test.go Normal file
View file

@ -0,0 +1,40 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- App ---
func TestApp_Good(t *testing.T) {
c := New(Options{{K: "name", V: "myapp"}})
assert.Equal(t, "myapp", c.App().Name)
}
func TestApp_Empty_Good(t *testing.T) {
c := New()
assert.NotNil(t, c.App())
assert.Equal(t, "", c.App().Name)
}
func TestApp_Runtime_Good(t *testing.T) {
c := New()
c.App().Runtime = &struct{ Name string }{Name: "wails"}
assert.NotNil(t, c.App().Runtime)
}
func TestApp_Find_Good(t *testing.T) {
app := Find("go", "go")
// Find looks for a binary — go should be in PATH
if app != nil {
assert.NotEmpty(t, app.Path)
}
}
func TestApp_Find_Bad(t *testing.T) {
app := Find("nonexistent-binary-xyz", "test")
assert.Nil(t, app)
}

88
tests/array_test.go Normal file
View file

@ -0,0 +1,88 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Array[T] ---
func TestArray_New_Good(t *testing.T) {
a := NewArray("a", "b", "c")
assert.Equal(t, 3, a.Len())
}
func TestArray_Add_Good(t *testing.T) {
a := NewArray[string]()
a.Add("x", "y")
assert.Equal(t, 2, a.Len())
assert.True(t, a.Contains("x"))
assert.True(t, a.Contains("y"))
}
func TestArray_AddUnique_Good(t *testing.T) {
a := NewArray("a", "b")
a.AddUnique("b", "c")
assert.Equal(t, 3, a.Len())
}
func TestArray_Contains_Good(t *testing.T) {
a := NewArray(1, 2, 3)
assert.True(t, a.Contains(2))
assert.False(t, a.Contains(99))
}
func TestArray_Filter_Good(t *testing.T) {
a := NewArray(1, 2, 3, 4, 5)
evens := a.Filter(func(n int) bool { return n%2 == 0 })
assert.Equal(t, 2, evens.Len())
assert.True(t, evens.Contains(2))
assert.True(t, evens.Contains(4))
}
func TestArray_Each_Good(t *testing.T) {
a := NewArray("a", "b", "c")
var collected []string
a.Each(func(s string) { collected = append(collected, s) })
assert.Equal(t, []string{"a", "b", "c"}, collected)
}
func TestArray_Remove_Good(t *testing.T) {
a := NewArray("a", "b", "c")
a.Remove("b")
assert.Equal(t, 2, a.Len())
assert.False(t, a.Contains("b"))
}
func TestArray_Remove_Bad(t *testing.T) {
a := NewArray("a", "b")
a.Remove("missing")
assert.Equal(t, 2, a.Len())
}
func TestArray_Deduplicate_Good(t *testing.T) {
a := NewArray("a", "b", "a", "c", "b")
a.Deduplicate()
assert.Equal(t, 3, a.Len())
}
func TestArray_Clear_Good(t *testing.T) {
a := NewArray(1, 2, 3)
a.Clear()
assert.Equal(t, 0, a.Len())
}
func TestArray_AsSlice_Good(t *testing.T) {
a := NewArray("x", "y")
s := a.AsSlice()
assert.Equal(t, []string{"x", "y"}, s)
}
func TestArray_Empty_Good(t *testing.T) {
a := NewArray[int]()
assert.Equal(t, 0, a.Len())
assert.False(t, a.Contains(0))
assert.Equal(t, []int(nil), a.AsSlice())
}

View file

@ -1,141 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"context"
"errors"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCore_PerformAsync_Good(t *testing.T) {
c, _ := New()
var completed atomic.Bool
var resultReceived any
c.RegisterAction(func(c *Core, msg Message) error {
if tc, ok := msg.(ActionTaskCompleted); ok {
resultReceived = tc.Result
completed.Store(true)
}
return nil
})
c.RegisterTask(func(c *Core, task Task) (any, bool, error) {
return "async-result", true, nil
})
taskID := c.PerformAsync(TestTask{})
assert.NotEmpty(t, taskID)
// Wait for completion
assert.Eventually(t, func() bool {
return completed.Load()
}, 1*time.Second, 10*time.Millisecond)
assert.Equal(t, "async-result", resultReceived)
}
func TestCore_PerformAsync_Shutdown(t *testing.T) {
c, _ := New()
_ = c.ServiceShutdown(context.Background())
taskID := c.PerformAsync(TestTask{})
assert.Empty(t, taskID, "PerformAsync should return empty string if already shut down")
}
func TestCore_Progress_Good(t *testing.T) {
c, _ := New()
var progressReceived float64
var messageReceived string
c.RegisterAction(func(c *Core, msg Message) error {
if tp, ok := msg.(ActionTaskProgress); ok {
progressReceived = tp.Progress
messageReceived = tp.Message
}
return nil
})
c.Progress("task-1", 0.5, "halfway", TestTask{})
assert.Equal(t, 0.5, progressReceived)
assert.Equal(t, "halfway", messageReceived)
}
func TestCore_WithService_UnnamedType(t *testing.T) {
// Primitive types have no package path
factory := func(c *Core) (any, error) {
s := "primitive"
return &s, nil
}
_, err := New(WithService(factory))
require.Error(t, err)
assert.Contains(t, err.Error(), "service name could not be discovered")
}
func TestRuntime_ServiceStartup_ErrorPropagation(t *testing.T) {
rt, _ := NewRuntime(nil)
// Register a service that fails startup
errSvc := &MockStartable{err: errors.New("startup failed")}
_ = rt.Core.RegisterService("error-svc", errSvc)
err := rt.ServiceStartup(context.Background(), nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "startup failed")
}
func TestCore_ServiceStartup_ContextCancellation(t *testing.T) {
c, _ := New()
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
s1 := &MockStartable{}
_ = c.RegisterService("s1", s1)
err := c.ServiceStartup(ctx, nil)
assert.Error(t, err)
assert.ErrorIs(t, err, context.Canceled)
assert.False(t, s1.started, "Srv should not have started if context was cancelled before loop")
}
func TestCore_ServiceShutdown_ContextCancellation(t *testing.T) {
c, _ := New()
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
s1 := &MockStoppable{}
_ = c.RegisterService("s1", s1)
err := c.ServiceShutdown(ctx)
assert.Error(t, err)
assert.ErrorIs(t, err, context.Canceled)
assert.False(t, s1.stopped, "Srv should not have stopped if context was cancelled before loop")
}
type TaskWithIDImpl struct {
id string
}
func (t *TaskWithIDImpl) SetTaskID(id string) { t.id = id }
func (t *TaskWithIDImpl) GetTaskID() string { return t.id }
func TestCore_PerformAsync_InjectsID(t *testing.T) {
c, _ := New()
c.RegisterTask(func(c *Core, t Task) (any, bool, error) { return nil, true, nil })
task := &TaskWithIDImpl{}
taskID := c.PerformAsync(task)
assert.Equal(t, taskID, task.GetTaskID())
}

View file

@ -1,40 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"testing"
)
func BenchmarkMessageBus_Action(b *testing.B) {
c, _ := New()
c.RegisterAction(func(c *Core, msg Message) error {
return nil
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = c.ACTION("test")
}
}
func BenchmarkMessageBus_Query(b *testing.B) {
c, _ := New()
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "result", true, nil
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = c.QUERY("test")
}
}
func BenchmarkMessageBus_Perform(b *testing.B) {
c, _ := New()
c.RegisterTask(func(c *Core, t Task) (any, bool, error) {
return "result", true, nil
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = c.PERFORM("test")
}
}

76
tests/cli_test.go Normal file
View file

@ -0,0 +1,76 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Cli ---
func TestCli_Good(t *testing.T) {
c := New()
assert.NotNil(t, c.Cli())
assert.NotNil(t, c.Cli().Command())
}
func TestCli_Named_Good(t *testing.T) {
c := New(Options{{K: "name", V: "myapp"}})
assert.NotNil(t, c.Cli().Command())
}
func TestCli_NewChildCommand_Good(t *testing.T) {
c := New(Options{{K: "name", V: "myapp"}})
child := c.Cli().NewChildCommand("test", "a test command")
assert.NotNil(t, child)
}
func TestCli_AddCommand_Good(t *testing.T) {
c := New()
cmd := NewCommand("hello", "says hello")
c.Cli().AddCommand(cmd)
}
func TestCli_Flags_Good(t *testing.T) {
c := New()
var name string
var debug bool
var port int
c.Cli().StringFlag("name", "app name", &name)
c.Cli().BoolFlag("debug", "enable debug", &debug)
c.Cli().IntFlag("port", "port number", &port)
}
func TestCli_Run_Good(t *testing.T) {
c := New()
executed := false
c.Cli().Command().Action(func() error {
executed = true
return nil
})
err := c.Cli().Run("")
assert.NoError(t, err)
assert.True(t, executed)
}
// --- Command ---
func TestCommand_New_Good(t *testing.T) {
cmd := NewCommand("test", "a test command")
assert.NotNil(t, cmd)
}
func TestCommand_Child_Good(t *testing.T) {
parent := NewCommand("root")
child := parent.NewChildCommand("sub", "a subcommand")
assert.NotNil(t, child)
}
func TestCommand_Flags_Good(t *testing.T) {
cmd := NewCommand("test")
var name string
var debug bool
cmd.StringFlag("name", "app name", &name)
cmd.BoolFlag("debug", "enable debug", &debug)
}

102
tests/config_test.go Normal file
View file

@ -0,0 +1,102 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Config ---
func TestConfig_SetGet_Good(t *testing.T) {
c := New()
c.Config().Set("api_url", "https://api.lthn.ai")
c.Config().Set("max_agents", 5)
val, ok := c.Config().Get("api_url")
assert.True(t, ok)
assert.Equal(t, "https://api.lthn.ai", val)
}
func TestConfig_Get_Bad(t *testing.T) {
c := New()
val, ok := c.Config().Get("missing")
assert.False(t, ok)
assert.Nil(t, val)
}
func TestConfig_TypedAccessors_Good(t *testing.T) {
c := New()
c.Config().Set("url", "https://lthn.ai")
c.Config().Set("port", 8080)
c.Config().Set("debug", true)
assert.Equal(t, "https://lthn.ai", c.Config().String("url"))
assert.Equal(t, 8080, c.Config().Int("port"))
assert.True(t, c.Config().Bool("debug"))
}
func TestConfig_TypedAccessors_Bad(t *testing.T) {
c := New()
// Missing keys return zero values
assert.Equal(t, "", c.Config().String("missing"))
assert.Equal(t, 0, c.Config().Int("missing"))
assert.False(t, c.Config().Bool("missing"))
}
// --- Feature Flags ---
func TestConfig_Features_Good(t *testing.T) {
c := New()
c.Config().Enable("dark-mode")
c.Config().Enable("beta")
assert.True(t, c.Config().Enabled("dark-mode"))
assert.True(t, c.Config().Enabled("beta"))
assert.False(t, c.Config().Enabled("missing"))
}
func TestConfig_Features_Disable_Good(t *testing.T) {
c := New()
c.Config().Enable("feature")
assert.True(t, c.Config().Enabled("feature"))
c.Config().Disable("feature")
assert.False(t, c.Config().Enabled("feature"))
}
func TestConfig_Features_CaseSensitive(t *testing.T) {
c := New()
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.Config().Enable("a")
c.Config().Enable("b")
c.Config().Enable("c")
c.Config().Disable("b")
features := c.Config().EnabledFeatures()
assert.Contains(t, features, "a")
assert.Contains(t, features, "c")
assert.NotContains(t, features, "b")
}
// --- ConfigVar ---
func TestConfigVar_Good(t *testing.T) {
v := NewConfigVar("hello")
assert.True(t, v.IsSet())
assert.Equal(t, "hello", v.Get())
v.Set("world")
assert.Equal(t, "world", v.Get())
v.Unset()
assert.False(t, v.IsSet())
assert.Equal(t, "", v.Get())
}

View file

@ -1,45 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"testing"
"github.com/stretchr/testify/assert"
)
type MockServiceWithIPC struct {
MockService
handled bool
}
func (m *MockServiceWithIPC) HandleIPCEvents(c *Core, msg Message) error {
m.handled = true
return nil
}
func TestCore_WithService_IPC(t *testing.T) {
svc := &MockServiceWithIPC{MockService: MockService{Name: "ipc-service"}}
factory := func(c *Core) (any, error) {
return svc, nil
}
c, err := New(WithService(factory))
assert.NoError(t, err)
// Trigger ACTION to verify handler was registered
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, svc.handled)
}
func TestCore_ACTION_Bad(t *testing.T) {
c, err := New()
assert.NoError(t, err)
errHandler := func(c *Core, msg Message) error {
return assert.AnError
}
c.RegisterAction(errHandler)
err = c.ACTION(nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), assert.AnError.Error())
}

View file

@ -1,165 +0,0 @@
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")
}

View file

@ -1,346 +1,63 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"context"
"embed"
"io"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// mockApp is a simple mock for testing app injection
type mockApp struct{}
// --- New ---
func TestCore_New_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
func TestNew_Good(t *testing.T) {
c := New()
assert.NotNil(t, c)
}
// Mock service for testing
type MockService struct {
Name string
func TestNew_WithOptions_Good(t *testing.T) {
c := New(Options{{K: "name", V: "myapp"}})
assert.NotNil(t, c)
assert.Equal(t, "myapp", c.App().Name)
}
func (m *MockService) GetName() string {
return m.Name
func TestNew_WithOptions_Bad(t *testing.T) {
// Empty options — should still create a valid Core
c := New(Options{})
assert.NotNil(t, c)
}
func TestCore_WithService_Good(t *testing.T) {
factory := func(c *Core) (any, error) {
return &MockService{Name: "test"}, nil
}
c, err := New(WithService(factory))
assert.NoError(t, err)
svc := c.Service().Get("core")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
// --- Accessors ---
func TestCore_WithService_Bad(t *testing.T) {
factory := func(c *Core) (any, error) {
return nil, assert.AnError
}
_, err := New(WithService(factory))
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
type MockConfigService struct{}
func (m *MockConfigService) Get(key string, out any) error { return nil }
func (m *MockConfigService) Set(key string, v any) error { return nil }
func TestCore_Services_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
err = c.RegisterService("config", &MockConfigService{})
assert.NoError(t, err)
svc := c.Service("config")
assert.NotNil(t, svc)
// Cfg() returns Cfg (always available, not a service)
cfg := c.Config()
assert.NotNil(t, cfg)
}
func TestCore_App_Good(t *testing.T) {
app := &mockApp{}
c, err := New(WithApp(app))
assert.NoError(t, err)
// To test the global CoreGUI() function, we need to set the global instance.
originalInstance := GetInstance()
SetInstance(c)
defer SetInstance(originalInstance)
assert.Equal(t, app, CoreGUI())
}
func TestCore_App_Ugly(t *testing.T) {
// This test ensures that calling CoreGUI() before the core is initialized panics.
originalInstance := GetInstance()
ClearInstance()
defer SetInstance(originalInstance)
assert.Panics(t, func() {
CoreGUI()
})
}
func TestCore_Core_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
func TestAccessors_Good(t *testing.T) {
c := New()
assert.NotNil(t, c.App())
assert.NotNil(t, c.Data())
assert.NotNil(t, c.Drive())
assert.NotNil(t, c.Fs())
assert.NotNil(t, c.Config())
assert.NotNil(t, c.Error())
assert.NotNil(t, c.Log())
assert.NotNil(t, c.Cli())
assert.NotNil(t, c.IPC())
assert.NotNil(t, c.I18n())
assert.Equal(t, c, c.Core())
}
func TestEtc_Features_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
c.Config().Enable("feature1")
c.Config().Enable("feature2")
assert.True(t, c.Config().Enabled("feature1"))
assert.True(t, c.Config().Enabled("feature2"))
assert.False(t, c.Config().Enabled("feature3"))
assert.False(t, c.Config().Enabled(""))
}
func TestEtc_Settings_Good(t *testing.T) {
c, _ := New()
c.Config().Set("api_url", "https://api.lthn.sh")
c.Config().Set("max_agents", 5)
assert.Equal(t, "https://api.lthn.sh", c.Config().GetString("api_url"))
assert.Equal(t, 5, c.Config().GetInt("max_agents"))
assert.Equal(t, "", c.Config().GetString("missing"))
}
func TestEtc_Features_Edge(t *testing.T) {
c, _ := New()
c.Config().Enable("foo")
assert.True(t, c.Config().Enabled("foo"))
assert.False(t, c.Config().Enabled("FOO")) // Case sensitive
c.Config().Disable("foo")
assert.False(t, c.Config().Enabled("foo"))
}
func TestCore_ServiceLifecycle_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
var messageReceived Message
handler := func(c *Core, msg Message) error {
messageReceived = msg
return nil
}
c.RegisterAction(handler)
// Test Startup
_ = c.ServiceStartup(context.TODO(), nil)
_, ok := messageReceived.(ActionServiceStartup)
assert.True(t, ok, "expected ActionServiceStartup message")
// Test Shutdown
_ = c.ServiceShutdown(context.TODO())
_, ok = messageReceived.(ActionServiceShutdown)
assert.True(t, ok, "expected ActionServiceShutdown message")
}
func TestCore_WithApp_Good(t *testing.T) {
app := &mockApp{}
c, err := New(WithApp(app))
assert.NoError(t, err)
assert.Equal(t, app, c.App().Runtime)
}
//go:embed testdata
var testFS embed.FS
func TestCore_WithAssets_Good(t *testing.T) {
c, err := New(WithAssets(testFS))
assert.NoError(t, err)
file, err := c.Embed().Open("testdata/test.txt")
assert.NoError(t, err)
defer func() { _ = file.Close() }()
content, err := io.ReadAll(file)
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(content))
}
func TestCore_WithServiceLock_Good(t *testing.T) {
c, err := New(WithServiceLock())
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
assert.Error(t, err)
}
func TestCore_RegisterService_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc := c.Service("test")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestCore_RegisterService_Bad(t *testing.T) {
c, err := New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{})
assert.Error(t, err)
err = c.RegisterService("", &MockService{})
assert.Error(t, err)
}
func TestCore_ServiceFor_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc, err := ServiceFor[*MockService](c, "test")
assert.NoError(t, err)
assert.Equal(t, "test", svc.GetName())
}
func TestCore_ServiceFor_Bad(t *testing.T) {
c, err := New()
assert.NoError(t, err)
_, err = ServiceFor[*MockService](c, "nonexistent")
assert.Error(t, err)
err = c.RegisterService("test", "not a service")
assert.NoError(t, err)
_, err = ServiceFor[*MockService](c, "test")
assert.Error(t, err)
}
func TestCore_MustServiceFor_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
err = c.RegisterService("test", &MockService{Name: "test"})
assert.NoError(t, err)
svc := MustServiceFor[*MockService](c, "test")
assert.Equal(t, "test", svc.GetName())
}
func TestCore_MustServiceFor_Ugly(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// MustServiceFor panics on missing service
assert.Panics(t, func() {
MustServiceFor[*MockService](c, "nonexistent")
})
err = c.RegisterService("test", "not a service")
assert.NoError(t, err)
// MustServiceFor panics on type mismatch
assert.Panics(t, func() {
MustServiceFor[*MockService](c, "test")
func TestOptions_Accessor_Good(t *testing.T) {
c := New(Options{
{K: "name", V: "testapp"},
{K: "port", V: 8080},
{K: "debug", V: true},
})
opts := c.Options()
assert.NotNil(t, opts)
assert.Equal(t, "testapp", opts.String("name"))
assert.Equal(t, 8080, opts.Int("port"))
assert.True(t, opts.Bool("debug"))
}
type MockAction struct {
handled bool
}
func (a *MockAction) Handle(c *Core, msg Message) error {
a.handled = true
return nil
}
func TestCore_ACTION_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
action := &MockAction{}
c.RegisterAction(action.Handle)
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, action.handled)
}
func TestCore_RegisterActions_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
action1 := &MockAction{}
action2 := &MockAction{}
c.RegisterActions(action1.Handle, action2.Handle)
err = c.ACTION(nil)
assert.NoError(t, err)
assert.True(t, action1.handled)
assert.True(t, action2.handled)
}
func TestCore_WithName_Good(t *testing.T) {
factory := func(c *Core) (any, error) {
return &MockService{Name: "test"}, nil
}
c, err := New(WithName("my-service", factory))
assert.NoError(t, err)
svc := c.Service("my-service")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.GetName())
}
func TestCore_WithName_Bad(t *testing.T) {
factory := func(c *Core) (any, error) {
return nil, assert.AnError
}
_, err := New(WithName("my-service", factory))
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestCore_GlobalInstance_ThreadSafety_Good(t *testing.T) {
// Save original instance
original := GetInstance()
defer SetInstance(original)
// Test SetInstance/GetInstance
c1, _ := New()
SetInstance(c1)
assert.Equal(t, c1, GetInstance())
// Test ClearInstance
ClearInstance()
assert.Nil(t, GetInstance())
// Test concurrent access (race detector should catch issues)
c2, _ := New(WithApp(&mockApp{}))
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
SetInstance(c2)
_ = GetInstance()
done <- true
}()
go func() {
inst := GetInstance()
if inst != nil {
_ = inst.App
}
done <- true
}()
}
// Wait for all goroutines
for i := 0; i < 20; i++ {
<-done
}
func TestOptions_Accessor_Nil(t *testing.T) {
c := New()
// No options passed — Options() returns nil
assert.Nil(t, c.Options())
}

127
tests/data_test.go Normal file
View file

@ -0,0 +1,127 @@
package core_test
import (
"embed"
"io"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
//go:embed testdata
var testFS embed.FS
// --- Data (Embedded Content Mounts) ---
func TestData_New_Good(t *testing.T) {
c := New()
r := c.Data().New(Options{
{K: "name", V: "test"},
{K: "source", V: testFS},
{K: "path", V: "testdata"},
})
assert.True(t, r.OK)
assert.NotNil(t, r.Value)
}
func TestData_New_Bad(t *testing.T) {
c := New()
// Missing name
r := c.Data().New(Options{
{K: "source", V: testFS},
})
assert.False(t, r.OK)
// Missing source
r = c.Data().New(Options{
{K: "name", V: "test"},
})
assert.False(t, r.OK)
// Wrong source type
r = c.Data().New(Options{
{K: "name", V: "test"},
{K: "source", V: "not-an-fs"},
})
assert.False(t, r.OK)
}
func TestData_ReadString_Good(t *testing.T) {
c := New()
c.Data().New(Options{
{K: "name", V: "app"},
{K: "source", V: testFS},
{K: "path", V: "testdata"},
})
content, err := c.Data().ReadString("app/test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", content)
}
func TestData_ReadString_Bad(t *testing.T) {
c := New()
_, err := c.Data().ReadString("nonexistent/file.txt")
assert.Error(t, err)
}
func TestData_ReadFile_Good(t *testing.T) {
c := New()
c.Data().New(Options{
{K: "name", V: "app"},
{K: "source", V: testFS},
{K: "path", V: "testdata"},
})
data, err := c.Data().ReadFile("app/test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(data))
}
func TestData_Get_Good(t *testing.T) {
c := New()
c.Data().New(Options{
{K: "name", V: "brain"},
{K: "source", V: testFS},
{K: "path", V: "testdata"},
})
emb := c.Data().Get("brain")
assert.NotNil(t, emb)
// Read via the Embed directly
file, err := emb.Open("test.txt")
assert.NoError(t, err)
defer file.Close()
content, _ := io.ReadAll(file)
assert.Equal(t, "hello from testdata\n", string(content))
}
func TestData_Get_Bad(t *testing.T) {
c := New()
emb := c.Data().Get("nonexistent")
assert.Nil(t, emb)
}
func TestData_Mounts_Good(t *testing.T) {
c := New()
c.Data().New(Options{{K: "name", V: "a"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}})
c.Data().New(Options{{K: "name", V: "b"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}})
mounts := c.Data().Mounts()
assert.Len(t, mounts, 2)
assert.Contains(t, mounts, "a")
assert.Contains(t, mounts, "b")
}
// --- Legacy Embed() accessor ---
func TestEmbed_Legacy_Good(t *testing.T) {
c := New()
c.Data().New(Options{
{K: "name", V: "app"},
{K: "source", V: testFS},
{K: "path", V: "testdata"},
})
// Legacy accessor reads from Data mount "app"
emb := c.Embed()
assert.NotNil(t, emb)
}

77
tests/drive_test.go Normal file
View file

@ -0,0 +1,77 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Drive (Transport Handles) ---
func TestDrive_New_Good(t *testing.T) {
c := New()
r := c.Drive().New(Options{
{K: "name", V: "api"},
{K: "transport", V: "https://api.lthn.ai"},
})
assert.True(t, r.OK)
assert.Equal(t, "api", r.Value.Name)
assert.Equal(t, "https://api.lthn.ai", r.Value.Transport)
}
func TestDrive_New_Bad(t *testing.T) {
c := New()
// Missing name
r := c.Drive().New(Options{
{K: "transport", V: "https://api.lthn.ai"},
})
assert.False(t, r.OK)
}
func TestDrive_Get_Good(t *testing.T) {
c := New()
c.Drive().New(Options{
{K: "name", V: "ssh"},
{K: "transport", V: "ssh://claude@10.69.69.165"},
})
handle := c.Drive().Get("ssh")
assert.NotNil(t, handle)
assert.Equal(t, "ssh://claude@10.69.69.165", handle.Transport)
}
func TestDrive_Get_Bad(t *testing.T) {
c := New()
handle := c.Drive().Get("nonexistent")
assert.Nil(t, handle)
}
func TestDrive_Has_Good(t *testing.T) {
c := New()
c.Drive().New(Options{{K: "name", V: "mcp"}, {K: "transport", V: "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.Drive().New(Options{{K: "name", V: "api"}, {K: "transport", V: "https://api.lthn.ai"}})
c.Drive().New(Options{{K: "name", V: "ssh"}, {K: "transport", V: "ssh://claude@10.69.69.165"}})
c.Drive().New(Options{{K: "name", V: "mcp"}, {K: "transport", V: "mcp://mcp.lthn.sh"}})
names := c.Drive().Names()
assert.Len(t, names, 3)
assert.Contains(t, names, "api")
assert.Contains(t, names, "ssh")
assert.Contains(t, names, "mcp")
}
func TestDrive_OptionsPreserved_Good(t *testing.T) {
c := New()
c.Drive().New(Options{
{K: "name", V: "api"},
{K: "transport", V: "https://api.lthn.ai"},
{K: "timeout", V: 30},
})
handle := c.Drive().Get("api")
assert.Equal(t, 30, handle.Options.Int("timeout"))
}

View file

@ -1,31 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestE_Good(t *testing.T) {
err := E("test.op", "test message", assert.AnError)
assert.Error(t, err)
assert.Equal(t, "test.op: test message: assert.AnError general error for testing", err.Error())
err = E("test.op", "test message", nil)
assert.Error(t, err)
assert.Equal(t, "test.op: test message", err.Error())
}
func TestE_Unwrap(t *testing.T) {
originalErr := errors.New("original error")
err := E("test.op", "test message", originalErr)
assert.True(t, errors.Is(err, originalErr))
var eErr *Err
assert.True(t, errors.As(err, &eErr))
assert.Equal(t, "test.op", eErr.Op)
}

132
tests/embed_test.go Normal file
View file

@ -0,0 +1,132 @@
package core_test
import (
"bytes"
"compress/gzip"
"encoding/base64"
"os"
"path/filepath"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Embed (Mount + ReadFile + Sub) ---
func TestMount_Good(t *testing.T) {
emb, err := Mount(testFS, "testdata")
assert.NoError(t, err)
assert.NotNil(t, emb)
}
func TestMount_Bad(t *testing.T) {
_, err := Mount(testFS, "nonexistent")
assert.Error(t, err)
}
func TestEmbed_ReadFile_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
data, err := emb.ReadFile("test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(data))
}
func TestEmbed_ReadString_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
s, err := emb.ReadString("test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", s)
}
func TestEmbed_Open_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
f, err := emb.Open("test.txt")
assert.NoError(t, err)
defer f.Close()
}
func TestEmbed_ReadDir_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
entries, err := emb.ReadDir(".")
assert.NoError(t, err)
assert.NotEmpty(t, entries)
}
func TestEmbed_Sub_Good(t *testing.T) {
emb, _ := Mount(testFS, ".")
sub, err := emb.Sub("testdata")
assert.NoError(t, err)
data, err := sub.ReadFile("test.txt")
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(data))
}
func TestEmbed_BaseDir_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
assert.Equal(t, "testdata", emb.BaseDir())
}
func TestEmbed_FS_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
assert.NotNil(t, emb.FS())
}
func TestEmbed_EmbedFS_Good(t *testing.T) {
emb, _ := Mount(testFS, "testdata")
efs := emb.EmbedFS()
// Should return the original embed.FS
_, err := efs.ReadFile("testdata/test.txt")
assert.NoError(t, err)
}
// --- Extract (Template Directory) ---
func TestExtract_Good(t *testing.T) {
dir := t.TempDir()
err := Extract(testFS, dir, nil)
assert.NoError(t, err)
// testdata/test.txt should be extracted
content, err := os.ReadFile(filepath.Join(dir, "testdata", "test.txt"))
assert.NoError(t, err)
assert.Equal(t, "hello from testdata\n", string(content))
}
// --- Asset Pack (Build-time) ---
func TestAddGetAsset_Good(t *testing.T) {
AddAsset("test-group", "greeting", mustCompress("hello world"))
result, err := GetAsset("test-group", "greeting")
assert.NoError(t, err)
assert.Equal(t, "hello world", result)
}
func TestGetAsset_Bad(t *testing.T) {
_, err := GetAsset("missing-group", "missing")
assert.Error(t, err)
AddAsset("exists", "item", mustCompress("data"))
_, err = GetAsset("exists", "missing-item")
assert.Error(t, err)
}
func TestGetAssetBytes_Good(t *testing.T) {
AddAsset("bytes-group", "file", mustCompress("binary content"))
data, err := GetAssetBytes("bytes-group", "file")
assert.NoError(t, err)
assert.Equal(t, []byte("binary content"), data)
}
// mustCompress is a test helper — compresses a string the way AddAsset expects.
func mustCompress(input string) string {
// AddAsset stores pre-compressed data. We need to compress it the same way.
// Use the internal format: base64(gzip(input))
var buf bytes.Buffer
b64 := base64.NewEncoder(base64.StdEncoding, &buf)
gz, _ := gzip.NewWriterLevel(b64, gzip.BestCompression)
gz.Write([]byte(input))
gz.Close()
b64.Close()
return buf.String()
}

196
tests/error_test.go Normal file
View file

@ -0,0 +1,196 @@
package core_test
import (
"errors"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Error Creation ---
func TestE_Good(t *testing.T) {
err := E("user.Save", "failed to save", nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "user.Save")
assert.Contains(t, err.Error(), "failed to save")
}
func TestE_WithCause_Good(t *testing.T) {
cause := errors.New("connection refused")
err := E("db.Connect", "database unavailable", cause)
assert.ErrorIs(t, err, cause)
}
func TestWrap_Good(t *testing.T) {
cause := errors.New("timeout")
err := Wrap(cause, "api.Call", "request failed")
assert.Error(t, err)
assert.ErrorIs(t, err, cause)
}
func TestWrap_Nil_Good(t *testing.T) {
err := Wrap(nil, "api.Call", "request failed")
assert.Nil(t, err)
}
func TestWrapCode_Good(t *testing.T) {
cause := errors.New("invalid email")
err := WrapCode(cause, "VALIDATION_ERROR", "user.Validate", "bad input")
assert.Error(t, err)
assert.Equal(t, "VALIDATION_ERROR", ErrCode(err))
}
func TestNewCode_Good(t *testing.T) {
err := NewCode("NOT_FOUND", "resource not found")
assert.Error(t, err)
assert.Equal(t, "NOT_FOUND", ErrCode(err))
}
// --- Error Introspection ---
func TestOp_Good(t *testing.T) {
err := E("brain.Recall", "search failed", nil)
assert.Equal(t, "brain.Recall", Op(err))
}
func TestOp_Bad(t *testing.T) {
err := errors.New("plain error")
assert.Equal(t, "", Op(err))
}
func TestErrorMessage_Good(t *testing.T) {
err := E("op", "the message", nil)
assert.Equal(t, "the message", ErrorMessage(err))
}
func TestErrorMessage_Plain(t *testing.T) {
err := errors.New("plain")
assert.Equal(t, "plain", ErrorMessage(err))
}
func TestErrorMessage_Nil(t *testing.T) {
assert.Equal(t, "", ErrorMessage(nil))
}
func TestRoot_Good(t *testing.T) {
root := errors.New("root cause")
wrapped := Wrap(root, "layer1", "first wrap")
double := Wrap(wrapped, "layer2", "second wrap")
assert.Equal(t, root, Root(double))
}
func TestRoot_Nil(t *testing.T) {
assert.Nil(t, Root(nil))
}
func TestStackTrace_Good(t *testing.T) {
err := Wrap(E("inner", "cause", nil), "outer", "wrapper")
stack := StackTrace(err)
assert.Len(t, stack, 2)
assert.Equal(t, "outer", stack[0])
assert.Equal(t, "inner", stack[1])
}
func TestFormatStackTrace_Good(t *testing.T) {
err := Wrap(E("a", "x", nil), "b", "y")
formatted := FormatStackTrace(err)
assert.Equal(t, "b -> a", formatted)
}
// --- ErrorLog ---
func TestErrorLog_Good(t *testing.T) {
c := New()
cause := errors.New("boom")
err := c.Log().Error(cause, "test.Op", "something broke")
assert.Error(t, err)
assert.ErrorIs(t, err, cause)
}
func TestErrorLog_Nil_Good(t *testing.T) {
c := New()
err := c.Log().Error(nil, "test.Op", "no error")
assert.Nil(t, err)
}
func TestErrorLog_Warn_Good(t *testing.T) {
c := New()
cause := errors.New("warning")
err := c.Log().Warn(cause, "test.Op", "heads up")
assert.Error(t, err)
}
func TestErrorLog_Must_Ugly(t *testing.T) {
c := New()
assert.Panics(t, func() {
c.Log().Must(errors.New("fatal"), "test.Op", "must fail")
})
}
func TestErrorLog_Must_Nil_Good(t *testing.T) {
c := New()
assert.NotPanics(t, func() {
c.Log().Must(nil, "test.Op", "no error")
})
}
// --- ErrorPanic ---
func TestErrorPanic_Recover_Good(t *testing.T) {
c := New()
// Should not panic — Recover catches it
assert.NotPanics(t, func() {
defer c.Error().Recover()
panic("test panic")
})
}
func TestErrorPanic_SafeGo_Good(t *testing.T) {
c := New()
done := make(chan bool, 1)
c.Error().SafeGo(func() {
done <- true
})
assert.True(t, <-done)
}
func TestErrorPanic_SafeGo_Panic_Good(t *testing.T) {
c := New()
done := make(chan bool, 1)
c.Error().SafeGo(func() {
defer func() { done <- true }()
panic("caught by SafeGo")
})
// SafeGo recovers — goroutine completes without crashing the process
<-done
}
// --- Standard Library Wrappers ---
func TestIs_Good(t *testing.T) {
target := errors.New("target")
wrapped := Wrap(target, "op", "msg")
assert.True(t, Is(wrapped, target))
}
func TestAs_Good(t *testing.T) {
err := E("op", "msg", nil)
var e *Err
assert.True(t, As(err, &e))
assert.Equal(t, "op", e.Op)
}
func TestNewError_Good(t *testing.T) {
err := NewError("simple error")
assert.Equal(t, "simple error", err.Error())
}
func TestJoin_Good(t *testing.T) {
e1 := errors.New("first")
e2 := errors.New("second")
joined := Join(e1, e2)
assert.ErrorIs(t, joined, e1)
assert.ErrorIs(t, joined, e2)
}

211
tests/fs_test.go Normal file
View file

@ -0,0 +1,211 @@
package core_test
import (
"path/filepath"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Fs (Sandboxed Filesystem) ---
func TestFs_WriteRead_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "test.txt")
err := c.Fs().Write(path, "hello core")
assert.NoError(t, err)
content, err := c.Fs().Read(path)
assert.NoError(t, err)
assert.Equal(t, "hello core", content)
}
func TestFs_Read_Bad(t *testing.T) {
c := New()
_, err := c.Fs().Read("/nonexistent/path/to/file.txt")
assert.Error(t, err)
}
func TestFs_EnsureDir_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "sub", "dir")
err := c.Fs().EnsureDir(path)
assert.NoError(t, err)
assert.True(t, c.Fs().IsDir(path))
}
func TestFs_IsDir_Good(t *testing.T) {
c := New()
dir := t.TempDir()
assert.True(t, c.Fs().IsDir(dir))
assert.False(t, c.Fs().IsDir(filepath.Join(dir, "nonexistent")))
assert.False(t, c.Fs().IsDir(""))
}
func TestFs_IsFile_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "test.txt")
c.Fs().Write(path, "data")
assert.True(t, c.Fs().IsFile(path))
assert.False(t, c.Fs().IsFile(dir)) // dir, not file
assert.False(t, c.Fs().IsFile(""))
}
func TestFs_Exists_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "exists.txt")
c.Fs().Write(path, "yes")
assert.True(t, c.Fs().Exists(path))
assert.True(t, c.Fs().Exists(dir))
assert.False(t, c.Fs().Exists(filepath.Join(dir, "nope")))
}
func TestFs_List_Good(t *testing.T) {
dir := t.TempDir()
c := New()
c.Fs().Write(filepath.Join(dir, "a.txt"), "a")
c.Fs().Write(filepath.Join(dir, "b.txt"), "b")
entries, err := c.Fs().List(dir)
assert.NoError(t, err)
assert.Len(t, entries, 2)
}
func TestFs_Stat_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "stat.txt")
c.Fs().Write(path, "data")
info, err := c.Fs().Stat(path)
assert.NoError(t, err)
assert.Equal(t, "stat.txt", info.Name())
}
func TestFs_Open_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "open.txt")
c.Fs().Write(path, "content")
file, err := c.Fs().Open(path)
assert.NoError(t, err)
file.Close()
}
func TestFs_Create_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "sub", "created.txt")
w, err := c.Fs().Create(path)
assert.NoError(t, err)
w.Write([]byte("hello"))
w.Close()
content, _ := c.Fs().Read(path)
assert.Equal(t, "hello", content)
}
func TestFs_Append_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "append.txt")
c.Fs().Write(path, "first")
w, err := c.Fs().Append(path)
assert.NoError(t, err)
w.Write([]byte(" second"))
w.Close()
content, _ := c.Fs().Read(path)
assert.Equal(t, "first second", content)
}
func TestFs_ReadStream_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "stream.txt")
c.Fs().Write(path, "streamed")
r, err := c.Fs().ReadStream(path)
assert.NoError(t, err)
r.Close()
}
func TestFs_WriteStream_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "sub", "ws.txt")
w, err := c.Fs().WriteStream(path)
assert.NoError(t, err)
w.Write([]byte("stream"))
w.Close()
}
func TestFs_Delete_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "delete.txt")
c.Fs().Write(path, "gone")
err := c.Fs().Delete(path)
assert.NoError(t, err)
assert.False(t, c.Fs().Exists(path))
}
func TestFs_DeleteAll_Good(t *testing.T) {
dir := t.TempDir()
c := New()
sub := filepath.Join(dir, "deep", "nested")
c.Fs().EnsureDir(sub)
c.Fs().Write(filepath.Join(sub, "file.txt"), "data")
err := c.Fs().DeleteAll(filepath.Join(dir, "deep"))
assert.NoError(t, err)
assert.False(t, c.Fs().Exists(filepath.Join(dir, "deep")))
}
func TestFs_Rename_Good(t *testing.T) {
dir := t.TempDir()
c := New()
old := filepath.Join(dir, "old.txt")
new := filepath.Join(dir, "new.txt")
c.Fs().Write(old, "data")
err := c.Fs().Rename(old, new)
assert.NoError(t, err)
assert.False(t, c.Fs().Exists(old))
assert.True(t, c.Fs().Exists(new))
}
func TestFs_WriteMode_Good(t *testing.T) {
dir := t.TempDir()
c := New()
path := filepath.Join(dir, "secret.txt")
err := c.Fs().WriteMode(path, "secret", 0600)
assert.NoError(t, err)
info, _ := c.Fs().Stat(path)
assert.Equal(t, "secret.txt", info.Name())
}

View file

@ -1,104 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"errors"
"testing"
)
// FuzzE exercises the E() error constructor with arbitrary input.
func FuzzE(f *testing.F) {
f.Add("svc.Method", "something broke", true)
f.Add("", "", false)
f.Add("a.b.c.d.e.f", "unicode: \u00e9\u00e8\u00ea", true)
f.Fuzz(func(t *testing.T, op, msg string, withErr bool) {
var underlying error
if withErr {
underlying = errors.New("wrapped")
}
e := E(op, msg, underlying)
if e == nil {
t.Fatal("E() returned nil")
}
s := e.Error()
if s == "" && (op != "" || msg != "") {
t.Fatal("Error() returned empty string for non-empty op/msg")
}
// Round-trip: Unwrap should return the underlying error
var coreErr *Err
if !errors.As(e, &coreErr) {
t.Fatal("errors.As failed for *Err")
}
if withErr && coreErr.Unwrap() == nil {
t.Fatal("Unwrap() returned nil with underlying error")
}
if !withErr && coreErr.Unwrap() != nil {
t.Fatal("Unwrap() returned non-nil without underlying error")
}
})
}
// FuzzServiceRegistration exercises service registration with arbitrary names.
func FuzzServiceRegistration(f *testing.F) {
f.Add("myservice")
f.Add("")
f.Add("a/b/c")
f.Add("service with spaces")
f.Add("service\x00null")
f.Fuzz(func(t *testing.T, name string) {
c, _ := New()
err := c.RegisterService(name, struct{}{})
if name == "" {
if err == nil {
t.Fatal("expected error for empty name")
}
return
}
if err != nil {
t.Fatalf("unexpected error for name %q: %v", name, err)
}
// Retrieve should return the same service
got := c.Service(name)
if got == nil {
t.Fatalf("service %q not found after registration", name)
}
// Duplicate registration should fail
err = c.RegisterService(name, struct{}{})
if err == nil {
t.Fatalf("expected duplicate error for name %q", name)
}
})
}
// FuzzMessageDispatch exercises action dispatch with concurrent registrations.
func FuzzMessageDispatch(f *testing.F) {
f.Add("hello")
f.Add("")
f.Add("test\nmultiline")
f.Fuzz(func(t *testing.T, payload string) {
c, _ := New()
var received string
c.IPC().RegisterAction(func(_ *Core, msg Message) error {
received = msg.(string)
return nil
})
err := c.IPC().Action(payload)
if err != nil {
t.Fatalf("action dispatch failed: %v", err)
}
if received != payload {
t.Fatalf("got %q, want %q", received, payload)
}
})
}

26
tests/i18n_test.go Normal file
View file

@ -0,0 +1,26 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
func TestI18n_Good(t *testing.T) {
c := New()
assert.NotNil(t, c.I18n())
}
func TestI18n_AddLocales_Good(t *testing.T) {
c := New()
// AddLocales takes *Embed mounts — mount testdata and add it
r := c.Data().New(Options{
{K: "name", V: "lang"},
{K: "source", V: testFS},
{K: "path", V: "testdata"},
})
if r.OK {
c.I18n().AddLocales(r.Value)
}
}

View file

@ -1,121 +1,97 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"errors"
"testing"
"time"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
type IPCTestQuery struct{ Value string }
type IPCTestTask struct{ Value string }
// --- IPC: Actions ---
func TestIPC_Query(t *testing.T) {
c, _ := New()
type testMessage struct{ payload string }
// No handler
res, handled, err := c.QUERY(IPCTestQuery{})
assert.False(t, handled)
assert.Nil(t, res)
assert.Nil(t, err)
// With handler
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
if tq, ok := q.(IPCTestQuery); ok {
return tq.Value + "-response", true, nil
}
return nil, false, nil
})
res, handled, err = c.QUERY(IPCTestQuery{Value: "test"})
assert.True(t, handled)
assert.Nil(t, err)
assert.Equal(t, "test-response", res)
}
func TestIPC_QueryAll(t *testing.T) {
c, _ := New()
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "h1", true, nil
})
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "h2", true, nil
})
results, err := c.QUERYALL(IPCTestQuery{})
assert.Nil(t, err)
assert.Len(t, results, 2)
assert.Contains(t, results, "h1")
assert.Contains(t, results, "h2")
}
func TestIPC_Perform(t *testing.T) {
c, _ := New()
c.RegisterTask(func(c *Core, task Task) (any, bool, error) {
if tt, ok := task.(IPCTestTask); ok {
if tt.Value == "error" {
return nil, true, errors.New("task error")
}
return "done", true, nil
}
return nil, false, nil
})
// Success
res, handled, err := c.PERFORM(IPCTestTask{Value: "run"})
assert.True(t, handled)
assert.Nil(t, err)
assert.Equal(t, "done", res)
// Error
res, handled, err = c.PERFORM(IPCTestTask{Value: "error"})
assert.True(t, handled)
assert.Error(t, err)
assert.Nil(t, res)
}
func TestIPC_PerformAsync(t *testing.T) {
c, _ := New()
type AsyncResult struct {
TaskID string
Result any
Error error
}
done := make(chan AsyncResult, 1)
c.RegisterTask(func(c *Core, task Task) (any, bool, error) {
if tt, ok := task.(IPCTestTask); ok {
return tt.Value + "-done", true, nil
}
return nil, false, nil
})
c.RegisterAction(func(c *Core, msg Message) error {
if m, ok := msg.(ActionTaskCompleted); ok {
done <- AsyncResult{
TaskID: m.TaskID,
Result: m.Result,
Error: m.Error,
}
}
func TestAction_Good(t *testing.T) {
c := New()
var received Message
c.RegisterAction(func(_ *Core, msg Message) error {
received = msg
return nil
})
taskID := c.PerformAsync(IPCTestTask{Value: "async"})
assert.NotEmpty(t, taskID)
select {
case res := <-done:
assert.Equal(t, taskID, res.TaskID)
assert.Equal(t, "async-done", res.Result)
assert.Nil(t, res.Error)
case <-time.After(time.Second):
t.Fatal("timed out waiting for task completion")
}
err := c.ACTION(testMessage{payload: "hello"})
assert.NoError(t, err)
assert.Equal(t, testMessage{payload: "hello"}, received)
}
func TestAction_Multiple_Good(t *testing.T) {
c := New()
count := 0
handler := func(_ *Core, _ Message) error { count++; return nil }
c.RegisterActions(handler, handler, handler)
_ = c.ACTION(nil)
assert.Equal(t, 3, count)
}
func TestAction_None_Good(t *testing.T) {
c := New()
// No handlers registered — should not error
err := c.ACTION(nil)
assert.NoError(t, err)
}
// --- IPC: Queries ---
func TestQuery_Good(t *testing.T) {
c := New()
c.RegisterQuery(func(_ *Core, q Query) (any, bool, error) {
if q == "ping" {
return "pong", true, nil
}
return nil, false, nil
})
result, handled, err := c.QUERY("ping")
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "pong", result)
}
func TestQuery_Unhandled_Good(t *testing.T) {
c := New()
c.RegisterQuery(func(_ *Core, q Query) (any, bool, error) {
return nil, false, nil
})
_, handled, err := c.QUERY("unknown")
assert.NoError(t, err)
assert.False(t, handled)
}
func TestQueryAll_Good(t *testing.T) {
c := New()
c.RegisterQuery(func(_ *Core, _ Query) (any, bool, error) {
return "a", true, nil
})
c.RegisterQuery(func(_ *Core, _ Query) (any, bool, error) {
return "b", true, nil
})
results, err := c.QUERYALL("anything")
assert.NoError(t, err)
assert.Len(t, results, 2)
assert.Contains(t, results, "a")
assert.Contains(t, results, "b")
}
// --- IPC: Tasks ---
func TestPerform_Good(t *testing.T) {
c := New()
c.RegisterTask(func(_ *Core, t Task) (any, bool, error) {
if t == "compute" {
return 42, true, nil
}
return nil, false, nil
})
result, handled, err := c.PERFORM("compute")
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, 42, result)
}

69
tests/lock_test.go Normal file
View file

@ -0,0 +1,69 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Lock (Named Mutexes) ---
func TestLock_Good(t *testing.T) {
c := New()
lock := c.Lock("test")
assert.NotNil(t, lock)
assert.NotNil(t, lock.Mu)
}
func TestLock_SameName_Good(t *testing.T) {
c := New()
l1 := c.Lock("shared")
l2 := c.Lock("shared")
// Same name returns same lock
assert.Equal(t, l1, l2)
}
func TestLock_DifferentName_Good(t *testing.T) {
c := New()
l1 := c.Lock("a")
l2 := c.Lock("b")
assert.NotEqual(t, l1, l2)
}
func TestLock_MutexWorks_Good(t *testing.T) {
c := New()
lock := c.Lock("counter")
counter := 0
lock.Mu.Lock()
counter++
lock.Mu.Unlock()
assert.Equal(t, 1, counter)
}
func TestLockEnable_Good(t *testing.T) {
c := New()
c.Service("early", struct{}{})
c.LockEnable()
c.LockApply()
// After lock, registration should fail
result := c.Service("late", struct{}{})
assert.NotNil(t, result)
}
func TestStartables_Good(t *testing.T) {
c := New()
svc := &testService{name: "s"}
c.Service("s", svc)
startables := c.Startables()
assert.Len(t, startables, 1)
}
func TestStoppables_Good(t *testing.T) {
c := New()
svc := &testService{name: "s"}
c.Service("s", svc)
stoppables := c.Stoppables()
assert.Len(t, stoppables, 1)
}

37
tests/log_test.go Normal file
View file

@ -0,0 +1,37 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Log (Structured Logger) ---
func TestLog_New_Good(t *testing.T) {
l := NewLog(LogOpts{Level: LevelInfo})
assert.NotNil(t, l)
}
func TestLog_Levels_Good(t *testing.T) {
for _, level := range []Level{LevelDebug, LevelInfo, LevelWarn, LevelError} {
l := NewLog(LogOpts{Level: level})
l.Debug("debug msg")
l.Info("info msg")
l.Warn("warn msg")
l.Error("error msg")
}
}
func TestLog_CoreLog_Good(t *testing.T) {
c := New()
assert.NotNil(t, c.Log())
}
func TestLog_ErrorSink_Interface(t *testing.T) {
l := NewLog(LogOpts{Level: LevelInfo})
var sink ErrorSink = l
sink.Error("test", "key", "val")
sink.Warn("test", "key", "val")
}

View file

@ -1,176 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"errors"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBus_Action_Good(t *testing.T) {
c, _ := New()
var received []Message
c.IPC().RegisterAction(func(_ *Core, msg Message) error {
received = append(received, msg)
return nil
})
c.IPC().RegisterAction(func(_ *Core, msg Message) error {
received = append(received, msg)
return nil
})
err := c.IPC().Action("hello")
assert.NoError(t, err)
assert.Len(t, received, 2)
}
func TestBus_Action_Bad(t *testing.T) {
c, _ := New()
err1 := errors.New("handler1 failed")
err2 := errors.New("handler2 failed")
c.IPC().RegisterAction(func(_ *Core, msg Message) error { return err1 })
c.IPC().RegisterAction(func(_ *Core, msg Message) error { return nil })
c.IPC().RegisterAction(func(_ *Core, msg Message) error { return err2 })
err := c.IPC().Action("test")
assert.Error(t, err)
assert.ErrorIs(t, err, err1)
assert.ErrorIs(t, err, err2)
}
func TestBus_RegisterAction_Good(t *testing.T) {
c, _ := New()
var coreRef *Core
c.IPC().RegisterAction(func(core *Core, msg Message) error {
coreRef = core
return nil
})
_ = c.IPC().Action(nil)
assert.Same(t, c, coreRef, "handler should receive the Core reference")
}
func TestBus_Query_Good(t *testing.T) {
c, _ := New()
c.IPC().RegisterQuery(func(_ *Core, q Query) (any, bool, error) {
return "first", true, nil
})
result, handled, err := c.IPC().Query(TestQuery{Value: "test"})
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "first", result)
}
func TestBus_QueryAll_Good(t *testing.T) {
c, _ := New()
c.IPC().RegisterQuery(func(_ *Core, q Query) (any, bool, error) {
return "a", true, nil
})
c.IPC().RegisterQuery(func(_ *Core, q Query) (any, bool, error) {
return nil, false, nil // skips
})
c.IPC().RegisterQuery(func(_ *Core, q Query) (any, bool, error) {
return "b", true, nil
})
results, err := c.IPC().QueryAll(TestQuery{})
assert.NoError(t, err)
assert.Equal(t, []any{"a", "b"}, results)
}
func TestBus_Perform_Good(t *testing.T) {
c, _ := New()
c.IPC().RegisterTask(func(_ *Core, t Task) (any, bool, error) {
return "done", true, nil
})
result, handled, err := c.IPC().Perform(TestTask{})
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "done", result)
}
func TestBus_ConcurrentAccess_Good(t *testing.T) {
c, _ := New()
var wg sync.WaitGroup
const goroutines = 20
// Concurrent register + dispatch
for i := 0; i < goroutines; i++ {
wg.Add(2)
go func() {
defer wg.Done()
c.IPC().RegisterAction(func(_ *Core, msg Message) error { return nil })
}()
go func() {
defer wg.Done()
_ = c.IPC().Action("ping")
}()
}
for i := 0; i < goroutines; i++ {
wg.Add(2)
go func() {
defer wg.Done()
c.IPC().RegisterQuery(func(_ *Core, q Query) (any, bool, error) { return nil, false, nil })
}()
go func() {
defer wg.Done()
_, _ = c.IPC().QueryAll(TestQuery{})
}()
}
for i := 0; i < goroutines; i++ {
wg.Add(2)
go func() {
defer wg.Done()
c.IPC().RegisterTask(func(_ *Core, t Task) (any, bool, error) { return nil, false, nil })
}()
go func() {
defer wg.Done()
_, _, _ = c.IPC().Perform(TestTask{})
}()
}
wg.Wait()
}
func TestBus_Action_NoHandlers(t *testing.T) {
c, _ := New()
err := c.IPC().Action("no one listening")
assert.NoError(t, err)
}
func TestBus_Query_NoHandlers(t *testing.T) {
c, _ := New()
result, handled, err := c.IPC().Query(TestQuery{})
assert.NoError(t, err)
assert.False(t, handled)
assert.Nil(t, result)
}
func TestBus_QueryAll_NoHandlers(t *testing.T) {
c, _ := New()
results, err := c.IPC().QueryAll(TestQuery{})
assert.NoError(t, err)
assert.Empty(t, results)
}
func TestBus_Perform_NoHandlers(t *testing.T) {
c, _ := New()
result, handled, err := c.IPC().Perform(TestTask{})
assert.NoError(t, err)
assert.False(t, handled)
assert.Nil(t, result)
}

94
tests/options_test.go Normal file
View file

@ -0,0 +1,94 @@
package core_test
import (
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- Option / Options ---
func TestOptions_Get_Good(t *testing.T) {
opts := Options{
{K: "name", V: "brain"},
{K: "port", V: 8080},
}
val, ok := opts.Get("name")
assert.True(t, ok)
assert.Equal(t, "brain", val)
}
func TestOptions_Get_Bad(t *testing.T) {
opts := Options{{K: "name", V: "brain"}}
val, ok := opts.Get("missing")
assert.False(t, ok)
assert.Nil(t, val)
}
func TestOptions_Has_Good(t *testing.T) {
opts := Options{{K: "debug", V: true}}
assert.True(t, opts.Has("debug"))
assert.False(t, opts.Has("missing"))
}
func TestOptions_String_Good(t *testing.T) {
opts := Options{{K: "name", V: "brain"}}
assert.Equal(t, "brain", opts.String("name"))
}
func TestOptions_String_Bad(t *testing.T) {
opts := Options{{K: "port", V: 8080}}
// Wrong type — returns empty string
assert.Equal(t, "", opts.String("port"))
// Missing key — returns empty string
assert.Equal(t, "", opts.String("missing"))
}
func TestOptions_Int_Good(t *testing.T) {
opts := Options{{K: "port", V: 8080}}
assert.Equal(t, 8080, opts.Int("port"))
}
func TestOptions_Int_Bad(t *testing.T) {
opts := Options{{K: "name", V: "brain"}}
assert.Equal(t, 0, opts.Int("name"))
assert.Equal(t, 0, opts.Int("missing"))
}
func TestOptions_Bool_Good(t *testing.T) {
opts := Options{{K: "debug", V: true}}
assert.True(t, opts.Bool("debug"))
}
func TestOptions_Bool_Bad(t *testing.T) {
opts := Options{{K: "name", V: "brain"}}
assert.False(t, opts.Bool("name"))
assert.False(t, opts.Bool("missing"))
}
func TestOptions_TypedStruct_Good(t *testing.T) {
// Packages plug typed structs into Option.V
type BrainConfig struct {
Name string
OllamaURL string
Collection string
}
cfg := BrainConfig{Name: "brain", OllamaURL: "http://localhost:11434", Collection: "openbrain"}
opts := Options{{K: "config", V: cfg}}
val, ok := opts.Get("config")
assert.True(t, ok)
bc, ok := val.(BrainConfig)
assert.True(t, ok)
assert.Equal(t, "brain", bc.Name)
assert.Equal(t, "http://localhost:11434", bc.OllamaURL)
}
func TestOptions_Empty_Good(t *testing.T) {
opts := Options{}
assert.False(t, opts.Has("anything"))
assert.Equal(t, "", opts.String("anything"))
assert.Equal(t, 0, opts.Int("anything"))
assert.False(t, opts.Bool("anything"))
}

View file

@ -1,203 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
type TestQuery struct {
Value string
}
type TestTask struct {
Value string
}
func TestCore_QUERY_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// Register a handler that responds to TestQuery
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
if tq, ok := q.(TestQuery); ok {
return "result-" + tq.Value, true, nil
}
return nil, false, nil
})
result, handled, err := c.QUERY(TestQuery{Value: "test"})
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "result-test", result)
}
func TestCore_QUERY_NotHandled(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// No handlers registered
result, handled, err := c.QUERY(TestQuery{Value: "test"})
assert.NoError(t, err)
assert.False(t, handled)
assert.Nil(t, result)
}
func TestCore_QUERY_FirstResponder(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// First handler responds
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "first", true, nil
})
// Second handler would respond but shouldn't be called
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "second", true, nil
})
result, handled, err := c.QUERY(TestQuery{})
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "first", result)
}
func TestCore_QUERY_SkipsNonHandlers(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// First handler doesn't handle
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return nil, false, nil
})
// Second handler responds
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "second", true, nil
})
result, handled, err := c.QUERY(TestQuery{})
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "second", result)
}
func TestCore_QUERYALL_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// Multiple handlers respond
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "first", true, nil
})
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "second", true, nil
})
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return nil, false, nil // Doesn't handle
})
results, err := c.QUERYALL(TestQuery{})
assert.NoError(t, err)
assert.Len(t, results, 2)
assert.Contains(t, results, "first")
assert.Contains(t, results, "second")
}
func TestCore_QUERYALL_AggregatesErrors(t *testing.T) {
c, err := New()
assert.NoError(t, err)
err1 := errors.New("error1")
err2 := errors.New("error2")
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "result1", true, err1
})
c.RegisterQuery(func(c *Core, q Query) (any, bool, error) {
return "result2", true, err2
})
results, err := c.QUERYALL(TestQuery{})
assert.Error(t, err)
assert.ErrorIs(t, err, err1)
assert.ErrorIs(t, err, err2)
assert.Len(t, results, 2)
}
func TestCore_PERFORM_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
executed := false
c.RegisterTask(func(c *Core, t Task) (any, bool, error) {
if tt, ok := t.(TestTask); ok {
executed = true
return "done-" + tt.Value, true, nil
}
return nil, false, nil
})
result, handled, err := c.PERFORM(TestTask{Value: "work"})
assert.NoError(t, err)
assert.True(t, handled)
assert.True(t, executed)
assert.Equal(t, "done-work", result)
}
func TestCore_PERFORM_NotHandled(t *testing.T) {
c, err := New()
assert.NoError(t, err)
// No handlers registered
result, handled, err := c.PERFORM(TestTask{Value: "work"})
assert.NoError(t, err)
assert.False(t, handled)
assert.Nil(t, result)
}
func TestCore_PERFORM_FirstResponder(t *testing.T) {
c, err := New()
assert.NoError(t, err)
callCount := 0
c.RegisterTask(func(c *Core, t Task) (any, bool, error) {
callCount++
return "first", true, nil
})
c.RegisterTask(func(c *Core, t Task) (any, bool, error) {
callCount++
return "second", true, nil
})
result, handled, err := c.PERFORM(TestTask{})
assert.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "first", result)
assert.Equal(t, 1, callCount) // Only first handler called
}
func TestCore_PERFORM_WithError(t *testing.T) {
c, err := New()
assert.NoError(t, err)
expectedErr := errors.New("task failed")
c.RegisterTask(func(c *Core, t Task) (any, bool, error) {
return nil, true, expectedErr
})
result, handled, err := c.PERFORM(TestTask{})
assert.Error(t, err)
assert.ErrorIs(t, err, expectedErr)
assert.True(t, handled)
assert.Nil(t, result)
}

View file

@ -1,20 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewWithFactories_EmptyName(t *testing.T) {
factories := map[string]ServiceFactory{
"": func() (any, error) {
return &MockService{Name: "test"}, nil
},
}
_, err := NewWithFactories(nil, factories)
assert.Error(t, err)
assert.Contains(t, err.Error(), "service name cannot be empty")
}

View file

@ -1,129 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewRuntime(t *testing.T) {
testCases := []struct {
name string
app any
factories map[string]ServiceFactory
expectErr bool
expectErrStr string
checkRuntime func(*testing.T, *Runtime)
}{
{
name: "Good path",
app: nil,
factories: map[string]ServiceFactory{},
expectErr: false,
checkRuntime: func(t *testing.T, rt *Runtime) {
assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
},
},
{
name: "With non-nil app",
app: &mockApp{},
factories: map[string]ServiceFactory{},
expectErr: false,
checkRuntime: func(t *testing.T, rt *Runtime) {
assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
assert.NotNil(t, rt.Core.App)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rt, err := NewRuntime(tc.app)
if tc.expectErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), tc.expectErrStr)
assert.Nil(t, rt)
} else {
assert.NoError(t, err)
if tc.checkRuntime != nil {
tc.checkRuntime(t, rt)
}
}
})
}
}
func TestNewWithFactories_Good(t *testing.T) {
factories := map[string]ServiceFactory{
"test": func() (any, error) {
return &MockService{Name: "test"}, nil
},
}
rt, err := NewWithFactories(nil, factories)
assert.NoError(t, err)
assert.NotNil(t, rt)
svc := rt.Core.Service("test")
assert.NotNil(t, svc)
mockSvc, ok := svc.(*MockService)
assert.True(t, ok)
assert.Equal(t, "test", mockSvc.Name)
}
func TestNewWithFactories_Bad(t *testing.T) {
factories := map[string]ServiceFactory{
"test": func() (any, error) {
return nil, assert.AnError
},
}
_, err := NewWithFactories(nil, factories)
assert.Error(t, err)
assert.ErrorIs(t, err, assert.AnError)
}
func TestNewWithFactories_Ugly(t *testing.T) {
factories := map[string]ServiceFactory{
"test": nil,
}
_, err := NewWithFactories(nil, factories)
assert.Error(t, err)
assert.Contains(t, err.Error(), "factory is nil")
}
func TestRuntime_Lifecycle_Good(t *testing.T) {
rt, err := NewRuntime(nil)
assert.NoError(t, err)
assert.NotNil(t, rt)
// ServiceName
assert.Equal(t, "Core", rt.ServiceName())
// ServiceStartup & ServiceShutdown
// These are simple wrappers around the core methods, which are tested in core_test.go.
// We call them here to ensure coverage.
rt.ServiceStartup(context.TODO(), nil)
rt.ServiceShutdown(context.TODO())
// Test shutdown with nil core
rt.Core = nil
rt.ServiceShutdown(context.TODO())
}
func TestNewServiceRuntime_Good(t *testing.T) {
c, err := New()
assert.NoError(t, err)
sr := NewServiceRuntime(c, "test options")
assert.NotNil(t, sr)
assert.Equal(t, c, sr.Core())
// We can't directly test sr.Cfg() without a registered config service,
// as it will panic.
assert.Panics(t, func() {
sr.Config()
})
}

97
tests/runtime_test.go Normal file
View file

@ -0,0 +1,97 @@
package core_test
import (
"context"
"errors"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- ServiceRuntime ---
type testOpts struct {
URL string
Timeout int
}
type runtimeService struct {
*ServiceRuntime[testOpts]
}
func TestServiceRuntime_Good(t *testing.T) {
c := New()
opts := testOpts{URL: "https://api.lthn.ai", Timeout: 30}
rt := NewServiceRuntime(c, opts)
assert.Equal(t, c, rt.Core())
assert.Equal(t, opts, rt.Opts())
assert.Equal(t, "https://api.lthn.ai", rt.Opts().URL)
assert.Equal(t, 30, rt.Opts().Timeout)
assert.NotNil(t, rt.Config())
}
func TestServiceRuntime_Embedded_Good(t *testing.T) {
c := New()
svc := &runtimeService{
ServiceRuntime: NewServiceRuntime(c, testOpts{URL: "https://lthn.sh"}),
}
assert.Equal(t, "https://lthn.sh", svc.Opts().URL)
}
// --- NewWithFactories ---
func TestNewWithFactories_Good(t *testing.T) {
rt, err := NewWithFactories(nil, map[string]ServiceFactory{
"svc1": func() (any, error) { return &testService{name: "one"}, nil },
"svc2": func() (any, error) { return &testService{name: "two"}, nil },
})
assert.NoError(t, err)
assert.NotNil(t, rt)
assert.NotNil(t, rt.Core)
svc := rt.Core.Service("svc1")
assert.NotNil(t, svc)
ts, ok := svc.(*testService)
assert.True(t, ok)
assert.Equal(t, "one", ts.name)
}
func TestNewWithFactories_Bad(t *testing.T) {
// Nil factory
_, err := NewWithFactories(nil, map[string]ServiceFactory{
"bad": nil,
})
assert.Error(t, err)
// Factory returns error
_, err = NewWithFactories(nil, map[string]ServiceFactory{
"fail": func() (any, error) { return nil, errors.New("factory failed") },
})
assert.Error(t, err)
}
func TestNewRuntime_Good(t *testing.T) {
rt, err := NewRuntime(nil)
assert.NoError(t, err)
assert.NotNil(t, rt)
}
// --- Lifecycle via Runtime ---
func TestRuntime_Lifecycle_Good(t *testing.T) {
svc := &testService{name: "lifecycle"}
rt, err := NewWithFactories(nil, map[string]ServiceFactory{
"test": func() (any, error) { return svc, nil },
})
assert.NoError(t, err)
err = rt.ServiceStartup(context.Background(), nil)
assert.NoError(t, err)
assert.True(t, svc.started)
err = rt.ServiceShutdown(context.Background())
assert.NoError(t, err)
assert.True(t, svc.stopped)
}

View file

@ -1,116 +0,0 @@
package core_test
import (
. "forge.lthn.ai/core/go/pkg/core"
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestServiceManager_RegisterService_Good(t *testing.T) {
c, _ := New()
err := c.RegisterService("svc1", &MockService{Name: "one"})
assert.NoError(t, err)
got := c.Service("svc1")
assert.NotNil(t, got)
assert.Equal(t, "one", got.(*MockService).GetName())
}
func TestServiceManager_RegisterService_Bad(t *testing.T) {
c, _ := New()
// Empty name
err := c.RegisterService("", &MockService{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot be empty")
// Duplicate
err = c.RegisterService("dup", &MockService{})
assert.NoError(t, err)
err = c.RegisterService("dup", &MockService{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "already registered")
// Locked
c2, _ := New(WithServiceLock())
err = c2.RegisterService("late", &MockService{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "serviceLock")
}
func TestServiceManager_ServiceNotFound_Good(t *testing.T) {
c, _ := New()
assert.Nil(t, c.Service("nonexistent"))
}
func TestServiceManager_Startables_Good(t *testing.T) {
s1 := &MockStartable{}
s2 := &MockStartable{}
c, _ := New(
WithName("s1", func(_ *Core) (any, error) { return s1, nil }),
WithName("s2", func(_ *Core) (any, error) { return s2, nil }),
)
// Startup should call both
err := c.ServiceStartup(context.Background(), nil)
assert.NoError(t, err)
}
func TestServiceManager_Stoppables_Good(t *testing.T) {
s1 := &MockStoppable{}
s2 := &MockStoppable{}
c, _ := New(
WithName("s1", func(_ *Core) (any, error) { return s1, nil }),
WithName("s2", func(_ *Core) (any, error) { return s2, nil }),
)
// Shutdown should call both
err := c.ServiceShutdown(context.Background())
assert.NoError(t, err)
}
func TestServiceManager_Lock_Good(t *testing.T) {
c, _ := New(
WithName("early", func(_ *Core) (any, error) { return &MockService{}, nil }),
WithServiceLock(),
)
// Register after lock — should fail
err := c.RegisterService("late", &MockService{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "serviceLock")
// Early service is still accessible
assert.NotNil(t, c.Service("early"))
}
func TestServiceManager_LockNotAppliedWithoutEnable_Good(t *testing.T) {
// No WithServiceLock — should allow registration after New()
c, _ := New()
err := c.RegisterService("svc", &MockService{})
assert.NoError(t, err)
}
type mockFullLifecycle struct{}
func (m *mockFullLifecycle) OnStartup(_ context.Context) error { return nil }
func (m *mockFullLifecycle) OnShutdown(_ context.Context) error { return nil }
func TestServiceManager_LifecycleBoth_Good(t *testing.T) {
svc := &mockFullLifecycle{}
c, _ := New(
WithName("both", func(_ *Core) (any, error) { return svc, nil }),
)
// Should participate in both startup and shutdown
err := c.ServiceStartup(context.Background(), nil)
assert.NoError(t, err)
err = c.ServiceShutdown(context.Background())
assert.NoError(t, err)
}

102
tests/service_test.go Normal file
View file

@ -0,0 +1,102 @@
package core_test
import (
"context"
"testing"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
type testService struct {
name string
started bool
stopped bool
}
func (s *testService) OnStartup(_ context.Context) error { s.started = true; return nil }
func (s *testService) OnShutdown(_ context.Context) error { s.stopped = true; return nil }
// --- Service Registration ---
func TestService_Register_Good(t *testing.T) {
c := New()
svc := &testService{name: "auth"}
result := c.Service("auth", svc)
assert.Nil(t, result) // nil = success
got := c.Service("auth")
assert.Equal(t, svc, got)
}
func TestService_Register_Bad(t *testing.T) {
c := New()
svc := &testService{name: "auth"}
// Register once — ok
c.Service("auth", svc)
// Register duplicate — returns error
result := c.Service("auth", svc)
assert.NotNil(t, result)
// Empty name — returns error
result = c.Service("", svc)
assert.NotNil(t, result)
}
func TestService_Get_Good(t *testing.T) {
c := New()
c.Service("brain", &testService{name: "brain"})
svc := c.Service("brain")
assert.NotNil(t, svc)
ts, ok := svc.(*testService)
assert.True(t, ok)
assert.Equal(t, "brain", ts.name)
}
func TestService_Get_Bad(t *testing.T) {
c := New()
svc := c.Service("nonexistent")
assert.Nil(t, svc)
}
func TestService_Registry_Good(t *testing.T) {
c := New()
// Zero args returns *Service
registry := c.Service()
assert.NotNil(t, registry)
}
// --- Service Lifecycle ---
func TestService_Lifecycle_Good(t *testing.T) {
c := New()
svc := &testService{name: "lifecycle"}
c.Service("lifecycle", svc)
// Startup
err := c.ServiceStartup(context.Background(), nil)
assert.NoError(t, err)
assert.True(t, svc.started)
// Shutdown
err = c.ServiceShutdown(context.Background())
assert.NoError(t, err)
assert.True(t, svc.stopped)
}
func TestService_Lock_Good(t *testing.T) {
c := New()
c.Service("early", &testService{name: "early"})
// Lock service registration
c.LockEnable()
c.LockApply()
// Attempt to register after lock
result := c.Service("late", &testService{name: "late"})
assert.NotNil(t, result) // error — locked
}

66
tests/task_test.go Normal file
View file

@ -0,0 +1,66 @@
package core_test
import (
"sync"
"testing"
"time"
. "forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
)
// --- PerformAsync ---
func TestPerformAsync_Good(t *testing.T) {
c := New()
var mu sync.Mutex
var result string
c.RegisterTask(func(_ *Core, task Task) (any, bool, error) {
mu.Lock()
result = "done"
mu.Unlock()
return "completed", true, nil
})
taskID := c.PerformAsync("work")
assert.NotEmpty(t, taskID)
time.Sleep(100 * time.Millisecond)
mu.Lock()
assert.Equal(t, "done", result)
mu.Unlock()
}
func TestPerformAsync_Progress_Good(t *testing.T) {
c := New()
c.RegisterTask(func(_ *Core, task Task) (any, bool, error) {
return nil, true, nil
})
taskID := c.PerformAsync("work")
c.Progress(taskID, 0.5, "halfway", "work")
}
// --- RegisterAction + RegisterActions ---
func TestRegisterAction_Good(t *testing.T) {
c := New()
called := false
c.RegisterAction(func(_ *Core, _ Message) error {
called = true
return nil
})
_ = c.Action(nil)
assert.True(t, called)
}
func TestRegisterActions_Good(t *testing.T) {
c := New()
count := 0
h := func(_ *Core, _ Message) error { count++; return nil }
c.RegisterActions(h, h)
_ = c.Action(nil)
assert.Equal(t, 2, count)
}