- EquipmentLive.Form: 46.73% → 92.52% (+45.79%) - Added 27 tests covering form validation, SNMP testing, site selection - Tests mount behavior, save/update flows, deletion, and discovery triggers - Tests SNMP config changes and agent assignment - UserCredentialController: 33.93% → 64.29% (+30.36%) - Added 7 tests for WebAuthn registration and authentication flows - Tests challenge generation, credential registration, and error cases - Tests discoverable authentication and account-specific auth Overall coverage: 60.40% → 63.67% (+3.27%)
176 lines
6.2 KiB
Elixir
176 lines
6.2 KiB
Elixir
defmodule ToweropsWeb.UserCredentialControllerTest do
|
|
use ToweropsWeb.ConnCase, async: true
|
|
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Towerops.Accounts
|
|
|
|
describe "POST /api/webauthn/registration/challenge" do
|
|
setup :register_and_log_in_user
|
|
|
|
test "returns challenge for authenticated user with confirmed email", %{conn: conn, user: user} do
|
|
conn = post(conn, ~p"/api/webauthn/registration/challenge")
|
|
assert %{"challenge" => challenge, "user" => user_data} = json_response(conn, 200)
|
|
assert is_binary(challenge)
|
|
assert user_data["name"] == user.email
|
|
end
|
|
|
|
test "returns error for user without confirmed email" do
|
|
# Create unconfirmed user
|
|
user = unconfirmed_user_fixture()
|
|
conn = log_in_user(build_conn(), user)
|
|
|
|
conn = post(conn, ~p"/api/webauthn/registration/challenge")
|
|
assert %{"error" => error} = json_response(conn, 403)
|
|
assert error =~ "Email confirmation required"
|
|
end
|
|
|
|
test "requires authentication" do
|
|
conn = build_conn()
|
|
conn = post(conn, ~p"/api/webauthn/registration/challenge")
|
|
assert redirected_to(conn) == ~p"/users/log-in"
|
|
end
|
|
end
|
|
|
|
describe "POST /api/webauthn/register" do
|
|
setup :register_and_log_in_user
|
|
|
|
test "registers credential successfully", %{conn: conn} do
|
|
# Set challenge in session
|
|
conn = put_session(conn, :webauthn_challenge, "test_challenge_123")
|
|
|
|
# Mock successful registration params
|
|
params = %{
|
|
"name" => "My Passkey",
|
|
"rawId" => Base.url_encode64("credential_id", padding: false),
|
|
"response" => %{
|
|
"clientDataJSON" =>
|
|
Base.url_encode64(~s({"type":"webauthn.create","challenge":"test_challenge_123"}), padding: false),
|
|
"attestationObject" => Base.url_encode64("mock_attestation", padding: false)
|
|
},
|
|
"type" => "public-key"
|
|
}
|
|
|
|
conn = post(conn, ~p"/api/webauthn/register", params)
|
|
|
|
# Should return error since WebAuthn verification will fail in test
|
|
# But this tests the controller flow
|
|
assert conn.status in [201, 422]
|
|
end
|
|
|
|
test "returns error when email not confirmed", %{conn: _conn} do
|
|
# Create unconfirmed user
|
|
unconfirmed_user = unconfirmed_user_fixture()
|
|
conn = log_in_user(build_conn(), unconfirmed_user)
|
|
conn = put_session(conn, :webauthn_challenge, "test_challenge")
|
|
|
|
params = %{
|
|
"name" => "My Passkey",
|
|
"rawId" => Base.url_encode64("credential_id", padding: false)
|
|
}
|
|
|
|
conn = post(conn, ~p"/api/webauthn/register", params)
|
|
assert %{"error" => error} = json_response(conn, 403)
|
|
assert error =~ "Email confirmation required"
|
|
end
|
|
|
|
test "returns error on registration failure", %{conn: conn} do
|
|
conn = put_session(conn, :webauthn_challenge, "test_challenge")
|
|
|
|
# Invalid params
|
|
params = %{"invalid" => "data"}
|
|
|
|
conn = post(conn, ~p"/api/webauthn/register", params)
|
|
assert %{"error" => _} = json_response(conn, 422)
|
|
end
|
|
|
|
test "requires authentication" do
|
|
conn = build_conn()
|
|
conn = post(conn, ~p"/api/webauthn/register", %{})
|
|
assert redirected_to(conn) == ~p"/users/log-in"
|
|
end
|
|
end
|
|
|
|
describe "POST /api/webauthn/authentication/challenge" do
|
|
test "returns challenge for user with passkeys", %{conn: conn} do
|
|
user = user_fixture()
|
|
_credential = credential_fixture(user, %{name: "Test Key"})
|
|
|
|
conn = post(conn, ~p"/api/webauthn/authentication/challenge", %{"email" => user.email})
|
|
assert %{"challenge" => challenge} = json_response(conn, 200)
|
|
assert is_binary(challenge)
|
|
end
|
|
|
|
test "returns challenge for discoverable authentication", %{conn: conn} do
|
|
# No email provided - discoverable credential flow
|
|
conn = post(conn, ~p"/api/webauthn/authentication/challenge", %{})
|
|
assert %{"challenge" => challenge} = json_response(conn, 200)
|
|
assert is_binary(challenge)
|
|
end
|
|
|
|
test "returns error for user without passkeys", %{conn: conn} do
|
|
user = user_fixture()
|
|
|
|
conn = post(conn, ~p"/api/webauthn/authentication/challenge", %{"email" => user.email})
|
|
assert %{"error" => _} = json_response(conn, 404)
|
|
end
|
|
|
|
test "returns error for non-existent user", %{conn: conn} do
|
|
conn =
|
|
post(conn, ~p"/api/webauthn/authentication/challenge", %{
|
|
"email" => "nonexistent@example.com"
|
|
})
|
|
|
|
assert %{"error" => _} = json_response(conn, 404)
|
|
end
|
|
|
|
test "does not require authentication", %{conn: conn} do
|
|
user = user_fixture()
|
|
conn = post(conn, ~p"/api/webauthn/authentication/challenge", %{"email" => user.email})
|
|
# Should not redirect to login
|
|
assert conn.status in [200, 404]
|
|
end
|
|
end
|
|
|
|
describe "POST /api/webauthn/authenticate" do
|
|
test "returns error when no challenge in session" do
|
|
conn = build_conn()
|
|
conn = post(conn, ~p"/api/webauthn/authenticate", %{})
|
|
assert %{"error" => _} = json_response(conn, 401)
|
|
end
|
|
end
|
|
|
|
describe "DELETE /users/credentials/:id" do
|
|
setup :register_and_log_in_user
|
|
|
|
test "deletes credential successfully", %{conn: conn, user: user} do
|
|
# Create a test credential
|
|
credential = credential_fixture(user, %{name: "Test Key"})
|
|
|
|
conn = delete(conn, ~p"/users/credentials/#{credential.id}")
|
|
assert redirected_to(conn) == ~p"/users/settings"
|
|
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "deleted successfully"
|
|
|
|
# Verify credential is deleted
|
|
assert Accounts.list_user_credentials(user.id) == []
|
|
end
|
|
|
|
test "cannot delete another user's credential", %{conn: conn} do
|
|
other_user = user_fixture()
|
|
credential = credential_fixture(other_user, %{name: "Other Key"})
|
|
|
|
conn = delete(conn, ~p"/users/credentials/#{credential.id}")
|
|
assert redirected_to(conn) == ~p"/users/settings"
|
|
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "not found"
|
|
|
|
# Verify credential still exists
|
|
assert length(Accounts.list_user_credentials(other_user.id)) == 1
|
|
end
|
|
|
|
test "requires authentication" do
|
|
conn = build_conn()
|
|
conn = delete(conn, ~p"/users/credentials/#{Ecto.UUID.generate()}")
|
|
assert redirected_to(conn) == ~p"/users/log-in"
|
|
end
|
|
end
|
|
end
|