Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ The `name` field is a unique identifier for a task. The `name` is the `task_name

The `url` field is the url that will be checked for correct response JSON and/or stale data and must begin with `"http"` or `"https"`.

Instead of `url`, a task may specify a `path` field, e.g. `"path": "/predictions?filter[route]=Red,Orange,Blue"`. When `path` is used, the url that is checked is built by joining the `BASE_URL` environment variable with the given `path`. If the optional `API_KEY` environment variable is also set, it will be appended to the resulting url as an `api_key` query parameter.

The `active` field, if set to false, will ignore that check.

The `frequency_in_seconds` is the minimum desired frequency to run a check.
Expand Down
10 changes: 10 additions & 0 deletions lib/api_checker/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,18 @@ defmodule ApiChecker.Application do
end
end

defp load_base_url_from_env do
Application.put_env(:api_checker, :base_url, System.get_env("BASE_URL"))
end

defp load_api_key_from_env do
Application.put_env(:api_checker, :api_key, System.get_env("API_KEY"))
end

def start(_type, _args) do
load_env_vars_from_file()
load_base_url_from_env()
load_api_key_from_env()
# List all child processes to be supervised
children = [
# Starts a worker by calling: ApiChecker.Worker.start_link(arg)
Expand Down
50 changes: 49 additions & 1 deletion lib/api_checker/periodic_task.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ defmodule ApiChecker.PeriodicTask do
frequency_in_seconds: json["frequency_in_seconds"],
time_ranges: time_ranges,
name: json["name"],
url: json["url"],
url: build_url(json),
checks: checks
}}
else
Expand All @@ -43,6 +43,54 @@ defmodule ApiChecker.PeriodicTask do
end
end

@doc """
Builds the url for a periodic task from its json configuration.

When `"url"` is present, it is used as-is. When `"path"` is present
instead, the url is built by joining the `:base_url` application
configuration value with the given path. In that case, if the
`:api_key` application configuration value is present, it is appended
to the url as an `api_key` query parameter.
"""
def build_url(%{"url" => url}) when is_binary(url) do
url
end

def build_url(%{"path" => path}) when is_binary(path) do
case Application.get_env(:api_checker, :base_url) do
base_url when is_binary(base_url) and base_url != "" ->
base_url
|> URI.merge(path)
|> URI.to_string()
|> append_api_key(Application.get_env(:api_checker, :api_key))

_ ->
nil
end
end

def build_url(_) do
nil
end

defp append_api_key(url, api_key) when is_binary(api_key) and api_key != "" do
uri = URI.parse(url)

query =
(uri.query || "")
|> URI.decode_query(%{}, :rfc3986)
|> Map.put("api_key", api_key)
|> URI.encode_query(:rfc3986)

uri
|> Map.put(:query, query)
|> URI.to_string()
end

defp append_api_key(url, _api_key) do
url
end

def validate(%PeriodicTask{} = task) do
Validator.validate(task)
end
Expand Down
108 changes: 107 additions & 1 deletion test/api_checker/periodic_task_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ApiChecker.PeriodicTaskTest do
use ExUnit.Case, async: true
use ExUnit.Case
alias ApiChecker.Check.JsonCheck
alias ApiChecker.{PeriodicTask, PeriodicTask.WeeklyTimeRange}
doctest PeriodicTask
Expand Down Expand Up @@ -53,6 +53,112 @@ defmodule ApiChecker.PeriodicTaskTest do
ignored = Map.put(@valid_periodic_task_json, "active", false)
assert {:error, :ignored} = PeriodicTask.from_json(ignored)
end

test "builds url from path and application base_url config when path is given" do
previous_base_url = Application.get_env(:api_checker, :base_url)

on_exit(fn ->
if previous_base_url,
do: Application.put_env(:api_checker, :base_url, previous_base_url),
else: Application.delete_env(:api_checker, :base_url)
end)

Application.put_env(:api_checker, :base_url, "https://api-v3.mbta.com")

json =
@valid_periodic_task_json
|> Map.drop(["url"])
|> Map.put("path", "/predictions?filter[route]=Red,Orange,Blue")

assert {:ok, task} = PeriodicTask.from_json(json)
assert task.url == "https://api-v3.mbta.com/predictions?filter[route]=Red,Orange,Blue"
end

test "errors when path is given but application base_url config is not set" do
previous_base_url = Application.get_env(:api_checker, :base_url)

on_exit(fn ->
if previous_base_url, do: Application.put_env(:api_checker, :base_url, previous_base_url)
end)

Application.delete_env(:api_checker, :base_url)

json =
@valid_periodic_task_json
|> Map.drop(["url"])
|> Map.put("path", "/predictions?filter[route]=Red,Orange,Blue")

assert {:error, _} = PeriodicTask.from_json(json)
end

test "appends api_key to url built from path when application api_key config is set" do
previous_base_url = Application.get_env(:api_checker, :base_url)
previous_api_key = Application.get_env(:api_checker, :api_key)

on_exit(fn ->
if previous_base_url,
do: Application.put_env(:api_checker, :base_url, previous_base_url),
else: Application.delete_env(:api_checker, :base_url)

if previous_api_key,
do: Application.put_env(:api_checker, :api_key, previous_api_key),
else: Application.delete_env(:api_checker, :api_key)
end)

Application.put_env(:api_checker, :base_url, "https://api-v3.mbta.com")
Application.put_env(:api_checker, :api_key, "secret-key")

json =
@valid_periodic_task_json
|> Map.drop(["url"])
|> Map.put("path", "/predictions?filter[route]=Red,Orange,Blue")

assert {:ok, task} = PeriodicTask.from_json(json)
uri = URI.parse(task.url)
assert "#{uri.scheme}://#{uri.host}#{uri.path}" == "https://api-v3.mbta.com/predictions"
assert URI.decode_query(uri.query) == %{"filter[route]" => "Red,Orange,Blue", "api_key" => "secret-key"}
end

test "appends api_key with a leading ? when the path has no query string" do
previous_base_url = Application.get_env(:api_checker, :base_url)
previous_api_key = Application.get_env(:api_checker, :api_key)

on_exit(fn ->
if previous_base_url,
do: Application.put_env(:api_checker, :base_url, previous_base_url),
else: Application.delete_env(:api_checker, :base_url)

if previous_api_key,
do: Application.put_env(:api_checker, :api_key, previous_api_key),
else: Application.delete_env(:api_checker, :api_key)
end)

Application.put_env(:api_checker, :base_url, "https://api-v3.mbta.com")
Application.put_env(:api_checker, :api_key, "secret-key")

json =
@valid_periodic_task_json
|> Map.drop(["url"])
|> Map.put("path", "/predictions")

assert {:ok, task} = PeriodicTask.from_json(json)
assert task.url == "https://api-v3.mbta.com/predictions?api_key=secret-key"
end

test "does not append api_key to an explicitly given url" do
previous_api_key = Application.get_env(:api_checker, :api_key)

on_exit(fn ->
if previous_api_key,
do: Application.put_env(:api_checker, :api_key, previous_api_key),
else: Application.delete_env(:api_checker, :api_key)
end)

Application.put_env(:api_checker, :api_key, "secret-key")

assert {:ok, task} = PeriodicTask.from_json(@valid_periodic_task_json)
assert task.url == "https://api-v3.mbta.com/predictions?filter%5Broute%5D=Red,Orange,Blue"
end
end

@valid_periodic_task %PeriodicTask{
Expand Down
Loading