defmodule ToweropsWeb.Api.V1.SitesControllerTest do use ToweropsWeb.ConnCase, async: true import Towerops.AccountsFixtures import Towerops.OrganizationsFixtures alias Towerops.ApiTokens alias Towerops.Sites setup do user = user_fixture() organization = organization_fixture(user.id) {:ok, {_token, raw_token}} = ApiTokens.create_api_token(%{ organization_id: organization.id, user_id: user.id, name: "Test Token" }) conn = build_conn() |> put_req_header("authorization", "Bearer #{raw_token}") |> put_req_header("accept", "application/json") %{ conn: conn, user: user, organization: organization, raw_token: raw_token } end describe "index/2" do test "lists all sites for authenticated organization", %{ conn: conn, organization: organization } do {:ok, site1} = Sites.create_site(%{name: "Site Alpha", organization_id: organization.id}) {:ok, site2} = Sites.create_site(%{name: "Site Beta", organization_id: organization.id}) conn = get(conn, ~p"/api/v1/sites") assert %{"sites" => sites} = json_response(conn, 200) assert length(sites) == 2 site_ids = Enum.map(sites, & &1["id"]) assert site1.id in site_ids assert site2.id in site_ids site_json = List.first(sites) assert Map.has_key?(site_json, "id") assert Map.has_key?(site_json, "name") assert Map.has_key?(site_json, "location") assert Map.has_key?(site_json, "snmp_community") assert Map.has_key?(site_json, "inserted_at") end test "returns empty list when organization has no sites", %{conn: conn} do conn = get(conn, ~p"/api/v1/sites") assert %{"sites" => []} = json_response(conn, 200) end test "does not return sites from other organizations", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, _other_site} = Sites.create_site(%{name: "Other Site", organization_id: other_org.id}) conn = get(conn, ~p"/api/v1/sites") assert %{"sites" => sites} = json_response(conn, 200) assert Enum.empty?(sites) end end describe "create/2" do test "creates site with valid attributes", %{conn: conn, organization: organization} do site_params = %{ "site" => %{ "name" => "Main Office", "location" => "New York, NY", "snmp_community" => "public" } } conn = post(conn, ~p"/api/v1/sites", site_params) assert %{ "id" => id, "name" => "Main Office", "location" => "New York, NY", "snmp_community" => "public" } = json_response(conn, 201) assert is_binary(id) and byte_size(id) > 0 # Verify the site was created in the correct organization site = Sites.get_site!(id) assert site.organization_id == organization.id end test "creates site with minimal attributes", %{conn: conn} do site_params = %{ "site" => %{ "name" => "Minimal Site" } } conn = post(conn, ~p"/api/v1/sites", site_params) assert %{ "id" => _id, "name" => "Minimal Site", "location" => nil, "snmp_community" => nil } = json_response(conn, 201) end test "returns 422 with invalid site params", %{conn: conn} do site_params = %{ "site" => %{ "name" => "" } } conn = post(conn, ~p"/api/v1/sites", site_params) assert %{"errors" => errors} = json_response(conn, 422) assert Map.has_key?(errors, "name") end test "returns 400 when site parameter is missing", %{conn: conn} do conn = post(conn, ~p"/api/v1/sites", %{}) assert %{"error" => "Missing 'site' parameter"} = json_response(conn, 400) end end describe "show/2" do test "returns site when it belongs to organization", %{ conn: conn, organization: organization } do {:ok, site} = Sites.create_site(%{ name: "Test Site", location: "Boston, MA", snmp_community: "private", organization_id: organization.id }) conn = get(conn, ~p"/api/v1/sites/#{site.id}") assert %{ "id" => id, "name" => "Test Site", "location" => "Boston, MA", "snmp_community" => "private" } = json_response(conn, 200) assert id == site.id end test "returns 404 when site does not exist", %{conn: conn} do conn = get(conn, ~p"/api/v1/sites/#{Ecto.UUID.generate()}") assert %{"error" => "Site not found"} = json_response(conn, 404) end test "returns 404 when site belongs to different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, other_site} = Sites.create_site(%{name: "Other Site", organization_id: other_org.id}) conn = get(conn, ~p"/api/v1/sites/#{other_site.id}") assert %{"error" => "Site not found"} = json_response(conn, 404) end end describe "update/2" do test "updates site with valid attributes", %{conn: conn, organization: organization} do {:ok, site} = Sites.create_site(%{ name: "Original Name", location: "Chicago, IL", organization_id: organization.id }) update_params = %{ "site" => %{ "name" => "Updated Name", "location" => "Boston, MA" } } conn = patch(conn, ~p"/api/v1/sites/#{site.id}", update_params) assert %{ "id" => id, "name" => "Updated Name", "location" => "Boston, MA" } = json_response(conn, 200) assert id == site.id end test "returns 404 when site does not exist", %{conn: conn} do update_params = %{ "site" => %{ "name" => "Updated Name" } } conn = patch(conn, ~p"/api/v1/sites/#{Ecto.UUID.generate()}", update_params) assert %{"error" => "Site not found"} = json_response(conn, 404) end test "returns 404 when site belongs to different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, other_site} = Sites.create_site(%{name: "Other Site", organization_id: other_org.id}) update_params = %{ "site" => %{ "name" => "Hacked Name" } } conn = patch(conn, ~p"/api/v1/sites/#{other_site.id}", update_params) assert %{"error" => "Site not found"} = json_response(conn, 404) end test "returns 422 with invalid update params", %{conn: conn, organization: organization} do {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id}) update_params = %{ "site" => %{ "name" => "" } } conn = patch(conn, ~p"/api/v1/sites/#{site.id}", update_params) assert %{"errors" => errors} = json_response(conn, 422) assert Map.has_key?(errors, "name") end test "returns 400 when site parameter is missing", %{ conn: conn, organization: organization } do {:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id}) conn = patch(conn, ~p"/api/v1/sites/#{site.id}", %{}) assert %{"error" => "Missing 'site' parameter"} = json_response(conn, 400) end end describe "delete/2" do test "deletes site successfully", %{conn: conn, organization: organization} do {:ok, site} = Sites.create_site(%{name: "Doomed Site", organization_id: organization.id}) conn = delete(conn, ~p"/api/v1/sites/#{site.id}") assert %{"success" => true} = json_response(conn, 200) # Verify site is deleted assert_raise Ecto.NoResultsError, fn -> Sites.get_site!(site.id) end end test "returns 404 when site does not exist", %{conn: conn} do conn = delete(conn, ~p"/api/v1/sites/#{Ecto.UUID.generate()}") assert %{"error" => "Site not found"} = json_response(conn, 404) end test "returns 404 when site belongs to different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, other_site} = Sites.create_site(%{name: "Other Site", organization_id: other_org.id}) conn = delete(conn, ~p"/api/v1/sites/#{other_site.id}") assert %{"error" => "Site not found"} = json_response(conn, 404) end end describe "authentication" do test "returns 401 without authorization header" do conn = build_conn() |> put_req_header("accept", "application/json") |> get(~p"/api/v1/sites") assert %{"error" => _message} = json_response(conn, 401) end test "returns 401 with invalid token" do conn = build_conn() |> put_req_header("authorization", "Bearer towerops_invalid_token") |> put_req_header("accept", "application/json") |> get(~p"/api/v1/sites") assert %{"error" => _message} = json_response(conn, 401) end end end