1package daemon
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/require"
7
8 "github.com/bnema/vev/internal/domain"
9 "github.com/bnema/vev/internal/protocol"
10)
11
12func attentionRouteEntry(key uint64, name string, marker byte, seq uint64) protocol.RecentRouteEntry {
13 entry := testRouteEntry(key, 1, name, marker, protocol.RouteKindRemote)
14 entry.HostLabel = "box"
15 entry.Attention = seq != 0
16 entry.AttentionSeq = seq
17 return entry
18}
19
20// TestOldestRouteAttention pins the cross-daemon half of jump-to-attention
21// (Plan 003 C5): the oldest client-observed bell wins, and a lifecycle this
22// daemon owns is never delegated to the client.
23func TestOldestRouteAttention(t *testing.T) {
24 owned := testRouteTarget("mine", 9).LifecycleID
25 tests := []struct {
26 name string
27 entries []protocol.RecentRouteEntry
28 wantKey uint64
29 wantOK bool
30 }{
31 {name: "no bells", entries: []protocol.RecentRouteEntry{attentionRouteEntry(1, "a", 1, 0)}},
32 {
33 name: "oldest onset wins over recency",
34 entries: []protocol.RecentRouteEntry{attentionRouteEntry(1, "new", 1, 7), attentionRouteEntry(2, "old", 2, 3), attentionRouteEntry(3, "quiet", 3, 0)},
35 wantKey: 2, wantOK: true,
36 },
37 {
38 name: "this daemon's own lifecycle is skipped",
39 entries: []protocol.RecentRouteEntry{attentionRouteEntry(1, "mine", 9, 1), attentionRouteEntry(2, "theirs", 2, 4)},
40 wantKey: 2, wantOK: true,
41 },
42 }
43 for _, tt := range tests {
44 t.Run(tt.name, func(t *testing.T) {
45 snapshot := protocol.RecentRouteSnapshot{Generation: 5, Entries: tt.entries}
46 require.NoError(t, snapshot.Validate())
47 action, ok := oldestRouteAttention(snapshot, func(lifecycle domain.SessionLifecycleID) bool { return lifecycle == owned })
48 require.Equal(t, tt.wantOK, ok)
49 if tt.wantOK {
50 require.Equal(t, protocol.RouteNavigationAction{SnapshotGeneration: 5, Key: tt.wantKey, Generation: 1}, action)
51 }
52 })
53 }
54}
55
56// TestJumpAttentionDelegatesOtherDaemonsToTheClient drives the real jump:
57// with no bell on this daemon, the oldest route bell becomes a client
58// navigation request on the attachment.
59func TestJumpAttentionDelegatesOtherDaemonsToTheClient(t *testing.T) {
60 d, source, ac, _ := newManualSessionWithPTYs(t, nil)
61 transport := &closeTrackingTransport{}
62 ac.replaceTransport(transport)
63 rc := d.attachCoordinator(source, nil, ac, true)
64 token := source.captureAttachmentCapability(ac, transport)
65 token.lease = rc.attachmentLease(ac)
66 ac.installTestAttachmentCapability(token)
67 effect, admitted := ac.beginAttachmentEffect(token)
68 require.True(t, admitted)
69 defer effect.End()
70 ac.setRouteSnapshot(protocol.RecentRouteSnapshot{
71 Generation: 6,
72 Entries: []protocol.RecentRouteEntry{attentionRouteEntry(4, "later", 4, 9), attentionRouteEntry(5, "first", 5, 2)},
73 })
74
75 require.NoError(t, d.jumpAttentionForAttachment(source, ac, effect))
76
77 var actions []protocol.RouteNavigationAction
78 for _, frame := range transport.Sends() {
79 if action, ok := decodeServerMessage(t, frame).(protocol.RouteNavigationAction); ok {
80 actions = append(actions, action)
81 }
82 }
83 require.Equal(t, []protocol.RouteNavigationAction{{SnapshotGeneration: 6, Key: 5, Generation: 1}}, actions)
84 require.Same(t, source, ac.currentSession(), "the client navigates; the daemon never switches")
85}