Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
chore: increase test coverage of go-sdk (#526)
Browse files Browse the repository at this point in the history
* started refactoring initialization logic a bit

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* enhanced tests, fixed handling error when event could not be decoded, enhanced example, and correctly pass logger implementation to cp-connector

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* added codecov

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* fixed variable name and added additional checks in constructor func test

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* cleanup

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* fixed tests.yml workflow

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* bit of cleanup

Signed-off-by: warber <bernd.warmuth@dynatrace.com>

* review

Signed-off-by: warber <bernd.warmuth@dynatrace.com>
  • Loading branch information
warber authored Jul 27, 2022
1 parent b3ecfef commit c15488f
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 139 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Test
run: go test -race -v ./...
run: go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...
- name: Report test coverage
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
22 changes: 22 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
coverage:
status:
project:
default:
# basic
target: auto
threshold: 2% # allow cov to drop by 2% (just in case)
patch:
default:
threshold: 1% # allow patch

ignore:
- "**/*.yaml" # ignore all yaml files (Kubernetes manifests, etc...)
- "**/*.yml" # same as above
- "**/*.md" # ignore all markdown files, those are not relevant for building/testing
- "**/*.sh" # ignore shell scripts

comment:
layout: "diff, files, flags"

github_checks:
annotations: false
9 changes: 6 additions & 3 deletions examples/go-sdk/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ go 1.18

replace github.com/keptn/go-utils => ../../../go-utils

require github.com/keptn/go-utils v0.0.0-00010101000000-000000000000
require (
github.com/keptn/go-utils v0.0.0-00010101000000-000000000000
github.com/sirupsen/logrus v1.8.1
)

require (
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/cloudevents/sdk-go/observability/opentelemetry/v2 v2.0.0-20211001212819-74757a691209 // indirect
github.com/cloudevents/sdk-go/v2 v2.10.0 // indirect
Expand All @@ -24,7 +28,6 @@ require (
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/stretchr/testify v1.7.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 // indirect
go.opentelemetry.io/otel v1.7.0 // indirect
Expand All @@ -36,7 +39,7 @@ require (
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
Expand Down
6 changes: 4 additions & 2 deletions examples/go-sdk/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
Expand Down Expand Up @@ -321,8 +323,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
7 changes: 7 additions & 0 deletions examples/go-sdk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ package main

import (
"github.com/keptn/go-utils/pkg/sdk"
"github.com/sirupsen/logrus"
"log"
)

const greetingsTriggeredEventType = "sh.keptn.event.greeting.triggered"
const serviceName = "greetings-service"

func main() {
logrus.SetLevel(logrus.DebugLevel)
log.Fatal(sdk.NewKeptn(
// the name of your keptn service
serviceName,
// the task handler containing logic to handle the
// "sh.keptn.event.greeting.triggered" event
sdk.WithTaskHandler(
greetingsTriggeredEventType,
NewGreetingsHandler()),
// using logrus library as a logger
sdk.WithLogger(logrus.StandardLogger()),
).Start())
}
86 changes: 72 additions & 14 deletions pkg/sdk/internal/api/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,103 @@ package api

import (
"fmt"
"github.com/benbjohnson/clock"
keptnapi "github.com/keptn/go-utils/pkg/api/utils"
"github.com/keptn/go-utils/pkg/sdk/connector/controlplane"
"github.com/keptn/go-utils/pkg/sdk/connector/eventsource"
eventsourceHttp "github.com/keptn/go-utils/pkg/sdk/connector/eventsource/http"
eventsourceNats "github.com/keptn/go-utils/pkg/sdk/connector/eventsource/nats"
"github.com/keptn/go-utils/pkg/sdk/connector/logforwarder"
"github.com/keptn/go-utils/pkg/sdk/connector/logger"
"github.com/keptn/go-utils/pkg/sdk/connector/nats"
"github.com/keptn/go-utils/pkg/sdk/connector/subscriptionsource"
"github.com/keptn/go-utils/pkg/sdk/internal/config"
"net/http"
"net/url"
"strings"
)

// Initializer implements both methods of creating a new keptn API with internal or remote execution plane
type Initializer struct {
Remote func(baseURL string, options ...func(*keptnapi.APISet)) (*keptnapi.APISet, error)
Internal func(client *http.Client, apiMappings ...keptnapi.InClusterAPIMappings) (*keptnapi.InternalAPISet, error)
type InitializationResult struct {
KeptnAPI keptnapi.KeptnInterface
ControlPlane *controlplane.ControlPlane
EventSenderCallback controlplane.EventSender
ResourceHandler *keptnapi.ResourceHandler
}

func CreateKeptnAPI(httpClient *http.Client, env config.EnvConfig) (keptnapi.KeptnInterface, error) {
return createAPI(httpClient, env, Initializer{keptnapi.New, keptnapi.NewInternal})
}

func createAPI(httpClient *http.Client, env config.EnvConfig, apiInit Initializer) (keptnapi.KeptnInterface, error) {
// Initialize takes care of creating the API clients and initializing the cp-connector library based
// on environment variables
func Initialize(env config.EnvConfig, clientFactory HTTPClientGetter, logger logger.Logger) (*InitializationResult, error) {
// initialize http client
httpClient, err := clientFactory.Get()
if err != nil {
return nil, fmt.Errorf("could not initialize HTTP client: %w", err)
}
// fall back to uninitialized http client
if httpClient == nil {
httpClient = &http.Client{}
}

// initialize api
api, err := apiSet(env, httpClient)
if err != nil {
return nil, fmt.Errorf("could not initialize control plane client api: %w", err)
}
// initialize api handlers and cp-connector components
resourceHandler := resourceHandler(env)
ss, es, lf := createCPComponents(api, logger, env)
controlPlane := controlplane.New(ss, es, lf, controlplane.WithLogger(logger))

return &InitializationResult{
KeptnAPI: api,
ControlPlane: controlPlane,
EventSenderCallback: es.Sender(),
ResourceHandler: resourceHandler,
}, nil

}

func apiSet(env config.EnvConfig, httpClient *http.Client) (keptnapi.KeptnInterface, error) {

if env.PubSubConnectionType() == config.ConnectionTypeHTTP {
scheme := "http"
parsed, err := url.ParseRequestURI(env.KeptnAPIEndpoint)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not parse given Keptn API endpoint: %w", err)
}

// accepts either "" or http
if parsed.Scheme == "" || !strings.HasPrefix(parsed.Scheme, "http") {
return nil, fmt.Errorf("invalid scheme for keptn endpoint, %s is not http or https", env.KeptnAPIEndpoint)
}

if strings.HasPrefix(parsed.Scheme, "http") {
// if no value is assigned to the endpoint than we keep the default scheme
scheme = parsed.Scheme
}
return apiInit.Remote(env.KeptnAPIEndpoint, keptnapi.WithScheme(scheme), keptnapi.WithHTTPClient(httpClient), keptnapi.WithAuthToken(env.KeptnAPIToken))
return keptnapi.New(env.KeptnAPIEndpoint, keptnapi.WithScheme(scheme), keptnapi.WithHTTPClient(httpClient), keptnapi.WithAuthToken(env.KeptnAPIToken))

}
return keptnapi.NewInternal(httpClient)

return apiInit.Internal(httpClient)
}

func eventSource(apiSet keptnapi.KeptnInterface, logger logger.Logger, env config.EnvConfig) eventsource.EventSource {
if env.PubSubConnectionType() == config.ConnectionTypeHTTP {
return eventsourceHttp.New(clock.New(), eventsourceHttp.NewEventAPI(apiSet.ShipyardControlV1(), apiSet.APIV1()))
}
natsConnector := nats.New(env.EventBrokerURL, nats.WithLogger(logger))
return eventsourceNats.New(natsConnector, eventsourceNats.WithLogger(logger))
}

func subscriptionSource(apiSet keptnapi.KeptnInterface, logger logger.Logger) subscriptionsource.SubscriptionSource {
return subscriptionsource.New(apiSet.UniformV1(), subscriptionsource.WithLogger(logger))
}

func logForwarder(apiSet keptnapi.KeptnInterface, logger logger.Logger) logforwarder.LogForwarder {
return logforwarder.New(apiSet.LogsV1(), logforwarder.WithLogger(logger))
}

func createCPComponents(apiSet keptnapi.KeptnInterface, logger logger.Logger, env config.EnvConfig) (subscriptionsource.SubscriptionSource, eventsource.EventSource, logforwarder.LogForwarder) {
return subscriptionSource(apiSet, logger), eventSource(apiSet, logger, env), logForwarder(apiSet, logger)
}
func resourceHandler(env config.EnvConfig) *keptnapi.ResourceHandler {
return keptnapi.NewResourceHandler(env.ConfigurationServiceURL)
}
119 changes: 58 additions & 61 deletions pkg/sdk/internal/api/initializer_test.go
Original file line number Diff line number Diff line change
@@ -1,74 +1,71 @@
package api

import (
"fmt"
keptnapi "github.com/keptn/go-utils/pkg/api/utils"
"github.com/keptn/go-utils/pkg/sdk/connector/logger"
"github.com/keptn/go-utils/pkg/sdk/internal/config"
"github.com/stretchr/testify/require"
"net/http"
"reflect"
"testing"
)

func Test_createAPI(t *testing.T) {
type fakeHTTPClientFactory struct {
GetFn func() (*http.Client, error)
}

apiInit := Initializer{
Internal: func(client *http.Client, apiMappings ...keptnapi.InClusterAPIMappings) (*keptnapi.InternalAPISet, error) {
return &keptnapi.InternalAPISet{}, nil
},
Remote: func(baseURL string, options ...func(*keptnapi.APISet)) (*keptnapi.APISet, error) {
return &keptnapi.APISet{}, nil
},
}
func (f *fakeHTTPClientFactory) Get() (*http.Client, error) {
return f.GetFn()
}

tests := []struct {
name string
env config.EnvConfig
wantInternal bool
wantErr bool
}{
{
name: "test no env internal NATS ",
env: config.EnvConfig{},
wantInternal: true,
wantErr: false,
},
{
name: "test FAIL for no http address",
env: config.EnvConfig{
KeptnAPIEndpoint: "ssh://mynotsogoodendpoint",
},
wantErr: true,
wantInternal: false,
},
{
name: "test FAIL for no good address",
env: config.EnvConfig{
KeptnAPIEndpoint: ":///MALFORMEDendpoint",
},
wantErr: true,
wantInternal: false,
},
{
name: "test PASS for http address",
env: config.EnvConfig{
KeptnAPIEndpoint: "http://endpoint",
},
wantErr: false,
wantInternal: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createAPI(nil, tt.env, apiInit)
if (err != nil) != tt.wantErr {
t.Errorf("createAPI() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil && tt.wantInternal && !reflect.DeepEqual(got, &keptnapi.InternalAPISet{}) {
t.Errorf("createAPI() got = %v, wanted internal API", got)
} else if err == nil && !tt.wantInternal && !reflect.DeepEqual(got, &keptnapi.APISet{}) {
t.Errorf("createAPI() got = %v, want remote execution plane", got)
}
func Test_Initialize(t *testing.T) {
t.Run("Remote use case - invalid keptn api endpoint", func(t *testing.T) {
env := config.EnvConfig{KeptnAPIEndpoint: "://mynotsogoodendpoint"}
result, err := Initialize(env, CreateClientGetter(env), logger.NewDefaultLogger())
require.Error(t, err)
require.Nil(t, result)
})
t.Run("Remote use case - no http address as keptn api endpoint", func(t *testing.T) {
env := config.EnvConfig{KeptnAPIEndpoint: "ssh://mynotsogoodendpoint"}
result, err := Initialize(env, CreateClientGetter(env), logger.NewDefaultLogger())
require.Error(t, err)
require.Nil(t, result)

})
}
})
t.Run("Remote use case - remote api set is used", func(t *testing.T) {
env := config.EnvConfig{KeptnAPIEndpoint: "http://endpoint"}
result, err := Initialize(env, CreateClientGetter(env), logger.NewDefaultLogger())
require.NoError(t, err)
require.NotNil(t, result.ControlPlane)
require.NotNil(t, result.EventSenderCallback)
require.NotNil(t, result.KeptnAPI)
require.IsType(t, &keptnapi.APISet{}, result.KeptnAPI)
require.NotNil(t, result.ResourceHandler)
})
t.Run("Internal Use case - internal api set is used", func(t *testing.T) {
env := config.EnvConfig{}
result, err := Initialize(env, CreateClientGetter(env), logger.NewDefaultLogger())
require.NoError(t, err)
require.NotNil(t, result.ControlPlane)
require.NotNil(t, result.EventSenderCallback)
require.NotNil(t, result.KeptnAPI)
require.IsType(t, &keptnapi.InternalAPISet{}, result.KeptnAPI)
require.NotNil(t, result.ResourceHandler)
})
t.Run("HTTP client creation fails", func(t *testing.T) {
env := config.EnvConfig{KeptnAPIEndpoint: "http://endpoint"}
result, err := Initialize(env, &fakeHTTPClientFactory{GetFn: func() (*http.Client, error) { return nil, fmt.Errorf("err") }}, logger.NewDefaultLogger())
require.Error(t, err)
require.Nil(t, result)
})
t.Run("HTTP client creation returns nil client", func(t *testing.T) {
env := config.EnvConfig{KeptnAPIEndpoint: "http://endpoint"}
result, err := Initialize(env, &fakeHTTPClientFactory{GetFn: func() (*http.Client, error) { return nil, nil }}, logger.NewDefaultLogger())
require.NoError(t, err)
require.NotNil(t, result.ControlPlane)
require.NotNil(t, result.EventSenderCallback)
require.NotNil(t, result.KeptnAPI)
require.IsType(t, &keptnapi.APISet{}, result.KeptnAPI)
require.NotNil(t, result.ResourceHandler)
})
}
Loading

0 comments on commit c15488f

Please sign in to comment.