mirror of
https://github.com/ngrok/ngrok-operator.git
synced 2026-05-17 16:50:44 +00:00
chore(testing): Add tests for Domain Reconciler (#646)
* chore(mocks): Generate mocks with consistent prefix for .gitattributes * feat(testing): Create a mock domain client * feat(testing): Add tests for domain controller * chore(domain-controller): Use k8s ptr library for comparison of CNAME target
This commit is contained in:
+1
-3
@@ -15,6 +15,4 @@ manifest-bundle.yaml linguist-generated=true
|
||||
helm/ngrok-operator/values.schema.json linguist-generated=true
|
||||
|
||||
# Generated by mockgen
|
||||
internal/mocks/conn.go linguist-generated=true
|
||||
internal/mocks/dialer.go linguist-generated=true
|
||||
internal/mocks/tunnel.go linguist-generated=true
|
||||
internal/mocks/mock_*.go linguist-generated=true
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||
@@ -126,7 +127,7 @@ func (d *Domain) Equal(ngrokDomain *ngrok.ReservedDomain) bool {
|
||||
d.Status.Region == ngrokDomain.Region &&
|
||||
d.Status.Domain == ngrokDomain.Domain &&
|
||||
d.Status.URI == ngrokDomain.URI &&
|
||||
d.Status.CNAMETarget == ngrokDomain.CNAMETarget &&
|
||||
ptr.Equal(d.Status.CNAMETarget, ngrokDomain.CNAMETarget) &&
|
||||
d.Spec.Description == ngrokDomain.Description &&
|
||||
d.Spec.Metadata == ngrokDomain.Metadata
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ require (
|
||||
github.com/ngrok/ngrok-api-go/v7 v7.3.0
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.36.3
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/mock v0.5.2
|
||||
@@ -112,6 +113,7 @@ require (
|
||||
)
|
||||
|
||||
tool (
|
||||
github.com/onsi/ginkgo/v2/ginkgo
|
||||
go.uber.org/mock/mockgen
|
||||
sigs.k8s.io/controller-runtime/tools/setup-envtest
|
||||
sigs.k8s.io/controller-tools/cmd/controller-gen
|
||||
|
||||
@@ -475,6 +475,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
"github.com/ngrok/ngrok-operator/internal/controller"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var _ = Describe("DomainReconciler", func() {
|
||||
const (
|
||||
timeout = 10 * time.Second
|
||||
duration = 10 * time.Second
|
||||
interval = 250 * time.Millisecond
|
||||
NgrokManagedDomainSuffix = "ngrok.app"
|
||||
CustomDomainSuffix = "custom-domain.xyz"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx context.Context
|
||||
namespace string = "default"
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = GinkgoT().Context()
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// List all domain CRs in the env test cluster
|
||||
domains := &ingressv1alpha1.DomainList{}
|
||||
err := k8sClient.List(ctx, domains)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// Delete all domain CRs in the env test cluster
|
||||
for _, d := range domains.Items {
|
||||
Expect(k8sClient.Delete(ctx, &d)).To(Succeed())
|
||||
}
|
||||
|
||||
// Eventually, listing all the domain CRs should return an empty list because we
|
||||
// deleted all of them and the finalizer should have cleaned up the ngrok domains.
|
||||
Eventually(func(g Gomega) {
|
||||
domains := &ingressv1alpha1.DomainList{}
|
||||
err := k8sClient.List(ctx, domains)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(domains.Items).To(BeEmpty())
|
||||
}, timeout, interval).Should(Succeed())
|
||||
|
||||
// Reset the internal state of the domain client between tests
|
||||
// to ensure that each test starts with a clean slate.
|
||||
domainClient.Reset()
|
||||
})
|
||||
|
||||
Describe("CreateDomain", func() {
|
||||
var (
|
||||
createDomainErr error
|
||||
domainSuffix string
|
||||
domainName string
|
||||
domain *ingressv1alpha1.Domain
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
domain = &ingressv1alpha1.Domain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-domain",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: ingressv1alpha1.DomainSpec{
|
||||
Domain: domainName,
|
||||
},
|
||||
}
|
||||
createDomainErr = k8sClient.Create(ctx, domain)
|
||||
})
|
||||
|
||||
When("the domain is a ngrok managed domain", func() {
|
||||
BeforeEach(func() {
|
||||
domainSuffix = NgrokManagedDomainSuffix
|
||||
domainName = fmt.Sprintf("test-domain-%s.%s", rand.String(10), domainSuffix)
|
||||
})
|
||||
|
||||
When("the domain does not exist in ngrok", func() {
|
||||
It("should create the domain in ngrok", func() {
|
||||
Expect(createDomainErr).ToNot(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
foundDomain := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(domain), foundDomain)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(foundDomain.Status.ID).To(MatchRegexp("^rd"))
|
||||
g.Expect(foundDomain.Status.Domain).To(Equal(domainName))
|
||||
g.Expect(foundDomain.Status.CNAMETarget).To(BeNil())
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain already exists in ngrok", func() {
|
||||
var (
|
||||
existingDomain *ngrok.ReservedDomain
|
||||
preCreateDomainErr error
|
||||
)
|
||||
BeforeEach(func() {
|
||||
existingDomain, preCreateDomainErr = domainClient.Create(ctx, &ngrok.ReservedDomainCreate{Domain: domainName})
|
||||
Expect(preCreateDomainErr).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should use the existing domain in ngrok", func() {
|
||||
Expect(createDomainErr).ToNot(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
foundDomain := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(domain), foundDomain)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(foundDomain.Status.ID).To(Equal(existingDomain.ID))
|
||||
g.Expect(foundDomain.Status.Domain).To(Equal(domainName))
|
||||
g.Expect(foundDomain.Status.CNAMETarget).To(BeNil())
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain is a custom domain", func() {
|
||||
BeforeEach(func() {
|
||||
domainSuffix = CustomDomainSuffix
|
||||
domainName = fmt.Sprintf("test-domain-%s.%s", rand.String(10), domainSuffix)
|
||||
})
|
||||
|
||||
When("the domain does not exist in ngrok", func() {
|
||||
It("should create the domain in ngrok", func() {
|
||||
Expect(createDomainErr).ToNot(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
foundDomain := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(domain), foundDomain)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(foundDomain.Status.ID).To(MatchRegexp("^rd"))
|
||||
g.Expect(foundDomain.Status.Domain).To(Equal(domainName))
|
||||
g.Expect(foundDomain.Status.CNAMETarget).ToNot(BeNil())
|
||||
g.Expect(*foundDomain.Status.CNAMETarget).To(MatchRegexp("\\.ngrok-cname\\.com$"))
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain already exists in ngrok", func() {
|
||||
var (
|
||||
existingDomain *ngrok.ReservedDomain
|
||||
preCreateDomainErr error
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
existingDomain, preCreateDomainErr = domainClient.Create(ctx, &ngrok.ReservedDomainCreate{Domain: domainName})
|
||||
Expect(preCreateDomainErr).ToNot(HaveOccurred())
|
||||
|
||||
GinkgoLogr.Info("Pre-created domain", "domain", existingDomain.Domain, "id", existingDomain.ID)
|
||||
})
|
||||
|
||||
It("should use the existing domain in ngrok", func() {
|
||||
Expect(createDomainErr).ToNot(HaveOccurred())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
foundDomain := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(domain), foundDomain)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(foundDomain.Status.ID).To(Equal(existingDomain.ID))
|
||||
g.Expect(foundDomain.Status.Domain).To(Equal(domainName))
|
||||
g.Expect(foundDomain.Status.CNAMETarget).ToNot(BeNil())
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("UpdateDomain", func() {
|
||||
var (
|
||||
domainName string
|
||||
domain *ingressv1alpha1.Domain
|
||||
objKey client.ObjectKey
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
name := fmt.Sprintf("test-domain-%s", rand.String(10))
|
||||
domainName = fmt.Sprintf("test-domain-%s.%s", rand.String(10), NgrokManagedDomainSuffix)
|
||||
objKey = client.ObjectKey{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}
|
||||
domain = &ingressv1alpha1.Domain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: ingressv1alpha1.DomainSpec{
|
||||
Domain: domainName,
|
||||
},
|
||||
}
|
||||
domain.Spec.Metadata = "starting metadata"
|
||||
domain.Spec.Description = "starting description"
|
||||
|
||||
Expect(k8sClient.Create(ctx, domain)).To(Succeed())
|
||||
})
|
||||
|
||||
It("updates the domain metadata", func() {
|
||||
patch := client.MergeFrom(domain.DeepCopy())
|
||||
domain.Spec.Metadata = "updated metadata"
|
||||
Expect(k8sClient.Patch(ctx, domain, patch)).To(Succeed())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
d := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, objKey, d)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(d.Spec.Metadata).To(Equal("updated metadata"))
|
||||
g.Expect(d.Status.ID).ToNot(BeEmpty())
|
||||
g.Expect(d.Status.Domain).To(Equal(domainName))
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
|
||||
It("updates the domain description", func() {
|
||||
patch := client.MergeFrom(domain.DeepCopy())
|
||||
domain.Spec.Description = "updated description"
|
||||
Expect(k8sClient.Patch(ctx, domain, patch)).To(Succeed())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
d := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, objKey, d)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(d.Spec.Description).To(Equal("updated description"))
|
||||
g.Expect(d.Status.ID).ToNot(BeEmpty())
|
||||
g.Expect(d.Status.Domain).To(Equal(domainName))
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
|
||||
When("the domain was manually deleted in ngrok", func() {
|
||||
var (
|
||||
previousID string
|
||||
)
|
||||
BeforeEach(func() {
|
||||
Eventually(func(g Gomega) {
|
||||
d := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, objKey, d)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(d.Status.ID).ToNot(BeEmpty())
|
||||
g.Expect(d.Status.Domain).To(Equal(domainName))
|
||||
|
||||
previousID = d.Status.ID
|
||||
g.Expect(domainClient.Delete(ctx, previousID)).To(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
It("should create a new domain in ngrok", func() {
|
||||
// Simulate a manual reconcile by adding an annotation
|
||||
patch := client.MergeFrom(domain.DeepCopy())
|
||||
controller.AddAnnotations(domain, map[string]string{
|
||||
"manual-reconcile": "true",
|
||||
})
|
||||
Expect(k8sClient.Patch(ctx, domain, patch)).To(Succeed())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
d := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, objKey, d)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(d.Status.ID).ToNot(BeEmpty())
|
||||
g.Expect(d.Status.ID).ToNot(Equal(previousID))
|
||||
g.Expect(d.Status.Domain).To(Equal(domainName))
|
||||
g.Expect(d.Status.CNAMETarget).To(BeNil())
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DeleteDomain", func() {
|
||||
var (
|
||||
reclaimPolicy ingressv1alpha1.DomainReclaimPolicy
|
||||
domain *ingressv1alpha1.Domain
|
||||
|
||||
ngrokDomainID string
|
||||
deleteDomainInNgrokBeforeK8s bool
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
By("Creating the domain")
|
||||
domain = &ingressv1alpha1.Domain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-domain",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: ingressv1alpha1.DomainSpec{
|
||||
ReclaimPolicy: reclaimPolicy,
|
||||
Domain: fmt.Sprintf("test-domain-%s.%s", rand.String(10), NgrokManagedDomainSuffix),
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, domain)).To(Succeed())
|
||||
|
||||
By("Waiting for the domain to be created in ngrok")
|
||||
Eventually(func(g Gomega) {
|
||||
d := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(domain), d)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
g.Expect(d.Status.ID).ToNot(BeEmpty())
|
||||
g.Expect(d.Status.Domain).To(Equal(domain.Spec.Domain))
|
||||
|
||||
ngrokDomainID = d.Status.ID
|
||||
}, timeout, interval).Should(Succeed())
|
||||
|
||||
if deleteDomainInNgrokBeforeK8s {
|
||||
By("Deleting the domain in ngrok")
|
||||
// Simulate the domain being deleted in ngrok
|
||||
Expect(domainClient.Delete(ctx, ngrokDomainID)).To(Succeed())
|
||||
}
|
||||
|
||||
By("Deleting the domain CR in k8s")
|
||||
Expect(k8sClient.Delete(ctx, domain)).To(Succeed())
|
||||
|
||||
By("Waiting for the domain to be deleted in kubernetes")
|
||||
Eventually(func(g Gomega) {
|
||||
d := &ingressv1alpha1.Domain{}
|
||||
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(domain), d)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
|
||||
}, timeout, interval).Should(Succeed())
|
||||
})
|
||||
|
||||
When("The domain exists in ngrok", func() {
|
||||
When("The reclaim policy is set to delete", func() {
|
||||
BeforeEach(func() {
|
||||
reclaimPolicy = ingressv1alpha1.DomainReclaimPolicyDelete
|
||||
})
|
||||
|
||||
It("should delete the domain in ngrok", func() {
|
||||
rd, err := domainClient.Get(ctx, ngrokDomainID)
|
||||
Expect(ngrok.IsNotFound(err)).To(BeTrue())
|
||||
Expect(rd).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
When("The reclaim policy is set to retain", func() {
|
||||
BeforeEach(func() {
|
||||
reclaimPolicy = ingressv1alpha1.DomainReclaimPolicyRetain
|
||||
})
|
||||
|
||||
It("should not delete the domain in ngrok", func() {
|
||||
rd, err := domainClient.Get(ctx, ngrokDomainID)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(rd).ToNot(BeNil())
|
||||
Expect(rd.ID).To(Equal(ngrokDomainID))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("The domain does not exist in ngrok", func() {
|
||||
BeforeEach(func() {
|
||||
deleteDomainInNgrokBeforeK8s = true
|
||||
})
|
||||
|
||||
When("The reclaim policy is set to delete", func() {
|
||||
BeforeEach(func() {
|
||||
reclaimPolicy = ingressv1alpha1.DomainReclaimPolicyDelete
|
||||
})
|
||||
|
||||
It("The domain should be deleted in ngrok", func() {
|
||||
rd, err := domainClient.Get(ctx, ngrokDomainID)
|
||||
Expect(ngrok.IsNotFound(err)).To(BeTrue())
|
||||
Expect(rd).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
When("The reclaim policy is set to retain", func() {
|
||||
BeforeEach(func() {
|
||||
reclaimPolicy = ingressv1alpha1.DomainReclaimPolicyRetain
|
||||
})
|
||||
|
||||
It("The domain is still missing in ngrok", func() {
|
||||
iter := domainClient.List(&ngrok.Paging{})
|
||||
for iter.Next(ctx) {
|
||||
d := iter.Item()
|
||||
if d.Domain == domain.Spec.Domain {
|
||||
Fail("Domain should not exist in ngrok")
|
||||
}
|
||||
}
|
||||
Expect(iter.Err()).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,19 +1,12 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestControllers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Controllers package Test Suite")
|
||||
}
|
||||
|
||||
var _ = Describe("HTTPSEdgeController", func() {
|
||||
DescribeTable("isMigratingAuthProviders", func(current *ngrok.HTTPSEdgeRoute, desired *ingressv1alpha1.HTTPSEdgeRouteSpec, expected bool) {
|
||||
Expect(isMigratingAuthProviders(current, desired)).To(Equal(expected))
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 ngrok, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/ngrok/ngrok-operator/internal/mocks/nmockapi"
|
||||
"github.com/ngrok/ngrok-operator/internal/testutils"
|
||||
"github.com/ngrok/ngrok-operator/pkg/managerdriver"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1"
|
||||
ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var (
|
||||
cfg *rest.Config
|
||||
k8sClient client.Client
|
||||
testEnv *envtest.Environment
|
||||
driver *managerdriver.Driver
|
||||
domainClient *nmockapi.DomainClient
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
|
||||
func TestControllers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecs(t, "Controller Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
|
||||
|
||||
ctx, cancel = context.WithCancel(GinkgoT().Context())
|
||||
|
||||
By("bootstrapping test environment")
|
||||
operatorAPIs := filepath.Join("..", "..", "..", "helm", "ngrok-operator", "templates", "crds")
|
||||
gwAPIs := filepath.Join(".", "testdata", "gatewayapi-crds.yaml")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{operatorAPIs, gwAPIs},
|
||||
}
|
||||
|
||||
var err error
|
||||
// cfg is defined in this file globally.
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cfg).NotTo(BeNil())
|
||||
|
||||
schemeAdders := []func(*runtime.Scheme) error{
|
||||
scheme.AddToScheme,
|
||||
ngrokv1alpha1.AddToScheme,
|
||||
ingressv1alpha1.AddToScheme,
|
||||
gatewayv1.Install,
|
||||
gatewayv1alpha2.Install,
|
||||
}
|
||||
for _, addFunc := range schemeAdders {
|
||||
Expect(addFunc(scheme.Scheme)).NotTo(HaveOccurred())
|
||||
}
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sClient).NotTo(BeNil())
|
||||
|
||||
driver = managerdriver.NewDriver(
|
||||
logr.New(logr.Discard().GetSink()),
|
||||
scheme.Scheme,
|
||||
testutils.DefaultControllerName,
|
||||
types.NamespacedName{
|
||||
Name: "test-manager-name",
|
||||
Namespace: "test-manager-namespace",
|
||||
},
|
||||
managerdriver.WithGatewayEnabled(true),
|
||||
managerdriver.WithSyncAllowConcurrent(true),
|
||||
)
|
||||
|
||||
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
Metrics: server.Options{
|
||||
// Set to 0 to disable the metrics server for tests
|
||||
BindAddress: "0",
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sManager).NotTo(BeNil())
|
||||
|
||||
domainClient = nmockapi.NewDomainClient()
|
||||
|
||||
err = (&DomainReconciler{
|
||||
Client: k8sManager.GetClient(),
|
||||
Log: logf.Log.WithName("controllers").WithName("Domain"),
|
||||
Recorder: k8sManager.GetEventRecorderFor("domain-controller"),
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
DomainsClient: domainClient,
|
||||
}).SetupWithManager(k8sManager)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
err = k8sManager.Start(ctx)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}()
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
cancel()
|
||||
err := testEnv.Stop()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
@@ -1,7 +1,10 @@
|
||||
package mocks
|
||||
|
||||
//go:generate go tool go.uber.org/mock/mockgen -package mocks -destination conn.go net Conn
|
||||
// Note: Generate the mock files with names like mock_*.go. This is so that
|
||||
// the generated files are picked up by the .gitattributes file.
|
||||
|
||||
//go:generate go tool go.uber.org/mock/mockgen -package mocks -destination tunnel.go golang.ngrok.com/ngrok Tunnel
|
||||
//go:generate go tool go.uber.org/mock/mockgen -package mocks -destination mock_conn.go net Conn
|
||||
|
||||
//go:generate go tool go.uber.org/mock/mockgen -package mocks -destination dialer.go github.com/ngrok/ngrok-operator/pkg/tunneldriver Dialer
|
||||
//go:generate go tool go.uber.org/mock/mockgen -package mocks -destination mock_tunnel.go golang.ngrok.com/ngrok Tunnel
|
||||
|
||||
//go:generate go tool go.uber.org/mock/mockgen -package mocks -destination mock_dialer.go github.com/ngrok/ngrok-operator/pkg/tunneldriver Dialer
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package mocks -destination conn.go net Conn
|
||||
// mockgen -package mocks -destination mock_conn.go net Conn
|
||||
//
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package mocks -destination dialer.go github.com/ngrok/ngrok-operator/pkg/tunneldriver Dialer
|
||||
// mockgen -package mocks -destination mock_dialer.go github.com/ngrok/ngrok-operator/pkg/tunneldriver Dialer
|
||||
//
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package mocks -destination tunnel.go golang.ngrok.com/ngrok Tunnel
|
||||
// mockgen -package mocks -destination mock_tunnel.go golang.ngrok.com/ngrok Tunnel
|
||||
//
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
@@ -0,0 +1,76 @@
|
||||
package nmockapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
type baseClient[T any] struct {
|
||||
idPrefix string
|
||||
items map[string]T
|
||||
}
|
||||
|
||||
func newBase[T any](idPrefix string) baseClient[T] {
|
||||
return baseClient[T]{
|
||||
items: make(map[string]T),
|
||||
idPrefix: idPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) Get(_ context.Context, id string) (T, error) {
|
||||
item, ok := m.items[id]
|
||||
if !ok {
|
||||
return *new(T), m.notFoundErr()
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) List(_ *ngrok.Paging) ngrok.Iter[T] {
|
||||
items := slices.Collect(maps.Values(m.items))
|
||||
return NewIter(items, nil)
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) Delete(ctx context.Context, id string) error {
|
||||
_, err := m.Get(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(m.items, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset clears the items in the client.
|
||||
// This is useful for resetting the state of the client between tests, without allocating a new client.
|
||||
func (m *baseClient[T]) Reset() {
|
||||
m.items = make(map[string]T)
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) newID() string {
|
||||
return fmt.Sprintf("%s_%s", m.idPrefix, ksuid.New().String())
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) notFoundErr() error {
|
||||
return &ngrok.Error{
|
||||
StatusCode: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) any(predicate func(T) bool) bool {
|
||||
for _, item := range m.items {
|
||||
if predicate(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *baseClient[T]) createdAt() string {
|
||||
return time.Now().Format(time.RFC3339)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package nmockapi
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
)
|
||||
|
||||
// DomainClient is a mock implementation of the ngrok API client for managing reserved domains. It
|
||||
// tries to mimic the behavior of the actual ngrok API client, but it is not a complete
|
||||
// implementation. It is used for testing purposes only and should not be used in production
|
||||
// environments.
|
||||
type DomainClient struct {
|
||||
baseClient[*ngrok.ReservedDomain]
|
||||
}
|
||||
|
||||
func NewDomainClient() *DomainClient {
|
||||
return &DomainClient{
|
||||
baseClient: newBase[*ngrok.ReservedDomain](
|
||||
"rd",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *DomainClient) Create(_ context.Context, item *ngrok.ReservedDomainCreate) (*ngrok.ReservedDomain, error) {
|
||||
if m.any(func(rd *ngrok.ReservedDomain) bool { return rd.Domain == item.Domain }) {
|
||||
return nil, &ngrok.Error{
|
||||
StatusCode: http.StatusConflict,
|
||||
Msg: fmt.Sprintf("Domain %s already exists", item.Domain),
|
||||
ErrorCode: "ERR_NGROK_413",
|
||||
}
|
||||
}
|
||||
|
||||
id := m.newID()
|
||||
|
||||
newDomain := &ngrok.ReservedDomain{
|
||||
ID: id,
|
||||
CreatedAt: m.createdAt(),
|
||||
Domain: item.Domain,
|
||||
Region: item.Region,
|
||||
URI: fmt.Sprintf("https://mock-api.ngrok.com/reserved_domains/%s", id),
|
||||
}
|
||||
|
||||
if !isNgrokManagedDomain(newDomain) {
|
||||
cname := fmt.Sprintf("%s.%s.ngrok-cname.com", rand.String(17), rand.String(17))
|
||||
newDomain.CNAMETarget = &cname
|
||||
}
|
||||
m.items[id] = newDomain
|
||||
return newDomain, nil
|
||||
}
|
||||
|
||||
func (m *DomainClient) Update(ctx context.Context, item *ngrok.ReservedDomainUpdate) (*ngrok.ReservedDomain, error) {
|
||||
existingItem, err := m.Get(ctx, item.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if item.Description != nil {
|
||||
existingItem.Description = *item.Description
|
||||
}
|
||||
if item.Metadata != nil {
|
||||
existingItem.Metadata = *item.Metadata
|
||||
}
|
||||
|
||||
if item.CertificateID != nil {
|
||||
existingItem.Certificate = &ngrok.Ref{
|
||||
ID: *item.CertificateID,
|
||||
URI: fmt.Sprintf("https://mock-api.ngrok.com/certificates/%s", *item.CertificateID),
|
||||
}
|
||||
}
|
||||
|
||||
if item.CertificateManagementPolicy != nil {
|
||||
existingItem.CertificateManagementPolicy = item.CertificateManagementPolicy
|
||||
}
|
||||
|
||||
m.items[item.ID] = existingItem
|
||||
return existingItem, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ngrokManagedDomainSuffixes = []string{
|
||||
"ngrok.app",
|
||||
"ngrok.dev",
|
||||
"ngrok.pizza",
|
||||
"ngrok-free.app",
|
||||
"ngrok-free.dev",
|
||||
"ngrok-free.pizza",
|
||||
"ngrok.io",
|
||||
}
|
||||
)
|
||||
|
||||
func isNgrokManagedDomain(domain *ngrok.ReservedDomain) bool {
|
||||
if domain == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return slices.ContainsFunc(ngrokManagedDomainSuffixes, func(suffix string) bool {
|
||||
return strings.HasSuffix(domain.Domain, suffix)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package nmockapi_test
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"github.com/ngrok/ngrok-operator/internal/mocks/nmockapi"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
)
|
||||
|
||||
var _ = Describe("DomainClient", func() {
|
||||
const (
|
||||
NgrokManagedDomainSuffix = "ngrok.app"
|
||||
CustomDomainSuffix = "custom-domain.xyz"
|
||||
)
|
||||
|
||||
var (
|
||||
domainClient *nmockapi.DomainClient
|
||||
ctx context.Context
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
domainClient = nmockapi.NewDomainClient()
|
||||
ctx = GinkgoT().Context()
|
||||
})
|
||||
|
||||
Describe("Get()", func() {
|
||||
var (
|
||||
id string
|
||||
domain *ngrok.ReservedDomain
|
||||
err error
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
domain, err = domainClient.Get(ctx, id)
|
||||
})
|
||||
|
||||
When("the domain exists", func() {
|
||||
BeforeEach(func() {
|
||||
domain, err := domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: "test-domain.ngrok.io",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
id = domain.ID
|
||||
})
|
||||
|
||||
It("should return the domain", func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(domain.Domain).To(Equal("test-domain.ngrok.io"))
|
||||
Expect(domain.ID).To(MatchRegexp("^rd_"))
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain does not exist", func() {
|
||||
BeforeEach(func() {
|
||||
id = "non-existing-id"
|
||||
})
|
||||
|
||||
It("should return an ngrok not found error", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(ngrok.IsNotFound(err)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("List()", func() {
|
||||
var (
|
||||
domains []*ngrok.ReservedDomain
|
||||
err error
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
iter := domainClient.List(nil)
|
||||
|
||||
domains = make([]*ngrok.ReservedDomain, 0)
|
||||
for iter.Next(ctx) {
|
||||
domains = append(domains, iter.Item())
|
||||
}
|
||||
err = iter.Err()
|
||||
})
|
||||
|
||||
When("there are no domains", func() {
|
||||
It("the iterator should return an empty list", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(domains).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
When("there are domains", func() {
|
||||
BeforeEach(func() {
|
||||
_, createDomain1Err := domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: "test-domain-1.ngrok.io",
|
||||
})
|
||||
Expect(createDomain1Err).ToNot(HaveOccurred())
|
||||
_, createDomain2Err := domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: "test-domain-2.ngrok.io",
|
||||
})
|
||||
Expect(createDomain2Err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("the iterator should return the domains", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(domains).To(HaveLen(2))
|
||||
|
||||
Expect(slices.ContainsFunc(domains, func(domain *ngrok.ReservedDomain) bool {
|
||||
return domain.Domain == "test-domain-1.ngrok.io"
|
||||
})).To(BeTrue())
|
||||
Expect(slices.ContainsFunc(domains, func(domain *ngrok.ReservedDomain) bool {
|
||||
return domain.Domain == "test-domain-2.ngrok.io"
|
||||
})).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Create()", func() {
|
||||
var (
|
||||
domain *ngrok.ReservedDomain
|
||||
err error
|
||||
domainSuffix string
|
||||
domainName string
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
domain, err = domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: domainName,
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain is a ngrok managed domain", func() {
|
||||
BeforeEach(func() {
|
||||
domainSuffix = NgrokManagedDomainSuffix
|
||||
domainName = fmt.Sprintf("test-domain-%s.%s", rand.String(10), domainSuffix)
|
||||
})
|
||||
|
||||
It("should create and return the domain", func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(domain.Domain).To(Equal(domainName))
|
||||
})
|
||||
|
||||
It("should create the domain with an ID", func() {
|
||||
Expect(domain.ID).ToNot(BeEmpty())
|
||||
Expect(domain.ID).To(MatchRegexp("^rd_"))
|
||||
})
|
||||
|
||||
It("should create the domain with a timestamp", func() {
|
||||
Expect(domain.CreatedAt).ToNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("should not create the domain with a CNAMETarget", func() {
|
||||
Expect(domain.CNAMETarget).To(BeNil())
|
||||
})
|
||||
|
||||
When("the domain is already taken", func() {
|
||||
BeforeEach(func() {
|
||||
_, err := domainClient.Create(ctx, &ngrok.ReservedDomainCreate{Domain: domainName})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should return an ngrok already exists error", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(ngrok.IsErrorCode(err, 413)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain is a custom domain", func() {
|
||||
BeforeEach(func() {
|
||||
domainSuffix = CustomDomainSuffix
|
||||
domainName = fmt.Sprintf("test-domain-%s.%s", rand.String(10), domainSuffix)
|
||||
})
|
||||
|
||||
It("should create and return the domain", func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(domain.Domain).To(Equal(domainName))
|
||||
})
|
||||
|
||||
It("should create the domain with an ID", func() {
|
||||
Expect(domain.ID).ToNot(BeEmpty())
|
||||
Expect(domain.ID).To(MatchRegexp("^rd_"))
|
||||
})
|
||||
|
||||
It("should create the domain with a timestamp", func() {
|
||||
Expect(domain.CreatedAt).ToNot(BeEmpty())
|
||||
})
|
||||
|
||||
It("should create the domain with a CNAMETarget", func() {
|
||||
Expect(domain.CNAMETarget).ToNot(BeNil())
|
||||
Expect(*domain.CNAMETarget).To(MatchRegexp("^[a-zA-Z0-9]{17}\\.[a-zA-Z0-9]{17}\\.ngrok-cname\\.com$"))
|
||||
})
|
||||
|
||||
When("the domain is already taken", func() {
|
||||
BeforeEach(func() {
|
||||
_, err := domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: domainName,
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should return an ngrok already exists error", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(ngrok.IsErrorCode(err, 413)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Delete()", func() {
|
||||
var (
|
||||
id string
|
||||
deleteErr error
|
||||
domain *ngrok.ReservedDomain
|
||||
getErr error
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
deleteErr = domainClient.Delete(ctx, id)
|
||||
domain, getErr = domainClient.Get(ctx, id)
|
||||
})
|
||||
|
||||
When("the domain exists", func() {
|
||||
BeforeEach(func() {
|
||||
domain, err := domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: "test-domain-4.ngrok.io",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
id = domain.ID
|
||||
})
|
||||
|
||||
It("should delete the domain", func() {
|
||||
Expect(deleteErr).ToNot(HaveOccurred())
|
||||
|
||||
Expect(getErr).To(HaveOccurred())
|
||||
Expect(ngrok.IsNotFound(getErr)).To(BeTrue())
|
||||
|
||||
Expect(domain).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain does not exist", func() {
|
||||
BeforeEach(func() {
|
||||
id = "non-existing-id"
|
||||
})
|
||||
|
||||
It("Should return an ngrok not found error", func() {
|
||||
Expect(deleteErr).To(HaveOccurred())
|
||||
Expect(ngrok.IsNotFound(deleteErr)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Update()", func() {
|
||||
var (
|
||||
createdDomain *ngrok.ReservedDomain
|
||||
createErr error
|
||||
domainUpdate *ngrok.ReservedDomainUpdate
|
||||
|
||||
updatedDomain *ngrok.ReservedDomain
|
||||
updateErr error
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
createdDomain, createErr = domainClient.Create(ctx, &ngrok.ReservedDomainCreate{
|
||||
Domain: "test-domain-5.ngrok.io",
|
||||
})
|
||||
Expect(createErr).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
JustBeforeEach(func() {
|
||||
updatedDomain, updateErr = domainClient.Update(ctx, domainUpdate)
|
||||
})
|
||||
|
||||
When("the domain exists", func() {
|
||||
BeforeEach(func() {
|
||||
domainUpdate = &ngrok.ReservedDomainUpdate{
|
||||
ID: createdDomain.ID,
|
||||
Metadata: ngrok.String("new-metadata"),
|
||||
Description: ngrok.String("new-description"),
|
||||
}
|
||||
})
|
||||
|
||||
It("should update the domain", func() {
|
||||
Expect(updateErr).ToNot(HaveOccurred())
|
||||
Expect(updatedDomain).ToNot(BeNil())
|
||||
Expect(updatedDomain.ID).To(Equal(createdDomain.ID))
|
||||
Expect(updatedDomain.Metadata).To(Equal("new-metadata"))
|
||||
Expect(updatedDomain.Description).To(Equal("new-description"))
|
||||
})
|
||||
})
|
||||
|
||||
When("the domain does not exist", func() {
|
||||
BeforeEach(func() {
|
||||
domainUpdate = &ngrok.ReservedDomainUpdate{
|
||||
ID: "non-existing-id",
|
||||
Metadata: ngrok.String("new-metadata"),
|
||||
Description: ngrok.String("new-description"),
|
||||
}
|
||||
})
|
||||
|
||||
It("should return a not found error", func() {
|
||||
Expect(updateErr).To(HaveOccurred())
|
||||
Expect(ngrok.IsNotFound(updateErr)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
package nmockapi
|
||||
|
||||
import context "context"
|
||||
|
||||
// Iter is a mock iterator that implements the ngrok.Iter[T] interface.
|
||||
type Iter[T any] struct {
|
||||
items []T
|
||||
err error
|
||||
n int
|
||||
}
|
||||
|
||||
func (m *Iter[T]) Next(_ context.Context) bool {
|
||||
// If there is an error, stop iteration
|
||||
if m.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Increment the index
|
||||
m.n++
|
||||
|
||||
return m.n < len(m.items) && m.n >= 0
|
||||
}
|
||||
|
||||
func (m *Iter[T]) Item() T {
|
||||
if m.n >= 0 && m.n < len(m.items) {
|
||||
return m.items[m.n]
|
||||
}
|
||||
return *new(T)
|
||||
}
|
||||
|
||||
func (m *Iter[T]) Err() error {
|
||||
return m.err
|
||||
}
|
||||
|
||||
func NewIter[T any](items []T, err error) *Iter[T] {
|
||||
return &Iter[T]{
|
||||
items: items,
|
||||
err: err,
|
||||
n: -1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package nmockapi_test
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ngrok/ngrok-api-go/v7"
|
||||
"github.com/ngrok/ngrok-operator/internal/mocks/nmockapi"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Iter", func() {
|
||||
var (
|
||||
iter ngrok.Iter[string]
|
||||
iterErr error
|
||||
items []string
|
||||
ctx context.Context
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = GinkgoT().Context()
|
||||
})
|
||||
|
||||
JustBeforeEach(func() {
|
||||
iter = nmockapi.NewIter(items, iterErr)
|
||||
})
|
||||
|
||||
Describe("Item()", func() {
|
||||
Context("when there is an error", func() {
|
||||
BeforeEach(func() {
|
||||
iterErr = fmt.Errorf("ut-oh")
|
||||
})
|
||||
|
||||
It("should return an empty item", func() {
|
||||
Expect(iter.Item()).To(Equal(""))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there is no error", func() {
|
||||
BeforeEach(func() {
|
||||
iterErr = nil
|
||||
items = []string{"a", "b", "c"}
|
||||
})
|
||||
|
||||
It("should return the current item", func() {
|
||||
iter.Next(ctx)
|
||||
Expect(iter.Item()).To(Equal("a"))
|
||||
|
||||
iter.Next(ctx)
|
||||
Expect(iter.Item()).To(Equal("b"))
|
||||
|
||||
iter.Next(ctx)
|
||||
Expect(iter.Item()).To(Equal("c"))
|
||||
})
|
||||
|
||||
Context("when called before Next", func() {
|
||||
It("should return an empty item", func() {
|
||||
Expect(iter.Item()).To(Equal(""))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when called after Next returns false", func() {
|
||||
It("should return an empty item", func() {
|
||||
for iter.Next(ctx) {
|
||||
// Iterate until there are no more items
|
||||
}
|
||||
Expect(iter.Item()).To(Equal(""))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Next", func() {
|
||||
Context("when there is an error", func() {
|
||||
BeforeEach(func() {
|
||||
iterErr = fmt.Errorf("ut-oh")
|
||||
})
|
||||
|
||||
It("should return false", func() {
|
||||
Expect(iter.Next(ctx)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there is no error", func() {
|
||||
BeforeEach(func() {
|
||||
iterErr = nil
|
||||
})
|
||||
|
||||
Context("when there are no items", func() {
|
||||
BeforeEach(func() {
|
||||
iter = nmockapi.NewIter([]string{}, nil)
|
||||
})
|
||||
|
||||
It("should return false", func() {
|
||||
|
||||
})
|
||||
})
|
||||
BeforeEach(func() {
|
||||
iter = nmockapi.NewIter([]string{"a", "b", "c"}, nil)
|
||||
})
|
||||
|
||||
It("should return true while there are more values", func() {
|
||||
Expect(iter.Next(ctx)).To(BeTrue())
|
||||
Expect(iter.Next(ctx)).To(BeTrue())
|
||||
Expect(iter.Next(ctx)).To(BeTrue())
|
||||
|
||||
Expect(iter.Next(ctx)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Err()", func() {
|
||||
Context("when there is an error", func() {
|
||||
BeforeEach(func() {
|
||||
iterErr = fmt.Errorf("ut-oh")
|
||||
})
|
||||
|
||||
It("should return the error", func() {
|
||||
Expect(iter.Err()).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there is no error", func() {
|
||||
BeforeEach(func() {
|
||||
iterErr = nil
|
||||
})
|
||||
|
||||
It("should return nil", func() {
|
||||
Expect(iter.Err()).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
package nmockapi_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestNmockAPI(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "nmockapi Suite")
|
||||
}
|
||||
Reference in New Issue
Block a user