@@ -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
+ }