go/contract_test.go
Snider 2dff772a40 feat: implement RFC plans 1-5 — Registry[T], Action/Task, Process, primitives
Plans 1-5 complete for core/go scope. 456 tests, 84.4% coverage, 100% AX-7 naming.

Critical bugs (Plan 1):
- P4-3+P7-3: ACTION broadcast calls all handlers with panic recovery
- P7-2+P7-4: RunE() with defer ServiceShutdown, Run() delegates
- P3-1: Startable/Stoppable return Result (breaking, clean)
- P9-1: Zero os/exec — App.Find() rewritten with os.Stat+PATH
- I3: Embed() removed, I15: New() comment fixed
- I9: CommandLifecycle removed → Command.Managed field

Registry[T] (Plan 2):
- Universal thread-safe named collection with 3 lock modes
- All 5 registries migrated: services, commands, drive, data, lock
- Insertion order preserved (fixes P4-1)
- c.RegistryOf("name") cross-cutting accessor

Action/Task system (Plan 3):
- Action type with Run()/Exists(), ActionHandler signature
- c.Action("name") dual-purpose accessor (register/invoke)
- TaskDef with Steps — sequential chain, async dispatch, previous-input piping
- Panic recovery on all Action execution
- broadcast() internal, ACTION() sugar

Process primitive (Plan 4):
- c.Process() returns Action sugar — Run/RunIn/RunWithEnv/Start/Kill/Exists
- No deps added — delegates to c.Action("process.*")
- Permission-by-registration: no handler = no capability

Missing primitives (Plan 5):
- core.ID() — atomic counter + crypto/rand suffix
- ValidateName() / SanitisePath() — reusable validation
- Fs.WriteAtomic() — write-to-temp-then-rename
- Fs.NewUnrestricted() / Fs.Root() — legitimate sandbox bypass
- AX-7: 456/456 tests renamed to TestFile_Function_{Good,Bad,Ugly}

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 15:18:25 +00:00

133 lines
3.6 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package core_test
import (
"context"
"testing"
. "dappco.re/go/core"
"github.com/stretchr/testify/assert"
)
// --- WithService ---
// stub service used only for name-discovery tests.
type stubNamedService struct{}
// stubFactory is a package-level factory so the runtime function name carries
// the package path "core_test.stubFactory" — last segment after '/' is
// "core_test", and after stripping a "_test" suffix we get "core".
// For a real service package such as "dappco.re/go/agentic" the discovered
// name would be "agentic".
func stubFactory(c *Core) Result {
return Result{Value: &stubNamedService{}, OK: true}
}
// TestWithService_NameDiscovery_Good verifies that WithService discovers the
// service name from the factory's package path and registers the instance via
// RegisterService, making it retrievable through c.Services().
//
// stubFactory lives in package "dappco.re/go/core_test", so the last path
// segment is "core_test" — WithService strips the "_test" suffix and registers
// the service under the name "core".
func TestContract_WithService_NameDiscovery_Good(t *testing.T) {
c := New(WithService(stubFactory))
names := c.Services()
// Service should be auto-registered under a discovered name (not just "cli" which is built-in)
assert.Greater(t, len(names), 1, "expected auto-discovered service to be registered alongside built-in 'cli'")
}
// TestWithService_FactorySelfRegisters_Good verifies that when a factory
// returns Result{OK:true} with no Value (it registered itself), WithService
// does not attempt a second registration and returns success.
func TestContract_WithService_FactorySelfRegisters_Good(t *testing.T) {
selfReg := func(c *Core) Result {
// Factory registers directly, returns no instance.
c.Service("self", Service{})
return Result{OK: true}
}
c := New(WithService(selfReg))
// "self" must be present and registered exactly once.
svc := c.Service("self")
assert.True(t, svc.OK, "expected self-registered service to be present")
}
// --- WithName ---
func TestContract_WithName_Good(t *testing.T) {
c := New(
WithName("custom", func(c *Core) Result {
return Result{Value: &stubNamedService{}, OK: true}
}),
)
assert.Contains(t, c.Services(), "custom")
}
// --- Lifecycle ---
type lifecycleService struct {
started bool
}
func (s *lifecycleService) OnStartup(_ context.Context) Result {
s.started = true
return Result{OK: true}
}
func TestContract_WithService_Lifecycle_Good(t *testing.T) {
svc := &lifecycleService{}
c := New(
WithService(func(c *Core) Result {
return Result{Value: svc, OK: true}
}),
)
c.ServiceStartup(context.Background(), nil)
assert.True(t, svc.started)
}
// --- IPC Handler ---
type ipcService struct {
received Message
}
func (s *ipcService) HandleIPCEvents(c *Core, msg Message) Result {
s.received = msg
return Result{OK: true}
}
func TestContract_WithService_IPCHandler_Good(t *testing.T) {
svc := &ipcService{}
c := New(
WithService(func(c *Core) Result {
return Result{Value: svc, OK: true}
}),
)
c.ACTION("ping")
assert.Equal(t, "ping", svc.received)
}
// --- Error ---
// TestWithService_FactoryError_Bad verifies that a failing factory
// stops further option processing (second service not registered).
func TestContract_WithService_FactoryError_Bad(t *testing.T) {
secondCalled := false
c := New(
WithService(func(c *Core) Result {
return Result{Value: E("test", "factory failed", nil), OK: false}
}),
WithService(func(c *Core) Result {
secondCalled = true
return Result{OK: true}
}),
)
assert.NotNil(t, c)
assert.False(t, secondCalled, "second option should not run after first fails")
}