Skip to content

Commit

Permalink
Implement hex releases
Browse files Browse the repository at this point in the history
  • Loading branch information
Anil Kulkarni committed Jun 27, 2018
1 parent 038d4a9 commit 7276317
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 1 deletion.
35 changes: 35 additions & 0 deletions lib/hex.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Wand.Hex do
alias HTTPoison.{Error, Response}
@http Wand.Interfaces.Http.impl()
@base "https://hex.pm/"
@headers [{"Accept", "application/json"}]

def releases(package) do
URI.parse(@base)
|> URI.merge("/api/packages/#{URI.encode(package)}")
|> @http.get(@headers)
|> parse_response
end

defp parse_response({:ok, %Response{status_code: 200, body: body}}) do
Poison.decode(body)
|> parse_json
end

defp parse_response({:ok, %Response{status_code: 404}}) do
{:error, :not_found}
end
defp parse_response({:ok, %Response{}}), do: {:error, :bad_response}
defp parse_response({:error, %Error{}}), do: {:error, :no_connection}

defp parse_json({:ok, %{"releases" => releases}}) do
releases = Enum.map(releases, &Map.get(&1, "version"))
|> Enum.reject(&(&1 == nil or Version.parse(&1) == :error))

case releases do
[] -> {:error, :not_found}
releases -> {:ok, releases}
end
end
defp parse_json(_), do: {:error, :bad_response}
end
76 changes: 76 additions & 0 deletions test/hex_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
defmodule HexTest do
use ExUnit.Case, async: true
import Mox
alias Wand.Hex
alias HTTPoison.{Response, Error}

setup :verify_on_exit!

test "releases returns :not_found if the package isn't on hex" do
stub_http(:ok, 404, "")
assert Hex.releases("not_a_module") == {:error, :not_found}
end

test "returns :not_found if there are no releases" do
stub_http(:ok, 200, %{releases: []})
assert Hex.releases("poison") == {:error, :not_found}
end

test "releases returns :no_connection if HTTPoison returns an error" do
stub_http(:error)
assert Hex.releases("poison") == {:error, :no_connection}
end

test "returns :bad_response when the server returns non-json" do
stub_http(:ok, 200, "[NOT JSON")
assert Hex.releases("poison") == {:error, :bad_response}
end

test "returns :bad_response when the server returns a 400" do
stub_http(:ok, 400, "")
assert Hex.releases("poison") == {:error, :bad_response}
end

test "returns :bad_response when missing the releases key" do
stub_http(:ok, 200, "{}")
assert Hex.releases("poison") == {:error, :bad_response}
end

test "returns the releases" do
stub_http(:ok, 200, valid_body())
expected = {:ok, ["3.1.0", "3.0.0", "2.2.0", "2.1.0", "2.0.1", "2.0.0", "1.5.2",
"1.5.1", "1.5.0", "1.4.0", "1.3.1", "1.3.0", "1.2.1", "1.2.0",
"1.1.1", "1.1.0", "1.0.3", "1.0.2", "1.0.1", "1.0.0"]}
assert Hex.releases("poison") == expected
end

test "strips out invalid releases" do
stub_http(:ok, 200, %{
releases: [
%{version: "1.1.0"},
%{missing_version: "1.1.0"},
%{version: "ABC"},
]
})
assert Hex.releases("poison") == {:ok, ["1.1.0"]}
end

defp stub_http(:ok, status, %{}=contents), do: stub_http(:ok, status, Poison.encode!(contents))
defp stub_http(:ok, status, contents) do
response = %Response{
status_code: status,
body: contents,
}
expect(Wand.HttpMock, :get, fn _url, _headers -> {:ok, response} end)
end

defp stub_http(:error) do
# expect(Wand.HttpMock, :get, fn(url, headers) -> HTTPoison.get(url, headers) end)
response = %Error{id: nil, reason: :nxdomain}
expect(Wand.HttpMock, :get, fn(_url, _headers) -> {:error, response} end)
end

defp valid_body() do
"{\"docs_html_url\":\"https://hexdocs.pm/poison/\",\"downloads\":{\"all\":6270192,\"day\":13995,\"recent\":962319,\"week\":81020},\"html_url\":\"https://hex.pm/packages/poison\",\"inserted_at\":\"2014-08-20T04:43:51.000000\",\"meta\":{\"description\":\"An incredibly fast, pure Elixir JSON library\",\"licenses\":[\"CC0-1.0\"],\"links\":{\"GitHub\":\"https://github.com/devinus/poison\"},\"maintainers\":[\"Devin Torres\"]},\"name\":\"poison\",\"owners\":[{\"email\":\"devin@devintorr.es\",\"url\":\"https://hex.pm/api/users/devinus\",\"username\":\"devinus\"}],\"releases\":[{\"url\":\"https://hex.pm/api/packages/poison/releases/3.1.0\",\"version\":\"3.1.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/3.0.0\",\"version\":\"3.0.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/2.2.0\",\"version\":\"2.2.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/2.1.0\",\"version\":\"2.1.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/2.0.1\",\"version\":\"2.0.1\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/2.0.0\",\"version\":\"2.0.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.5.2\",\"version\":\"1.5.2\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.5.1\",\"version\":\"1.5.1\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.5.0\",\"version\":\"1.5.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.4.0\",\"version\":\"1.4.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.3.1\",\"version\":\"1.3.1\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.3.0\",\"version\":\"1.3.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.2.1\",\"version\":\"1.2.1\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.2.0\",\"version\":\"1.2.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.1.1\",\"version\":\"1.1.1\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.1.0\",\"version\":\"1.1.0\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.0.3\",\"version\":\"1.0.3\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.0.2\",\"version\":\"1.0.2\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.0.1\",\"version\":\"1.0.1\"},{\"url\":\"https://hex.pm/api/packages/poison/releases/1.0.0\",\"version\":\"1.0.0\"}],\"repository\":\"hexpm\",\"retirements\":{},\"updated_at\":\"2017-01-15T01:34:31.327060\",\"url\":\"https://hex.pm/api/packages/poison\"}"
end
end
2 changes: 1 addition & 1 deletion test/support/mocks.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Mox.defmock(Wand.FileMock, for: Wand.Interfaces.File)
Mox.defmock(Wand.HexMock, for: Wand.Interfaces.Http)
Mox.defmock(Wand.HttpMock, for: Wand.Interfaces.Http)
Mox.defmock(Wand.IOMock, for: Wand.Interfaces.IO)
Mox.defmock(Wand.SystemMock, for: Wand.Interfaces.System)

0 comments on commit 7276317

Please sign in to comment.