main
 1# frozen_string_literal: true
 2
 3RSpec.describe Net::Llm::Anthropic do
 4  subject(:client) do
 5    described_class.new(
 6      api_key: ENV.fetch("ANTHROPIC_API_KEY", "test-key")
 7    )
 8  end
 9
10  describe "#initialize" do
11    it "sets default model" do
12      expect(client.model).to eq("claude-sonnet-4-20250514")
13    end
14
15    it "allows custom model" do
16      custom = described_class.new(
17        api_key: ENV.fetch("ANTHROPIC_API_KEY", "test-key"),
18        model: "claude-3-opus-20240229"
19      )
20      expect(custom.model).to eq("claude-3-opus-20240229")
21    end
22  end
23
24  describe "#messages" do
25    let(:messages) { [{ role: "user", content: "Hello" }] }
26
27    context "without streaming", :vcr do
28      it "POST /v1/messages" do
29        result = client.messages(messages)
30
31        expect(result["content"]).not_to be_empty
32        expect(result["role"]).to eq("assistant")
33      end
34    end
35
36    context "with streaming", :vcr do
37      it "yields SSE events to the block" do
38        results = []
39        client.messages(messages) { |event| results << event }
40
41        expect(results).not_to be_empty
42        expect(results.last["type"]).to eq("message_stop")
43      end
44    end
45
46    context "with system prompt", :vcr do
47      it "includes system in request" do
48        result = client.messages(messages, system: "You are a helpful assistant.")
49
50        expect(result["content"]).not_to be_empty
51      end
52    end
53
54    context "with tools", :vcr do
55      let(:tools) do
56        [{
57          name: "get_weather",
58          description: "Get weather for a city",
59          input_schema: {
60            type: "object",
61            properties: { city: { type: "string" } },
62            required: ["city"]
63          }
64        }]
65      end
66
67      it "POST /v1/messages with tools" do
68        messages = [{ role: "user", content: "What is the weather in Tokyo?" }]
69        result = client.messages(messages, tools: tools)
70
71        expect(result["content"]).not_to be_empty
72        expect(result["stop_reason"]).to eq("tool_use")
73      end
74    end
75
76    context "error handling" do
77      it "returns error hash on HTTP failure" do
78        bad_client = described_class.new(api_key: "invalid-key")
79        stub_request(:post, "https://api.anthropic.com/v1/messages")
80          .to_return(status: 401, body: "Unauthorized")
81
82        result = bad_client.messages(messages)
83        expect(result["code"]).to eq("401")
84        expect(result["body"]).to eq("Unauthorized")
85      end
86    end
87  end
88end