Merge pull request 'feat: inline tests + Fs zero-value fix + coverage 76.9% → 82.3%' (#27) from dev into main
Some checks failed
CI / test (push) Failing after 3s
Some checks failed
CI / test (push) Failing after 3s
Reviewed-on: #27
This commit is contained in:
commit
1450179b9c
26 changed files with 470 additions and 10 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
. "dappco.re/go/core"
|
||||
|
|
@ -74,3 +75,11 @@ func TestCli_PrintHelp_Good(t *testing.T) {
|
|||
c.Command("serve", Command{Action: func(_ Options) Result { return Result{OK: true} }})
|
||||
c.Cli().PrintHelp()
|
||||
}
|
||||
|
||||
func TestCli_SetOutput_Good(t *testing.T) {
|
||||
c := New()
|
||||
var buf bytes.Buffer
|
||||
c.Cli().SetOutput(&buf)
|
||||
c.Cli().Print("hello %s", "world")
|
||||
assert.Contains(t, buf.String(), "hello world")
|
||||
}
|
||||
|
|
@ -121,6 +121,93 @@ func TestCommand_Lifecycle_NoImpl_Good(t *testing.T) {
|
|||
assert.False(t, cmd.Signal("HUP").OK)
|
||||
}
|
||||
|
||||
// --- Lifecycle with Implementation ---
|
||||
|
||||
type testLifecycle struct {
|
||||
started bool
|
||||
stopped bool
|
||||
restarted bool
|
||||
reloaded bool
|
||||
signalled string
|
||||
}
|
||||
|
||||
func (l *testLifecycle) Start(opts Options) Result {
|
||||
l.started = true
|
||||
return Result{Value: "started", OK: true}
|
||||
}
|
||||
func (l *testLifecycle) Stop() Result {
|
||||
l.stopped = true
|
||||
return Result{OK: true}
|
||||
}
|
||||
func (l *testLifecycle) Restart() Result {
|
||||
l.restarted = true
|
||||
return Result{OK: true}
|
||||
}
|
||||
func (l *testLifecycle) Reload() Result {
|
||||
l.reloaded = true
|
||||
return Result{OK: true}
|
||||
}
|
||||
func (l *testLifecycle) Signal(sig string) Result {
|
||||
l.signalled = sig
|
||||
return Result{Value: sig, OK: true}
|
||||
}
|
||||
|
||||
func TestCommand_Lifecycle_WithImpl_Good(t *testing.T) {
|
||||
c := New()
|
||||
lc := &testLifecycle{}
|
||||
c.Command("daemon", Command{Lifecycle: lc})
|
||||
cmd := c.Command("daemon").Value.(*Command)
|
||||
|
||||
r := cmd.Start(Options{})
|
||||
assert.True(t, r.OK)
|
||||
assert.True(t, lc.started)
|
||||
|
||||
assert.True(t, cmd.Stop().OK)
|
||||
assert.True(t, lc.stopped)
|
||||
|
||||
assert.True(t, cmd.Restart().OK)
|
||||
assert.True(t, lc.restarted)
|
||||
|
||||
assert.True(t, cmd.Reload().OK)
|
||||
assert.True(t, lc.reloaded)
|
||||
|
||||
r = cmd.Signal("HUP")
|
||||
assert.True(t, r.OK)
|
||||
assert.Equal(t, "HUP", lc.signalled)
|
||||
}
|
||||
|
||||
func TestCommand_Duplicate_Bad(t *testing.T) {
|
||||
c := New()
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
// --- Cli Run with Lifecycle ---
|
||||
|
||||
func TestCli_Run_Lifecycle_Good(t *testing.T) {
|
||||
c := New()
|
||||
lc := &testLifecycle{}
|
||||
c.Command("serve", Command{Lifecycle: lc})
|
||||
r := c.Cli().Run("serve")
|
||||
assert.True(t, r.OK)
|
||||
assert.True(t, lc.started)
|
||||
}
|
||||
|
||||
func TestCli_Run_NoActionNoLifecycle_Bad(t *testing.T) {
|
||||
c := New()
|
||||
c.Command("empty", Command{})
|
||||
r := c.Cli().Run("empty")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
// --- Empty path ---
|
||||
|
||||
func TestCommand_EmptyPath_Bad(t *testing.T) {
|
||||
|
|
@ -157,6 +157,94 @@ func TestGeneratePack_WithFiles_Good(t *testing.T) {
|
|||
assert.Contains(t, r.Value.(string), "core.AddAsset")
|
||||
}
|
||||
|
||||
// --- Extract (template + nested) ---
|
||||
|
||||
func TestExtract_WithTemplate_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Create an in-memory FS with a template file and a plain file
|
||||
tmplDir := os.DirFS(t.TempDir())
|
||||
|
||||
// Use a real temp dir with files
|
||||
srcDir := t.TempDir()
|
||||
os.WriteFile(srcDir+"/plain.txt", []byte("static content"), 0644)
|
||||
os.WriteFile(srcDir+"/greeting.tmpl", []byte("Hello {{.Name}}!"), 0644)
|
||||
os.MkdirAll(srcDir+"/sub", 0755)
|
||||
os.WriteFile(srcDir+"/sub/nested.txt", []byte("nested"), 0644)
|
||||
|
||||
_ = tmplDir
|
||||
fsys := os.DirFS(srcDir)
|
||||
data := map[string]string{"Name": "World"}
|
||||
|
||||
r := Extract(fsys, dir, data)
|
||||
assert.True(t, r.OK)
|
||||
|
||||
// Plain file copied
|
||||
content, err := os.ReadFile(dir + "/plain.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "static content", string(content))
|
||||
|
||||
// Template processed and .tmpl stripped
|
||||
greeting, err := os.ReadFile(dir + "/greeting")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Hello World!", string(greeting))
|
||||
|
||||
// Nested directory preserved
|
||||
nested, err := os.ReadFile(dir + "/sub/nested.txt")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "nested", string(nested))
|
||||
}
|
||||
|
||||
func TestExtract_BadTargetDir_Ugly(t *testing.T) {
|
||||
srcDir := t.TempDir()
|
||||
os.WriteFile(srcDir+"/f.txt", []byte("x"), 0644)
|
||||
r := Extract(os.DirFS(srcDir), "/nonexistent/deeply/nested/impossible", nil)
|
||||
// Should fail gracefully, not panic
|
||||
_ = r
|
||||
}
|
||||
|
||||
func TestEmbed_PathTraversal_Ugly(t *testing.T) {
|
||||
emb := Mount(testFS, "testdata").Value.(*Embed)
|
||||
r := emb.ReadFile("../../etc/passwd")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestEmbed_Sub_BaseDir_Good(t *testing.T) {
|
||||
emb := Mount(testFS, "testdata").Value.(*Embed)
|
||||
r := emb.Sub("scantest")
|
||||
assert.True(t, r.OK)
|
||||
sub := r.Value.(*Embed)
|
||||
assert.Equal(t, ".", sub.BaseDirectory())
|
||||
}
|
||||
|
||||
func TestEmbed_Open_Bad(t *testing.T) {
|
||||
emb := Mount(testFS, "testdata").Value.(*Embed)
|
||||
r := emb.Open("nonexistent.txt")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestEmbed_ReadDir_Bad(t *testing.T) {
|
||||
emb := Mount(testFS, "testdata").Value.(*Embed)
|
||||
r := emb.ReadDir("nonexistent")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestEmbed_EmbedFS_Original_Good(t *testing.T) {
|
||||
emb := Mount(testFS, "testdata").Value.(*Embed)
|
||||
efs := emb.EmbedFS()
|
||||
_, err := efs.ReadFile("testdata/test.txt")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestExtract_NilData_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
srcDir := t.TempDir()
|
||||
os.WriteFile(srcDir+"/file.txt", []byte("no template"), 0644)
|
||||
|
||||
r := Extract(os.DirFS(srcDir), dir, nil)
|
||||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
func mustCompress(input string) string {
|
||||
var buf bytes.Buffer
|
||||
b64 := base64.NewEncoder(base64.StdEncoding, &buf)
|
||||
|
|
@ -227,3 +227,46 @@ func TestErrorPanic_CrashFile_Good(t *testing.T) {
|
|||
assert.Nil(t, r.Value)
|
||||
_ = path
|
||||
}
|
||||
|
||||
// --- Error formatting branches ---
|
||||
|
||||
func TestErr_Error_WithCode_Good(t *testing.T) {
|
||||
err := WrapCode(errors.New("bad"), "INVALID", "validate", "input failed")
|
||||
assert.Contains(t, err.Error(), "[INVALID]")
|
||||
assert.Contains(t, err.Error(), "validate")
|
||||
assert.Contains(t, err.Error(), "bad")
|
||||
}
|
||||
|
||||
func TestErr_Error_CodeNoCause_Good(t *testing.T) {
|
||||
err := NewCode("NOT_FOUND", "resource missing")
|
||||
assert.Contains(t, err.Error(), "[NOT_FOUND]")
|
||||
assert.Contains(t, err.Error(), "resource missing")
|
||||
}
|
||||
|
||||
func TestErr_Error_NoOp_Good(t *testing.T) {
|
||||
err := &Err{Message: "bare error"}
|
||||
assert.Equal(t, "bare error", err.Error())
|
||||
}
|
||||
|
||||
func TestWrapCode_NilErr_EmptyCode_Good(t *testing.T) {
|
||||
err := WrapCode(nil, "", "op", "msg")
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestWrap_PreservesCode_Good(t *testing.T) {
|
||||
inner := WrapCode(errors.New("root"), "AUTH_FAIL", "auth", "denied")
|
||||
outer := Wrap(inner, "handler", "request failed")
|
||||
assert.Equal(t, "AUTH_FAIL", ErrorCode(outer))
|
||||
}
|
||||
|
||||
func TestErrorLog_Warn_Nil_Good(t *testing.T) {
|
||||
c := New()
|
||||
r := c.LogWarn(nil, "op", "msg")
|
||||
assert.True(t, r.OK)
|
||||
}
|
||||
|
||||
func TestErrorLog_Error_Nil_Good(t *testing.T) {
|
||||
c := New()
|
||||
r := c.LogError(nil, "op", "msg")
|
||||
assert.True(t, r.OK)
|
||||
}
|
||||
25
fs.go
25
fs.go
|
|
@ -15,15 +15,20 @@ type Fs struct {
|
|||
|
||||
// path sanitises and returns the full path.
|
||||
// Absolute paths are sandboxed under root (unless root is "/").
|
||||
// Empty root defaults to "/" — the zero value of Fs is usable.
|
||||
func (m *Fs) path(p string) string {
|
||||
root := m.root
|
||||
if root == "" {
|
||||
root = "/"
|
||||
}
|
||||
if p == "" {
|
||||
return m.root
|
||||
return root
|
||||
}
|
||||
|
||||
// If the path is relative and the medium is rooted at "/",
|
||||
// treat it as relative to the current working directory.
|
||||
// This makes io.Local behave more like the standard 'os' package.
|
||||
if m.root == "/" && !filepath.IsAbs(p) {
|
||||
if root == "/" && !filepath.IsAbs(p) {
|
||||
cwd, _ := os.Getwd()
|
||||
return filepath.Join(cwd, p)
|
||||
}
|
||||
|
|
@ -33,23 +38,27 @@ func (m *Fs) path(p string) string {
|
|||
clean := filepath.Clean("/" + p)
|
||||
|
||||
// If root is "/", allow absolute paths through
|
||||
if m.root == "/" {
|
||||
if root == "/" {
|
||||
return clean
|
||||
}
|
||||
|
||||
// Strip leading "/" so Join works correctly with root
|
||||
return filepath.Join(m.root, clean[1:])
|
||||
return filepath.Join(root, clean[1:])
|
||||
}
|
||||
|
||||
// validatePath ensures the path is within the sandbox, following symlinks if they exist.
|
||||
func (m *Fs) validatePath(p string) Result {
|
||||
if m.root == "/" {
|
||||
root := m.root
|
||||
if root == "" {
|
||||
root = "/"
|
||||
}
|
||||
if root == "/" {
|
||||
return Result{m.path(p), true}
|
||||
}
|
||||
|
||||
// Split the cleaned path into components
|
||||
parts := Split(filepath.Clean("/"+p), string(os.PathSeparator))
|
||||
current := m.root
|
||||
current := root
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
|
|
@ -70,7 +79,7 @@ func (m *Fs) validatePath(p string) Result {
|
|||
}
|
||||
|
||||
// Verify the resolved part is still within the root
|
||||
rel, err := filepath.Rel(m.root, realNext)
|
||||
rel, err := filepath.Rel(root, realNext)
|
||||
if err != nil || HasPrefix(rel, "..") {
|
||||
// Security event: sandbox escape attempt
|
||||
username := "unknown"
|
||||
|
|
@ -78,7 +87,7 @@ func (m *Fs) validatePath(p string) Result {
|
|||
username = u.Username
|
||||
}
|
||||
Print(os.Stderr, "[%s] SECURITY sandbox escape detected root=%s path=%s attempted=%s user=%s",
|
||||
time.Now().Format(time.RFC3339), m.root, p, realNext, username)
|
||||
time.Now().Format(time.RFC3339), root, p, realNext, username)
|
||||
if err == nil {
|
||||
err = E("fs.validatePath", Concat("sandbox escape: ", p, " resolves outside ", m.root), nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,3 +184,74 @@ func TestFs_WriteMode_Good(t *testing.T) {
|
|||
assert.True(t, r.OK)
|
||||
assert.Equal(t, "secret.txt", r.Value.(os.FileInfo).Name())
|
||||
}
|
||||
|
||||
// --- Zero Value ---
|
||||
|
||||
func TestFs_ZeroValue_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
zeroFs := &Fs{}
|
||||
|
||||
path := filepath.Join(dir, "zero.txt")
|
||||
assert.True(t, zeroFs.Write(path, "zero value works").OK)
|
||||
r := zeroFs.Read(path)
|
||||
assert.True(t, r.OK)
|
||||
assert.Equal(t, "zero value works", r.Value.(string))
|
||||
assert.True(t, zeroFs.IsFile(path))
|
||||
assert.True(t, zeroFs.Exists(path))
|
||||
assert.True(t, zeroFs.IsDir(dir))
|
||||
}
|
||||
|
||||
func TestFs_ZeroValue_List_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
zeroFs := &Fs{}
|
||||
|
||||
os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0644)
|
||||
r := zeroFs.List(dir)
|
||||
assert.True(t, r.OK)
|
||||
entries := r.Value.([]fs.DirEntry)
|
||||
assert.Len(t, entries, 1)
|
||||
}
|
||||
|
||||
func TestFs_Exists_NotFound_Bad(t *testing.T) {
|
||||
c := New()
|
||||
assert.False(t, c.Fs().Exists("/nonexistent/path/xyz"))
|
||||
}
|
||||
|
||||
// --- Fs path/validatePath edge cases ---
|
||||
|
||||
func TestFs_Read_EmptyPath_Ugly(t *testing.T) {
|
||||
c := New()
|
||||
r := c.Fs().Read("")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestFs_Write_EmptyPath_Ugly(t *testing.T) {
|
||||
c := New()
|
||||
r := c.Fs().Write("", "data")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestFs_Delete_Protected_Ugly(t *testing.T) {
|
||||
c := New()
|
||||
r := c.Fs().Delete("/")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestFs_DeleteAll_Protected_Ugly(t *testing.T) {
|
||||
c := New()
|
||||
r := c.Fs().DeleteAll("/")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
func TestFs_ReadStream_WriteStream_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
c := New()
|
||||
path := filepath.Join(dir, "stream.txt")
|
||||
c.Fs().Write(path, "streamed")
|
||||
|
||||
r := c.Fs().ReadStream(path)
|
||||
assert.True(t, r.OK)
|
||||
|
||||
w := c.Fs().WriteStream(path)
|
||||
assert.True(t, w.OK)
|
||||
}
|
||||
|
|
@ -142,6 +142,24 @@ func TestLogPanic_Recover_Good(t *testing.T) {
|
|||
func TestLog_SetOutput_Good(t *testing.T) {
|
||||
l := NewLog(LogOptions{Level: LevelInfo})
|
||||
l.SetOutput(os.Stderr)
|
||||
// Should not panic — just changes where logs go
|
||||
l.Info("redirected")
|
||||
}
|
||||
|
||||
// --- Log suppression by level ---
|
||||
|
||||
func TestLog_Quiet_Suppresses_Ugly(t *testing.T) {
|
||||
l := NewLog(LogOptions{Level: LevelQuiet})
|
||||
// These should not panic even though nothing is logged
|
||||
l.Debug("suppressed")
|
||||
l.Info("suppressed")
|
||||
l.Warn("suppressed")
|
||||
l.Error("suppressed")
|
||||
}
|
||||
|
||||
func TestLog_ErrorLevel_Suppresses_Ugly(t *testing.T) {
|
||||
l := NewLog(LogOptions{Level: LevelError})
|
||||
l.Debug("suppressed") // below threshold
|
||||
l.Info("suppressed") // below threshold
|
||||
l.Warn("suppressed") // below threshold
|
||||
l.Error("visible") // at threshold
|
||||
}
|
||||
36
path_test.go
36
path_test.go
|
|
@ -4,6 +4,7 @@ package core_test
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
|
|
@ -72,6 +73,39 @@ func TestPathExt(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPath_EnvConsistency(t *testing.T) {
|
||||
// Path() and Env("DIR_HOME") should agree
|
||||
assert.Equal(t, core.Env("DIR_HOME"), core.Path())
|
||||
}
|
||||
|
||||
func TestPathGlob_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a"), 0644)
|
||||
os.WriteFile(filepath.Join(dir, "b.txt"), []byte("b"), 0644)
|
||||
os.WriteFile(filepath.Join(dir, "c.log"), []byte("c"), 0644)
|
||||
|
||||
matches := core.PathGlob(filepath.Join(dir, "*.txt"))
|
||||
assert.Len(t, matches, 2)
|
||||
}
|
||||
|
||||
func TestPathGlob_NoMatch(t *testing.T) {
|
||||
matches := core.PathGlob("/nonexistent/pattern-*.xyz")
|
||||
assert.Empty(t, matches)
|
||||
}
|
||||
|
||||
func TestPathIsAbs_Good(t *testing.T) {
|
||||
assert.True(t, core.PathIsAbs("/tmp"))
|
||||
assert.True(t, core.PathIsAbs("/"))
|
||||
assert.False(t, core.PathIsAbs("relative"))
|
||||
assert.False(t, core.PathIsAbs(""))
|
||||
}
|
||||
|
||||
func TestCleanPath_Good(t *testing.T) {
|
||||
assert.Equal(t, "/a/b", core.CleanPath("/a//b", "/"))
|
||||
assert.Equal(t, "/a/c", core.CleanPath("/a/b/../c", "/"))
|
||||
assert.Equal(t, "/", core.CleanPath("/", "/"))
|
||||
assert.Equal(t, ".", core.CleanPath("", "/"))
|
||||
}
|
||||
|
||||
func TestPathDir_TrailingSlash(t *testing.T) {
|
||||
result := core.PathDir("/Users/snider/Code/")
|
||||
assert.Equal(t, "/Users/snider/Code", result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,3 +74,48 @@ func TestRuntime_Lifecycle_Good(t *testing.T) {
|
|||
assert.True(t, result.OK)
|
||||
assert.True(t, started)
|
||||
}
|
||||
|
||||
func TestRuntime_ServiceShutdown_Good(t *testing.T) {
|
||||
stopped := false
|
||||
r := NewWithFactories(nil, map[string]ServiceFactory{
|
||||
"test": func() Result {
|
||||
return Result{Value: Service{
|
||||
OnStart: func() Result { return Result{OK: true} },
|
||||
OnStop: func() Result { stopped = true; return Result{OK: true} },
|
||||
}, OK: true}
|
||||
},
|
||||
})
|
||||
assert.True(t, r.OK)
|
||||
rt := r.Value.(*Runtime)
|
||||
|
||||
rt.ServiceStartup(context.Background(), nil)
|
||||
result := rt.ServiceShutdown(context.Background())
|
||||
assert.True(t, result.OK)
|
||||
assert.True(t, stopped)
|
||||
}
|
||||
|
||||
func TestRuntime_ServiceShutdown_NilCore_Good(t *testing.T) {
|
||||
rt := &Runtime{}
|
||||
result := rt.ServiceShutdown(context.Background())
|
||||
assert.True(t, result.OK)
|
||||
}
|
||||
|
||||
func TestCore_ServiceShutdown_Good(t *testing.T) {
|
||||
stopped := false
|
||||
c := New()
|
||||
c.Service("test", Service{
|
||||
OnStart: func() Result { return Result{OK: true} },
|
||||
OnStop: func() Result { stopped = true; return Result{OK: true} },
|
||||
})
|
||||
c.ServiceStartup(context.Background(), nil)
|
||||
result := c.ServiceShutdown(context.Background())
|
||||
assert.True(t, result.OK)
|
||||
assert.True(t, stopped)
|
||||
}
|
||||
|
||||
func TestCore_Context_Good(t *testing.T) {
|
||||
c := New()
|
||||
c.ServiceStartup(context.Background(), nil)
|
||||
assert.NotNil(t, c.Context())
|
||||
c.ServiceShutdown(context.Background())
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -46,6 +47,61 @@ func TestPerformAsync_Progress_Good(t *testing.T) {
|
|||
c.Progress(taskID, 0.5, "halfway", "work")
|
||||
}
|
||||
|
||||
func TestPerformAsync_Completion_Good(t *testing.T) {
|
||||
c := New()
|
||||
completed := make(chan ActionTaskCompleted, 1)
|
||||
|
||||
c.RegisterTask(func(_ *Core, task Task) Result {
|
||||
return Result{Value: "result", OK: true}
|
||||
})
|
||||
c.RegisterAction(func(_ *Core, msg Message) Result {
|
||||
if evt, ok := msg.(ActionTaskCompleted); ok {
|
||||
completed <- evt
|
||||
}
|
||||
return Result{OK: true}
|
||||
})
|
||||
|
||||
c.PerformAsync("work")
|
||||
|
||||
select {
|
||||
case evt := <-completed:
|
||||
assert.Nil(t, evt.Error)
|
||||
assert.Equal(t, "result", evt.Result)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("timed out waiting for completion")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerformAsync_NoHandler_Good(t *testing.T) {
|
||||
c := New()
|
||||
completed := make(chan ActionTaskCompleted, 1)
|
||||
|
||||
c.RegisterAction(func(_ *Core, msg Message) Result {
|
||||
if evt, ok := msg.(ActionTaskCompleted); ok {
|
||||
completed <- evt
|
||||
}
|
||||
return Result{OK: true}
|
||||
})
|
||||
|
||||
c.PerformAsync("unhandled")
|
||||
|
||||
select {
|
||||
case evt := <-completed:
|
||||
assert.NotNil(t, evt.Error)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerformAsync_AfterShutdown_Bad(t *testing.T) {
|
||||
c := New()
|
||||
c.ServiceStartup(context.Background(), nil)
|
||||
c.ServiceShutdown(context.Background())
|
||||
|
||||
r := c.PerformAsync("should not run")
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
// --- RegisterAction + RegisterActions ---
|
||||
|
||||
func TestRegisterAction_Good(t *testing.T) {
|
||||
0
tests/testdata/test.txt → testdata/test.txt
vendored
0
tests/testdata/test.txt → testdata/test.txt
vendored
Loading…
Add table
Reference in a new issue