Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,64 @@ Define your own config yml file and give absolute path to that file using `-conf

> Pro Tip: You can use the commands in Jenkins Job as well to create cron backups, restore, update or delete consul KVs.

## Unit Tests

The project includes a suite of unit tests to ensure code quality and correctness. Due to complexities encountered in the build and test environment during development, not all tests are currently passing, and precise code coverage metrics could not be obtained. However, the tests were designed with high coverage in mind.

The tests are organized as follows:

* **`config/consulDetails_test.go`**: Contains tests for parsing the Consul configuration YAML file (`consulConfig.yml`). It verifies that the YAML is correctly unmarshalled into the configuration structs and that the mapping of configurations to a usable map structure (`GetConsulConfigMap`) is accurate. Tests include scenarios for valid, invalid, and non-existent configuration files.

* **`src/helpers_test.go`**: Provides comprehensive tests for various utility functions used throughout the application. This includes:
* Parsing and cleaning key-value pair strings.
* Conversion of data structures to and from JSON (for backup/restore).
* File I/O operations for creating backup files and reading them.
* Validation of file paths.
* Logic for comparing and filtering lists of key-value pairs for synchronization.
* String manipulation utilities.

* **`src/consulwrapper_test.go`**: Focuses on testing the core logic for interacting with the Consul Key-Value store. These tests were designed to use mock implementations of the Consul API client. The intended coverage includes:
* Connecting to Consul.
* Performing basic CRUD (Create, Read, Update, Delete) operations on KV pairs (`putKV`, `getKV`, `deleteKV`, `listAllKV`).
* Higher-level operations like `AddKVToConsul`, `DeleteKVFromConsul`, `BackupConsulKV`, `RestoreConsulKV`, and `SyncConsulKVStore`, verifying their logic with different input parameters and simulated Consul responses.

* **`src/executor_test.go`**: Tests the command-line interface logic. This involves:
* Simulating various command-line arguments for each subcommand (`add`, `delete`, `backup`, `restore`, `sync`).
* Verifying that the correct functions from `consulwrapper.go` are called with the appropriate parameters.
* Testing argument validation, including missing arguments and help flag invocation. Mocking was intended for `os.Exit` calls to verify error exits.

* **`main_test.go`**: A simple test to ensure that the `main()` function in `main.go` correctly calls the main execution function (`src.ExecuteGoConsulKVFunc`).

### Running the Tests

To run the unit tests, navigate to the root directory of the project and execute the following command:

```bash
go test ./...
```

This command will discover and run all test files in the current directory and its subdirectories.

*(Note: Due to the aforementioned environment complexities, some tests may not pass or execute correctly at this time.)*

### Test Coverage

The goal for these unit tests was to achieve near 100% code coverage. However, due to the technical difficulties encountered in the build and test environment, which prevented the tests from running to completion reliably, accurate code coverage metrics could not be generated at the time of this writing.

Once the environment issues are resolved and the tests are passing, code coverage can be measured using Go's built-in tools:

1. Generate a coverage profile:
```bash
go test -coverprofile=coverage.out ./...
```
2. View the coverage report in HTML format:
```bash
go tool cover -html=coverage.out
```
Or, to get a summary by function in the terminal:
```bash
go tool cover -func=coverage.out
```

------
Primary dependency: https://godoc.org/github.com/hashicorp/consul/api
9 changes: 8 additions & 1 deletion config/consulDetails.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"fmt" // Added fmt import
"os"

"gopkg.in/yaml.v2"
Expand Down Expand Up @@ -50,13 +51,19 @@ func ParseConfigFile(configFilePath string) (*AllConsuls, error) {
if err := yd.Decode(&env); err != nil {
return nil, err
}
if env == nil || len(env.ConsulConfigs) == 0 {
return nil, fmt.Errorf("no consul configurations found or failed to parse from file: %s", filePath)
}

return env, nil
}

// GetConsulConfigMap : Get Map of AllConsuls
func GetConsulConfigMap(consuls *AllConsuls) map[string]ConsulDetail {
var consulsMap = make(map[string]ConsulDetail)
consulsMap := make(map[string]ConsulDetail)
if consuls == nil || consuls.ConsulConfigs == nil {
return consulsMap // Return empty map if input is nil or contains no configs
}
for _, c := range consuls.ConsulConfigs {
consulsMap[c.ConsulName] = c
}
Expand Down
147 changes: 147 additions & 0 deletions config/consulDetails_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package config

import (
"os"
// "path/filepath" // Removed unused import
"testing"

"github.com/stretchr/testify/assert"
)

// Helper function to create a temporary YAML file for testing
func createTempYAMLFile(t *testing.T, content string) string {
t.Helper()
tempFile, err := os.CreateTemp(t.TempDir(), "testConfig-*.yml")
assert.NoError(t, err)
_, err = tempFile.WriteString(content)
assert.NoError(t, err)
assert.NoError(t, tempFile.Close())
return tempFile.Name()
}

func TestParseConfigFile(t *testing.T) {
t.Run("ValidConfigFile", func(t *testing.T) {
validYAML := `
consul.details:
- name: "consul-dev"
dc: "dc1"
url: "http://localhost:8500"
token: "dev-token"
base.path: "services/"
- name: "consul-prod"
dc: "dc2"
url: "https://consul.prod:8500"
token: "prod-token"
base.path: "prod/services/"
`
configFile := createTempYAMLFile(t, validYAML)
defer os.Remove(configFile) // Clean up

expectedConfig := &AllConsuls{ // Changed ConsulConfiguration to AllConsuls
ConsulConfigs: []ConsulDetail{ // Changed ConsulFilters to ConsulConfigs
{
ConsulName: "consul-dev",
DataCentre: "dc1",
BaseURL: "http://localhost:8500", // Combined Scheme and Address into BaseURL
Token: "dev-token",
BasePath: "services/",
},
{
ConsulName: "consul-prod",
DataCentre: "dc2",
BaseURL: "https://consul.prod:8500", // Combined Scheme and Address into BaseURL
Token: "prod-token",
BasePath: "prod/services/",
},
},
}

config, err := ParseConfigFile(configFile)
assert.NoError(t, err)
assert.Equal(t, expectedConfig, config)
})

t.Run("NonExistentConfigFile", func(t *testing.T) {
config, err := ParseConfigFile("non_existent_file.yml")
assert.Error(t, err)
assert.Nil(t, config)
})

t.Run("InvalidConfigFile", func(t *testing.T) {
// Updated to reflect actual field names and correct YAML structure
invalidYAML := `
consul.details:
- name: "consul-dev"
dc: "dc1"
# url: "localhost:8500" # This line is intentionally mis-indented for failure
url: "mis-indented-url"
`
configFile := createTempYAMLFile(t, invalidYAML)
defer os.Remove(configFile) // Clean up

config, err := ParseConfigFile(configFile)
assert.Error(t, err)
assert.Nil(t, config)
})
}

func TestGetConsulConfigMap(t *testing.T) {
sampleConfig := &AllConsuls{ // Changed ConsulConfiguration to AllConsuls
ConsulConfigs: []ConsulDetail{ // Changed ConsulFilters to ConsulConfigs
{
ConsulName: "consul-dev",
DataCentre: "dc1",
BaseURL: "http://localhost:8500", // Combined Scheme and Address
Token: "dev-token",
BasePath: "services/",
},
{
ConsulName: "consul-prod",
DataCentre: "dc2",
BaseURL: "https://consul.prod:8500", // Combined Scheme and Address
Token: "prod-token",
BasePath: "prod/services/",
},
},
}

configMap := GetConsulConfigMap(sampleConfig)

// Assert that the map has the correct keys
assert.Contains(t, configMap, "consul-dev")
assert.Contains(t, configMap, "consul-prod")

// Assert values for "consul-dev"
devDetail, ok := configMap["consul-dev"]
assert.True(t, ok)
assert.Equal(t, "consul-dev", devDetail.ConsulName)
// assert.Equal(t, "dev", devDetail.Env) // Env removed
assert.Equal(t, "dc1", devDetail.DataCentre)
// assert.Equal(t, "localhost:8500", devDetail.Address) // Address removed
assert.Equal(t, "dev-token", devDetail.Token)
// assert.Equal(t, "http", devDetail.Scheme) // Scheme removed
assert.Equal(t, "services/", devDetail.BasePath)
assert.Equal(t, "http://localhost:8500", devDetail.BaseURL) // BaseURL is now directly from config


// Assert values for "consul-prod"
prodDetail, ok := configMap["consul-prod"]
assert.True(t, ok)
assert.Equal(t, "consul-prod", prodDetail.ConsulName)
// assert.Equal(t, "prod", prodDetail.Env) // Env removed
assert.Equal(t, "dc2", prodDetail.DataCentre)
// assert.Equal(t, "consul.prod:8500", prodDetail.Address) // Address removed
assert.Equal(t, "prod-token", prodDetail.Token)
// assert.Equal(t, "https", prodDetail.Scheme) // Scheme removed
assert.Equal(t, "prod/services/", prodDetail.BasePath)
assert.Equal(t, "https://consul.prod:8500", prodDetail.BaseURL) // BaseURL is now directly from config

// Test with nil configuration
nilMap := GetConsulConfigMap(nil)
assert.Empty(t, nilMap, "Map should be empty for nil configuration")

// Test with empty ConsulConfigs
emptyFiltersConfig := &AllConsuls{ConsulConfigs: []ConsulDetail{}} // Changed to AllConsuls and ConsulConfigs
emptyFiltersMap := GetConsulConfigMap(emptyFiltersConfig)
assert.Empty(t, emptyFiltersMap, "Map should be empty for empty ConsulConfigs")
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
module github.com/iAviPro/goConsulKV
module github.com/ConsulScale

go 1.23.8

toolchain go1.23.9

require (
github.com/hashicorp/consul/api v1.32.1
github.com/iAviPro/goConsulKV v1.0.1
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/armon/go-metrics v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand All @@ -24,6 +27,9 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/stretchr/objx v0.5.2 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading