towerops/test/towerops_web/graphql/resolvers/authenticated_test.exs
2026-06-15 11:15:46 -05:00

501 lines
18 KiB
Elixir

defmodule ToweropsWeb.GraphQL.Resolvers.AuthenticatedTest do
@moduledoc """
Direct invocation tests for happy and not-found paths of GraphQL resolvers.
Skips the Absinthe pipeline so we can test resolver logic in isolation.
"""
use Towerops.DataCase, async: true
import Towerops.AccountsFixtures
import Towerops.AgentsFixtures
import Towerops.DevicesFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Repo
alias ToweropsWeb.GraphQL.Resolvers
setup do
user = user_fixture()
org = organization_fixture(user.id)
%{user: user, organization: org, ctx: %{context: %{organization_id: org.id, user: user}}}
end
describe "Activity.list/3" do
test "returns ok-tuple for authenticated context", %{ctx: ctx} do
assert {:ok, items} = Resolvers.Activity.list(nil, %{}, ctx)
assert is_list(items)
end
test "ignores invalid type strings", %{ctx: ctx} do
assert {:ok, _items} = Resolvers.Activity.list(nil, %{type: "definitely_not_an_atom_xyz"}, ctx)
end
test "accepts known-existing atom type", %{ctx: ctx} do
_ = :alert_created
assert {:ok, _items} = Resolvers.Activity.list(nil, %{type: "alert_created"}, ctx)
end
end
describe "Agent.list/3 and create/3 / delete/3" do
test "lists agent tokens for the org", %{organization: org, ctx: ctx} do
_token = agent_token_fixture(org.id, "Foo")
assert {:ok, agents} = Resolvers.Agent.list(nil, %{}, ctx)
assert Enum.any?(agents, &(&1.name == "Foo"))
end
test "creates a new agent token and returns the raw token", %{ctx: ctx} do
assert {:ok, %{token: raw, name: name}} = Resolvers.Agent.create(nil, %{name: "Bar"}, ctx)
assert is_binary(raw) and raw != ""
assert name == "Bar"
end
test "returns 'Agent not found' for unknown id", %{ctx: ctx} do
assert {:error, "Agent not found"} =
Resolvers.Agent.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "delete on unknown id returns not-found error", %{ctx: ctx} do
assert {:error, "Agent not found"} =
Resolvers.Agent.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
end
describe "Organization.list_mine/3 and get/3 and update/3" do
test "list_mine includes the user's org", %{user: user, organization: org} do
ctx = %{context: %{user: user}}
assert {:ok, orgs} = Resolvers.Organization.list_mine(nil, %{}, ctx)
assert Enum.any?(orgs, &(&1.id == org.id))
end
test "get/3 returns the org from context", %{ctx: ctx, organization: org} do
assert {:ok, fetched} = Resolvers.Organization.get(nil, %{}, ctx)
assert fetched.id == org.id
end
test "update/3 with valid input changes the name", %{ctx: ctx} do
assert {:ok, updated} = Resolvers.Organization.update(nil, %{input: %{name: "Renamed Co"}}, ctx)
assert updated.name == "Renamed Co"
end
test "update/3 with invalid input returns an error string", %{ctx: ctx} do
assert {:error, msg} = Resolvers.Organization.update(nil, %{input: %{name: "x"}}, ctx)
assert is_binary(msg) and byte_size(msg) > 0
end
end
describe "Site resolvers" do
test "list/3 returns sites for org, get/3 returns one, create/3 succeeds, update/3 + delete/3 work", %{
organization: org,
ctx: ctx
} do
device = device_fixture(%{organization_id: org.id})
site_id = device.site_id
assert {:ok, sites} = Resolvers.Site.list(nil, %{}, ctx)
assert Enum.any?(sites, &(&1.id == site_id))
assert {:ok, fetched} = Resolvers.Site.get(nil, %{id: site_id}, ctx)
assert fetched.id == site_id
assert {:ok, created} =
Resolvers.Site.create(nil, %{input: %{name: "New Site"}}, ctx)
assert created.name == "New Site"
assert {:ok, updated} =
Resolvers.Site.update(nil, %{id: created.id, input: %{name: "Renamed"}}, ctx)
assert updated.name == "Renamed"
assert {:ok, %{success: true}} = Resolvers.Site.delete(nil, %{id: created.id}, ctx)
refute Repo.get(Towerops.Sites.Site, created.id)
end
test "get/3 with unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Site not found"} =
Resolvers.Site.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "update/3 with unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Site not found"} =
Resolvers.Site.update(
nil,
%{id: Ecto.UUID.generate(), input: %{name: "x"}},
ctx
)
end
test "delete/3 with unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Site not found"} =
Resolvers.Site.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
end
describe "Integration resolvers" do
test "list/3 returns []", %{ctx: ctx} do
assert {:ok, []} = Resolvers.Integration.list(nil, %{}, ctx)
end
test "update on unknown id returns 'Integration not found'", %{ctx: ctx} do
assert {:error, "Integration not found"} =
Resolvers.Integration.update(
nil,
%{id: Ecto.UUID.generate(), input: %{name: "x"}},
ctx
)
end
test "delete on unknown id returns 'Integration not found'", %{ctx: ctx} do
assert {:error, "Integration not found"} =
Resolvers.Integration.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "test_connection on unknown id returns 'Integration not found'", %{ctx: ctx} do
assert {:error, "Integration not found"} =
Resolvers.Integration.test_connection(nil, %{id: Ecto.UUID.generate()}, ctx)
end
end
describe "MaintenanceWindow resolvers" do
test "list/3 returns []", %{ctx: ctx} do
assert {:ok, []} = Resolvers.MaintenanceWindow.list(nil, %{}, ctx)
end
test "list/3 honors filter args", %{ctx: ctx} do
for filter <- ["active", "upcoming", "past", "everything"] do
assert {:ok, _} = Resolvers.MaintenanceWindow.list(nil, %{filter: filter}, ctx)
end
end
test "get/3 unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Maintenance window not found"} =
Resolvers.MaintenanceWindow.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "update on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Maintenance window not found"} =
Resolvers.MaintenanceWindow.update(
nil,
%{id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "delete on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Maintenance window not found"} =
Resolvers.MaintenanceWindow.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "create + get + update + delete happy path", %{ctx: ctx} do
starts = DateTime.utc_now() |> DateTime.add(3600, :second) |> DateTime.truncate(:second)
ends = DateTime.add(starts, 7200, :second)
assert {:ok, window} =
Resolvers.MaintenanceWindow.create(
nil,
%{
input: %{
name: "Smoke Window",
starts_at: starts,
ends_at: ends,
suppress_alerts: true
}
},
ctx
)
assert {:ok, fetched} = Resolvers.MaintenanceWindow.get(nil, %{id: window.id}, ctx)
assert fetched.id == window.id
assert {:ok, updated} =
Resolvers.MaintenanceWindow.update(
nil,
%{id: window.id, input: %{name: "Renamed"}},
ctx
)
assert updated.name == "Renamed"
assert {:ok, %{success: true}} =
Resolvers.MaintenanceWindow.delete(nil, %{id: window.id}, ctx)
end
test "create with invalid input returns formatted error", %{ctx: ctx} do
assert {:error, msg} =
Resolvers.MaintenanceWindow.create(nil, %{input: %{}}, ctx)
assert is_binary(msg) and byte_size(msg) > 0
end
end
describe "Alert resolvers — happy paths" do
test "list returns inserted alerts; get/3 fetches by id", %{organization: org, ctx: ctx} do
device = device_fixture(%{organization_id: org.id})
{:ok, alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: "device_down",
triggered_at: DateTime.truncate(DateTime.utc_now(), :second),
message: "down"
})
assert {:ok, alerts} = Resolvers.Alert.list(nil, %{}, ctx)
assert Enum.any?(alerts, &(&1.id == alert.id))
assert {:ok, fetched} = Resolvers.Alert.get(nil, %{id: alert.id}, ctx)
assert fetched.id == alert.id
end
test "acknowledge transitions an alert and returns it", %{organization: org, ctx: ctx} do
device = device_fixture(%{organization_id: org.id})
{:ok, alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: "device_down",
triggered_at: DateTime.truncate(DateTime.utc_now(), :second)
})
assert {:ok, acked} = Resolvers.Alert.acknowledge(nil, %{id: alert.id}, ctx)
assert acked.id == alert.id
assert acked.acknowledged_at
end
test "resolve_alert transitions and returns it", %{organization: org, ctx: ctx} do
device = device_fixture(%{organization_id: org.id})
{:ok, alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: "device_down",
triggered_at: DateTime.truncate(DateTime.utc_now(), :second)
})
assert {:ok, resolved} = Resolvers.Alert.resolve_alert(nil, %{id: alert.id}, ctx)
assert resolved.resolved_at
end
end
describe "EscalationPolicy resolvers (not-found paths)" do
test "get on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Escalation policy not found"} =
Resolvers.EscalationPolicy.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "update on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Escalation policy not found"} =
Resolvers.EscalationPolicy.update(
nil,
%{id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "delete on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Escalation policy not found"} =
Resolvers.EscalationPolicy.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "create_rule with unknown policy returns not-found", %{ctx: ctx} do
assert {:error, "Escalation policy not found"} =
Resolvers.EscalationPolicy.create_rule(
nil,
%{escalation_policy_id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "update_rule on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Rule not found"} =
Resolvers.EscalationPolicy.update_rule(
nil,
%{id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "delete_rule on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Rule not found"} =
Resolvers.EscalationPolicy.delete_rule(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "delete_target on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Target not found"} =
Resolvers.EscalationPolicy.delete_target(nil, %{id: Ecto.UUID.generate()}, ctx)
end
end
describe "Schedule resolvers (not-found paths)" do
test "get on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Schedule not found"} =
Resolvers.Schedule.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "on_call with unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Schedule not found"} =
Resolvers.Schedule.on_call(nil, %{schedule_id: Ecto.UUID.generate()}, ctx)
end
test "update on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Schedule not found"} =
Resolvers.Schedule.update(
nil,
%{id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "delete on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Schedule not found"} =
Resolvers.Schedule.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "create_layer with unknown schedule returns not-found", %{ctx: ctx} do
assert {:error, "Schedule not found"} =
Resolvers.Schedule.create_layer(
nil,
%{schedule_id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "delete_layer on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Layer not found"} =
Resolvers.Schedule.delete_layer(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "remove_member on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Member not found"} =
Resolvers.Schedule.remove_member(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "create_override with unknown schedule returns not-found", %{ctx: ctx} do
assert {:error, "Schedule not found"} =
Resolvers.Schedule.create_override(
nil,
%{schedule_id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "delete_override on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Override not found"} =
Resolvers.Schedule.delete_override(nil, %{id: Ecto.UUID.generate()}, ctx)
end
end
describe "Member resolvers (error paths)" do
test "remove on unknown user returns 'Member not found'", %{ctx: ctx} do
assert {:error, "Member not found"} =
Resolvers.Member.remove(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "update_role on unknown user returns 'Member not found'", %{ctx: ctx} do
assert {:error, "Member not found"} =
Resolvers.Member.update_role(
nil,
%{id: Ecto.UUID.generate(), role: "technician"},
ctx
)
end
test "cancel_invitation on unknown id returns 'Invitation not found'", %{ctx: ctx} do
assert {:error, "Invitation not found"} =
Resolvers.Member.cancel_invitation(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "list/3 returns owner as a member", %{ctx: ctx, user: user} do
assert {:ok, members} = Resolvers.Member.list(nil, %{}, ctx)
assert Enum.any?(members, &(&1.user_id == user.id))
end
end
describe "Device resolvers" do
test "list/3 with no devices returns []", %{ctx: ctx} do
assert {:ok, []} = Resolvers.Device.list(nil, %{}, ctx)
end
test "get/3 returns the device", %{organization: org, ctx: ctx} do
d = device_fixture(%{organization_id: org.id})
assert {:ok, found} = Resolvers.Device.get(nil, %{id: d.id}, ctx)
assert found.id == d.id
end
test "get on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Device not found"} =
Resolvers.Device.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "delete on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Device not found"} =
Resolvers.Device.delete(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "update on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Device not found"} =
Resolvers.Device.update(
nil,
%{id: Ecto.UUID.generate(), input: %{}},
ctx
)
end
test "metrics on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Device not found"} =
Resolvers.Device.metrics(nil, %{device_id: Ecto.UUID.generate()}, ctx)
end
test "interfaces on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Device not found"} =
Resolvers.Device.interfaces(nil, %{device_id: Ecto.UUID.generate()}, ctx)
end
test "interfaces returns [] when device has no snmp_device", %{organization: org, ctx: ctx} do
d = device_fixture(%{organization_id: org.id})
assert {:ok, []} = Resolvers.Device.interfaces(nil, %{device_id: d.id}, ctx)
end
test "metrics returns [] when device has no checks", %{organization: org, ctx: ctx} do
d = device_fixture(%{organization_id: org.id})
assert {:ok, []} =
Resolvers.Device.metrics(
nil,
%{device_id: d.id, time_range: "12h", sensor_type: nil},
ctx
)
end
test "metrics with malformed time_range falls back to 24h", %{organization: org, ctx: ctx} do
d = device_fixture(%{organization_id: org.id})
assert {:ok, []} =
Resolvers.Device.metrics(
nil,
%{device_id: d.id, time_range: "garbage"},
ctx
)
end
end
describe "Alert resolvers" do
test "list/3 returns []", %{ctx: ctx} do
assert {:ok, []} = Resolvers.Alert.list(nil, %{}, ctx)
end
test "get on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Alert not found"} =
Resolvers.Alert.get(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "acknowledge on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Alert not found"} =
Resolvers.Alert.acknowledge(nil, %{id: Ecto.UUID.generate()}, ctx)
end
test "resolve_alert on unknown id returns not-found", %{ctx: ctx} do
assert {:error, "Alert not found"} =
Resolvers.Alert.resolve_alert(nil, %{id: Ecto.UUID.generate()}, ctx)
end
end
end