From 5b7303b0cc8932872e1e8d30b672c47382317ea0 Mon Sep 17 00:00:00 2001 From: eonedar Date: Thu, 12 Sep 2024 14:07:26 +0100 Subject: [PATCH] add test files and .golangci.yml --- .github/workflows/go.yml | 2 +- .golangci.yml | 101 +++++++ .../configuration_internal_test.go | 208 +++++++++++++ src/internal/logging/logging_test.go | 286 ++++++++++++++++++ src/internal/metric/metric_test.go | 35 +++ 5 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 .golangci.yml create mode 100644 src/internal/configuration/configuration_internal_test.go create mode 100644 src/internal/logging/logging_test.go create mode 100644 src/internal/metric/metric_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0b0b380..13e43ef 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -22,4 +22,4 @@ jobs: run: go build -v -mod=mod -o target/hello-world-app ./src - name: Test - run: go test -v ./src + run: go test -mod=mod -v ./src diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..14b4de6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,101 @@ +--- +run: + # default concurrency is a available CPU number + concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m + deadline: 2m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # list of build tags, all linters use it. Default is empty list. + build-tags: + - adp-playground + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but next dirs are always skipped independently + # from this option's value: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # Dummy stuff, fill if we want to skip + skip-dirs: + - src/external_libs + - autogenerated_by_my_lib + - static_modules + - zip-validation + + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + skip-files: + - ".*\\.my\\.go$" + - lib/bad.go + + + # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + #modules-download-mode: readonly|release|vendor + modules-download-mode: + + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" + format: line-number + + # print lines of code with issue, default is true + print-issued-lines: true + + # print linter name in the end of issue text, default is true + print-linter-name: true + +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + exclude: + + # Independently from option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is true. + exclude-use-default: false + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: false + + # Show only new issues created after git revision `REV` + # new-from-rev: HEAD~1 + new-from-rev: + + # Show only new issues created in git patch with set file path. + new-from-patch: + +# golangci.com configuration +# https://github.com/golangci/golangci/wiki/Configuration +service: + golangci-lint-version: 1.43.0 # use fixed version to not introduce new linters unexpectedly + prepare: + - echo "here I can run custom commands, but no preparation needed" diff --git a/src/internal/configuration/configuration_internal_test.go b/src/internal/configuration/configuration_internal_test.go new file mode 100644 index 0000000..3bb08c6 --- /dev/null +++ b/src/internal/configuration/configuration_internal_test.go @@ -0,0 +1,208 @@ +// COPYRIGHT Ericsson 2024 + +// The copyright to the computer program(s) herein is the property of +// Ericsson Inc. The programs may be used and/or copied only with written +// permission from Ericsson Inc. or in accordance with the terms and +// conditions stipulated in the agreement/contract under which the +// program(s) have been supplied. + +package configuration + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "os" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +const ( + key string = "OS_ENV" + defaultValueInt int = 1 + defaultValueString string = "string" +) + +func TestGetConfig(t *testing.T) { + t.Parallel() + + testConfig := AppConfig + + assert.NotNil(t, testConfig, + "Instance should not be nil") + + assert.Equal(t, 8050, testConfig.LocalPort, + "Port should be 8050, but got : "+strconv.Itoa(testConfig.LocalPort)) + + assert.Equal(t, "http", testConfig.LocalProtocol, + "Protocol should be http, but got : "+testConfig.LocalProtocol) + + assert.Equal(t, "certificate.pem", testConfig.CertFile, + "Certificate file name should be `certificate.pem`, but got : "+testConfig.CertFile) + + assert.Equal(t, "key.pem", testConfig.KeyFile, + "Keyfile file name should be `key.pem`, but got : "+testConfig.KeyFile) + + assert.Equal(t, "", testConfig.LogControlFile, + "LogControlFile should be an empty string, but got : "+testConfig.LogControlFile) +} + +func TestReloadAppConfig(t *testing.T) { + t.Parallel() + config1 := AppConfig + ReloadAppConfig() + config2 := AppConfig + assert.NotSame(t, config1, config2, + "AppConfig should be different pointer after ReloadAppConfig()") +} + +func TestGetOsEnvIntSet(t *testing.T) { + t.Setenv(key, "123") + + result := getOsEnvInt(key, defaultValueInt) + assert.Equal(t, 123, result) +} + +func TestGetOsEnvIntUnset(t *testing.T) { + t.Parallel() + + result := getOsEnvInt(key, defaultValueInt) + assert.Equal(t, defaultValueInt, result) +} + +func TestGetOsEnvIntSetBadInt(t *testing.T) { + t.Setenv(key, "abc") + + result := getOsEnvInt(key, defaultValueInt) + assert.Equal(t, defaultValueInt, result) +} + +func TestGetOsEnvStringSet(t *testing.T) { + t.Setenv(key, "someValue") + + result := getOsEnvString(key, defaultValueString) + assert.Equal(t, "someValue", result) +} + +func TestGetOsEnvStringUnset(t *testing.T) { + t.Parallel() + + result := getOsEnvString(key, defaultValueString) + assert.Equal(t, defaultValueString, result) +} + +func TestInvalidTLSConfigs(t *testing.T) { + tlsConfig := NewTLSConfig() + assert.Nil(t, tlsConfig) + + logMtlsConfig := LogmTLSConfig() + assert.Nil(t, logMtlsConfig) +} + +func TestTLSConfigs(t *testing.T) { + err := generateCACert() + assert.Nil(t, err, fmt.Sprintf("error generating cacert: %v", err)) + err = generateKeyCertPair() + assert.Nil(t, err, fmt.Sprintf("error generating certs: %v", err)) + t.Setenv("CA_CERT_FILE_NAME", "cacert.crt") + t.Setenv("APP_CERT", "cert.pem") + t.Setenv("APP_KEY", "key.pem") + ReloadAppConfig() + + tlsConfig := NewTLSConfig() + assert.NotNil(t, tlsConfig) + + logMtlsConfig := LogmTLSConfig() + assert.NotNil(t, logMtlsConfig) + + t.Cleanup(func() { + e := os.Remove("cacert.crt") + assert.Nil(t, e, fmt.Sprintf("error deleting cacert.crt: %v", e)) + e = os.Remove("cert.pem") + assert.Nil(t, e, fmt.Sprintf("error deleting cert.pem: %v", e)) + e = os.Remove("key.pem") + assert.Nil(t, e, fmt.Sprintf("error deleting key.pem: %v", e)) + }) +} + +func generateCACert() error { + file, err := os.OpenFile("cacert.crt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) + if err != nil { + return err + } + err = file.Close() + + return err +} + +func generateKeyCertPair() error { + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return err + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 64) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return err + } + + keyUsage := x509.KeyUsageDigitalSignature + keyUsage |= x509.KeyUsageKeyEncipherment + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"test"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(10000), + + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return err + } + + certOut, err := os.Create("cert.pem") + if err != nil { + return err + } + + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return err + } + + if err := certOut.Close(); err != nil { + return err + } + + keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return err + } + + privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return err + } + + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return err + } + + err = keyOut.Close() + + return err +} diff --git a/src/internal/logging/logging_test.go b/src/internal/logging/logging_test.go new file mode 100644 index 0000000..a078236 --- /dev/null +++ b/src/internal/logging/logging_test.go @@ -0,0 +1,286 @@ +package logging + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "eric-oss-hello-world-go-app/src/internal/configuration" + "fmt" + "io" + "math/big" + "os" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +const logOutputFileName = "testlogfile" + +func createTestFile(t *testing.T, fileName, fileContent string) { + file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) + assert.Nil(t, err, fmt.Sprintf("error creating file: %v", err)) + n, err := file.WriteString(fileContent) + assert.NotEqual(t, n, 0) + assert.Nil(t, err) + err = file.Close() + assert.Nil(t, err) + t.Cleanup(func() { + e := os.Remove(fileName) + assert.Nil(t, e, fmt.Sprintf("error deleting file: %v", e)) + }) +} + +func TestInitWithoutLogCtrl(t *testing.T) { + Init() + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, InfoLevel) + assert.Equal(t, logger.logrus.GetLevel(), InfoLevel) +} + +func TestInitWithLogCtrl(t *testing.T) { + createTestFile(t, "logcontrol.json", "[{\"severity\": \"debug\",\"container\": \"rapp-eric-oss-hello-world-go-app\"}]") + t.Setenv("LOG_CTRL_FILE", "logcontrol.json") + t.Setenv("CONTAINER_NAME", "rapp-eric-oss-hello-world-go-app") + configuration.ReloadAppConfig() + Init() + assert.True(t, true) + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, DebugLevel) + assert.Equal(t, logger.logrus.GetLevel(), DebugLevel) +} + +func TestInitWithInfoLevel(t *testing.T) { + createTestFile(t, "logcontrol.json", "[{\"severity\": \"info\",\"container\": \"rapp-eric-oss-hello-world-go-app\"}]") + t.Setenv("LOG_CTRL_FILE", "logcontrol.json") + t.Setenv("CONTAINER_NAME", "rapp-eric-oss-hello-world-go-app") + configuration.ReloadAppConfig() + Init() + assert.True(t, true) + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, InfoLevel) + assert.Equal(t, logger.logrus.GetLevel(), InfoLevel) +} + +func TestInitWithWarningLevel(t *testing.T) { + createTestFile(t, "logcontrol.json", "[{\"severity\": \"warning\",\"container\": \"rapp-eric-oss-hello-world-go-app\"}]") + t.Setenv("LOG_CTRL_FILE", "logcontrol.json") + t.Setenv("CONTAINER_NAME", "rapp-eric-oss-hello-world-go-app") + configuration.ReloadAppConfig() + Init() + assert.True(t, true) + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, WarningLevel) + assert.Equal(t, logger.logrus.GetLevel(), WarningLevel) +} + +func TestInitWithErrorLevel(t *testing.T) { + createTestFile(t, "logcontrol.json", "[{\"severity\": \"error\",\"container\": \"rapp-eric-oss-hello-world-go-app\"}]") + t.Setenv("LOG_CTRL_FILE", "logcontrol.json") + t.Setenv("CONTAINER_NAME", "rapp-eric-oss-hello-world-go-app") + configuration.ReloadAppConfig() + Init() + assert.True(t, true) + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, ErrorLevel) + assert.Equal(t, logger.logrus.GetLevel(), ErrorLevel) +} + +func TestInitWithFatalLevel(t *testing.T) { + createTestFile(t, "logcontrol.json", "[{\"severity\": \"critical\",\"container\": \"rapp-eric-oss-hello-world-go-app\"}]") + t.Setenv("LOG_CTRL_FILE", "logcontrol.json") + t.Setenv("CONTAINER_NAME", "rapp-eric-oss-hello-world-go-app") + configuration.ReloadAppConfig() + Init() + assert.True(t, true) + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, FatalLevel) + assert.Equal(t, logger.logrus.GetLevel(), FatalLevel) +} + +func TestInitWithInvalidLogCtrl(t *testing.T) { + createTestFile(t, "logcontrol.json", "[][]") + t.Setenv("LOG_CTRL_FILE", "logcontrol.json") + t.Setenv("CONTAINER_NAME", "rapp-eric-oss-hello-world-go-app") + configuration.ReloadAppConfig() + Init() + assert.True(t, true) + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, InfoLevel) + assert.Equal(t, logger.logrus.GetLevel(), InfoLevel) +} + +func TestSetLevel(t *testing.T) { + Init() + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, InfoLevel) + assert.Equal(t, logger.logrus.GetLevel(), InfoLevel) + + SetLevel(DebugLevel) + assert.Equal(t, logger.level, DebugLevel) + assert.Equal(t, logger.logrus.GetLevel(), DebugLevel) +} + +func TestLogOnLowestLevel(t *testing.T) { + Init() + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, InfoLevel) + assert.Equal(t, logger.logrus.GetLevel(), InfoLevel) + + file, err := os.OpenFile(logOutputFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) + assert.Nil(t, err, fmt.Sprintf("error creating file: %v", err)) + + wrt := io.MultiWriter(os.Stdout, file) + SetLevel(DebugLevel) + SetOutput(wrt) + + Error("error test") + Warning("warning test") + Info("info test") + Debug("debug test") + + data, err := os.ReadFile(logOutputFileName) + assert.Nil(t, err, fmt.Sprintf("error opening file: %v", err)) + + assert.True(t, strings.Contains(string(data), "error test"), "This message should be logged") + assert.True(t, strings.Contains(string(data), "warning test"), "This message should be logged") + assert.True(t, strings.Contains(string(data), "info test"), "This message should be logged") + assert.True(t, strings.Contains(string(data), "debug test"), "This message should be logged") + t.Cleanup(func() { + err := file.Close() + assert.Nil(t, err) + e := os.Remove(logOutputFileName) + assert.Nil(t, e, fmt.Sprintf("error deleting test log file: %v", e)) + }) +} + +func TestLogOnHighestLevel(t *testing.T) { + Init() + assert.NotNil(t, logger.logrus) + assert.Equal(t, logger.level, InfoLevel) + assert.Equal(t, logger.logrus.GetLevel(), InfoLevel) + + file, err := os.OpenFile(logOutputFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) + assert.Nil(t, err, fmt.Sprintf("error creating file: %v", err)) + + wrt := io.MultiWriter(os.Stdout, file) + SetLevel(FatalLevel) + SetOutput(wrt) + + Error("error test") + Warning("warning test") + Info("info test") + Debug("debug test") + + data, err := os.ReadFile(logOutputFileName) + assert.Nil(t, err, fmt.Sprintf("error opening file: %v", err)) + + assert.False(t, strings.Contains(string(data), "error test"), "This message should not be logged") + assert.False(t, strings.Contains(string(data), "warning test"), "This message should not be logged") + assert.False(t, strings.Contains(string(data), "info test"), "This message should not be logged") + assert.False(t, strings.Contains(string(data), "debug test"), "This message should not be logged") + t.Cleanup(func() { + err = file.Close() + assert.Nil(t, err) + e := os.Remove(logOutputFileName) + assert.Nil(t, e, fmt.Sprintf("error deleting test log file: %v", e)) + }) +} + +func TestDispatch(t *testing.T) { + err := generateCACert() + assert.Nil(t, err, fmt.Sprintf("error generating cacert: %v", err)) + err = generateKeyCertPair() + assert.Nil(t, err, fmt.Sprintf("error generating certs: %v", err)) + t.Setenv("CA_CERT_FILE_NAME", "cacert.crt") + t.Setenv("APP_CERT", "cert.pem") + t.Setenv("APP_KEY", "key.pem") + configuration.ReloadAppConfig() + Init() + Info("test") + t.Cleanup(func() { + e := os.Remove("cacert.crt") + assert.Nil(t, e, fmt.Sprintf("error deleting cacert.crt: %v", e)) + e = os.Remove("cert.pem") + assert.Nil(t, e, fmt.Sprintf("error deleting cert.pem: %v", e)) + e = os.Remove("key.pem") + assert.Nil(t, e, fmt.Sprintf("error deleting key.pem: %v", e)) + }) +} + +func generateCACert() error { + file, err := os.OpenFile("cacert.crt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666) + if err != nil { + return err + } + err = file.Close() + + return err +} + +func generateKeyCertPair() error { + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return err + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 64) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return err + } + + keyUsage := x509.KeyUsageDigitalSignature + keyUsage |= x509.KeyUsageKeyEncipherment + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"test"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(10000), + + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return err + } + + certOut, err := os.Create("cert.pem") + if err != nil { + return err + } + + if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return err + } + + if err := certOut.Close(); err != nil { + return err + } + + keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + if err != nil { + return err + } + + privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return err + } + + if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return err + } + + err = keyOut.Close() + return err +} diff --git a/src/internal/metric/metric_test.go b/src/internal/metric/metric_test.go new file mode 100644 index 0000000..3878374 --- /dev/null +++ b/src/internal/metric/metric_test.go @@ -0,0 +1,35 @@ +package metric_test + +import ( + "eric-oss-hello-world-go-app/src/internal/metric" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateMetrics(t *testing.T) { + t.Parallel() + + assert.Nil(t, metric.RequestsTotal) + assert.Nil(t, metric.RequestsFailedTotal) + assert.Nil(t, metric.HelloWorldHTTPRequestsTotal) + + metric.SetupMetrics() + + assert.NotNil(t, metric.RequestsTotal, + "RequestsTotal has not been initialized") + assert.NotNil(t, metric.RequestsFailedTotal, + "RequestsFailedTotal has not been initialized") + assert.NotNil(t, metric.HelloWorldHTTPRequestsTotal, + "HelloWorldHTTPRequestsTotal has not been initialized") +} + +func TestRegisterMetrics(t *testing.T) { + t.Parallel() + + metric.SetupMetrics() + + metrics, err := metric.Registry.Gather() + assert.NoError(t, err) + assert.Len(t, metrics, 2) +}