test(vereinfacht): add tests and scope README
- Config, Client, SyncContact, Vereinfacht module tests (no real API) - vereinfacht_test_README: document test scope
This commit is contained in:
parent
c46365576d
commit
e4e6cfdd47
5 changed files with 291 additions and 0 deletions
61
test/mv/config_vereinfacht_test.exs
Normal file
61
test/mv/config_vereinfacht_test.exs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
defmodule Mv.ConfigVereinfachtTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Config Vereinfacht-related helpers.
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
describe "vereinfacht_env_configured?/0" do
|
||||
test "returns false when no Vereinfacht ENV variables are set" do
|
||||
clear_vereinfacht_env()
|
||||
refute Mv.Config.vereinfacht_env_configured?()
|
||||
end
|
||||
|
||||
test "returns true when VEREINFACHT_API_URL is set" do
|
||||
set_vereinfacht_env("VEREINFACHT_API_URL", "https://api.example.com")
|
||||
assert Mv.Config.vereinfacht_env_configured?()
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
|
||||
test "returns true when VEREINFACHT_CLUB_ID is set" do
|
||||
set_vereinfacht_env("VEREINFACHT_CLUB_ID", "2")
|
||||
assert Mv.Config.vereinfacht_env_configured?()
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
end
|
||||
|
||||
describe "vereinfacht_configured?/0" do
|
||||
test "returns false when no config is set" do
|
||||
clear_vereinfacht_env()
|
||||
# Settings may have nil for vereinfacht fields
|
||||
refute Mv.Config.vereinfacht_configured?()
|
||||
end
|
||||
end
|
||||
|
||||
describe "vereinfacht_contact_view_url/1" do
|
||||
test "returns nil when API URL is not configured" do
|
||||
clear_vereinfacht_env()
|
||||
assert Mv.Config.vereinfacht_contact_view_url("123") == nil
|
||||
end
|
||||
|
||||
test "returns URL when API URL is set" do
|
||||
set_vereinfacht_env("VEREINFACHT_API_URL", "https://api.example.com/api/v1")
|
||||
|
||||
assert Mv.Config.vereinfacht_contact_view_url("42") ==
|
||||
"https://api.example.com/api/v1/finance-contacts/42"
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
end
|
||||
|
||||
defp set_vereinfacht_env(key, value) do
|
||||
System.put_env(key, value)
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
92
test/mv/vereinfacht/changes/sync_contact_test.exs
Normal file
92
test/mv/vereinfacht/changes/sync_contact_test.exs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
defmodule Mv.Vereinfacht.Changes.SyncContactTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Vereinfacht.Changes.SyncContact.
|
||||
|
||||
When Vereinfacht is not configured, member create/update should succeed
|
||||
and vereinfacht_contact_id remains nil.
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Membership
|
||||
|
||||
setup do
|
||||
clear_vereinfacht_env()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "member create when Vereinfacht not configured" do
|
||||
test "member is created and vereinfacht_contact_id is nil" do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
attrs = %{
|
||||
first_name: "Sync",
|
||||
last_name: "Test",
|
||||
email: "sync_test_#{System.unique_integer([:positive])}@example.com"
|
||||
}
|
||||
|
||||
assert {:ok, member} = Membership.create_member(attrs, actor: system_actor)
|
||||
assert member.vereinfacht_contact_id == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "member update when Vereinfacht not configured" do
|
||||
test "member is updated and vereinfacht_contact_id is unchanged" do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
assert {:ok, updated} =
|
||||
Membership.update_member(member, %{first_name: "Updated"}, actor: system_actor)
|
||||
|
||||
assert updated.vereinfacht_contact_id == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "when Vereinfacht is configured" do
|
||||
# Regression: after_transaction callback receives 2 args (changeset, result), not 3.
|
||||
# If the callback had arity 3, create_member would raise BadArityError.
|
||||
# Also: Client must send JSON-encoded body (iodata); raw map causes ArgumentError
|
||||
# when the request is sent. With an unreachable URL we get :econnrefused before
|
||||
# that, so this test would not catch the iodata bug; a Bypass/stub server would.
|
||||
test "create_member succeeds and after_transaction runs without error (API may fail)" do
|
||||
set_vereinfacht_env()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
attrs = %{
|
||||
first_name: "API",
|
||||
last_name: "Test",
|
||||
email: "api_test_#{System.unique_integer([:positive])}@example.com"
|
||||
}
|
||||
|
||||
assert {:ok, member} = Membership.create_member(attrs, actor: system_actor)
|
||||
assert member.id
|
||||
# Sync may fail (e.g. connection refused), so contact_id can stay nil
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
|
||||
test "update_member succeeds and after_transaction runs without error (API may fail)" do
|
||||
set_vereinfacht_env()
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
|
||||
assert {:ok, updated} =
|
||||
Membership.update_member(member, %{first_name: "Updated"}, actor: system_actor)
|
||||
|
||||
assert updated.id == member.id
|
||||
after
|
||||
clear_vereinfacht_env()
|
||||
end
|
||||
end
|
||||
|
||||
defp set_vereinfacht_env do
|
||||
System.put_env("VEREINFACHT_API_URL", "http://127.0.0.1:1/api/v1")
|
||||
System.put_env("VEREINFACHT_API_KEY", "test-key")
|
||||
System.put_env("VEREINFACHT_CLUB_ID", "2")
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
50
test/mv/vereinfacht/client_test.exs
Normal file
50
test/mv/vereinfacht/client_test.exs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
defmodule Mv.Vereinfacht.ClientTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Vereinfacht.Client.
|
||||
|
||||
Only tests the "not configured" path; no real HTTP calls. Config reads from
|
||||
ENV first, then from Settings (DB), so we use DataCase so get_settings() is available.
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Vereinfacht.Client
|
||||
|
||||
setup do
|
||||
clear_vereinfacht_env()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "create_contact/1" do
|
||||
test "returns {:error, :not_configured} when Vereinfacht is not configured" do
|
||||
member = build_member_struct()
|
||||
|
||||
assert Client.create_contact(member) == {:error, :not_configured}
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_contact/2" do
|
||||
test "returns {:error, :not_configured} when Vereinfacht is not configured" do
|
||||
member = build_member_struct()
|
||||
|
||||
assert Client.update_contact("123", member) == {:error, :not_configured}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_member_struct do
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
email: "test@example.com",
|
||||
street: "Street 1",
|
||||
house_number: "2",
|
||||
postal_code: "12345",
|
||||
city: "Berlin"
|
||||
}
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
59
test/mv/vereinfacht/vereinfacht_test.exs
Normal file
59
test/mv/vereinfacht/vereinfacht_test.exs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
defmodule Mv.VereinfachtTest do
|
||||
@moduledoc """
|
||||
Tests for Mv.Vereinfacht business logic.
|
||||
|
||||
No real API calls; tests "not configured" path and pure helpers (format_error).
|
||||
"""
|
||||
use Mv.DataCase, async: false
|
||||
|
||||
alias Mv.Vereinfacht
|
||||
|
||||
setup do
|
||||
clear_vereinfacht_env()
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "sync_member/1" do
|
||||
test "returns :ok when Vereinfacht is not configured (no-op)" do
|
||||
member = Mv.Fixtures.member_fixture()
|
||||
|
||||
assert Vereinfacht.sync_member(member) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "sync_members_without_contact/0" do
|
||||
test "returns {:error, :not_configured} when Vereinfacht is not configured" do
|
||||
assert Vereinfacht.sync_members_without_contact() == {:error, :not_configured}
|
||||
end
|
||||
end
|
||||
|
||||
describe "format_error/1" do
|
||||
test "formats HTTP error with detail" do
|
||||
assert Vereinfacht.format_error({:http, 422, "The email field is required."}) ==
|
||||
"Vereinfacht: The email field is required."
|
||||
end
|
||||
|
||||
test "formats HTTP error without detail" do
|
||||
assert Vereinfacht.format_error({:http, 500, nil}) ==
|
||||
"Vereinfacht: API error (HTTP 500)."
|
||||
end
|
||||
|
||||
test "formats request_failed" do
|
||||
assert Vereinfacht.format_error({:request_failed, %{reason: :econnrefused}}) ==
|
||||
"Vereinfacht: Request failed (e.g. connection error)."
|
||||
end
|
||||
|
||||
test "formats invalid_response and other terms" do
|
||||
assert Vereinfacht.format_error({:invalid_response, %{}}) ==
|
||||
"Vereinfacht: Invalid API response."
|
||||
|
||||
assert Vereinfacht.format_error(:timeout) == "Vereinfacht: :timeout"
|
||||
end
|
||||
end
|
||||
|
||||
defp clear_vereinfacht_env do
|
||||
System.delete_env("VEREINFACHT_API_URL")
|
||||
System.delete_env("VEREINFACHT_API_KEY")
|
||||
System.delete_env("VEREINFACHT_CLUB_ID")
|
||||
end
|
||||
end
|
||||
29
test/mv/vereinfacht/vereinfacht_test_README.md
Normal file
29
test/mv/vereinfacht/vereinfacht_test_README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Vereinfacht tests – scope and rationale
|
||||
|
||||
## Constraint: no real API in CI
|
||||
|
||||
Tests do **not** call the real Vereinfacht API or a shared test endpoint. All tests use dummy data and either:
|
||||
|
||||
- Assert behaviour when **Vereinfacht is not configured** (ENV + Settings unset), or
|
||||
- Run the **full Member/User flow** with a **unreachable URL** (e.g. `http://127.0.0.1:1`) so the HTTP client fails fast (e.g. `:econnrefused`) and we only assert that the application path does not crash.
|
||||
|
||||
## What the tests cover
|
||||
|
||||
| Test file | What it tests | Why it’s enough without an API |
|
||||
|-----------|----------------|---------------------------------|
|
||||
| **ConfigVereinfachtTest** | `vereinfacht_env_configured?`, `vereinfacht_configured?`, `vereinfacht_contact_view_url` with ENV set/cleared | Pure config logic; no HTTP. |
|
||||
| **ClientTest** | `create_contact/1` and `update_contact/2` return `{:error, :not_configured}` when nothing is configured | Ensures the client does not call Req when config is missing. |
|
||||
| **VereinfachtTest** | `sync_members_without_contact/0` returns `{:error, :not_configured}` when not configured | Ensures bulk sync is a no-op when config is missing. |
|
||||
| **SyncContactTest** | Member create/update with SyncContact change: not configured → no sync; configured with bad URL → action still succeeds, sync may fail | Ensures the Ash change and after_transaction arity are correct and the action result is not broken by sync failures. |
|
||||
|
||||
## What is *not* tested (and would need a stub or real endpoint)
|
||||
|
||||
- Actual HTTP request shape (body, headers) and response handling (201/200, error codes).
|
||||
- Persistence of `vereinfacht_contact_id` after a successful create.
|
||||
- Translation of specific API error payloads into user messages.
|
||||
|
||||
Those would require either a **Bypass** (or similar) stub in front of Req or a dedicated test endpoint; both are out of scope for the current “no real API” setup.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Given the constraint that the API is not called in CI, the tests are **meaningful**: they cover config, “not configured” paths, and integration of SyncContact with Member create/update without crashing. They are **sufficient** for regression safety and refactoring; extending them with a Bypass stub would be an optional next step if we want to assert on request/response shape without hitting the real API.
|
||||
Loading…
Add table
Add a link
Reference in a new issue