Skip to content

Commit

Permalink
test: Written unit test for telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
JulesFaucherre committed Jun 27, 2023
1 parent 08d75b7 commit 82bce56
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 54 deletions.
26 changes: 13 additions & 13 deletions cmd/telemetry.go → cmd/check_telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,9 @@ import (
"golang.org/x/term"
)

const (
TelemetryIsActive = "yes"
TelemetryIsInactive = "no"

// This value means telemetry is disabled because we have no access to stdin and so can't ask user approval
TelemetryDefaultDisabled = "default-disabled"
var (
createUUID = func() string { return uuid.New().String() }
isStdinOpen = term.IsTerminal(int(os.Stdin.Fd()))
)

type telemetryUI interface {
Expand Down Expand Up @@ -63,10 +60,11 @@ func askForTelemetryApproval(config *settings.Config, ui telemetryUI) error {
}

// If stdin is not available, send telemetry event, disactive telemetry and return
if !term.IsTerminal(int(os.Stdin.Fd())) {
telemetry.SendTelemetryApproval(telemetry.User{}, telemetry.NoStdin)
if !isStdinOpen {
config.Telemetry.IsActive = false
return nil
return telemetry.SendTelemetryApproval(telemetry.User{
UniqueID: config.Telemetry.UniqueID,
}, telemetry.NoStdin)
}

// Else ask user for telemetry approval
Expand All @@ -79,14 +77,16 @@ func askForTelemetryApproval(config *settings.Config, ui telemetryUI) error {
config.Telemetry.HasAnsweredPrompt = true

// If user allows telemetry, create a telemetry user
user := telemetry.User{}
user := telemetry.User{
UniqueID: config.Telemetry.UniqueID,
}
if config.Telemetry.UniqueID == "" {
user.UniqueID = uuid.New().String()
user.UniqueID = createUUID()
}

if config.Telemetry.IsActive && config.Token != "" {
me, err := api.GetMe(rest.NewFromConfig(config.Host, config))
if err != nil {
if err == nil {
user.UserID = me.ID
}
}
Expand All @@ -98,7 +98,7 @@ func askForTelemetryApproval(config *settings.Config, ui telemetryUI) error {
if !config.Telemetry.IsActive {
approval = telemetry.Disabled
}
if err := telemetry.SendTelemetryApproval(telemetry.User{}, approval); err != nil {
if err := telemetry.SendTelemetryApproval(user, approval); err != nil {
return err
}

Expand Down
203 changes: 203 additions & 0 deletions cmd/check_telemetry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package cmd

import (
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"

"github.com/CircleCI-Public/circleci-cli/settings"
"github.com/CircleCI-Public/circleci-cli/telemetry"
"github.com/spf13/afero"
"gotest.tools/v3/assert"
)

type testTelemetry struct {
events []telemetry.Event
}

func (cli *testTelemetry) Close() error { return nil }

func (cli *testTelemetry) Track(event telemetry.Event) error {
cli.events = append(cli.events, event)
return nil
}

func TestAskForTelemetryApproval(t *testing.T) {
// Mock HTTP
userId := "id"
uniqueId := "unique-id"
response := fmt.Sprintf(`{"id":"%s","login":"login","name":"name"}`, userId)
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "GET")
assert.Equal(t, r.URL.String(), "/me")
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(response))
}
server := httptest.NewServer(handler)
defer server.Close()

// Mock create UUID
oldUUIDCreate := createUUID
createUUID = func() string { return uniqueId }
defer (func() { createUUID = oldUUIDCreate })()

// Create test cases
type args struct {
closeStdin bool
promptApproval bool
config settings.TelemetrySettings
}
type want struct {
config settings.TelemetrySettings
fileNotCreated bool
telemetryEvents []telemetry.Event
}
type testCase struct {
name string
args args
want want
}

testCases := []testCase{
{
name: "Prompt approval should be saved in settings",
args: args{
promptApproval: true,
config: settings.TelemetrySettings{},
},
want: want{
config: settings.TelemetrySettings{
IsActive: true,
HasAnsweredPrompt: true,
UserID: userId,
UniqueID: uniqueId,
},
telemetryEvents: []telemetry.Event{
{Object: "cli-telemetry", Action: "enabled"},
},
},
},
{
name: "Prompt disapproval should be saved in settings",
args: args{
promptApproval: false,
config: settings.TelemetrySettings{},
},
want: want{
config: settings.TelemetrySettings{
IsActive: false,
HasAnsweredPrompt: true,
UniqueID: uniqueId,
},
telemetryEvents: []telemetry.Event{
{Object: "cli-telemetry", Action: "disabled"},
},
},
},
{
name: "Does not recreate a unique ID if there is one",
args: args{
promptApproval: true,
config: settings.TelemetrySettings{
UniqueID: "other-id",
},
},
want: want{
config: settings.TelemetrySettings{
IsActive: true,
HasAnsweredPrompt: true,
UserID: userId,
UniqueID: "other-id",
},
telemetryEvents: []telemetry.Event{
{Object: "cli-telemetry", Action: "enabled"},
},
},
},
{
name: "Does not change telemetry settings if user already answered prompt",
args: args{
config: settings.TelemetrySettings{
HasAnsweredPrompt: true,
},
},
want: want{
fileNotCreated: true,
telemetryEvents: []telemetry.Event{},
},
},
{
name: "Does not change telemetry settings if user disabled telemetry",
args: args{
config: settings.TelemetrySettings{
DisabledFromParams: true,
},
},
want: want{
fileNotCreated: true,
telemetryEvents: []telemetry.Event{},
},
},
{
name: "Does not change telemetry settings if stdin is not open",
args: args{closeStdin: true},
want: want{
fileNotCreated: true,
telemetryEvents: []telemetry.Event{
{Object: "cli-telemetry", Action: "disabled_default"},
},
},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
// Mock FS
oldFS := settings.FS.Fs
settings.FS.Fs = afero.NewMemMapFs()
defer (func() { settings.FS.Fs = oldFS })()

// Mock stdin
oldIsStdinOpen := isStdinOpen
isStdinOpen = !tt.args.closeStdin
defer (func() { isStdinOpen = oldIsStdinOpen })()

// Mock telemetry
telemetryClient := testTelemetry{events: make([]telemetry.Event, 0)}
oldCreateActiveTelemetry := telemetry.CreateActiveTelemetry
telemetry.CreateActiveTelemetry = func(_ telemetry.User) telemetry.Client {
return &telemetryClient
}
defer (func() { telemetry.CreateActiveTelemetry = oldCreateActiveTelemetry })()

// Run askForTelemetryApproval
config := settings.Config{
Token: "testtoken",
HTTPClient: http.DefaultClient,
Host: server.URL,
Telemetry: tt.args.config,
}
err := askForTelemetryApproval(&config, telemetryTestUI{tt.args.promptApproval})
assert.NilError(t, err)

// Verify good telemetry events were sent
assert.DeepEqual(t, telemetryClient.events, tt.want.telemetryEvents)

// Verify if settings file exist
exist, err := settings.FS.Exists(filepath.Join(settings.SettingsPath(), "telemetry.yml"))
assert.NilError(t, err)
assert.Equal(t, exist, !tt.want.fileNotCreated)
if tt.want.fileNotCreated {
return
}

// Verify settings file content
result := settings.TelemetrySettings{}
err = result.Load()
assert.NilError(t, err)
assert.Equal(t, result, tt.want.config)
})
}
}
5 changes: 5 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ func MakeCommands() *cobra.Command {
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
return rootCmdPreRun(rootOptions)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if rootOptions.Telemetry.Client != nil {
rootOptions.Telemetry.Client.Close()
}
},
}

// For supporting "Args" in command usage help
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ require (
github.com/segmentio/backo-go v1.0.1 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.1.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
Expand Down
Loading

0 comments on commit 82bce56

Please sign in to comment.