From a37695fbb980a11ff99c24f39ca72ed837e4e34a Mon Sep 17 00:00:00 2001 From: Randy Schmidt Date: Wed, 13 May 2026 16:23:00 -0600 Subject: [PATCH 1/2] Format Elixir sources - Apply Mix formatting to config, library, and test files - Normalize missing trailing newlines --- config/config.exs | 2 +- config/test.exs | 6 ++--- lib/mix/tasks/generate_vapid_keys.ex | 2 +- lib/web_push_elixir.ex | 13 +++++----- test/web_push_elixir_test.exs | 36 ++++++++++++++++------------ 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/config/config.exs b/config/config.exs index 249ba51..c4f4b17 100644 --- a/config/config.exs +++ b/config/config.exs @@ -2,4 +2,4 @@ import Config if Mix.env() == :test do import_config "test.exs" -end \ No newline at end of file +end diff --git a/config/test.exs b/config/test.exs index 6c52765..ad664a7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,6 +1,6 @@ import Config config :web_push_elixir, - vapid_public_key: "someVapidPublicKey", - vapid_private_key: "someVapidPrivateKey", - vapid_subject: "mailto:admin@email.com" \ No newline at end of file + vapid_public_key: "someVapidPublicKey", + vapid_private_key: "someVapidPrivateKey", + vapid_subject: "mailto:admin@email.com" diff --git a/lib/mix/tasks/generate_vapid_keys.ex b/lib/mix/tasks/generate_vapid_keys.ex index 583e208..11c193b 100644 --- a/lib/mix/tasks/generate_vapid_keys.ex +++ b/lib/mix/tasks/generate_vapid_keys.ex @@ -11,6 +11,6 @@ defmodule Mix.Tasks.Generate.Vapid.Keys do vapid_private_key: Base.url_encode64(private_key, padding: false), vapid_subject: "mailto:admin@email.com" } - |> IO.inspect + |> IO.inspect() end end diff --git a/lib/web_push_elixir.ex b/lib/web_push_elixir.ex index 159cf58..22cb444 100644 --- a/lib/web_push_elixir.ex +++ b/lib/web_push_elixir.ex @@ -147,7 +147,8 @@ defmodule WebPushElixir do {"content-encoding", "aesgcm"}, {"content-length", "#{byte_size(encrypted_payload.ciphertext)}"}, {"content-type", "application/octet-stream"}, - {"crypto-key", "dh=#{url_encode(encrypted_payload.local_public_key)};p256ecdsa=#{url_encode(vapid_public_key)}"}, + {"crypto-key", + "dh=#{url_encode(encrypted_payload.local_public_key)};p256ecdsa=#{url_encode(vapid_public_key)}"}, {"encryption", "salt=#{url_encode(encrypted_payload.salt)}"}, {"ttl", "#{ttl}"} ] @@ -156,11 +157,11 @@ defmodule WebPushElixir do headers = if topic, do: [{"topic", topic} | headers], else: headers case Req.run( - method: :post, - url: endpoint, - body: encrypted_payload.ciphertext, - headers: headers - ) do + method: :post, + url: endpoint, + body: encrypted_payload.ciphertext, + headers: headers + ) do {request, %{status: status} = response} when status in 200..202 -> {:ok, Map.put(response, :request, request)} diff --git a/test/web_push_elixir_test.exs b/test/web_push_elixir_test.exs index 4a70aca..20dbc15 100644 --- a/test/web_push_elixir_test.exs +++ b/test/web_push_elixir_test.exs @@ -4,10 +4,11 @@ defmodule WebPushElixirTest do @test_subscription %{ "endpoint" => "http://localhost:4040/some-push-service", "keys" => %{ - "p256dh" => "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM=", + "p256dh" => + "BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM=", "auth" => "tBHItJI5svbpez7KI4CCXg==" - } } + } test "it should send notification" do subscription = Jason.encode!(@test_subscription) @@ -15,14 +16,14 @@ defmodule WebPushElixirTest do {:ok, response} = WebPushElixir.send_notification(subscription, "some message") assert %{ - "authorization" => ["WebPush " <> <<_jwt::binary>>], - "content-encoding" => ["aesgcm"], - "content-length" => ["30"], - "content-type" => ["application/octet-stream"], - "crypto-key" => ["dh=" <> <<_crypto_keys::binary>>], - "encryption" => ["salt=" <> <<_salt::binary>>], - "ttl" => ["60"] - } = response.request.headers + "authorization" => ["WebPush " <> <<_jwt::binary>>], + "content-encoding" => ["aesgcm"], + "content-length" => ["30"], + "content-type" => ["application/octet-stream"], + "crypto-key" => ["dh=" <> <<_crypto_keys::binary>>], + "encryption" => ["salt=" <> <<_salt::binary>>], + "ttl" => ["60"] + } = response.request.headers assert response.status in 200..202 end @@ -38,7 +39,8 @@ defmodule WebPushElixirTest do test "it should send the urgency parameter" do subscription = Jason.encode!(@test_subscription) - {:ok, response} = WebPushElixir.send_notification(subscription, "some message", urgency: :high) + {:ok, response} = + WebPushElixir.send_notification(subscription, "some message", urgency: :high) assert ["high"] = Map.get(response.request.headers, "urgency") end @@ -46,22 +48,26 @@ defmodule WebPushElixirTest do test "it should set the topic if it is provided" do subscription = Jason.encode!(@test_subscription) - {:ok, response} = WebPushElixir.send_notification(subscription, "some message", topic: "some-test-topic") + {:ok, response} = + WebPushElixir.send_notification(subscription, "some message", topic: "some-test-topic") assert ["some-test-topic"] = Map.get(response.request.headers, "topic") end test "it should return expired" do - expired_subscription = Jason.encode!(%{@test_subscription | "endpoint" => "http://localhost:4040/gone"}) + expired_subscription = + Jason.encode!(%{@test_subscription | "endpoint" => "http://localhost:4040/gone"}) assert {:error, :expired} = WebPushElixir.send_notification(expired_subscription, "message") end test "it should return http error" do + error_subscription = + Jason.encode!(%{@test_subscription | "endpoint" => "http://localhost:4040/error"}) - error_subscription = Jason.encode!(%{@test_subscription | "endpoint" => "http://localhost:4040/error"}) + assert {:error, {:http_error, status, _body}} = + WebPushElixir.send_notification(error_subscription, "message") - assert {:error, {:http_error, status, _body}} = WebPushElixir.send_notification(error_subscription, "message") assert status != 200..202 end end From b4ca1b80941f7ce69aefee1a71038bd4c6d36c44 Mon Sep 17 00:00:00 2001 From: Randy Schmidt Date: Wed, 13 May 2026 16:23:14 -0600 Subject: [PATCH 2/2] Add precommit workflow guidance - Add mix precommit alias for formatting, compile, and tests - Run the precommit alias in the test environment - Document repository contribution guidelines --- AGENTS.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ mix.exs | 13 +++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3527cbe --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +This is an Elixir Mix library for sending encrypted Web Push notifications. +Core source lives in `lib/`: `lib/web_push_elixir.ex` contains the public +notification API and crypto/header logic, `lib/web_push_elixir/application.ex` +starts supervision, and `lib/mix/tasks/generate_vapid_keys.ex` defines the VAPID +key generator task. Runtime and test configuration are in `config/`. Tests live +in `test/`, with shared local HTTP behavior in `test/support/mock_server.ex`. +The browser demo and GitHub Pages assets are in `example/`. + +## Build, Test, and Development Commands + +- `mix deps.get` installs Hex dependencies. +- `mix compile` compiles the library and checks for compile-time errors. +- `mix test` runs the ExUnit suite; tests start a local Plug/Cowboy server on + port `4040`. +- `mix coveralls.json` runs test coverage in the same format used by CI. +- `mix format` formats all Elixir files according to Mix formatter defaults. +- `mix generate.vapid.keys` generates sample VAPID keys for local configuration. +- `mix precommit` checks formatting, compiles with warnings as errors, and runs + the test suite. Run this before every commit. + +## Coding Style & Naming Conventions + +Use standard Elixir formatting: two-space indentation, pipeline-friendly +function chains, and `snake_case` for functions, variables, and test names. +Modules use `PascalCase` under the `WebPushElixir` namespace, with Mix tasks +under `Mix.Tasks.*`. Prefer pattern matching and tagged tuples such as +`{:ok, response}` and `{:error, reason}` for control flow. Keep comments sparse; +public behavior should be documented with `@doc` examples when helpful. + +## Testing Guidelines + +Tests use ExUnit and should be named `*_test.exs`. Add focused tests next to the +behavior being changed, and use `test/support/mock_server.ex` for push-service +HTTP responses instead of calling external services. When changing request +headers, encryption payload behavior, or error handling, assert both return +tuples and relevant response/request fields. Run `mix test` before submitting; +run `mix coveralls.json` for changes that affect coverage-sensitive logic. + +## Commit & Pull Request Guidelines + +Recent history uses short imperative messages, often with Conventional Commit +prefixes such as `build(deps): bump jason from 1.4.4 to 1.4.5`, `build: 0.8.0`, +and `ci: revert test`. Run `mix precommit` before every commit, then keep +commits scoped and descriptive. Pull requests should include a concise summary, +test results, linked issues when applicable, and note any configuration or +example asset changes. Screenshots are only useful for changes under `example/`. + +## Security & Configuration Tips + +Never commit real VAPID private keys. Use placeholders in docs and configure +`vapid_public_key`, `vapid_private_key`, and `vapid_subject` through environment +specific config or application runtime configuration. diff --git a/mix.exs b/mix.exs index 00217f5..8ddf2bd 100644 --- a/mix.exs +++ b/mix.exs @@ -9,6 +9,7 @@ defmodule WebPushElixir.MixProject do elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), + aliases: aliases(), description: "Simple web push for Elixir", package: [ licenses: ["MIT"], @@ -18,6 +19,12 @@ defmodule WebPushElixir.MixProject do ] end + def cli do + [ + preferred_envs: [precommit: :test] + ] + end + # Run "mix help compile.app" to learn about applications. def application do [ @@ -42,4 +49,10 @@ defmodule WebPushElixir.MixProject do {:plug_cowboy, "~> 2.0", only: :test} ] end + + defp aliases do + [ + precommit: ["format --check-formatted", "compile --warnings-as-errors", "test"] + ] + end end