# frozen_string_literal: true RSpec.describe Dispatch::Adapter::Claude::ModelCatalog do describe ".build" do let(:model_id) { "claude-sonnet-4-5-20250929" } subject(:info) { described_class.build(model_id) } it "returns a ModelInfo" do expect(info).to be_a(Dispatch::Adapter::ModelInfo) end it "sets id correctly" do expect(info.id).to eq(model_id) end it "sets name to the id (no separate label in JSON)" do expect(info.name).to eq(model_id) end it "sets max_context_tokens from the pricing table" do expect(info.max_context_tokens).to eq( Dispatch::Adapter::Claude::PricingTable.context_window(model_id) ) end it "sets supports_vision to true" do expect(info.supports_vision).to be(true) end it "sets supports_tool_use to true" do expect(info.supports_tool_use).to be(true) end it "sets supports_streaming to true" do expect(info.supports_streaming).to be(true) end it "sets premium_request_multiplier to nil" do expect(info.premium_request_multiplier).to be_nil end it "sets pricing from the pricing table" do expect(info.pricing).to be_a(Dispatch::Adapter::ModelPricing) end it "falls back to 200_000 for context_window when table has no entry" do # Build with an id that is not in the table — simulate by using an # id absent from the table; ModelCatalog falls back to 200_000. # (We can't do that cleanly without a stub, so test the fallback # directly via a non-existent id.) unknown_info = described_class.build("some-future-model-not-in-table") expect(unknown_info.max_context_tokens).to eq(200_000) end it "returns nil pricing for an id not in the pricing table" do unknown_info = described_class.build("some-future-model-not-in-table") expect(unknown_info.pricing).to be_nil end end end RSpec.describe Dispatch::Adapter::Claude, "#list_models" do subject(:adapter) { described_class.allocate } it "returns a non-empty array" do expect(adapter.list_models).to be_an(Array) expect(adapter.list_models).not_to be_empty end it "every entry is a ModelInfo" do adapter.list_models.each do |info| expect(info).to be_a(Dispatch::Adapter::ModelInfo) end end it "every entry has pricing populated" do adapter.list_models.each do |info| expect(info.pricing).to be_a(Dispatch::Adapter::ModelPricing), "expected pricing on #{info.id}" end end it "Pricing.calculate works with every entry" do usage = Dispatch::Adapter::Usage.new( input_tokens: 1_000, output_tokens: 500 ) adapter.list_models.each do |info| cost = Dispatch::Adapter::Pricing.calculate(usage, info) expect(cost).to be_a(Dispatch::Adapter::UsageCost), "expected UsageCost for #{info.id}" expect(cost.total).to be >= 0 end end it "includes the three required models" do ids = adapter.list_models.map(&:id) 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