Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion lib/items_api/application.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
defmodule ItemsApi.Application do
use Application

@default_port 4000

@impl true
def start(_type, _args) do
port = String.to_integer(System.get_env("PORT") || "4000")
port = resolve_port()

children = [
{ItemsApi.Repo, []},
Expand All @@ -18,6 +20,41 @@ defmodule ItemsApi.Application do
{:ok, pid}
end

@doc """
Resolves the port from the PORT environment variable.

Returns the integer value of PORT if set and valid,
otherwise returns the default port (4000).

Invalid PORT values (non-numeric strings) are ignored
and the default port is used instead, with a warning logged.
"""
@spec resolve_port() :: non_neg_integer()
def resolve_port do
case System.get_env("PORT") do
nil ->
@default_port

port_string ->
parse_port(port_string)
end
end

@doc false
def default_port, do: @default_port

defp parse_port(port_string) do
case Integer.parse(port_string) do
{port, ""} when port > 0 and port <= 65_535 ->
port

_ ->
require Logger
Logger.warning("Invalid PORT value #{inspect(port_string)}, falling back to #{@default_port}")
@default_port
end
end

defp run_migrations do
migrations_path = Application.app_dir(:items_api, "priv/repo/migrations")
Ecto.Migrator.run(ItemsApi.Repo, migrations_path, :up, all: true)
Expand Down
110 changes: 110 additions & 0 deletions test/items_api/port_config_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
defmodule ItemsApi.PortConfigTest do
use ExUnit.Case, async: false

describe "resolve_port/0" do
test "returns default port 4000 when PORT env var is not set" do
System.delete_env("PORT")
assert ItemsApi.Application.resolve_port() == 4000
end

test "returns custom port when PORT env var is set" do
System.put_env("PORT", "5001")

try do
assert ItemsApi.Application.resolve_port() == 5001
after
System.delete_env("PORT")
end
end

test "returns custom port for various valid values" do
for {input, expected} <- [{"8080", 8080}, {"3000", 3000}, {"1", 1}, {"65535", 65535}] do
System.put_env("PORT", input)

try do
assert ItemsApi.Application.resolve_port() == expected,
"Expected PORT=#{input} to resolve to #{expected}"
after
System.delete_env("PORT")
end
end
end

test "falls back to default for non-numeric PORT value" do
System.put_env("PORT", "abc")

try do
assert ItemsApi.Application.resolve_port() == 4000
after
System.delete_env("PORT")
end
end

test "falls back to default for empty string PORT" do
System.put_env("PORT", "")

try do
assert ItemsApi.Application.resolve_port() == 4000
after
System.delete_env("PORT")
end
end

test "falls back to default for PORT value of 0" do
System.put_env("PORT", "0")

try do
assert ItemsApi.Application.resolve_port() == 4000
after
System.delete_env("PORT")
end
end

test "falls back to default for PORT value exceeding 65535" do
System.put_env("PORT", "70000")

try do
assert ItemsApi.Application.resolve_port() == 4000
after
System.delete_env("PORT")
end
end

test "falls back to default for PORT with trailing characters" do
System.put_env("PORT", "4000abc")

try do
assert ItemsApi.Application.resolve_port() == 4000
after
System.delete_env("PORT")
end
end

test "falls back to default for negative PORT value" do
System.put_env("PORT", "-1")

try do
assert ItemsApi.Application.resolve_port() == 4000
after
System.delete_env("PORT")
end
end
end

describe "default_port/0" do
test "returns 4000" do
assert ItemsApi.Application.default_port() == 4000
end
end

describe "application startup" do
test "Bandit is running and accepting connections" do
# The app is already started by mix test. Verify Bandit is listening.
assert Process.whereis(ItemsApi.Supervisor) != nil

children = Supervisor.which_children(ItemsApi.Supervisor)
bandit_running = Enum.any?(children, fn {_id, pid, _type, _modules} -> is_pid(pid) end)
assert bandit_running, "Expected Bandit to be running under supervisor"
end
end
end
Loading