towerops/test/towerops_web/live/helpers/access_control_test.exs
Graham McIntire 0ac99f679c
fix: convert alert_type from enum to string and fix SQL array syntax
Two critical production bugs fixed:

1. **Alert type enum → string conversion**
   - Changed Alert.alert_type from Ecto.Enum to :string for flexibility
   - Updated all queries to use "device_down"/"device_up" strings instead of atoms
   - Fixed pattern matching in alerts.ex and device_monitor_worker.ex
   - Updated 44+ test files to use string literals

2. **SQL array indexing syntax error in activity feed**
   - Fixed PostgreSQL syntax: `array_agg(...)[1]` → `(array_agg(...))[1]`
   - Prevents "syntax error at or near [" in activity feed queries

3. **Added comprehensive timer cleanup tests**
   - Tests for mobile_qr_live.ex timer cleanup on terminate
   - Tests for agent_live index timer cleanup
   - Verifies memory leak fixes from previous commits

Files changed:
- lib/towerops/alerts.ex
- lib/towerops/alerts/alert.ex
- lib/towerops/activity_feed.ex
- lib/towerops/workers/device_monitor_worker.ex
- test/**/*_test.exs (44+ files with alert_type references)
- test/towerops_web/live/mobile_qr_live_test.exs
- test/towerops_web/live/agent_live_test.exs
- test/towerops/workers/device_poller_worker_test.exs

All tests passing except 1 unrelated brute force protection test.
2026-03-05 09:12:39 -06:00

175 lines
4.8 KiB
Elixir

defmodule ToweropsWeb.Live.Helpers.AccessControlTest do
use Towerops.DataCase, async: true
import Towerops.AccountsFixtures
import Towerops.DevicesFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Alerts
alias Towerops.Sites
alias ToweropsWeb.Live.Helpers.AccessControl
describe "verify_device_access/2" do
setup do
user1 = user_fixture()
user2 = user_fixture()
org1 = organization_fixture(user1.id)
org2 = organization_fixture(user2.id)
site1 = site_fixture(organization_id: org1.id)
site2 = site_fixture(organization_id: org2.id)
device1 = device_fixture(%{site: site1, organization: org1})
device2 = device_fixture(%{site: site2, organization: org2})
%{
org1: org1,
org2: org2,
site1: site1,
site2: site2,
device1: device1,
device2: device2
}
end
test "returns {:ok, device} when device belongs to organization", %{
device1: device,
org1: org
} do
assert {:ok, returned_device} = AccessControl.verify_device_access(device.id, org.id)
assert returned_device.id == device.id
end
test "returns {:error, :unauthorized} when device belongs to different organization", %{
device1: device,
org2: wrong_org
} do
assert {:error, :unauthorized} = AccessControl.verify_device_access(device.id, wrong_org.id)
end
test "returns {:error, :not_found} when device does not exist", %{org1: org} do
fake_id = Ecto.UUID.generate()
assert {:error, :not_found} = AccessControl.verify_device_access(fake_id, org.id)
end
end
describe "verify_site_access/2" do
setup do
user1 = user_fixture()
user2 = user_fixture()
org1 = organization_fixture(user1.id)
org2 = organization_fixture(user2.id)
site1 = site_fixture(organization_id: org1.id)
site2 = site_fixture(organization_id: org2.id)
%{
org1: org1,
org2: org2,
site1: site1,
site2: site2
}
end
test "returns {:ok, site} when site belongs to organization", %{
site1: site,
org1: org
} do
assert {:ok, returned_site} = AccessControl.verify_site_access(site.id, org.id)
assert returned_site.id == site.id
end
test "returns {:error, :unauthorized} when site belongs to different organization", %{
site1: site,
org2: wrong_org
} do
assert {:error, :unauthorized} = AccessControl.verify_site_access(site.id, wrong_org.id)
end
test "returns {:error, :not_found} when site does not exist", %{org1: org} do
fake_id = Ecto.UUID.generate()
assert {:error, :not_found} = AccessControl.verify_site_access(fake_id, org.id)
end
end
describe "verify_alert_access/2" do
setup do
user1 = user_fixture()
user2 = user_fixture()
org1 = organization_fixture(user1.id)
org2 = organization_fixture(user2.id)
site1 = site_fixture(organization_id: org1.id)
site2 = site_fixture(organization_id: org2.id)
device1 = device_fixture(%{site: site1, organization: org1})
device2 = device_fixture(%{site: site2, organization: org2})
alert1 = alert_fixture(device_id: device1.id)
alert2 = alert_fixture(device_id: device2.id)
%{
org1: org1,
org2: org2,
device1: device1,
device2: device2,
alert1: alert1,
alert2: alert2
}
end
test "returns {:ok, alert} when alert's device belongs to organization", %{
alert1: alert,
org1: org
} do
assert {:ok, returned_alert} = AccessControl.verify_alert_access(alert.id, org.id)
assert returned_alert.id == alert.id
# Verify preloading worked
assert returned_alert.device
assert returned_alert.device.site
assert returned_alert.device.site.organization
end
test "returns {:error, :unauthorized} when alert's device belongs to different organization",
%{
alert1: alert,
org2: wrong_org
} do
assert {:error, :unauthorized} = AccessControl.verify_alert_access(alert.id, wrong_org.id)
end
test "returns {:error, :not_found} when alert does not exist", %{org1: org} do
fake_id = Ecto.UUID.generate()
assert {:error, :not_found} = AccessControl.verify_alert_access(fake_id, org.id)
end
end
# Helper functions to create test data
defp site_fixture(attrs) do
{:ok, site} =
attrs
|> Enum.into(%{
name: "Test Site #{System.unique_integer([:positive])}"
})
|> Sites.create_site()
site
end
defp alert_fixture(attrs) do
{:ok, alert} =
attrs
|> Enum.into(%{
alert_type: "device_down",
message: "Test alert",
triggered_at: DateTime.utc_now()
})
|> Alerts.create_alert()
alert
end
end