@@ -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,134 @@ 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
+ for _ , tc := range testCases {
2267
+ t .Run (tc .name , func (t * testing.T ) {
2268
+ t .Parallel ()
2269
+
2270
+ ctx := testutil .Context (t , testutil .WaitLong )
2271
+ logger := testutil .Logger (t )
2272
+
2273
+ fTelemetry := newFakeTelemetryReporter (ctx , t , 200 )
2274
+ fTelemetry .enabled = false
2275
+ firstClient := coderdtest .New (t , & coderdtest.Options {
2276
+ Logger : & logger ,
2277
+ TelemetryReporter : fTelemetry ,
2278
+ })
2279
+ firstUser := coderdtest .CreateFirstUser (t , firstClient )
2280
+ member , memberUser := coderdtest .CreateAnotherUser (t , firstClient , firstUser .OrganizationID , rbac .RoleTemplateAdmin ())
2281
+
2282
+ headers := http.Header {
2283
+ "Coder-Session-Token" : []string {member .SessionToken ()},
2284
+ }
2285
+ for k , v := range tc .headers {
2286
+ headers .Add (k , v )
2287
+ }
2288
+
2289
+ // enable telemetry now that user is created.
2290
+ fTelemetry .enabled = true
2291
+
2292
+ u , err := member .URL .Parse ("/api/v2/tailnet" )
2293
+ require .NoError (t , err )
2294
+ q := u .Query ()
2295
+ q .Set ("version" , "2.0" )
2296
+ u .RawQuery = q .Encode ()
2297
+
2298
+ predialTime := time .Now ()
2299
+
2300
+ //nolint:bodyclose // websocket package closes this for you
2301
+ wsConn , resp , err := websocket .Dial (ctx , u .String (), & websocket.DialOptions {
2302
+ HTTPHeader : headers ,
2303
+ })
2304
+ if err != nil {
2305
+ if resp != nil && resp .StatusCode != http .StatusSwitchingProtocols {
2306
+ err = codersdk .ReadBodyAsError (resp )
2307
+ }
2308
+ require .NoError (t , err )
2309
+ }
2310
+ defer wsConn .Close (websocket .StatusNormalClosure , "done" )
2311
+
2312
+ // Check telemetry
2313
+ snapshot := testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2314
+ require .Len (t , snapshot .UserTailnetConnections , 1 )
2315
+ telemetryConnection := snapshot .UserTailnetConnections [0 ]
2316
+ require .Equal (t , memberUser .ID .String (), telemetryConnection .UserID )
2317
+ require .GreaterOrEqual (t , telemetryConnection .ConnectedAt , predialTime )
2318
+ require .LessOrEqual (t , telemetryConnection .ConnectedAt , time .Now ())
2319
+ require .NotEmpty (t , telemetryConnection .PeerID )
2320
+ requireEqualOrBothNil (t , telemetryConnection .DeviceID , tc .expected .DeviceID )
2321
+ requireEqualOrBothNil (t , telemetryConnection .DeviceOS , tc .expected .DeviceOS )
2322
+ requireEqualOrBothNil (t , telemetryConnection .CoderDesktopVersion , tc .expected .CoderDesktopVersion )
2323
+
2324
+ beforeDisconnectTime := time .Now ()
2325
+ err = wsConn .Close (websocket .StatusNormalClosure , "done" )
2326
+ require .NoError (t , err )
2327
+
2328
+ snapshot = testutil .RequireRecvCtx (ctx , t , fTelemetry .snapshots )
2329
+ require .Len (t , snapshot .UserTailnetConnections , 1 )
2330
+ telemetryDisconnection := snapshot .UserTailnetConnections [0 ]
2331
+ require .Equal (t , memberUser .ID .String (), telemetryDisconnection .UserID )
2332
+ require .Equal (t , telemetryConnection .ConnectedAt , telemetryDisconnection .ConnectedAt )
2333
+ require .Equal (t , telemetryConnection .UserID , telemetryDisconnection .UserID )
2334
+ require .Equal (t , telemetryConnection .PeerID , telemetryDisconnection .PeerID )
2335
+ require .NotNil (t , telemetryDisconnection .DisconnectedAt )
2336
+ require .GreaterOrEqual (t , * telemetryDisconnection .DisconnectedAt , beforeDisconnectTime )
2337
+ require .LessOrEqual (t , * telemetryDisconnection .DisconnectedAt , time .Now ())
2338
+ requireEqualOrBothNil (t , telemetryConnection .DeviceID , tc .expected .DeviceID )
2339
+ requireEqualOrBothNil (t , telemetryConnection .DeviceOS , tc .expected .DeviceOS )
2340
+ requireEqualOrBothNil (t , telemetryConnection .CoderDesktopVersion , tc .expected .CoderDesktopVersion )
2341
+ })
2342
+ }
2249
2343
}
2250
2344
2251
2345
func buildWorkspaceWithAgent (
@@ -2414,3 +2508,12 @@ func (f *fakeTelemetryReporter) Enabled() bool {
2414
2508
2415
2509
// Close implements the telemetry.Reporter interface.
2416
2510
func (* fakeTelemetryReporter ) Close () {}
2511
+
2512
+ func requireEqualOrBothNil [T any ](t testing.TB , a , b * T ) {
2513
+ t .Helper ()
2514
+ if a != nil && b != nil {
2515
+ require .Equal (t , * a , * b )
2516
+ return
2517
+ }
2518
+ require .Equal (t , a , b )
2519
+ }
0 commit comments