@@ -64,6 +64,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
64
64
// reconnecting-pty proxy server we want to test is mounted.
65
65
client := appDetails .AppClient (t )
66
66
testReconnectingPTY (ctx , t , client , appDetails .Agent .ID , "" )
67
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
67
68
})
68
69
69
70
t .Run ("SignedTokenQueryParameter" , func (t * testing.T ) {
@@ -92,6 +93,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
92
93
// Make an unauthenticated client.
93
94
unauthedAppClient := codersdk .New (appDetails .AppClient (t ).URL )
94
95
testReconnectingPTY (ctx , t , unauthedAppClient , appDetails .Agent .ID , issueRes .SignedToken )
96
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
95
97
})
96
98
})
97
99
@@ -117,6 +119,9 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
117
119
body , err := io .ReadAll (resp .Body )
118
120
require .NoError (t , err )
119
121
require .Contains (t , string (body ), "Path-based applications are disabled" )
122
+ // Even though path-based apps are disabled, the request should indicate
123
+ // that the workspace was used.
124
+ assertWorkspaceLastUsedAtNotUpdated (t , appDetails )
120
125
})
121
126
122
127
t .Run ("LoginWithoutAuthOnPrimary" , func (t * testing.T ) {
@@ -142,6 +147,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
142
147
require .NoError (t , err )
143
148
require .True (t , loc .Query ().Has ("message" ))
144
149
require .True (t , loc .Query ().Has ("redirect" ))
150
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
145
151
})
146
152
147
153
t .Run ("LoginWithoutAuthOnProxy" , func (t * testing.T ) {
@@ -179,6 +185,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
179
185
// request is getting stripped.
180
186
require .Equal (t , u .Path , redirectURI .Path + "/" )
181
187
require .Equal (t , u .RawQuery , redirectURI .RawQuery )
188
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
182
189
})
183
190
184
191
t .Run ("NoAccessShould404" , func (t * testing.T ) {
@@ -195,6 +202,8 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
195
202
require .NoError (t , err )
196
203
defer resp .Body .Close ()
197
204
require .Equal (t , http .StatusNotFound , resp .StatusCode )
205
+ // TODO(cian): A blocked request should not count as workspace usage.
206
+ // assertWorkspaceLastUsedAtNotUpdated(t, appDetails.AppClient(t), appDetails)
198
207
})
199
208
200
209
t .Run ("RedirectsWithSlash" , func (t * testing.T ) {
@@ -209,6 +218,8 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
209
218
require .NoError (t , err )
210
219
defer resp .Body .Close ()
211
220
require .Equal (t , http .StatusTemporaryRedirect , resp .StatusCode )
221
+ // TODO(cian): The initial redirect should not count as workspace usage.
222
+ // assertWorkspaceLastUsedAtNotUpdated(t, appDetails.AppClient(t), appDetails)
212
223
})
213
224
214
225
t .Run ("RedirectsWithQuery" , func (t * testing.T ) {
@@ -226,6 +237,8 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
226
237
loc , err := resp .Location ()
227
238
require .NoError (t , err )
228
239
require .Equal (t , proxyTestAppQuery , loc .RawQuery )
240
+ // TODO(cian): The initial redirect should not count as workspace usage.
241
+ // assertWorkspaceLastUsedAtNotUpdated(t, appDetails.AppClient(t), appDetails)
229
242
})
230
243
231
244
t .Run ("Proxies" , func (t * testing.T ) {
@@ -267,6 +280,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
267
280
require .NoError (t , err )
268
281
require .Equal (t , proxyTestAppBody , string (body ))
269
282
require .Equal (t , http .StatusOK , resp .StatusCode )
283
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
270
284
})
271
285
272
286
t .Run ("ProxiesHTTPS" , func (t * testing.T ) {
@@ -312,6 +326,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
312
326
require .NoError (t , err )
313
327
require .Equal (t , proxyTestAppBody , string (body ))
314
328
require .Equal (t , http .StatusOK , resp .StatusCode )
329
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
315
330
})
316
331
317
332
t .Run ("BlocksMe" , func (t * testing.T ) {
@@ -331,6 +346,8 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
331
346
body , err := io .ReadAll (resp .Body )
332
347
require .NoError (t , err )
333
348
require .Contains (t , string (body ), "must be accessed with the full username, not @me" )
349
+ // TODO(cian): A blocked request should not count as workspace usage.
350
+ // assertWorkspaceLastUsedAtNotUpdated(t, appDetails.AppClient(t), appDetails)
334
351
})
335
352
336
353
t .Run ("ForwardsIP" , func (t * testing.T ) {
@@ -349,6 +366,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
349
366
require .Equal (t , proxyTestAppBody , string (body ))
350
367
require .Equal (t , http .StatusOK , resp .StatusCode )
351
368
require .Equal (t , "1.1.1.1,127.0.0.1" , resp .Header .Get ("X-Forwarded-For" ))
369
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
352
370
})
353
371
354
372
t .Run ("ProxyError" , func (t * testing.T ) {
@@ -361,6 +379,9 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
361
379
require .NoError (t , err )
362
380
defer resp .Body .Close ()
363
381
require .Equal (t , http .StatusBadGateway , resp .StatusCode )
382
+ // An valid authenticated attempt to access a workspace app
383
+ // should count as usage regardless of success.
384
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
364
385
})
365
386
366
387
t .Run ("NoProxyPort" , func (t * testing.T ) {
@@ -375,6 +396,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
375
396
// TODO(@deansheather): This should be 400. There's a todo in the
376
397
// resolve request code to fix this.
377
398
require .Equal (t , http .StatusInternalServerError , resp .StatusCode )
399
+ assertWorkspaceLastUsedAtUpdated (t , appDetails )
378
400
})
379
401
})
380
402
@@ -1430,16 +1452,12 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
1430
1452
t .Run ("ReportStats" , func (t * testing.T ) {
1431
1453
t .Parallel ()
1432
1454
1433
- flush := make (chan chan <- struct {}, 1 )
1434
-
1435
1455
reporter := & fakeStatsReporter {}
1436
1456
appDetails := setupProxyTest (t , & DeploymentOptions {
1437
1457
StatsCollectorOptions : workspaceapps.StatsCollectorOptions {
1438
1458
Reporter : reporter ,
1439
1459
ReportInterval : time .Hour ,
1440
1460
RollupWindow : time .Minute ,
1441
-
1442
- Flush : flush ,
1443
1461
},
1444
1462
})
1445
1463
@@ -1457,10 +1475,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
1457
1475
var stats []workspaceapps.StatsReport
1458
1476
require .Eventually (t , func () bool {
1459
1477
// Keep flushing until we get a non-empty stats report.
1460
- flushDone := make (chan struct {}, 1 )
1461
- flush <- flushDone
1462
- <- flushDone
1463
-
1478
+ appDetails .FlushStats ()
1464
1479
stats = reporter .stats ()
1465
1480
return len (stats ) > 0
1466
1481
}, testutil .WaitLong , testutil .IntervalFast , "stats not reported" )
@@ -1549,3 +1564,28 @@ func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Cli
1549
1564
// Ensure the connection closes.
1550
1565
require .ErrorIs (t , tr .ReadUntil (ctx , nil ), io .EOF )
1551
1566
}
1567
+
1568
+ // Accessing an app should update the workspace's LastUsedAt.
1569
+ // NOTE: Despite our efforts with the flush channel, this is inherently racy.
1570
+ func assertWorkspaceLastUsedAtUpdated (t testing.TB , details * Details ) {
1571
+ t .Helper ()
1572
+
1573
+ // Wait for stats to fully flush.
1574
+ require .Eventually (t , func () bool {
1575
+ details .FlushStats ()
1576
+ ws , err := details .SDKClient .Workspace (context .Background (), details .Workspace .ID )
1577
+ assert .NoError (t , err )
1578
+ return ws .LastUsedAt .After (details .Workspace .LastUsedAt )
1579
+ }, testutil .WaitShort , testutil .IntervalMedium , "workspace LastUsedAt not updated when it should have been" )
1580
+ }
1581
+
1582
+ // Except when it sometimes shouldn't (e.g. no access)
1583
+ // NOTE: Despite our efforts with the flush channel, this is inherently racy.
1584
+ func assertWorkspaceLastUsedAtNotUpdated (t testing.TB , details * Details ) {
1585
+ t .Helper ()
1586
+
1587
+ details .FlushStats ()
1588
+ ws , err := details .SDKClient .Workspace (context .Background (), details .Workspace .ID )
1589
+ require .NoError (t , err )
1590
+ require .Equal (t , ws .LastUsedAt , details .Workspace .LastUsedAt , "workspace LastUsedAt updated when it should not have been" )
1591
+ }
0 commit comments