Skip to content

test(opensearch): pin the wire encoding of reserved-instance purchases - #93

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-89
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-89

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Issue #89: after #88 bumped aws-sdk-go-v2/service/opensearch from v1.52.3 to v1.85.1 (smithy-go v1.24.2 to v1.28.5), the OpenSearch client serializes requests through smithy-go's schema-based engine (service/opensearch/schemas) instead of generated per-operation serializers. Every existing test in this repo mocks provider.ServiceClient, so nothing checked what PurchaseReservedInstanceOffering actually puts on the wire.

This is a test-only change. No production code changes.

What the new test does

tools/aws_opensearch_wire_test.go adds TestOpenSearchRIPurchaseWireEncoding:

  • It drives a confirmed cudly_aws_opensearch_ri_purchase call (dry_run=false, confirm=true, count=3) through the tool's handle, ExecutePurchase (spend caps, USD cap, budget, idempotency token) and the real provider client awsprovider.NewOpenSearchClient, which is the constructor the AWS provider's GetServiceClient uses. That client goes through purchasecfg.NewConfig and the SDK's serializer exactly as in production.
  • The client is pointed at an httptest server through aws.Config.BaseEndpoint, with static fixture credentials. The server answers DescribeReservedInstanceOfferings, DescribeReservedInstances, PurchaseReservedInstanceOffering, STS GetCallerIdentity and AddTags with fixtures, and fails the test on any other request.
  • A loopbackOnlyTransport refuses any request whose host is not the fake server, so a regression in endpoint resolution fails the test instead of reaching AWS. I probed this: with BaseEndpoint removed, the test fails with test transport refused non-loopback request to es.us-east-1.amazonaws.com.
  • On the captured purchase request it asserts:
    • the method is POST to /2021-01-01/opensearch/purchaseReservedInstanceOffering
    • there is no query string
    • Content-Type is application/json
    • the SigV4 scope is /us-east-1/es/aws4_request
    • the body has exactly the members InstanceCount, ReservationName and ReservedInstanceOfferingId
    • InstanceCount is the unquoted JSON number 3, checked on the raw JSON so a quoted "3" fails
    • ReservedInstanceOfferingId is the fixture offering
    • ReservationName equals the opensearch-id-… name derived from the MCP idempotency token, recomputed in the test

Why here and not in cloud-commitments-go

The issue says this "may belong in cloud-commitments-go's opensearch provider". I put it here because this module's go.mod is the one that pins service/opensearch v1.85.1, and MVS means that is the serializer the cudly-mcp binary actually ships. A test in cloud-commitments-go would pin whatever version that module resolves, which is a separate bump. The test is also end to end from the MCP tool arguments. A matching provider-level test in cloud-commitments-go would still be worthwhile, but I left it out of scope.

Verification

All evidence is local unit/fixture evidence on macOS. There are no real AWS calls, and every response is a hand-written fixture shaped like the documented restJson1/awsQuery responses. Real-cloud coverage gap: this pins what the client sends, not what AWS accepts. No real PurchaseReservedInstanceOffering was made or could be made.

Tests ran with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null because this machine's default AWS profile is invalid. The test itself uses static fixture credentials and needs no AWS config.

go test ./tools/ -run TestOpenSearchRIPurchaseWireEncoding -count=1 -v   # PASS
go test ./... -count=1                                                  # ok (root, cmd/cudly-mcp, tools)
go test -race ./tools/ -run 'TestOpenSearchRIPurchaseWireEncoding|TestSimpleAWSRI|TestBudget' -count=3   # ok
go build ./... && make build && go vet ./...                            # ok
golangci-lint v2.10.1 run --allow-parallel-runners ./...               # 0 issues
gocyclo -over 10 -ignore "_test\.go" .                                  # silent
make tidy-check                                                         # ok (no go.mod/go.sum change)
bash scripts/gofmt-hook.sh tools/aws_opensearch_wire_test.go            # ok
bash scripts/gosec-hook.sh tools/aws_opensearch_wire_test.go            # 0 issues
git-secrets --scan (fresh git init repo, isolated HOME, repo patterns)  # clean
bash scripts/test-git-secrets-allowlist.sh                              # 103 PASS, 0 FAIL

Fail-before / pass-after (mutation probes)

There was no bug to fix, so there is no failing state on main to show. Instead I ran the test against mutated copies of the SDK and the provider, each swapped in through a throwaway go.work replace. None of these copies are in the diff.

Probe Mutation Result
M1, SDK schema PurchaseReservedInstanceOfferingRequest.AddMember("InstanceCount", …) renamed to "instanceCount" in service/opensearch@v1.85.1/schemas FAIL: elements differ … {"instanceCount":3,…}
M2, SDK serializer s.WriteInt32(…InstanceCount…) changed to s.WriteString(…, fmt.Sprint(…)) FAIL: expected: "3" actual: "\"3\"" (InstanceCount must be an unquoted integer)
M3, provider ReservationName dropped from the purchase input FAIL: SDK client-side validation missing required field … ReservationName
M4, provider InstanceCount: aws.Int32(instanceCount - 1) FAIL: expected: "3" actual: "2"
Old SDK service/opensearch replaced with v1.52.3, the pre-#88 generated serializer. go list -deps confirms service/opensearch/schemas is not linked PASS
Current main service/opensearch v1.85.1, schema-based serializer PASS

The test passes on both the old and the new serializer and fails on each mutation. So the wire encoding of this request is unchanged across #88, and a future drift will be caught.

Notes

  • The test is t.Parallel(). It uses its own httptest server and the package's existing TestMain defaults (real-purchase gate on, generous caps, temp audit log). The only shared state it touches is the process budget ledger, and it reserves 3000 USD of the TestMain default. The budget tests that inspect or reset the ledger are non-parallel (t.Setenv), so they never overlap with it.
  • The STS fixture account is the non-numeric fixture-account, so the fixture can never look like a 12-digit account ID. No .gitallowed change was needed.
  • Out of scope: the other five purchase services (EC2, RDS, ElastiCache, Redshift, MemoryDB) keep their old generated serializers per the issue, so there are no wire tests for them here. AddTags and DescribeReservedInstance* are served but not wire-asserted.

Closes #89

Maintainer: please mirror the issue's triage labels onto this PR (I don't have label permissions on this repo): priority/p2, severity/medium, type/chore, urgency/this-quarter, impact/many, effort/s. The issue does not carry triaged.

🤖 Generated with Claude Code

aws-sdk-go-v2/service/opensearch v1.85.1 (LeanerCloud#88) serializes requests
through smithy-go's schema-based engine instead of generated
per-operation serializers. Every existing test mocks
provider.ServiceClient, so nothing checked the bytes
PurchaseReservedInstanceOffering puts on the wire.

Drive a confirmed cudly_aws_opensearch_ri_purchase call through the real
provider client (awsprovider.NewOpenSearchClient) pointed at an
httptest server via aws.Config.BaseEndpoint, with fixture responses and
a transport that refuses any non-loopback host. Assert the route, the
content type, the SigV4 scope (es, purchase region) and the JSON body:
exactly InstanceCount, ReservationName and ReservedInstanceOfferingId,
with InstanceCount an unquoted JSON number.

Closes LeanerCloud#89

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(opensearch): pin the wire encoding of reserved-instance purchases after the SDK serializer change

1 participant