Skip to content

Commit

Permalink
tests: more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
martabal committed Oct 30, 2024
1 parent 196b7a9 commit f068a75
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
cover.html

# Dependency directories (remove the comment below to include it)
# vendor/
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@ lint:
test:
cd src && go test -v ./...

test-count:
cd src && go test ./... -v | grep -c RUN

test-coverage:
cd src && go test ./... -coverprofile=cover.out && go tool cover -html=cover.out && rm cover.out

update:
cd src && go get -u . && go mod tidy
29 changes: 17 additions & 12 deletions src/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import (
"qbit-exp/logger"
"strconv"
"strings"
"sync"
"time"

"github.com/joho/godotenv"
)

var loadEnvOnce sync.Once

var (
QBittorrent QBittorrentSettings
Exporter ExporterSettings
Expand Down Expand Up @@ -66,19 +69,21 @@ func SetVar(port int, enableTracker bool, loglevel string, baseUrl string, usern
}

func LoadEnv() {
var envfile bool
flag.BoolVar(&envfile, "e", false, "Use .env file")
flag.Parse()
_, err := os.Stat(".env")
UsingEnvFile = false
if !os.IsNotExist(err) && !envfile {
UsingEnvFile = true
err := godotenv.Load(".env")
if err != nil {
errormessage := "Error loading .env file:" + err.Error()
panic(errormessage)
loadEnvOnce.Do(func() {
var envfile bool
flag.BoolVar(&envfile, "e", false, "Use .env file")
flag.Parse()
_, err := os.Stat(".env")
UsingEnvFile = false
if !os.IsNotExist(err) && !envfile {
UsingEnvFile = true
err := godotenv.Load(".env")
if err != nil {
errormessage := "Error loading .env file:" + err.Error()
panic(errormessage)
}
}
}
})

loglevel := logger.SetLogLevel(getEnv(defaultLogLevel))
qbitUsername := getEnv(defaultUsername)
Expand Down
152 changes: 152 additions & 0 deletions src/app/default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package app

import (
"bytes"
"log/slog"
"os"
"strconv"
"strings"
"testing"

"qbit-exp/logger"
)

var buff = &bytes.Buffer{}

func init() {
logger.Log = &logger.Logger{Logger: slog.New(slog.NewTextHandler(buff, &slog.HandlerOptions{}))}
}

func TestGetEnvReturnsEnvValue(t *testing.T) {
envVar := "EXPORTER_PORT"
expectedValue := "9090"
os.Setenv(envVar, expectedValue)
defer os.Unsetenv(envVar)
value := getEnv(defaultPort)

if value != expectedValue {
t.Errorf("Expected %s, got %s", expectedValue, value)
}
}

func TestGetEnvReturnsDefaultWhenEnvNotSet(t *testing.T) {
envVar := "EXPORTER_PORT"
expectedValue := strconv.Itoa(DEFAULT_PORT)
os.Unsetenv(envVar)
value := getEnv(defaultPort)

if value != expectedValue {
t.Errorf("Expected default %s, got %s", expectedValue, value)
}
}

func TestGetEnvLogsWarningIfHelpMessagePresent(t *testing.T) {
envVar := "QBITTORRENT_USERNAME"
os.Unsetenv(envVar)
expectedLogMessage := defaultUsername.Help
getEnv(defaultUsername)

if !strings.Contains(buff.String(), expectedLogMessage) {
t.Errorf("Expected log message to contain '%s', got '%s'", expectedLogMessage, buff.String())
}
}

func TestGetEnvWithDifferentDefaults(t *testing.T) {
tests := []struct {
name string
env Env
expectedValue string
}{
{"DefaultLogLevel", defaultLogLevel, "INFO"},
{"DefaultPort", defaultPort, strconv.Itoa(DEFAULT_PORT)},
{"DefaultTimeout", defaultTimeout, strconv.Itoa(DEFAULT_TIMEOUT)},
{"DefaultUsername", defaultUsername, "admin"},
{"DefaultPassword", defaultPassword, "adminadmin"},
{"DefaultBaseUrl", defaultBaseUrl, "http://localhost:8080"},
{"DefaultDisableTracker", defaultDisableTracker, "true"},
{"DefaultHighCardinality", defaultHighCardinality, "false"},
{"DefaultLabelWithHash", defaultLabelWithHash, "false"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Unsetenv(tt.env.Key)
value := getEnv(tt.env)
if value != tt.expectedValue {
t.Errorf("Expected %s, got %s", tt.expectedValue, value)
}
})
}
}

func TestGetEnvReturnsBooleanValues(t *testing.T) {
tests := []struct {
envVar Env
setValue string
expectVal string
}{
{defaultDisableTracker, "false", "false"},
{defaultHighCardinality, "true", "true"},
{defaultLabelWithHash, "true", "true"},
}

for _, tt := range tests {
t.Run(tt.envVar.Key, func(t *testing.T) {
cleanup := setAndClearEnv(tt.envVar.Key, tt.setValue)
defer cleanup()

value := getEnv(tt.envVar)
if value != tt.expectVal {
t.Errorf("Expected %s, got %s", tt.expectVal, value)
}
})
}
}

func TestGetEnvHandlesEmptyEnvVarGracefully(t *testing.T) {
// Arrange
envVar := "QBITTORRENT_USERNAME"
os.Setenv(envVar, "")
defer os.Unsetenv(envVar)
expectedValue := defaultUsername.DefaultValue

// Act
value := getEnv(defaultUsername)

// Assert
if value != expectedValue {
t.Errorf("Expected %s, got %s", expectedValue, value)
}
}

func TestGetEnvLogsWarningsCorrectly(t *testing.T) {
tests := []struct {
name string
env Env
expectedLog string
}{
{"UsernameWarning", defaultUsername, "Qbittorrent username is not set. Using default username"},
{"PasswordWarning", defaultPassword, "Qbittorrent password is not set. Using default password"},
{"BaseUrlWarning", defaultBaseUrl, "Qbittorrent base_url is not set. Using default base_url"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

os.Unsetenv(tt.env.Key)

getEnv(tt.env)

if !strings.Contains(buff.String(), tt.expectedLog) {
t.Errorf("Expected log message to contain '%s', got '%s'", tt.expectedLog, buff.String())
}
})
}
}

func setAndClearEnv(key, value string) func() {
os.Setenv(key, value)
return func() {
os.Unsetenv(key)
}
}
8 changes: 5 additions & 3 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ func main() {

qbit.Auth()

http.HandleFunc("/metrics", metrics)
http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
metrics(w, req, qbit.AllRequests)
})
addr := ":" + strconv.Itoa(app.Exporter.Port)
if app.Exporter.Port != app.DEFAULT_PORT {
logger.Log.Info("Listening on port " + strconv.Itoa(app.Exporter.Port))
Expand All @@ -53,7 +55,7 @@ func main() {
}
}

func metrics(w http.ResponseWriter, req *http.Request) {
func metrics(w http.ResponseWriter, req *http.Request, allRequestsFunc func(*prometheus.Registry) error) {
ip, _, err := net.SplitHostPort(req.RemoteAddr)
if err == nil {
logger.Log.Trace("New request from " + ip)
Expand All @@ -62,7 +64,7 @@ func metrics(w http.ResponseWriter, req *http.Request) {
}

registry := prometheus.NewRegistry()
err = qbit.AllRequests(registry)
err = allRequestsFunc(registry)
if err != nil {
http.Error(w, "", http.StatusServiceUnavailable)
runtime.GC()
Expand Down
84 changes: 84 additions & 0 deletions src/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"bytes"
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"

"qbit-exp/logger"

"github.com/prometheus/client_golang/prometheus"
)

var buff = &bytes.Buffer{}

func init() {
logger.Log = &logger.Logger{Logger: slog.New(slog.NewTextHandler(buff, &slog.HandlerOptions{}))}
}

func TestMetricsFailureResponse(t *testing.T) {

req, err := http.NewRequest("GET", "/metrics", nil)
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()

metrics(rec, req, func(registry *prometheus.Registry) error {
return fmt.Errorf("mock error")
})

if status := rec.Code; status != http.StatusServiceUnavailable {
t.Errorf("expected status code 503, got %d", status)
}
}

func TestMetricsReturnMetric(t *testing.T) {

buff.Reset()
opts := &slog.HandlerOptions{
Level: slog.Level(logger.Trace),
}

logger.Log = &logger.Logger{Logger: slog.New(slog.NewTextHandler(buff, opts))}

req, err := http.NewRequest("GET", "/metrics", nil)
req.RemoteAddr = "127.0.0.1:80"
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()

metrics(rec, req, func(registry *prometheus.Registry) error {

qbittorrent_app_version := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "qbittorrent_app_version",
Help: "The current qBittorrent version",
ConstLabels: map[string]string{
"version": string("1.0"),
},
})
registry.MustRegister(qbittorrent_app_version)
qbittorrent_app_version.Set(1)
return nil
})

if status := rec.Code; status != http.StatusOK {
t.Errorf("expected status code 200, got %d", status)
}

expectedBody := "# HELP qbittorrent_app_version The current qBittorrent version\n# TYPE qbittorrent_app_version gauge\nqbittorrent_app_version{version=\"1.0\"} 1\n"

if rec.Body.String() != expectedBody {
t.Errorf("expected \n%s, got \n%s", expectedBody, rec.Body.String())
}

traceMessage := "New request from"
if !strings.Contains(buff.String(), traceMessage) {
t.Errorf("expected %s, got %s", traceMessage, buff.String())
}
}
4 changes: 4 additions & 0 deletions src/qbit/qbit.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ func errorHelper(body []byte, err error, unmarshErr string) {
logger.Log.Error(unmarshErr)
}

// returns:
// - body (content of the http response)
// - retry (if it should retry that query)
// - err (the error if there was one during the request)
func apiRequest(uri string, method string, queryParams *[]QueryParams) ([]byte, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), app.QBittorrent.Timeout)
defer cancel()
Expand Down
Loading

0 comments on commit f068a75

Please sign in to comment.