Skip to content

Commit

Permalink
Improve contract test helper
Browse files Browse the repository at this point in the history
Signed-off-by: jose.vazquez <[email protected]>
  • Loading branch information
josvazg committed Nov 12, 2024
1 parent a7e4743 commit 41fd4b1
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 120 deletions.
18 changes: 16 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ CONTAINER_SPEC=.spec.template.spec.containers[0]

SILK_ASSET_GROUP="atlas-kubernetes-operator"

HELM_REPO_URL = "https://mongodb.github.io/helm-charts"
HELM_AKO_INSTALL_NAME = local-ako-install
HELM_AKO_NAMESPACE = test-ako

.DEFAULT_GOAL := help
.PHONY: help
help: ## Show this help screen
Expand Down Expand Up @@ -567,7 +571,17 @@ download-from-silk: ## Download the latest augmented SBOM for a given architectu
store-silk-sboms: download-from-silk ## Download & Store the latest augmented SBOM for a given version & architecture
SILK_ASSET_GROUP=$(SILK_ASSET_GROUP) ./scripts/store-sbom-in-s3.sh $(VERSION) $(TARGET_ARCH)

.PHONY: install-ako-helm
install-ako-helm:
helm repo add mongodb $(HELM_REPO_URL)
helm upgrade $(HELM_AKO_INSTALL_NAME) mongodb/mongodb-atlas-operator --atomic --install \
--set-string atlasURI=$(MCLI_OPS_MANAGER_URL) \
--set objectDeletionProtection=false \
--set subobjectDeletionProtection=false \
--namespace=$(HELM_AKO_NAMESPACE) --create-namespace
kubectl get crds

.PHONY: contract-tests
contract-tests: run-kind ## Run contract tests
contract-tests: run-kind install-ako-helm ## Run contract tests with support by a k8s cluster and AKO
go clean -testcache
AKO_CONTRACT_TEST=1 go test -v -race -cover ./test/contract/...
AKO_CONTRACT_TEST=1 HELM_AKO_NAMESPACE=$(HELM_AKO_NAMESPACE) go test -v -race -cover ./test/contract/...
35 changes: 13 additions & 22 deletions test/contract/audit/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,29 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/audit"
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/contract"
)

func TestDefaultAuditingGet(t *testing.T) {
contract.RunContractTest(t, "get default auditing", func(ct *contract.ContractTest) {
ctx := context.Background()
contract.RunGoContractTest(ctx, t, "get default auditing", func(ch contract.ContractHelper) {
projectName := "default-auditing-project"
ct.AddResources(time.Minute, contract.DefaultAtlasProject(projectName))
testProjectID := mustReadProjectID(t, ct.Ctx, ct.K8sClient, ct.Namespace(), projectName)
as := audit.NewAuditLog(ct.AtlasClient.AuditingApi)
require.NoError(t, ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject(projectName)))
testProjectID, err := ch.ProjectID(ctx, projectName)
require.NoError(t, err)
as := audit.NewAuditLog(ch.AtlasClient().AuditingApi)

result, err := as.Get(ct.Ctx, testProjectID)
result, err := as.Get(ctx, testProjectID)
require.NoError(t, err)
assert.Equal(t, audit.NewAuditConfig(nil), result)
})
}

func TestSyncs(t *testing.T) {
ctx := context.Background()
testCases := []struct {
title string
auditing *audit.AuditConfig
Expand Down Expand Up @@ -86,12 +87,13 @@ func TestSyncs(t *testing.T) {
),
},
}
contract.RunContractTest(t, "test syncs", func(ct *contract.ContractTest) {
contract.RunGoContractTest(ctx, t, "test syncs", func(ch contract.ContractHelper) {
projectName := "audit-syncs-project"
ct.AddResources(time.Minute, contract.DefaultAtlasProject(projectName))
testProjectID := mustReadProjectID(t, ct.Ctx, ct.K8sClient, ct.Namespace(), projectName)
require.NoError(t, ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject(projectName)))
testProjectID, err := ch.ProjectID(ctx, projectName)
require.NoError(t, err)
ctx := context.Background()
as := audit.NewAuditLog(ct.AtlasClient.AuditingApi)
as := audit.NewAuditLog(ch.AtlasClient().AuditingApi)

for _, tc := range testCases {
t.Run(tc.title, func(t *testing.T) {
Expand All @@ -105,14 +107,3 @@ func TestSyncs(t *testing.T) {
}
})
}

func mustReadProjectID(t *testing.T, ctx context.Context, k8sClient client.Client, ns, projectName string) string {
t.Helper()
project := akov2.AtlasProject{}
key := types.NamespacedName{
Namespace: ns,
Name: projectName,
}
require.NoError(t, k8sClient.Get(ctx, key, &project))
return project.Status.ID
}
49 changes: 0 additions & 49 deletions test/helper/contract/ako.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ package contract
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"strings"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
corev1 "k8s.io/api/core/v1"
Expand All @@ -17,10 +14,6 @@ import (
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas"
)

const (
mongodbRepoURL = "https://mongodb.github.io/helm-charts"
)

func DefaultAtlasProject(name string) client.Object {
return &akov2.AtlasProject{
ObjectMeta: metav1.ObjectMeta{Name: name},
Expand Down Expand Up @@ -51,41 +44,6 @@ func mustCreateVersionedAtlasClient(ctx context.Context) *admin.APIClient {
return client
}

func ensureTestAtlasOperator(namespace string) error {
if !isTestAtlasOperatorInstalled(namespace) {
return installTestAtlasOperator(namespace)
}
return nil
}

func isTestAtlasOperatorInstalled(namespace string) bool {
output, err := run("helm", "ls", "-n", namespace)
if err != nil {
return false
}
return strings.Contains(string(output), operatorInstallName)
}

func installTestAtlasOperator(namespace string) error {
if _, err := run("helm", "repo", "add", "mongodb", mongodbRepoURL); err != nil {
return fmt.Errorf("failed to set mongodb repo to URL %q: %w", mongodbRepoURL, err)
}
domain := os.Getenv("MCLI_OPS_MANAGER_URL")
args := []string{
"install",
operatorInstallName,
"mongodb/mongodb-atlas-operator",
"--atomic",
"--set-string", fmt.Sprintf("atlasURI=%s", domain),
"--set", "objectDeletionProtection=false",
"--set", "subobjectDeletionProtection=false",
"--namespace=" + namespace,
"--create-namespace",
}
_, err := run("helm", args...)
return err
}

func globalSecret(namespace string) client.Object {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -102,10 +60,3 @@ func globalSecret(namespace string) client.Object {
},
}
}

func run(cmd string, args ...string) ([]byte, error) {
log.Printf("Running:\n%s %s", cmd, strings.Join(args, " "))
output, err := exec.Command(cmd, args...).CombinedOutput()
log.Printf("%s", string(output))
return output, err
}
105 changes: 66 additions & 39 deletions test/helper/contract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,111 @@ package contract

import (
"context"
"fmt"
"os"
"testing"
"time"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/stretchr/testify/require"

akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/control"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/utils"
)

const (
operatorInstallName = "test-atlas-operator"
akoTestNamespace = "ako-test"
)

type ContractTest struct {
t *testing.T
akoInstalled bool
namespace string
resources []client.Object
type ContractHelper interface {
AtlasClient() *admin.APIClient
AddResources(ctx context.Context, timeout time.Duration, resources ...client.Object) error
ProjectID(ctx context.Context, projectName string) (string, error)
}

Ctx context.Context
AtlasClient *admin.APIClient
K8sClient client.Client
type contractTest struct {
credentials bool
namespace string
resources []client.Object
k8sClient client.Client
atlasClient *admin.APIClient
}

func (ct *ContractTest) cleanup() {
func (ct *contractTest) cleanup(ctx context.Context) error {
for i := len(ct.resources) - 1; i >= 0; i-- {
resource := ct.resources[i]
require.NoError(ct.t, ct.K8sClient.Delete(ct.Ctx, resource))
if err := ct.k8sClient.Delete(ctx, resource); err != nil {
return fmt.Errorf("failed to delete contract test pre-set resource: %w", err)
}
}
ct.resources = []client.Object{}
if ct.namespace != "" {
require.NoError(ct.t, ct.K8sClient.Delete(ct.Ctx, defaultNamespace(ct.namespace)))
if err := ct.k8sClient.Delete(ctx, defaultNamespace(ct.namespace)); err != nil {
return fmt.Errorf("failed to delete namespace %q: %w", ct.namespace, err)
}
}
return nil
}

func RunContractTest(t *testing.T, name string, ctFn func(ct *ContractTest)) {
t.Helper()
func RunGoContractTest(ctx context.Context, t *testing.T, name string, contractTest func(ch ContractHelper)) {
if !control.Enabled("AKO_CONTRACT_TEST") {
t.Skip("Skipping contract test as AKO_CONTRACT_TEST is unset")
return
}
ctx := context.Background()
ct := &ContractTest{
t: t,
Ctx: ctx,
K8sClient: mustCreateK8sClient(),
akoInstalled: false,
resources: []client.Object{},
AtlasClient: mustCreateVersionedAtlasClient(ctx),
}
defer ct.cleanup()
ct := newContractTest(ctx)
defer func() {
require.NoError(t, ct.cleanup(ctx))
}()
t.Run(name, func(t *testing.T) {
ctFn(ct)
contractTest(ct)
})
}

func (ct *ContractTest) AddResources(timeout time.Duration, resources ...client.Object) {
if !ct.akoInstalled {
require.NoError(ct.t, ensureTestAtlasOperator(akoTestNamespace))
require.NoError(ct.t, k8sRecreate(ct.Ctx, ct.K8sClient, globalSecret(akoTestNamespace)))
ct.akoInstalled = true
func newContractTest(ctx context.Context) *contractTest {
return &contractTest{
k8sClient: mustCreateK8sClient(),
credentials: false,
resources: []client.Object{},
atlasClient: mustCreateVersionedAtlasClient(ctx),
}
}

func (ct *contractTest) AtlasClient() *admin.APIClient {
return ct.atlasClient
}

func (ct *contractTest) AddResources(ctx context.Context, timeout time.Duration, resources ...client.Object) error {
if !ct.credentials {
akoTestNamespace := os.Getenv("HELM_AKO_NAMESPACE")
if err := k8sRecreate(ctx, ct.k8sClient, globalSecret(akoTestNamespace)); err != nil {
return fmt.Errorf("failed to set AKO namespace: %w", err)
}
ct.credentials = true
}
if ct.namespace == "" {
ct.namespace = utils.RandomName("test-ns")
require.NoError(ct.t, ct.K8sClient.Create(ct.Ctx, defaultNamespace(ct.namespace)))
if err := ct.k8sClient.Create(ctx, defaultNamespace(ct.namespace)); err != nil {
return fmt.Errorf("failed to create test namespace: %w", err)
}
}
for _, resource := range resources {
resource.SetNamespace(ct.namespace)
require.NoError(ct.t, ct.K8sClient.Create(ct.Ctx, resource))
if err := ct.k8sClient.Create(ctx, resource); err != nil {
return fmt.Errorf("failed to create resource: %w", err)
}
}
ct.resources = append(ct.resources, resources...)
require.NoError(ct.t, waitForReadyStatus(ct.K8sClient, resources, timeout))
if err := waitForReadyStatus(ct.k8sClient, resources, timeout); err != nil {
return fmt.Errorf("failed to reach READY status: %w", err)
}
return nil
}

func (ct *ContractTest) Namespace() string {
return ct.namespace
func (ct *contractTest) ProjectID(ctx context.Context, projectName string) (string, error) {
project := akov2.AtlasProject{}
key := types.NamespacedName{Namespace: ct.namespace, Name: projectName}
if err := ct.k8sClient.Get(ctx, key, &project); err != nil {
return "", fmt.Errorf("failed to get project ID: %w", err)
}
return project.Status.ID, nil
}
20 changes: 12 additions & 8 deletions test/helper/contract/contract_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package contract_test

import (
"context"
"fmt"
"os"
"strings"
Expand All @@ -19,17 +20,19 @@ const (
)

func TestContractTestSkip(t *testing.T) {
ctx := context.Background()
testWithEnv(func() {
contract.RunContractTest(t, "Skip contract test", func(_ *contract.ContractTest) {
contract.RunGoContractTest(ctx, t, "Skip contract test", func(_ contract.ContractHelper) {
panic("should not have got here!")
})
}, "-AKO_CONTRACT_TEST")
}

func TestContractTestClientSetFails(t *testing.T) {
ctx := context.Background()
testWithEnv(func() {
assert.Panics(t, func() {
contract.RunContractTest(t, "bad client settings panics", func(_ *contract.ContractTest) {})
contract.RunGoContractTest(ctx, t, "bad client settings panics", func(_ contract.ContractHelper) {})
})
},
"AKO_CONTRACT_TEST=1",
Expand All @@ -39,14 +42,15 @@ func TestContractTestClientSetFails(t *testing.T) {
}

func TestContractsWithResources(t *testing.T) {
contract.RunContractTest(t, "run contract test list projects", func(ct *contract.ContractTest) {
ct.AddResources(time.Minute, contract.DefaultAtlasProject("contract-tests-list-projects"))
_, _, err := ct.AtlasClient.ProjectsApi.ListProjects(ct.Ctx).Execute()
ctx := context.Background()
contract.RunGoContractTest(ctx, t, "run contract test list projects", func(ch contract.ContractHelper) {
ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject("contract-tests-list-projects"))
_, _, err := ch.AtlasClient().ProjectsApi.ListProjects(ctx).Execute()
assert.NoError(t, err)
})
contract.RunContractTest(t, "run contract test list orgs", func(ct *contract.ContractTest) {
ct.AddResources(time.Minute, contract.DefaultAtlasProject("contract-tests-list-orgs"))
_, _, err := ct.AtlasClient.OrganizationsApi.ListOrganizations(ct.Ctx).Execute()
contract.RunGoContractTest(ctx, t, "run contract test list orgs", func(ch contract.ContractHelper) {
ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject("contract-tests-list-orgs"))
_, _, err := ch.AtlasClient().OrganizationsApi.ListOrganizations(ctx).Execute()
assert.NoError(t, err)
})
}
Expand Down

0 comments on commit 41fd4b1

Please sign in to comment.