Implement loading and storing config to / from file

This commit is contained in:
Joshua Ramon Enslin 2025-02-25 04:00:30 +01:00
parent d4c83e27d2
commit 3cb49d005e
Signed by: jrenslin
GPG Key ID: 46016F84501B70AE
4 changed files with 386 additions and 13 deletions

View File

@ -51,5 +51,19 @@ func TestValidateInstanceWorks(t *testing.T) {
t.Fatalf("Output of ValidateInstanceLink() is not https://hessen.museum-digital.de where it should be") t.Fatalf("Output of ValidateInstanceLink() is not https://hessen.museum-digital.de where it should be")
} }
}
// Test that ValidateInstanceLink() works with a valid instance of md and cleans paths.
func TestValidateInstanceDoesCleanPathFromUrl(t *testing.T) {
result, err := ValidateInstanceLink("https://hessen.museum-digital.de/home")
if err != nil {
t.Fatalf("ValidateInstanceLink() returns an error where it should work")
}
if result != "https://hessen.museum-digital.de" {
t.Fatalf("Output of ValidateInstanceLink() is not https://hessen.museum-digital.de where it should be")
}
} }

View File

@ -25,8 +25,6 @@ func ValidateInstitutionId(institutionId int, instanceUrl string) (int, error) {
return 0, errors.New("The institution page does not respond with HTTP 200, the institution does not seem to exist") return 0, errors.New("The institution page does not respond with HTTP 200, the institution does not seem to exist")
} }
return institutionId, nil return institutionId, nil
} }

View File

@ -1,25 +1,212 @@
package configloader package configloader
import (
"os"
"encoding/json"
"path/filepath"
"io/ioutil"
)
type MDWebDavUploaderConfig struct { type MDWebDavUploaderConfig struct {
InstanceLink string `json:"instance"` InstanceLink string `json:"instance"`
Mail string `json:"mail"` Mail string `json:"mail"`
WebDavAuthToken string `json:"token"` WebDavAuthToken string `json:"token"`
InstitutionId int `json:"institution_id"` InstitutionId int `json:"institution_id"`
Parser string `json:"parser"` Parser string `json:"parser"`
MetadataFolder string `json:"metadata_folder"` MetadataFolder string `json:"metadata_folder"`
MediaFolder string `json:"media_folder"` MediaFolder string `json:"media_folder"`
PublishOnImport bool `json:"visible"` PublishOnImport bool `json:"visible"`
Settings map[string]string `json:"settings"`
} }
// Returns a uniform filepath for the configuration of this tool. // Returns the file path of the configuration file within the supplied
// directory. Moved to a dedicated function to provide a consistent
// filename.
func getConfigFileNameByDir(folder string) string {
return filepath.Join(folder, "config.json")
}
// Returns a uniform directory for the configuration of this tool.
// To be compatible across operating systems, this will be a JSON // To be compatible across operating systems, this will be a JSON
// file in the same directory as the current programm. // file in the same directory as the current programm.
func getConfigFilepath() string { func getConfigFilepath() (string, error) {
// Get the OS-dependent configuration directory.
generalConfigDir, dirErr := os.UserConfigDir()
if dirErr != nil {
return "", dirErr
}
// Select a subdirectory of that directory to store application-specific
// settings in. Attempt to create it and return the config filepath as
// a file in that directory.
configDir := filepath.Join(generalConfigDir, "museum-digital-uploader")
generateConfigDirErr := os.Mkdir(configDir, 0700)
// The config directory could be created, return path of the configuration
// file in it.
if generateConfigDirErr == nil {
return getConfigFileNameByDir(configDir), nil // Use this to create files
}
// There has been an error creating the config directory.
// This may be either that the directory already exists, which is alright
// (and even expected on most runs). Or it might be, that the path is
// already occupied with a file. Or something else. In those cases,
// the error should be returned.
// If the path is already occupied, inspect it more closely.
if os.IsExist(generateConfigDirErr) {
info, err := os.Stat(configDir)
if err != nil {
return "", err
}
// The directory already exists and is a directory. This is fine,
// so we return the config filename in the existing config directory.
if info.IsDir() {
return getConfigFileNameByDir(configDir), nil // Use this to create files
}
}
return "", generateConfigDirErr
}
// Wrapper around getConfigFilepath() allowing for an override for testing.
func getConfigFilepathOrOverride(overridePath string) (string, error) {
// Override path is not validated, as it should only be used
// in test settings
if overridePath != "" {
return overridePath, nil
}
configFilePath, err := getConfigFilepath()
if err != nil {
return "", err
}
return configFilePath, nil
}
// Validates each of the values of MDWebDavUploaderConfig.
func ValidateConfig(conf MDWebDavUploaderConfig) (MDWebDavUploaderConfig, error) {
// Validate and clean instance link
instanceLink, instanceErr := ValidateInstanceLink(conf.InstanceLink)
if instanceErr != nil {
return conf, instanceErr
}
conf.InstanceLink = instanceLink
// Validate and clean mail
mailLink, mailErr := ValidateMail(conf.Mail)
if mailErr != nil {
return conf, mailErr
}
conf.Mail = mailLink
// Validate and clean institution ID
institutionIdLink, institutionIdErr := ValidateInstitutionId(conf.InstitutionId, conf.InstanceLink)
if institutionIdErr != nil {
return conf, institutionIdErr
}
conf.InstitutionId = institutionIdLink
// Validate and clean parser
parserLink, parserErr := ValidateParser(conf.Parser)
if parserErr != nil {
return conf, parserErr
}
conf.Parser = parserLink
// Validate and clean metadata folder
metadataFolder, mFolderErr := ValidateUploadDir(conf.MetadataFolder)
if mFolderErr != nil {
return conf, mFolderErr
}
conf.MetadataFolder = metadataFolder
// Validate and clean media folder
mediaFolder, mediaFolderErr := ValidateUploadDir(conf.MediaFolder)
if mediaFolderErr != nil {
return conf, mediaFolderErr
}
conf.MediaFolder = mediaFolder
return conf, nil
} }
// Loads configuration from the configuration file (located using // Loads configuration from the configuration file (located using
// getConfigFilepath()). // getConfigFilepath()).
func LoadFromFile() MDWebDavUploaderConfig { // The function parameter overridePath is only useful for test settings.
// Outside those, set an empty string to use the default config path.
// Output parameters:
// 1: Config
// 2: Requirement for a re-run of the setup
// 3: Error
func LoadFromFile(overridePath string) (MDWebDavUploaderConfig, bool, error) {
// Get config file path
configFile, configFileErr := getConfigFilepathOrOverride(overridePath)
if configFileErr != nil {
return MDWebDavUploaderConfig{}, false, configFileErr
}
// Read the file
configFileBytes, ioErr := ioutil.ReadFile(configFile)
if ioErr != nil {
return MDWebDavUploaderConfig{}, true, ioErr
}
// Parse the file into a configuration struct
var data MDWebDavUploaderConfig
unmarshalErr := json.Unmarshal(configFileBytes, &data)
if unmarshalErr != nil {
return MDWebDavUploaderConfig{}, true, ioErr
}
// Validate data - the configuration file may have been altered externally
config, validationErr := ValidateConfig(data)
if validationErr != nil {
return MDWebDavUploaderConfig{}, true, validationErr
}
// Config could be parsed
return config, false, nil
}
// Stores a config from MDWebDavUploaderConfig to the config file.
// As with LoadFromFile(), it is possible to provide an overridePath for testing.
func StoreConfigToFile(conf MDWebDavUploaderConfig, overridePath string) error {
config, validationErr := ValidateConfig(conf)
if validationErr != nil {
return validationErr
}
configFilePath, configFileErr := getConfigFilepathOrOverride(overridePath)
if configFileErr != nil {
return configFileErr
}
configJson, encodeErr := json.Marshal(config)
if encodeErr != nil {
return encodeErr
}
writeErr := ioutil.WriteFile(configFilePath, configJson, 0644)
if writeErr != nil {
return writeErr
}
return nil
} }

View File

@ -0,0 +1,174 @@
package configloader
import (
"testing"
"os"
"path/filepath"
)
// Returns a generally valid config. Single values can then be
// replaced with the actually tested contents.
func getTestConfig() MDWebDavUploaderConfig {
input := MDWebDavUploaderConfig{}
input.InstanceLink = "https://hessen.museum-digital.de/home"
input.Mail = "test@example.com"
input.InstitutionId = 1;
input.Parser = "Lido"
tmpDir := os.TempDir()
testDir := filepath.Join(tmpDir, "/existing-dir-for-import")
mkdirErr := os.MkdirAll(testDir, os.ModePerm)
if mkdirErr != nil {
panic("Test failure: Failed to create test dir")
}
input.MetadataFolder = testDir
input.MediaFolder = testDir
return input
}
// Test that ValidateConfig() fails on non-URLs.
func TestValidateUploaderConfigFailsOnInvalidInstanceUrl(t *testing.T) {
input := getTestConfig()
input.InstanceLink = "abcmuseum-digital.org"
_, err := ValidateConfig(input)
if err == nil {
t.Fatalf("ValidateConfig() does not return an error on a non-URL (via ValidateInstanceLink)")
}
}
// Test that ValidateConfig cleans an instance URL as per ValidateInstanceLink().
func TestValidateUploaderConfigCleansInstanceViaValidateInstanceLink(t *testing.T) {
input := getTestConfig()
input.InstanceLink = "https://hessen.museum-digital.de/home"
returnVal, _ := ValidateConfig(input)
if returnVal.InstanceLink != "https://hessen.museum-digital.de" {
t.Fatalf("Failed to clean up input URL")
}
}
// Test that ValidateConfig() fails on invalid mail.
func TestValidateUploaderConfigFailsOnInvalidMail(t *testing.T) {
input := getTestConfig()
input.Mail = "test"
_, err := ValidateConfig(input)
if err == nil {
t.Fatalf("ValidateConfig() does not return an error on a mail address")
}
}
// Test that ValidateConfig() accepts a valid mail address.
func TestValidateUploaderConfigAcceptsValidMail(t *testing.T) {
input := getTestConfig()
input.Mail = "test@example.com"
returnVal, _ := ValidateConfig(input)
if returnVal.Mail != "test@example.com" {
t.Fatalf("Failed to accept valid mail")
}
}
// Test that ValidateConfig() fails on negative / invalid IDs.
func TestValidateUploaderConfigFailsOnInvalidInstitutionId(t *testing.T) {
input := getTestConfig()
input.InstitutionId = -1
_, err := ValidateConfig(input)
if err == nil {
t.Fatalf("ValidateConfig() does not return an error on an invalid institution ID")
}
}
// Test that ValidateConfig() accepts valid institution IDs.
func TestValidateUploaderConfigAcceptsValidInstitutionId(t *testing.T) {
input := getTestConfig()
input.InstitutionId = 1
returnVal, _ := ValidateConfig(input)
if returnVal.InstitutionId != 1 {
t.Fatalf("Failed to accept valid institution ID")
}
}
// Test that ValidateConfig() fails on negative / invalid IDs.
func TestValidateUploaderConfigFailsOnInvalidParser(t *testing.T) {
input := getTestConfig()
input.Parser = "nonexistentparser"
_, err := ValidateConfig(input)
if err == nil {
t.Fatalf("ValidateConfig() does not return an error on an invalid parser")
}
}
// Test that ValidateConfig() accepts valid ParserDs.
func TestValidateUploaderConfigAcceptsValidParser(t *testing.T) {
input := getTestConfig()
input.Parser = "ParserLido"
returnVal, _ := ValidateConfig(input)
if returnVal.Parser != "Lido" {
t.Fatalf("Failed to accept and clean valid parser")
}
}
// Test that ValidateConfig() fails on non-existent folder.
func TestValidateUploaderConfigFailsOnInvalidMetadataFolder(t *testing.T) {
input := getTestConfig()
input.MetadataFolder = "nonexistentfolder"
_, err := ValidateConfig(input)
if err == nil {
t.Fatalf("ValidateConfig() does not return an error on an invalid metadata folder")
}
}
// Test that saving and reading config works.
func TestWritingAndReadingConfigWorks(t *testing.T) {
input := getTestConfig()
input, _ = ValidateConfig(input)
writeErr := StoreConfigToFile(input, input.MetadataFolder + "/config.json")
if writeErr != nil {
t.Log("Error:")
t.Log(writeErr)
t.Fatalf("Failed to write config to override path")
}
loadedFromFile, setupRequired, err := LoadFromFile(input.MetadataFolder + "/config.json")
if setupRequired != false {
t.Fatalf("Expected no setup to be required, but return value indicated thus")
}
if err != nil {
t.Fatalf("Returned an error on trying to load config file")
}
// Golang can't compare structs with slices or maps
if input.InstanceLink != loadedFromFile.InstanceLink || input.Mail != loadedFromFile.Mail || input.WebDavAuthToken != loadedFromFile.WebDavAuthToken || input.InstitutionId != loadedFromFile.InstitutionId || input.Parser != loadedFromFile.Parser || input.MetadataFolder != loadedFromFile.MetadataFolder || input.MediaFolder != loadedFromFile.MediaFolder || input.PublishOnImport != loadedFromFile.PublishOnImport {
t.Log("Input")
t.Log(input)
t.Log("Loaded output")
t.Log(loadedFromFile)
t.Fatalf("Failed to write and then load the same config")
}
}