defmodule FrancisE2ETest do use ExUnit.Case @moduletag :e2e setup do port = Enum.random(10_000..20_000) on_exit(fn -> case System.cmd("lsof", ["-ti", ":#{port}"], stderr_to_stdout: true) do {pid_str, 0} when pid_str != "" -> pid = String.trim(pid_str) |> String.to_integer() System.cmd("kill", ["-INT", to_string(pid)]) Process.sleep(100) _ -> :ok end end) %{port: port} end describe "e2e: HTML response handlers" do @tag :capture_log test "html/2 serves HTML with correct headers over HTTP", %{port: port} do handler = quote do get("/", fn conn -> html(conn, "

Hello, World!

") end) end mod = Support.RouteTester.generate_module(handler, bandit_opts: [port: port]) start_supervised!(mod) response = Req.get!("http://localhost:#{port}/") assert response.status == 200 assert response.headers["content-type"] == ["text/html; charset=utf-8"] assert response.headers["cache-control"] == [ "no-cache, no-store, must-revalidate" ] assert response.body == "

Hello, World!

" end @tag :capture_log test "html/3 serves HTML with custom status over HTTP", %{port: port} do handler = quote do get("/", fn conn -> html(conn, 201, "

Created

") end) end mod = Support.RouteTester.generate_module(handler, bandit_opts: [port: port]) start_supervised!(mod) response = Req.get!("http://localhost:#{port}/") assert response.status == 201 assert response.headers["content-type"] == ["text/html; charset=utf-8"] assert response.headers["cache-control"] == [ "no-cache, no-store, must-revalidate" ] end @tag :capture_log test "safe_html/2 escapes content and serves over HTTP", %{port: port} do handler = quote do get("/", fn conn -> safe_html(conn, "") end) end mod = Support.RouteTester.generate_module(handler, bandit_opts: [port: port]) start_supervised!(mod) response = Req.get!("http://localhost:#{port}/") assert response.status == 200 assert response.headers["content-type"] == ["text/html; charset=utf-8"] assert response.body == "<script>alert('xss')</script>" refute response.body =~ "") end) end mod = Support.RouteTester.generate_module(handler, bandit_opts: [port: port]) start_supervised!(mod) response = Req.get!("http://localhost:#{port}/") assert response.status == 200 # XSS payload is escaped refute response.body =~ "") end) end mod = Support.RouteTester.generate_module(handler, bandit_opts: [port: port]) start_supervised!(mod) # HTML route html_resp = Req.get!("http://localhost:#{port}/html") assert html_resp.status == 200 assert html_resp.body == "

HTML Page

" assert html_resp.headers["content-type"] == ["text/html; charset=utf-8"] assert html_resp.headers["cache-control"] == [ "no-cache, no-store, must-revalidate" ] assert html_resp.headers["x-content-type-options"] == ["nosniff"] # JSON route json_resp = Req.get!("http://localhost:#{port}/json") assert json_resp.status == 200 assert json_resp.body == %{"message" => "hello"} assert json_resp.headers["content-type"] == [ "application/json; charset=utf-8" ] assert json_resp.headers["x-content-type-options"] == ["nosniff"] # Text route text_resp = Req.get!("http://localhost:#{port}/text") assert text_resp.status == 200 assert text_resp.body == "plain text" assert text_resp.headers["content-type"] == ["text/plain; charset=utf-8"] # Safe HTML route safe_resp = Req.get!("http://localhost:#{port}/safe") assert safe_resp.status == 200 refute safe_resp.body =~ " """ handler = quote do get("/", fn conn -> html(conn, unquote(page)) end) end mod = Support.RouteTester.generate_module(handler, bandit_opts: [port: port]) start_supervised!(mod) response = Req.get!("http://localhost:#{port}/") assert response.status == 200 assert response.headers["content-type"] == ["text/html; charset=utf-8"] # Verify the full document structure survived the round-trip assert response.body =~ "" assert response.body =~ "" assert response.body =~ "" assert response.body =~ "