Add beacon monitor registration and fix prod SMTP
Users can now register beacon monitors on the settings page. Each monitor gets a unique random token the remote program will use to authenticate its reports. Name is the only user-supplied field for now; more will be added as the monitor protocol is defined. Also adds :gen_smtp to deps. Swoosh's SMTP adapter depends on :mimemail which lives in that package, and production was 500'ing when trying to send confirmation emails without it.
This commit is contained in:
parent
50d4c0b52f
commit
355bed178d
11 changed files with 422 additions and 0 deletions
74
lib/microwaveprop/beacon_monitors.ex
Normal file
74
lib/microwaveprop/beacon_monitors.ex
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
defmodule Microwaveprop.BeaconMonitors do
|
||||
@moduledoc """
|
||||
The BeaconMonitors context: manages the monitor stations a user
|
||||
has registered. Each monitor has a unique random token the remote
|
||||
program uses to authenticate its reports.
|
||||
"""
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Microwaveprop.Accounts.User
|
||||
alias Microwaveprop.BeaconMonitors.BeaconMonitor
|
||||
alias Microwaveprop.Repo
|
||||
|
||||
@token_bytes 32
|
||||
|
||||
@doc """
|
||||
Returns all monitors for the given user, newest first.
|
||||
"""
|
||||
def list_monitors_for_user(%User{id: user_id}) do
|
||||
Repo.all(
|
||||
from m in BeaconMonitor,
|
||||
where: m.user_id == ^user_id,
|
||||
order_by: [desc: m.inserted_at]
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new monitor for the given user with a freshly generated token.
|
||||
"""
|
||||
def create_monitor(%User{} = user, attrs) do
|
||||
%BeaconMonitor{user_id: user.id, token: generate_token()}
|
||||
|> BeaconMonitor.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a monitor owned by the given user.
|
||||
|
||||
Returns `{:error, :not_found}` if the monitor does not exist or
|
||||
belongs to another user.
|
||||
"""
|
||||
def delete_monitor(%User{id: user_id}, monitor_id) do
|
||||
query =
|
||||
from m in BeaconMonitor,
|
||||
where: m.id == ^monitor_id and m.user_id == ^user_id
|
||||
|
||||
case Repo.one(query) do
|
||||
nil -> {:error, :not_found}
|
||||
monitor -> Repo.delete(monitor)
|
||||
end
|
||||
rescue
|
||||
Ecto.Query.CastError -> {:error, :not_found}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Looks up a monitor by its token. Returns nil if not found.
|
||||
"""
|
||||
def get_monitor_by_token(token) when is_binary(token) do
|
||||
Repo.get_by(BeaconMonitor, token: token)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a blank changeset for rendering the new-monitor form.
|
||||
"""
|
||||
def change_monitor(attrs \\ %{}) do
|
||||
BeaconMonitor.changeset(%BeaconMonitor{}, attrs)
|
||||
end
|
||||
|
||||
defp generate_token do
|
||||
@token_bytes
|
||||
|> :crypto.strong_rand_bytes()
|
||||
|> Base.url_encode64(padding: false)
|
||||
end
|
||||
end
|
||||
36
lib/microwaveprop/beacon_monitors/beacon_monitor.ex
Normal file
36
lib/microwaveprop/beacon_monitors/beacon_monitor.ex
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
defmodule Microwaveprop.BeaconMonitors.BeaconMonitor do
|
||||
@moduledoc """
|
||||
A beacon monitor is a remote station running the monitor program
|
||||
that reports beacon reception data. Each monitor is owned by a user
|
||||
and identified by a random token the program uses to authenticate.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Microwaveprop.Accounts.User
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "beacon_monitors" do
|
||||
field :name, :string
|
||||
field :token, :string
|
||||
field :last_seen_at, :utc_datetime
|
||||
|
||||
belongs_to :user, User
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for user-controlled fields (name only for now).
|
||||
Token and user_id are assigned by the context, not cast from user input.
|
||||
"""
|
||||
def changeset(monitor, attrs) do
|
||||
monitor
|
||||
|> cast(attrs, [:name])
|
||||
|> validate_required([:name])
|
||||
|> validate_length(:name, min: 1, max: 100)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
defmodule MicrowavepropWeb.BeaconMonitorController do
|
||||
use MicrowavepropWeb, :controller
|
||||
|
||||
alias Microwaveprop.BeaconMonitors
|
||||
|
||||
def create(conn, %{"beacon_monitor" => params}) do
|
||||
user = conn.assigns.current_scope.user
|
||||
|
||||
case BeaconMonitors.create_monitor(user, params) do
|
||||
{:ok, monitor} ->
|
||||
conn
|
||||
|> put_flash(:info, "Monitor '#{monitor.name}' created. Copy its token below.")
|
||||
|> redirect(to: ~p"/users/settings")
|
||||
|
||||
{:error, changeset} ->
|
||||
message =
|
||||
changeset
|
||||
|> Ecto.Changeset.traverse_errors(fn {msg, _} -> msg end)
|
||||
|> Enum.map_join("; ", fn {k, v} -> "#{k}: #{Enum.join(v, ", ")}" end)
|
||||
|
||||
conn
|
||||
|> put_flash(:error, "Could not create monitor (#{message}).")
|
||||
|> redirect(to: ~p"/users/settings")
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"id" => id}) do
|
||||
user = conn.assigns.current_scope.user
|
||||
|
||||
conn =
|
||||
case BeaconMonitors.delete_monitor(user, id) do
|
||||
{:ok, _monitor} -> put_flash(conn, :info, "Monitor deleted.")
|
||||
{:error, :not_found} -> put_flash(conn, :error, "Monitor not found.")
|
||||
end
|
||||
|
||||
redirect(conn, to: ~p"/users/settings")
|
||||
end
|
||||
end
|
||||
|
|
@ -4,10 +4,12 @@ defmodule MicrowavepropWeb.UserSettingsController do
|
|||
import MicrowavepropWeb.UserAuth, only: [require_sudo_mode: 2]
|
||||
|
||||
alias Microwaveprop.Accounts
|
||||
alias Microwaveprop.BeaconMonitors
|
||||
alias MicrowavepropWeb.UserAuth
|
||||
|
||||
plug :require_sudo_mode
|
||||
plug :assign_email_and_password_changesets
|
||||
plug :assign_beacon_monitors
|
||||
|
||||
def edit(conn, _params) do
|
||||
render(conn, :edit)
|
||||
|
|
@ -74,4 +76,12 @@ defmodule MicrowavepropWeb.UserSettingsController do
|
|||
|> assign(:email_changeset, Accounts.change_user_email(user))
|
||||
|> assign(:password_changeset, Accounts.change_user_password(user))
|
||||
end
|
||||
|
||||
defp assign_beacon_monitors(conn, _opts) do
|
||||
user = conn.assigns.current_scope.user
|
||||
|
||||
conn
|
||||
|> assign(:beacon_monitors, BeaconMonitors.list_monitors_for_user(user))
|
||||
|> assign(:beacon_monitor_changeset, BeaconMonitors.change_monitor())
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -46,4 +46,80 @@
|
|||
Save Password
|
||||
</.button>
|
||||
</.form>
|
||||
|
||||
<div class="divider" />
|
||||
|
||||
<section id="beacon-monitors" class="space-y-4">
|
||||
<.header>
|
||||
Beacon monitors
|
||||
<:subtitle>
|
||||
Register remote monitor stations. Each monitor gets a unique token the
|
||||
monitor program uses to authenticate its reports.
|
||||
</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.form
|
||||
:let={f}
|
||||
for={@beacon_monitor_changeset}
|
||||
as={:beacon_monitor}
|
||||
action={~p"/users/beacon-monitors"}
|
||||
id="create_beacon_monitor"
|
||||
class="flex items-end gap-2"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<.input
|
||||
field={f[:name]}
|
||||
type="text"
|
||||
label="Monitor name"
|
||||
placeholder="e.g. Shack Pi"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<.button variant="primary" phx-disable-with="Adding...">Add monitor</.button>
|
||||
</.form>
|
||||
|
||||
<%= if @beacon_monitors == [] do %>
|
||||
<p class="text-sm opacity-70">No monitors registered yet.</p>
|
||||
<% else %>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Token</th>
|
||||
<th>Last seen</th>
|
||||
<th class="w-1"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for monitor <- @beacon_monitors do %>
|
||||
<tr>
|
||||
<td class="font-semibold">{monitor.name}</td>
|
||||
<td>
|
||||
<code class="text-xs break-all select-all">{monitor.token}</code>
|
||||
</td>
|
||||
<td class="text-sm opacity-70">
|
||||
<%= if monitor.last_seen_at do %>
|
||||
{Calendar.strftime(monitor.last_seen_at, "%Y-%m-%d %H:%M UTC")}
|
||||
<% else %>
|
||||
never
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<.link
|
||||
href={~p"/users/beacon-monitors/#{monitor.id}"}
|
||||
method="delete"
|
||||
class="btn btn-ghost btn-xs text-error"
|
||||
data-confirm={"Delete monitor '#{monitor.name}'? Its token will stop working immediately."}
|
||||
>
|
||||
Delete
|
||||
</.link>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
</Layouts.app>
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ defmodule MicrowavepropWeb.Router do
|
|||
get "/users/settings", UserSettingsController, :edit
|
||||
put "/users/settings", UserSettingsController, :update
|
||||
get "/users/settings/confirm-email/:token", UserSettingsController, :confirm_email
|
||||
|
||||
post "/users/beacon-monitors", BeaconMonitorController, :create
|
||||
delete "/users/beacon-monitors/:id", BeaconMonitorController, :delete
|
||||
end
|
||||
|
||||
scope "/", MicrowavepropWeb do
|
||||
|
|
|
|||
1
mix.exs
1
mix.exs
|
|
@ -58,6 +58,7 @@ defmodule Microwaveprop.MixProject do
|
|||
{:heroicons,
|
||||
github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", app: false, compile: false, depth: 1},
|
||||
{:swoosh, "~> 1.16"},
|
||||
{:gen_smtp, "~> 1.2"},
|
||||
{:req, "~> 0.5"},
|
||||
{:telemetry_metrics, "~> 1.0"},
|
||||
{:telemetry_poller, "~> 1.0"},
|
||||
|
|
|
|||
2
mix.lock
2
mix.lock
|
|
@ -20,6 +20,7 @@
|
|||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
|
||||
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
|
||||
"gen_smtp": {:hex, :gen_smtp, "1.3.0", "62c3d91f0dcf6ce9db71bcb6881d7ad0d1d834c7f38c13fa8e952f4104a8442e", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0b73fbf069864ecbce02fe653b16d3f35fd889d0fdd4e14527675565c39d84e6"},
|
||||
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
|
||||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
|
||||
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
|
||||
"polaris": {:hex, :polaris, "0.1.0", "dca61b18e3e801ecdae6ac9f0eca5f19792b44a5cb4b8d63db50fc40fc038d22", [:mix], [{:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "13ef2b166650e533cb24b10e2f3b8ab4f2f449ba4d63156e8c569527f206e2c2"},
|
||||
"postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"},
|
||||
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
|
||||
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
|
||||
"stream_data": {:hex, :stream_data, "1.3.0", "bde37905530aff386dea1ddd86ecbf00e6642dc074ceffc10b7d4e41dfd6aac9", [:mix], [], "hexpm", "3cc552e286e817dca43c98044c706eec9318083a1480c52ae2688b08e2936e3c"},
|
||||
"styler": {:hex, :styler, "1.11.0", "35010d970689a23c2bcc8e97bd8bf7d20e3561d60c49be84654df5c37d051a9c", [:mix], [], "hexpm", "70f36165d0cf238a32b7a456fdef6a9c72e77e657d7ac4a0ace33aeba3f2b8c0"},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
defmodule Microwaveprop.Repo.Migrations.CreateBeaconMonitors do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:beacon_monitors, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false
|
||||
add :name, :string, null: false
|
||||
add :token, :string, null: false
|
||||
add :last_seen_at, :utc_datetime
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create index(:beacon_monitors, [:user_id])
|
||||
create unique_index(:beacon_monitors, [:token])
|
||||
end
|
||||
end
|
||||
99
test/microwaveprop/beacon_monitors_test.exs
Normal file
99
test/microwaveprop/beacon_monitors_test.exs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
defmodule Microwaveprop.BeaconMonitorsTest do
|
||||
use Microwaveprop.DataCase
|
||||
|
||||
import Microwaveprop.AccountsFixtures
|
||||
|
||||
alias Microwaveprop.BeaconMonitors
|
||||
alias Microwaveprop.BeaconMonitors.BeaconMonitor
|
||||
|
||||
describe "create_monitor/2" do
|
||||
test "creates a monitor with a generated token" do
|
||||
user = user_fixture()
|
||||
|
||||
assert {:ok, %BeaconMonitor{} = monitor} =
|
||||
BeaconMonitors.create_monitor(user, %{"name" => "Shack Pi"})
|
||||
|
||||
assert monitor.user_id == user.id
|
||||
assert monitor.name == "Shack Pi"
|
||||
assert is_binary(monitor.token)
|
||||
# strong_rand_bytes(32) |> url_encode64(padding: false) is 43 chars
|
||||
assert String.length(monitor.token) == 43
|
||||
end
|
||||
|
||||
test "generates a unique token per monitor" do
|
||||
user = user_fixture()
|
||||
{:ok, m1} = BeaconMonitors.create_monitor(user, %{"name" => "One"})
|
||||
{:ok, m2} = BeaconMonitors.create_monitor(user, %{"name" => "Two"})
|
||||
|
||||
refute m1.token == m2.token
|
||||
end
|
||||
|
||||
test "requires a name" do
|
||||
user = user_fixture()
|
||||
|
||||
assert {:error, changeset} = BeaconMonitors.create_monitor(user, %{"name" => ""})
|
||||
assert %{name: ["can't be blank"]} = errors_on(changeset)
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_monitors_for_user/1" do
|
||||
test "returns monitors belonging to the user, newest first" do
|
||||
user = user_fixture()
|
||||
other = user_fixture()
|
||||
|
||||
{:ok, first} = BeaconMonitors.create_monitor(user, %{"name" => "First"})
|
||||
Process.sleep(1100)
|
||||
{:ok, second} = BeaconMonitors.create_monitor(user, %{"name" => "Second"})
|
||||
{:ok, _other} = BeaconMonitors.create_monitor(other, %{"name" => "Nope"})
|
||||
|
||||
assert [second_result, first_result] = BeaconMonitors.list_monitors_for_user(user)
|
||||
assert second_result.id == second.id
|
||||
assert first_result.id == first.id
|
||||
end
|
||||
|
||||
test "returns empty list when user has no monitors" do
|
||||
user = user_fixture()
|
||||
assert BeaconMonitors.list_monitors_for_user(user) == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_monitor/2" do
|
||||
test "deletes a monitor owned by the user" do
|
||||
user = user_fixture()
|
||||
{:ok, monitor} = BeaconMonitors.create_monitor(user, %{"name" => "Bye"})
|
||||
|
||||
assert {:ok, %BeaconMonitor{}} = BeaconMonitors.delete_monitor(user, monitor.id)
|
||||
assert BeaconMonitors.list_monitors_for_user(user) == []
|
||||
end
|
||||
|
||||
test "does not delete a monitor owned by another user" do
|
||||
user = user_fixture()
|
||||
other = user_fixture()
|
||||
{:ok, monitor} = BeaconMonitors.create_monitor(other, %{"name" => "Theirs"})
|
||||
|
||||
assert {:error, :not_found} = BeaconMonitors.delete_monitor(user, monitor.id)
|
||||
assert [_] = BeaconMonitors.list_monitors_for_user(other)
|
||||
end
|
||||
|
||||
test "returns :not_found for nonexistent id" do
|
||||
user = user_fixture()
|
||||
|
||||
assert {:error, :not_found} =
|
||||
BeaconMonitors.delete_monitor(user, "11111111-1111-1111-1111-111111111111")
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_monitor_by_token/1" do
|
||||
test "returns the monitor matching the token" do
|
||||
user = user_fixture()
|
||||
{:ok, monitor} = BeaconMonitors.create_monitor(user, %{"name" => "Find Me"})
|
||||
|
||||
found = BeaconMonitors.get_monitor_by_token(monitor.token)
|
||||
assert found.id == monitor.id
|
||||
end
|
||||
|
||||
test "returns nil for unknown token" do
|
||||
refute BeaconMonitors.get_monitor_by_token("not-a-real-token")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
defmodule MicrowavepropWeb.BeaconMonitorControllerTest do
|
||||
use MicrowavepropWeb.ConnCase, async: true
|
||||
|
||||
import Microwaveprop.AccountsFixtures
|
||||
|
||||
alias Microwaveprop.BeaconMonitors
|
||||
|
||||
setup :register_and_log_in_user
|
||||
|
||||
describe "POST /users/beacon-monitors" do
|
||||
test "creates a monitor for the current user", %{conn: conn, user: user} do
|
||||
conn =
|
||||
post(conn, ~p"/users/beacon-monitors", %{
|
||||
"beacon_monitor" => %{"name" => "Shack Pi"}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/settings"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Shack Pi"
|
||||
|
||||
assert [monitor] = BeaconMonitors.list_monitors_for_user(user)
|
||||
assert monitor.name == "Shack Pi"
|
||||
assert is_binary(monitor.token)
|
||||
end
|
||||
|
||||
test "shows an error when name is blank", %{conn: conn, user: user} do
|
||||
conn =
|
||||
post(conn, ~p"/users/beacon-monitors", %{
|
||||
"beacon_monitor" => %{"name" => ""}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/settings"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "name"
|
||||
assert BeaconMonitors.list_monitors_for_user(user) == []
|
||||
end
|
||||
|
||||
test "redirects unauthenticated users to login" do
|
||||
conn = post(build_conn(), ~p"/users/beacon-monitors", %{"beacon_monitor" => %{"name" => "Nope"}})
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/log-in"
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /users/beacon-monitors/:id" do
|
||||
test "deletes a monitor owned by the current user", %{conn: conn, user: user} do
|
||||
{:ok, monitor} = BeaconMonitors.create_monitor(user, %{"name" => "Bye"})
|
||||
|
||||
conn = delete(conn, ~p"/users/beacon-monitors/#{monitor.id}")
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/settings"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "deleted"
|
||||
assert BeaconMonitors.list_monitors_for_user(user) == []
|
||||
end
|
||||
|
||||
test "does not delete a monitor owned by another user", %{conn: conn} do
|
||||
other = user_fixture()
|
||||
{:ok, monitor} = BeaconMonitors.create_monitor(other, %{"name" => "Theirs"})
|
||||
|
||||
conn = delete(conn, ~p"/users/beacon-monitors/#{monitor.id}")
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/settings"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "not found"
|
||||
assert [_] = BeaconMonitors.list_monitors_for_user(other)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue