1package app
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "github.com/bnema/zerowrap"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13)
14
15func TestNewKernel(t *testing.T) {
16 t.Parallel()
17
18 tmpDir := t.TempDir()
19 cfgPath := filepath.Join(tmpDir, "gordon.toml")
20 dataDir := filepath.Join(tmpDir, "data")
21
22 cfg := fmt.Sprintf(`[server]
23gordon_domain = "gordon.local"
24data_dir = %q
25
26[auth]
27enabled = false
28secrets_backend = "unsafe"
29`, dataDir)
30
31 err := os.WriteFile(cfgPath, []byte(cfg), 0o600)
32 require.NoError(t, err)
33
34 kernel, err := NewKernel(cfgPath)
35 require.NoError(t, err)
36 require.NotNil(t, kernel)
37 t.Cleanup(func() { require.NoError(t, kernel.Close()) })
38
39 require.NotNil(t, kernel.Config())
40 require.NotNil(t, kernel.Secrets())
41}
42
43// TestKernelClose_QuiescenceTimeoutSkipsCleanup proves the fail-closed kernel
44// Close ordering: daemon-owned app administration is cancelled and joined on
45// a bounded context before any remaining kernel resource is torn down, and
46// when that quiescence times out the dependency teardown is skipped and the
47// error is returned, so an unfinished deploy is never dropped onto a closed
48// state or runtime.
49func TestKernelClose_QuiescenceTimeoutSkipsCleanup(t *testing.T) {
50 t.Parallel()
51
52 order := []string{}
53 admin := &orderRecordingAdmin{order: &order, err: errors.New("in-flight deploy did not unwind")}
54 cleanedUp := false
55 kernel := &Kernel{
56 appAdmin: admin,
57 log: zerowrap.Default(),
58 cleanup: func() { cleanedUp = true; order = append(order, "cleanup") },
59 }
60
61 require.Error(t, kernel.Close())
62 require.Equal(t, []string{"app-shutdown"}, order, "teardown must stop when quiescence times out")
63 assert.False(t, cleanedUp)
64 assert.True(t, admin.deadline, "app administration shutdown must run on a bounded context")
65}
66
67// TestKernelClose_CleansUpAfterQuiescence keeps the normal path: once app
68// administration joined cleanly, the kernel still tears its resources down.
69func TestKernelClose_CleansUpAfterQuiescence(t *testing.T) {
70 t.Parallel()
71
72 order := []string{}
73 admin := &orderRecordingAdmin{order: &order}
74 kernel := &Kernel{
75 appAdmin: admin,
76 log: zerowrap.Default(),
77 cleanup: func() { order = append(order, "cleanup") },
78 }
79
80 require.NoError(t, kernel.Close())
81 require.Equal(t, []string{"app-shutdown", "cleanup"}, order)
82}
83
84// TestKernelClose_WithoutAppAdministrationStillCleansUp keeps the minimal
85// kernel path (no full service wiring) closing without a nil-interface trap.
86func TestKernelClose_WithoutAppAdministrationStillCleansUp(t *testing.T) {
87 t.Parallel()
88
89 cleaned := false
90 kernel := &Kernel{log: zerowrap.Default(), cleanup: func() { cleaned = true }}
91
92 require.NoError(t, kernel.Close())
93 assert.True(t, cleaned)
94}