1package daemon
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/require"
7
8 "github.com/bnema/vev/internal/domain"
9 portsmocks "github.com/bnema/vev/internal/ports/mocks"
10 "github.com/bnema/vev/internal/protocol"
11)
12
13func TestRenamePublishesCommittedRouteIdentityToAttachedClients(t *testing.T) {
14 d, sess, ac, sends := newManualSessionWithPTYs(t)
15 sess.mu.Lock()
16 sess.name = "0"
17 sess.ephemeral = true
18 sess.incarnation = domain.IncarnationID{1}
19 sess.mu.Unlock()
20 token := sess.captureAttachmentCapability(ac, ac.transport())
21 ac.installTestAttachmentCapability(token)
22 ac.setRouteSnapshot(protocol.RecentRouteSnapshot{Generation: 1})
23
24 require.NoError(t, d.renameSession(sess, "vps-infra"))
25
26 frame := awaitFrame(t, sends, "CommittedRouteIdentity")
27 identity := decodeServerMessage(t, frame).(protocol.CommittedRouteIdentity)
28 require.Equal(t, protocol.ExactSessionTarget{
29 LifecycleID: domain.IncarnationID{1},
30 SessionName: "vps-infra",
31 }, identity.Target)
32 require.False(t, identity.Ephemeral)
33}
34
35func TestRenameDefersCommittedRouteIdentityUntilFirstRouteSnapshot(t *testing.T) {
36 d, sess, ac, sends := newManualSessionWithPTYs(t)
37 sess.mu.Lock()
38 sess.name = "0"
39 sess.ephemeral = true
40 sess.incarnation = domain.IncarnationID{1}
41 sess.mu.Unlock()
42 token := sess.captureAttachmentCapability(ac, ac.transport())
43 ac.installTestAttachmentCapability(token)
44
45 require.NoError(t, d.renameSession(sess, "vps-infra"))
46 require.Empty(t, sends, "the route ledger must be published before its identity can be updated")
47
48 activeRef := protocol.RouteRef{Key: 1, Generation: 1}
49 snapshot := protocol.RecentRouteSnapshot{
50 Generation: 1,
51 Active: activeRef,
52 ActiveEntry: protocol.RecentRouteEntry{Key: 1, Generation: 1, Target: testRouteTarget("0", 1), Name: "0", Kind: protocol.RouteKindLocal},
53 Home: activeRef,
54 }
55 require.False(t, d.handleAttachmentClientFrame(token, mustClientEnvelope(snapshot)))
56
57 frame := awaitFrame(t, sends, "CommittedRouteIdentity")
58 identity, ok := decodeServerMessage(t, frame).(protocol.CommittedRouteIdentity)
59 require.True(t, ok)
60 require.Equal(t, protocol.ExactSessionTarget{
61 LifecycleID: domain.IncarnationID{1},
62 SessionName: "vps-infra",
63 }, identity.Target)
64 require.False(t, identity.Ephemeral)
65}
66
67func TestEphemeralPromotionPreservesIncarnation(t *testing.T) {
68 p, release := newBlockingPTY(t)
69 defer release()
70 store, state := newMockStore(t)
71 d := newTestDaemon(t, newFactory(t, p), stubClock{})
72 WithStore(t, store)(d)
73
74 sess, err := createSessionForTest(d, "0", true, "/tmp", domain.Size{Cols: 80, Rows: 24}, terminalEnv{}, d.baseEnv)
75 require.NoError(t, err)
76 sess.mu.Lock()
77 original := sess.incarnation
78 sess.mu.Unlock()
79 require.NotZero(t, original)
80
81 require.NoError(t, d.renameSession(sess, "local"))
82 require.Equal(t, original, sess.incarnation)
83 require.Equal(t, original, state.record(t, "local").IncarnationID)
84}
85
86func TestRenamePreservesIncarnationSnapshotSources(t *testing.T) {
87 d := newTestDaemon(t, portsmocks.NewMockPTYFactory(t), stubClock{})
88 repository := &retryablePurgeRepository{}
89 WithSnapshotRepository(repository)(d)
90 store, state := newMockStore(t)
91 WithStore(t, store)(d)
92 sess := newSnapshotTestSession(t, "old", false, "/work")
93 d.sessions = map[domain.SessionID]*session{sess.id: sess}
94 sess.mu.Lock()
95 record := sess.persistRecordLocked(1)
96 sess.mu.Unlock()
97 require.NoError(t, testPersister(t, d).Save(record))
98
99 require.NoError(t, d.renameSession(sess, "new"))
100 require.Empty(t, repository.calls, "rename must not delete incarnation-keyed snapshots")
101 require.False(t, state.has("old"))
102 require.True(t, state.has("new"))
103 require.Equal(t, sess.incarnation, state.record(t, "new").IncarnationID)
104}