# Phase 13 — Retarget headers and request-builder specs **Estimated time:** ~25 minutes **Touches:** `spec/dispatch/adapter/minimax/headers_spec.rb`, `spec/dispatch/adapter/minimax/request_builder_spec.rb`. ## Goal Update the two specs that exercise the most heavily-modified code: the simplified `Headers.build` (phase 04) and the MiniMax-specific constraints in `RequestBuilder.build` (phases 08, 09, 10). ## Pre-reading Before editing, read these files in full so you know the current shapes: 1. `lib/dispatch/adapter/minimax/headers.rb` 2. `lib/dispatch/adapter/minimax/request_builder.rb` 3. `spec/dispatch/adapter/minimax/headers_spec.rb` 4. `spec/dispatch/adapter/minimax/request_builder_spec.rb` ## Steps for headers_spec.rb ### 1. Delete every example that asserted on removed behavior DELETE examples covering: - `claude_code_only` mode - `interleaved_thinking` flag handling - `extra_betas` merging - `anthropic-version` header presence - `x-stainless-*` header presence - `User-Agent: claude-cli/...` header - OAuth token detection (`sk-ant-oat` prefix) - The `is_oauth:` keyword - `anthropic-beta` header construction Removing these examples is NOT "weakening tests" — they target deleted code. The behavior they covered no longer exists. ### 2. Replace with a focused MiniMax-only suite The new spec should cover ONLY the new contract: ```ruby # frozen_string_literal: true require "spec_helper" RSpec.describe Dispatch::Adapter::MiniMax::Headers do describe ".build" do it "always sets Authorization to Bearer " do headers = described_class.build(api_key: "k_abc") expect(headers["Authorization"]).to eq("Bearer k_abc") end it "always sets Content-Type to application/json" do headers = described_class.build(api_key: "k_abc") expect(headers["Content-Type"]).to eq("application/json") end it "sets Accept to application/json when stream is false" do headers = described_class.build(api_key: "k_abc", stream: false) expect(headers["Accept"]).to eq("application/json") end it "sets Accept to text/event-stream when stream is true" do headers = described_class.build(api_key: "k_abc", stream: true) expect(headers["Accept"]).to eq("text/event-stream") end it "lets caller extras pass through" do headers = described_class.build(api_key: "k", extra: { "X-Trace-Id" => "abc" }) expect(headers["X-Trace-Id"]).to eq("abc") end it "never lets caller extras override Authorization" do headers = described_class.build(api_key: "k", extra: { "Authorization" => "Bearer evil" }) expect(headers["Authorization"]).to eq("Bearer k") end it "does NOT include anthropic-version, x-stainless, or User-Agent headers" do headers = described_class.build(api_key: "k") expect(headers).not_to have_key("anthropic-version") expect(headers).not_to have_key("anthropic-beta") expect(headers.keys.grep(/x-stainless/i)).to eq([]) expect(headers).not_to have_key("User-Agent") end end end ``` ## Steps for request_builder_spec.rb ### 1. Delete every example covering removed concerns DELETE examples covering: - `is_oauth:` parameter behavior (keyword removed in phase 02). - `proxy_` prefixing of tool names (cloaking deleted in phase 02). - `metadata: { user_id: ... }` cloaking-derived defaults (still emit a metadata block when caller supplies one, but no auto- generated user_id). - Billing-payload synthetic system block. - The `claude-3-5-haiku` skip-billing-block special case. ### 2. Update remaining examples to MiniMax shape For each surviving example: - Replace `"claude-sonnet-4-5-20250929"` (or whatever the old default was) with `"MiniMax-M2.7"` in `model` fields and assertions. - Remove `is_oauth:` from any `RequestBuilder.build` call. - Update `system` assertion expectations: with cloaking gone, the system block is exactly what the caller passed (string → wrapped in one text block; array → passed through; nil → omitted). ### 3. Add new examples for MiniMax constraints Append new examples that exercise the work from phases 08, 09, 10: ```ruby context "temperature validation" do it "raises ArgumentError when temperature is 0.0" do expect { described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic", temperature: 0.0) }.to raise_error(ArgumentError, /temperature/) end it "raises ArgumentError when temperature exceeds 1.0" do expect { described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic", temperature: 1.01) }.to raise_error(ArgumentError, /temperature/) end it "raises ArgumentError when temperature is negative" do expect { described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic", temperature: -0.1) }.to raise_error(ArgumentError, /temperature/) end it "accepts temperature 1.0" do body = described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic", temperature: 1.0) expect(body[:temperature]).to eq(1.0) end it "omits temperature when nil" do body = described_class.build(model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic", temperature: nil) expect(body).not_to have_key(:temperature) end end context "ignored parameters" do it "strips top_k from the wire body even if forced via extras" do body = described_class.build( model_id: "MiniMax-M2.7", messages: [], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic" ) body[:top_k] = 5 # simulate accidental injection described_class.send(:strip_ignored!, body) expect(body).not_to have_key(:top_k) end # Mirror examples for stop_sequences, service_tier, mcp_servers, # context_management, container. end context "image / document content rejection" do it "raises ArgumentError when an ImageBlock is present" do skip "interface gem must define ImageBlock" unless defined?(Dispatch::Adapter::ImageBlock) msg = Dispatch::Adapter::Message.new( role: "user", content: [Dispatch::Adapter::ImageBlock.new(source: { type: "base64", media_type: "image/png", data: "" })] ) expect { described_class.build( model_id: "MiniMax-M2.7", messages: [msg], system: nil, tools: [], base_url: "https://api.minimax.io/anthropic" ) }.to raise_error(ArgumentError, /MiniMax does not support image/) end end ``` NOTE on `skip`: the rule says "no skipped or pending examples after phase 17". If the interface gem actually exposes `ImageBlock` / `DocumentBlock`, REMOVE the `skip` and replace with concrete construction. Verify by reading `reference/dispatch-adapter-minimax/Gemfile.lock` → `dispatch-adapter-interface` source location, then grep that gem for `class ImageBlock` / `class DocumentBlock`. If they exist, write the spec without `skip`. If they don't exist, REMOVE the example entirely (do not leave a `skip`). Document briefly in a comment why no example exists for image rejection. ## Acceptance criteria - `headers_spec.rb` and `request_builder_spec.rb` both pass cleanly (no `.skip`, no `.pending`). - `grep -n 'is_oauth\|sk-ant-oat\|claude_code_only\|anthropic-beta\|x-stainless\|claude-cli\|proxy_\|interleaved_thinking\|extra_betas' spec/dispatch/adapter/minimax/headers_spec.rb spec/dispatch/adapter/minimax/request_builder_spec.rb` returns ZERO matches. - `bundle exec rubocop --autocorrect-all` exits 0. ## Verification Run `run_tests` with `project_path=reference/dispatch-adapter-minimax`. Both rubocop and the two retargeted spec files must pass cleanly. Other spec failures are acceptable here (handled in phases 14–16).