Skip to content

Commit

Permalink
adding tests for dashboard settings
Browse files Browse the repository at this point in the history
fixing database persistence for settings.
using reflection instead of abusing AppConfig for parsing UserSettingsEntries to UserSettings struct.
  • Loading branch information
AnalogJ committed Aug 25, 2023
1 parent cb6cb1d commit 9a4dcf9
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 59 deletions.
2 changes: 0 additions & 2 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"strings"
)

const DB_USER_SETTINGS_SUBKEY = "user"

// When initializing this class the following methods must be called:
// Config.New
// Config.Init
Expand Down
14 changes: 14 additions & 0 deletions backend/pkg/config/mock/mock_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 46 additions & 2 deletions backend/pkg/database/mock/mock_database.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 17 additions & 53 deletions backend/pkg/database/sqlite_repository_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package database
import (
"context"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"strings"
)

// LoadSettings will retrieve settings from the database, store them in the AppConfig object, and return a Settings struct
Expand All @@ -27,88 +24,55 @@ func (sr *SqliteRepository) LoadUserSettings(ctx context.Context) (*models.UserS
return nil, fmt.Errorf("Could not get settings from DB: %v", err)
}

// store retrieved settings in the AppConfig obj
settings := models.UserSettings{}
for _, settingsEntry := range settingsEntries {
configKey := fmt.Sprintf("%s.%s", config.DB_USER_SETTINGS_SUBKEY, settingsEntry.SettingKeyName)

if settingsEntry.SettingDataType == "numeric" {
sr.AppConfig.SetDefault(configKey, settingsEntry.SettingValueNumeric)
} else if settingsEntry.SettingDataType == "string" {
sr.AppConfig.SetDefault(configKey, settingsEntry.SettingValueString)
} else if settingsEntry.SettingDataType == "bool" {
sr.AppConfig.SetDefault(configKey, settingsEntry.SettingValueBool)
} else if settingsEntry.SettingDataType == "array" {
sr.AppConfig.SetDefault(configKey, settingsEntry.SettingValueArray)
err := settings.FromUserSettingsEntry(&settingsEntry)
if err != nil {
return nil, fmt.Errorf("Could not get settings from DB: %v", err)
}
}

// unmarshal the dbsetting object data to a settings object.
var settings models.UserSettings
err := sr.AppConfig.UnmarshalKey(config.DB_USER_SETTINGS_SUBKEY, &settings)
if err != nil {
return nil, err
}
return &settings, nil
}

// testing
// curl -d '{"metrics": { "notify_level": 5, "status_filter_attributes": 5, "status_threshold": 5 }}' -H "Content-Type: application/json" -X POST http://localhost:9090/api/settings
// SaveSettings will update settings in AppConfig object, then save the settings to the database.
func (sr *SqliteRepository) SaveUserSettings(ctx context.Context, settings *models.UserSettings) error {
func (sr *SqliteRepository) SaveUserSettings(ctx context.Context, newSettings *models.UserSettings) error {
currentUser, currentUserErr := sr.GetCurrentUser(ctx)
if currentUserErr != nil {
return currentUserErr
}

//save the entries to the appconfig
settingsMap := &map[string]interface{}{}
err := mapstructure.Decode(settings, &settingsMap)
if err != nil {
return err
}
settingsWrapperMap := map[string]interface{}{}
settingsWrapperMap[config.DB_USER_SETTINGS_SUBKEY] = *settingsMap
err = sr.AppConfig.MergeConfigMap(settingsWrapperMap)
if err != nil {
return err
}
sr.Logger.Debugf("after merge settings: %v", sr.AppConfig.AllSettings())
//retrieve current settings from the database
settingsEntries := []models.UserSettingEntry{}
currentSettingsEntries := []models.UserSettingEntry{}

if err := sr.GormClient.
WithContext(ctx).
Where(models.UserSettingEntry{
UserID: currentUser.ID,
}).
Find(&settingsEntries).Error; err != nil {
Find(&currentSettingsEntries).Error; err != nil {
return fmt.Errorf("Could not get settings from DB: %v", err)
}

//update settingsEntries
for ndx, settingsEntry := range settingsEntries {
configKey := fmt.Sprintf("%s.%s", config.DB_USER_SETTINGS_SUBKEY, strings.ToLower(settingsEntry.SettingKeyName))
if !sr.AppConfig.IsSet(configKey) {
continue //skip any settings that don't exist in the appconfig
}

if settingsEntry.SettingDataType == "numeric" {
settingsEntries[ndx].SettingValueNumeric = sr.AppConfig.GetInt(configKey)
} else if settingsEntry.SettingDataType == "string" {
settingsEntries[ndx].SettingValueString = sr.AppConfig.GetString(configKey)
} else if settingsEntry.SettingDataType == "bool" {
settingsEntries[ndx].SettingValueBool = sr.AppConfig.GetBool(configKey)
} else if settingsEntry.SettingDataType == "array" {
settingsEntries[ndx].SettingValueArray = sr.AppConfig.GetStringSlice(configKey)
}
newSettingsEntries, err := newSettings.ToUserSettingsEntry(currentSettingsEntries)
if err != nil {
return fmt.Errorf("merge new settings with DB: %v", err)
}

for ndx, settingsEntry := range newSettingsEntries {

// store in database.
//TODO: this should be `sr.gormClient.Updates(&settingsEntries).Error`
err := sr.GormClient.
WithContext(ctx).
Model(&models.UserSettingEntry{}).
Where([]uuid.UUID{settingsEntry.ID}).
Select("setting_value_numeric", "setting_value_string", "setting_value_bool").
Updates(settingsEntries[ndx]).Error
Select("setting_value_numeric", "setting_value_string", "setting_value_bool", "setting_value_array").
Updates(newSettingsEntries[ndx]).Error
if err != nil {
return err
}
Expand All @@ -123,7 +87,7 @@ func (sr *SqliteRepository) PopulateDefaultUserSettings(ctx context.Context, use
settingsEntries = append(settingsEntries, models.UserSettingEntry{
UserID: userId,
SettingKeyName: "dashboard_locations",
SettingKeyDescription: "customized dashboard json locations",
SettingKeyDescription: "remote dashboard locations (github gists)",
SettingDataType: "array",
SettingValueArray: []string{},
})
Expand Down
103 changes: 103 additions & 0 deletions backend/pkg/database/sqlite_repository_settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package database

import (
"context"
"fmt"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"io/ioutil"
"log"
"os"
"testing"
)

// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type RepositorySettingsTestSuite struct {
suite.Suite
TestDatabase *os.File
TestConfig config.Interface

TestRepository DatabaseRepository
TestUser *models.User
}

// BeforeTest has a function to be executed right before the test starts and receives the suite and test names as input
func (suite *RepositorySettingsTestSuite) BeforeTest(suiteName, testName string) {

dbFile, err := ioutil.TempFile("", fmt.Sprintf("%s.*.db", testName))
if err != nil {
log.Fatal(err)
}
suite.TestDatabase = dbFile

testConfig, err := config.Create()
require.NoError(suite.T(), err)
testConfig.SetDefault("database.location", suite.TestDatabase.Name())
testConfig.SetDefault("log.level", "INFO")
suite.TestConfig = testConfig

dbRepo, err := NewRepository(testConfig, logrus.WithField("test", suite.T().Name()))
require.NoError(suite.T(), err)
suite.TestRepository = dbRepo
userModel := &models.User{
Username: "test_username",
Password: "testpassword",
Email: "test@test.com",
}
err = suite.TestRepository.CreateUser(context.Background(), userModel)
suite.TestUser = userModel
require.NoError(suite.T(), err)

}

// AfterTest has a function to be executed right after the test finishes and receives the suite and test names as input
func (suite *RepositorySettingsTestSuite) AfterTest(suiteName, testName string) {
os.Remove(suite.TestDatabase.Name())
}

// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestRepositorySettingsTestSuite(t *testing.T) {
suite.Run(t, new(RepositorySettingsTestSuite))

}

func (suite *RepositorySettingsTestSuite) TestLoadUserSettings() {
//setup
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")

//test
userSettings, err := suite.TestRepository.LoadUserSettings(authContext)
require.NoError(suite.T(), err)

//assert
require.Equal(suite.T(), userSettings, &models.UserSettings{
DashboardLocations: []string{},
})
}

func (suite *RepositorySettingsTestSuite) TestSaveUserSettings() {
//setup
authContext := context.WithValue(context.Background(), pkg.ContextKeyTypeAuthUsername, "test_username")

//test
err := suite.TestRepository.SaveUserSettings(authContext, &models.UserSettings{
DashboardLocations: []string{"https://gist.github.com/AnalogJ/a56ded05cc6766b377268f14719cb84d"},
})
require.NoError(suite.T(), err)
userSettings, err := suite.TestRepository.LoadUserSettings(authContext)
require.NoError(suite.T(), err)

//assert
require.Equal(suite.T(), userSettings, &models.UserSettings{
DashboardLocations: []string{
"https://gist.github.com/AnalogJ/a56ded05cc6766b377268f14719cb84d",
},
})
}

0 comments on commit 9a4dcf9

Please sign in to comment.