Skip to content

Commit 85759f8

Browse files
taras-tyshkoTaras Tyshko
andauthored
Rework health-check endpoint, spec, and tests (#2292)
### Summary This pull request refactors the `/status/alive` healthcheck endpoint to improve its reliability, documentation, and testing. The previous implementation was handled directly by a plug, causing it to be missing from the OpenAPI specification. Its associated tests were also outdated and did not correctly test the plug's database interaction. ### Changes 1. **API Specification (`ApiSpec`):** * Manually added the OpenAPI specification for the `GET /status/alive` path. This ensures the healthcheck endpoint is correctly documented for all API consumers. * Removed previous implementation `HealthcheckController` ### Swagger UI <img width="1696" height="1047" alt="Screenshot 2025-08-19 at 4 54 51 PM" src="https://github.com/user-attachments/assets/0638394c-6f4b-44b3-bbad-5cb9fb6ba55f" /> --------- Co-authored-by: Taras Tyshko <[email protected]>
1 parent a1cc95c commit 85759f8

File tree

6 files changed

+59
-59
lines changed

6 files changed

+59
-59
lines changed

lib/nerves_hub_web/api_spec.ex

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ defmodule NervesHubWeb.ApiSpec do
1313
alias NervesHubWeb.API.OpenAPI.DeviceControllerSpecs
1414
alias NervesHubWeb.Endpoint
1515
alias NervesHubWeb.Router
16+
alias NervesHubWeb.Plugs.ImAlive
1617

1718
@behaviour OpenApi
1819

@@ -28,7 +29,7 @@ defmodule NervesHubWeb.ApiSpec do
2829
version: "2.0.0"
2930
},
3031
# Populate the paths from a phoenix router
31-
paths: Paths.from_router(Router),
32+
paths: set_paths(),
3233
components: %Components{
3334
securitySchemes: %{
3435
"bearer_auth" => %SecurityScheme{
@@ -91,6 +92,10 @@ defmodule NervesHubWeb.ApiSpec do
9192
name: "Signing Keys",
9293
description: "Organization Signing Key management"
9394
},
95+
%Tag{
96+
name: "Status",
97+
description: "Application healthcheck"
98+
},
9499
%Tag{
95100
name: "Support Scripts",
96101
description: "Organization Support Script management"
@@ -101,4 +106,10 @@ defmodule NervesHubWeb.ApiSpec do
101106
# Discover request/response schemas from path specs
102107
|> OpenApiSpex.resolve_schema_modules()
103108
end
109+
110+
defp set_paths() do
111+
Router
112+
|> Paths.from_router()
113+
|> Map.merge(ImAlive.status_path_spec())
114+
end
104115
end

lib/nerves_hub_web/controllers/healthcheck_controller.ex

Lines changed: 0 additions & 40 deletions
This file was deleted.

lib/nerves_hub_web/plugs/im_alive.ex

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ defmodule NervesHubWeb.Plugs.ImAlive do
66

77
import Plug.Conn
88

9+
@status_path "/status/alive"
910
def init(config), do: config
1011

11-
def call(%{request_path: "/status/alive"} = conn, _) do
12+
def call(%{request_path: @status_path} = conn, _) do
1213
case Ecto.Adapters.SQL.query(NervesHub.Repo, "SELECT true", []) do
1314
{:ok, _} -> send_result(conn, 200, "Hello, Friend!")
1415
_result -> send_result(conn, 500, "Sorry, Friend :(")
@@ -17,6 +18,40 @@ defmodule NervesHubWeb.Plugs.ImAlive do
1718

1819
def call(conn, _), do: conn
1920

21+
def status_path_spec() do
22+
%{
23+
@status_path => %OpenApiSpex.PathItem{
24+
get: %OpenApiSpex.Operation{
25+
summary: "Check application status",
26+
description:
27+
"Provides a simple health check to verify that the application is running, responsive, and can connect to the database.",
28+
tags: ["Status"],
29+
operationId: "Status.alive",
30+
responses: %{
31+
"200" => %OpenApiSpex.Response{
32+
description: "The application is running and the database is reachable.",
33+
content: %{
34+
"text/plain" => %OpenApiSpex.MediaType{
35+
schema: %OpenApiSpex.Schema{type: :string, example: "Hello, Friend!"}
36+
}
37+
}
38+
},
39+
"500" => %OpenApiSpex.Response{
40+
description: "The application is running but the database is unreachable.",
41+
content: %{
42+
"text/plain" => %OpenApiSpex.MediaType{
43+
schema: %OpenApiSpex.Schema{type: :string, example: "Sorry, Friend :("}
44+
}
45+
}
46+
}
47+
},
48+
# This endpoint does not require authentication, so we override the global security
49+
security: []
50+
}
51+
}
52+
}
53+
end
54+
2055
defp send_result(conn, code, message) do
2156
conn
2257
|> put_resp_content_type("text/plain")

lib/nerves_hub_web/router.ex

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,6 @@ defmodule NervesHubWeb.Router do
7979
plug(NervesHubWeb.API.Plugs.Device)
8080
end
8181

82-
scope "/" do
83-
pipe_through(:api)
84-
get("/healthcheck", NervesHubWeb.HealthcheckController, :index)
85-
end
86-
8782
scope "/api" do
8883
pipe_through(:api)
8984
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
@@ -152,7 +147,6 @@ defmodule NervesHubWeb.Router do
152147

153148
get("/", ProductController, :show)
154149
delete("/", ProductController, :delete)
155-
put("/", ProductController, :update)
156150

157151
scope "/devices" do
158152
get("/", DeviceController, :index)

test/nerves_hub_web/controllers/healthcheck_controller_test.exs

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule NervesHubWeb.Plugs.ImAliveTest do
2+
use NervesHubWeb.ConnCase, async: true
3+
4+
describe "GET /status/alive" do
5+
test "returns 200 OK when the database is reachable", %{conn: conn} do
6+
conn = get(conn, "/status/alive")
7+
assert conn.status == 200
8+
assert text_response(conn, 200) == "Hello, Friend!"
9+
end
10+
end
11+
end

0 commit comments

Comments
 (0)