defmodule ToweropsWeb.Org.IntegrationsLiveTest do use ToweropsWeb.ConnCase import Phoenix.LiveViewTest import Towerops.AccountsFixtures import Towerops.OrganizationsFixtures alias Towerops.Preseem.Client, as: PreseemClient setup do user = user_fixture(enable_totp: true) organization = organization_fixture(user.id) %{user: user, organization: organization} end describe "gaiia webhook setup" do test "saving a new gaiia integration auto-generates a webhook secret", %{ conn: conn, user: user, organization: org } do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-gaiia") |> render_click() view |> form("#gaiia-form", %{integration: %{api_key: "test-gaiia-key"}}) |> render_submit() assert {:ok, integration} = Towerops.Integrations.get_integration(org.id, "gaiia") assert integration.credentials["api_key"] == "test-gaiia-key" assert integration.credentials["webhook_secret"] assert String.length(integration.credentials["webhook_secret"]) == 64 end test "saving an existing gaiia integration preserves the webhook secret", %{ conn: conn, user: user, organization: org } do # Create integration with a known webhook secret {:ok, _integration} = Towerops.Integrations.create_integration(org.id, %{ provider: "gaiia", enabled: true, credentials: %{"api_key" => "old-key", "webhook_secret" => "existing-secret-abc"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-gaiia") |> render_click() view |> form("#gaiia-form", %{integration: %{api_key: "new-gaiia-key"}}) |> render_submit() assert {:ok, integration} = Towerops.Integrations.get_integration(org.id, "gaiia") assert integration.credentials["api_key"] == "old-key" assert integration.credentials["webhook_secret"] == "existing-secret-abc" end test "displays webhook URL when gaiia integration is configured", %{ conn: conn, user: user, organization: org } do {:ok, _integration} = Towerops.Integrations.create_integration(org.id, %{ provider: "gaiia", enabled: true, credentials: %{"api_key" => "key", "webhook_secret" => "secret123"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") html = view |> element("#configure-gaiia") |> render_click() assert html =~ "/api/v1/webhooks/gaiia/#{org.id}" end test "displays webhook secret when gaiia integration is configured", %{ conn: conn, user: user, organization: org } do {:ok, _integration} = Towerops.Integrations.create_integration(org.id, %{ provider: "gaiia", enabled: true, credentials: %{"api_key" => "key", "webhook_secret" => "secret123"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") html = view |> element("#configure-gaiia") |> render_click() assert html =~ "secret123" end test "regenerate webhook secret changes the stored secret", %{ conn: conn, user: user, organization: org } do {:ok, _integration} = Towerops.Integrations.create_integration(org.id, %{ provider: "gaiia", enabled: true, credentials: %{"api_key" => "key", "webhook_secret" => "old-secret"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-gaiia") |> render_click() view |> element("#regenerate-webhook-secret") |> render_click() assert {:ok, integration} = Towerops.Integrations.get_integration(org.id, "gaiia") assert integration.credentials["webhook_secret"] != "old-secret" assert String.length(integration.credentials["webhook_secret"]) == 64 assert integration.credentials["api_key"] == "key" end test "shows setup instructions when gaiia is being configured", %{ conn: conn, user: user, organization: org } do {:ok, _integration} = Towerops.Integrations.create_integration(org.id, %{ provider: "gaiia", enabled: true, credentials: %{"api_key" => "key", "webhook_secret" => "secret"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") html = view |> element("#configure-gaiia") |> render_click() assert html =~ "Webhook Configuration" assert html =~ "Webhook URL" assert html =~ "Webhook Secret" assert html =~ "Gaiia admin panel" end end describe "index" do test "renders integrations page", %{conn: conn, user: user, organization: org} do {:ok, _view, html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") assert html =~ "Integrations" assert html =~ "Preseem" end test "shows preseem description", %{conn: conn, user: user, organization: org} do {:ok, _view, html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") assert html =~ "QoE monitoring" end test "can open preseem configuration", %{conn: conn, user: user, organization: org} do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") html = view |> element("#configure-preseem") |> render_click() assert html =~ "API Key" end test "can save preseem credentials", %{conn: conn, user: user, organization: org} do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-preseem") |> render_click() view |> form("#preseem-form", %{integration: %{api_key: "test-key-123"}}) |> render_submit() assert {:ok, integration} = Towerops.Integrations.get_integration(org.id, "preseem") assert integration.credentials["api_key"] == "test-key-123" end test "can test preseem connection successfully", %{conn: conn, user: user, organization: org} do Req.Test.stub(PreseemClient, fn conn -> Req.Test.json(conn, %{ "data" => [%{"id" => 1, "name" => "Test AP"}], "paginator" => %{"page" => 1, "page_count" => 1, "limit" => 100, "total_count" => 1} }) end) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-preseem") |> render_click() view |> form("#preseem-form", %{integration: %{api_key: "test-key"}}) |> render_change() html = view |> element("#test-connection") |> render_click() assert html =~ "Connection successful" end test "close_config clears the configuring panel", %{conn: conn, user: user, organization: org} do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") # Open preseem config first html = view |> element("#configure-preseem") |> render_click() assert html =~ "API Key" # Trigger close via the LV process (the close button is rendered inside the panel) html = render_hook(view, "close_config", %{}) refute html =~ ~s(name="integration[api_key]") end test "validate event updates the form changeset state", %{ conn: conn, user: user, organization: org } do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-preseem") |> render_click() html = view |> form("#preseem-form", %{integration: %{api_key: "validated-key"}}) |> render_change() # The form re-renders without crashing — verify the configure panel is still active assert html =~ "API Key" end test "test_connection without filling required fields shows error", %{ conn: conn, user: user, organization: org } do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-preseem") |> render_click() html = view |> element("#test-connection") |> render_click() assert html =~ "Please fill in all required fields first" end test "toggle_enabled flips an existing integration's enabled flag", %{ conn: conn, user: user, organization: org } do {:ok, integration} = Towerops.Integrations.create_integration(org.id, %{ provider: "preseem", enabled: true, credentials: %{"api_key" => "key"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") render_hook(view, "toggle_enabled", %{"provider" => "preseem"}) {:ok, reloaded} = Towerops.Integrations.get_integration(org.id, "preseem") refute reloaded.enabled _ = integration end test "toggle_enabled is a no-op for unknown integration", %{ conn: conn, user: user, organization: org } do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") # Should not crash even though no integration exists for "preseem" html = render_hook(view, "toggle_enabled", %{"provider" => "preseem"}) assert is_binary(html) end test "saving exclusive billing integration when another active blocks save", %{ conn: conn, user: user, organization: org } do # Activate gaiia first {:ok, _} = Towerops.Integrations.create_integration(org.id, %{ provider: "gaiia", enabled: true, credentials: %{"api_key" => "k", "webhook_secret" => "s"} }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") # When active billing exists, the picker for sonar isn't rendered, so # drive the events directly through the LV. render_hook(view, "configure", %{"provider" => "sonar"}) html = render_hook(view, "save", %{ "integration" => %{"instance_url" => "https://x", "api_token" => "t"} }) assert html =~ "Only one" end test "regenerate_webhook_secret is a no-op when no gaiia integration exists", %{ conn: conn, user: user, organization: org } do {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") # No gaiia integration; event should be a no-op (and not crash). html = render_hook(view, "regenerate_webhook_secret", %{}) assert is_binary(html) end test "renders provider category descriptions", %{conn: conn, user: user, organization: org} do {:ok, _view, html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") assert html =~ "Billing" assert html =~ "Incident" assert html =~ "Infrastructure" end test "clears sync status when credentials change", %{conn: conn, user: user, organization: org} do {:ok, _} = Towerops.Integrations.create_integration(org.id, %{ provider: "preseem", enabled: true, credentials: %{"api_key" => "old-key"}, last_sync_status: "failed", last_sync_message: "some raw error from previous sync" }) {:ok, view, _html} = conn |> log_in_user(user) |> live(~p"/orgs/#{org.slug}/settings/integrations") view |> element("#configure-preseem") |> render_click() view |> form("#preseem-form", %{integration: %{api_key: "new-key-456"}}) |> render_submit() assert {:ok, integration} = Towerops.Integrations.get_integration(org.id, "preseem") assert integration.credentials["api_key"] == "new-key-456" assert integration.last_sync_status == "never" assert is_nil(integration.last_sync_message) assert is_nil(integration.last_synced_at) end end end