Skip to content

test: align spec suite with baseline, and fix three client bugs it found - #537

Merged
razor-x merged 5 commits into
mainfrom
claude/ruby-python-sdk-testing-2tqetp
Aug 6, 2026
Merged

test: align spec suite with baseline, and fix three client bugs it found#537
razor-x merged 5 commits into
mainfrom
claude/ruby-python-sdk-testing-2tqetp

Conversation

@razor-x

@razor-x razor-x commented Aug 6, 2026

Copy link
Copy Markdown
Member

Brings the Ruby spec suite in line with the scope and strategy used by seamapi/javascript-http, then fixes the three SDK bugs that work uncovered.

Five commits, split so each fix is reviewable on its own:

Commit Touches
test: align spec suite with the JavaScript SDK baseline specs only
test: keep every spec under spec/ specs + Rakefile
fix: allow the client option to be used on its own http_single_workspace.rb
fix: wait when wait_for_action_attempt is a hash on the client helpers/action_attempt.rb, deep_hash_accessor.rb
fix: wait for action attempts by default in the from_* constructors seam.rb, http.rb, http_single_workspace.rb

Each fix commit is green on its own, and carries the spec that stops being skipped.

The baseline this follows

The JavaScript SDK has one fixture, getTestServer, that gives every test a freshly seeded fake. Its test files are named after SDK concerns — auth, env, headers, http errors, pagination, retry, serialization, waiting on action attempts — and they reach for routes like devices.get only as a means of exercising the client. There are no per-route tests, because routes are generated. nock appears only where the fake cannot help.

The Ruby suite had the shape inverted: spec/clients held ten files of per-route WebMock stubs, while the fake was used by just two specs.

Fixture

  • Add FakeSeamConnect, which starts the fake on an unused port, polls /health until ready, reads the seed, and stops the server after the example. Examples opt in with :fake and get endpoint, seed, and seam.
  • Start the fake directly from node_modules/.bin rather than through npm run start, so the spawned pid is the server itself and no process-group juggling is needed to stop it.
  • Replace spec/support/helpers.rb, whose stub_seam_request helper made stubbing the path of least resistance.

Scope

  • Drop spec/clients — ten files asserting that generated route methods return the resource class they were generated to return. The JavaScript SDK has no equivalent.
  • Drop the two overlapping stubbed request specs and the mocked action attempt resource spec, folding what they covered into http_error_spec and wait_for_action_attempt_spec against the fake. The action attempt spec had stubbed :get on /action_attempts/get while the SDK POSTs to it.
  • Exclude generated routes and resources from coverage. The report previously listed generated route files as the worst-covered in the repo, which is pressure to write exactly the specs this PR removes.
  • Fix the misspelled wait_for_action_attepmt_spec filename, and spec/clients/action_attempts_spec.rb which declared RSpec.describe Seam::Clients::AccessCodes.
  • Move the DeepHashAccessor spec out of lib/seam so every spec lives under spec/.

Coverage

  • Convert the api key, personal access token, env, http error, wait for action attempt, faraday options, and multi workspace specs to the fake.
  • Add serialization_spec, headers_spec, retry_spec, and defaults_spec.
  • Cover invalid input validation messages, paginator argument checks, the workspace/api-key mutual exclusion, and the publishable key rejection.

WebMock is kept for the two cases the fake cannot serve — asserting the request the SDK sends, and counting retry attempts, since a simulated outage stays in place for every request. That is the role nock plays in the JavaScript SDK.

Fix 1: the client: option could not be used on its own

SingleWorkspace#initialize parsed the auth options before it considered client, so passing only a client raised SeamInvalidOptionsError. Supplying an api_key alongside one made the option pointless, since the auth headers were built and then thrown away with the client that would have carried them.

The auth options are now parsed only when a client has to be built, so a client from another instance can be reused the way SeamHttp.fromClient allows in the JavaScript SDK.

Fix 2: wait_for_action_attempt as a hash on the client never waited

SingleWorkspace wraps its defaults in a DeepHashAccessor, which deep-wraps nested hashes. So a hash passed as wait_for_action_attempt reached decide_and_wait as an accessor rather than a Hash, the is_a?(Hash) branch was skipped, and the client returned a pending action attempt without waiting. Seam.new(wait_for_action_attempt: {timeout: 5}) never waited. The same option passed on a method call arrives as a plain Hash and did work — that asymmetry is now covered both ways.

decide_and_wait accepts either form, and DeepHashAccessor#to_h lets the wrapped value be read back as the hash it was built from.

This one is a good argument for the rest of the PR: the old spec passed only because its stub returned an already-successful action attempt, so the code path that waits was never exercised. Running against the fake exposed it immediately.

Fix 3: from_api_key and from_personal_access_token did not wait by default

Seam.new defaulted wait_for_action_attempt to true while Seam.from_api_key and Seam.from_personal_access_token defaulted it to false, so which constructor you picked silently decided whether unlock_door handed back a resolved action attempt or a pending one.

The README already documents the option as enabled by default, and the other Seam SDKs enable it by default, so this was an oversight rather than an intended difference. All three layers — Seam, Seam::Http, Seam::Http::SingleWorkspace — now default it to true. Callers who want the old behavior can still pass wait_for_action_attempt: false, and defaults_spec covers that.

Note this is a behavior change for anyone using the from_* constructors: those calls will now block until the action attempt resolves, and can raise ActionAttemptFailedError or ActionAttemptTimeoutError where they previously returned a pending attempt. That is the documented behavior and matches every other constructor, but it is the one change here that existing callers could notice.

Verification

77 examples, 0 failures

rake lint is clean.

claude added 4 commits August 6, 2026 02:55
Bring the Ruby spec suite in line with the scope and strategy used by
seamapi/javascript-http: specs are organized by SDK concern, exercise the
SDK against fake-seam-connect, and assert against seeded records rather
than hand-written stubs.

Fixture:

- Add FakeSeamConnect, which starts the fake on an unused port, polls
  /health until it is ready, reads the seed, and stops the server after
  the example. Examples opt in with `:fake`.
- Start the fake directly from node_modules/.bin rather than through
  `npm run start`, so the spawned pid is the server itself and no
  process group juggling is needed to stop it.
- Replace spec/support/helpers.rb, whose stub_seam_request helper
  encouraged stubbing over exercising the SDK.

Scope:

- Drop spec/clients, which covered generated route methods rather than
  SDK behavior. The JavaScript SDK has no equivalent specs and reaches
  its routes only as a means of exercising the client.
- Drop the overlapping request specs and the mocked action attempt
  resource spec, folding what they covered into http_error_spec and
  wait_for_action_attempt_spec against the fake.
- Exclude generated routes and resources from coverage, so the report
  stops rewarding specs for generated code.
- Fix the misspelled wait_for_action_attepmt_spec filename.

Coverage:

- Convert the api key, personal access token, env, http error, wait for
  action attempt, faraday options, and multi workspace specs to the
  fake.
- Add serialization, headers, retry, and defaults specs.
- Cover invalid input validation messages, paginator argument checks,
  and the personal access token option checks.

WebMock is kept for the two cases the fake cannot serve, asserting the
request the SDK sends and counting retry attempts, which is the role
nock plays in the JavaScript SDK.

Two specs are marked pending, both bugs the previous stubs concealed:

- The client option cannot be used on its own. initialize parses the
  auth options before it considers the client, so it still demands an
  api_key or personal_access_token.
- wait_for_action_attempt set to a hash on the client never waits.
  SingleWorkspace wraps its defaults in a DeepHashAccessor, so the value
  no longer satisfies the is_a?(Hash) check in decide_and_wait. The old
  spec passed only because its stub returned a successful action attempt
  and never had to wait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DzapiU8A9NMdyoTybL9xB
Move the DeepHashAccessor spec out of lib/seam so all specs live in one
place, and drop the extra Rakefile pattern that existed to reach it.
Rename the BaseResource hash spec after its subject, which is
BaseResource rather than DeepHashAccessor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DzapiU8A9NMdyoTybL9xB
SingleWorkspace#initialize parsed the auth options before it considered
the client, so passing only a client raised SeamInvalidOptionsError.
Supplying an api_key alongside one made the option pointless, since the
auth headers were built and then discarded with the client that would
have carried them.

Parse the auth options only when a client has to be built, so a client
from another instance can be reused the way SeamHttp.fromClient allows in
the JavaScript SDK.

Drops the pending marker from the spec that covered this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DzapiU8A9NMdyoTybL9xB
SingleWorkspace wraps its defaults in a DeepHashAccessor, so a hash
passed as wait_for_action_attempt reached decide_and_wait as an accessor
rather than a Hash. The is_a?(Hash) branch was skipped and the client
returned a pending action attempt without waiting, so
Seam.new(wait_for_action_attempt: {timeout: 5}) never waited. The same
option passed on a method call arrives as a plain Hash and did work.

Accept either form in decide_and_wait, and add DeepHashAccessor#to_h so
the wrapped value can be read back as the hash it was built from.

Drops the pending marker from the spec that covered this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DzapiU8A9NMdyoTybL9xB
@razor-x razor-x changed the title test: align spec suite with the JavaScript SDK baseline test: align spec suite with the JavaScript SDK baseline, and fix two client bugs it found Aug 6, 2026
Seam.from_api_key and Seam.from_personal_access_token defaulted
wait_for_action_attempt to false while Seam.new defaulted it to true, so
which constructor was used silently decided whether unlock_door returned
a resolved action attempt or a pending one.

The README already documents the option as enabled by default, and the
other Seam SDKs enable it by default, so the false defaults in the from_*
constructors were an oversight rather than an intended difference.

Default the option to true in Seam, Seam::Http, and
Seam::Http::SingleWorkspace so every constructor agrees. Callers who want
the old behavior can still pass wait_for_action_attempt: false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DzapiU8A9NMdyoTybL9xB
@razor-x razor-x changed the title test: align spec suite with the JavaScript SDK baseline, and fix two client bugs it found test: align spec suite with the JavaScript SDK baseline, and fix three client bugs it found Aug 6, 2026
@razor-x razor-x changed the title test: align spec suite with the JavaScript SDK baseline, and fix three client bugs it found test: align spec suite with baseline, and fix three client bugs it found Aug 6, 2026
@razor-x
razor-x merged commit 6c0eb6a into main Aug 6, 2026
17 checks passed
@razor-x
razor-x deleted the claude/ruby-python-sdk-testing-2tqetp branch August 6, 2026 04:33
razor-x added a commit that referenced this pull request Aug 6, 2026
test: restore coverage lost in #537
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.

2 participants