Browse Source

Merge branch 'release/0.0.43'

* release/0.0.43: (21 commits)
  chore(docs): Take version from Mix.
  fix(logging): Remove call to Apex.
  fix(test): Opt for reducing lint issues.
  fix(indie): Make it lean.
  fix(auth): Add method to check for support of IndieAuth.
  fix(clients/indieauth): Correct method used.
  feat(auth): Add client for handling IndieAuth verification.
  Update for https/tls
  Go for a map.
  fix(mnesia): Add location.
  Pass error down.
  Bump limit.
  fix(tweaks): Updates and logic.
  Update post_encoded call.
  Ensure we encode the request.
  Patch it up for webmention sends.
  Don't compress.
  Remove unused struct def.
  Move to tesla using a patched version.
  Tweaks all around.
  ...
jackyalcine 1 month ago
parent
commit
d6d236efbb
Signed by: Jacky Alciné <yo@jacky.wtf> GPG Key ID: 537A4F904B15268D

+ 1
- 0
.gitignore View File

@@ -23,3 +23,4 @@ erl_crash.dump
23 23
 elixir-*.tar
24 24
 
25 25
 priv/dist
26
+.elixir_ls

+ 2
- 1
config/config.exs View File

@@ -1,8 +1,9 @@
1 1
 use Mix.Config
2 2
 
3 3
 config :indieweb,
4
-  http_adapter: IndieWeb.Http.Adapters.HTTPotion,
5 4
   cache_adapter: IndieWeb.Cache.Adapters.Cachex,
6 5
   auth_adapter: IndieWeb.Auth.Adapters.Default
7 6
 
7
+config :mnesia, dir: 'priv/mnesia/#{Mix.env()}/#{node()}'
8
+
8 9
 import_config "#{Mix.env()}.exs"

+ 1
- 1
config/test.exs View File

@@ -3,7 +3,7 @@ use Mix.Config
3 3
 config :indieweb,
4 4
   webmention_url_adapter: IndieWeb.Test.WebmentionUrlAdapter
5 5
 
6
-config :logger, level: :debug
6
+config :logger, level: :warn
7 7
 
8 8
 config :exvcr,
9 9
   vcr_cassette_library_dir: "test/fixtures/vcr_cassettes",

+ 1
- 1
lib/indieweb/app/microformats.ex View File

@@ -41,7 +41,7 @@ defmodule IndieWeb.App.Microformats do
41 41
   def resolve(uri) do
42 42
     case MF2.fetch(uri) do
43 43
       {:ok, mf2_data} -> do_format(mf2_data)
44
-      false -> {:error, :failed_to_fetch_h_x_app_data}
44
+      _ -> {:error, :failed_to_fetch_h_x_app_data}
45 45
     end
46 46
   end
47 47
 

+ 1
- 1
lib/indieweb/application.ex View File

@@ -6,7 +6,7 @@ defmodule IndieWeb.Application do
6 6
     import Supervisor.Spec
7 7
 
8 8
     children = [
9
-      worker(Cachex, [:indieweb, []])
9
+      worker(Cachex, [:indieweb, [limit: 100]])
10 10
     ]
11 11
 
12 12
     opts = [strategy: :one_for_one, name: IndieWeb.Supervisor]

+ 6
- 0
lib/indieweb/auth.ex View File

@@ -69,6 +69,12 @@ defmodule IndieWeb.Auth do
69 69
 
70 70
   def authenticate(_), do: {:error, :unrecognized_authorization_request}
71 71
 
72
+  def supported?(url) do
73
+    ~w(authorization token)a
74
+    |> Enum.map(&endpoint_for(&1, url))
75
+    |> Enum.all?(&is_binary/1)
76
+  end
77
+
72 78
   defp do_generate_redirect_uri(redirect_uri, code, state) do
73 79
     query =
74 80
       redirect_uri

+ 4
- 1
lib/indieweb/auth/adapters/default.ex View File

@@ -75,7 +75,10 @@ defmodule IndieWeb.Auth.Adapters.Default do
75 75
 
76 76
   @impl true
77 77
   def scope_persist(code, scope) do
78
-    :ok = IndieWeb.Cache.set(do_make_scope_key(code), scope, expire: @code_age)
78
+    case IndieWeb.Cache.set(do_make_scope_key(code), scope, expire: @code_age) do
79
+      :ok -> :ok
80
+      _ -> {:error, :scope_not_persisted}
81
+    end
79 82
   end
80 83
 
81 84
   @impl true

+ 1
- 1
lib/indieweb/auth/scope.ex View File

@@ -12,7 +12,7 @@ defmodule IndieWeb.Auth.Scope do
12 12
     do: persist!(code, __MODULE__.to_string(scope))
13 13
 
14 14
   def persist!(code, scope) do
15
-    :ok = IndieWeb.Auth.adapter().scope_persist(code, scope)
15
+    IndieWeb.Auth.adapter().scope_persist(code, scope)
16 16
   end
17 17
 
18 18
   @spec get(binary()) :: binary() | nil

+ 13
- 0
lib/indieweb/auth/token.ex View File

@@ -25,4 +25,17 @@ defmodule IndieWeb.Auth.Token do
25 25
   def info_for(token) do
26 26
     IndieWeb.Auth.adapter().token_info(token)
27 27
   end
28
+
29
+  def verify(endpoint, token) do
30
+    with(
31
+      {:ok, %IndieWeb.Http.Response{body: body, code: 200}} <-
32
+        IndieWeb.Http.get(endpoint,
33
+          headers: [{"Authorization", "Bearer #{token}"}]
34
+        ),
35
+      {:ok, json_body} <- Jason.decode(body)
36
+    ) do
37
+    else
38
+      {:error, _} -> false
39
+    end
40
+  end
28 41
 end

+ 22
- 0
lib/indieweb/clients/indieauth.ex View File

@@ -0,0 +1,22 @@
1
+defmodule IndieWeb.Clients.IndieAuth do
2
+  def verify_token(token_endpoint, token) do
3
+    with(
4
+      {:ok, %IndieWeb.Http.Response{body: body, code: 200}} <-
5
+        IndieWeb.Http.get(token_endpoint,
6
+          headers: [
7
+            {"Authorization", "Bearer #{token}"},
8
+            {"Content-Type", "application/json"}
9
+          ]
10
+        ),
11
+      {:ok, json_body} <- Jason.decode(body)
12
+    ) do
13
+      {:ok, json_body}
14
+    else
15
+      {:ok, %IndieWeb.Http.Response{} = resp} ->
16
+        {:error, :invalid_request, response: resp}
17
+
18
+      {:error, error} ->
19
+        {:error, :invalid_response, error: inspect(error)}
20
+    end
21
+  end
22
+end

+ 2
- 0
lib/indieweb/hcard.ex View File

@@ -55,6 +55,7 @@ defmodule IndieWeb.HCard do
55 55
 
56 56
   [1]: http://microformats.org/wiki/representative-h-card-parsing
57 57
   """
58
+  @spec fetch_representative(binary()) :: nil | {:ok, map()}
58 59
   def fetch_representative(uri) when is_binary(uri) do
59 60
     case Microformats2.Utility.fetch(uri) do
60 61
       {:ok, mf2} -> fetch_representative(mf2, uri)
@@ -62,6 +63,7 @@ defmodule IndieWeb.HCard do
62 63
     end
63 64
   end
64 65
 
66
+  @spec fetch_representative(map(), binary()) :: nil | {:ok, map()}
65 67
   def fetch_representative(mf2, uri) do
66 68
     %{scheme: scheme, authority: authority} =
67 69
       URI.parse(uri |> String.trim_trailing("/"))

+ 90
- 56
lib/indieweb/http.ex View File

@@ -1,67 +1,49 @@
1 1
 defmodule IndieWeb.Http do
2
-  @moduledoc """
3
-  Provides a facade for handling HTTP actions.
4
-  """
2
+  defmodule Error do
3
+    @moduledoc "Defines an error obtained when making a network request."
4
+    @enforce_keys ~w(message)a
5 5
 
6
-  def timeout, do: 10_000
6
+    @typedoc "Representative type of errors with `IndieWeb.Http`."
7
+    @type t :: %__MODULE__{message: any(), raw: any()}
7 8
 
8
-  @doc "Obtains an implementation of a `IndieWeb.Http.Adapter` module."
9
-  @spec adapter() :: IndieWeb.HTTP.Adapter.t()
10
-  def adapter,
11
-    do:
12
-      Application.get_env(
13
-        :indieweb,
14
-        :http_adapter,
15
-        IndieWeb.Http.Adapters.HTTPotion
16
-      )
17
-
18
-  @doc "Sends a HTTP request to the URI `uri` with the provided options."
19
-  @spec request(binary(), atom(), keyword()) ::
20
-          {:ok, IndieWeb.Http.Response.t()} | {:error, IndieWeb.Http.Error.t()}
21
-  def request(uri, method \\ :get, opts \\ []) do
22
-    adapter().request(uri, method, opts)
9
+    defstruct ~w(message raw)a
23 10
   end
24 11
 
25
-  for method <- ~w(get post options head put patch delete)a do
26
-    @doc """
27
-    Sends a #{String.upcase(Atom.to_string(method))} request to the specified URI.
12
+  defmodule Response do
13
+    @moduledoc "Defines a response obtained when making a network request."
14
+    @enforce_keys ~w(code body url headers raw)a
15
+    defstruct ~w(body code url headers rels raw)a
28 16
 
29
-    See `request/3` for more information about making requests.
30
-    """
31
-    def unquote(method)(uri, opts \\ []),
32
-      do: IndieWeb.Http.request(uri, unquote(method), opts)
17
+    @type t :: %__MODULE__{
18
+            body: String.t(),
19
+            code: non_neg_integer,
20
+            url: String.t(),
21
+            headers: Access.t(),
22
+            rels: map(),
23
+            raw: any()
24
+          }
33 25
   end
34 26
 
35
-  def extract_link_header_values(headers) do
36
-    Map.take(headers, ["link", "Link"])
37
-    |> Map.values()
38
-    |> List.flatten()
39
-    |> Enum.map(fn header -> String.split(header, ",", trim: true) end)
40
-    |> List.flatten()
41
-    |> Enum.map(fn header ->
42
-      [rel, value] =
43
-        header
44
-        |> String.trim()
45
-        |> String.split(";")
46
-        |> Enum.reverse()
47
-        |> Enum.map(fn part -> String.trim(part) end)
48
-
49
-      rel_values =
50
-        rel
51
-        |> String.split("=")
52
-        |> List.last()
53
-        |> String.trim("\"")
54
-        |> String.split()
55
-
56
-      link_value =
57
-        value |> String.trim_leading("<") |> String.trim_trailing(">")
58
-
59
-      Enum.map(rel_values, fn rel_value -> {rel_value, link_value} end)
60
-    end)
61
-    |> List.flatten()
62
-    |> Enum.reduce(%{}, fn {key, val}, acc ->
63
-      Map.put(acc, key, Enum.sort(Map.get(acc, key, []) ++ [val]))
64
-    end)
27
+  defmodule Client do
28
+    use Tesla
29
+
30
+    adapter(Tesla.Adapter.Hackney,
31
+      recv_timeout: 30_000,
32
+      ssl: [
33
+        versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
34
+      ]
35
+    )
36
+
37
+    plug(Tesla.Middleware.Logger)
38
+    plug(Tesla.Middleware.RequestId)
39
+    plug(Tesla.Middleware.KeepRequest)
40
+    plug(Tesla.Middleware.FollowRedirects)
41
+    plug(Tesla.Middleware.DecodeRels)
42
+
43
+    plug(Tesla.Middleware.Headers, [
44
+      {"user-agent",
45
+       "IndieWeb-Elixir/#{Mix.project()[:version]}-#{Mix.env()} (https://git.jacky.wtf/indieweb/elixir)"}
46
+    ])
65 47
   end
66 48
 
67 49
   def make_absolute_uri(path, _) when path in ["", nil], do: path
@@ -72,4 +54,56 @@ defmodule IndieWeb.Http do
72 54
 
73 55
   def make_absolute_uri(path, base_uri) when is_binary(path),
74 56
     do: URI.merge(base_uri, path) |> URI.to_string()
57
+
58
+  def extract_link_header_values(%Response{} = resp),
59
+    do:
60
+      resp
61
+      |> Map.get(:raw, %{})
62
+      |> Map.get(:opts, [])
63
+      |> Keyword.get(:rels, %{}) || %{}
64
+
65
+  def request(url, method \\ :get, opts \\ [])
66
+
67
+  def request(url, method, opts) when is_binary(url) do
68
+    case IndieWeb.Http.Client.request([url: url, method: method] ++ opts) do
69
+      {:ok, %Tesla.Env{} = env} ->
70
+        {:ok,
71
+         %IndieWeb.Http.Response{
72
+           raw: env,
73
+           code: env.status,
74
+           body: env.body,
75
+           rels: env.opts[:rels] || %{},
76
+           headers: env.headers,
77
+           url: env.url
78
+         }}
79
+
80
+      {:error, error} ->
81
+        {:error, %IndieWeb.Http.Error{message: error, raw: nil}}
82
+    end
83
+  end
84
+
85
+  def request(_url, _method, _opts), do: {:error, :no_url_provided}
86
+
87
+  for method <- ~w(get post options head put patch delete)a do
88
+    @doc """
89
+    Sends a #{String.upcase(Atom.to_string(method))} request to the specified URL.
90
+
91
+    See `request/3` for more information about making requests.
92
+    """
93
+    def unquote(method)(url, opts \\ []),
94
+      do: IndieWeb.Http.request(url, unquote(method), opts)
95
+  end
96
+
97
+  def post_encoded(url, opts) do
98
+    post(
99
+      url,
100
+      opts ++
101
+        [
102
+          body: URI.encode_query(opts[:body]),
103
+          headers:
104
+            Keyword.get(opts, :headers, []) ++
105
+              [{"Content-Type", "application/x-www-form-urlencoded"}]
106
+        ]
107
+    )
108
+  end
75 109
 end

+ 0
- 7
lib/indieweb/http/adapter.ex View File

@@ -1,7 +0,0 @@
1
-defmodule IndieWeb.Http.Adapter do
2
-  @moduledoc "Provides an abstraction on handling HTTP actions."
3
-  @doc "Defines the method for making a general HTTP request."
4
-  @callback request(uri :: binary(), method :: atom(), opts :: keyword()) ::
5
-              {:ok, IndieWeb.Http.Response.t()}
6
-              | {:error, IndieWeb.Http.Error.t()}
7
-end

+ 0
- 33
lib/indieweb/http/adapters/httpotion.ex View File

@@ -1,33 +0,0 @@
1
-defmodule IndieWeb.Http.Adapters.HTTPotion do
2
-  @behaviour IndieWeb.Http.Adapter
3
-
4
-  @impl true
5
-  def request(uri, method, opts) do
6
-    options =
7
-      [
8
-        timeout: Keyword.get(opts, :timeout, IndieWeb.Http.timeout()),
9
-        follow_redirects: true,
10
-        auto_sni: true,
11
-        headers: Keyword.get(opts, :headers, %{}) |> Map.to_list() || nil,
12
-        body: Keyword.get(opts, :body, %{}) |> URI.encode_query() || nil,
13
-        query: Keyword.get(opts, :query, nil)
14
-      ]
15
-      |> Enum.reject(fn {_, v} -> is_nil(v) end)
16
-      |> Keyword.new()
17
-
18
-    case HTTPotion.request(method, uri, options) do
19
-      %HTTPotion.ErrorResponse{} = err_resp ->
20
-        {:error, %IndieWeb.Http.Error{message: err_resp.message, raw: err_resp}}
21
-
22
-      %HTTPotion.Response{status_code: code, body: body, headers: headers} =
23
-          resp ->
24
-        {:ok,
25
-         %IndieWeb.Http.Response{
26
-           code: code,
27
-           body: body,
28
-           headers: headers.hdrs,
29
-           raw: resp
30
-         }}
31
-    end
32
-  end
33
-end

+ 0
- 9
lib/indieweb/http/error.ex View File

@@ -1,9 +0,0 @@
1
-defmodule IndieWeb.Http.Error do
2
-  @moduledoc "Defines an error obtained when making a network request."
3
-  @enforce_keys ~w(message)a
4
-
5
-  @typedoc "Representative type of errors with `IndieWeb.Http`."
6
-  @type t :: %__MODULE__{message: any(), raw: any()}
7
-
8
-  defstruct ~w(message raw)a
9
-end

+ 0
- 12
lib/indieweb/http/response.ex View File

@@ -1,12 +0,0 @@
1
-defmodule IndieWeb.Http.Response do
2
-  @moduledoc "Defines a response obtained when making a network request."
3
-  @enforce_keys ~w(code body headers raw)a
4
-  defstruct ~w(body code headers raw)a
5
-
6
-  @type t :: %__MODULE__{
7
-          body: binary(),
8
-          code: non_neg_integer,
9
-          headers: Access.t(),
10
-          raw: any()
11
-        }
12
-end

+ 15
- 6
lib/indieweb/link_rel.ex View File

@@ -5,25 +5,33 @@ defmodule IndieWeb.LinkRel do
5 5
 
6 6
   def find(url, value) do
7 7
     with(
8
-      {:ok, %IndieWeb.Http.Response{code: code, body: body, headers: headers}}
9
-      when code < 299 and code >= 200 <- IndieWeb.Http.get(url)
8
+      {:ok, %IndieWeb.Http.Response{body: body, rels: rels, code: code}}
9
+      when code < 400 <- IndieWeb.Http.get(url)
10 10
     ) do
11
-      header_endpoints =
12
-        IndieWeb.Http.extract_link_header_values(headers) |> Map.get(value, [])
11
+      matching_rel_keys =
12
+        rels
13
+        |> Map.keys()
14
+        |> Enum.filter(fn rel_str ->
15
+          rel_str |> String.split(" ") |> Enum.member?(value)
16
+        end)
17
+
18
+      prefetched_rel_values =
19
+        Map.take(rels, matching_rel_keys) |> Map.values() |> List.flatten()
13 20
 
14 21
       rel_endpoints =
15 22
         case Microformats2.parse(body, url) do
16 23
           %{rel_urls: rel_url_map} ->
17 24
             Enum.filter(rel_url_map, fn {_url, %{rels: rels}} ->
18
-              value in rels
25
+              value in rels || Enum.any?(rels, &String.contains?(&1, value))
19 26
             end)
20 27
             |> Enum.map(fn {key, _} -> key end)
28
+            |> Enum.uniq()
21 29
 
22 30
           _ ->
23 31
             []
24 32
         end
25 33
 
26
-      (header_endpoints ++ rel_endpoints)
34
+      (prefetched_rel_values ++ rel_endpoints)
27 35
       |> Enum.map(&do_normalize_url(&1, url))
28 36
     else
29 37
       _ -> []
@@ -42,6 +50,7 @@ defmodule IndieWeb.LinkRel do
42 50
           !String.starts_with?(url, "/") ->
43 51
         page_url <> "/" <> url
44 52
 
53
+      # Empty path is the same URL.
45 54
       url == "" ->
46 55
         page_url
47 56
 

+ 12
- 0
lib/indieweb/url.ex View File

@@ -0,0 +1,12 @@
1
+defmodule IndieWeb.URL do
2
+  def canonalize(url)
3
+  def canonalize(%URI{path: path} = url) when path in [nil, ""], do: canonalize(URI.merge(url, "/"))
4
+  def canonalize(url), do: url
5
+
6
+  def resolve_redirect(url) do
7
+    case IndieWeb.Http.get(url) do
8
+      {:ok, %{url: resolved_url}} -> resolved_url
9
+      _ -> url
10
+    end
11
+  end
12
+end

+ 22
- 10
lib/indieweb/webmention.ex View File

@@ -76,12 +76,12 @@ defmodule IndieWeb.Webmention do
76 76
 
77 77
   [1]: https://www.w3.org/TR/webmention
78 78
   """
79
-  @spec send(binary(), any()) ::
79
+  @spec send(binary(), any(), map()) ::
80 80
           {:ok, IndieWeb.Webmention.SendResponse.t()} | {:error, any()}
81
-  def send(target_url, source) do
81
+  def send(target_url, source, opts \\ %{}) do
82 82
     with(
83 83
       {:ok, endpoint_url} <- discover_endpoint(target_url),
84
-      {:ok, resp} <- direct_send!(endpoint_url, target_url, source)
84
+      {:ok, resp} <- direct_send!(endpoint_url, target_url, source, opts)
85 85
     ) do
86 86
       {:ok, resp}
87 87
     else
@@ -92,16 +92,19 @@ defmodule IndieWeb.Webmention do
92 92
   @doc """
93 93
   Sends out a Webmention to the provided `endpoint` for `target` from `source`.
94 94
   """
95
-  @spec send(binary(), any()) ::
95
+  @spec direct_send!(binary(), binary(), any(), map()) ::
96 96
           {:ok, IndieWeb.Webmention.SendResponse.t()} | {:error, any()}
97
-  def direct_send!(endpoint, target_url, source) do
97
+  def direct_send!(endpoint, target_url, source, opts) do
98 98
     with(
99 99
       {:ok, source_url} <- resolve_source_url(source),
100 100
       {:ok, %IndieWeb.Http.Response{code: code, body: body, headers: headers}}
101 101
       when code >= 200 and code < 400 <-
102
-        IndieWeb.Http.post(endpoint,
103
-          body: %{"source" => source_url, "target" => target_url},
104
-          headers: %{"Content-Type" => "application/x-www-form-urlencoded"}
102
+        IndieWeb.Http.post_encoded(endpoint,
103
+          body:
104
+            Map.merge(
105
+              %{"source" => URI.to_string(source_url), "target" => target_url},
106
+              opts
107
+            )
105 108
         )
106 109
     ) do
107 110
       send_resp = %SendResponse{
@@ -127,10 +130,19 @@ defmodule IndieWeb.Webmention do
127 130
   a valid action to take from it.
128 131
   """
129 132
   @spec receive(map()) :: {:ok, [action: atom(), args: map()]} | {:error, any()}
130
-  def receive([source: source_url, target: target_url] = _args) do
133
+  def receive(args) do
134
+    source_url = args[:source]
135
+    target_url = args[:target]
136
+
131 137
     case resolve_target_from_url(target_url) do
132 138
       {:ok, target} ->
133
-        {:ok, [source: source_url, target: target, target_url: target_url]}
139
+        {:ok,
140
+         [
141
+           source: source_url,
142
+           target: target,
143
+           target_url: target_url,
144
+           args: Keyword.drop(args, ~w(source target)a)
145
+         ]}
134 146
 
135 147
       {:error, error} ->
136 148
         {:error, :webmention_receive_failure, reason: error}

+ 12
- 0
lib/indieweb/webmention/vouch.ex View File

@@ -0,0 +1,12 @@
1
+defmodule IndieWeb.Webmention.Vouch do
2
+  def valid?(vouch, remote_url) do
3
+    # TODO: Fetch MF2 of page at 'vouch'.
4
+    # TODO: Get re h-card of 'remote_url'.
5
+    with(
6
+      {:ok, mf2} <- Microformats2.Utility.fetch(vouch),
7
+      {:ok, hcard} <- IndieWeb.HCard.resolve(remote_url)
8
+    ) do
9
+      hcards = Microformats2.Utility.extract_deep(mf2, "card")
10
+    end
11
+  end
12
+end

+ 14
- 7
mix.exs View File

@@ -7,8 +7,8 @@ defmodule IndieWeb.MixProject do
7 7
       aliases: aliases(),
8 8
       app: :indieweb,
9 9
       name: "IndieWeb",
10
-      version: "0.0.42",
11
-      elixir: "~> 1.6",
10
+      version: "0.0.43",
11
+      elixir: "~> 1.7",
12 12
       elixirc_paths: elixirc_paths(Mix.env()),
13 13
       start_permanent: Mix.env() == :prod,
14 14
       build_embedded: Mix.env() == :prod,
@@ -35,7 +35,7 @@ defmodule IndieWeb.MixProject do
35 35
   def application do
36 36
     [
37 37
       mod: {IndieWeb.Application, []},
38
-      extra_applications: [:logger, :cachex, :runtime_tools, :httpotion]
38
+      extra_applications: [:logger, :cachex, :runtime_tools]
39 39
     ]
40 40
   end
41 41
 
@@ -43,11 +43,18 @@ defmodule IndieWeb.MixProject do
43 43
     [
44 44
       {:apex, "~> 1.2.1", only: [:dev, :test]},
45 45
       {:cachex, "~> 3.1.0"},
46
+      {:ex_doc, "~> 0.19", only: [:dev, :test], runtime: false},
46 47
       {:excoveralls, "~> 0.10.0", only: [:test]},
47
-      {:ex_doc, "~> 0.19", override: true},
48
-      {:exvcr, "~> 0.10.0", only: :test},
49
-      {:faker, "~> 0.12.0", only: :test},
50
-      {:microformats2, "~> 0.2.0"}
48
+      {:exvcr, "~> 0.10.0", only: :test, runtime: false},
49
+      {:faker, "~> 0.12.0", only: :test, runtime: false},
50
+      {:jason, "~> 1.0"},
51
+      {:hackney, "~> 1.15.1"},
52
+      {:microformats2, "~> 0.2.0"},
53
+      {:tesla,
54
+       git: "https://github.com/jalcine/tesla",
55
+       branch: "jalcine/check-regex-run-results",
56
+       override: true},
57
+      {:tesla_request_id, "~> 0.2.0"}
51 58
     ]
52 59
   end
53 60
 

+ 13
- 13
mix.lock View File

@@ -1,35 +1,35 @@
1 1
 %{
2 2
   "apex": {:hex, :apex, "1.2.1", "297f5dac23fa2a32648b890a0838fce2772114010e0b9ec975cae6021cc5a092", [:mix], [], "hexpm"},
3
-  "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
4 3
   "cachex": {:hex, :cachex, "3.1.3", "86ed0669ea4b2f3e3982dbb5c6ca9e0964e46738e572c9156f22ceb75f57c336", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
5 4
   "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
6
-  "credo": {:hex, :credo, "1.0.3", "5278e8953f379b41ebe27c75c96d2e154ebc3f75dfe057c8b68c39133c25bb9f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
7
-  "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"},
5
+  "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
8 6
   "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
9
-  "ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
7
+  "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
10 8
   "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"},
11 9
   "excoveralls": {:hex, :excoveralls, "0.10.6", "e2b9718c9d8e3ef90bc22278c3f76c850a9f9116faf4ebe9678063310742edc2", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
12 10
   "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
13
-  "exvcr": {:hex, :exvcr, "0.10.3", "1ae3b97560430acfa88ebc737c85b2b7a9dbacd8a2b26789a19718b51ae3522c", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
11
+  "exvcr": {:hex, :exvcr, "0.10.4", "ba7ff59af5625c0bad41909b7a94599523c09bbefba5e1d85fb7643889965204", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
14 12
   "faker": {:hex, :faker, "0.12.0", "796cbac868c86c2df6f273ea4cdf2e271860863820e479e04a374b7ee6c376b6", [:mix], [], "hexpm"},
15
-  "floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
16
-  "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
13
+  "floki": {:hex, :floki, "0.23.0", "956ab6dba828c96e732454809fb0bd8d43ce0979b75f34de6322e73d4c917829", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"},
14
+  "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
17 15
   "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
18
-  "httpotion": {:hex, :httpotion, "3.1.1", "b8ad199dea2c56a70c89e7f9e4d09898c7e85871783b7417c04cb4f1d4d8e919", [:mix], [{:ibrowse, "== 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm"},
19
-  "ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [:rebar3], [], "hexpm"},
20 16
   "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
21
-  "inch_ex": {:git, "https://github.com/rrrene/inch_ex.git", "45ecbc4b57fa8b943b9e4cd0d4da72da95c9dd4a", []},
22 17
   "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
23 18
   "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
24 19
   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm"},
20
+  "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
21
+  "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
25 22
   "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
26 23
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
27
-  "microformats2": {:hex, :microformats2, "0.2.1", "452e8749566500ea70fcaa9b5680b00a20803aa8d1e490f8f84ae3c38a30799a", [:mix], [{:floki, "~> 0.7", [hex: :floki, repo: "hexpm", optional: false]}, {:httpotion, "~> 3.0", [hex: :httpotion, repo: "hexpm", optional: false]}], "hexpm"},
24
+  "microformats2": {:hex, :microformats2, "0.2.2", "ebd2a6973b13d00c373762e0a1e1926a3e192860e57694e71222c2803711fc4b", [:mix], [{:floki, "~> 0.7", [hex: :floki, repo: "hexpm", optional: false]}, {:httpotion, "~> 3.0", [hex: :httpotion, repo: "hexpm", optional: true]}], "hexpm"},
25
+  "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
28 26
   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
29
-  "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
27
+  "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
30 28
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
31 29
   "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm"},
32
-  "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
30
+  "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
31
+  "tesla": {:git, "https://github.com/jalcine/tesla", "390a267a84d3b535f12e5efb6f58d66f157de57b", [branch: "jalcine/check-regex-run-results"]},
32
+  "tesla_request_id": {:hex, :tesla_request_id, "0.2.0", "9745364ed3c850864fb2744daefb9b7104fe7f1efcf34eb350c61da805de2a46", [:mix], [{:tesla, "~> 1.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
33 33
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
34 34
   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},
35 35
 }

+ 73
- 0
test/fixtures/vcr_cassettes/webmention_vouch_indieweb_chatnames.json
File diff suppressed because it is too large
View File


+ 7
- 0
test/support/http.ex View File

@@ -0,0 +1,7 @@
1
+defmodule IndieWeb.HttpMock do
2
+  defmacro __using__(_) do
3
+    quote do
4
+      use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
5
+    end
6
+  end
7
+end

+ 5
- 0
test/test_helper.exs View File

@@ -1,3 +1,8 @@
1
+~w(cachex faker)a
2
+|> Enum.each(fn app ->
3
+  {:ok, _pid} = Application.ensure_all_started(app)
4
+end)
5
+
1 6
 ExUnit.start()
2 7
 
3 8
 ExUnit.configure(

+ 1
- 1
test/unit/indieweb/app_test.exs View File

@@ -3,7 +3,7 @@ defmodule IndieWeb.AppTest do
3 3
   use ExVCR.Mock
4 4
   alias IndieWeb.App, as: Subject
5 5
 
6
-  @uri Faker.Internet.url()
6
+  @uri "https://fake.uri/for-real"
7 7
 
8 8
   describe ".clear/1" do
9 9
     test "clears app information from cache" do

+ 58
- 2
test/unit/indieweb/auth_test.exs View File

@@ -1,6 +1,6 @@
1 1
 defmodule IndieWeb.AuthTest do
2 2
   use IndieWeb.TestCase, async: true
3
-  use ExVCR.Mock
3
+  use IndieWeb.HttpMock
4 4
   alias IndieWeb.Auth, as: Subject
5 5
 
6 6
   describe ".endpoint_for/2" do
@@ -14,6 +14,16 @@ defmodule IndieWeb.AuthTest do
14 14
         assert @endpoint =
15 15
                  Subject.endpoint_for(:authorization, "https://foobar.com")
16 16
       end
17
+
18
+      no_html = "<html></html>"
19
+
20
+      use_cassette :stub,
21
+        uri: "~r/*/",
22
+        body: no_html,
23
+        headers: %{"Link" => "<#{@endpoint}>; rel=\"authorization_endpoint\""} do
24
+        assert @endpoint =
25
+                 Subject.endpoint_for(:authorization, "https://foobar.com")
26
+      end
17 27
     end
18 28
 
19 29
     test "authorization endpoint - finds none for site" do
@@ -31,13 +41,22 @@ defmodule IndieWeb.AuthTest do
31 41
       use_cassette :stub, uri: "~r/*/", body: html do
32 42
         assert @endpoint = Subject.endpoint_for(:token, "https://foobar.com")
33 43
       end
44
+
45
+      no_html = "<html></html>"
46
+
47
+      use_cassette :stub,
48
+        uri: "~r/*/",
49
+        body: no_html,
50
+        headers: %{"Link" => "<#{@endpoint}>; rel=\"token_endpoint\""} do
51
+        assert @endpoint = Subject.endpoint_for(:token, "https://foobar.com")
52
+      end
34 53
     end
35 54
 
36 55
     test "token endpoint - finds none for site" do
37 56
       html = "<html><head></head></html>"
38 57
 
39 58
       use_cassette :stub, uri: "~r/*/", body: html do
40
-        refute Subject.endpoint_for(:authorization, "https://foobar.com")
59
+        refute Subject.endpoint_for(:token, "https://foobar.com")
41 60
       end
42 61
     end
43 62
   end
@@ -93,4 +112,41 @@ defmodule IndieWeb.AuthTest do
93 112
       assert %{"state" => "state", "code" => _} = obtained_query_params
94 113
     end
95 114
   end
115
+
116
+  describe ".supported?/1" do
117
+    test "fails if no authorization endpoint was found" do
118
+      html =
119
+        "<html><head><link rel='token_endpoint' href='#{Faker.Internet.url()}' /></head></html>"
120
+
121
+      use_cassette :stub, uri: "~r/*/", body: html do
122
+        refute Subject.supported?(Faker.Internet.url())
123
+      end
124
+    end
125
+
126
+    test "fails if no token endpoint was found" do
127
+      html =
128
+        "<html><head><link rel='authorization_endpoint' href='#{
129
+          Faker.Internet.url()
130
+        }' /></head></html>"
131
+
132
+      use_cassette :stub, uri: "~r/*/", body: html do
133
+        refute Subject.supported?(Faker.Internet.url())
134
+      end
135
+    end
136
+
137
+    test "passes if both endpoints were found" do
138
+      html = """
139
+        <html>
140
+          <head>
141
+            <link rel="authorization_endpoint" href="#{Faker.Internet.url()}" />
142
+            <link rel="token_endpoint" href="#{Faker.Internet.url()}" />
143
+          </head>
144
+        </html>
145
+      """
146
+
147
+      use_cassette :stub, uri: "~r/*/", body: html do
148
+        assert Subject.supported?(Faker.Internet.url())
149
+      end
150
+    end
151
+  end
96 152
 end

+ 50
- 0
test/unit/indieweb/clients/indieauth_test.exs View File

@@ -0,0 +1,50 @@
1
+defmodule IndieWeb.Clients.IndieAuthTest do
2
+  use IndieWeb.TestCase, async: false
3
+  use IndieWeb.HttpMock
4
+  alias IndieWeb.Clients.IndieAuth, as: Subject
5
+  doctest Subject
6
+
7
+  describe ".verify/2" do
8
+    test "returns true if endpoint verifies token successfully" do
9
+      url = Faker.Internet.url()
10
+      body = Jason.encode!(%{"me" => url})
11
+
12
+      use_cassette :stub,
13
+        uri: "~r/*/",
14
+        method: "get",
15
+        headers: %{"content-type" => "application/json"},
16
+        body: body do
17
+        assert {:ok, %{"me" => ^url}} =
18
+                 Subject.verify_token(Faker.Internet.url(), Faker.Lorem.word())
19
+      end
20
+    end
21
+
22
+    test "returns false if endpoint fails out" do
23
+      url = Faker.Internet.url()
24
+      body = Jason.encode!("invalid_user")
25
+
26
+      use_cassette :stub,
27
+        uri: "~r/*/",
28
+        method: "get",
29
+        status_code: 400,
30
+        headers: %{"content-type" => "application/json"},
31
+        body: body do
32
+        assert {:error, _, _} = Subject.verify_token(url, Faker.Lorem.word())
33
+      end
34
+    end
35
+
36
+    test "returns false if endpoint denies token" do
37
+      url = Faker.Internet.url()
38
+      body = Jason.encode!(%{"error" => "invalid_user"})
39
+
40
+      use_cassette :stub,
41
+        uri: "~r/*/",
42
+        method: "get",
43
+        status_code: 401,
44
+        headers: %{"content-type" => "application/json"},
45
+        body: body do
46
+        assert {:error, _, _} = Subject.verify_token(url, Faker.Lorem.word())
47
+      end
48
+    end
49
+  end
50
+end

+ 3
- 5
test/unit/indieweb/hcard_test.exs View File

@@ -1,6 +1,6 @@
1 1
 defmodule IndieWeb.HCardTest do
2 2
   use IndieWeb.TestCase, async: false
3
-  use ExVCR.Mock
3
+  use IndieWeb.HttpMock
4 4
   alias IndieWeb.HCard, as: Subject
5 5
 
6 6
   @url "https://indieweb.card"
@@ -37,7 +37,7 @@ defmodule IndieWeb.HCardTest do
37 37
       <html>
38 38
       <body>
39 39
       <div class="h-card">
40
-      <img class="u-photo" src="/photo" alt="#{Faker.Lorem.sentence()}" />
40
+      <img class="u-photo" src="/photo" alt="A random sentence goes here." />
41 41
       <a href="#{@url}" class="u-url u-uid">
42 42
       <span class="p-name">#{@hcard["name"]}</span>
43 43
       </a>
@@ -56,9 +56,7 @@ defmodule IndieWeb.HCardTest do
56 56
       <html>
57 57
       <body>
58 58
       <div class="h-card">
59
-      <img class="u-photo" src="#{@hcard["photo"]}" alt="#{
60
-        Faker.Lorem.sentence()
61
-      }" />
59
+      <img class="u-photo" src="#{@hcard["photo"]}" alt="A random sentence goes here." />
62 60
       <a href="#{@url}" class="u-url u-uid">
63 61
       <span class="p-name">#{@hcard["name"]}</span>
64 62
       </a>

+ 17
- 17
test/unit/indieweb/http_test.exs View File

@@ -1,19 +1,13 @@
1 1
 defmodule IndieWeb.HttpTest do
2 2
   use IndieWeb.TestCase, async: false
3
-  use ExVCR.Mock
3
+  use IndieWeb.HttpMock
4 4
   alias IndieWeb.Http, as: Subject
5 5
   doctest Subject
6 6
 
7
-  describe ".adapter/0" do
8
-    test "defaults to using HTTPotion" do
9
-      assert Subject.adapter() == IndieWeb.Http.Adapters.HTTPotion
10
-    end
11
-  end
12
-
13 7
   describe ".request/2" do
14 8
     test "successfully sends a HTTP GET request by default" do
15 9
       use_cassette :stub, uri: "~r/*", method: :get do
16
-        assert {:ok, %IndieWeb.Http.Response{code: 200}} =
10
+        assert {:ok, %Subject.Response{code: 200}} =
17 11
                  Subject.request("https://indieweb.org")
18 12
       end
19 13
     end
@@ -21,17 +15,27 @@ defmodule IndieWeb.HttpTest do
21 15
     for method <- ~w(get post options head put patch delete)a do
22 16
       test "successfully sends a HTTP #{method} request" do
23 17
         use_cassette :stub, uri: "~r/*", method: unquote(method) do
24
-          assert {:ok, %IndieWeb.Http.Response{}} =
18
+          assert {:ok, resp} =
25 19
                    Subject.request("https://indieweb.org", unquote(method))
26 20
 
27
-          assert {:ok, %IndieWeb.Http.Response{}} =
21
+          assert {:ok, %Subject.Response{}} =
28 22
                    Subject.unquote(method)("https://indieweb.org")
29 23
         end
30 24
       end
31 25
     end
32 26
   end
33 27
 
28
+  describe ".post_encoded/2" do
29
+    test "sends a encoded request" do
30
+      assert {:ok, resp} =
31
+               Subject.post_encoded("http://httpbin.org/anything",
32
+                 body: %{"test" => "data"}
33
+               )
34
+    end
35
+  end
36
+
34 37
   describe ".extract_link_rel_from_headers/1" do
38
+    # TODO: There's a bug in Telsa that doesn't allow for multiple rel values.
35 39
     test "extracts values from response" do
36 40
       use_cassette :stub,
37 41
         uri: "~r/*/",
@@ -48,15 +52,11 @@ defmodule IndieWeb.HttpTest do
48 52
               "<https://v2.jacky.wtf/api/indie/token>; rel=\"token_endpoint\""
49 53
         } do
50 54
         assert {:ok, resp} = IndieWeb.Http.head("https://v2.jacky.wtf")
51
-        assert values = IndieWeb.Http.extract_link_header_values(resp.headers)
52
-        assert %{"self" => ["https://v2.jacky.wtf"]} = values
55
+        assert values = IndieWeb.Http.extract_link_header_values(resp)
56
+        assert %{"self" => "https://v2.jacky.wtf"} = values
53 57
 
54 58
         assert %{
55
-                 "me" => [
56
-                   "https://playvicious.social/@jalcine",
57
-                   "https://twitter.com/jackyalcine",
58
-                   "https://www.instagram.com/jackyalcine/"
59
-                 ]
59
+                 "me" => "https://www.instagram.com/jackyalcine/"
60 60
                } = values
61 61
       end
62 62
     end

+ 30
- 0
test/unit/indieweb/url_test.exs View File

@@ -0,0 +1,30 @@
1
+defmodule IndieWeb.URLTest do
2
+  use IndieWeb.TestCase, async: false
3
+  use IndieWeb.HttpMock
4
+  alias IndieWeb.URL, as: Subject
5
+
6
+  doctest Subject
7
+
8
+  describe ".resolve_redirect/1" do
9
+    test "resolves HTTP to HTTPS" do
10
+      assert "https://jacky.wtf:443/" =
11
+               Subject.resolve_redirect("http://jacky.wtf/")
12
+    end
13
+
14
+    # test "resolves 'www'. to '' prefixing"
15
+    # test "resolves to a different path"
16
+    # test "resolves to a different domain"
17
+  end
18
+
19
+  describe ".canonalize/1" do
20
+    test "returns full URL unchanged" do
21
+      url = URI.parse("https://example.com/")
22
+      assert ^url = Subject.canonalize(url)
23
+    end
24
+
25
+    test "adds a trailing '/' to URL without a path" do
26
+      url = URI.parse("https://example.com")
27
+      assert Subject.canonalize(url) == URI.parse("https://example.com/")
28
+    end
29
+  end
30
+end

+ 17
- 0
test/unit/indieweb/webmention/vouch_test.exs View File

@@ -0,0 +1,17 @@
1
+defmodule IndieWeb.Webmention.VouchTest do
2
+  use IndieWeb.TestCase, async: false
3
+  use IndieWeb.HttpMock
4
+  alias IndieWeb.Webmention.Vouch, as: Subject
5
+
6
+  describe ".valid?/2" do
7
+    @tag skip: true
8
+    test "confirms remote h-card exists on vouch page" do
9
+      use_cassette "webmention_vouch_indieweb_chatnames" do
10
+        assert Subject.valid?(
11
+                 "https://indieweb.org/chat-names",
12
+                 "https://jacky.wtf"
13
+               )
14
+      end
15
+    end
16
+  end
17
+end

+ 4
- 4
test/unit/indieweb/webmention_test.exs View File

@@ -1,14 +1,13 @@
1 1
 defmodule IndieWeb.WebmentionTest do
2 2
   use IndieWeb.TestCase, async: false
3
-  use ExVCR.Mock
3
+  use IndieWeb.HttpMock
4 4
   alias IndieWeb.Webmention, as: Subject
5 5
 
6 6
   setup do
7 7
     Application.put_env(
8 8
       :indieweb,
9 9
       :webmention_url_adapter,
10
-      IndieWeb.Test.WebmentionUrlAdapter,
11
-      persistent: true
10
+      IndieWeb.Test.WebmentionUrlAdapter
12 11
     )
13 12
   end
14 13
 
@@ -236,7 +235,8 @@ defmodule IndieWeb.WebmentionTest do
236 235
       assert [
237 236
                source: "https://webmention.target/source",
238 237
                target: :fake_source,
239
-               target_url: "https://target.indieweb/fake"
238
+               target_url: "https://target.indieweb/fake",
239
+               args: []
240 240
              ] = resp
241 241
     end
242 242
 

Loading…
Cancel
Save