1 package segment
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "net/http"
10 "net/http/httptest"
11 "os"
12 "os/user"
13 "runtime"
14 "strings"
15 "testing"
16 "time"
17
18 "github.com/golang/mock/gomock"
19 "github.com/sethvargo/go-envconfig"
20
21 "github.com/redhat-developer/odo/pkg/config"
22 "github.com/redhat-developer/odo/pkg/kclient"
23 "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
24
25 "github.com/redhat-developer/odo/pkg/log"
26 "github.com/redhat-developer/odo/pkg/preference"
27 scontext "github.com/redhat-developer/odo/pkg/segment/context"
28 "github.com/redhat-developer/odo/pkg/version"
29 )
30
31 type segmentResponse struct {
32 Batch []struct {
33 UserId string `json:"userId"`
34 MessageId string `json:"messageId"`
35 Traits struct {
36 OS string `json:"os"`
37 } `json:"traits"`
38 Properties struct {
39 Error string `json:"error"`
40 ErrorType string `json:"error-type"`
41 Success bool `json:"success"`
42 Version string `json:"version"`
43 ComponentType string `json:"componentType"`
44 ClusterType string `json:"clusterType"`
45 } `json:"properties"`
46 Type string `json:"type"`
47 } `json:"batch"`
48 MessageID string `json:"messageId"`
49 }
50
51 func mockServer() (chan []byte, *httptest.Server) {
52 done := make(chan []byte, 1)
53
54 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55 defer r.Body.Close()
56 bin, err := io.ReadAll(r.Body)
57 if err != nil {
58 log.Error(err)
59 return
60 }
61 done <- bin
62 }))
63 return done, server
64 }
65
66 func TestClientUploadWithoutConsent(t *testing.T) {
67 body, server := mockServer()
68 defer server.Close()
69 defer close(body)
70
71 c, err := newCustomClient(createConfigDir(t), server.URL)
72 if err != nil {
73 t.Error(err)
74 }
75
76 testError := errors.New("error occurred")
77 uploadData := fakeTelemetryData("odo preference view", testError, context.Background())
78
79 ctx := context.Background()
80 scontext.SetTelemetryStatus(ctx, false)
81 if err = c.Upload(ctx, uploadData); err != nil {
82 t.Error(err)
83 }
84
85 if err = c.Close(); err != nil {
86 t.Error(err)
87 }
88
89 select {
90 case x := <-body:
91 t.Errorf("server should not receive data: %q", x)
92 default:
93 }
94 }
95
96 func TestClientUploadWithConsent(t *testing.T) {
97 body, server := mockServer()
98 defer server.Close()
99 defer close(body)
100
101 tests := []struct {
102 cmd string
103 testName string
104 err error
105 success bool
106 errType string
107 version string
108 }{
109 {
110 testName: "command ran successfully",
111 err: nil,
112 success: true,
113 errType: "",
114 version: version.VERSION,
115 },
116 {
117 testName: "command failed",
118 err: errors.New("some error occurred"),
119 success: false,
120 errType: "*errors.errorString",
121 version: version.VERSION,
122 },
123 }
124 for _, tt := range tests {
125 t.Log("Running test: ", tt.testName)
126 t.Run(tt.testName, func(t *testing.T) {
127 c, err := newCustomClient(createConfigDir(t), server.URL)
128 if err != nil {
129 t.Error(err)
130 }
131 uploadData := fakeTelemetryData("odo init", tt.err, context.Background())
132
133 ctx := scontext.NewContext(context.Background())
134 scontext.SetTelemetryStatus(ctx, true)
135 if err = c.Upload(ctx, uploadData); err != nil {
136 t.Error(err)
137 }
138
139
140
141
142
143 if err = c.Close(); err != nil {
144 t.Error(err)
145 }
146
147 select {
148 case x := <-body:
149 s := segmentResponse{}
150 if err1 := json.Unmarshal(x, &s); err1 != nil {
151 t.Error(err1)
152 }
153
154
155
156
157 if s.Batch[0].Type != "identify" && s.Batch[1].Type != "track" {
158 t.Errorf("Missing Identify or Track information.\nIdentify: %v\nTrack:%v", s.Batch[0].Type, s.Batch[1].Type)
159 }
160 if s.Batch[0].Traits.OS != runtime.GOOS {
161 t.Error("OS does not match")
162 }
163 if !tt.success {
164 if s.Batch[1].Properties.Error != tt.err.Error() {
165 t.Error("Error does not match")
166 }
167 } else {
168 if s.Batch[1].Properties.Error != "" {
169 t.Error("Error does not match")
170 }
171 }
172 if s.Batch[1].Properties.Success != tt.success {
173 t.Error("Success does not match")
174 }
175 if s.Batch[1].Properties.ErrorType != tt.errType {
176 t.Error("Error Type does not match")
177 }
178 if !strings.Contains(s.Batch[1].Properties.Version, version.VERSION) {
179 t.Error("Odo version does not match")
180 }
181
182 default:
183 t.Error("Server should receive data")
184 }
185 })
186 }
187 }
188
189 func TestIsTelemetryEnabled(t *testing.T) {
190 type testStruct struct {
191 name string
192 env map[string]string
193 consentTelemetryPref bool
194 want func(odoDisableTelemetry, odoTrackingConsent string, consentTelemetry bool) bool
195 }
196 var tests []testStruct
197
198
199 for _, consentTelemetry := range []bool{true, false} {
200 consentTelemetry := consentTelemetry
201 tests = append(tests, testStruct{
202 name: fmt.Sprintf(
203 "ODO_DISABLE_TELEMETRY and ODO_TRACKING_CONSENT not set, consentTelemetry=%v", consentTelemetry),
204 consentTelemetryPref: consentTelemetry,
205 want: func(_, _ string, consentTelemetry bool) bool {
206 return consentTelemetry
207 },
208 })
209 }
210
211
212
213 for _, odoTrackingConsent := range []string{"", "yes", "no", "bar"} {
214 for _, consentTelemetry := range []bool{true, false} {
215 odoTrackingConsent := odoTrackingConsent
216 consentTelemetry := consentTelemetry
217 tests = append(tests, testStruct{
218 name: fmt.Sprintf(
219 "ODO_DISABLE_TELEMETRY not set, ODO_TRACKING_CONSENT=%q, consentTelemetry=%v", odoTrackingConsent, consentTelemetry),
220 consentTelemetryPref: consentTelemetry,
221 env: map[string]string{
222 TrackingConsentEnv: odoTrackingConsent,
223 },
224 want: func(_, odoTrackingConsent string, consentTelemetry bool) bool {
225
226 switch odoTrackingConsent {
227 case "yes":
228 return true
229 case "no":
230 return false
231 default:
232 return consentTelemetry
233 }
234 },
235 })
236 }
237 }
238
239
240
241 for _, odoDisableTelemetry := range []string{"true", "false"} {
242 for _, consentTelemetry := range []bool{true, false} {
243 odoDisableTelemetry := odoDisableTelemetry
244 consentTelemetry := consentTelemetry
245 tests = append(tests, testStruct{
246 name: fmt.Sprintf("ODO_DISABLE_TELEMETRY=%q,ODO_TRACKING_CONSENT not set,ConsentTelemetry=%v",
247 odoDisableTelemetry, consentTelemetry),
248 env: map[string]string{
249
250 DisableTelemetryEnv: odoDisableTelemetry,
251 },
252 want: func(odoDisableTelemetry, _ string, consentTelemetry bool) bool {
253 if odoDisableTelemetry == "true" {
254 return false
255 }
256
257 return consentTelemetry
258 },
259 })
260 }
261 }
262
263 for _, odoDisableTelemetry := range []string{"true", "false"} {
264 for _, odoTrackingConsent := range []string{"", "yes", "no", "bar"} {
265 for _, consentTelemetry := range []bool{true, false} {
266 odoDisableTelemetry := odoDisableTelemetry
267 odoTrackingConsent := odoTrackingConsent
268 consentTelemetry := consentTelemetry
269 tests = append(tests, testStruct{
270 name: fmt.Sprintf("ODO_DISABLE_TELEMETRY=%q,ODO_TRACKING_CONSENT=%q,ConsentTelemetry=%v",
271 odoDisableTelemetry, odoTrackingConsent, consentTelemetry),
272 env: map[string]string{
273
274 DisableTelemetryEnv: odoDisableTelemetry,
275 TrackingConsentEnv: odoTrackingConsent,
276 },
277 consentTelemetryPref: consentTelemetry,
278 want: func(odoDisableTelemetry, odoTrackingConsent string, consentTelemetry bool) bool {
279 if odoDisableTelemetry == "true" || odoTrackingConsent == "no" {
280 return false
281 }
282 if odoTrackingConsent == "yes" {
283 return true
284 }
285 return consentTelemetry
286 },
287 })
288 }
289 }
290 }
291
292 for _, tt := range tests {
293 t.Run(tt.name, func(t *testing.T) {
294 ctrl := gomock.NewController(t)
295 cfg := preference.NewMockClient(ctrl)
296 cfg.EXPECT().GetConsentTelemetry().Return(tt.consentTelemetryPref).AnyTimes()
297
298 envConfig, err := config.GetConfigurationWith(envconfig.MapLookuper(tt.env))
299 if err != nil {
300 t.Errorf("Get configuration fails: %v", err)
301 }
302 got := IsTelemetryEnabled(cfg, *envConfig)
303
304
305 want := tt.want(tt.env[DisableTelemetryEnv], tt.env[TrackingConsentEnv], tt.consentTelemetryPref)
306 if got != want {
307 t.Errorf(tt.name, "IsTelemetryEnabled: got %v, wanted %v. Env is set to %v. %s is set to %q.",
308 got, want, tt.env, preference.ConsentTelemetrySetting, tt.consentTelemetryPref)
309 }
310 })
311 }
312 }
313
314 func TestClientUploadWithContext(t *testing.T) {
315 var uploadData TelemetryData
316 body, server := mockServer()
317 defer server.Close()
318 defer close(body)
319
320 ctx := scontext.NewContext(context.Background())
321 scontext.SetTelemetryStatus(ctx, true)
322
323 for k, v := range map[string]string{scontext.ComponentType: "nodejs", scontext.ClusterType: ""} {
324 switch k {
325 case scontext.ComponentType:
326 scontext.SetComponentType(ctx, v)
327 uploadData = fakeTelemetryData("odo init", nil, ctx)
328 case scontext.ClusterType:
329 fakeClient, _ := kclient.FakeNew()
330 scontext.SetClusterType(ctx, fakeClient)
331 uploadData = fakeTelemetryData("odo set project", nil, ctx)
332 }
333 c, err := newCustomClient(createConfigDir(t), server.URL)
334 if err != nil {
335 t.Error(err)
336 }
337
338 if err = c.Upload(ctx, uploadData); err != nil {
339 t.Error(err)
340 }
341 if err = c.Close(); err != nil {
342 t.Error(err)
343 }
344 select {
345 case x := <-body:
346 s := segmentResponse{}
347 if err1 := json.Unmarshal(x, &s); err1 != nil {
348 t.Error(err1)
349 }
350 if s.Batch[1].Type == "identify" {
351 switch k {
352 case scontext.ComponentType:
353 if s.Batch[1].Properties.ComponentType != v {
354 t.Errorf("%v did not match. Want: %q Got: %q", scontext.ComponentType, v, s.Batch[1].Properties.ComponentType)
355 }
356 case scontext.ClusterType:
357 if s.Batch[1].Properties.ClusterType != v {
358 t.Errorf("%v did not match. Want: %q Got: %q", scontext.ClusterType, v, s.Batch[1].Properties.ClusterType)
359 }
360 }
361 }
362 default:
363 t.Error("Server should receive some data")
364 }
365 }
366 }
367
368 func TestSetError(t *testing.T) {
369 user, err := user.Current()
370 if err != nil {
371 t.Error(err.Error())
372 }
373
374 tests := []struct {
375 err error
376 hasPII bool
377 }{
378 {
379 err: errors.New("this is an error string"),
380 hasPII: false,
381 },
382 {
383 err: fmt.Errorf("failed to execute devfile commands for component %s-comp. failed to Get https://my-cluster.project.local cannot run exec command [curl https://mycluster.domain.local -u foo -p password 123]", user.Username),
384 hasPII: true,
385 },
386 }
387
388 for _, tt := range tests {
389 var want string
390 got := SetError(tt.err)
391
392
393
394 if tt.hasPII {
395 want = fmt.Sprintf("failed to execute devfile commands for component %s-comp. failed to Get %s cannot run exec command %s", Sanitizer, Sanitizer, Sanitizer)
396 } else {
397 want = tt.err.Error()
398 }
399 if got != want {
400 t.Errorf("got: %q\nwant:%q", got, want)
401 }
402
403 }
404 }
405
406 func Test_sanitizeExec(t *testing.T) {
407 err := fmt.Errorf("unable to execute the run command: unable to exec command [curl -K localhost:8080 -u user1 -p pwd123]")
408 got := sanitizeExec(err.Error())
409 want := fmt.Sprintf("unable to execute the run command: unable to exec command %s", Sanitizer)
410 if got != want {
411 t.Errorf("got: %q\nwant:%q", got, want)
412 }
413 }
414
415 func Test_sanitizeURL(t *testing.T) {
416 cases := []error{
417 fmt.Errorf("resource project validation check failed.: Get https://my-cluster.project.local request cancelled"),
418 fmt.Errorf("resource project validation check failed.: Get http://my-cluster.project.local request cancelled"),
419 fmt.Errorf("resource project validation check failed.: Get http://192.168.0.1:6443 request cancelled"),
420 fmt.Errorf("resource project validation check failed.: Get 10.18.25.1 request cancelled"),
421 fmt.Errorf("resource project validation check failed.: Get www.sample.com request cancelled"),
422 }
423
424 for _, err := range cases {
425 got := sanitizeURL(err.Error())
426 want := fmt.Sprintf("resource project validation check failed.: Get %s request cancelled", Sanitizer)
427 if got != want {
428 t.Errorf("got: %q\nwant:%q", got, want)
429 }
430 }
431 }
432
433 func Test_sanitizeFilePath(t *testing.T) {
434 unixPath := "/home/xyz/.odo/preference.yaml"
435 windowsPath := "C:\\User\\XYZ\\preference.yaml"
436
437 cases := []struct {
438 name string
439 err error
440 }{
441 {
442 name: "filepath-unix",
443 err: fmt.Errorf("cannot find the preference file at %s", unixPath),
444 },
445 {
446 name: "filepath-windows",
447 err: fmt.Errorf("cannot find the preference file at %s", windowsPath),
448 },
449 }
450 for _, tt := range cases {
451 if tt.name == "filepath-windows" && os.Getenv("GOOS") != "windows" {
452 t.Skip("Cannot run a windows test on a unix system")
453 } else if tt.name == "filepath-unix" && os.Getenv("GOOS") != "linux" {
454 t.Skip("Cannot run a unix test on a windows system")
455 }
456
457 got := sanitizeFilePath(tt.err.Error())
458 want := fmt.Sprintf("cannot find the preference file at %s", Sanitizer)
459 if got != want {
460 t.Errorf("got: %q\nwant:%q", got, want)
461 }
462 }
463 }
464
465 func Test_sanitizeUserInfo(t *testing.T) {
466 user, err1 := user.Current()
467 if err1 != nil {
468 t.Error(err1.Error())
469 }
470
471 err := fmt.Errorf("cannot create component name with %s", user.Username)
472 got := sanitizeUserInfo(err.Error())
473 want := fmt.Sprintf("cannot create component name with %s", Sanitizer)
474 if got != want {
475 t.Errorf("got: %q\nwant:%q", got, want)
476 }
477 }
478
479
480 func createConfigDir(t *testing.T) string {
481 fs := filesystem.NewFakeFs()
482 configDir, err := fs.TempDir(os.TempDir(), "telemetry")
483 if err != nil {
484 t.Error(err)
485 }
486 return configDir
487 }
488
489
490 func fakeTelemetryData(cmd string, err error, ctx context.Context) TelemetryData {
491 return TelemetryData{
492 Event: cmd,
493 Properties: TelemetryProperties{
494 Duration: time.Second.Milliseconds(),
495 Error: SetError(err),
496 ErrorType: ErrorType(err),
497 Success: err == nil,
498 Tty: RunningInTerminal(),
499 Version: version.VERSION,
500 CmdProperties: scontext.GetContextProperties(ctx),
501 },
502 }
503 }
504
View as plain text