fix: return an error if the user's home configuration directory cannot be found #54
7 changed files with 319 additions and 55 deletions
|
@ -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: "",
|
||||
|
|
83
internal/config/config_test.go
Normal file
83
internal/config/config_test.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
107
internal/config/credentials_test.go
Normal file
107
internal/config/credentials_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import (
|
|||
)
|
||||
|
||||
func TestGetFQDN(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
instance string
|
||||
want string
|
||||
|
|
Loading…
Reference in a new issue