diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index f675cbe7..915e197e 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -477,6 +477,18 @@ func (bridge *Bridge) Close(ctx context.Context) { bridge.watchers = nil } +func (bridge *Bridge) ComputeTelemetry() bool { + var telemetry = true + + safe.RLock(func() { + for _, user := range bridge.users { + telemetry = telemetry && user.IsTelemetryEnabled(context.Background()) + } + }, bridge.usersLock) + + return telemetry +} + func (bridge *Bridge) publish(event events.Event) { bridge.watchersLock.RLock() defer bridge.watchersLock.RUnlock() diff --git a/internal/user/user.go b/internal/user/user.go index 71a46236..25dc72cb 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -591,6 +591,16 @@ func (user *User) Close() { } } +// IsTelemetryEnabled check if the telemetry is enabled or disabled for this user. +func (user *User) IsTelemetryEnabled(ctx context.Context) bool { + settings, err := user.client.GetUserSettings(ctx) + if err != nil { + user.log.WithError(err).Warn("Failed to retrieve API user Settings") + return false + } + return settings.Telemetry == proton.SettingEnabled +} + // initUpdateCh initializes the user's update channels in the given address mode. // It is assumed that user.apiAddrs and user.updateCh are already locked. func (user *User) initUpdateCh(mode vault.AddressMode) { diff --git a/internal/user/user_test.go b/internal/user/user_test.go index c0888147..cf3388d5 100644 --- a/internal/user/user_test.go +++ b/internal/user/user_test.go @@ -80,6 +80,23 @@ func TestUser_AddressMode(t *testing.T) { }) } +func TestUser_Telemetry(t *testing.T) { + withAPI(t, context.Background(), func(ctx context.Context, s *server.Server, m *proton.Manager) { + withAccount(t, s, "username", "password", []string{}, func(string, []string) { + withUser(t, ctx, s, m, "username", "password", func(user *User) { + // By default, user should have Telemetry enabled. + telemetry := user.IsTelemetryEnabled(ctx) + require.Equal(t, true, telemetry) + + user.client.Close() + // If telemetry cannot be retrieved it is disabled. + telemetry = user.IsTelemetryEnabled(ctx) + require.Equal(t, false, telemetry) + }) + }) + }) +} + func withAPI(_ testing.TB, ctx context.Context, fn func(context.Context, *server.Server, *proton.Manager)) { //nolint:revive server := server.New() defer server.Close() diff --git a/tests/bdd_test.go b/tests/bdd_test.go index 125cb656..cad4a1fc 100644 --- a/tests/bdd_test.go +++ b/tests/bdd_test.go @@ -153,6 +153,8 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^bridge sends an update not available event$`, s.bridgeSendsAnUpdateNotAvailableEvent) ctx.Step(`^bridge sends a forced update event$`, s.bridgeSendsAForcedUpdateEvent) ctx.Step(`^bridge reports a message with "([^"]*)"$`, s.bridgeReportsMessage) + ctx.Step(`^bridge telemetry feature is enabled$`, s.bridgeTelemetryFeatureEnabled) + ctx.Step(`^bridge telemetry feature is disabled$`, s.bridgeTelemetryFeatureDisabled) // ==== FRONTEND ==== ctx.Step(`^frontend sees that bridge is version "([^"]*)"$`, s.frontendSeesThatBridgeIsVersion) @@ -166,6 +168,7 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^user "([^"]*)" is listed but not connected$`, s.userIsListedButNotConnected) ctx.Step(`^user "([^"]*)" is not listed$`, s.userIsNotListed) ctx.Step(`^user "([^"]*)" finishes syncing$`, s.userFinishesSyncing) + ctx.Step(`^user "([^"]*)" has telemetry set to (\d+)$`, s.userHasTelemetrySetTo) // ==== IMAP ==== ctx.Step(`^user "([^"]*)" connects IMAP client "([^"]*)"$`, s.userConnectsIMAPClient) diff --git a/tests/bridge_test.go b/tests/bridge_test.go index 45f6e67a..14632013 100644 --- a/tests/bridge_test.go +++ b/tests/bridge_test.go @@ -284,6 +284,22 @@ func (s *scenario) bridgeReportsMessage(message string) error { return nil } +func (s *scenario) bridgeTelemetryFeatureEnabled() error { + return s.checkTelemetry(true) +} + +func (s *scenario) bridgeTelemetryFeatureDisabled() error { + return s.checkTelemetry(false) +} + +func (s *scenario) checkTelemetry(expect bool) error { + res := s.t.bridge.ComputeTelemetry() + if res != expect { + return fmt.Errorf("expected telemetry feature %v but got %v ", expect, res) + } + return nil +} + func (s *scenario) theUserHidesAllMail() error { return s.t.bridge.SetShowAllMail(false) } diff --git a/tests/features/user/telemetry.feature b/tests/features/user/telemetry.feature new file mode 100644 index 00000000..9fd7ff64 --- /dev/null +++ b/tests/features/user/telemetry.feature @@ -0,0 +1,18 @@ +Feature: Bridge send usage metrics + Background: + Given there exists an account with username "[user:user1]" and password "password" + And there exists an account with username "[user:user2]" and password "password" + And bridge starts + + + Scenario: Telemetry availability - No user + Then bridge telemetry feature is enabled + + Scenario: Telemetry availability - Multi user + When the user logs in with username "[user:user1]" and password "password" + And user "[user:user1]" finishes syncing + Then bridge telemetry feature is enabled + When the user logs in with username "[user:user2]" and password "password" + And user "[user:user2]" finishes syncing + When user "[user:user2]" has telemetry set to 0 + Then bridge telemetry feature is disabled diff --git a/tests/user_test.go b/tests/user_test.go index 3be34f5f..17542987 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -414,6 +414,18 @@ func (s *scenario) userFinishesSyncing(username string) error { return s.bridgeSendsSyncStartedAndFinishedEventsForUser(username) } +func (s *scenario) userHasTelemetrySetTo(username string, telemetry int) error { + return s.t.withClientPass(context.Background(), username, s.t.getUserByName(username).userPass, func(ctx context.Context, c *proton.Client) error { + var req proton.SetTelemetryReq + req.Telemetry = proton.SettingsBool(telemetry) + _, err := c.SetUserSettingsTelemetry(ctx, req) + if err != nil { + return err + } + return nil + }) +} + func (s *scenario) addAdditionalAddressToAccount(username, address string, disabled bool) error { userID := s.t.getUserByName(username).getUserID()