add forgiving API key validation with inline errors

Add KeyValidation module for format-checking API keys before
attempting connections. Auto-strips whitespace, detects common
mistakes (e.g. pasting a Stripe publishable key), and returns
helpful error messages.

Inline field errors across all three entry points:
- Setup wizard: provider + Stripe keys
- Admin provider form: simplified to single Connect button
- Email settings: per-field errors instead of flash toasts

Also: plain text inputs for all API keys (not password fields),
accessible error states (aria-invalid, role=alert, thick border,
bold text), inner_block slot declaration on error component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-04 12:17:56 +00:00
parent e139a75b69
commit 76cff0494e
10 changed files with 557 additions and 216 deletions

View File

@@ -82,11 +82,11 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "postmark"}})
|> render_change()
# Submit with an API key
# Submit with an API key (Postmark uses UUID format)
html =
view
|> form("form[phx-submit=\"save\"]", %{
email: %{adapter: "postmark", api_key: "pm_test_123"}
email: %{adapter: "postmark", api_key: "abc12345-abcd-1234-abcd-123456789abc"}
})
|> render_submit()
@@ -108,7 +108,7 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
|> form("form[phx-submit=\"save\"]", %{email: %{adapter: "postmark", api_key: ""}})
|> render_submit()
assert html =~ "Missing required fields"
assert html =~ "API key is required"
end
test "disconnect clears email configuration", %{conn: conn} do
@@ -167,7 +167,7 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
view
|> form("form[phx-submit=\"save\"]", %{
email: %{adapter: "postmark", api_key: "pm_new_key"}
email: %{adapter: "postmark", api_key: "def12345-abcd-1234-abcd-123456789def"}
})
|> render_submit()

View File

@@ -167,14 +167,6 @@ defmodule BerrypodWeb.Admin.ProvidersTest do
assert html =~ "Connect to Printify"
end
test "test connection shows error when no api key", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin/providers/new")
html = render_click(view, "test_connection")
assert html =~ "Please enter your API key"
end
test "saves new connection", %{conn: conn} do
expect(MockProvider, :test_connection, fn _conn ->
{:ok, %{shop_name: "My Printify Shop", shop_id: 12345}}
@@ -196,55 +188,6 @@ defmodule BerrypodWeb.Admin.ProvidersTest do
end
end
describe "form - test connection" do
setup %{conn: conn, user: user} do
Application.put_env(:berrypod, :provider_modules, %{
"printify" => MockProvider
})
on_exit(fn -> Application.delete_env(:berrypod, :provider_modules) end)
%{conn: log_in_user(conn, user)}
end
test "shows success when connection is valid", %{conn: conn} do
expect(MockProvider, :test_connection, fn _conn ->
{:ok, %{shop_name: "My Printify Shop", shop_id: 12345}}
end)
{:ok, view, _html} = live(conn, ~p"/admin/providers/new")
# Validate first to set pending_api_key
view
|> form("#provider-form", %{
"provider_connection" => %{"api_key" => "valid_key_123"}
})
|> render_change()
html = render_click(view, "test_connection")
assert html =~ "Connected to My Printify Shop"
end
test "shows error when connection fails", %{conn: conn} do
expect(MockProvider, :test_connection, fn _conn ->
{:error, :unauthorized}
end)
{:ok, view, _html} = live(conn, ~p"/admin/providers/new")
view
|> form("#provider-form", %{
"provider_connection" => %{"api_key" => "bad_key"}
})
|> render_change()
html = render_click(view, "test_connection")
assert html =~ "doesn&#39;t seem to be valid"
end
end
describe "form - edit" do
setup %{conn: conn, user: user} do
connection =