1package usecase
2
3import (
4 "testing"
5 "time"
6
7 "github.com/stretchr/testify/require"
8)
9
10func TestRuntimeResidencyDisabledQuitsOnLastWindow(t *testing.T) {
11 r := NewRuntimeResidency(0)
12 r.WindowOpened(time.Now())
13 decision := r.WindowClosed(time.Now())
14 require.Equal(t, ResidencyQuit, decision.Action)
15 require.False(t, r.Armed())
16}
17
18func TestRuntimeResidencyNegativeTimeoutDisables(t *testing.T) {
19 r := NewRuntimeResidency(-time.Second)
20 r.WindowOpened(time.Now())
21 require.Equal(t, ResidencyQuit, r.WindowClosed(time.Now()).Action)
22}
23
24func TestRuntimeResidencyFirstIdleArmsDeadline(t *testing.T) {
25 now := time.Now()
26 r := NewRuntimeResidency(time.Minute)
27 r.WindowOpened(now)
28 decision := r.WindowClosed(now)
29 require.Equal(t, ResidencyArm, decision.Action)
30 require.Equal(t, now.Add(time.Minute), decision.Deadline)
31 require.True(t, r.Armed())
32 gen := decision.Generation
33
34 expiry := r.HandleExpiry(now.Add(2*time.Minute), gen)
35 require.Equal(t, ResidencyQuit, expiry.Action)
36 require.False(t, r.Armed())
37}
38
39func TestRuntimeResidencyReopenCancelsDeadline(t *testing.T) {
40 now := time.Now()
41 r := NewRuntimeResidency(time.Minute)
42 r.WindowOpened(now)
43 arm := r.WindowClosed(now)
44 require.Equal(t, ResidencyArm, arm.Action)
45
46 cancel := r.WindowOpened(now)
47 require.Equal(t, ResidencyCancel, cancel.Action)
48 require.False(t, r.Armed())
49
50 stale := r.HandleExpiry(now.Add(2*time.Minute), arm.Generation)
51 require.Equal(t, ResidencyNone, stale.Action)
52}
53
54func TestRuntimeResidencyFailedReopenRestoresIdle(t *testing.T) {
55 now := time.Now()
56 r := NewRuntimeResidency(time.Minute)
57 r.WindowOpened(now)
58 require.Equal(t, ResidencyArm, r.WindowClosed(now).Action)
59
60 // Optimistic open followed by failure: the hold must not leak, idle
61 // eligibility returns.
62 r.WindowOpened(now)
63 decision := r.OpenFailed(now)
64 require.Equal(t, ResidencyArm, decision.Action)
65 require.True(t, r.Armed())
66}
67
68func TestRuntimeResidencyPopupRemainingPreventsIdle(t *testing.T) {
69 now := time.Now()
70 r := NewRuntimeResidency(time.Minute)
71 r.WindowOpened(now)
72 r.WindowOpened(now)
73 decision := r.WindowClosed(now)
74 require.Equal(t, ResidencyNone, decision.Action)
75 require.False(t, r.Armed())
76}
77
78func TestRuntimeResidencyDuplicateCloseIsIdempotent(t *testing.T) {
79 r := NewRuntimeResidency(0)
80 decision := r.WindowClosed(time.Now())
81 require.Equal(t, ResidencyQuit, decision.Action)
82 again := r.WindowClosed(time.Now())
83 require.Equal(t, ResidencyQuit, again.Action)
84}
85
86func TestRuntimeResidencyStaleExpiryIgnored(t *testing.T) {
87 now := time.Now()
88 r := NewRuntimeResidency(time.Minute)
89 r.WindowOpened(now)
90 first := r.WindowClosed(now)
91 r.WindowOpened(now)
92 second := r.WindowClosed(now)
93 require.NotEqual(t, first.Generation, second.Generation)
94
95 require.Equal(t, ResidencyNone, r.HandleExpiry(now, first.Generation).Action)
96 require.True(t, r.Armed())
97 require.Equal(t, ResidencyQuit, r.HandleExpiry(now, second.Generation).Action)
98}
99
100func TestRuntimeResidencyExplicitQuitNeverWaits(t *testing.T) {
101 now := time.Now()
102 r := NewRuntimeResidency(time.Minute)
103 r.WindowOpened(now)
104 require.Equal(t, ResidencyArm, r.WindowClosed(now).Action)
105 require.True(t, r.Armed())
106
107 decision := r.ShutdownRequested()
108 require.Equal(t, ResidencyQuit, decision.Action)
109 require.False(t, r.Armed())
110}
111
112func TestRuntimeResidencyWorkBlocksIdleUntilDrained(t *testing.T) {
113 now := time.Now()
114 r := NewRuntimeResidency(time.Minute)
115 r.WindowOpened(now)
116 r.WorkStarted(now)
117 decision := r.WindowClosed(now)
118 require.Equal(t, ResidencyNone, decision.Action)
119 require.False(t, r.Armed())
120
121 drained := r.WorkFinished(now)
122 require.Equal(t, ResidencyArm, drained.Action)
123 require.True(t, r.Armed())
124}
125
126func TestRuntimeResidencyWorkCancelsArmedDeadline(t *testing.T) {
127 now := time.Now()
128 r := NewRuntimeResidency(time.Minute)
129 r.WindowOpened(now)
130 require.Equal(t, ResidencyArm, r.WindowClosed(now).Action)
131 require.Equal(t, ResidencyCancel, r.WorkStarted(now).Action)
132 require.False(t, r.Armed())
133}