rename project from SimpleshopTheme to Berrypod
All modules, configs, paths, and references updated. 836 tests pass, zero warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
20
test/berrypod_web/controllers/error_html_test.exs
Normal file
20
test/berrypod_web/controllers/error_html_test.exs
Normal file
@@ -0,0 +1,20 @@
|
||||
defmodule BerrypodWeb.ErrorHTMLTest do
|
||||
use BerrypodWeb.ConnCase, async: true
|
||||
|
||||
# Bring render_to_string/4 for testing custom views
|
||||
import Phoenix.Template, only: [render_to_string: 4]
|
||||
|
||||
test "renders 404.html with themed page" do
|
||||
html = render_to_string(BerrypodWeb.ErrorHTML, "404", "html", [])
|
||||
assert html =~ "404"
|
||||
assert html =~ "Page Not Found"
|
||||
assert html =~ "shop-root"
|
||||
end
|
||||
|
||||
test "renders 500.html with themed page" do
|
||||
html = render_to_string(BerrypodWeb.ErrorHTML, "500", "html", [])
|
||||
assert html =~ "500"
|
||||
assert html =~ "Server Error"
|
||||
assert html =~ "shop-root"
|
||||
end
|
||||
end
|
||||
14
test/berrypod_web/controllers/error_json_test.exs
Normal file
14
test/berrypod_web/controllers/error_json_test.exs
Normal file
@@ -0,0 +1,14 @@
|
||||
defmodule BerrypodWeb.ErrorJSONTest do
|
||||
use BerrypodWeb.ConnCase, async: true
|
||||
|
||||
test "renders 404" do
|
||||
assert BerrypodWeb.ErrorJSON.render("404.json", %{}) == %{
|
||||
errors: %{detail: "Not Found"}
|
||||
}
|
||||
end
|
||||
|
||||
test "renders 500" do
|
||||
assert BerrypodWeb.ErrorJSON.render("500.json", %{}) ==
|
||||
%{errors: %{detail: "Internal Server Error"}}
|
||||
end
|
||||
end
|
||||
76
test/berrypod_web/controllers/image_controller_test.exs
Normal file
76
test/berrypod_web/controllers/image_controller_test.exs
Normal file
@@ -0,0 +1,76 @@
|
||||
defmodule BerrypodWeb.ImageControllerTest do
|
||||
use BerrypodWeb.ConnCase
|
||||
|
||||
alias Berrypod.Media
|
||||
|
||||
@svg_content ~s(<svg xmlns="http://www.w3.org/2000/svg"><circle fill="#000000" r="10"/></svg>)
|
||||
@sample_jpeg File.read!("test/fixtures/sample_1200x800.jpg")
|
||||
|
||||
describe "recolored_svg/2" do
|
||||
test "returns 404 for non-existent image", %{conn: conn} do
|
||||
conn = get(conn, ~p"/images/#{Ecto.UUID.generate()}/recolored/ff6600")
|
||||
assert response(conn, 404) =~ "Image not found"
|
||||
end
|
||||
|
||||
test "returns 400 for non-SVG image", %{conn: conn} do
|
||||
{:ok, image} =
|
||||
Media.upload_image(%{
|
||||
image_type: "logo",
|
||||
filename: "test.jpg",
|
||||
content_type: "image/jpeg",
|
||||
file_size: byte_size(@sample_jpeg),
|
||||
data: @sample_jpeg
|
||||
})
|
||||
|
||||
conn = get(conn, ~p"/images/#{image.id}/recolored/ff6600")
|
||||
assert response(conn, 400) =~ "not an SVG"
|
||||
end
|
||||
|
||||
test "returns 400 for invalid color", %{conn: conn} do
|
||||
{:ok, image} =
|
||||
Media.upload_image(%{
|
||||
image_type: "logo",
|
||||
filename: "test.svg",
|
||||
content_type: "image/svg+xml",
|
||||
file_size: byte_size(@svg_content),
|
||||
data: @svg_content
|
||||
})
|
||||
|
||||
conn = get(conn, ~p"/images/#{image.id}/recolored/invalid")
|
||||
assert response(conn, 400) =~ "Invalid color"
|
||||
end
|
||||
|
||||
test "recolors SVG with valid hex color", %{conn: conn} do
|
||||
{:ok, image} =
|
||||
Media.upload_image(%{
|
||||
image_type: "logo",
|
||||
filename: "test.svg",
|
||||
content_type: "image/svg+xml",
|
||||
file_size: byte_size(@svg_content),
|
||||
data: @svg_content
|
||||
})
|
||||
|
||||
conn = get(conn, ~p"/images/#{image.id}/recolored/ff6600")
|
||||
|
||||
assert response(conn, 200) =~ ~s(fill="#ff6600")
|
||||
assert get_resp_header(conn, "content-type") == ["image/svg+xml; charset=utf-8"]
|
||||
assert get_resp_header(conn, "cache-control") == ["public, max-age=3600"]
|
||||
end
|
||||
|
||||
test "handles color with leading hash", %{conn: conn} do
|
||||
{:ok, image} =
|
||||
Media.upload_image(%{
|
||||
image_type: "logo",
|
||||
filename: "test.svg",
|
||||
content_type: "image/svg+xml",
|
||||
file_size: byte_size(@svg_content),
|
||||
data: @svg_content
|
||||
})
|
||||
|
||||
# URL encodes # as %23
|
||||
conn = get(conn, "/images/#{image.id}/recolored/%23ff6600")
|
||||
|
||||
assert response(conn, 200) =~ ~s(fill="#ff6600")
|
||||
end
|
||||
end
|
||||
end
|
||||
16
test/berrypod_web/controllers/page_controller_test.exs
Normal file
16
test/berrypod_web/controllers/page_controller_test.exs
Normal file
@@ -0,0 +1,16 @@
|
||||
defmodule BerrypodWeb.PageControllerTest do
|
||||
use BerrypodWeb.ConnCase, async: false
|
||||
|
||||
import Berrypod.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user_fixture()
|
||||
{:ok, _} = Berrypod.Settings.set_site_live(true)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "GET / renders the shop home page", %{conn: conn} do
|
||||
conn = get(conn, ~p"/")
|
||||
assert html_response(conn, 200) =~ "Original designs, printed on demand"
|
||||
end
|
||||
end
|
||||
141
test/berrypod_web/controllers/user_session_controller_test.exs
Normal file
141
test/berrypod_web/controllers/user_session_controller_test.exs
Normal file
@@ -0,0 +1,141 @@
|
||||
defmodule BerrypodWeb.UserSessionControllerTest do
|
||||
use BerrypodWeb.ConnCase
|
||||
|
||||
import Berrypod.AccountsFixtures
|
||||
alias Berrypod.Accounts
|
||||
|
||||
setup do
|
||||
%{unconfirmed_user: unconfirmed_user_fixture(), user: user_fixture()}
|
||||
end
|
||||
|
||||
describe "POST /users/log-in - email and password" do
|
||||
test "logs the user in", %{conn: conn, user: user} do
|
||||
user = set_password(user)
|
||||
|
||||
conn =
|
||||
post(conn, ~p"/users/log-in", %{
|
||||
"user" => %{"email" => user.email, "password" => valid_user_password()}
|
||||
})
|
||||
|
||||
assert get_session(conn, :user_token)
|
||||
assert redirected_to(conn) == ~p"/admin"
|
||||
|
||||
# Now do a logged in request and assert on the page content
|
||||
conn = get(conn, ~p"/admin/settings")
|
||||
response = html_response(conn, 200)
|
||||
assert response =~ user.email
|
||||
end
|
||||
|
||||
test "logs the user in with remember me", %{conn: conn, user: user} do
|
||||
user = set_password(user)
|
||||
|
||||
conn =
|
||||
post(conn, ~p"/users/log-in", %{
|
||||
"user" => %{
|
||||
"email" => user.email,
|
||||
"password" => valid_user_password(),
|
||||
"remember_me" => "true"
|
||||
}
|
||||
})
|
||||
|
||||
assert conn.resp_cookies["_berrypod_web_user_remember_me"]
|
||||
assert redirected_to(conn) == ~p"/admin"
|
||||
end
|
||||
|
||||
test "logs the user in with return to", %{conn: conn, user: user} do
|
||||
user = set_password(user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> init_test_session(user_return_to: "/foo/bar")
|
||||
|> post(~p"/users/log-in", %{
|
||||
"user" => %{
|
||||
"email" => user.email,
|
||||
"password" => valid_user_password()
|
||||
}
|
||||
})
|
||||
|
||||
assert redirected_to(conn) == "/foo/bar"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Welcome back!"
|
||||
end
|
||||
|
||||
test "redirects to login page with invalid credentials", %{conn: conn, user: user} do
|
||||
conn =
|
||||
post(conn, ~p"/users/log-in?mode=password", %{
|
||||
"user" => %{"email" => user.email, "password" => "invalid_password"}
|
||||
})
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid email or password"
|
||||
assert redirected_to(conn) == ~p"/users/log-in"
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /users/log-in - magic link" do
|
||||
test "logs the user in", %{conn: conn, user: user} do
|
||||
{token, _hashed_token} = generate_user_magic_link_token(user)
|
||||
|
||||
conn =
|
||||
post(conn, ~p"/users/log-in", %{
|
||||
"user" => %{"token" => token}
|
||||
})
|
||||
|
||||
assert get_session(conn, :user_token)
|
||||
assert redirected_to(conn) == ~p"/admin"
|
||||
|
||||
# Now do a logged in request and assert on the page content
|
||||
conn = get(conn, ~p"/admin/settings")
|
||||
response = html_response(conn, 200)
|
||||
assert response =~ user.email
|
||||
end
|
||||
|
||||
test "confirms unconfirmed user", %{conn: conn, unconfirmed_user: user} do
|
||||
{token, _hashed_token} = generate_user_magic_link_token(user)
|
||||
refute user.confirmed_at
|
||||
|
||||
conn =
|
||||
post(conn, ~p"/users/log-in", %{
|
||||
"user" => %{"token" => token},
|
||||
"_action" => "confirmed"
|
||||
})
|
||||
|
||||
assert get_session(conn, :user_token)
|
||||
assert redirected_to(conn) == ~p"/admin"
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "User confirmed successfully."
|
||||
|
||||
assert Accounts.get_user!(user.id).confirmed_at
|
||||
|
||||
# Now do a logged in request and assert on the page content
|
||||
conn = get(conn, ~p"/admin/settings")
|
||||
response = html_response(conn, 200)
|
||||
assert response =~ user.email
|
||||
end
|
||||
|
||||
test "redirects to login page when magic link is invalid", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, ~p"/users/log-in", %{
|
||||
"user" => %{"token" => "invalid"}
|
||||
})
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
|
||||
"The link is invalid or it has expired."
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/log-in"
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /users/log-out" do
|
||||
test "logs the user out", %{conn: conn, user: user} do
|
||||
conn = conn |> log_in_user(user) |> delete(~p"/users/log-out")
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
refute get_session(conn, :user_token)
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
|
||||
end
|
||||
|
||||
test "succeeds even if the user is not logged in", %{conn: conn} do
|
||||
conn = delete(conn, ~p"/users/log-out")
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
refute get_session(conn, :user_token)
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Logged out successfully"
|
||||
end
|
||||
end
|
||||
end
|
||||
178
test/berrypod_web/controllers/webhook_controller_test.exs
Normal file
178
test/berrypod_web/controllers/webhook_controller_test.exs
Normal file
@@ -0,0 +1,178 @@
|
||||
defmodule BerrypodWeb.WebhookControllerTest do
|
||||
use BerrypodWeb.ConnCase
|
||||
|
||||
import Berrypod.ProductsFixtures
|
||||
|
||||
@webhook_secret "test_webhook_secret_123"
|
||||
|
||||
setup do
|
||||
_conn =
|
||||
provider_connection_fixture(%{
|
||||
provider_type: "printify",
|
||||
config: %{"shop_id" => "12345", "webhook_secret" => @webhook_secret}
|
||||
})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "POST /webhooks/printify" do
|
||||
test "returns 401 without signature", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post(~p"/webhooks/printify", %{type: "product:updated"})
|
||||
|
||||
assert json_response(conn, 401)["error"] == "Invalid signature"
|
||||
end
|
||||
|
||||
test "returns 401 with invalid signature", %{conn: conn} do
|
||||
body = Jason.encode!(%{type: "product:updated", resource: %{id: "123"}})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pfy-signature", "sha256=invalid")
|
||||
|> post(~p"/webhooks/printify", body)
|
||||
|
||||
assert json_response(conn, 401)["error"] == "Invalid signature"
|
||||
end
|
||||
|
||||
test "accepts valid signature and returns 200", %{conn: conn} do
|
||||
body = Jason.encode!(%{type: "product:updated", resource: %{id: "123", shop_id: "12345"}})
|
||||
signature = compute_signature(body, @webhook_secret)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pfy-signature", "sha256=#{signature}")
|
||||
|> post(~p"/webhooks/printify", body)
|
||||
|
||||
assert json_response(conn, 200)["status"] == "ok"
|
||||
end
|
||||
|
||||
test "handles product:updated event", %{conn: conn} do
|
||||
body = Jason.encode!(%{type: "product:updated", resource: %{id: "123", shop_id: "12345"}})
|
||||
signature = compute_signature(body, @webhook_secret)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pfy-signature", "sha256=#{signature}")
|
||||
|> post(~p"/webhooks/printify", body)
|
||||
|
||||
# Should return 200 even if job processing fails (inline mode tries to execute)
|
||||
assert json_response(conn, 200)["status"] == "ok"
|
||||
end
|
||||
|
||||
test "handles product:deleted event", %{conn: conn} do
|
||||
body = Jason.encode!(%{type: "product:deleted", resource: %{id: "456"}})
|
||||
signature = compute_signature(body, @webhook_secret)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pfy-signature", "sha256=#{signature}")
|
||||
|> post(~p"/webhooks/printify", body)
|
||||
|
||||
assert json_response(conn, 200)["status"] == "ok"
|
||||
end
|
||||
|
||||
test "returns 401 when no webhook secret configured", %{conn: conn} do
|
||||
# Remove the provider connection to simulate no secret
|
||||
Berrypod.Repo.delete_all(Berrypod.Products.ProviderConnection)
|
||||
|
||||
body = Jason.encode!(%{type: "product:updated", resource: %{id: "123"}})
|
||||
signature = compute_signature(body, @webhook_secret)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pfy-signature", "sha256=#{signature}")
|
||||
|> post(~p"/webhooks/printify", body)
|
||||
|
||||
assert json_response(conn, 401)["error"] == "Invalid signature"
|
||||
end
|
||||
end
|
||||
|
||||
defp compute_signature(body, secret) do
|
||||
:crypto.mac(:hmac, :sha256, secret, body)
|
||||
|> Base.encode16(case: :lower)
|
||||
end
|
||||
|
||||
describe "POST /webhooks/printful" do
|
||||
@printful_secret "printful_test_secret_456"
|
||||
|
||||
setup do
|
||||
_conn =
|
||||
provider_connection_fixture(%{
|
||||
provider_type: "printful",
|
||||
config: %{"store_id" => "99999", "webhook_secret" => @printful_secret}
|
||||
})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "returns 401 without token", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post(~p"/webhooks/printful", %{type: "product_updated"})
|
||||
|
||||
assert json_response(conn, 401)["error"] == "Invalid token"
|
||||
end
|
||||
|
||||
test "returns 401 with wrong token", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pf-webhook-token", "wrong_token")
|
||||
|> post(~p"/webhooks/printful", %{type: "product_updated"})
|
||||
|
||||
assert json_response(conn, 401)["error"] == "Invalid token"
|
||||
end
|
||||
|
||||
test "accepts valid token via header", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pf-webhook-token", @printful_secret)
|
||||
|> post(~p"/webhooks/printful", %{type: "product_updated", data: %{}})
|
||||
|
||||
assert json_response(conn, 200)["status"] == "ok"
|
||||
end
|
||||
|
||||
test "accepts valid token via query param", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post(~p"/webhooks/printful?token=#{@printful_secret}", %{
|
||||
type: "product_updated",
|
||||
data: %{}
|
||||
})
|
||||
|
||||
assert json_response(conn, 200)["status"] == "ok"
|
||||
end
|
||||
|
||||
test "handles unknown event gracefully", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pf-webhook-token", @printful_secret)
|
||||
|> post(~p"/webhooks/printful", %{type: "unknown_event", data: %{}})
|
||||
|
||||
assert json_response(conn, 200)["status"] == "ok"
|
||||
end
|
||||
|
||||
test "returns 401 when no webhook secret configured", %{conn: conn} do
|
||||
Berrypod.Repo.delete_all(Berrypod.Products.ProviderConnection)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> put_req_header("x-pf-webhook-token", @printful_secret)
|
||||
|> post(~p"/webhooks/printful", %{type: "product_updated"})
|
||||
|
||||
assert json_response(conn, 401)["error"] == "Invalid token"
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user