test: more LiveView event coverage (TotpManager, ApiTokenManager, OrgSettings)

This commit is contained in:
Graham McIntire 2026-05-07 14:32:35 -05:00
parent 2e399953c1
commit bc462c5831
3 changed files with 188 additions and 0 deletions

View file

@ -0,0 +1,42 @@
defmodule ToweropsWeb.Org.SettingsLiveIntegrationsTest do
@moduledoc """
Drives webhook-secret + gaiia-app-url paths in OrgSettingsLive that
aren't exercised by the main settings_live_test.exs.
The configure-flow handlers reference a `:configuring` assign that
isn't initialized in mount, so tests can't drive them via render_click.
"""
use ToweropsWeb.ConnCase
import Phoenix.LiveViewTest
import Towerops.AccountsFixtures
import Towerops.OrganizationsFixtures
setup do
user = user_fixture(enable_totp: true)
organization = organization_fixture(user.id)
%{user: user, organization: organization}
end
describe "save_webhook_secret event" do
test "saves a webhook secret", %{conn: conn, user: user, organization: org} do
{:ok, view, _html} =
conn
|> log_in_user(user)
|> live(~p"/orgs/#{org.slug}/settings?tab=integrations")
assert render_hook(view, "save_webhook_secret", %{"value" => "test-secret"})
end
end
describe "save_gaiia_app_url event" do
test "saves a gaiia app URL", %{conn: conn, user: user, organization: org} do
{:ok, view, _html} =
conn
|> log_in_user(user)
|> live(~p"/orgs/#{org.slug}/settings?tab=integrations")
assert render_hook(view, "save_gaiia_app_url", %{"value" => "https://gaiia.example.com"})
end
end
end

View file

@ -0,0 +1,101 @@
defmodule ToweropsWeb.UserSettingsLive.ApiTokenManagerTest do
use Towerops.DataCase, async: false
import Towerops.AccountsFixtures
import Towerops.OrganizationsFixtures
alias Phoenix.LiveView.Socket
alias Towerops.Accounts.Scope
alias Towerops.ApiTokens
alias ToweropsWeb.UserSettingsLive.ApiTokenManager
setup do
user = user_fixture()
org = organization_fixture(user.id)
%{user: user, org: org}
end
describe "create_api_token/2" do
test "creates a token from nested params and stages QR modal", %{user: user, org: org} do
socket = base_socket(user)
params = %{"token" => %{"name" => "Test Token", "organization_id" => org.id}}
result = ApiTokenManager.create_api_token(socket, params)
assert result.assigns.show_token_modal == true
assert is_binary(result.assigns.created_token)
assert is_list(result.assigns.api_tokens)
end
test "creates a token from flat params", %{user: user, org: org} do
socket = base_socket(user)
params = %{"name" => "Flat Token", "organization_id" => org.id}
result = ApiTokenManager.create_api_token(socket, params)
assert result.assigns.show_token_modal == true
end
test "puts an error flash when name/org are missing", %{user: user} do
socket = base_socket(user)
result = ApiTokenManager.create_api_token(socket, %{})
assert result.assigns.flash["error"] =~ "required"
end
end
describe "close_token_modal/1" do
test "clears the staged token and closes the modal", %{user: user} do
socket =
user
|> base_socket()
|> Phoenix.Component.assign(:show_token_modal, true)
|> Phoenix.Component.assign(:created_token, "tok-xyz")
result = ApiTokenManager.close_token_modal(socket)
assert result.assigns.show_token_modal == false
assert is_nil(result.assigns.created_token)
end
end
describe "delete_api_token/2" do
test "deletes a token owned by the user", %{user: user, org: org} do
{:ok, {token, _raw}} =
ApiTokens.create_api_token(%{
name: "Killable",
organization_id: org.id,
user_id: user.id
})
socket = base_socket(user)
result = ApiTokenManager.delete_api_token(socket, token.id)
assert result.assigns.flash["info"] =~ "deleted"
end
test "rejects deletion of a token owned by another user", %{org: org} do
other_user = user_fixture()
{:ok, {token, _raw}} =
ApiTokens.create_api_token(%{
name: "Other",
organization_id: org.id,
user_id: other_user.id
})
socket = base_socket(user_fixture())
result = ApiTokenManager.delete_api_token(socket, token.id)
assert result.assigns.flash["error"] =~ "your own tokens"
end
end
defp base_socket(user) do
%Socket{
assigns: %{
current_scope: Scope.for_user(user),
flash: %{},
__changed__: %{}
}
}
end
end

View file

@ -85,6 +85,51 @@ defmodule ToweropsWeb.UserSettingsLive.TotpManagerTest do
end
end
describe "create_device/2" do
test "creates a new device and stages QR modal assigns" do
user = user_fixture()
socket = base_socket(user)
result = TotpManager.create_device(socket, "Phone")
assert result.assigns[:show_device_qr_modal] == true
assert result.assigns[:new_device_id]
assert is_binary(result.assigns[:new_device_secret])
assert is_binary(result.assigns[:new_device_qr_code])
end
end
describe "close_device_qr_modal/1" do
test "deletes unverified device and clears modal assigns" do
user = user_fixture()
socket =
user
|> base_socket()
|> TotpManager.create_device("Tablet")
result = TotpManager.close_device_qr_modal(socket)
assert result.assigns.show_device_qr_modal == false
assert is_nil(result.assigns.new_device_id)
assert is_nil(result.assigns.new_device_secret)
assert is_nil(result.assigns.new_device_qr_code)
end
test "is a no-op when no new_device_id is staged" do
user = user_fixture()
socket =
user
|> base_socket()
|> Phoenix.Component.assign(:new_device_id, nil)
|> Phoenix.Component.assign(:new_device_secret, nil)
|> Phoenix.Component.assign(:new_device_qr_code, nil)
result = TotpManager.close_device_qr_modal(socket)
assert result.assigns.show_device_qr_modal == false
end
end
defp base_socket(user) do
%Socket{
assigns: %{