- add MobileSocket for token-authenticated mobile WebSocket connections - add MobileChannel with org-level and device-level real-time events - add /mobile/socket endpoint for iOS app connections - add "Link Mobile App" to user dropdown menu for discoverability - improve QR code page with numbered steps and clearer instructions - add socket/channel tests (17 total)
209 lines
6 KiB
Elixir
209 lines
6 KiB
Elixir
defmodule ToweropsWeb.MobileChannelTest do
|
|
use Towerops.DataCase, async: false
|
|
|
|
import Phoenix.ChannelTest
|
|
|
|
alias Towerops.MobileSessions
|
|
alias ToweropsWeb.MobileSocket
|
|
|
|
@endpoint ToweropsWeb.Endpoint
|
|
|
|
setup do
|
|
user = Towerops.AccountsFixtures.user_fixture()
|
|
organization = Towerops.OrganizationsFixtures.organization_fixture(user.id)
|
|
|
|
device =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: organization.id,
|
|
snmp_version: "2c",
|
|
snmp_community: "public"
|
|
})
|
|
|
|
{:ok, qr_token} = MobileSessions.create_qr_login_token(user.id)
|
|
|
|
{:ok, session} =
|
|
MobileSessions.complete_qr_login(qr_token.token, %{
|
|
device_name: "Test iPhone",
|
|
device_os: "iOS 18.0",
|
|
app_version: "1.0.0"
|
|
})
|
|
|
|
{:ok, socket} = connect(MobileSocket, %{"token" => session.raw_token})
|
|
|
|
%{socket: socket, user: user, organization: organization, device: device}
|
|
end
|
|
|
|
# --- Org-level join ---
|
|
|
|
describe "join mobile:org:* topic" do
|
|
test "joins successfully with valid org access", %{socket: socket, organization: org} do
|
|
assert {:ok, _reply, _socket} =
|
|
subscribe_and_join(socket, "mobile:org:#{org.id}", %{})
|
|
end
|
|
|
|
test "rejects join for unauthorized org", %{socket: socket} do
|
|
fake_org_id = Ecto.UUID.generate()
|
|
|
|
assert {:error, %{reason: "unauthorized"}} =
|
|
subscribe_and_join(socket, "mobile:org:#{fake_org_id}", %{})
|
|
end
|
|
end
|
|
|
|
# --- Org-level alert events ---
|
|
|
|
describe "org-level alert events" do
|
|
test "pushes alert:changed when alert broadcast received", %{
|
|
socket: socket,
|
|
organization: org
|
|
} do
|
|
{:ok, _reply, _socket} = subscribe_and_join(socket, "mobile:org:#{org.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"organization:#{org.id}:alerts",
|
|
{:alert_changed, org.id}
|
|
)
|
|
|
|
assert_push "alert:changed", %{organization_id: _}
|
|
end
|
|
end
|
|
|
|
# --- Org-level device events ---
|
|
|
|
describe "org-level device events" do
|
|
test "pushes device:changed on device_status_changed", %{
|
|
socket: socket,
|
|
organization: org
|
|
} do
|
|
{:ok, _reply, _socket} = subscribe_and_join(socket, "mobile:org:#{org.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"devices:org:#{org.id}",
|
|
{:device_status_changed, org.id}
|
|
)
|
|
|
|
assert_push "device:changed", %{event: "device_status_changed"}
|
|
end
|
|
|
|
test "pushes device:changed on device_created", %{socket: socket, organization: org} do
|
|
{:ok, _reply, _socket} = subscribe_and_join(socket, "mobile:org:#{org.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"devices:org:#{org.id}",
|
|
{:device_created, org.id}
|
|
)
|
|
|
|
assert_push "device:changed", %{event: "device_created"}
|
|
end
|
|
|
|
test "pushes device:changed on device_updated", %{socket: socket, organization: org} do
|
|
{:ok, _reply, _socket} = subscribe_and_join(socket, "mobile:org:#{org.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"devices:org:#{org.id}",
|
|
{:device_updated, org.id}
|
|
)
|
|
|
|
assert_push "device:changed", %{event: "device_updated"}
|
|
end
|
|
|
|
test "pushes device:changed on device_deleted", %{socket: socket, organization: org} do
|
|
{:ok, _reply, _socket} = subscribe_and_join(socket, "mobile:org:#{org.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"devices:org:#{org.id}",
|
|
{:device_deleted, org.id}
|
|
)
|
|
|
|
assert_push "device:changed", %{event: "device_deleted"}
|
|
end
|
|
end
|
|
|
|
# --- Device-level join ---
|
|
|
|
describe "join mobile:device:* topic" do
|
|
test "joins successfully for device in user's org", %{socket: socket, device: device} do
|
|
assert {:ok, _reply, _socket} =
|
|
subscribe_and_join(socket, "mobile:device:#{device.id}", %{})
|
|
end
|
|
|
|
test "rejects join for non-existent device", %{socket: socket} do
|
|
assert {:error, %{reason: "not_found"}} =
|
|
subscribe_and_join(socket, "mobile:device:#{Ecto.UUID.generate()}", %{})
|
|
end
|
|
end
|
|
|
|
# --- Device-level sensor events ---
|
|
|
|
describe "device-level sensor events" do
|
|
test "pushes device:event for sensor threshold change", %{
|
|
socket: socket,
|
|
device: device
|
|
} do
|
|
{:ok, _reply, _socket} =
|
|
subscribe_and_join(socket, "mobile:device:#{device.id}", %{})
|
|
|
|
event = %{
|
|
device_id: device.id,
|
|
event_type: "sensor_threshold_critical",
|
|
severity: "critical",
|
|
message: "CPU temperature exceeded 90C",
|
|
metadata: %{
|
|
sensor_name: "CPU Temp",
|
|
sensor_type: "temperature",
|
|
current_value: 95.0,
|
|
threshold_value: 90.0
|
|
},
|
|
occurred_at: DateTime.utc_now()
|
|
}
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"device:#{device.id}",
|
|
{:device_event, event}
|
|
)
|
|
|
|
assert_push "device:event", payload
|
|
assert payload.event_type == "sensor_threshold_critical"
|
|
assert payload.severity == "critical"
|
|
assert payload.device_id == device.id
|
|
end
|
|
end
|
|
|
|
# --- Device-level bulk updates ---
|
|
|
|
describe "device-level bulk updates" do
|
|
test "pushes device:updated for sensors_updated", %{socket: socket, device: device} do
|
|
{:ok, _reply, _socket} =
|
|
subscribe_and_join(socket, "mobile:device:#{device.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"device:#{device.id}",
|
|
{:sensors_updated, device.id}
|
|
)
|
|
|
|
assert_push "device:updated", %{
|
|
device_id: _,
|
|
update_type: "sensors_updated"
|
|
}
|
|
end
|
|
|
|
test "pushes device:updated for interfaces_updated", %{socket: socket, device: device} do
|
|
{:ok, _reply, _socket} =
|
|
subscribe_and_join(socket, "mobile:device:#{device.id}", %{})
|
|
|
|
Phoenix.PubSub.broadcast(
|
|
Towerops.PubSub,
|
|
"device:#{device.id}",
|
|
{:interfaces_updated, device.id}
|
|
)
|
|
|
|
assert_push "device:updated", %{update_type: "interfaces_updated"}
|
|
end
|
|
end
|
|
end
|