mirror of
https://github.com/teslamate-org/teslamate.git
synced 2026-01-24 21:06:08 +08:00
Add tests
This commit is contained in:
@@ -2,11 +2,12 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
import Core.Dependency, only: [call: 3]
|
||||
|
||||
alias TeslaMate.Mqtt.Publisher
|
||||
alias TeslaMate.Vehicles
|
||||
|
||||
defstruct [:car_id, :last_summary]
|
||||
defstruct [:car_id, :last_summary, :deps]
|
||||
alias __MODULE__, as: State
|
||||
|
||||
def child_spec(arg) do
|
||||
@@ -23,8 +24,15 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
@impl true
|
||||
def init(opts) do
|
||||
car_id = Keyword.fetch!(opts, :car_id)
|
||||
:ok = Vehicles.subscribe(car_id)
|
||||
{:ok, %State{car_id: car_id}}
|
||||
|
||||
deps = %{
|
||||
vehicles: Keyword.get(opts, :deps_vehicles, Vehicles),
|
||||
publisher: Keyword.get(opts, :deps_publisher, Publisher)
|
||||
}
|
||||
|
||||
:ok = call(deps.vehicles, :subscribe, [car_id])
|
||||
|
||||
{:ok, %State{car_id: car_id, deps: deps}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -40,10 +48,11 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
end)
|
||||
|> Task.async_stream(
|
||||
fn {key, value} ->
|
||||
Publisher.publish("teslamate/cars/#{car_id}/#{key}", to_string(value),
|
||||
retain: true,
|
||||
qos: 1
|
||||
)
|
||||
call(state.deps.publisher, :publish, [
|
||||
"teslamate/cars/#{car_id}/#{key}",
|
||||
to_string(value),
|
||||
[retain: true, qos: 1]
|
||||
])
|
||||
end,
|
||||
max_concurrency: 10,
|
||||
on_timeout: :kill_task,
|
||||
@@ -54,6 +63,6 @@ defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriber do
|
||||
_ok -> nil
|
||||
end)
|
||||
|
||||
{:noreply, state}
|
||||
{:noreply, %State{state | last_summary: summary}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,7 +55,8 @@ defmodule TeslaMate.MixProject do
|
||||
{:mojito, "~> 0.5"},
|
||||
{:geocalc, "~> 0.5"},
|
||||
{:srtm, "~> 0.1"},
|
||||
{:fuse, "~> 2.4"}
|
||||
{:fuse, "~> 2.4"},
|
||||
{:mock, "~> 0.3", only: :test}
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
"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"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||
"mint": {:hex, :mint, "0.4.0", "b93a10192957624ed4a8b8641eff1819019c36487bdf49e2b505afd2cc9b7911", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"mojito": {:hex, :mojito, "0.5.0", "de003eafc09633d236e487621f1a8d7d55ca0414a5a418e09ab70a595f9c9c12", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 0.4.0", [hex: :mint, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
|
||||
"phoenix": {:hex, :phoenix, "1.4.9", "746d098e10741c334d88143d3c94cab1756435f94387a63441792e66ec0ee974", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
defmodule MqttPublisherMock do
|
||||
use GenServer
|
||||
|
||||
defstruct [:pid]
|
||||
alias __MODULE__, as: State
|
||||
|
||||
# API
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts, name: Keyword.fetch!(opts, :name))
|
||||
end
|
||||
|
||||
def publish(name, topic, msg, opts), do: GenServer.call(name, {:publish, topic, msg, opts})
|
||||
|
||||
# Callbacks
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
{:ok, %State{pid: Keyword.fetch!(opts, :pid)}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:publish, _topic, _msg, _opts} = action, _from, %State{pid: pid} = state) do
|
||||
send(pid, {MqttPublisherMock, action})
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
end
|
||||
@@ -12,6 +12,8 @@ defmodule VehiclesMock do
|
||||
|
||||
def restart(name), do: GenServer.call(name, :restart)
|
||||
|
||||
def subscribe(name, car_id), do: GenServer.call(name, {:subscribe, car_id})
|
||||
|
||||
# Callbacks
|
||||
|
||||
@impl true
|
||||
@@ -20,6 +22,11 @@ defmodule VehiclesMock do
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:subscribe, _car_id} = action, _from, %State{pid: pid} = state) do
|
||||
send(pid, {VehiclesMock, action})
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
def handle_call(:restart, _from, %State{pid: pid} = state) do
|
||||
send(pid, {VehiclesMock, :restart})
|
||||
{:reply, :ok, state}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
defmodule TeslaMate.Locations.GeocoderTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias TeslaMate.Locations.Geocoder
|
||||
|
||||
import Mock
|
||||
|
||||
@response {:ok,
|
||||
%Mojito.Response{
|
||||
body:
|
||||
"{\"place_id\":241575531,\"licence\":\"Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright\",\"osm_type\":\"node\",\"osm_id\":5983038298,\"lat\":\"37.8895442\",\"lon\":\"41.1288167\",\"place_rank\":30,\"category\":\"amenity\",\"type\":\"cafe\",\"importance\":0,\"addresstype\":\"amenity\",\"name\":\"Kahve Deryası\",\"display_name\":\"Kahve Deryası, Cihan Kavşağı, Batman, Ziyagökalp Mahallesi, Batman merkez, Batman, Southeastern Anatolia Region, 72060, Turkey\",\"address\":{\"cafe\":\"Kahve Deryası\",\"road\":\"Cihan Kavşağı\",\"residential\":\"Batman\",\"suburb\":\"Ziyagökalp Mahallesi\",\"city\":\"Batman merkez\",\"state\":\"Southeastern Anatolia Region\",\"postcode\":\"72060\",\"country\":\"Turkey\",\"country_code\":\"tr\"},\"extratags\":{},\"namedetails\":{\"name\":\"Kahve Deryası\"},\"boundingbox\":[\"37.8894442\",\"37.8896442\",\"41.1287167\",\"41.1289167\"]}",
|
||||
complete: true,
|
||||
headers: [
|
||||
{"date", "Sun, 01 Sep 2019 21:03:23 GMT"},
|
||||
{"server", "Apache/2.4.29 (Ubuntu)"},
|
||||
{"access-control-allow-origin", "*"},
|
||||
{"access-control-allow-methods", "OPTIONS,GET"},
|
||||
{"strict-transport-security", "max-age=31536000; includeSubDomains; preload"},
|
||||
{"expect-ct",
|
||||
"max-age=0, report-uri=\"https://openstreetmap.report-uri.com/r/d/ct/reportOnly\""},
|
||||
{"content-type", "application/json; charset=UTF-8"}
|
||||
],
|
||||
status_code: 200
|
||||
}}
|
||||
|
||||
test "geocoders coordinates" do
|
||||
with_mock Mojito,
|
||||
get:
|
||||
fn "https://nominatim.openstreetmap.org/reverse?format=jsonv2&addressdetails=1&extratags=1&namedetails=1&zoom=19&lat=37.889602&lon=41.129182",
|
||||
_headers ->
|
||||
@response
|
||||
end do
|
||||
assert Geocoder.reverse_lookup(37.889602, 41.129182) ==
|
||||
{:ok,
|
||||
%{
|
||||
city: "Batman merkez",
|
||||
country: "Turkey",
|
||||
county: nil,
|
||||
display_name:
|
||||
"Kahve Deryası, Cihan Kavşağı, Batman, Ziyagökalp Mahallesi, Batman merkez, Batman, Southeastern Anatolia Region, 72060, Turkey",
|
||||
house_number: nil,
|
||||
latitude: "37.8895442",
|
||||
longitude: "41.1288167",
|
||||
name: "Kahve Deryası",
|
||||
neighbourhood: "Ziyagökalp Mahallesi",
|
||||
place_id: 241_575_531,
|
||||
postcode: "72060",
|
||||
raw: %{
|
||||
"cafe" => "Kahve Deryası",
|
||||
"city" => "Batman merkez",
|
||||
"country" => "Turkey",
|
||||
"country_code" => "tr",
|
||||
"postcode" => "72060",
|
||||
"residential" => "Batman",
|
||||
"road" => "Cihan Kavşağı",
|
||||
"state" => "Southeastern Anatolia Region",
|
||||
"suburb" => "Ziyagökalp Mahallesi"
|
||||
},
|
||||
road: "Cihan Kavşağı",
|
||||
state: "Southeastern Anatolia Region",
|
||||
state_district: nil
|
||||
}}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
defmodule TeslaMate.Mqtt.PubSub.VehicleSubscriberTest do
|
||||
use TeslaMate.DataCase, async: true
|
||||
|
||||
alias TeslaMate.Mqtt.PubSub.VehicleSubscriber
|
||||
alias TeslaMate.Vehicles.Vehicle.Summary
|
||||
|
||||
defp start_subscriber(name, car_id) do
|
||||
publisher_name = :"mqtt_publisher_#{name}"
|
||||
vehicles_name = :"vehicles_#{name}"
|
||||
|
||||
{:ok, _pid} = start_supervised({MqttPublisherMock, name: publisher_name, pid: self()})
|
||||
{:ok, _pid} = start_supervised({VehiclesMock, name: vehicles_name, pid: self()})
|
||||
|
||||
start_supervised(
|
||||
{VehicleSubscriber,
|
||||
[
|
||||
name: name,
|
||||
car_id: car_id,
|
||||
deps_publisher: {MqttPublisherMock, publisher_name},
|
||||
deps_vehicles: {VehiclesMock, vehicles_name}
|
||||
]}
|
||||
)
|
||||
end
|
||||
|
||||
test "", %{test: name} do
|
||||
{:ok, pid} = start_subscriber(name, 0)
|
||||
|
||||
assert_receive {VehiclesMock, {:subscribe, 0}}
|
||||
|
||||
summary = %Summary{
|
||||
display_name: "Foo",
|
||||
state: :online,
|
||||
since: DateTime.utc_now(),
|
||||
battery_level: 60.0,
|
||||
ideal_battery_range_km: 230.5,
|
||||
est_battery_range_km: 220.0,
|
||||
battery_range_km: 230.5,
|
||||
charge_energy_added: 25,
|
||||
speed: 40,
|
||||
outside_temp: 15,
|
||||
inside_temp: 20.0,
|
||||
locked: true,
|
||||
sentry_mode: false,
|
||||
plugged_in: false,
|
||||
scheduled_charging_start_time: DateTime.utc_now() |> DateTime.add(60 * 60 * 10, :second),
|
||||
charge_limit_soc: 90,
|
||||
charger_power: 50
|
||||
}
|
||||
|
||||
send(pid, summary)
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/charge_limit_soc", "90", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/display_name", "Foo", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/inside_temp", "20.0", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/locked", "true", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/outside_temp", "15", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/plugged_in", "false", [retain: true, qos: 1]}}
|
||||
|
||||
scheduled_charging_start_time_str = to_string(summary.scheduled_charging_start_time)
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/scheduled_charging_start_time",
|
||||
^scheduled_charging_start_time_str, [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/sentry_mode", "false", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/speed", "40", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/state", "online", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/battery_level", "60.0", [retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/battery_range_km", "230.5",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/est_battery_range_km", "220.0",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/ideal_battery_range_km", "230.5",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/charge_energy_added", "25",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/charger_power", "50", [retain: true, qos: 1]}}
|
||||
|
||||
since_str = to_string(summary.since)
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/since", ^since_str, [retain: true, qos: 1]}}
|
||||
|
||||
# send same summary again
|
||||
send(pid, summary)
|
||||
|
||||
refute_receive _
|
||||
end
|
||||
|
||||
test "send empty string if scheduled_charging_start_time is nil", %{test: name} do
|
||||
{:ok, pid} = start_subscriber(name, 0)
|
||||
|
||||
assert_receive {VehiclesMock, {:subscribe, 0}}
|
||||
|
||||
summary = %Summary{
|
||||
scheduled_charging_start_time: nil
|
||||
}
|
||||
|
||||
send(pid, summary)
|
||||
|
||||
assert_receive {MqttPublisherMock,
|
||||
{:publish, "teslamate/cars/0/scheduled_charging_start_time", "",
|
||||
[retain: true, qos: 1]}}
|
||||
|
||||
refute_receive _
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule TeslaMateWeb.GeoFenceLiveTest do
|
||||
use TeslaMateWeb.ConnCase
|
||||
|
||||
alias TeslaMate.{Locations, Settings}
|
||||
alias TeslaMate.{Locations, Settings, Log}
|
||||
alias TeslaMate.Locations.GeoFence
|
||||
|
||||
def geofence_fixture(attrs \\ %{}) do
|
||||
@@ -151,6 +151,22 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do
|
||||
end
|
||||
|
||||
describe "New" do
|
||||
test "pre-fills the coordinates with the most recent position", %{conn: conn} do
|
||||
assert {:ok, car} = Log.create_car(%{efficiency: 0.153, eid: 42, model: "3", vid: 42})
|
||||
|
||||
assert {:ok, _} =
|
||||
Log.insert_position(car.id, %{
|
||||
date: DateTime.utc_now(),
|
||||
latitude: 48.067612,
|
||||
longitude: 12.862226
|
||||
})
|
||||
|
||||
assert {:ok, view, html} = live(conn, "/geo-fences/new")
|
||||
|
||||
assert html =~ ~r/<input .*? id="geo_fence_latitude" .*? value="48.067612">/
|
||||
assert html =~ ~r/<input .*? id="geo_fence_longitude" .*? value="12.862226">/
|
||||
end
|
||||
|
||||
test "validates cahnges when creating a new geo-fence", %{conn: conn} do
|
||||
assert {:ok, view, html} = live(conn, "/geo-fences/new")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user