diff --git a/internal/config/config.go b/internal/config/config.go index 29cae4a..8946aa0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,11 @@ import ( ) const ( - configFileName = "config.json" + configFileName string = "config.json" + + defaultHTTPTimeout int = 5 + defaultHTTPMediaTimeout int = 30 + defaultLineWrapMaxWidth int = 80 ) type Config struct { @@ -35,7 +39,10 @@ type Integrations struct { } func NewConfigFromFile(configDir string) (*Config, error) { - path := configFile(configDir) + path, err := configPath(configDir) + if err != nil { + return nil, fmt.Errorf("unable to calculate the path to your config file: %w", err) + } file, err := os.Open(path) if err != nil { @@ -53,13 +60,19 @@ func NewConfigFromFile(configDir string) (*Config, error) { } func FileExists(configDir string) (bool, error) { - path := configFile(configDir) + path, err := configPath(configDir) + if err != nil { + return false, fmt.Errorf("unable to calculate the path to your config file: %w", err) + } return utilities.FileExists(path) } func SaveDefaultConfigToFile(configDir string) error { - path := configFile(configDir) + path, err := configPath(configDir) + if err != nil { + return fmt.Errorf("unable to calculate the path to your config file: %w", err) + } file, err := os.Create(path) if err != nil { @@ -69,7 +82,7 @@ func SaveDefaultConfigToFile(configDir string) error { config := defaultConfig() - credentialsFilePath, err := utilities.AbsolutePath(defaultCredentialsConfigFile(configDir)) + credentialsFilePath, err := defaultCredentialsConfigFile(configDir) if err != nil { return fmt.Errorf("unable to calculate the path to the credentials file: %w", err) } @@ -86,8 +99,13 @@ func SaveDefaultConfigToFile(configDir string) error { return nil } -func configFile(configDir string) string { - return filepath.Join(utilities.CalculateConfigDir(configDir), configFileName) +func configPath(configDir string) (string, error) { + configDir, err := utilities.CalculateConfigDir(configDir) + if err != nil { + return "", fmt.Errorf("unable to get the config directory: %w", err) + } + + return filepath.Join(configDir, configFileName), nil } func defaultConfig() Config { @@ -95,10 +113,10 @@ func defaultConfig() Config { CredentialsFile: "", CacheDirectory: "", HTTP: HTTPConfig{ - Timeout: 5, - MediaTimeout: 30, + Timeout: defaultHTTPTimeout, + MediaTimeout: defaultHTTPMediaTimeout, }, - LineWrapMaxWidth: 80, + LineWrapMaxWidth: defaultLineWrapMaxWidth, Integrations: Integrations{ Browser: "", Editor: "", diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..795a060 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,83 @@ +package config_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" +) + +func TestConfigFile(t *testing.T) { + t.Log("Testing saving and loading the configuration") + + projectDir, err := projectRoot() + if err != nil { + t.Fatalf("Unable to get the project root directory: %v", err) + } + + configDir := filepath.Join(projectDir, "test", "config") + + t.Run("Save the default configuration to file", testSaveDefaultConfigToFile(configDir)) + t.Run("Load the configuration from file", testLoadConfigFromFile(configDir)) + + expectedConfigFile := filepath.Join(configDir, "config.json") + if err := os.Remove(expectedConfigFile); err != nil { + t.Fatalf( + "received an error after trying to clean up the test configuration at %q: %v", + expectedConfigFile, + err, + ) + } +} + +func testSaveDefaultConfigToFile(configDir string) func(t *testing.T) { + return func(t *testing.T) { + if err := config.SaveDefaultConfigToFile(configDir); err != nil { + t.Fatalf("Unable to save the default configuration within %q: %v", configDir, err) + } + + fileExists, err := config.FileExists(configDir) + if err != nil { + t.Fatalf("Unable to determine if the configuration exists within %q: %v", configDir, err) + } + + if !fileExists { + t.Fatalf("The configuration does not appear to exist within %q", configDir) + } + } +} + +func testLoadConfigFromFile(configDir string) func(t *testing.T) { + return func(t *testing.T) { + config, err := config.NewConfigFromFile(configDir) + if err != nil { + t.Fatalf("Unable to load the configuration from file: %v", err) + } + + expectedDefaultHTTPTimeout := 5 + + if config.HTTP.Timeout != expectedDefaultHTTPTimeout { + t.Errorf( + "Unexpected HTTP Timeout settings loaded from the configuration: want %d, got %d", + expectedDefaultHTTPTimeout, + config.HTTP.Timeout, + ) + } else { + t.Logf( + "Expected HTTP Timeout settings loaded from the configuration: got %d", + config.HTTP.Timeout, + ) + } + } +} + +func projectRoot() (string, error) { + cwd, err := os.Getwd() + if err != nil { + return "", fmt.Errorf("unable to get the current working directory, %w", err) + } + + return filepath.Join(cwd, "..", ".."), nil +} diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 57e8ee5..4a2df20 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -38,9 +38,15 @@ func (e CredentialsNotFoundError) Error() string { // directory. If the directory is not specified then the default directory is used. If the directory // is not present, it will be created. func SaveCredentials(filePath, username string, credentials Credentials) (string, error) { - directory := filepath.Dir(filePath) + part := filepath.Dir(filePath) - if err := utilities.EnsureDirectory(utilities.CalculateConfigDir(directory)); err != nil { + // ensure that the directory exists. + credentialsDir, err := utilities.CalculateConfigDir(part) + if err != nil { + return "", fmt.Errorf("unable to calculate the directory to your credentials file: %w", err) + } + + if err := utilities.EnsureDirectory(credentialsDir); err != nil { return "", fmt.Errorf("unable to ensure the configuration directory: %w", err) } @@ -128,6 +134,16 @@ func saveCredentialsConfigFile(authConfig CredentialsConfig, filePath string) er return nil } -func defaultCredentialsConfigFile(configDir string) string { - return filepath.Join(utilities.CalculateConfigDir(configDir), defaultCredentialsFileName) +func defaultCredentialsConfigFile(configDir string) (string, error) { + dir, err := utilities.CalculateConfigDir(configDir) + if err != nil { + return "", fmt.Errorf("unable to calculate the config directory: %w", err) + } + + path, err := utilities.AbsolutePath(filepath.Join(dir, defaultCredentialsFileName)) + if err != nil { + return "", fmt.Errorf("unable to get the absolute path to the credentials config file: %w", err) + } + + return path, nil } diff --git a/internal/config/credentials_test.go b/internal/config/credentials_test.go new file mode 100644 index 0000000..96621dd --- /dev/null +++ b/internal/config/credentials_test.go @@ -0,0 +1,107 @@ +package config_test + +import ( + "maps" + "os" + "path/filepath" + "testing" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" +) + +func TestCredentialsFile(t *testing.T) { + t.Log("Testing saving and loading credentials from file") + + projectDir, err := projectRoot() + if err != nil { + t.Fatalf("Unable to get the project root directory: %v", err) + } + + credentialsFile := filepath.Join(projectDir, "test", "config", "credentials.json") + + credentialsMap := map[string]config.Credentials{ + "admin": { + Instance: "https://gts.red-crow.private", + ClientID: "01EOB91DVQGPA364QK32TM3LXR1998BMXSZE4", + ClientSecret: "ffd76025-4b23-4ce6-b8ea-077ce3cadf5a", + AccessToken: "C9VDXGGRPZ0448SH562N6N6893VNPGJMGJ336TXLMH8RXGWF4", + }, + "bobby": { + Instance: "https://gts.red-crow.private", + ClientID: "01CUVHR6LIST7Q6R25Z9Y14WZK780V91S9VQB", + ClientSecret: "379fc272-c7cc-4ccb-8461-f3f71207f798", + AccessToken: "F0YWQG1R4DDAMXGBZ514BCW7ATWN6JRGLDRUZO4RFAMTT6J38", + }, + "app": { + Instance: "https://gts.red-crow.private", + ClientID: "01HLZY7XCD60564OP3RG6FZTOAD3LGF0R8SEK", + ClientSecret: "dfd8e954-53b1-4f00-9c09-0b181f44bb79", + AccessToken: "JZ2PZ4YNE1BB38VMRIQ7DNWXKZE6B1EBV310RNC53KQCVHXGB", + }, + } + + t.Run("Saving credentials to file", testSaveCredentials(credentialsFile, credentialsMap)) + + expectedCurrentAccount := "bobby@gts.red-crow.private" + t.Run("Updating the current account in the credentials file", testUpdateCurrentAccount(expectedCurrentAccount, credentialsFile)) + + t.Run("Loading the credentials from file", testLoadCredentialsConfigFromFile(credentialsFile, expectedCurrentAccount)) + + if err := os.Remove(credentialsFile); err != nil { + t.Fatalf( + "received an error after trying to clean up the test configuration at %q: %v", + credentialsFile, + err, + ) + } +} + +func testSaveCredentials(credentialsFile string, credentialsMap map[string]config.Credentials) func(t *testing.T) { + return func(t *testing.T) { + for username, credentials := range maps.All(credentialsMap) { + if _, err := config.SaveCredentials(credentialsFile, username, credentials); err != nil { + t.Fatalf( + "Unable to save the credentials for %s to %q: %v", + username, + credentialsFile, + err, + ) + } + } + + t.Log("All credentials saved to file.") + } +} + +func testUpdateCurrentAccount(account, credentialsFile string) func(t *testing.T) { + return func(t *testing.T) { + if err := config.UpdateCurrentAccount(account, credentialsFile); err != nil { + t.Fatalf("Unable to update the current account to %q: %v", account, err) + } + + t.Logf("Successfully updated the current account.") + } +} + +func testLoadCredentialsConfigFromFile(credentialsFile string, expectedCurrentAccount string) func(t *testing.T) { + return func(t *testing.T) { + credentials, err := config.NewCredentialsConfigFromFile(credentialsFile) + if err != nil { + t.Fatalf( + "Unable to load the credentials configuration from %q: %v", + credentialsFile, + err, + ) + } + + if credentials.CurrentAccount != expectedCurrentAccount { + t.Errorf( + "Unexpected current account found in the credentials configuration file: want %s, got %s", + expectedCurrentAccount, + credentials.CurrentAccount, + ) + } else { + t.Logf("Expected current account found in the credentials configuration file: got %s", credentials.CurrentAccount) + } + } +} diff --git a/internal/utilities/directories.go b/internal/utilities/directories.go index f3cd194..eddecfd 100644 --- a/internal/utilities/directories.go +++ b/internal/utilities/directories.go @@ -14,17 +14,17 @@ const ( cacheStatusesDir = "statuses" ) -func CalculateConfigDir(configDir string) string { +func CalculateConfigDir(configDir string) (string, error) { if configDir != "" { - return configDir + return configDir, nil } configRoot, err := os.UserConfigDir() if err != nil { - return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "config") + return "", fmt.Errorf("unable to get your default config diretory: %w", err) } - return filepath.Join(configRoot, internal.ApplicationName) + return filepath.Join(configRoot, internal.ApplicationName), nil } func CalculateMediaCacheDir(cacheRoot, instance string) (string, error) { diff --git a/internal/utilities/directories_test.go b/internal/utilities/directories_test.go index 53e04e7..b55cc22 100644 --- a/internal/utilities/directories_test.go +++ b/internal/utilities/directories_test.go @@ -7,34 +7,45 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) -func TestCalculateMediaCacheDir(t *testing.T) { - t.Parallel() +func TestDirectoryCalculations(t *testing.T) { + t.Log("Testing the directory calculations") projectDir, err := projectRoot() if err != nil { t.Fatalf("Unable to get the project root directory: %v", err) } - cacheRoot := filepath.Join(projectDir, "test", "cache") - instance := "http://gotosocial.yellow-desert.social" + t.Setenv("XDG_CACHE_HOME", "/home/enbas/.cache") + t.Setenv("XDG_CONFIG_HOME", "/home/enbas/.config") - got, err := utilities.CalculateMediaCacheDir(cacheRoot, instance) - if err != nil { - t.Fatalf("Unable to calculate the media cache directory: %v", err) - } + t.Run("Media Cache Directory Calculation", testCalculateMediaCacheDir(projectDir)) + t.Run("Media Cache Directory Calculation (with XDG_CACHE_HOME)", testCalculateMediaCacheDirWithXDG) + t.Run("Statuses Cache Directory Calculation", testCalculateStatusesCacheDir(projectDir)) + t.Run("Configuration Directory Calculation", testCalculateConfigDir(projectDir)) + t.Run("Configuration Directory Calculation (with XDG_CONFIG_HOME)", testCalculateConfigCacheDirWithXDG) +} - want := projectDir + "/test/cache/gotosocial.yellow-desert.social/media" +func testCalculateMediaCacheDir(projectDir string) func(t *testing.T) { + return func(t *testing.T) { + cacheRoot := filepath.Join(projectDir, "test", "cache") + instance := "http://gotosocial.yellow-desert.social" - if got != want { - t.Errorf("Unexpected media cache directory calculated: want %s, got %s", want, got) - } else { - t.Logf("Expected media cache directory calculated: got %s", got) + got, err := utilities.CalculateMediaCacheDir(cacheRoot, instance) + if err != nil { + t.Fatalf("Unable to calculate the media cache directory: %v", err) + } + + want := projectDir + "/test/cache/gotosocial.yellow-desert.social/media" + + if got != want { + t.Errorf("Unexpected media cache directory calculated: want %s, got %s", want, got) + } else { + t.Logf("Expected media cache directory calculated: got %s", got) + } } } -func TestCalculateMediaCacheDirWithXDG(t *testing.T) { - t.Setenv("XDG_CACHE_HOME", "/home/enbas/.cache") - +func testCalculateMediaCacheDirWithXDG(t *testing.T) { cacheRoot := "" instance := "https://gotosocial.yellow-desert.social" @@ -52,27 +63,58 @@ func TestCalculateMediaCacheDirWithXDG(t *testing.T) { } } -func TestCalculateStatusesCacheDir(t *testing.T) { - t.Parallel() +func testCalculateStatusesCacheDir(projectDir string) func(t *testing.T) { + return func(t *testing.T) { + cacheRoot := filepath.Join(projectDir, "test", "cache") + instance := "https://fedi.blue-mammoth.party" - projectDir, err := projectRoot() - if err != nil { - t.Fatalf("Unable to get the project root directory: %v", err) - } + got, err := utilities.CalculateStatusesCacheDir(cacheRoot, instance) + if err != nil { + t.Fatalf("Unable to calculate the statuses cache directory: %v", err) + } - cacheRoot := filepath.Join(projectDir, "test", "cache") - instance := "https://fedi.blue-mammoth.party" + want := projectDir + "/test/cache/fedi.blue-mammoth.party/statuses" - got, err := utilities.CalculateStatusesCacheDir(cacheRoot, instance) - if err != nil { - t.Fatalf("Unable to calculate the statuses cache directory: %v", err) - } - - want := projectDir + "/test/cache/fedi.blue-mammoth.party/statuses" - - if got != want { - t.Errorf("Unexpected statuses cache directory calculated: want %s, got %s", want, got) - } else { - t.Logf("Expected statuses cache directory calculated: got %s", got) + if got != want { + t.Errorf("Unexpected statuses cache directory calculated: want %s, got %s", want, got) + } else { + t.Logf("Expected statuses cache directory calculated: got %s", got) + } + } +} + +func testCalculateConfigDir(projectDir string) func(t *testing.T) { + return func(t *testing.T) { + configDir := filepath.Join(projectDir, "test", "config") + + got, err := utilities.CalculateConfigDir(configDir) + if err != nil { + t.Fatalf("Unable to calculate the config directory: %v", err) + } + + want := projectDir + "/test/config" + + if got != want { + t.Errorf("Unexpected config directory calculated: want %s, got %s", want, got) + } else { + t.Logf("Expected config directory calculated: got %s", got) + } + } +} + +func testCalculateConfigCacheDirWithXDG(t *testing.T) { + configDir := "" + + got, err := utilities.CalculateConfigDir(configDir) + if err != nil { + t.Fatalf("Unable to calculate the config directory: %v", err) + } + + want := "/home/enbas/.config/enbas" + + if got != want { + t.Errorf("Unexpected config directory calculated: want %s, got %s", want, got) + } else { + t.Logf("Expected config directory calculated: got %s", got) } } diff --git a/internal/utilities/utilities_test.go b/internal/utilities/utilities_test.go index b947314..6d16a25 100644 --- a/internal/utilities/utilities_test.go +++ b/internal/utilities/utilities_test.go @@ -7,8 +7,6 @@ import ( ) func TestGetFQDN(t *testing.T) { - t.Parallel() - cases := []struct { instance string want string