test: OpenAPI contract layer (offline drift detection)#8
Open
gurinderu wants to merge 2 commits into
Open
Conversation
The mock was hand-written and drifted from the real API, so wire-format bugs (SG create object-vs-array, inline boot_disk missing clusterId) only surfaced at apply. This adds a contract layer that validates against vodopad's authoritative public spec without a live account. - Vendor docs/fluence-public.yaml (OpenAPI 3.1) under internal/client/mock/testdata; refresh via `make openapi-refresh` from a local vodopad checkout. - Shared validators (mock/spec.go) built with pb33f/libopenapi + libopenapi-validator (kin-openapi can't parse 3.1's nullable-array constructs). Model is built tolerantly: vodopad's public spec currently has a dangling $ref on an unrelated billing endpoint; every other path still resolves. - Standalone contract suite (client/contract_test.go) validates each request body against the spec, pinning the regressions: SG ingress/egress is an array (allow-all omitted, deny = []), object form rejected; inline boot_disk must carry clusterId. - Schema-enforcing mock middleware: validates every request (400 on bad body, like the real API's ValidatedJson) and records response drift (ContractViolations) so the read path can't silently diverge. Per-test opt-out via SetContractEnforcement. Full suite, go vet, and golangci-lint all clean with enforcement active.
…nges (#9) Attaching a public IP or binding a security group flags the VM restart_required; the change only takes effect after a restart, but nothing in the provider could restart the VM — the client wrapped no restart endpoint. Add a RestartVM client method (POST /v2/vms/{id}/restart) and a new cloudless_vm_restart resource that restarts the VM once, only when it is flagged restart_required, and waits for it to come back ready. Wiring it after the attachment resources (depends_on / triggers) coalesces all pending changes into a single restart; it is a no-op when no restart is pending, so it never reboots a healthy VM. The VM endpoints briefly return 406 ("VM is not in a status to ...") just after an attach, so add IsNotAcceptable and retry the restart until the VM settles. The mock gains the restart/softreboot endpoints, restart_required modeling, and a RestartCount accessor for tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #7 (base =
fix/sg-create-ssh-idempotency-storage-name).Why
The mock is hand-written and drifts from the real API, so wire-format bugs (SG create object-vs-array, inline
boot_diskmissingclusterId) only surfaced at a live apply. This adds a contract layer that validates against vodopad's authoritative public spec without a live account — the cheap, always-on half of the integration-testing plan (Phase 1).What
internal/client/mock/testdata/fluence-public.yaml(OpenAPI 3.1) +make openapi-refreshto regenerate from a local vodopad checkout.mock/spec.go) onpb33f/libopenapi+libopenapi-validator— required because the spec is 3.1 (type: [array,'null']), which kin-openapi can't parse. The model is built tolerantly around a known upstream defect (below).client/contract_test.go): validates each request body against the spec; pins the regressions — SG ingress/egress is an array (allow-all omitted, deny[]), the object form is rejected, and inlineboot_diskmust carryclusterId(both directions).ValidatedJson) and records response drift (Server.ContractViolations()), with a per-testSetContractEnforcementopt-out. Every existingresource.UnitTestnow gets contract enforcement for free — all still pass.Notes
fluence-public.yamlhas a dangling$ref(ResponseOutputFormat) on a billing endpoint, so the public spec is technically invalid. Worked around via a tolerant model build (unrelated to provider endpoints); worth fixing in vodopad.pb33f/libopenapi+libopenapi-validator(test path only).go test ./...,go vet,golangci-lint(strict config) all clean with enforcement active.