Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[kubeops] Add tests for GetRelease #1434

Merged
merged 5 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions cmd/kubeops/internal/handler/dashboardcompat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package handler

import (
"net/http/httptest"
"runtime/debug"

"github.com/google/go-cmp/cmp"
"github.com/kubeapps/common/response"
h3chart "helm.sh/helm/v3/pkg/chart"
h3 "helm.sh/helm/v3/pkg/release"
h2chart "k8s.io/helm/pkg/proto/hapi/chart"
h2 "k8s.io/helm/pkg/proto/hapi/release"

"testing"
)

func TestNewDashboardCompatibleRelease(t *testing.T) {
type testScenario struct {
// Scenario params
Description string
Helm3Release h3.Release
Helm2Release h2.Release
MarshallingFunction func(h2.Release) string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is always asResponse, in which case you plan to change this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add a test scenario where we test newDashboardCompatibleRelease against for example spew.Sdump, or a hand-written one where for example not all fields are relevant/populated etc.

}
tests := []testScenario{
{
Description: "Two equivalent releases",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Foo",
Namespace: "default",
Chart: &h3chart.Chart{
Metadata: &h3chart.Metadata{},
Values: map[string]interface{}{
"port": 8080,
},
},
Info: &h3.Info{
Status: h3.StatusDeployed,
},
Version: 1,
Config: map[string]interface{}{
"port": 3000,
"user": map[string]interface{}{
"name": "user1",
"password": "123456",
},
},
},
Helm2Release: h2.Release{
Name: "Foo",
Namespace: "default",
Info: &h2.Info{
Status: &h2.Status{
Code: h2.Status_DEPLOYED,
},
},
Chart: &h2chart.Chart{
Metadata: &h2chart.Metadata{},
Values: &h2chart.Config{
Raw: "port: 8080\n",
},
},
Version: 1,
Config: &h2chart.Config{
Raw: "port: 3000\nuser:\n name: user1\n password: \"123456\"\n",
},
},
},
{
Description: "Two equivalent releases with switched order of values",
MarshallingFunction: asResponse,
Helm3Release: h3.Release{
Name: "Foo",
Namespace: "default",
Chart: &h3chart.Chart{
Metadata: &h3chart.Metadata{},
Values: map[string]interface{}{},
},
Info: &h3.Info{
Status: h3.StatusDeployed,
},
Version: 1,
Config: map[string]interface{}{
"user": map[string]interface{}{
"password": "123456",
"name": "user1",
},
"port": 3000,
},
},
Helm2Release: h2.Release{
Name: "Foo",
Namespace: "default",
Info: &h2.Info{
Status: &h2.Status{
Code: h2.Status_DEPLOYED,
},
},
Chart: &h2chart.Chart{
Metadata: &h2chart.Metadata{},
Values: &h2chart.Config{
Raw: "{}\n",
},
},
Version: 1,
Config: &h2chart.Config{
Raw: "port: 3000\nuser:\n name: user1\n password: \"123456\"\n",
},
},
},
}

for _, test := range tests {
t.Run(test.Description, func(t *testing.T) {
// Capture the panic and report it in an orderly fashion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused as to why we're handling a panic that we've discovered in tests, rather than removing the panic from the code? What causes the panic... if I run these tests without the panic handling, it just succeeds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came back to this later today, found the cause of the panic, added some failing tests and fixed. See what you think: #1445

defer func() {
if r := recover(); r != nil {
t.Errorf("Got a panic: %v. \nStacktrace: \n%s", r, string(debug.Stack()))
}
}()
// Perform conversion
compatibleH3rls := newDashboardCompatibleRelease(test.Helm3Release)
// Marshall both: Compatible H3Release and H2Release
h3Marshalled := test.MarshallingFunction(compatibleH3rls)
t.Logf("Marshalled Helm 3 Release %s", h3Marshalled)
h2Marshalled := test.MarshallingFunction(test.Helm2Release)
t.Logf("Marshalled Helm 2 Release %s", h2Marshalled)
// Check result
if h3Marshalled != h2Marshalled {
t.Errorf("Not equal: %s", cmp.Diff(h3Marshalled, h2Marshalled))
}
})
}
}

func asResponse(data h2.Release) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain why you are using asResponse? Why not using directly a marshall function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still not sure about this ^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the current setting, the result from the function newDashboardCompatibleRelease is used in kubeops/internal/handler here & here, which both marshal the data using github.com/unrolled/render. To avoid digging in the aforementioned library and to enable further tests where newDashboardCompatibleRelease are marshalled/viewed in different ways, I created asResponse as a helper function to mimic the behaviour in /cmd/kubeops/internal/handler/handler.go.

If my reasoning seems too stretched I can just bake it into the test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, a comment explaining that would be helpful for the future

w := httptest.NewRecorder()
response.NewDataResponse(data).Write(w)
return w.Body.String()
}
47 changes: 47 additions & 0 deletions cmd/kubeops/internal/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io/ioutil"
"net/http/httptest"
"sort"
"strings"
"testing"

Expand Down Expand Up @@ -120,6 +121,21 @@ func TestActions(t *testing.T) {
},
{
// Scenario params
Description: "Get a non-existing release",
ExistingReleases: []release.Release{},
DisableAuth: true,
Skip: true,
// Request params
RequestBody: "",
RequestQuery: "",
Action: "get",
Params: map[string]string{"namespace": "default", "releaseName": "foobar"},
// Expected result
StatusCode: 404,
RemainingReleases: []release.Release{},
ResponseBody: "",
},
{
Description: "Delete a simple release",
ExistingReleases: []release.Release{
createRelease("foobarchart", "foobar", "default", 1, release.StatusDeployed),
Expand Down Expand Up @@ -154,6 +170,27 @@ func TestActions(t *testing.T) {
RemainingReleases: []release.Release{},
ResponseBody: "",
},
{
// Scenario params
Description: "Get a simple release",
ExistingReleases: []release.Release{
createRelease("foo", "foobar", "default", 1, release.StatusDeployed),
createRelease("oof", "oofbar", "dev", 1, release.StatusDeployed),
},
DisableAuth: true,
// Request params
RequestBody: "",
RequestQuery: "",
Action: "get",
Params: map[string]string{"namespace": "default", "releaseName": "foobar"},
// Expected result
StatusCode: 200,
RemainingReleases: []release.Release{
createRelease("foo", "foobar", "default", 1, release.StatusDeployed),
createRelease("oof", "oofbar", "dev", 1, release.StatusDeployed),
},
ResponseBody: `{"data":{"name":"foobar","info":{"status":{"code":1}},"chart":{"metadata":{"name":"foo"},"values":{"raw":"{}\n"}},"config":{"raw":"{}\n"},"version":1,"namespace":"default"}}`,
},
{
// Scenario params
Description: "Delete and purge a simple release with purge=1",
Expand Down Expand Up @@ -213,6 +250,8 @@ func TestActions(t *testing.T) {
}
// Perform request
switch test.Action {
case "get":
GetRelease(*cfg, response, req, test.Params)
case "create":
CreateRelease(*cfg, response, req, test.Params)
case "delete":
Expand All @@ -225,6 +264,14 @@ func TestActions(t *testing.T) {
t.Errorf("Expecting a StatusCode %d, received %d", test.StatusCode, response.Code)
}
releases := derefReleases(cfg.ActionConfig.Releases)
// The Helm memory driver does not appear to have consistent ordering.
// See https://github.com/helm/helm/issues/7263
// Just sort by "name.version.namespace" which is good enough here.
sort.Slice(releases, func(i, j int) bool {
iKey := fmt.Sprintf("%s.%d.%s", releases[i].Name, releases[i].Version, releases[i].Namespace)
jKey := fmt.Sprintf("%s.%d.%s", releases[j].Name, releases[j].Version, releases[j].Namespace)
return iKey < jKey
})
rlsComparer := cmp.Comparer(func(x release.Release, y release.Release) bool {
return x.Name == y.Name &&
x.Version == y.Version &&
Expand Down
58 changes: 58 additions & 0 deletions pkg/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,64 @@ func makeReleases(t *testing.T, actionConfig *action.Configuration, rels []relea
}
}

func TestGetRelease(t *testing.T) {
fooApp := releaseStub{"foo", "my_ns", 1, release.StatusDeployed}
barApp := releaseStub{"bar", "other_ns", 1, release.StatusDeployed}
testCases := []struct {
description string
existingReleases []releaseStub
targetApp string
targetNamespace string
expectedResult string
shouldFail bool
}{
{
description: "Get an existing release",
existingReleases: []releaseStub{fooApp, barApp},
targetApp: "foo",
targetNamespace: "my_ns",
expectedResult: "foo",
},
{
description: "Get an existing release with default namespace",
existingReleases: []releaseStub{fooApp, barApp},
targetApp: "foo",
targetNamespace: "",
expectedResult: "foo",
},
{
description: "Get an non-existing release",
existingReleases: []releaseStub{barApp},
targetApp: "foo",
targetNamespace: "my_ns",
expectedResult: "",
shouldFail: true,
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
cfg := newActionConfigFixture(t)
makeReleases(t, cfg, tc.existingReleases)
rls, err := GetRelease(cfg, tc.targetApp)
if tc.shouldFail && err == nil {
t.Errorf("Get %s/%s should fail", tc.targetNamespace, tc.targetApp)
}
if !tc.shouldFail {
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if rls == nil {
t.Fatalf("Release is nil: %v", rls)
}
if rls.Name != tc.expectedResult {
t.Errorf("Expecting app %s, received %s", tc.expectedResult, rls.Name)
}
}
})
}
}

func TestCreateReleases(t *testing.T) {
testCases := []struct {
desc string
Expand Down