diff --git a/internal/configstatus/config_status_test.go b/internal/configstatus/config_status_test.go
new file mode 100644
index 00000000..743618c2
--- /dev/null
+++ b/internal/configstatus/config_status_test.go
@@ -0,0 +1,206 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+package configstatus_test
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfigStatus_init_virgin(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+ require.Equal(t, "1.0.0", config.Data.Metadata.Version)
+
+ require.Equal(t, false, config.Data.DataV1.PendingSince.IsZero())
+ require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
+
+ require.Equal(t, "", config.Data.DataV1.Autoconf)
+ require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
+ require.Equal(t, false, config.Data.DataV1.ReportSent)
+ require.Equal(t, false, config.Data.DataV1.ReportClick)
+ require.Equal(t, "", config.Data.DataV1.FailureDetails)
+}
+
+func TestConfigStatus_init_existing(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ var data = configstatus.ConfigurationStatusData{
+ Metadata: configstatus.Metadata{Version: "1.0.0"},
+ DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
+ }
+ require.NoError(t, dumpConfigStatusInFile(&data, file))
+
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, "1.0.0", config.Data.Metadata.Version)
+ require.Equal(t, "Mr TBird", config.Data.DataV1.Autoconf)
+}
+
+func TestConfigStatus_init_bad_version(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ var data = configstatus.ConfigurationStatusData{
+ Metadata: configstatus.Metadata{Version: "2.0.0"},
+ DataV1: configstatus.DataV1{Autoconf: "Mr TBird"},
+ }
+ require.NoError(t, dumpConfigStatusInFile(&data, file))
+
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, "1.0.0", config.Data.Metadata.Version)
+ require.Equal(t, "", config.Data.DataV1.Autoconf)
+}
+
+func TestConfigStatus_IsPending(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, true, config.IsPending())
+ config.Data.DataV1.PendingSince = time.Time{}
+ require.Equal(t, false, config.IsPending())
+}
+
+func TestConfigStatus_IsFromFailure(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, false, config.IsFromFailure())
+ config.Data.DataV1.FailureDetails = "test"
+ require.Equal(t, true, config.IsFromFailure())
+}
+
+func TestConfigStatus_ApplySuccess(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, true, config.IsPending())
+ require.NoError(t, config.ApplySuccess())
+ require.Equal(t, false, config.IsPending())
+
+ config2, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
+ require.Equal(t, true, config2.Data.DataV1.PendingSince.IsZero())
+ require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
+ require.Equal(t, "", config2.Data.DataV1.Autoconf)
+ require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
+ require.Equal(t, false, config2.Data.DataV1.ReportSent)
+ require.Equal(t, false, config2.Data.DataV1.ReportClick)
+ require.Equal(t, "", config2.Data.DataV1.FailureDetails)
+}
+
+func TestConfigStatus_ApplyFailure(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+ require.NoError(t, config.ApplySuccess())
+
+ require.NoError(t, config.ApplyFailure("Big Failure"))
+ require.Equal(t, true, config.IsFromFailure())
+ require.Equal(t, true, config.IsPending())
+
+ config2, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
+ require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
+ require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
+ require.Equal(t, "", config2.Data.DataV1.Autoconf)
+ require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
+ require.Equal(t, false, config2.Data.DataV1.ReportSent)
+ require.Equal(t, false, config2.Data.DataV1.ReportClick)
+ require.Equal(t, "Big Failure", config2.Data.DataV1.FailureDetails)
+}
+
+func TestConfigStatus_ApplyProgress(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, true, config.IsPending())
+ require.Equal(t, true, config.Data.DataV1.LastProgress.IsZero())
+
+ require.NoError(t, config.ApplyProgress())
+
+ config2, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
+ require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
+ require.Equal(t, false, config2.Data.DataV1.LastProgress.IsZero())
+ require.Equal(t, "", config2.Data.DataV1.Autoconf)
+ require.Equal(t, uint64(0), config2.Data.DataV1.ClickedLink)
+ require.Equal(t, false, config2.Data.DataV1.ReportSent)
+ require.Equal(t, false, config2.Data.DataV1.ReportClick)
+ require.Equal(t, "", config2.Data.DataV1.FailureDetails)
+}
+
+func TestConfigStatus_RecordLinkClicked(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, uint64(0), config.Data.DataV1.ClickedLink)
+ require.NoError(t, config.RecordLinkClicked(0))
+ require.Equal(t, uint64(1), config.Data.DataV1.ClickedLink)
+ require.NoError(t, config.RecordLinkClicked(1))
+ require.Equal(t, uint64(3), config.Data.DataV1.ClickedLink)
+
+ config2, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ require.Equal(t, "1.0.0", config2.Data.Metadata.Version)
+ require.Equal(t, false, config2.Data.DataV1.PendingSince.IsZero())
+ require.Equal(t, true, config2.Data.DataV1.LastProgress.IsZero())
+ require.Equal(t, "", config2.Data.DataV1.Autoconf)
+ require.Equal(t, uint64(3), config2.Data.DataV1.ClickedLink)
+ require.Equal(t, false, config2.Data.DataV1.ReportSent)
+ require.Equal(t, false, config2.Data.DataV1.ReportClick)
+ require.Equal(t, "", config2.Data.DataV1.FailureDetails)
+}
+
+func dumpConfigStatusInFile(data *configstatus.ConfigurationStatusData, file string) error {
+ f, err := os.Create(file)
+ if err != nil {
+ return err
+ }
+ defer func() { _ = f.Close() }()
+
+ return json.NewEncoder(f).Encode(data)
+}
diff --git a/internal/configstatus/configuration_abort_test.go b/internal/configstatus/configuration_abort_test.go
new file mode 100644
index 00000000..e076952a
--- /dev/null
+++ b/internal/configstatus/configuration_abort_test.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+package configstatus_test
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfigurationAbort_default(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigAbortBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_abort", req.Event)
+ require.Equal(t, 0, req.Values.Duration)
+ require.Equal(t, false, req.Dimensions.ReportClick)
+ require.Equal(t, false, req.Dimensions.ReportSent)
+ require.Equal(t, uint64(0), req.Dimensions.ClickedLink)
+}
+
+func TestConfigurationAbort_fed(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ var data = configstatus.ConfigurationStatusData{
+ Metadata: configstatus.Metadata{Version: "1.0.0"},
+ DataV1: configstatus.DataV1{
+ PendingSince: time.Now().Add(-10 * time.Minute),
+ LastProgress: time.Time{},
+ Autoconf: "Mr TBird",
+ ClickedLink: 42,
+ ReportSent: false,
+ ReportClick: true,
+ FailureDetails: "Not an error",
+ },
+ }
+ require.NoError(t, dumpConfigStatusInFile(&data, file))
+
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigAbortBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_abort", req.Event)
+ require.Equal(t, 10, req.Values.Duration)
+ require.Equal(t, true, req.Dimensions.ReportClick)
+ require.Equal(t, false, req.Dimensions.ReportSent)
+ require.Equal(t, uint64(42), req.Dimensions.ClickedLink)
+}
diff --git a/internal/configstatus/configuration_progress.go b/internal/configstatus/configuration_progress.go
index 37dfd2d8..8046e689 100644
--- a/internal/configstatus/configuration_progress.go
+++ b/internal/configstatus/configuration_progress.go
@@ -45,6 +45,9 @@ func (*ConfigProgressBuilder) New(data *ConfigurationStatusData) ConfigProgressD
}
func numberOfDay(now, prev time.Time) int {
+ if now.IsZero() || prev.IsZero() {
+ return 0
+ }
if now.Year() > prev.Year() {
if now.YearDay() > prev.YearDay() {
return 365 + (now.YearDay() - prev.YearDay())
diff --git a/internal/configstatus/configuration_progress_test.go b/internal/configstatus/configuration_progress_test.go
new file mode 100644
index 00000000..ff0cd88c
--- /dev/null
+++ b/internal/configstatus/configuration_progress_test.go
@@ -0,0 +1,71 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+package configstatus_test
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfigurationProgress_default(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigProgressBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_progress", req.Event)
+ require.Equal(t, 0, req.Values.NbDay)
+ require.Equal(t, 0, req.Values.NbDaySinceLast)
+}
+
+func TestConfigurationProgress_fed(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ var data = configstatus.ConfigurationStatusData{
+ Metadata: configstatus.Metadata{Version: "1.0.0"},
+ DataV1: configstatus.DataV1{
+ PendingSince: time.Now().AddDate(0, 0, -5),
+ LastProgress: time.Now().AddDate(0, 0, -2),
+ Autoconf: "Mr TBird",
+ ClickedLink: 42,
+ ReportSent: false,
+ ReportClick: true,
+ FailureDetails: "Not an error",
+ },
+ }
+ require.NoError(t, dumpConfigStatusInFile(&data, file))
+
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigProgressBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_progress", req.Event)
+ require.Equal(t, 5, req.Values.NbDay)
+ require.Equal(t, 2, req.Values.NbDaySinceLast)
+}
diff --git a/internal/configstatus/configuration_recovery_test.go b/internal/configstatus/configuration_recovery_test.go
new file mode 100644
index 00000000..722e5245
--- /dev/null
+++ b/internal/configstatus/configuration_recovery_test.go
@@ -0,0 +1,79 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+package configstatus_test
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfigurationRecovery_default(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigRecoveryBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_recovery", req.Event)
+ require.Equal(t, 0, req.Values.Duration)
+ require.Equal(t, "", req.Dimensions.Autoconf)
+ require.Equal(t, false, req.Dimensions.ReportClick)
+ require.Equal(t, false, req.Dimensions.ReportSent)
+ require.Equal(t, uint64(0), req.Dimensions.ClickedLink)
+ require.Equal(t, "", req.Dimensions.FailureDetails)
+}
+
+func TestConfigurationRecovery_fed(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ var data = configstatus.ConfigurationStatusData{
+ Metadata: configstatus.Metadata{Version: "1.0.0"},
+ DataV1: configstatus.DataV1{
+ PendingSince: time.Now().Add(-10 * time.Minute),
+ LastProgress: time.Time{},
+ Autoconf: "Mr TBird",
+ ClickedLink: 42,
+ ReportSent: false,
+ ReportClick: true,
+ FailureDetails: "Not an error",
+ },
+ }
+ require.NoError(t, dumpConfigStatusInFile(&data, file))
+
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigRecoveryBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_recovery", req.Event)
+ require.Equal(t, 10, req.Values.Duration)
+ require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
+ require.Equal(t, true, req.Dimensions.ReportClick)
+ require.Equal(t, false, req.Dimensions.ReportSent)
+ require.Equal(t, uint64(42), req.Dimensions.ClickedLink)
+ require.Equal(t, "Not an error", req.Dimensions.FailureDetails)
+}
diff --git a/internal/configstatus/configuration_success_test.go b/internal/configstatus/configuration_success_test.go
new file mode 100644
index 00000000..83b73f24
--- /dev/null
+++ b/internal/configstatus/configuration_success_test.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+package configstatus_test
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
+ "github.com/stretchr/testify/require"
+)
+
+func TestConfigurationSuccess_default(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigSuccessBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_success", req.Event)
+ require.Equal(t, 0, req.Values.Duration)
+ require.Equal(t, "", req.Dimensions.Autoconf)
+ require.Equal(t, false, req.Dimensions.ReportClick)
+ require.Equal(t, false, req.Dimensions.ReportSent)
+ require.Equal(t, uint64(0), req.Dimensions.ClickedLink)
+}
+
+func TestConfigurationSuccess_fed(t *testing.T) {
+ dir := t.TempDir()
+ file := filepath.Join(dir, "dummy.json")
+ var data = configstatus.ConfigurationStatusData{
+ Metadata: configstatus.Metadata{Version: "1.0.0"},
+ DataV1: configstatus.DataV1{
+ PendingSince: time.Now().Add(-10 * time.Minute),
+ LastProgress: time.Time{},
+ Autoconf: "Mr TBird",
+ ClickedLink: 42,
+ ReportSent: false,
+ ReportClick: true,
+ FailureDetails: "Not an error",
+ },
+ }
+ require.NoError(t, dumpConfigStatusInFile(&data, file))
+
+ config, err := configstatus.LoadConfigurationStatus(file)
+ require.NoError(t, err)
+
+ var builder = configstatus.ConfigSuccessBuilder{}
+ req := builder.New(config.Data)
+
+ require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
+ require.Equal(t, "bridge_config_success", req.Event)
+ require.Equal(t, 10, req.Values.Duration)
+ require.Equal(t, "Mr TBird", req.Dimensions.Autoconf)
+ require.Equal(t, true, req.Dimensions.ReportClick)
+ require.Equal(t, false, req.Dimensions.ReportSent)
+ require.Equal(t, uint64(42), req.Dimensions.ClickedLink)
+}
diff --git a/tests/config_status_test.go b/tests/config_status_test.go
index 254c2a1f..e6fff35c 100644
--- a/tests/config_status_test.go
+++ b/tests/config_status_test.go
@@ -119,7 +119,7 @@ func (s *scenario) forceConfigStatusProgressToBeSentForUser(username string) err
if err != nil {
return err
}
- defer f.Close()
+ defer func() { _ = f.Close() }()
return json.NewEncoder(f).Encode(data)
}