gui/pkg/lifecycle/service_test.go
Snider 6241bdddb6 feat(lifecycle): add application lifecycle core.Service with Platform interface and IPC
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:41:42 +00:00

259 lines
6.1 KiB
Go

// pkg/lifecycle/service_test.go
package lifecycle
import (
"context"
"sync"
"testing"
"forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- Mock Platform ---
type mockPlatform struct {
mu sync.Mutex
handlers map[EventType][]func()
fileHandlers []func(string)
}
func newMockPlatform() *mockPlatform {
return &mockPlatform{
handlers: make(map[EventType][]func()),
}
}
func (m *mockPlatform) OnApplicationEvent(eventType EventType, handler func()) func() {
m.mu.Lock()
defer m.mu.Unlock()
m.handlers[eventType] = append(m.handlers[eventType], handler)
idx := len(m.handlers[eventType]) - 1
return func() {
m.mu.Lock()
defer m.mu.Unlock()
if idx < len(m.handlers[eventType]) {
m.handlers[eventType] = append(m.handlers[eventType][:idx], m.handlers[eventType][idx+1:]...)
}
}
}
func (m *mockPlatform) OnOpenedWithFile(handler func(string)) func() {
m.mu.Lock()
defer m.mu.Unlock()
m.fileHandlers = append(m.fileHandlers, handler)
idx := len(m.fileHandlers) - 1
return func() {
m.mu.Lock()
defer m.mu.Unlock()
if idx < len(m.fileHandlers) {
m.fileHandlers = append(m.fileHandlers[:idx], m.fileHandlers[idx+1:]...)
}
}
}
// simulateEvent fires all registered handlers for the given event type.
func (m *mockPlatform) simulateEvent(eventType EventType) {
m.mu.Lock()
handlers := make([]func(), len(m.handlers[eventType]))
copy(handlers, m.handlers[eventType])
m.mu.Unlock()
for _, h := range handlers {
h()
}
}
// simulateFileOpen fires all registered file-open handlers.
func (m *mockPlatform) simulateFileOpen(path string) {
m.mu.Lock()
handlers := make([]func(string), len(m.fileHandlers))
copy(handlers, m.fileHandlers)
m.mu.Unlock()
for _, h := range handlers {
h(path)
}
}
// handlerCount returns the number of registered handlers for event-based + file-based.
func (m *mockPlatform) handlerCount() int {
m.mu.Lock()
defer m.mu.Unlock()
count := len(m.fileHandlers)
for _, handlers := range m.handlers {
count += len(handlers)
}
return count
}
// --- Test helpers ---
func newTestLifecycleService(t *testing.T) (*Service, *core.Core, *mockPlatform) {
t.Helper()
mock := newMockPlatform()
c, err := core.New(
core.WithService(Register(mock)),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
svc := core.MustServiceFor[*Service](c, "lifecycle")
return svc, c, mock
}
// --- Tests ---
func TestRegister_Good(t *testing.T) {
svc, _, _ := newTestLifecycleService(t)
assert.NotNil(t, svc)
}
func TestApplicationStarted_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionApplicationStarted); ok {
received = true
}
return nil
})
mock.simulateEvent(EventApplicationStarted)
assert.True(t, received)
}
func TestDidBecomeActive_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionDidBecomeActive); ok {
received = true
}
return nil
})
mock.simulateEvent(EventDidBecomeActive)
assert.True(t, received)
}
func TestDidResignActive_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionDidResignActive); ok {
received = true
}
return nil
})
mock.simulateEvent(EventDidResignActive)
assert.True(t, received)
}
func TestWillTerminate_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionWillTerminate); ok {
received = true
}
return nil
})
mock.simulateEvent(EventWillTerminate)
assert.True(t, received)
}
func TestPowerStatusChanged_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionPowerStatusChanged); ok {
received = true
}
return nil
})
mock.simulateEvent(EventPowerStatusChanged)
assert.True(t, received)
}
func TestSystemSuspend_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionSystemSuspend); ok {
received = true
}
return nil
})
mock.simulateEvent(EventSystemSuspend)
assert.True(t, received)
}
func TestSystemResume_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionSystemResume); ok {
received = true
}
return nil
})
mock.simulateEvent(EventSystemResume)
assert.True(t, received)
}
func TestOpenedWithFile_Good(t *testing.T) {
_, c, mock := newTestLifecycleService(t)
var receivedPath string
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if a, ok := msg.(ActionOpenedWithFile); ok {
receivedPath = a.Path
}
return nil
})
mock.simulateFileOpen("/Users/snider/Documents/test.txt")
assert.Equal(t, "/Users/snider/Documents/test.txt", receivedPath)
}
func TestOnShutdown_CancelsAll_Good(t *testing.T) {
svc, _, mock := newTestLifecycleService(t)
// Verify handlers were registered during OnStartup
assert.Greater(t, mock.handlerCount(), 0, "handlers should be registered after OnStartup")
// Shutdown should cancel all registrations
err := svc.OnShutdown(context.Background())
require.NoError(t, err)
assert.Equal(t, 0, mock.handlerCount(), "all handlers should be cancelled after OnShutdown")
}
func TestRegister_Bad(t *testing.T) {
// No lifecycle service registered — actions are not received
c, err := core.New(core.WithServiceLock())
require.NoError(t, err)
var received bool
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionApplicationStarted); ok {
received = true
}
return nil
})
// No way to trigger events without the service
assert.False(t, received)
}