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