mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
621 lines
14 KiB
Go
621 lines
14 KiB
Go
// SPDX-FileCopyrightText: 2022 Free Mobile
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package cmd_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"akvorado/common/helpers/yaml"
|
|
|
|
"akvorado/cmd"
|
|
"akvorado/common/helpers"
|
|
)
|
|
|
|
type dummyConfiguration struct {
|
|
Module1 dummyModule1Configuration
|
|
Module2 dummyModule2Configuration
|
|
}
|
|
type dummyModule1Configuration struct {
|
|
Listen string `validate:"listen"`
|
|
Topic string `validate:"gte=3"`
|
|
Workers int `validate:"gte=1"`
|
|
}
|
|
type dummyModule2Configuration struct {
|
|
Details dummyModule2DetailsConfiguration
|
|
Elements []dummyModule2ElementsConfiguration
|
|
MoreDetails `mapstructure:",squash" yaml:",inline"`
|
|
}
|
|
type MoreDetails struct {
|
|
Stuff string
|
|
}
|
|
type dummyModule2ElementsConfiguration struct {
|
|
Name string
|
|
Gauge int
|
|
}
|
|
type dummyModule2DetailsConfiguration struct {
|
|
Workers int
|
|
IntervalValue time.Duration
|
|
}
|
|
|
|
func (c *dummyConfiguration) Reset() {
|
|
*c = dummyConfiguration{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "nothingness",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "hello",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 1,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestValidation(t *testing.T) {
|
|
config := `---
|
|
module1:
|
|
topic: fl
|
|
workers: -5
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: configFile,
|
|
}
|
|
|
|
parsed := dummyConfiguration{}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err == nil {
|
|
t.Fatal("Parse() didn't error")
|
|
} else if diff := helpers.Diff(err.Error(), `invalid configuration:
|
|
Key: 'dummyConfiguration.Module1.Topic' Error:Field validation for 'Topic' failed on the 'gte' tag
|
|
Key: 'dummyConfiguration.Module1.Workers' Error:Field validation for 'Workers' failed on the 'gte' tag`); diff != "" {
|
|
t.Fatalf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestDump(t *testing.T) {
|
|
// Configuration file
|
|
config := `---
|
|
module1:
|
|
topic: flows
|
|
module2:
|
|
details:
|
|
workers: 5
|
|
interval-value: 20m
|
|
stuff: bye
|
|
elements:
|
|
- name: first
|
|
gauge: 67
|
|
- name: second
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: configFile,
|
|
Dump: true,
|
|
}
|
|
|
|
parsed := dummyConfiguration{}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
// Expected configuration
|
|
expected := dummyConfiguration{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "bye",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 5,
|
|
IntervalValue: 20 * time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{"first", 67},
|
|
{"second", 0},
|
|
},
|
|
},
|
|
}
|
|
if diff := helpers.Diff(parsed, expected); diff != "" {
|
|
t.Errorf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
|
|
var gotRaw map[string]gin.H
|
|
if err := yaml.Unmarshal(out.Bytes(), &gotRaw); err != nil {
|
|
t.Fatalf("Unmarshal() error:\n%+v", err)
|
|
}
|
|
expectedRaw := map[string]gin.H{
|
|
"module1": {
|
|
"listen": "127.0.0.1:8080",
|
|
"topic": "flows",
|
|
"workers": 100,
|
|
},
|
|
"module2": {
|
|
"stuff": "bye",
|
|
"details": gin.H{
|
|
"workers": 5,
|
|
"intervalvalue": "20m0s",
|
|
},
|
|
"elements": []any{
|
|
gin.H{
|
|
"name": "first",
|
|
"gauge": 67,
|
|
},
|
|
gin.H{
|
|
"name": "second",
|
|
"gauge": 0,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if diff := helpers.Diff(gotRaw, expectedRaw); diff != "" {
|
|
t.Errorf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestEnvOverride(t *testing.T) {
|
|
// Configuration file
|
|
config := `---
|
|
module1:
|
|
topic: flows
|
|
module2:
|
|
details:
|
|
workers: 5
|
|
interval-value: 20m
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
// Environment
|
|
clean := func() {
|
|
for _, env := range os.Environ() {
|
|
if strings.HasPrefix(env, "AKVORADO_CFG_DUMMY_") {
|
|
os.Unsetenv(strings.Split(env, "=")[0])
|
|
}
|
|
}
|
|
}
|
|
clean()
|
|
defer clean()
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE1_LISTEN", "127.0.0.1:9000")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE1_TOPIC", "something")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE2_DETAILS_INTERVALVALUE", "10m")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE2_STUFF", "bye")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE2_ELEMENTS_0_NAME", "something")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE2_ELEMENTS_0_GAUGE", "18")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE2_ELEMENTS_1_NAME", "something else")
|
|
os.Setenv("AKVORADO_CFG_DUMMY_MODULE2_ELEMENTS_1_GAUGE", "7")
|
|
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: configFile,
|
|
Dump: true,
|
|
}
|
|
|
|
parsed := dummyConfiguration{}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
// Expected configuration
|
|
expected := dummyConfiguration{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:9000",
|
|
Topic: "something",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "bye",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 5,
|
|
IntervalValue: 10 * time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{"something", 18},
|
|
{"something else", 7},
|
|
},
|
|
},
|
|
}
|
|
if diff := helpers.Diff(parsed, expected); diff != "" {
|
|
t.Errorf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestHTTPConfiguration(t *testing.T) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/yaml; charset=utf-8")
|
|
fmt.Fprint(w, `---
|
|
module1:
|
|
topic: flows
|
|
module2:
|
|
details:
|
|
workers: 5
|
|
interval-value: 20m
|
|
stuff: bye
|
|
elements:
|
|
- {"name": "first", "gauge": 67}
|
|
- {"name": "second"}
|
|
`)
|
|
}))
|
|
defer ts.Close()
|
|
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: ts.URL,
|
|
Dump: true,
|
|
}
|
|
|
|
parsed := dummyConfiguration{}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
// Expected configuration
|
|
expected := dummyConfiguration{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "bye",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 5,
|
|
IntervalValue: 20 * time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{"first", 67},
|
|
{"second", 0},
|
|
},
|
|
},
|
|
}
|
|
if diff := helpers.Diff(parsed, expected); diff != "" {
|
|
t.Errorf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestUnused(t *testing.T) {
|
|
t.Run("ignored fields", func(t *testing.T) {
|
|
config := `---
|
|
.unused: should be ignored
|
|
module1:
|
|
.too: nope
|
|
topic: flow
|
|
workers: 10
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
c := cmd.ConfigRelatedOptions{Path: configFile}
|
|
|
|
parsed := dummyConfiguration{}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("unused fields", func(t *testing.T) {
|
|
config := `---
|
|
unused: should not be ignored
|
|
module1:
|
|
extra: 111
|
|
topic: flow
|
|
workers: 10
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
c := cmd.ConfigRelatedOptions{Path: configFile}
|
|
|
|
parsed := dummyConfiguration{}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err == nil {
|
|
t.Fatal("Parse() didn't error")
|
|
} else if diff := helpers.Diff(err.Error(), `invalid configuration:
|
|
invalid key "Module1.extra"
|
|
invalid key "unused"`); diff != "" {
|
|
t.Fatalf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDefaultInSlice(t *testing.T) {
|
|
try := func(t *testing.T, parse func(cmd.ConfigRelatedOptions, *bytes.Buffer) any, expected any) {
|
|
// Configuration file
|
|
config := `---
|
|
modules:
|
|
- module1:
|
|
topic: flows1
|
|
- module1:
|
|
topic: flows2
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: configFile,
|
|
Dump: true,
|
|
}
|
|
|
|
out := bytes.NewBuffer([]byte{})
|
|
parsed := parse(c, out)
|
|
if diff := helpers.Diff(parsed, expected); diff != "" {
|
|
t.Errorf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
t.Run("without pointer", func(t *testing.T) {
|
|
try(t, func(c cmd.ConfigRelatedOptions, out *bytes.Buffer) any {
|
|
parsed := struct {
|
|
Modules []dummyConfiguration
|
|
}{}
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
return parsed
|
|
},
|
|
// Expected configuration
|
|
struct {
|
|
Modules []dummyConfiguration
|
|
}{
|
|
Modules: []dummyConfiguration{
|
|
{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows1",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "hello",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 1,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows2",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "hello",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 1,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
t.Run("with pointer", func(t *testing.T) {
|
|
try(t, func(c cmd.ConfigRelatedOptions, out *bytes.Buffer) any {
|
|
parsed := struct {
|
|
Modules []*dummyConfiguration
|
|
}{}
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
return parsed
|
|
},
|
|
struct {
|
|
Modules []*dummyConfiguration
|
|
}{
|
|
Modules: []*dummyConfiguration{
|
|
{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows1",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "hello",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 1,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows2",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "hello",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 1,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestDevNullDefault(t *testing.T) {
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: "/dev/null",
|
|
Dump: true,
|
|
}
|
|
|
|
var parsed dummyConfiguration
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
// Expected configuration
|
|
expected := dummyConfiguration{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "nothingness",
|
|
Workers: 100,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "hello",
|
|
},
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 1,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if diff := helpers.Diff(parsed, expected); diff != "" {
|
|
t.Errorf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestZeroCanOverrideDefault(t *testing.T) {
|
|
config := `---
|
|
module1:
|
|
topic: flows
|
|
workers: 10
|
|
module2:
|
|
details:
|
|
workers: 0
|
|
stuff: ""
|
|
`
|
|
configFile := filepath.Join(t.TempDir(), "config.yaml")
|
|
os.WriteFile(configFile, []byte(config), 0o644)
|
|
|
|
c := cmd.ConfigRelatedOptions{
|
|
Path: configFile,
|
|
Dump: true,
|
|
}
|
|
|
|
parsed := dummyConfiguration{}
|
|
expected := dummyConfiguration{
|
|
Module1: dummyModule1Configuration{
|
|
Listen: "127.0.0.1:8080",
|
|
Topic: "flows",
|
|
Workers: 10,
|
|
},
|
|
Module2: dummyModule2Configuration{
|
|
Details: dummyModule2DetailsConfiguration{
|
|
Workers: 0,
|
|
IntervalValue: time.Minute,
|
|
},
|
|
MoreDetails: MoreDetails{
|
|
Stuff: "",
|
|
},
|
|
Elements: []dummyModule2ElementsConfiguration{
|
|
{
|
|
Name: "el1",
|
|
Gauge: 10,
|
|
}, {
|
|
Name: "el2",
|
|
Gauge: 11,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
out := bytes.NewBuffer([]byte{})
|
|
if _, err := c.Parse(out, "dummy", &parsed); err != nil {
|
|
t.Fatalf("Parse() error:\n%+v", err)
|
|
}
|
|
if diff := helpers.Diff(parsed, expected); diff != "" {
|
|
t.Fatalf("Parse() (-got, +want):\n%s", diff)
|
|
}
|
|
|
|
log.Printf("output:\n%s", out.Bytes())
|
|
if !bytes.Contains(out.Bytes(), []byte(`stuff: ""`)) {
|
|
t.Errorf("Dumped configuration should contain `stuff: \"\"`")
|
|
}
|
|
if !bytes.Contains(out.Bytes(), []byte(`workers: 0`)) {
|
|
t.Errorf("Dumped configuration should contain `workers: 0`")
|
|
}
|
|
}
|