@@ -3,21 +3,25 @@ package v1
33import (
44 "context"
55 "encoding/json"
6+ "errors"
67 "io"
78 "log/slog"
89 "net/http"
910 "net/http/httptest"
1011 "strings"
1112 "testing"
13+ "time"
1214
1315 "github.com/GoYoko/web"
1416)
1517
16- func TestInternalHostHandler_VMActivityRefreshesIdleTimer (t * testing.T ) {
18+ func TestInternalHostHandler_VMActivityPersistsAndRefreshesIdleTimer (t * testing.T ) {
1719 refresher := & internalVMIdleRefresherStub {ch : make (chan string , 1 )}
20+ activity := & internalVMActivityTaskRepoStub {ch : make (chan internalVMActivityCall , 1 )}
1821 h := & InternalHostHandler {
1922 logger : slog .New (slog .NewTextHandler (io .Discard , nil )),
2023 idleRefresher : refresher ,
24+ taskActivity : activity ,
2125 }
2226
2327 body := `{"vm_id":"vm-activity-1","last_active_at":1710000000}`
@@ -40,12 +44,57 @@ func TestInternalHostHandler_VMActivityRefreshesIdleTimer(t *testing.T) {
4044 default :
4145 t .Fatal ("expected idle refresher to be called" )
4246 }
47+ select {
48+ case got := <- activity .ch :
49+ if got .vmID != "vm-activity-1" || ! got .at .Equal (time .Unix (1710000000 , 0 )) || got .minInterval != vmActivityTaskRefreshInterval {
50+ t .Fatalf ("activity call = %+v" , got )
51+ }
52+ default :
53+ t .Fatal ("expected task activity to be persisted" )
54+ }
55+ }
56+
57+ func TestInternalHostHandler_VMActivityRefreshesIdleTimerWhenPersistFails (t * testing.T ) {
58+ refresher := & internalVMIdleRefresherStub {ch : make (chan string , 1 )}
59+ h := & InternalHostHandler {
60+ logger : slog .New (slog .NewTextHandler (io .Discard , nil )),
61+ idleRefresher : refresher ,
62+ taskActivity : & internalVMActivityTaskRepoStub {
63+ ch : make (chan internalVMActivityCall , 1 ),
64+ err : errors .New ("persist failed" ),
65+ },
66+ }
67+
68+ w := web .New ()
69+ w .POST ("/internal/vm/activity" , web .BindHandler (h .VMActivity ))
70+ rec := httptest .NewRecorder ()
71+ req := httptest .NewRequest (http .MethodPost , "/internal/vm/activity" , strings .NewReader (`{"vm_id":"vm-activity-1","last_active_at":1710000000}` ))
72+ req .Header .Set ("Content-Type" , "application/json" )
73+ w .Echo ().ServeHTTP (rec , req )
74+
75+ select {
76+ case got := <- refresher .ch :
77+ if got != "vm-activity-1" {
78+ t .Fatalf ("refreshed vm id = %q" , got )
79+ }
80+ default :
81+ t .Fatal ("expected idle refresher to run after persist failure" )
82+ }
83+
84+ var resp web.Resp
85+ if err := json .Unmarshal (rec .Body .Bytes (), & resp ); err != nil {
86+ t .Fatalf ("unmarshal resp: %v, body = %s" , err , rec .Body .String ())
87+ }
88+ if resp .Code == 0 {
89+ t .Fatalf ("response = %+v, want error" , resp )
90+ }
4391}
4492
4593func TestInternalHostHandler_VMActivityRejectsEmptyVMID (t * testing.T ) {
4694 h := & InternalHostHandler {
4795 logger : slog .New (slog .NewTextHandler (io .Discard , nil )),
4896 idleRefresher : & internalVMIdleRefresherStub {ch : make (chan string , 1 )},
97+ taskActivity : & internalVMActivityTaskRepoStub {ch : make (chan internalVMActivityCall , 1 )},
4998 }
5099
51100 w := web .New ()
@@ -65,14 +114,59 @@ func TestInternalHostHandler_VMActivityRejectsEmptyVMID(t *testing.T) {
65114 }
66115}
67116
117+ func TestInternalHostHandler_VMActivityRejectsMissingTimestamp (t * testing.T ) {
118+ h := & InternalHostHandler {
119+ logger : slog .New (slog .NewTextHandler (io .Discard , nil )),
120+ idleRefresher : & internalVMIdleRefresherStub {ch : make (chan string , 1 )},
121+ taskActivity : & internalVMActivityTaskRepoStub {ch : make (chan internalVMActivityCall , 1 )},
122+ }
123+
124+ w := web .New ()
125+ w .POST ("/internal/vm/activity" , web .BindHandler (h .VMActivity ))
126+
127+ rec := httptest .NewRecorder ()
128+ req := httptest .NewRequest (http .MethodPost , "/internal/vm/activity" , strings .NewReader (`{"vm_id":"vm-activity-1"}` ))
129+ req .Header .Set ("Content-Type" , "application/json" )
130+ w .Echo ().ServeHTTP (rec , req )
131+
132+ var resp web.Resp
133+ if err := json .Unmarshal (rec .Body .Bytes (), & resp ); err != nil {
134+ t .Fatalf ("unmarshal resp: %v, body = %s" , err , rec .Body .String ())
135+ }
136+ if resp .Code == 0 {
137+ t .Fatalf ("response = %+v, want error" , resp )
138+ }
139+ }
140+
68141type internalVMIdleRefresherStub struct {
69142 ch chan string
70143}
71144
72- func (s * internalVMIdleRefresherStub ) Refresh (_ context.Context , vmID string ) error {
145+ func (s * internalVMIdleRefresherStub ) KeepAwake (context.Context , string ) error { return nil }
146+
147+ func (s * internalVMIdleRefresherStub ) RecordActivity (_ context.Context , vmID string ) error {
73148 select {
74149 case s .ch <- vmID :
75150 default :
76151 }
77152 return nil
78153}
154+
155+ type internalVMActivityCall struct {
156+ vmID string
157+ at time.Time
158+ minInterval time.Duration
159+ }
160+
161+ type internalVMActivityTaskRepoStub struct {
162+ ch chan internalVMActivityCall
163+ err error
164+ }
165+
166+ func (s * internalVMActivityTaskRepoStub ) RefreshLastActiveAtByVMID (_ context.Context , vmID string , at time.Time , minInterval time.Duration ) error {
167+ select {
168+ case s .ch <- internalVMActivityCall {vmID : vmID , at : at , minInterval : minInterval }:
169+ default :
170+ }
171+ return s .err
172+ }
0 commit comments