501 lines
18 KiB
Elixir
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
|