@@ -51,6 +51,7 @@ import (
51
51
"github.com/coder/coder/v2/coderd/jwtutils"
52
52
"github.com/coder/coder/v2/coderd/rbac"
53
53
"github.com/coder/coder/v2/coderd/telemetry"
54
+ "github.com/coder/coder/v2/coderd/util/ptr"
54
55
"github.com/coder/coder/v2/codersdk"
55
56
"github.com/coder/coder/v2/codersdk/agentsdk"
56
57
"github.com/coder/coder/v2/codersdk/workspacesdk"
@@ -2135,30 +2136,21 @@ func TestOwnedWorkspacesCoordinate(t *testing.T) {
2135
2136
2136
2137
ctx := testutil .Context (t , testutil .WaitLong )
2137
2138
logger := testutil .Logger (t )
2138
-
2139
- fTelemetry := newFakeTelemetryReporter (ctx , t , 200 )
2140
- fTelemetry .enabled = false
2141
2139
firstClient , _ , api := coderdtest .NewWithAPI (t , & coderdtest.Options {
2142
- Coordinator : tailnet .NewCoordinator (logger ),
2143
- TelemetryReporter : fTelemetry ,
2140
+ Coordinator : tailnet .NewCoordinator (logger ),
2144
2141
})
2145
2142
firstUser := coderdtest .CreateFirstUser (t , firstClient )
2146
2143
member , memberUser := coderdtest .CreateAnotherUser (t , firstClient , firstUser .OrganizationID , rbac .RoleTemplateAdmin ())
2147
2144
2148
2145
// Create a workspace with an agent
2149
2146
firstWorkspace := buildWorkspaceWithAgent (t , member , firstUser .OrganizationID , memberUser .ID , api .Database , api .Pubsub )
2150
2147
2151
- // enable telemetry now that workspace is built; we don't care about snapshots before this.
2152
- fTelemetry .enabled = true
2153
-
2154
2148
u , err := member .URL .Parse ("/api/v2/tailnet" )
2155
2149
require .NoError (t , err )
2156
2150
q := u .Query ()
2157
2151
q .Set ("version" , "2.0" )
2158
2152
u .RawQuery = q .Encode ()
2159
2153
2160
- predialTime := time .Now ()
2161
-
2162
2154
//nolint:bodyclose // websocket package closes this for you
2163
2155
wsConn , resp , err := websocket .Dial (ctx , u .String (), & websocket.DialOptions {
2164
2156
HTTPHeader : http.Header {
@@ -2173,15 +2165,6 @@ func TestOwnedWorkspacesCoordinate(t *testing.T) {
2173
2165
}
2174
2166
defer wsConn .Close (websocket .StatusNormalClosure , "done" )
2175
2167
2176
- // Check telemetry
2177
- snapshot := testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2178
- require .Len (t , snapshot .UserTailnetConnections , 1 )
2179
- telemetryConnection := snapshot .UserTailnetConnections [0 ]
2180
- require .Equal (t , memberUser .ID .String (), telemetryConnection .UserID )
2181
- require .GreaterOrEqual (t , telemetryConnection .ConnectedAt , predialTime )
2182
- require .LessOrEqual (t , telemetryConnection .ConnectedAt , time .Now ())
2183
- require .NotEmpty (t , telemetryConnection .PeerID )
2184
-
2185
2168
rpcClient , err := tailnet .NewDRPCClient (
2186
2169
websocket .NetConn (ctx , wsConn , websocket .MessageBinary ),
2187
2170
logger ,
@@ -2229,23 +2212,135 @@ func TestOwnedWorkspacesCoordinate(t *testing.T) {
2229
2212
NumAgents : 0 ,
2230
2213
},
2231
2214
})
2232
- err = stream .Close ()
2233
- require .NoError (t , err )
2215
+ }
2234
2216
2235
- beforeDisconnectTime := time .Now ()
2236
- err = wsConn .Close (websocket .StatusNormalClosure , "done" )
2217
+ func TestUserTailnetTelemetry (t * testing.T ) {
2218
+ t .Parallel ()
2219
+
2220
+ telemetryData := & codersdk.CoderDesktopTelemetry {
2221
+ DeviceOS : "Windows" ,
2222
+ DeviceID : "device001" ,
2223
+ CoderDesktopVersion : "0.22.1" ,
2224
+ }
2225
+ fullHeader , err := json .Marshal (telemetryData )
2237
2226
require .NoError (t , err )
2238
2227
2239
- snapshot = testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2240
- require .Len (t , snapshot .UserTailnetConnections , 1 )
2241
- telemetryDisconnection := snapshot .UserTailnetConnections [0 ]
2242
- require .Equal (t , memberUser .ID .String (), telemetryDisconnection .UserID )
2243
- require .Equal (t , telemetryConnection .ConnectedAt , telemetryDisconnection .ConnectedAt )
2244
- require .Equal (t , telemetryConnection .UserID , telemetryDisconnection .UserID )
2245
- require .Equal (t , telemetryConnection .PeerID , telemetryDisconnection .PeerID )
2246
- require .NotNil (t , telemetryDisconnection .DisconnectedAt )
2247
- require .GreaterOrEqual (t , * telemetryDisconnection .DisconnectedAt , beforeDisconnectTime )
2248
- require .LessOrEqual (t , * telemetryDisconnection .DisconnectedAt , time .Now ())
2228
+ testCases := []struct {
2229
+ name string
2230
+ headers map [string ]string
2231
+ // only used for DeviceID, DeviceOS, CoderDesktopVersion
2232
+ expected telemetry.UserTailnetConnection
2233
+ }{
2234
+ {
2235
+ name : "no header" ,
2236
+ headers : map [string ]string {},
2237
+ expected : telemetry.UserTailnetConnection {},
2238
+ },
2239
+ {
2240
+ name : "full header" ,
2241
+ headers : map [string ]string {
2242
+ codersdk .CoderDesktopTelemetryHeader : string (fullHeader ),
2243
+ },
2244
+ expected : telemetry.UserTailnetConnection {
2245
+ DeviceOS : ptr .Ref ("Windows" ),
2246
+ DeviceID : ptr .Ref ("device001" ),
2247
+ CoderDesktopVersion : ptr .Ref ("0.22.1" ),
2248
+ },
2249
+ },
2250
+ {
2251
+ name : "empty header" ,
2252
+ headers : map [string ]string {
2253
+ codersdk .CoderDesktopTelemetryHeader : "" ,
2254
+ },
2255
+ expected : telemetry.UserTailnetConnection {},
2256
+ },
2257
+ {
2258
+ name : "invalid header" ,
2259
+ headers : map [string ]string {
2260
+ codersdk .CoderDesktopTelemetryHeader : "{\" device_os" ,
2261
+ },
2262
+ expected : telemetry.UserTailnetConnection {},
2263
+ },
2264
+ }
2265
+
2266
+ // nolint: paralleltest // no longer need to reinitialize loop vars in go 1.22
2267
+ for _ , tc := range testCases {
2268
+ t .Run (tc .name , func (t * testing.T ) {
2269
+ t .Parallel ()
2270
+
2271
+ ctx := testutil .Context (t , testutil .WaitLong )
2272
+ logger := testutil .Logger (t )
2273
+
2274
+ fTelemetry := newFakeTelemetryReporter (ctx , t , 200 )
2275
+ fTelemetry .enabled = false
2276
+ firstClient := coderdtest .New (t , & coderdtest.Options {
2277
+ Logger : & logger ,
2278
+ TelemetryReporter : fTelemetry ,
2279
+ })
2280
+ firstUser := coderdtest .CreateFirstUser (t , firstClient )
2281
+ member , memberUser := coderdtest .CreateAnotherUser (t , firstClient , firstUser .OrganizationID , rbac .RoleTemplateAdmin ())
2282
+
2283
+ headers := http.Header {
2284
+ "Coder-Session-Token" : []string {member .SessionToken ()},
2285
+ }
2286
+ for k , v := range tc .headers {
2287
+ headers .Add (k , v )
2288
+ }
2289
+
2290
+ // enable telemetry now that user is created.
2291
+ fTelemetry .enabled = true
2292
+
2293
+ u , err := member .URL .Parse ("/api/v2/tailnet" )
2294
+ require .NoError (t , err )
2295
+ q := u .Query ()
2296
+ q .Set ("version" , "2.0" )
2297
+ u .RawQuery = q .Encode ()
2298
+
2299
+ predialTime := time .Now ()
2300
+
2301
+ //nolint:bodyclose // websocket package closes this for you
2302
+ wsConn , resp , err := websocket .Dial (ctx , u .String (), & websocket.DialOptions {
2303
+ HTTPHeader : headers ,
2304
+ })
2305
+ if err != nil {
2306
+ if resp != nil && resp .StatusCode != http .StatusSwitchingProtocols {
2307
+ err = codersdk .ReadBodyAsError (resp )
2308
+ }
2309
+ require .NoError (t , err )
2310
+ }
2311
+ defer wsConn .Close (websocket .StatusNormalClosure , "done" )
2312
+
2313
+ // Check telemetry
2314
+ snapshot := testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2315
+ require .Len (t , snapshot .UserTailnetConnections , 1 )
2316
+ telemetryConnection := snapshot .UserTailnetConnections [0 ]
2317
+ require .Equal (t , memberUser .ID .String (), telemetryConnection .UserID )
2318
+ require .GreaterOrEqual (t , telemetryConnection .ConnectedAt , predialTime )
2319
+ require .LessOrEqual (t , telemetryConnection .ConnectedAt , time .Now ())
2320
+ require .NotEmpty (t , telemetryConnection .PeerID )
2321
+ requireEqualOrBothNil (t , telemetryConnection .DeviceID , tc .expected .DeviceID )
2322
+ requireEqualOrBothNil (t , telemetryConnection .DeviceOS , tc .expected .DeviceOS )
2323
+ requireEqualOrBothNil (t , telemetryConnection .CoderDesktopVersion , tc .expected .CoderDesktopVersion )
2324
+
2325
+ beforeDisconnectTime := time .Now ()
2326
+ err = wsConn .Close (websocket .StatusNormalClosure , "done" )
2327
+ require .NoError (t , err )
2328
+
2329
+ snapshot = testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2330
+ require .Len (t , snapshot .UserTailnetConnections , 1 )
2331
+ telemetryDisconnection := snapshot .UserTailnetConnections [0 ]
2332
+ require .Equal (t , memberUser .ID .String (), telemetryDisconnection .UserID )
2333
+ require .Equal (t , telemetryConnection .ConnectedAt , telemetryDisconnection .ConnectedAt )
2334
+ require .Equal (t , telemetryConnection .UserID , telemetryDisconnection .UserID )
2335
+ require .Equal (t , telemetryConnection .PeerID , telemetryDisconnection .PeerID )
2336
+ require .NotNil (t , telemetryDisconnection .DisconnectedAt )
2337
+ require .GreaterOrEqual (t , * telemetryDisconnection .DisconnectedAt , beforeDisconnectTime )
2338
+ require .LessOrEqual (t , * telemetryDisconnection .DisconnectedAt , time .Now ())
2339
+ requireEqualOrBothNil (t , telemetryConnection .DeviceID , tc .expected .DeviceID )
2340
+ requireEqualOrBothNil (t , telemetryConnection .DeviceOS , tc .expected .DeviceOS )
2341
+ requireEqualOrBothNil (t , telemetryConnection .CoderDesktopVersion , tc .expected .CoderDesktopVersion )
2342
+ })
2343
+ }
2249
2344
}
2250
2345
2251
2346
func buildWorkspaceWithAgent (
@@ -2414,3 +2509,12 @@ func (f *fakeTelemetryReporter) Enabled() bool {
2414
2509
2415
2510
// Close implements the telemetry.Reporter interface.
2416
2511
func (* fakeTelemetryReporter ) Close () {}
2512
+
2513
+ func requireEqualOrBothNil [T any ](t testing.TB , a , b * T ) {
2514
+ t .Helper ()
2515
+ if a != nil && b != nil {
2516
+ require .Equal (t , * a , * b )
2517
+ return
2518
+ }
2519
+ require .Equal (t , a , b )
2520
+ }
0 commit comments