Repository navigation
feat(subscriptions): typed get/update/pause/resume lifecycle (#31) - #52
Merged
Merged
Conversation
Add GetSubscription, UpdateSubscription, PauseSubscription and
ResumeSubscription for the /v1/subscriptions/{id} member routes, typed
from production wire shapes observed on 2026-09-13.
- IDs are validated (blank, ".", "..", control characters) before any
request and escaped into one path segment; DeleteSubscription shares
the validator.
- SubscriptionUpdate sends only set fields; empty update, empty/blank
codes and non-positive intervals are refused client-side.
- A 2xx without data.subscription.id, or for another ID, returns
MalformedResponseError instead of a zero-value struct.
- Writes are never retried; 404/422/402/403 keep their typed errors.
- Live lifecycle smoke (build tag live) cleans up via t.Cleanup.
- Release contract test skips a Keep a Changelog [Unreleased] heading.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merged
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.
Closes #31.
Premise check (primary sources, 2026-09-13)
oilpriceapi-apiorigin/mainconfig/routes.rb:571-580resources :subscriptions, only: [:index, :show, :create, :update, :destroy]withmember { post :pause; post :resume }/v1/subscriptionsisapp/controllers/v1/subscriptions_controller.rb,app/models/watch.rbWatchto avoid colliding withBilling::Subscription. No member action touches Stripe or billing, so a live smoke was permitted.Subscriptionstruct. No new fields were declared.Observed wire shapes (production)
GET /v1/subscriptions/:id,PATCH,POST .../pause,POST .../resume→ 200{"status":"success","data":{"subscription":{id,name,codes,interval_seconds,status,deliver_webhook,source,tool_name,last_evaluated_at,next_run_at,created_at}}}.last_evaluated_atisnulluntil the evaluator runs.paused. Resume → 200,active,next_run_atre-armed.PATCH {}→ 200, no change.{"error":{"code":"NOT_FOUND","message":"Subscription not found","status":404,"request_id":...,"docs":...}}.PATCH {"codes":["NOT_A_REAL_CODE"]}→ 422{"status":"fail","data":{"error":"VALIDATION_ERROR","message":"Codes contains invalid commodity codes: NOT_A_REAL_CODE","details":{"codes":[...]}}}.PATCH {"interval_seconds":1}→ 422VALIDATION_ERROR"below your plan minimum of 60 seconds". Create returns a 402 upgrade trigger for the same violation; update does not.PATCH {"status":"bogus"}→ 500. Filed as OilpriceAPI/oilpriceapi-api#8471. The SDK does not exposestatuson update, so the SDK cannot trigger it.Changes
GetSubscription,UpdateSubscription(id, SubscriptionUpdate),PauseSubscription,ResumeSubscription, sharing onesubscriptionMemberhelper..,.., control characters →*InvalidInputError{Field:"id"}. The ID isurl.PathEscaped into one segment and still goes throughresolveEndpoint(fix: resolveEndpoint path contract — spaces over-rejected, percent-encoded ".." under-rejected #47 contract).DeleteSubscriptionnow uses the same validator;DeleteSubscription(".")previously sentDELETE /v1/subscriptions/.and returned success against the fake server.SubscriptionUpdateuses pointer fields, so only set fields are sent andfalse/""can be sent explicitly. Empty update, empty or blank codes, and non-positive interval →*InvalidInputError.statusis deliberately not exposed; use pause/resume.data.subscription.id, or names a different ID →*MalformedResponseError. The ID comparison is case-insensitive because the server matches UUIDs that way.*NotFoundError; 422/402/403 →*APIErrorwith the body intact; 5xx →*ServerError.release_contract_test.go: skips a Keep a Changelog[Unreleased]heading. Before this change the guard failed on it (latest changelog version "Unreleased" does not match SDK Version "1.7.0"), which blocked the required Unreleased entry.Versionis unchanged.[Unreleased]and the README method table. No version bump, tag or release.TDD evidence
Red 1: tests before any implementation (verbatim)
Red 2: against a naive implementation copying the existing
DeleteSubscriptionpattern (verbatim excerpt)A compile failure proves little, so the tests were also run against the obvious existing-convention code.
19 subtests failed. The 404, 422, entitlement, write-not-retried, cancellation and timeout tests passed against the naive code, because the shared transport already provides that behaviour. They are kept as regression guards for the new methods.
Green (verbatim)
Full checks:
go test -race -count=1 ./... -v→ 388 PASS, 0 FAIL, 0 SKIP (baseline 336 / 0).go vet ./...andgo vet -tags live ./...→ clean.gofmt -l .→ empty.Test fidelity: the 404, 422, 200 and null-timestamp bodies in
subscription_lifecycle_test.goare copied from the production responses above. The 402 body followsAgentUpgradeTriggers#render_agent_upgrade_required. Production currently emits that 402 on create, not update, so treat it as a shape guard. The 403 body is a generic plan-gate shape.Live lifecycle smoke: RAN, passed
TestLiveSubscriptionLifecycle(build taglive) runs create → get → update (name + codes) → pause → resume → delete → get 404 against production with the test key. It creates its own watch (86400s interval,WTI_USD), andt.Cleanupdeletes it on failure or skip.Cleanup was verified afterwards with a read-only list. The account holds the 1 pre-existing watch (
mcp-live-contract-1788524538194) and no leftovers. The pre-implementation curl probe also deleted its own watch (HTTP 204).Issues filed while doing this
PATCH status=<invalid>returns 500 (confirmed live); resume andPATCH status=activeskip the watch-count limit (code-confirmed, live unverified); update's interval floor returns 422 where create returns the 402 upgrade trigger.CreateSubscription's defaultX-OPA-Source: sdk-gois outside the server allowlist and is stored asapi(code-confirmed, live unverified).Not merged. No release.
🤖 Generated with Claude Code
https://claude.ai/code/session_015ao5paex73xXvuM424Libo