Skip to content

Commit bf569c4

Browse files
committed
subservers: add unit tests for integrated server startup logic
- Introduce tests for critical and non-critical sub-server startup behavior. - Ensure failures in critical servers stop startup, while non-critical failures are tolerated.
1 parent 6099d31 commit bf569c4

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

subservers/manager_test.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package subservers
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
restProxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
9+
"github.com/lightninglabs/lightning-terminal/litrpc"
10+
"github.com/lightninglabs/lightning-terminal/perms"
11+
"github.com/lightninglabs/lightning-terminal/status"
12+
"github.com/lightninglabs/lndclient"
13+
tafn "github.com/lightninglabs/taproot-assets/fn"
14+
"github.com/lightningnetwork/lnd/lnrpc"
15+
"github.com/stretchr/testify/require"
16+
"google.golang.org/grpc"
17+
"gopkg.in/macaroon-bakery.v2/bakery"
18+
)
19+
20+
// mockSubServer is a lightweight SubServer test double.
21+
type mockSubServer struct {
22+
// name is returned by Name().
23+
name string
24+
25+
// remote toggles Remote() return value.
26+
remote bool
27+
28+
// startErr is returned from Start() when set.
29+
startErr error
30+
31+
// started tracks whether Start() succeeded.
32+
started bool
33+
}
34+
35+
// Name returns the mock sub-server name.
36+
func (t *mockSubServer) Name() string {
37+
return t.name
38+
}
39+
40+
// Remote indicates whether the sub-server runs remotely.
41+
func (t *mockSubServer) Remote() bool {
42+
return t.remote
43+
}
44+
45+
// RemoteConfig returns nil for the mock.
46+
func (t *mockSubServer) RemoteConfig() *RemoteDaemonConfig {
47+
return nil
48+
}
49+
50+
// Start marks the server started unless startErr is set.
51+
func (t *mockSubServer) Start(_ lnrpc.LightningClient,
52+
_ *lndclient.GrpcLndServices, _ bool) error {
53+
54+
if t.startErr != nil {
55+
return t.startErr
56+
}
57+
58+
t.started = true
59+
60+
return nil
61+
}
62+
63+
// Stop marks the server as stopped.
64+
func (t *mockSubServer) Stop() error {
65+
t.started = false
66+
return nil
67+
}
68+
69+
// RegisterGrpcService is a no-op for the mock.
70+
func (t *mockSubServer) RegisterGrpcService(_ grpc.ServiceRegistrar) {}
71+
72+
// RegisterRestService is a no-op for the mock.
73+
func (t *mockSubServer) RegisterRestService(_ context.Context,
74+
_ *restProxy.ServeMux, _ string, _ []grpc.DialOption) error {
75+
76+
return nil
77+
}
78+
79+
// ServerErrChan returns nil for the mock.
80+
func (t *mockSubServer) ServerErrChan() chan error {
81+
return nil
82+
}
83+
84+
// MacPath returns an empty string for the mock.
85+
func (t *mockSubServer) MacPath() string {
86+
return ""
87+
}
88+
89+
// Permissions returns nil for the mock.
90+
func (t *mockSubServer) Permissions() map[string][]bakery.Op {
91+
return nil
92+
}
93+
94+
// WhiteListedURLs returns nil for the mock.
95+
func (t *mockSubServer) WhiteListedURLs() map[string]struct{} {
96+
return nil
97+
}
98+
99+
// Impl returns an empty option for the mock.
100+
func (t *mockSubServer) Impl() tafn.Option[any] {
101+
return tafn.None[any]()
102+
}
103+
104+
// ValidateMacaroon always succeeds for the mock.
105+
func (t *mockSubServer) ValidateMacaroon(context.Context,
106+
[]bakery.Op, string) error {
107+
108+
return nil
109+
}
110+
111+
// newTestManager creates a Manager and status Manager with permissive perms.
112+
func newTestManager(t *testing.T) (*Manager, *status.Manager) {
113+
t.Helper()
114+
115+
permsMgr, err := perms.NewManager(true)
116+
require.NoError(t, err)
117+
118+
statusMgr := status.NewStatusManager()
119+
120+
return NewManager(permsMgr, statusMgr), statusMgr
121+
}
122+
123+
// TestStartIntegratedServersCriticalFailureStopsStartup ensures critical
124+
// startup errors abort integrated startup.
125+
func TestStartIntegratedServersCriticalFailureStopsStartup(t *testing.T) {
126+
manager, statusMgr := newTestManager(t)
127+
128+
nonCritical := &mockSubServer{name: "loop"}
129+
critical := &mockSubServer{
130+
name: TAP,
131+
startErr: errors.New("boom"),
132+
}
133+
134+
require.NoError(t, manager.AddServer(nonCritical, true))
135+
require.NoError(t, manager.AddServer(critical, true))
136+
137+
err := manager.StartIntegratedServers(nil, nil, true)
138+
require.Error(t, err)
139+
require.Contains(t, err.Error(), TAP)
140+
141+
resp, err := statusMgr.SubServerStatus(
142+
context.Background(), &litrpc.SubServerStatusReq{},
143+
)
144+
require.NoError(t, err)
145+
146+
statuses := resp.SubServers
147+
require.Contains(t, statuses, TAP)
148+
require.Equal(t, "boom", statuses[TAP].Error)
149+
require.False(t, statuses[TAP].Running)
150+
151+
require.False(
152+
t, nonCritical.started, "non-critical sub-server should not "+
153+
"start after critical failure",
154+
)
155+
}
156+
157+
// TestStartIntegratedServersNonCriticalFailureContinues verifies non-critical
158+
// startup failures are tolerated.
159+
func TestStartIntegratedServersNonCriticalFailureContinues(t *testing.T) {
160+
manager, statusMgr := newTestManager(t)
161+
162+
failing := &mockSubServer{
163+
name: "loop",
164+
startErr: errors.New("start failed"),
165+
}
166+
succeeding := &mockSubServer{name: "pool"}
167+
168+
require.NoError(t, manager.AddServer(failing, true))
169+
require.NoError(t, manager.AddServer(succeeding, true))
170+
171+
err := manager.StartIntegratedServers(nil, nil, true)
172+
require.NoError(t, err)
173+
174+
resp, err := statusMgr.SubServerStatus(
175+
context.Background(), &litrpc.SubServerStatusReq{},
176+
)
177+
require.NoError(t, err)
178+
179+
statuses := resp.SubServers
180+
181+
require.Contains(t, statuses, failing.name)
182+
require.Equal(t, "start failed", statuses[failing.name].Error)
183+
require.False(t, statuses[failing.name].Running)
184+
185+
require.True(t, succeeding.started)
186+
require.Contains(t, statuses, succeeding.name)
187+
require.True(t, statuses[succeeding.name].Running)
188+
require.Empty(t, statuses[succeeding.name].Error)
189+
}

0 commit comments

Comments
 (0)