Skip to content

Commit

Permalink
test: update tests for config.go and health.go (#5)
Browse files Browse the repository at this point in the history
* feat: use `jonboulle/clockwork` to mock sleep

* test: config env loading

* fix: default rendering

* feat: update workflow to run on PRs
  • Loading branch information
scmmishra authored Dec 15, 2023
1 parent 7ba8929 commit 46d936f
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 38 deletions.
52 changes: 26 additions & 26 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@ name: Build & Test

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21.4'

- name: Build
run: go build -v ./...

- name: Test & Generate Coverfile
run: go test -coverprofile=cover.out -v ./...

- name: Report test coverage results to DeepSource
run: |
# Install deepsource CLI
curl https://deepsource.io/cli | sh
# From the root directory, run the report coverage command
./bin/deepsource report --analyzer test-coverage --key go --value-file ./cover.out
env:
DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }}

- uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21.4"

- name: Build
run: go build -v ./...

- name: Test & Generate Coverfile
run: go test -coverprofile=cover.out -v ./...

- name: Report test coverage results to DeepSource
run: |
# Install deepsource CLI
curl https://deepsource.io/cli | sh
# From the root directory, run the report coverage command
./bin/deepsource report --analyzer test-coverage --key go --value-file ./cover.out
env:
DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
Expand Down
10 changes: 3 additions & 7 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type DeploymentConfig struct {
HealthCheck HealthCheck `yaml:"health_check"`
}

func replaceEnvVariables(input string) (string, error) {
func replaceEnvVariables(input string) string {
re := regexp.MustCompile(`\{env\.([a-zA-Z_][a-zA-Z0-9_]*)\}`)

return re.ReplaceAllStringFunc(input, func(match string) string {
Expand All @@ -72,7 +72,7 @@ func replaceEnvVariables(input string) (string, error) {
}

return match
}), nil
})
}

func LoadConfig(path string) (DeploymentConfig, error) {
Expand Down Expand Up @@ -112,11 +112,7 @@ func LoadConfig(path string) (DeploymentConfig, error) {
}

for i, rule := range c.Caddy.Rules {
newTlsValue, err := replaceEnvVariables(rule.Tls)
if err != nil {
return c, err
}

newTlsValue := replaceEnvVariables(rule.Tls)
c.Caddy.Rules[i].Tls = newTlsValue
}

Expand Down
53 changes: 53 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,56 @@ app:
assert.Equal(t, "testuser", config.App.Registry.Username)
assert.Equal(t, "testpassword", config.App.Registry.Password)
}

func TestLoadConfig_ReplaceEnvVariablesInCaddyRules(t *testing.T) {
// Set up a temporary YAML file with configuration data that includes environment variables
tempFile, err := os.CreateTemp("", "*.yaml")
require.NoError(t, err)
defer os.Remove(tempFile.Name())
_, err = tempFile.WriteString(`
caddy:
admin_api: "http://localhost:2019"
rules:
- match: "localhost"
tls: "{env.TEST_ENV_VAR}"
`)
require.NoError(t, err)
err = tempFile.Close()
require.NoError(t, err)

// Set the environment variable that we're using in the config file
os.Setenv("TEST_ENV_VAR", "test value")
defer os.Unsetenv("TEST_ENV_VAR") // clean up after the test

// Load the configuration from the temporary file
config, err := LoadConfig(tempFile.Name())
require.NoError(t, err)

// Assert that the environment variable in the Caddy rule was replaced correctly
assert.Equal(t, "test value", config.Caddy.Rules[0].Tls)
}

func TestLoadConfig_MissingEnvVariablesInCaddyRules(t *testing.T) {
// Set up a temporary YAML file with configuration data that includes environment variables
tempFile, err := os.CreateTemp("", "*.yaml")
require.NoError(t, err)
defer os.Remove(tempFile.Name())
_, err = tempFile.WriteString(`
caddy:
admin_api: "http://localhost:2019"
rules:
- match: "localhost"
tls: "{env.TEST_ENV_VAR}"
`)
require.NoError(t, err)
err = tempFile.Close()
require.NoError(t, err)

// Set the environment variable that we're using in the config file
// Load the configuration from the temporary file
config, err := LoadConfig(tempFile.Name())
require.NoError(t, err)

// Assert that the environment variable in the Caddy rule was replaced correctly
assert.Equal(t, "{env.TEST_ENV_VAR}", config.Caddy.Rules[0].Tls)
}
9 changes: 7 additions & 2 deletions internal/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import (
"net/http"
"time"

"github.com/jonboulle/clockwork"
"github.com/scmmishra/slick-deploy/internal/config"
)

func CheckHealth(host string, cfg *config.HealthCheck) error {
return CheckHealthWithClock(host, cfg, clockwork.NewRealClock())
}

func CheckHealthWithClock(host string, cfg *config.HealthCheck, clock clockwork.Clock) error {
if cfg.Endpoint == "" || host == "" {
return nil
}
Expand All @@ -31,7 +36,7 @@ func CheckHealth(host string, cfg *config.HealthCheck) error {
for i := 0; i < maxRetries; i++ {
resp, err := client.Get(endpoint)
if err != nil {
time.Sleep(delay)
clock.Sleep(delay)
continue
}

Expand All @@ -42,7 +47,7 @@ func CheckHealth(host string, cfg *config.HealthCheck) error {
}

fmt.Println("Retrying...")
time.Sleep(delay)
clock.Sleep(delay)
}

return fmt.Errorf("unable to reach endpoint %s after %d attempts", endpoint, maxRetries)
Expand Down
78 changes: 75 additions & 3 deletions internal/health/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/jonboulle/clockwork"
"github.com/scmmishra/slick-deploy/internal/config"
"github.com/stretchr/testify/assert"
)

type sleepCounterClock struct {
clockwork.FakeClock
sleepCount int
}

func (s *sleepCounterClock) Sleep(d time.Duration) {
s.sleepCount++
s.FakeClock.Sleep(d)
}

// setupTestServer helps in creating a test HTTP server.
func setupTestServer(handler http.HandlerFunc) (string, func()) {
ts := httptest.NewServer(handler)
Expand Down Expand Up @@ -47,8 +59,10 @@ func TestCheckHealth_Success(t *testing.T) {
defer teardown()

err := CheckHealth(serverURL, &config.HealthCheck{
Endpoint: "/health",
TimeoutSeconds: 5,
Endpoint: "/",
TimeoutSeconds: 5,
IntervalSeconds: 2,
MaxRetries: 3,
})

assert.NoError(t, err, "Expected no error for healthy response")
Expand Down Expand Up @@ -89,4 +103,62 @@ func TestCheckHealth_NetworkError(t *testing.T) {
assert.Error(t, err, "Expected a network error")
}

// Additional test cases can be added here
func TestCheckHealth_SleepWithError(t *testing.T) {
t.Parallel()

clk := &sleepCounterClock{FakeClock: clockwork.NewFakeClock()}

serverURL, teardown := setupTestServer(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError) // Return 500 Internal Server Error
})

defer teardown()

go func() {
for {
clk.Advance(1 * time.Second)
}
}()

err := CheckHealthWithClock(serverURL, &config.HealthCheck{
Endpoint: "/health",
TimeoutSeconds: 5,
IntervalSeconds: 2,
MaxRetries: 3,
}, clk)

assert.Error(t, err, "Expected an error for server error response")
assert.Greater(t, clk.sleepCount, 0, "Expected Sleep to be called at least once")
}

func TestCheckHealthWithClock_StalledServer(t *testing.T) {
t.Parallel()

// Create a test server that delays its response
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second) // Delay response
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

clk := clockwork.NewFakeClock()

// Run CheckHealthWithClock in a separate goroutine, as it will block due to the server delay
go func() {
err := CheckHealthWithClock(server.URL, &config.HealthCheck{
Endpoint: "/health",
TimeoutSeconds: 2, // This is less than the server delay
IntervalSeconds: 2,
MaxRetries: 1,
}, clk)

// We expect an error, as the server response will be delayed beyond the timeout
assert.Error(t, err, "Expected an error due to server response delay")
}()

// Advance the clock in a loop to simulate time passing
for i := 0; i < 5; i++ {
clk.Advance(1 * time.Second)
time.Sleep(1 * time.Second) // This is needed to allow the CheckHealthWithClock goroutine to progress
}
}

0 comments on commit 46d936f

Please sign in to comment.