Skip to content

Commit 200b1be

Browse files
committed
HYPERFLEET-1088 - feat: add channel/version E2E tests with typed partner client
Replace Go module spec consumption with vendored release artifacts. Generate typed partner client (Channel, Version) alongside core client. Split version tests into separate e2e/version/ suite. - Remove hyperfleet-api-spec Go module dependency - Add make update-specs with pinned versions for spec fetching - Generate pkg/api/partner/ from template spec release artifact - Refactor HyperFleetClient to symmetric Core/Partner fields - Add channel CRUD + delete-restrict tests (e2e/channel/) - Add version CRUD + filtering + uniqueness tests (e2e/version/) - Add nil guards on ID pointer dereferences in test setup - Surface version delete errors in CleanupTestChannel - Remove doJSONRequest in favor of typed generated methods - Gitignore generated code and fetched specs
1 parent 61202eb commit 200b1be

23 files changed

Lines changed: 5363 additions & 37 deletions

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,4 @@ deploy-scripts/.env
5353

5454
# Generated code
5555
pkg/api/openapi/
56-
57-
# Downloaded from hyperfleet-api during make generate
58-
openapi/openapi.yaml
56+
pkg/api/partner/

Makefile

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,35 @@ help: ## Display this help
4343

4444
##@ Code Generation
4545

46+
# Spec versions — bump to pull new releases
47+
CORE_SPEC_VERSION ?= v1.0.19
48+
CORE_SPEC_REPO := openshift-hyperfleet/hyperfleet-api-spec
49+
PARTNER_SPEC_VERSION ?= v1.0.19
50+
PARTNER_SPEC_REPO := openshift-hyperfleet/hyperfleet-api-spec-template
51+
52+
.PHONY: update-specs
53+
update-specs: ## Fetch OpenAPI specs from pinned versions and regenerate
54+
@rm -f openapi/core-openapi.yaml openapi/partner-openapi.yaml
55+
@echo " → core @ $(CORE_SPEC_VERSION)"
56+
@curl -sfL https://github.com/$(CORE_SPEC_REPO)/releases/download/$(CORE_SPEC_VERSION)/core-openapi.yaml -o openapi/core-openapi.yaml
57+
@echo " → partner @ $(PARTNER_SPEC_VERSION)"
58+
@curl -sfL https://github.com/$(PARTNER_SPEC_REPO)/releases/download/$(PARTNER_SPEC_VERSION)/template-openapi.yaml -o openapi/partner-openapi.yaml
59+
@echo "✓ Specs fetched: core=$(CORE_SPEC_VERSION) partner=$(PARTNER_SPEC_VERSION)"
60+
$(MAKE) generate
61+
4662
.PHONY: generate
47-
generate: $(OAPI_CODEGEN) ## Generate API client code from OpenAPI schema
48-
$(GO) mod download
49-
rm -rf pkg/api/openapi
50-
mkdir -p pkg/api/openapi openapi
51-
@rm -f openapi/openapi.yaml
52-
@cp "$$($(GO) list -m -f '{{.Dir}}' github.com/openshift-hyperfleet/hyperfleet-api-spec)/schemas/core/openapi.yaml" openapi/openapi.yaml
53-
$(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml
54-
@echo "✓ API client code generated in pkg/api/openapi/"
63+
generate: $(OAPI_CODEGEN) ## Generate API client code from vendored OpenAPI specs
64+
rm -rf pkg/api/openapi pkg/api/partner
65+
mkdir -p pkg/api/openapi pkg/api/partner
66+
$(OAPI_CODEGEN) --config openapi/oapi-codegen-core.yaml openapi/core-openapi.yaml
67+
$(OAPI_CODEGEN) --config openapi/oapi-codegen-partner.yaml openapi/partner-openapi.yaml
68+
@echo "✓ API client code generated in pkg/api/openapi/ and pkg/api/partner/"
69+
70+
.PHONY: verify-generate
71+
verify-generate: generate ## CI gate: verify generated code is up-to-date
72+
@git diff --exit-code -- pkg/api/ || \
73+
{ echo "ERROR: generated code is stale — run 'make generate'"; exit 1; }
74+
@echo "✓ Generated code is up-to-date"
5575

5676
##@ Development
5777

@@ -72,8 +92,7 @@ run: build ## Build and run with help
7292
clean: ## Remove build artifacts
7393
rm -rf $(BIN_DIR)
7494
rm -rf $(OUTPUT_DIR)
75-
rm -f openapi/openapi.yaml
76-
rm -rf pkg/api/openapi
95+
rm -rf pkg/api/openapi pkg/api/partner
7796
rm -f coverage.out coverage.html
7897

7998
##@ Testing

e2e/channel/crud_lifecycle.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package channel
2+
3+
import (
4+
"context"
5+
6+
"github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability
8+
9+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/partner"
10+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper"
11+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels"
12+
)
13+
14+
var _ = ginkgo.Describe("[Suite: channel][crud] Channel CRUD Lifecycle",
15+
ginkgo.Label(labels.Tier1),
16+
func() {
17+
var h *helper.Helper
18+
var channelID string
19+
var channel *partner.Channel
20+
21+
ginkgo.BeforeEach(func(ctx context.Context) {
22+
h = helper.New()
23+
24+
var err error
25+
channel, err = h.Client.CreateChannelFromPayload(ctx, h.TestDataPath("payloads/channels/channel-request.json"))
26+
Expect(err).NotTo(HaveOccurred(), "failed to create channel")
27+
Expect(channel.Id).NotTo(BeNil(), "channel ID should be generated")
28+
Expect(channel.Name).NotTo(BeEmpty(), "channel name should be present")
29+
channelID = *channel.Id
30+
ginkgo.GinkgoWriter.Printf("Created channel ID: %s, Name: %s\n", channelID, channel.Name)
31+
32+
ginkgo.DeferCleanup(func(ctx context.Context) {
33+
if err := h.CleanupTestChannel(ctx, channelID); err != nil {
34+
ginkgo.GinkgoWriter.Printf("Warning: failed to cleanup channel %s: %v\n", channelID, err)
35+
}
36+
})
37+
})
38+
39+
ginkgo.It("should create a channel with correct fields", func(ctx context.Context) {
40+
Expect(channel.Kind).To(HaveValue(Equal("Channel")), "kind should be Channel")
41+
Expect(channel.Generation).To(Equal(int32(1)), "new channel should have generation=1")
42+
Expect(channel.DeletedTime).To(BeNil(), "new channel should not have deleted_time")
43+
})
44+
45+
ginkgo.It("should retrieve channel by ID", func(ctx context.Context) {
46+
ginkgo.By("fetching channel by ID")
47+
fetched, err := h.Client.GetChannel(ctx, channelID)
48+
Expect(err).NotTo(HaveOccurred(), "failed to get channel")
49+
50+
Expect(*fetched.Id).To(Equal(channelID))
51+
Expect(fetched.Name).To(Equal(channel.Name))
52+
Expect(fetched.Kind).To(HaveValue(Equal("Channel")))
53+
Expect(fetched.Generation).To(Equal(int32(1)))
54+
})
55+
56+
ginkgo.It("should list channels and find the created one", func(ctx context.Context) {
57+
ginkgo.By("listing all channels")
58+
list, err := h.Client.ListChannels(ctx, "")
59+
Expect(err).NotTo(HaveOccurred(), "failed to list channels")
60+
Expect(list.Items).NotTo(BeEmpty(), "channel list should not be empty")
61+
62+
ginkgo.By("verifying created channel appears in list")
63+
found := false
64+
for _, ch := range list.Items {
65+
if ch.Id != nil && *ch.Id == channelID {
66+
found = true
67+
break
68+
}
69+
}
70+
Expect(found).To(BeTrue(), "created channel should appear in list")
71+
})
72+
73+
ginkgo.It("should update channel via PATCH", func(ctx context.Context) {
74+
ginkgo.By("patching channel spec")
75+
patched, err := h.Client.PatchChannel(ctx, channelID, partner.ChannelPatchRequest{
76+
Spec: &partner.ChannelSpec{
77+
IsDefault: true,
78+
EnabledRegex: ".*",
79+
},
80+
})
81+
Expect(err).NotTo(HaveOccurred(), "failed to patch channel")
82+
Expect(patched.Generation).To(Equal(int32(2)), "generation should increment after PATCH")
83+
84+
ginkgo.By("verifying patched spec via GET")
85+
fetched, err := h.Client.GetChannel(ctx, channelID)
86+
Expect(err).NotTo(HaveOccurred())
87+
Expect(fetched.Spec.IsDefault).To(BeTrue())
88+
})
89+
90+
ginkgo.It("should delete channel", func(ctx context.Context) {
91+
ginkgo.By("deleting channel")
92+
deleted, err := h.Client.DeleteChannel(ctx, channelID)
93+
Expect(err).NotTo(HaveOccurred(), "failed to delete channel")
94+
Expect(deleted.DeletedTime).NotTo(BeNil(), "deleted channel should have deleted_time set")
95+
})
96+
},
97+
)

e2e/channel/delete_restrict.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package channel
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net/http"
7+
8+
"github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability
10+
11+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/client"
12+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper"
13+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels"
14+
)
15+
16+
var _ = ginkgo.Describe("[Suite: channel][delete-restrict] Channel Deletion Blocked While Versions Exist",
17+
ginkgo.Label(labels.Tier1, labels.Negative),
18+
func() {
19+
var h *helper.Helper
20+
var channelID string
21+
var versionID string
22+
23+
ginkgo.BeforeEach(func(ctx context.Context) {
24+
h = helper.New()
25+
26+
ginkgo.By("creating channel with a version")
27+
ch, err := h.Client.CreateChannelFromPayload(ctx, h.TestDataPath("payloads/channels/channel-request.json"))
28+
Expect(err).NotTo(HaveOccurred(), "failed to create channel")
29+
Expect(ch.Id).NotTo(BeNil(), "channel ID should not be nil")
30+
channelID = *ch.Id
31+
32+
ver, err := h.Client.CreateVersionFromPayload(ctx, channelID, h.TestDataPath("payloads/versions/version-request.json"))
33+
Expect(err).NotTo(HaveOccurred(), "failed to create version")
34+
Expect(ver.Id).NotTo(BeNil(), "version ID should not be nil")
35+
versionID = *ver.Id
36+
37+
ginkgo.DeferCleanup(func(ctx context.Context) {
38+
if err := h.CleanupTestChannel(ctx, channelID); err != nil {
39+
ginkgo.GinkgoWriter.Printf("Warning: failed to cleanup channel %s: %v\n", channelID, err)
40+
}
41+
})
42+
})
43+
44+
ginkgo.It("should return 409 when deleting channel with existing versions", func(ctx context.Context) {
45+
ginkgo.By("attempting to delete channel while version exists")
46+
_, err := h.Client.DeleteChannel(ctx, channelID)
47+
var httpErr *client.HTTPError
48+
Expect(errors.As(err, &httpErr)).To(BeTrue(), "error should be HTTPError")
49+
Expect(httpErr.StatusCode).To(Equal(http.StatusConflict),
50+
"deleting channel with existing versions should return 409")
51+
})
52+
53+
ginkgo.It("should allow channel deletion after all versions are deleted", func(ctx context.Context) {
54+
ginkgo.By("deleting the version first")
55+
_, err := h.Client.DeleteVersion(ctx, channelID, versionID)
56+
Expect(err).NotTo(HaveOccurred(), "failed to delete version")
57+
58+
ginkgo.By("deleting the channel after versions are gone")
59+
deleted, err := h.Client.DeleteChannel(ctx, channelID)
60+
Expect(err).NotTo(HaveOccurred(), "channel deletion should succeed after versions deleted")
61+
Expect(deleted.DeletedTime).NotTo(BeNil(), "deleted channel should have deleted_time set")
62+
})
63+
},
64+
)

e2e/e2e.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package e2e
33
// Import test suites for auto-registration
44
import (
55
_ "github.com/openshift-hyperfleet/hyperfleet-e2e/e2e/adapter"
6+
_ "github.com/openshift-hyperfleet/hyperfleet-e2e/e2e/channel"
67
_ "github.com/openshift-hyperfleet/hyperfleet-e2e/e2e/cluster"
78
_ "github.com/openshift-hyperfleet/hyperfleet-e2e/e2e/nodepool"
9+
_ "github.com/openshift-hyperfleet/hyperfleet-e2e/e2e/version"
810
)

e2e/version/crud_lifecycle.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package version
2+
3+
import (
4+
"context"
5+
6+
"github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega" //nolint:staticcheck // dot import for test readability
8+
9+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/api/partner"
10+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/helper"
11+
"github.com/openshift-hyperfleet/hyperfleet-e2e/pkg/labels"
12+
)
13+
14+
var _ = ginkgo.Describe("[Suite: version][crud] Version CRUD Lifecycle",
15+
ginkgo.Label(labels.Tier1),
16+
func() {
17+
var h *helper.Helper
18+
var channelID string
19+
var versionID string
20+
var version *partner.Version
21+
22+
ginkgo.BeforeEach(func(ctx context.Context) {
23+
h = helper.New()
24+
25+
ginkgo.By("creating parent channel")
26+
ch, err := h.Client.CreateChannelFromPayload(ctx, h.TestDataPath("payloads/channels/channel-request.json"))
27+
Expect(err).NotTo(HaveOccurred(), "failed to create channel")
28+
Expect(ch.Id).NotTo(BeNil(), "channel ID should not be nil")
29+
channelID = *ch.Id
30+
ginkgo.GinkgoWriter.Printf("Created parent channel ID: %s\n", channelID)
31+
32+
ginkgo.By("creating version under channel")
33+
version, err = h.Client.CreateVersionFromPayload(ctx, channelID, h.TestDataPath("payloads/versions/version-request.json"))
34+
Expect(err).NotTo(HaveOccurred(), "failed to create version")
35+
Expect(version.Id).NotTo(BeNil(), "version ID should be generated")
36+
Expect(version.Name).NotTo(BeEmpty(), "version name should be present")
37+
versionID = *version.Id
38+
ginkgo.GinkgoWriter.Printf("Created version ID: %s, Name: %s\n", versionID, version.Name)
39+
40+
ginkgo.DeferCleanup(func(ctx context.Context) {
41+
if err := h.CleanupTestChannel(ctx, channelID); err != nil {
42+
ginkgo.GinkgoWriter.Printf("Warning: failed to cleanup channel %s: %v\n", channelID, err)
43+
}
44+
})
45+
})
46+
47+
ginkgo.It("should create a version with correct fields", func(ctx context.Context) {
48+
Expect(version.Kind).To(HaveValue(Equal("Version")), "kind should be Version")
49+
Expect(version.Generation).To(Equal(int32(1)), "new version should have generation=1")
50+
Expect(version.DeletedTime).To(BeNil(), "new version should not have deleted_time")
51+
})
52+
53+
ginkgo.It("should retrieve version by ID", func(ctx context.Context) {
54+
ginkgo.By("fetching version by ID")
55+
fetched, err := h.Client.GetVersion(ctx, channelID, versionID)
56+
Expect(err).NotTo(HaveOccurred(), "failed to get version")
57+
58+
Expect(*fetched.Id).To(Equal(versionID))
59+
Expect(fetched.Name).To(Equal(version.Name))
60+
Expect(fetched.Kind).To(HaveValue(Equal("Version")))
61+
Expect(fetched.Generation).To(Equal(int32(1)))
62+
})
63+
64+
ginkgo.It("should list versions under channel", func(ctx context.Context) {
65+
ginkgo.By("listing versions")
66+
list, err := h.Client.ListVersions(ctx, channelID, "")
67+
Expect(err).NotTo(HaveOccurred(), "failed to list versions")
68+
Expect(list.Items).NotTo(BeEmpty(), "version list should not be empty")
69+
70+
ginkgo.By("verifying created version appears in list")
71+
found := false
72+
for _, v := range list.Items {
73+
if v.Id != nil && *v.Id == versionID {
74+
found = true
75+
break
76+
}
77+
}
78+
Expect(found).To(BeTrue(), "created version should appear in list")
79+
})
80+
81+
ginkgo.It("should update version via PATCH", func(ctx context.Context) {
82+
ginkgo.By("patching version spec")
83+
patched, err := h.Client.PatchVersion(ctx, channelID, versionID, partner.VersionPatchRequest{
84+
Spec: &partner.VersionSpec{
85+
RawVersion: "4.18.0",
86+
Enabled: true,
87+
IsDefault: true,
88+
ReleaseImage: "quay.io/openshift-release-dev/ocp-release:4.18.0",
89+
},
90+
})
91+
Expect(err).NotTo(HaveOccurred(), "failed to patch version")
92+
Expect(patched.Generation).To(Equal(int32(2)), "generation should increment after PATCH")
93+
94+
ginkgo.By("verifying patched spec via GET")
95+
fetched, err := h.Client.GetVersion(ctx, channelID, versionID)
96+
Expect(err).NotTo(HaveOccurred())
97+
Expect(fetched.Spec.IsDefault).To(BeTrue())
98+
Expect(fetched.Spec.RawVersion).To(Equal("4.18.0"))
99+
})
100+
101+
ginkgo.It("should delete version", func(ctx context.Context) {
102+
ginkgo.By("deleting version")
103+
deleted, err := h.Client.DeleteVersion(ctx, channelID, versionID)
104+
Expect(err).NotTo(HaveOccurred(), "failed to delete version")
105+
Expect(deleted.DeletedTime).NotTo(BeNil(), "deleted version should have deleted_time set")
106+
})
107+
},
108+
)

0 commit comments

Comments
 (0)