defmodule ToweropsWeb.OnboardingLiveTest do @moduledoc """ Tests for ToweropsWeb.OnboardingLive. Note: OnboardingLive is not currently routed, so we test the module's behavior through direct socket/event simulation rather than HTTP mounting. """ use ToweropsWeb.ConnCase, async: true import Towerops.AccountsFixtures import Towerops.OrganizationsFixtures alias Phoenix.HTML.Form alias Phoenix.LiveView.Socket alias ToweropsWeb.OnboardingLive setup do user = user_fixture() organization = organization_fixture(user.id) %{user: user, organization: organization} end describe "module structure" do test "module exists and is a LiveView" do Code.ensure_loaded!(OnboardingLive) assert function_exported?(OnboardingLive, :mount, 3) assert function_exported?(OnboardingLive, :handle_event, 3) assert function_exported?(OnboardingLive, :handle_params, 3) end end describe "mount/3" do test "initializes with correct default state", %{user: user, organization: organization} do socket = %Socket{ assigns: %{ __changed__: %{}, current_scope: %{user: user, organization: organization}, flash: %{} }, endpoint: ToweropsWeb.Endpoint } assert {:ok, socket} = OnboardingLive.mount(%{}, %{}, socket) assert %{assigns: assigns} = socket assert assigns.step == :snmp assert assigns.organization == organization assert assigns.steps == [:snmp, :site, :billing, :agent] assert assigns.selected_provider == nil assert assigns.agent_token == nil assert assigns.agent_token_value == nil assert assigns.page_title end end describe "handle_event skip" do test "skip advances from snmp to site", %{user: user, organization: organization} do socket = build_socket(user, organization) %{assigns: assigns} = socket socket = %{socket | assigns: %{assigns | step: :snmp}} assert {:noreply, %{assigns: new_assigns}} = OnboardingLive.handle_event("skip", %{}, socket) assert new_assigns.step == :site end test "skip advances from site to billing", %{user: user, organization: organization} do socket = build_socket(user, organization) %{assigns: assigns} = socket socket = %{socket | assigns: %{assigns | step: :site}} assert {:noreply, %{assigns: new_assigns}} = OnboardingLive.handle_event("skip", %{}, socket) assert new_assigns.step == :billing end end describe "handle_event go_to_step" do test "navigates to specified step", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event("go_to_step", %{"step" => "site"}, socket) assert %{assigns: %{step: :site}} = socket end test "navigates to billing step", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event("go_to_step", %{"step" => "billing"}, socket) assert %{assigns: %{step: :billing}} = socket end end describe "handle_event select_provider" do test "sets selected provider and creates integration form", %{user: user, organization: organization} do socket = build_socket(user, organization) %{assigns: assigns} = socket socket = %{socket | assigns: %{assigns | step: :billing}} assert {:noreply, socket} = OnboardingLive.handle_event("select_provider", %{"provider" => "sonar"}, socket) assert %{assigns: %{selected_provider: "sonar", integration_form: form}} = socket assert is_struct(form, Form) end end describe "handle_event validate_snmp / save_snmp" do test "validate updates the form changeset", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event( "validate_snmp", %{"organization" => %{"snmp_community" => "secret"}}, socket ) assert %{assigns: %{form: form}} = socket assert is_struct(form, Form) end test "save_snmp advances to :site on success", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event( "save_snmp", %{"organization" => %{"snmp_community" => "ok"}}, socket ) assert %{assigns: %{step: :site}} = socket end test "save_snmp returns same step on validation failure", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event( "save_snmp", %{"organization" => %{"name" => ""}}, socket ) assert %{assigns: %{step: step}} = socket assert step in [:snmp, :site] end end describe "handle_event validate_site / save_site" do test "validate updates the site form", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event( "validate_site", %{"site" => %{"name" => "S"}}, socket ) assert %{assigns: %{site_form: site_form}} = socket assert is_struct(site_form, Form) end test "save_site advances to :billing on success", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event( "save_site", %{"site" => %{"name" => "Main Site"}}, socket ) assert %{assigns: %{step: :billing}} = socket end test "save_site stays on :site when validation fails", %{user: user, organization: organization} do socket = build_socket(user, organization) assert {:noreply, socket} = OnboardingLive.handle_event( "save_site", %{"site" => %{"name" => ""}}, socket ) assert %{assigns: %{site_form: site_form}} = socket assert is_struct(site_form, Form) end end describe "handle_event validate_integration / save_integration" do test "validate updates the integration form", %{user: user, organization: organization} do socket = user |> build_socket(organization) |> put_in([Access.key(:assigns), :selected_provider], "sonar") |> put_in([Access.key(:assigns), :step], :billing) assert {:noreply, socket} = OnboardingLive.handle_event( "validate_integration", %{"integration" => %{"instance_url" => "https://x", "api_token" => "k"}}, socket ) assert %{assigns: %{integration_form: integration_form}} = socket assert is_struct(integration_form, Form) end test "save_integration advances to :agent and loads token", %{user: user, organization: organization} do socket = user |> build_socket(organization) |> put_in([Access.key(:assigns), :selected_provider], "splynx") |> put_in([Access.key(:assigns), :step], :billing) assert {:noreply, socket} = OnboardingLive.handle_event( "save_integration", %{ "integration" => %{ "instance_url" => "https://splynx.example.com", "api_key" => "k", "api_secret" => "s" } }, socket ) assert %{assigns: %{step: :agent, agent_token: agent_token}} = socket assert agent_token end end describe "handle_event finish + skip-to-done + go_to_step :agent" do test "finish/3 marks onboarding completed and pushes to dashboard", %{user: user, organization: organization} do socket = user |> build_socket(organization) |> put_in([Access.key(:assigns), :step], :agent) assert {:noreply, socket_after} = OnboardingLive.handle_event("finish", %{}, socket) # complete_onboarding pushes navigate; the redirect is captured in flash/redirected. assert socket_after.redirected end test "skip from :agent triggers complete_onboarding", %{user: user, organization: organization} do socket = user |> build_socket(organization) |> put_in([Access.key(:assigns), :step], :agent) assert {:noreply, socket_after} = OnboardingLive.handle_event("skip", %{}, socket) assert socket_after.redirected end test "go_to_step :agent loads the agent token", %{user: user, organization: organization} do socket = user |> build_socket(organization) |> put_in([Access.key(:assigns), :step], :site) assert {:noreply, socket_after} = OnboardingLive.handle_event("go_to_step", %{"step" => "agent"}, socket) assert %{assigns: %{step: :agent}} = socket_after assert %{assigns: assigns_after} = socket_after assert Map.has_key?(assigns_after, :agent_token) end end describe "next_step / step_index" do test "step ordering" do assert OnboardingLive.next_step(:snmp) == :site assert OnboardingLive.next_step(:site) == :billing assert OnboardingLive.next_step(:billing) == :agent assert OnboardingLive.next_step(:agent) == :done assert OnboardingLive.step_index(:snmp) == 0 assert OnboardingLive.step_index(:agent) == 3 end end describe "normalize_integration_params/2" do test "sonar shape" do params = %{"instance_url" => " https://x.com ", "api_token" => "tok"} assert %{credentials: creds} = OnboardingLive.normalize_integration_params(params, "sonar") assert creds["instance_url"] == "https://x.com" assert creds["api_token"] == "tok" end test "splynx shape" do params = %{"instance_url" => "u", "api_key" => "k", "api_secret" => "s"} assert %{credentials: creds} = OnboardingLive.normalize_integration_params(params, "splynx") assert creds["api_secret"] == "s" end test "fallback shape" do params = %{"api_key" => "k"} assert %{credentials: creds} = OnboardingLive.normalize_integration_params(params, "anything") assert creds["api_key"] == "k" end test "fallback handles missing api_key gracefully" do assert %{credentials: %{"api_key" => ""}} = OnboardingLive.normalize_integration_params(%{}, "fallback") end end # Helper to build a socket with required assigns for OnboardingLive defp build_socket(user, organization) do changeset = Towerops.Organizations.change_organization(organization) site_changeset = Towerops.Sites.change_site(%Towerops.Sites.Site{}, %{}) %Socket{ assigns: %{ __changed__: %{}, current_scope: %{user: user, organization: organization}, flash: %{}, step: :snmp, steps: [:snmp, :site, :billing, :agent], organization: organization, form: Phoenix.Component.to_form(changeset), site_form: Phoenix.Component.to_form(site_changeset, as: "site"), billing_providers: [ %{id: "gaiia", name: "Gaiia", icon: "hero-user-group"}, %{id: "sonar", name: "Sonar", icon: "hero-currency-dollar"}, %{id: "splynx", name: "Splynx", icon: "hero-banknotes"}, %{id: "visp", name: "VISP", icon: "hero-cloud"} ], selected_provider: nil, integration_form: nil, agent_token: nil, agent_token_value: nil, page_title: "Setup" }, endpoint: ToweropsWeb.Endpoint } end end