fix: return an error if the user's home configuration directory cannot be found #54

Manually merged
dananglin merged 1 commit from update-config-path-code into main 2024-08-18 21:29:24 +01:00
7 changed files with 319 additions and 55 deletions
Showing only changes of commit d38431d9e8 - Show all commits

View file

@ -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: "",

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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) {

View file

@ -7,14 +7,26 @@ 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)
}
t.Setenv("XDG_CACHE_HOME", "/home/enbas/.cache")
t.Setenv("XDG_CONFIG_HOME", "/home/enbas/.config")
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)
}
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"
@ -30,11 +42,10 @@ func TestCalculateMediaCacheDir(t *testing.T) {
} 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,14 +63,8 @@ func TestCalculateMediaCacheDirWithXDG(t *testing.T) {
}
}
func TestCalculateStatusesCacheDir(t *testing.T) {
t.Parallel()
projectDir, err := projectRoot()
if err != nil {
t.Fatalf("Unable to get the project root directory: %v", err)
}
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"
@ -75,4 +80,41 @@ func TestCalculateStatusesCacheDir(t *testing.T) {
} 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)
}
}

View file

@ -7,8 +7,6 @@ import (
)
func TestGetFQDN(t *testing.T) {
t.Parallel()
cases := []struct {
instance string
want string