Promoted pure presentation and utility helpers from `defp` to `def @doc false` across ~20 LiveViews, Oban workers, and sync modules so they're reachable from unit tests. Refactored several `cond` blocks into idiomatic function heads with guards. Added ~250 new test cases in new files under test/towerops and test/towerops_web, including DB-backed tests for CnMaestro.Sync and AlertNotificationWorker, and removed dead LiveView tab components and CapacityLive (no callers anywhere in lib/test). Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types, Phoenix HTML modules, Inspect protocol impls) from coverage calculations — these are not our project code. Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
74 lines
2.2 KiB
Elixir
74 lines
2.2 KiB
Elixir
defmodule Towerops.Weather.Client do
|
|
@moduledoc """
|
|
OpenWeatherMap API client.
|
|
|
|
Uses the free Current Weather Data API (2.5) which allows 60 calls/min.
|
|
Fetches weather by latitude/longitude — perfect for tower site locations.
|
|
"""
|
|
|
|
alias Towerops.HTTP
|
|
|
|
require Logger
|
|
|
|
@base_url "https://api.openweathermap.org/data/2.5"
|
|
|
|
@doc """
|
|
Fetches current weather for the given coordinates.
|
|
|
|
Returns `{:ok, map}` with the raw OWM response or `{:error, reason}`.
|
|
"""
|
|
def get_current_weather(lat, lon, opts \\ []) do
|
|
with {:ok, key} <- resolve_api_key(opts[:api_key]) do
|
|
req_opts =
|
|
maybe_add_test_plug(
|
|
[url: "#{@base_url}/weather", params: [lat: lat, lon: lon, appid: key]],
|
|
opts
|
|
)
|
|
|
|
__MODULE__
|
|
|> HTTP.get(req_opts)
|
|
|> handle_response()
|
|
end
|
|
end
|
|
|
|
defp resolve_api_key(nil), do: validate_api_key(api_key())
|
|
defp resolve_api_key(""), do: {:error, :no_api_key}
|
|
defp resolve_api_key(key) when is_binary(key), do: {:ok, key}
|
|
|
|
defp validate_api_key(nil), do: {:error, :no_api_key}
|
|
defp validate_api_key(""), do: {:error, :no_api_key}
|
|
defp validate_api_key(key) when is_binary(key), do: {:ok, key}
|
|
|
|
defp handle_response({:ok, %Req.Response{status: 200, body: body}}), do: {:ok, body}
|
|
defp handle_response({:ok, %Req.Response{status: 401}}), do: {:error, :invalid_api_key}
|
|
defp handle_response({:ok, %Req.Response{status: 429}}), do: {:error, :rate_limited}
|
|
|
|
defp handle_response({:ok, %Req.Response{status: status, body: body}}) do
|
|
Logger.warning("OWM API error", status: status, body: inspect(body))
|
|
{:error, {:api_error, status}}
|
|
end
|
|
|
|
defp handle_response({:error, reason}) do
|
|
Logger.warning("OWM API request failed", error: inspect(reason))
|
|
{:error, reason}
|
|
end
|
|
|
|
@doc """
|
|
Tests if the API key is valid by making a minimal request.
|
|
"""
|
|
def test_connection(api_key) do
|
|
# Use a known location (London) for the test
|
|
get_current_weather(51.5074, -0.1278, api_key: api_key)
|
|
end
|
|
|
|
defp api_key do
|
|
Application.get_env(:towerops, :openweathermap_api_key)
|
|
end
|
|
|
|
defp maybe_add_test_plug(req_opts, opts) do
|
|
case Keyword.fetch(opts, :plug) do
|
|
{:ok, plug} -> Keyword.put(req_opts, :plug, plug)
|
|
:error -> req_opts
|
|
end
|
|
end
|
|
end
|