- Add active_page attribute to authenticated layout - Update nav_link component to show active state with border and text styling - Update all LiveView templates to pass active_page parameter - Fix datetime truncation issues in tests and schemas for :utc_datetime fields - Fix dashboard test assertions for equipment status text
394 lines
14 KiB
Elixir
394 lines
14 KiB
Elixir
defmodule Towerops.OrganizationsTest do
|
|
use Towerops.DataCase
|
|
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Towerops.Organizations
|
|
|
|
describe "organizations" do
|
|
setup do
|
|
user = user_fixture()
|
|
%{user: user}
|
|
end
|
|
|
|
test "list_user_organizations/1 returns all organizations for a user", %{user: user} do
|
|
{:ok, org1} = Organizations.create_organization(%{name: "Org 1"}, user.id)
|
|
{:ok, org2} = Organizations.create_organization(%{name: "Org 2"}, user.id)
|
|
|
|
orgs = Organizations.list_user_organizations(user.id)
|
|
assert length(orgs) == 2
|
|
org_ids = Enum.map(orgs, & &1.id)
|
|
assert org1.id in org_ids
|
|
assert org2.id in org_ids
|
|
end
|
|
|
|
test "list_user_organizations/1 returns empty list for user with no orgs" do
|
|
user = user_fixture()
|
|
assert Organizations.list_user_organizations(user.id) == []
|
|
end
|
|
|
|
test "get_organization!/1 returns the organization with given id", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
assert Organizations.get_organization!(organization.id).id == organization.id
|
|
end
|
|
|
|
test "get_organization_by_slug!/1 returns the organization with given slug", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
assert Organizations.get_organization_by_slug!(organization.slug).id == organization.id
|
|
end
|
|
|
|
test "create_organization/2 with valid data creates organization", %{user: user} do
|
|
valid_attrs = %{name: "New Organization"}
|
|
|
|
assert {:ok, organization} = Organizations.create_organization(valid_attrs, user.id)
|
|
assert organization.name == "New Organization"
|
|
assert organization.slug
|
|
end
|
|
|
|
test "create_organization/2 creates owner membership", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
membership = Organizations.get_membership(organization.id, user.id)
|
|
assert membership.role == :owner
|
|
end
|
|
|
|
test "create_organization/2 with invalid data returns error changeset" do
|
|
user = user_fixture()
|
|
assert {:error, changeset} = Organizations.create_organization(%{name: nil}, user.id)
|
|
assert "can't be blank" in errors_on(changeset).name
|
|
end
|
|
|
|
test "update_organization/2 with valid data updates the organization", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
update_attrs = %{name: "Updated Org"}
|
|
assert {:ok, updated} = Organizations.update_organization(organization, update_attrs)
|
|
assert updated.name == "Updated Org"
|
|
end
|
|
|
|
test "update_organization/2 with invalid data returns error changeset", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
assert {:error, changeset} = Organizations.update_organization(organization, %{name: nil})
|
|
assert "can't be blank" in errors_on(changeset).name
|
|
end
|
|
|
|
test "delete_organization/1 deletes the organization", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
assert {:ok, _} = Organizations.delete_organization(organization)
|
|
assert_raise Ecto.NoResultsError, fn -> Organizations.get_organization!(organization.id) end
|
|
end
|
|
|
|
test "change_organization/1 returns an organization changeset", %{user: user} do
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
assert %Ecto.Changeset{} = Organizations.change_organization(organization)
|
|
end
|
|
end
|
|
|
|
describe "memberships" do
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
%{user: user, organization: organization}
|
|
end
|
|
|
|
test "get_membership/2 returns the membership", %{user: user, organization: organization} do
|
|
membership = Organizations.get_membership(organization.id, user.id)
|
|
assert membership.user_id == user.id
|
|
assert membership.organization_id == organization.id
|
|
end
|
|
|
|
test "get_membership/2 returns nil when membership doesn't exist", %{organization: organization} do
|
|
other_user = user_fixture()
|
|
assert Organizations.get_membership(organization.id, other_user.id) == nil
|
|
end
|
|
|
|
test "get_membership!/2 returns the membership", %{user: user, organization: organization} do
|
|
membership = Organizations.get_membership!(organization.id, user.id)
|
|
assert membership.user_id == user.id
|
|
end
|
|
|
|
test "get_membership!/2 raises when membership doesn't exist", %{organization: organization} do
|
|
other_user = user_fixture()
|
|
|
|
assert_raise Ecto.NoResultsError, fn ->
|
|
Organizations.get_membership!(organization.id, other_user.id)
|
|
end
|
|
end
|
|
|
|
test "list_organization_memberships/1 returns all memberships", %{
|
|
user: user,
|
|
organization: organization
|
|
} do
|
|
# Add another member
|
|
other_user = user_fixture()
|
|
|
|
{:ok, _membership} =
|
|
Organizations.create_membership(%{
|
|
organization_id: organization.id,
|
|
user_id: other_user.id,
|
|
role: :member
|
|
})
|
|
|
|
memberships = Organizations.list_organization_memberships(organization.id)
|
|
assert length(memberships) == 2
|
|
user_ids = Enum.map(memberships, & &1.user_id)
|
|
assert user.id in user_ids
|
|
assert other_user.id in user_ids
|
|
end
|
|
|
|
test "list_organization_notification_recipients/1 returns owners and admins", %{
|
|
organization: organization
|
|
} do
|
|
admin = user_fixture()
|
|
member = user_fixture()
|
|
|
|
{:ok, _admin_membership} =
|
|
Organizations.create_membership(%{
|
|
organization_id: organization.id,
|
|
user_id: admin.id,
|
|
role: :admin
|
|
})
|
|
|
|
{:ok, _member_membership} =
|
|
Organizations.create_membership(%{
|
|
organization_id: organization.id,
|
|
user_id: member.id,
|
|
role: :member
|
|
})
|
|
|
|
recipients = Organizations.list_organization_notification_recipients(organization.id)
|
|
recipient_ids = Enum.map(recipients, & &1.id)
|
|
|
|
# Owner and admin should be in recipients
|
|
assert admin.id in recipient_ids
|
|
# Member should NOT be in recipients
|
|
refute member.id in recipient_ids
|
|
end
|
|
|
|
test "create_membership/1 with valid data creates membership", %{organization: organization} do
|
|
new_user = user_fixture()
|
|
|
|
valid_attrs = %{
|
|
organization_id: organization.id,
|
|
user_id: new_user.id,
|
|
role: :member
|
|
}
|
|
|
|
assert {:ok, membership} = Organizations.create_membership(valid_attrs)
|
|
assert membership.role == :member
|
|
assert membership.user_id == new_user.id
|
|
end
|
|
|
|
test "create_membership/1 with invalid data returns error changeset" do
|
|
assert {:error, changeset} = Organizations.create_membership(%{})
|
|
assert "can't be blank" in errors_on(changeset).organization_id
|
|
end
|
|
|
|
test "update_membership/2 changes user role", %{user: user, organization: organization} do
|
|
membership = Organizations.get_membership!(organization.id, user.id)
|
|
|
|
assert {:ok, updated} = Organizations.update_membership(membership, %{role: :admin})
|
|
assert updated.role == :admin
|
|
end
|
|
|
|
test "delete_membership/1 deletes the membership", %{organization: organization} do
|
|
new_user = user_fixture()
|
|
|
|
{:ok, membership} =
|
|
Organizations.create_membership(%{
|
|
organization_id: organization.id,
|
|
user_id: new_user.id,
|
|
role: :member
|
|
})
|
|
|
|
assert {:ok, _} = Organizations.delete_membership(membership)
|
|
assert Organizations.get_membership(organization.id, new_user.id) == nil
|
|
end
|
|
end
|
|
|
|
describe "invitations" do
|
|
setup do
|
|
owner = user_fixture()
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, owner.id)
|
|
%{owner: owner, organization: organization}
|
|
end
|
|
|
|
test "list_pending_invitations/1 returns pending invitations", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
{:ok, invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "invited@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: "test-token-#{System.unique_integer()}",
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second)
|
|
})
|
|
|
|
invitations = Organizations.list_pending_invitations(organization.id)
|
|
assert length(invitations) == 1
|
|
assert hd(invitations).id == invitation.id
|
|
end
|
|
|
|
test "list_pending_invitations/1 excludes accepted invitations", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
{:ok, invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "invited@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: "test-token-#{System.unique_integer()}",
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second),
|
|
accepted_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
})
|
|
|
|
invitations = Organizations.list_pending_invitations(organization.id)
|
|
refute Enum.any?(invitations, &(&1.id == invitation.id))
|
|
end
|
|
|
|
test "list_pending_invitations/1 excludes expired invitations", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
{:ok, invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "invited@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: "test-token-#{System.unique_integer()}",
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), -1, :day), :second)
|
|
})
|
|
|
|
invitations = Organizations.list_pending_invitations(organization.id)
|
|
refute Enum.any?(invitations, &(&1.id == invitation.id))
|
|
end
|
|
|
|
test "create_invitation/1 with valid data creates invitation", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
valid_attrs = %{
|
|
organization_id: organization.id,
|
|
email: "test@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: "unique-token-#{System.unique_integer()}",
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second)
|
|
}
|
|
|
|
assert {:ok, invitation} = Organizations.create_invitation(valid_attrs)
|
|
assert invitation.email == "test@example.com"
|
|
assert invitation.role == :member
|
|
end
|
|
|
|
test "get_invitation_by_token/1 returns invitation", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
token = "test-token-#{System.unique_integer()}"
|
|
|
|
{:ok, _invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "test@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: token,
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second)
|
|
})
|
|
|
|
invitation = Organizations.get_invitation_by_token(token)
|
|
assert invitation.token == token
|
|
end
|
|
|
|
test "get_invitation_by_token/1 returns nil for accepted invitation", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
token = "test-token-#{System.unique_integer()}"
|
|
|
|
{:ok, _invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "test@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: token,
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second),
|
|
accepted_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
})
|
|
|
|
assert Organizations.get_invitation_by_token(token) == nil
|
|
end
|
|
|
|
test "accept_invitation/2 creates membership and marks invitation accepted", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
token = "test-token-#{System.unique_integer()}"
|
|
|
|
{:ok, invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "test@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: token,
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second)
|
|
})
|
|
|
|
new_user = user_fixture()
|
|
assert {:ok, membership} = Organizations.accept_invitation(invitation, new_user.id)
|
|
assert membership.user_id == new_user.id
|
|
assert membership.role == :member
|
|
|
|
# Invitation should no longer be retrievable
|
|
assert Organizations.get_invitation_by_token(token) == nil
|
|
end
|
|
|
|
test "delete_invitation/1 deletes the invitation", %{
|
|
owner: owner,
|
|
organization: organization
|
|
} do
|
|
{:ok, invitation} =
|
|
Organizations.create_invitation(%{
|
|
organization_id: organization.id,
|
|
email: "test@example.com",
|
|
role: :member,
|
|
invited_by_id: owner.id,
|
|
token: "test-token-#{System.unique_integer()}",
|
|
expires_at: DateTime.truncate(DateTime.add(DateTime.utc_now(), 7, :day), :second)
|
|
})
|
|
|
|
assert {:ok, _} = Organizations.delete_invitation(invitation)
|
|
assert Organizations.list_pending_invitations(organization.id) == []
|
|
end
|
|
end
|
|
|
|
describe "authorization" do
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
membership = Organizations.get_membership!(organization.id, user.id)
|
|
%{membership: membership}
|
|
end
|
|
|
|
test "can?/3 delegates to Policy module", %{membership: membership} do
|
|
# Owner can delete organization
|
|
assert Organizations.can?(membership, :delete, :organization)
|
|
|
|
# Change to member role
|
|
{:ok, member_membership} = Organizations.update_membership(membership, %{role: :member})
|
|
|
|
# Member cannot delete organization
|
|
refute Organizations.can?(member_membership, :delete, :organization)
|
|
end
|
|
end
|
|
end
|