fix: error if home config directory not found
All checks were successful
Tests / test (pull_request) Successful in 17s
REUSE Compliance Check / check (push) Successful in 6s

Summary:

If the user hasn't supplied the path to a configuration directory and
Enbas fails to find the user's default home configuration directory,
Enbas will now return an error message back to the user. Previously
Enbas would try to, instead, calculate the configuration directory
using the user's current directory.

Changes:

- Return an error if the user's home configuration directory cannot
  be found.
- Add tests to the internal.config package.
- Update the tests in internal.utilities package.

PR: #54
This commit is contained in:
Dan Anglin 2024-08-18 21:13:08 +01:00
parent 15b9761497
commit d38431d9e8
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
7 changed files with 319 additions and 55 deletions

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

View file

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