# frozen_string_literal: true RSpec.describe Dispatch::Adapter::Claude::PricingTable do describe ".known_ids" do it "returns an Array of Strings" do expect(described_class.known_ids).to be_an(Array) expect(described_class.known_ids).to all(be_a(String)) end it "includes the three required models" do ids = described_class.known_ids expect(ids).to include("claude-opus-4-7-20251018") expect(ids).to include("claude-sonnet-4-5-20250929") expect(ids).to include("claude-haiku-4-5-20251001") end end describe ".lookup" do context "with a known model id" do subject(:pricing) { described_class.lookup("claude-opus-4-7-20251018") } it "returns a ModelPricing" do expect(pricing).to be_a(Dispatch::Adapter::ModelPricing) end it "has non-zero input_per_mtok" do expect(pricing.input_per_mtok).to be > 0 end it "has non-zero output_per_mtok" do expect(pricing.output_per_mtok).to be > 0 end it "has non-zero cache_read_per_mtok" do expect(pricing.cache_read_per_mtok).to be > 0 end it "has non-zero cache_write_per_mtok" do expect(pricing.cache_write_per_mtok).to be > 0 end end context "with claude-sonnet-4-5-20250929" do subject(:pricing) { described_class.lookup("claude-sonnet-4-5-20250929") } it "returns a ModelPricing with correct rates" do expect(pricing).to be_a(Dispatch::Adapter::ModelPricing) expect(pricing.input_per_mtok).to eq(3.0) expect(pricing.output_per_mtok).to eq(15.0) expect(pricing.cache_read_per_mtok).to eq(0.3) expect(pricing.cache_write_per_mtok).to eq(3.75) end end context "with claude-haiku-4-5-20251001" do subject(:pricing) { described_class.lookup("claude-haiku-4-5-20251001") } it "returns a ModelPricing with non-zero rates" do expect(pricing).to be_a(Dispatch::Adapter::ModelPricing) expect(pricing.input_per_mtok).to be > 0 expect(pricing.output_per_mtok).to be > 0 end end context "with an unknown model id" do it "returns nil" do expect(described_class.lookup("does-not-exist")).to be_nil end it "returns nil for empty string" do expect(described_class.lookup("")).to be_nil end end end describe ".context_window" do it "returns an Integer for a known model" do expect(described_class.context_window("claude-opus-4-7-20251018")).to be_an(Integer) end it "returns a positive value" do expect(described_class.context_window("claude-opus-4-7-20251018")).to be > 0 end it "returns nil for an unknown model" do expect(described_class.context_window("unknown-model")).to be_nil end end describe ".max_output_tokens" do it "returns an Integer for a known model" do expect(described_class.max_output_tokens("claude-sonnet-4-5-20250929")).to be_an(Integer) end it "returns a positive value" do expect(described_class.max_output_tokens("claude-sonnet-4-5-20250929")).to be > 0 end it "returns nil for an unknown model" do expect(described_class.max_output_tokens("unknown-model")).to be_nil end end describe "Pricing.calculate round-trip" do it "produces a valid UsageCost when fed a ModelInfo built from the table" do model_id = "claude-sonnet-4-5-20250929" pricing = described_class.lookup(model_id) model_info = Dispatch::Adapter::ModelInfo.new( id: model_id, name: "Claude Sonnet 4.5", max_context_tokens: described_class.context_window(model_id), supports_vision: true, supports_tool_use: true, supports_streaming: true, pricing: pricing ) usage = Dispatch::Adapter::Usage.new( input_tokens: 1_000_000, output_tokens: 1_000_000, cache_read_tokens: 1_000_000, cache_creation_tokens: 1_000_000 ) cost = Dispatch::Adapter::Pricing.calculate(usage, model_info) # input: 1M * $3.0/M = $3.0 # output: 1M * $15.0/M = $15.0 # cache_read: 1M * $0.3/M = $0.3 # cache_write: 1M * $3.75/M = $3.75 # total: $22.05 expect(cost).to be_a(Dispatch::Adapter::UsageCost) expect(cost.input).to eq(3.0) expect(cost.output).to eq(15.0) expect(cost.cache_read).to eq(0.3) expect(cost.cache_write).to eq(3.75) expect(cost.total).to eq(22.05) end end end