Files
2026-04-25 07:39:49 +01:00

172 lines
5.4 KiB
Elixir

defmodule Wikipedia.APITest do
use ExUnit.Case, async: true
alias Wikipedia.API
alias Wikipedia.API.ErrorResponse
@config %Wikipedia.Config{
user_agent: "test_agent",
req_options: [plug: {Req.Test, Wikipedia.API}, max_retries: 0],
api_cooldown: 0
}
describe "Action API silent-bug fix (HTTP 200 + body error envelope)" do
@tag :capture_log
test "promotes ratelimited Action API error into a retryable ErrorResponse" do
Req.Test.stub(API, fn conn ->
conn
|> Plug.Conn.put_resp_header("retry-after", "42")
|> Req.Test.json(%{
"error" => %{"code" => "ratelimited", "info" => "You've exceeded your rate limit."}
})
end)
assert {:error, %ErrorResponse{} = err} = API.get_wikipedia_title("Q352766", @config)
assert err.status == 200
assert err.code == "ratelimited"
assert err.kind == :rate_limit
assert err.retry_delay_seconds == 42
assert ErrorResponse.retry_delay_seconds(err) == 42
assert ErrorResponse.retryable?(err)
end
@tag :capture_log
test "promotes maxlag Action API error into a retryable ErrorResponse" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"error" => %{
"code" => "maxlag",
"info" => "Waiting for a database server: 7 seconds lagged."
}
})
end)
assert {:error, %ErrorResponse{kind: :rate_limit} = err} =
API.get_wikipedia_title("Q352766", @config)
assert err.code == "maxlag"
assert ErrorResponse.retryable?(err)
end
@tag :capture_log
test "promotes readonly Action API error into a retryable server error" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"error" => %{"code" => "readonly", "info" => "The wiki is currently in read-only mode."}
})
end)
assert {:error, %ErrorResponse{kind: :server_error} = err} =
API.get_wikipedia_title("Q352766", @config)
assert ErrorResponse.retryable?(err)
end
@tag :capture_log
test "promotes internal_api_error_* codes into retryable server errors" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"error" => %{
"code" => "internal_api_error_DBQueryError",
"info" => "[abc] Exception Caught"
}
})
end)
assert {:error, %ErrorResponse{kind: :server_error} = err} =
API.get_wikipedia_title("Q352766", @config)
assert ErrorResponse.retryable?(err)
end
@tag :capture_log
test "classifies unknown error codes as non-retryable client errors" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"error" => %{"code" => "badtoken", "info" => "Invalid CSRF token."}
})
end)
assert {:error, %ErrorResponse{kind: :client_error} = err} =
API.get_wikipedia_title("Q352766", @config)
refute ErrorResponse.retryable?(err)
end
end
describe "Action API non-error body shapes still return {:ok, _}" do
test "wbgetentities with a missing entity returns {:ok, body} — not an error" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"entities" => %{"Q99999999999" => %{"id" => "Q99999999999", "missing" => ""}}
})
end)
assert {:ok, nil} = API.get_wikipedia_title("Q99999999999", @config)
end
test "wbgetentities with no enwiki sitelink returns {:ok, nil}" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"entities" => %{"Q352766" => %{"id" => "Q352766", "sitelinks" => %{}}}
})
end)
assert {:ok, nil} = API.get_wikipedia_title("Q352766", @config)
end
test "query with empty extract returns {:ok, nil}" do
Req.Test.stub(API, fn conn ->
Req.Test.json(conn, %{
"query" => %{
"pages" => %{"123" => %{"pageid" => 123, "title" => "X", "extract" => nil}}
}
})
end)
assert {:ok, nil} = API.get_article_extract("X", @config)
end
end
describe "REST v1 API error classification" do
@tag :capture_log
test "404 on article summary surfaces as a not-found ErrorResponse" do
Req.Test.stub(API, fn conn ->
conn
|> Plug.Conn.put_status(404)
|> Req.Test.json(%{
"httpCode" => 404,
"httpReason" => "Not Found",
"messageTranslations" => %{"en" => "The specified title does not exist."}
})
end)
assert {:error, %ErrorResponse{status: 404, kind: :not_found} = err} =
API.get_article_summary("Missing%20Title", @config)
refute ErrorResponse.retryable?(err)
end
@tag :capture_log
test "429 on article summary surfaces as a retryable rate-limit ErrorResponse" do
Req.Test.stub(API, fn conn ->
conn
|> Plug.Conn.put_status(429)
|> Plug.Conn.put_resp_header("retry-after", "25")
|> Req.Test.json(%{
"httpCode" => 429,
"httpReason" => "Too Many Requests",
"messageTranslations" => %{"en" => "Slow down."}
})
end)
assert {:error, %ErrorResponse{status: 429, kind: :rate_limit} = err} =
API.get_article_summary("Steven%20Wilson", @config)
assert err.retry_delay_seconds == 25
assert ErrorResponse.retry_delay_seconds(err) == 25
assert ErrorResponse.retryable?(err)
end
end
end