Skip to content

Commit

Permalink
test: add tests (#57)
Browse files Browse the repository at this point in the history
* ci: remove go test stage in Dockerfile

* feat: add tests

* fix: decrease required docker api version to v1.40

* fix: stream test logs

* ci: remove permissions

* ci: run build workflow only on tags

* fix: add app setting to specify docker api version

* fix: adjust docker api version setting

* fix: adjust docker api version setting

* fix: adjust docker api version setting

* fix: add config validator

* test: adjust test command

* test: add coverage generate

* test: adjust docker compose tests

* test: adjust docker compose tests

* test: add git tests

* test: adjust Makefile coverage command

* test: add app config tests

* docs: update wiki

* test: add logger tests

* docs: update wiki

* refactor: go mod tidy

* refactor: move verifyProviderSecret function to security.go

* refactor: move verifyProviderSecret function to security.go

* test: add parse tests

* test: remove parse tests
  • Loading branch information
kimdre authored Aug 4, 2024
1 parent c046ce0 commit 942322e
Show file tree
Hide file tree
Showing 20 changed files with 677 additions and 50 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ on:
push:
tags:
- 'v*'
pull_request:
branches:
- main

jobs:
build:
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tests

on:
workflow_dispatch:
push:
paths-ignore:
- 'README.md'
- 'docs/**'

jobs:
test:
name: Run Acceptance Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version-file: 'go.mod'
cache: true
- run: go mod download
- run: go test -v -cover ./... -timeout 5m
timeout-minutes: 5
env:
WEBHOOK_SECRET: test_Secret1
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ go.work.sum

*.env
.env

cover.out
cover.html
5 changes: 0 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -ldflags="-s -w" -o / ./...

# Run the tests in the container
FROM build-stage AS run-test-stage

RUN go test -v ./...

FROM gcr.io/distroless/base-debian12@sha256:1aae189e3baecbb4044c648d356ddb75025b2ba8d14cdc9c2a19ba784c90bfb9 AS build-release-stage

WORKDIR /
Expand Down
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
BINARY_DIR=bin
BINARY_NAME=docker-compose-webhook
.PHONY: test build run lint fmt update update-all submodule-commit
.PHONY: test build run lint fmt update update-all submodule-commit generate-coverage

test:
@echo "Running tests..."
@go test -v ./... -timeout 30m
@WEBHOOK_SECRET="test_Secret1" go test -v -cover -p 1 ./... -timeout 5m

generate-coverage:
@echo "Running tests with coverage..."
@WEBHOOK_SECRET="test_Secret1" go test -v -coverprofile cover.out ./...
@go tool cover -html cover.out -o cover.html

build:
mkdir -p $(BINARY_DIR)
Expand Down
2 changes: 1 addition & 1 deletion cmd/docker-compose-webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func main() {
log.Info("starting application", slog.String("log_level", c.LogLevel))

// Test/verify the connection to the docker socket
err = docker.VerifySocketConnection()
err = docker.VerifySocketConnection(c.DockerAPIVersion)
if err != nil {
log.Critical(docker.ErrDockerSocketConnectionFailed.Error(), logger.ErrAttr(err))
}
Expand Down
2 changes: 1 addition & 1 deletion docs
Submodule docs updated from 159384 to 681b02
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/docker/docker v27.1.1+incompatible
github.com/go-git/go-git/v5 v5.12.0
github.com/google/uuid v1.6.0
gopkg.in/validator.v2 v2.0.1
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
34 changes: 28 additions & 6 deletions internal/config/app_config.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
package config

import (
"errors"
"github.com/caarlos0/env/v11"
"gopkg.in/validator.v2"
"strings"
)

// AppConfig is used to configure this application
type AppConfig struct {
LogLevel string `env:"LOG_LEVEL,required" envDefault:"info"` // LogLevel is the log level for the application
HttpPort uint16 `env:"HTTP_PORT,required" envDefault:"80"` // HttpPort is the port the HTTP server will listen on
WebhookSecret string `env:"WEBHOOK_SECRET,required"` // WebhookSecret is the secret used to authenticate the webhook
GitAccessToken string `env:"GIT_ACCESS_TOKEN"` // GitAccessToken is the access token used to authenticate with the Git server (e.g. GitHub) for private repositories
AuthType string `env:"AUTH_TYPE" envDefault:"oauth2"` // AuthType is the type of authentication to use when cloning repositories
SkipTLSVerification bool `env:"SKIP_TLS_VERIFICATION" envDefault:"false"` // SkipTLSVerification skips the TLS verification when cloning repositories.
LogLevel string `env:"LOG_LEVEL,required" envDefault:"info"` // LogLevel is the log level for the application
HttpPort uint16 `env:"HTTP_PORT,required" envDefault:"80" validate:"min=1,max=65535"` // HttpPort is the port the HTTP server will listen on
WebhookSecret string `env:"WEBHOOK_SECRET,required"` // WebhookSecret is the secret used to authenticate the webhook
GitAccessToken string `env:"GIT_ACCESS_TOKEN"` // GitAccessToken is the access token used to authenticate with the Git server (e.g. GitHub) for private repositories
AuthType string `env:"AUTH_TYPE" envDefault:"oauth2"` // AuthType is the type of authentication to use when cloning repositories
SkipTLSVerification bool `env:"SKIP_TLS_VERIFICATION" envDefault:"false"` // SkipTLSVerification skips the TLS verification when cloning repositories.
DockerAPIVersion string `env:"DOCKER_API_VERSION" envDefault:"v1.40" validate:"regexp=^v[0-9]+\\.[0-9]+$"` // DockerAPIVersion is the version of the Docker API to use
}

var (
ErrInvalidLogLevel = validator.TextErr{Err: errors.New("invalid log level, must be one of debug, info, warn, error")}
ErrInvalidDockerAPIVersion = validator.TextErr{Err: errors.New("invalid Docker API version format, must be e.g. v1.40")}
)

// GetAppConfig returns the configuration
func GetAppConfig() (*AppConfig, error) {
cfg := AppConfig{}
if err := env.Parse(&cfg); err != nil {
return nil, err
}

logLvl := strings.ToLower(cfg.LogLevel)
if logLvl != "debug" && logLvl != "info" && logLvl != "warn" && logLvl != "error" {
return nil, ErrInvalidLogLevel
}

if err := validator.Validate(cfg); err != nil {
if strings.Contains(err.Error(), "DockerAPIVersion") {
return nil, ErrInvalidDockerAPIVersion
}

return nil, err
}

return &cfg, nil
}
72 changes: 72 additions & 0 deletions internal/config/app_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package config

import (
"errors"
"os"
"testing"
)

func TestGetAppConfig(t *testing.T) {
// Set up test cases
tests := []struct {
name string
envVars map[string]string
expectedErr error
}{
{
name: "valid config",
envVars: map[string]string{
"LOG_LEVEL": "info",
"HTTP_PORT": "8080",
"WEBHOOK_SECRET": "secret",
"AUTH_TYPE": "oauth2",
"DOCKER_API_VERSION": "v1.40",
"GIT_ACCESS_TOKEN": "token",
"SKIP_TLS_VERIFICATION": "false",
},
expectedErr: nil,
},
{
name: "invalid log level",
envVars: map[string]string{
"LOG_LEVEL": "invalid",
"WEBHOOK_SECRET": "secret",
},
expectedErr: ErrInvalidLogLevel,
},
{
name: "invalid API version",
envVars: map[string]string{
"LOG_LEVEL": "info",
"WEBHOOK_SECRET": "secret",
"DOCKER_API_VERSION": "1.40",
},
expectedErr: ErrInvalidDockerAPIVersion,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the environment
for k, v := range tt.envVars {
if err := os.Setenv(k, v); err != nil {
t.Fatalf("failed to set environment variable: %v", err)
}
}

// Run the test
_, err := GetAppConfig()
if !errors.Is(err, tt.expectedErr) {
t.Errorf("expected error to be '%v', got '%v'", tt.expectedErr, err)
}
if err == nil {
// Clean up the environment
for k := range tt.envVars {
if err := os.Unsetenv(k); err != nil {
t.Fatalf("failed to unset environment variable: %v", err)
}
}
}
})
}
}
11 changes: 10 additions & 1 deletion internal/config/deploy_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"errors"
"fmt"
"gopkg.in/validator.v2"
"os"
"path"

Expand Down Expand Up @@ -74,10 +75,18 @@ func GetDeployConfig(repoDir, name string) (*DeployConfig, error) {
for _, configFile := range DefaultDeploymentConfigFileNames {
config, err := getDeployConfigFile(repoDir, files, configFile)
if err != nil {
continue
if errors.Is(err, ErrConfigFileNotFound) {
continue
} else {
return nil, err
}
}

if config != nil {
if err := validator.Validate(config); err != nil {
return nil, err
}

return config, nil
}
}
Expand Down
112 changes: 112 additions & 0 deletions internal/config/deploy_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package config

import (
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
)

var projectName = "test"

func createTestFile(fileName string, content string) error {
err := os.WriteFile(fileName, []byte(content), 0o644)
if err != nil {
return err
}

return nil
}

func createTmpDir(t *testing.T) string {
dirName, err := os.MkdirTemp(os.TempDir(), "test-*")
if err != nil {
t.Fatal(err)
}

return dirName
}

func TestGetDeployConfig(t *testing.T) {
fileName := ".compose-deploy.yaml"
reference := "refs/heads/test"
workingDirectory := "/test"
composeFiles := []string{"test.compose.yaml"}

deployConfig := fmt.Sprintf(`name: %s
reference: %s
working_dir: %s
compose_files:
- %s
`, projectName, reference, workingDirectory, composeFiles[0])

dirName := createTmpDir(t)
defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
t.Fatal(err)
}
}(dirName)

filePath := filepath.Join(dirName, fileName)

err := createTestFile(filePath, deployConfig)
if err != nil {
t.Fatal(err)
}

config, err := GetDeployConfig(dirName, projectName)
if err != nil {
t.Fatal(err)
}

if config.Name != projectName {
t.Errorf("expected name to be %v, got %s", projectName, config.Name)
}

if config.Reference != reference {
t.Errorf("expected reference to be %v, got %s", reference, config.Reference)
}

if config.WorkingDirectory != workingDirectory {
t.Errorf("expected working directory to be '%v', got '%s'", workingDirectory, config.WorkingDirectory)
}

if !reflect.DeepEqual(config.ComposeFiles, composeFiles) {
t.Errorf("expected compose files to be %v, got %v", composeFiles, config.ComposeFiles)
}
}

func TestGetDeployConfig_Default(t *testing.T) {
defaultConfig := DefaultDeployConfig(projectName)

dirName := createTmpDir(t)
defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
t.Fatal(err)
}
}(dirName)

config, err := GetDeployConfig(dirName, projectName)
if err != nil {
t.Fatal(err)
}

if config.Name != projectName {
t.Errorf("expected name to be %v, got %s", projectName, config.Name)
}

if config.Reference != defaultConfig.Reference {
t.Errorf("expected reference to be %s, got %s", defaultConfig.Reference, config.Reference)
}

if config.WorkingDirectory != defaultConfig.WorkingDirectory {
t.Errorf("expected working directory to be %s, got %s", defaultConfig.WorkingDirectory, config.WorkingDirectory)
}

if !reflect.DeepEqual(config.ComposeFiles, defaultConfig.ComposeFiles) {
t.Errorf("expected compose files to be %v, got %v", defaultConfig.ComposeFiles, config.ComposeFiles)
}
}
Loading

0 comments on commit 942322e

Please sign in to comment.