Commit 14510866 authored by Brad Bowman's avatar Brad Bowman

Update cmd tests to use ReleaseMock and assocaites from the helm package

parent e974ab31
......@@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestDelete(t *testing.T) {
......@@ -33,28 +34,32 @@ func TestDelete(t *testing.T) {
args: []string{"aeneas"},
flags: []string{},
expected: "", // Output of a delete is an empty string and exit 0.
resp: releaseMock(&releaseOptions{name: "aeneas"}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
},
{
name: "delete with timeout",
args: []string{"aeneas"},
flags: []string{"--timeout", "120"},
expected: "",
resp: releaseMock(&releaseOptions{name: "aeneas"}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
},
{
name: "delete without hooks",
args: []string{"aeneas"},
flags: []string{"--no-hooks"},
expected: "",
resp: releaseMock(&releaseOptions{name: "aeneas"}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
},
{
name: "purge",
args: []string{"aeneas"},
flags: []string{"--purge"},
expected: "",
resp: releaseMock(&releaseOptions{name: "aeneas"}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
},
{
name: "delete without release",
......
......@@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestGetHooks(t *testing.T) {
......@@ -30,8 +31,9 @@ func TestGetHooks(t *testing.T) {
{
name: "get hooks with release",
args: []string{"aeneas"},
expected: mockHookTemplate,
resp: releaseMock(&releaseOptions{name: "aeneas"}),
expected: helm.MockHookTemplate,
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})},
},
{
name: "get hooks without args",
......
......@@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestGetManifest(t *testing.T) {
......@@ -30,8 +31,9 @@ func TestGetManifest(t *testing.T) {
{
name: "get manifest with release",
args: []string{"juno"},
expected: mockManifest,
resp: releaseMock(&releaseOptions{name: "juno"}),
expected: helm.MockManifest,
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"}),
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"})},
},
{
name: "get manifest without args",
......
......@@ -23,15 +23,17 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestGetCmd(t *testing.T) {
tests := []releaseCase{
{
name: "get with a release",
resp: releaseMock(&releaseOptions{name: "thomas-guide"}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
args: []string{"thomas-guide"},
expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + mockHookTemplate + "\nMANIFEST:",
expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + helm.MockHookTemplate + "\nMANIFEST:",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})},
},
{
name: "get requires release name arg",
......
......@@ -23,15 +23,17 @@ import (
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestGetValuesCmd(t *testing.T) {
tests := []releaseCase{
{
name: "get values with a release",
resp: releaseMock(&releaseOptions{name: "thomas-guide"}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
args: []string{"thomas-guide"},
expected: "name: \"value\"",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})},
},
{
name: "get values requires release name arg",
......
......@@ -21,115 +21,30 @@ import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/helm/helmpath"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/repo"
)
var mockHookTemplate = `apiVersion: v1
kind: Job
metadata:
annotations:
"helm.sh/hooks": pre-install
`
var mockManifest = `apiVersion: v1
kind: Secret
metadata:
name: fixture
`
type releaseOptions struct {
name string
version int32
chart *chart.Chart
statusCode release.Status_Code
namespace string
}
func releaseMock(opts *releaseOptions) *release.Release {
date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0}
name := opts.name
if name == "" {
name = "testrelease-" + string(rand.Intn(100))
}
var version int32 = 1
if opts.version != 0 {
version = opts.version
}
namespace := opts.namespace
if namespace == "" {
namespace = "default"
}
ch := opts.chart
if opts.chart == nil {
ch = &chart.Chart{
Metadata: &chart.Metadata{
Name: "foo",
Version: "0.1.0-beta.1",
},
Templates: []*chart.Template{
{Name: "templates/foo.tpl", Data: []byte(mockManifest)},
},
}
}
scode := release.Status_DEPLOYED
if opts.statusCode > 0 {
scode = opts.statusCode
}
return &release.Release{
Name: name,
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: scode},
Description: "Release mock",
},
Chart: ch,
Config: &chart.Config{Raw: `name: "value"`},
Version: version,
Namespace: namespace,
Hooks: []*release.Hook{
{
Name: "pre-install-hook",
Kind: "Job",
Path: "pre-install-hook.yaml",
Manifest: mockHookTemplate,
LastRun: &date,
Events: []release.Hook_Event{release.Hook_PRE_INSTALL},
},
},
Manifest: mockManifest,
}
}
// releaseCmd is a command that works with a FakeClient
type releaseCmd func(c *helm.FakeClient, out io.Writer) *cobra.Command
// runReleaseCases runs a set of release cases through the given releaseCmd.
func runReleaseCases(t *testing.T, tests []releaseCase, rcmd releaseCmd) {
var buf bytes.Buffer
for _, tt := range tests {
c := &helm.FakeClient{
Rels: []*release.Release{tt.resp},
Rels: tt.rels,
}
cmd := rcmd(c, &buf)
cmd.ParseFlags(tt.flags)
......@@ -154,6 +69,8 @@ type releaseCase struct {
expected string
err bool
resp *release.Release
// Rels are the available releases at the start of the test.
rels []*release.Release
}
// tempHelmHome sets up a Helm Home in a temp dir.
......@@ -230,6 +147,7 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error {
t.Logf("$HELM_HOME has been configured at %s.\n", settings.Home.String())
return nil
}
func TestRootCmd(t *testing.T) {
......
......@@ -27,10 +27,10 @@ import (
func TestHistoryCmd(t *testing.T) {
mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release {
return releaseMock(&releaseOptions{
name: name,
version: vers,
statusCode: code,
return helm.ReleaseMock(&helm.MockReleaseOptions{
Name: name,
Version: vers,
StatusCode: code,
})
}
......
......@@ -36,7 +36,7 @@ func TestListCmd(t *testing.T) {
{
name: "with a release",
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}),
},
expected: "thomas-guide",
},
......@@ -44,7 +44,7 @@ func TestListCmd(t *testing.T) {
name: "list",
args: []string{},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "atlas"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}),
},
expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n",
},
......@@ -52,8 +52,8 @@ func TestListCmd(t *testing.T) {
name: "list, one deployed, one failed",
args: []string{"-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_FAILED}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_FAILED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expected: "thomas-guide\natlas-guide",
},
......@@ -61,8 +61,8 @@ func TestListCmd(t *testing.T) {
name: "with a release, multiple flags",
args: []string{"--deleted", "--deployed", "--failed", "-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETED}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
// Note: We're really only testing that the flags parsed correctly. Which results are returned
// depends on the backend. And until pkg/helm is done, we can't mock this.
......@@ -72,8 +72,8 @@ func TestListCmd(t *testing.T) {
name: "with a release, multiple flags",
args: []string{"--all", "-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETED}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
// See note on previous test.
expected: "thomas-guide\natlas-guide",
......@@ -82,8 +82,8 @@ func TestListCmd(t *testing.T) {
name: "with a release, multiple flags, deleting",
args: []string{"--all", "-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_DELETING}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETING}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
// See note on previous test.
expected: "thomas-guide\natlas-guide",
......@@ -92,8 +92,8 @@ func TestListCmd(t *testing.T) {
name: "namespace defined, multiple flags",
args: []string{"--all", "-q", "--namespace test123"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", namespace: "test123"}),
releaseMock(&releaseOptions{name: "atlas-guide", namespace: "test321"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Namespace: "test123"}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Namespace: "test321"}),
},
// See note on previous test.
expected: "thomas-guide",
......@@ -102,8 +102,8 @@ func TestListCmd(t *testing.T) {
name: "with a pending release, multiple flags",
args: []string{"--all", "-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_PENDING_INSTALL}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expected: "thomas-guide\natlas-guide",
},
......@@ -111,10 +111,10 @@ func TestListCmd(t *testing.T) {
name: "with a pending release, pending flag",
args: []string{"--pending", "-q"},
resp: []*release.Release{
releaseMock(&releaseOptions{name: "thomas-guide", statusCode: release.Status_PENDING_INSTALL}),
releaseMock(&releaseOptions{name: "wild-idea", statusCode: release.Status_PENDING_UPGRADE}),
releaseMock(&releaseOptions{name: "crazy-maps", statusCode: release.Status_PENDING_ROLLBACK}),
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "wild-idea", StatusCode: release.Status_PENDING_UPGRADE}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-maps", StatusCode: release.Status_PENDING_ROLLBACK}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
},
expected: "thomas-guide\nwild-idea\ncrazy-maps",
},
......
......@@ -107,7 +107,7 @@ func TestReset_deployedReleases(t *testing.T) {
var buf bytes.Buffer
resp := []*release.Release{
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
}
c := &helm.FakeClient{
Rels: resp,
......@@ -139,7 +139,7 @@ func TestReset_forceFlag(t *testing.T) {
var buf bytes.Buffer
resp := []*release.Release{
releaseMock(&releaseOptions{name: "atlas-guide", statusCode: release.Status_DEPLOYED}),
helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}),
}
c := &helm.FakeClient{
Rels: resp,
......
......@@ -52,7 +52,7 @@ func TestStatusCmd(t *testing.T) {
name: "get status of a deployed release",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\n"),
rel: releaseMockWithStatus(&release.Status{
rel: ReleaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
}),
},
......@@ -60,7 +60,7 @@ func TestStatusCmd(t *testing.T) {
name: "get status of a deployed release with notes",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nNOTES:\nrelease notes\n"),
rel: releaseMockWithStatus(&release.Status{
rel: ReleaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Notes: "release notes",
}),
......@@ -69,7 +69,7 @@ func TestStatusCmd(t *testing.T) {
name: "get status of a deployed release with resources",
args: []string{"flummoxed-chickadee"},
expected: outputWithStatus("DEPLOYED\n\nRESOURCES:\nresource A\nresource B\n\n"),
rel: releaseMockWithStatus(&release.Status{
rel: ReleaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
Resources: "resource A\nresource B\n",
}),
......@@ -82,7 +82,7 @@ func TestStatusCmd(t *testing.T) {
"TEST \tSTATUS \tINFO \tSTARTED \tCOMPLETED \n" +
fmt.Sprintf("test run 1\tSUCCESS \textra info\t%s\t%s\n", dateString, dateString) +
fmt.Sprintf("test run 2\tFAILURE \t \t%s\t%s\n", dateString, dateString)),
rel: releaseMockWithStatus(&release.Status{
rel: ReleaseMockWithStatus(&release.Status{
Code: release.Status_DEPLOYED,
LastTestSuiteRun: &release.TestSuite{
StartedAt: &date,
......@@ -138,7 +138,7 @@ func outputWithStatus(status string) string {
status)
}
func releaseMockWithStatus(status *release.Status) *release.Release {
func ReleaseMockWithStatus(status *release.Status) *release.Release {
return &release.Release{
Name: "flummoxed-chickadee",
Info: &release.Info{
......
......@@ -28,6 +28,7 @@ import (
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release"
)
func TestUpgradeCmd(t *testing.T) {
......@@ -43,9 +44,9 @@ func TestUpgradeCmd(t *testing.T) {
t.Errorf("Error creating chart for upgrade: %v", err)
}
ch, _ := chartutil.Load(chartPath)
_ = releaseMock(&releaseOptions{
name: "funny-bunny",
chart: ch,
_ = helm.ReleaseMock(&helm.MockReleaseOptions{
Name: "funny-bunny",
Chart: ch,
})
// update chart version
......@@ -94,61 +95,68 @@ func TestUpgradeCmd(t *testing.T) {
{
name: "upgrade a release",
args: []string{"funny-bunny", chartPath},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 2, chart: ch}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch})},
},
{
name: "upgrade a release with timeout",
args: []string{"funny-bunny", chartPath},
flags: []string{"--timeout", "120"},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 3, chart: ch2}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2})},
},
{
name: "upgrade a release with --reset-values",
args: []string{"funny-bunny", chartPath},
flags: []string{"--reset-values", "true"},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 4, chart: ch2}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2})},
},
{
name: "upgrade a release with --reuse-values",
args: []string{"funny-bunny", chartPath},
flags: []string{"--reuse-values", "true"},
resp: releaseMock(&releaseOptions{name: "funny-bunny", version: 5, chart: ch2}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2}),
expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2})},
},
{
name: "install a release with 'upgrade --install'",
args: []string{"zany-bunny", chartPath},
flags: []string{"-i"},
resp: releaseMock(&releaseOptions{name: "zany-bunny", version: 1, chart: ch}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch}),
expected: "Release \"zany-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch})},
},
{
name: "install a release with 'upgrade --install' and timeout",
args: []string{"crazy-bunny", chartPath},
flags: []string{"-i", "--timeout", "120"},
resp: releaseMock(&releaseOptions{name: "crazy-bunny", version: 1, chart: ch}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch}),
expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch})},
},
{
name: "upgrade a release with wait",
args: []string{"crazy-bunny", chartPath},
flags: []string{"--wait"},
resp: releaseMock(&releaseOptions{name: "crazy-bunny", version: 2, chart: ch2}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2}),
expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n",
rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2})},
},
{
name: "upgrade a release with missing dependencies",
args: []string{"bonkers-bunny", missingDepsPath},
resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "bonkers-bunny", Version: 1, Chart: ch3}),
err: true,
},
{
name: "upgrade a release with bad dependencies",
args: []string{"bonkers-bunny", badDepsPath},
resp: releaseMock(&releaseOptions{name: "bonkers-bunny", version: 1, chart: ch3}),
resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "bonkers-bunny", Version: 1, Chart: ch3}),
err: true,
},
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment