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
13// TestKillSessionServerShutdownSaturatedAppendsNotice proves that when the
14// daemon is shutting down and the snapshot worker is saturated — so there is
15// no way to retain the session's terminal state and no attached client to
16// toast, because the daemon is dying — the failure is handed to the
17// NoticeStore instead of being silently lost.
18// TestStartupClaimRecordsAndToastsPendingNotices proves notices persisted by
19// a previous daemon are recorded to history and toasted to the first client
20// that attaches after this daemon starts before their claim is acknowledged.
21func TestStartupClaimRecordsAndToastsPendingNotices(t *testing.T) {
22 claimed := []domain.Notification{
23 {Code: domain.NoticeSnapshotWrite, Severity: domain.NoticeError, Message: "session a shut down without saving terminal state"},
24 {Code: domain.NoticeSnapshotWrite, Severity: domain.NoticeError, Message: "session b shut down without saving terminal state"},
25 }
26 notices := portsmocks.NewMockNoticeStore(t)
27 notices.EXPECT().Claim().Return(claimed, nil).Once()
28 notices.EXPECT().Ack().Return(nil).Once()
29
30 p, releasePTY := newBlockingPTY(t)
31 defer releasePTY()
32 d := newTestDaemon(t, newFactory(t, p), stubClock{})
33 d.noticeStore = notices
34
35 d.restoreSnapshots(t.Context())
36
37 require.Len(t, d.notices.history(), 2, "each drained notice is recorded individually")
38
39 tr, _ := newCapturingTransport(t)
40 sess, ac, err := d.route(protocol.Hello{
41 Version: protocol.Version,
42 Intent: protocol.IntentEphemeral,
43 Size: domain.Size{Cols: 80, Rows: 24},
44 }, tr)
45 require.NoError(t, err)
46 d.firstPaint(sess, ac)
47
48 toasts := awaitToastCount(t, ac, 1)
49 require.Equal(t, domain.NoticeSnapshotWrite, toasts[0].Code)
50 require.Equal(t, 2, toasts[0].Count, "two same-code drained notices dedup into one toast counted twice")
51 require.Empty(t, d.notices.drainPending(), "firstPaint must consume the queue")
52}