defmodule ToweropsWeb.Plugs.UpdateSessionActivityTest do use ToweropsWeb.ConnCase, async: false alias Towerops.Accounts.BrowserSession alias ToweropsWeb.Plugs.UpdateSessionActivity describe "init/1" do test "passes through opts unchanged" do assert UpdateSessionActivity.init(:some_opts) == :some_opts end end describe "call/2" do test "passes through when no user_token in session", %{conn: conn} do conn = conn |> init_test_session(%{}) |> UpdateSessionActivity.call(%{}) assert conn.state == :unset end test "starts background task when user_token present", %{conn: conn} do conn = conn |> init_test_session(%{user_token: "existing-token"}) |> UpdateSessionActivity.call(%{}) assert conn.state == :unset end test "starts background task when user_token and live_socket_id present", %{conn: conn} do conn = conn |> init_test_session(%{ user_token: "existing-token", live_socket_id: "users_sessions:abc" }) |> UpdateSessionActivity.call(%{}) assert conn.state == :unset end end describe "session_timed_out?/1" do test "returns true when session has been inactive for more than 30 minutes" do old_time = DateTime.add(DateTime.utc_now(), -31, :minute) session = %{last_activity_at: old_time} assert UpdateSessionActivity.session_timed_out?(session) end test "returns false when session was recently active" do recent_time = DateTime.add(DateTime.utc_now(), -5, :second) session = %{last_activity_at: recent_time} refute UpdateSessionActivity.session_timed_out?(session) end test "uses inserted_at when last_activity_at is nil" do old_time = DateTime.add(DateTime.utc_now(), -31, :minute) session = %{last_activity_at: nil, inserted_at: old_time} assert UpdateSessionActivity.session_timed_out?(session) end test "returns false for recent inserted_at when last_activity_at is nil" do recent_time = DateTime.add(DateTime.utc_now(), -5, :second) session = %{last_activity_at: nil, inserted_at: recent_time} refute UpdateSessionActivity.session_timed_out?(session) end test "returns false at exactly 30 minutes of inactivity" do borderline_time = DateTime.add(DateTime.utc_now(), -30, :minute) session = %{last_activity_at: borderline_time} refute UpdateSessionActivity.session_timed_out?(session) end test "returns true just over 30 minutes of inactivity" do old_time = DateTime.add(DateTime.utc_now(), -30, :minute) old_time = DateTime.add(old_time, -1, :second) session = %{last_activity_at: old_time} assert UpdateSessionActivity.session_timed_out?(session) end end describe "handle_session_activity/2" do test "returns :ok when no session found for token" do assert :ok == UpdateSessionActivity.handle_session_activity("nonexistent-token", nil) end test "returns :ok when session found and not timed out" do user = Towerops.AccountsFixtures.user_fixture() {_token, user_token} = Towerops.Accounts.generate_user_session_token_with_record(user) %BrowserSession{} |> BrowserSession.create_changeset(%{ user_id: user.id, user_token_id: user_token.id, ip_address: "127.0.0.1", last_activity_at: DateTime.utc_now(), expires_at: DateTime.add(DateTime.utc_now(), 7, :day) }) |> Towerops.Repo.insert!() assert :ok == UpdateSessionActivity.handle_session_activity(user_token.id, nil) end end describe "revoke_inactive_session/2" do test "returns nil when no live_socket_id" do user = Towerops.AccountsFixtures.user_fixture() token = Towerops.Accounts.generate_user_session_token(user) assert nil == UpdateSessionActivity.revoke_inactive_session(token, nil) end test "broadcasts disconnect when live_socket_id present" do user = Towerops.AccountsFixtures.user_fixture() token = Towerops.Accounts.generate_user_session_token(user) ToweropsWeb.Endpoint.subscribe("users_sessions:test-session") assert :ok == UpdateSessionActivity.revoke_inactive_session(token, "users_sessions:test-session") assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: "users_sessions:test-session"} end end end