1227 lines
45 KiB
Python
1227 lines
45 KiB
Python
import pytest
|
|
import uuid
|
|
import os
|
|
from unittest.mock import patch
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import orm
|
|
import stripe
|
|
|
|
from agentops.opsboard.models import (
|
|
OrgModel,
|
|
UserOrgModel,
|
|
UserModel,
|
|
OrgInviteModel,
|
|
OrgRoles,
|
|
PremStatus,
|
|
)
|
|
from agentops.opsboard.views.orgs import (
|
|
get_user_orgs,
|
|
get_org,
|
|
create_org,
|
|
update_org,
|
|
invite_to_org,
|
|
get_org_invites,
|
|
accept_org_invite,
|
|
remove_from_org,
|
|
change_member_role,
|
|
create_checkout_session,
|
|
CreateCheckoutSessionBody,
|
|
)
|
|
from agentops.opsboard.schemas import (
|
|
OrgCreateSchema,
|
|
OrgUpdateSchema,
|
|
OrgInviteSchema,
|
|
OrgMemberRemoveSchema,
|
|
OrgMemberRoleSchema,
|
|
)
|
|
|
|
|
|
# Mock Stripe environment variables for testing to avoid warnings
|
|
@pytest.fixture(autouse=True)
|
|
def mock_stripe_env_vars():
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{
|
|
'STRIPE_SECRET_KEY': 'sk_test_mock_key',
|
|
'STRIPE_SUBSCRIPTION_PRICE_ID': 'price_test_subscription',
|
|
'STRIPE_TOKEN_PRICE_ID': 'price_test_token',
|
|
'STRIPE_SPAN_PRICE_ID': 'price_test_span',
|
|
},
|
|
),
|
|
patch('stripe.Account.retrieve'),
|
|
):
|
|
yield
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_orgs(mock_request, orm_session, test_user_org_owner):
|
|
"""Test getting all organizations for a user."""
|
|
# Call the endpoint function
|
|
result = get_user_orgs(request=mock_request, orm=orm_session)
|
|
|
|
# Verify that it returns a list and contains our test org
|
|
assert isinstance(result, list)
|
|
assert len(result) > 0
|
|
|
|
# Check that our test org is in the results
|
|
org_ids = [o.id for o in result]
|
|
assert str(test_user_org_owner.id) in org_ids
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_org(mock_request, orm_session, test_user_org_member):
|
|
"""Test getting a specific organization by ID."""
|
|
# Call the endpoint function
|
|
result = get_org(request=mock_request, org_id=str(test_user_org_member.id), orm=orm_session)
|
|
|
|
# Verify the org data matches
|
|
assert result.id == str(test_user_org_member.id)
|
|
assert result.name == test_user_org_member.name
|
|
assert len(result.users) == 1
|
|
assert result.users[0].user_id == "00000000-0000-0000-0000-000000000000"
|
|
assert result.users[0].role == OrgRoles.developer.value
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_org_not_found(mock_request, orm_session):
|
|
"""Test getting an organization that doesn't exist."""
|
|
non_existent_id = str(uuid.uuid4())
|
|
|
|
# Expect an HTTP 404 exception
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
get_org(request=mock_request, org_id=non_existent_id, orm=orm_session)
|
|
|
|
assert excinfo.value.status_code == 404
|
|
assert excinfo.value.detail == "Organization not found"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_org(mock_request, orm_session, test_user):
|
|
"""Test creating a new organization."""
|
|
# Create the request body
|
|
body = OrgCreateSchema(name="New Test Organization")
|
|
|
|
# Call the endpoint function
|
|
result = create_org(request=mock_request, orm=orm_session, body=body)
|
|
|
|
# Verify the organization was created with the right data
|
|
assert result.name == body.name
|
|
assert result.id is not None
|
|
|
|
# Verify we can find it in the database
|
|
created_org = orm_session.query(OrgModel).filter_by(id=uuid.UUID(result.id)).one()
|
|
assert created_org.name == body.name
|
|
|
|
# Verify that the user is an owner
|
|
user_org = orm_session.query(UserOrgModel).filter_by(user_id=test_user.id, org_id=created_org.id).one()
|
|
assert user_org.role == OrgRoles.owner
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_org(mock_request, orm_session, test_user):
|
|
"""Test updating an organization's name."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Original Org Name",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit() # Commit to ensure it's persisted
|
|
|
|
# Create the update body
|
|
body = OrgUpdateSchema(name="Updated Organization Name")
|
|
|
|
# Fetch org again with relationships to ensure we have the latest
|
|
org = orm_session.query(OrgModel).options(orm.selectinload(OrgModel.users)).filter_by(id=org.id).one()
|
|
|
|
# Verify the user is an owner of this org
|
|
membership = org.get_user_membership(user_id)
|
|
assert membership is not None, "User membership not found"
|
|
assert membership.role == OrgRoles.owner, "User is not an owner"
|
|
|
|
# Now call the update endpoint
|
|
result = update_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the updates were applied
|
|
assert result.name == body.name
|
|
|
|
# Refresh the session to get the latest data
|
|
orm_session.expire_all() # Clear cached objects
|
|
|
|
# Verify the database was updated
|
|
updated_org = orm_session.query(OrgModel).filter_by(id=org.id).one()
|
|
assert updated_org.name == body.name
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_org_not_admin(mock_request, orm_session, test_user_org_member):
|
|
"""Test updating an organization without admin permissions."""
|
|
# Change the role to developer (not admin or owner)
|
|
user_org = test_user_org_member.get_user_membership(mock_request.state.session.user_id)
|
|
user_org.role = OrgRoles.developer
|
|
orm_session.flush()
|
|
|
|
# Create the update body
|
|
body = OrgUpdateSchema(name="Updated Organization Name")
|
|
|
|
# Expect an HTTP 404 exception (since we use security by obscurity)
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
update_org(request=mock_request, org_id=str(test_user_org_member.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 404
|
|
assert excinfo.value.detail == "Organization not found"
|
|
|
|
# Restore the role
|
|
user_org.role = OrgRoles.owner
|
|
orm_session.flush()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('agentops.opsboard.views.orgs._send_invitation_email')
|
|
async def test_invite_to_org(mock_send_email, mock_request, orm_session, test_user):
|
|
"""Test inviting a user to an organization."""
|
|
# Mock the email function to not raise any exceptions
|
|
mock_send_email.return_value = None
|
|
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Invites",
|
|
prem_status=PremStatus.enterprise, # Use enterprise to avoid the member limit
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit() # Commit to ensure it's persisted
|
|
|
|
# Create the invite body
|
|
body = OrgInviteSchema(email="newuser@example.com", role=OrgRoles.developer.value)
|
|
|
|
# Fetch org again with relationships to ensure we have the latest
|
|
org = (
|
|
orm_session.query(OrgModel)
|
|
.options(orm.selectinload(OrgModel.users), orm.selectinload(OrgModel.invites))
|
|
.filter_by(id=org.id)
|
|
.one()
|
|
)
|
|
|
|
# Verify the user is an owner of this org
|
|
membership = org.get_user_membership(user_id)
|
|
assert membership is not None, "User membership not found"
|
|
assert membership.role == OrgRoles.owner, "User is not an owner"
|
|
|
|
# Now call the invite endpoint
|
|
result = invite_to_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "Invitation sent successfully"
|
|
|
|
# Verify the invite exists in the database
|
|
invite = orm_session.query(OrgInviteModel).filter_by(org_id=org.id, invitee_email=body.email).one()
|
|
assert invite.role == OrgRoles.developer
|
|
assert invite.org_name == org.name
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invite_to_org_already_member(mock_request, orm_session, test_user_org_owner):
|
|
"""Test inviting a user who is already a member."""
|
|
# Create the invite body with the same email as the existing member
|
|
body = OrgInviteSchema(email="test@example.com", role=OrgRoles.developer.value)
|
|
|
|
# Expect an HTTP 400 exception
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
invite_to_org(request=mock_request, org_id=str(test_user_org_owner.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "User is already a member of this organization"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invite_to_org_not_admin(mock_request, orm_session, test_user_org_member):
|
|
"""Test inviting a user without admin permissions."""
|
|
# Change the role to developer (not admin or owner)
|
|
user_org = test_user_org_member.get_user_membership(mock_request.state.session.user_id)
|
|
user_org.role = OrgRoles.developer
|
|
orm_session.flush()
|
|
|
|
# Create the invite body
|
|
body = OrgInviteSchema(email="newuser@example.com", role=OrgRoles.developer.value)
|
|
|
|
# Expect an HTTP 404 exception (since we use security by obscurity)
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
invite_to_org(request=mock_request, org_id=str(test_user_org_member.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 404
|
|
assert excinfo.value.detail == "Organization not found"
|
|
|
|
# Restore the role
|
|
user_org.role = OrgRoles.owner
|
|
orm_session.flush()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('agentops.opsboard.views.orgs._send_invitation_email')
|
|
async def test_invite_to_org_case_insensitive(mock_send_email, mock_request, orm_session, test_user):
|
|
"""Test that email invitations are case-insensitive."""
|
|
mock_send_email.return_value = None
|
|
|
|
org = OrgModel(
|
|
name="Test Org for Case Sensitivity",
|
|
prem_status=PremStatus.enterprise,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit()
|
|
|
|
body = OrgInviteSchema(email="newuser@example.com", role=OrgRoles.developer.value)
|
|
result = invite_to_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
assert result.success is True
|
|
|
|
body_upper = OrgInviteSchema(email="NEWUSER@example.com", role=OrgRoles.developer.value)
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
invite_to_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body_upper)
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "User already has a pending invitation"
|
|
|
|
body_mixed = OrgInviteSchema(email="NewUser@Example.COM", role=OrgRoles.developer.value)
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
invite_to_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body_mixed)
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "User already has a pending invitation"
|
|
|
|
invite = orm_session.query(OrgInviteModel).filter_by(org_id=org.id).one()
|
|
assert invite.invitee_email == "newuser@example.com" # Should be lowercase
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invite_to_org_member_limit(mock_request, orm_session, test_user):
|
|
"""Test inviting a user when the organization has reached its member limit."""
|
|
# Create a fresh organization with free plan (which has a member limit of 1)
|
|
org = OrgModel(
|
|
name="Test Org with Member Limit",
|
|
prem_status=PremStatus.free, # Free plan has a member limit of 1
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship (this is the first and only allowed member)
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit() # Commit to ensure it's persisted
|
|
|
|
# Create the invite body for a second user (which should exceed the limit)
|
|
body = OrgInviteSchema(email="newuser@example.com", role=OrgRoles.developer.value)
|
|
|
|
# Expect an HTTP 400 exception due to member limit
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
invite_to_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "Organization has reached its member limit"
|
|
|
|
# Verify no invite was created
|
|
invites = orm_session.query(OrgInviteModel).filter_by(org_id=org.id).all()
|
|
assert len(invites) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('agentops.opsboard.views.orgs._send_invitation_email')
|
|
async def test_invite_to_org_after_upgrade(mock_send_email, mock_request, orm_session, test_user):
|
|
"""Test inviting users after upgrading from free to enterprise plan."""
|
|
# Mock the email function to not raise any exceptions
|
|
mock_send_email.return_value = None
|
|
|
|
# Create a fresh organization with free plan (which has a member limit of 1)
|
|
org = OrgModel(
|
|
name="Test Org for Plan Upgrade",
|
|
prem_status=PremStatus.free, # Free plan has a member limit of 1
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship (this is the first and only allowed member)
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit() # Commit to ensure it's persisted
|
|
|
|
# Upgrade the organization plan to enterprise (which has no member limit)
|
|
org.prem_status = PremStatus.enterprise
|
|
orm_session.commit()
|
|
|
|
# Reload the org with all relationships
|
|
org = OrgModel.get_by_id(orm_session, org.id)
|
|
|
|
# Create the invite body for a second user (which should now work)
|
|
body = OrgInviteSchema(email="newuser@example.com", role=OrgRoles.developer.value)
|
|
|
|
# This should now succeed with the enterprise plan
|
|
result = invite_to_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the invite success response
|
|
assert result.success is True
|
|
assert result.message == "Invitation sent successfully"
|
|
|
|
# Verify the invite was created in the database
|
|
invites = orm_session.query(OrgInviteModel).filter_by(org_id=org.id).all()
|
|
assert len(invites) == 1
|
|
assert invites[0].invitee_email == "newuser@example.com"
|
|
assert invites[0].role == OrgRoles.developer
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_org_invites(mock_request, orm_session, test_user):
|
|
"""Test getting all invitations for the authenticated user (as invitee)."""
|
|
# Create a fresh org for this test
|
|
org = OrgModel(name="Test Org for Invites")
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
invite = OrgInviteModel(
|
|
inviter_id=test_user.id,
|
|
invitee_email="test@example.com",
|
|
org_id=org.id,
|
|
role=OrgRoles.developer,
|
|
org_name=org.name,
|
|
)
|
|
orm_session.add(invite)
|
|
orm_session.commit() # Commit to ensure it's persisted
|
|
|
|
# Call the endpoint function
|
|
result = get_org_invites(request=mock_request, orm=orm_session)
|
|
|
|
# Verify the result contains our invite
|
|
assert isinstance(result, list)
|
|
assert len(result) > 0
|
|
|
|
# Find our invite in the results (might be other test artifacts)
|
|
our_invite = None
|
|
for invite_response in result:
|
|
if invite_response.org_id == str(org.id) and invite_response.invitee_email == "test@example.com":
|
|
our_invite = invite_response
|
|
break
|
|
|
|
assert our_invite is not None
|
|
assert our_invite.inviter_id == str(test_user.id)
|
|
assert our_invite.org_id == str(org.id)
|
|
assert our_invite.role == OrgRoles.developer.value
|
|
assert our_invite.invitee_email == "test@example.com"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_accept_org_invite(mock_request, orm_session, test_user):
|
|
"""Test accepting an invitation to join an organization."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Invite",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# The current user ID from the mock request
|
|
user_id = mock_request.state.session.user_id
|
|
|
|
invite = OrgInviteModel(
|
|
inviter_id=test_user.id,
|
|
invitee_email="test@example.com",
|
|
org_id=org.id,
|
|
role=OrgRoles.developer,
|
|
org_name=org.name,
|
|
)
|
|
orm_session.add(invite)
|
|
orm_session.commit() # Ensure it's persisted
|
|
|
|
# Now we should be able to find the invitation when accept_org_invite runs
|
|
invitation = (
|
|
orm_session.query(OrgInviteModel).filter_by(invitee_email="test@example.com", org_id=org.id).first()
|
|
)
|
|
assert invitation is not None, "Invitation not found in database"
|
|
|
|
# Call the endpoint function
|
|
result = accept_org_invite(request=mock_request, org_id=str(org.id), orm=orm_session)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "Organization invitation accepted"
|
|
|
|
# Verify the invite was removed and user-org relationship was created
|
|
invites = (
|
|
orm_session.query(OrgInviteModel).filter_by(invitee_email="test@example.com", org_id=org.id).all()
|
|
)
|
|
assert len(invites) == 0
|
|
|
|
# Verify the user-org relationship was created
|
|
user_orgs = orm_session.query(UserOrgModel).filter_by(user_id=user_id, org_id=org.id).all()
|
|
assert len(user_orgs) > 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_accept_org_invite_case_insensitive(mock_request, orm_session, test_user):
|
|
"""Test accepting an invitation with different email casing."""
|
|
org = OrgModel(
|
|
name="Test Org for Case Sensitive Accept",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
invite = OrgInviteModel(
|
|
inviter_id=test_user.id,
|
|
invitee_email="test@example.com",
|
|
org_id=org.id,
|
|
role=OrgRoles.developer,
|
|
org_name=org.name,
|
|
)
|
|
orm_session.add(invite)
|
|
orm_session.commit()
|
|
|
|
# Get the test user from the session to ensure auth_user relationship is loaded
|
|
user_from_session = orm_session.get(UserModel, test_user.id)
|
|
if not user_from_session:
|
|
pytest.skip("Test user not found in session")
|
|
|
|
# Check if auth_user exists, if not skip this test
|
|
if not user_from_session.auth_user:
|
|
pytest.skip("Test user does not have auth_user relationship set up")
|
|
|
|
# Save the original email
|
|
original_email = user_from_session.auth_user.email
|
|
|
|
# Temporarily modify the auth user's email in memory only (not persisted)
|
|
# This simulates a user whose auth email has different casing
|
|
object.__setattr__(user_from_session.auth_user, 'email', 'TEST@EXAMPLE.COM')
|
|
|
|
try:
|
|
# Accept with the uppercase email (should still find the lowercase invite)
|
|
result = accept_org_invite(request=mock_request, org_id=str(org.id), orm=orm_session)
|
|
|
|
# Verify the result
|
|
assert result.success is True
|
|
assert result.message == "Organization invitation accepted"
|
|
|
|
# Verify invite was removed
|
|
invites = orm_session.query(OrgInviteModel).filter_by(org_id=org.id).all()
|
|
assert len(invites) == 0
|
|
|
|
# Verify user was added to org
|
|
user_id = mock_request.state.session.user_id
|
|
user_orgs = orm_session.query(UserOrgModel).filter_by(user_id=user_id, org_id=org.id).all()
|
|
assert len(user_orgs) > 0
|
|
|
|
finally:
|
|
# Restore the original email
|
|
object.__setattr__(user_from_session.auth_user, 'email', original_email)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_accept_org_invite_not_found(mock_request, orm_session):
|
|
"""Test accepting an invitation that doesn't exist."""
|
|
non_existent_id = str(uuid.uuid4())
|
|
|
|
# Expect an HTTP 404 exception
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
accept_org_invite(request=mock_request, org_id=non_existent_id, orm=orm_session)
|
|
|
|
assert excinfo.value.status_code == 404
|
|
assert excinfo.value.detail == "Invitation not found"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_from_org(mock_request, orm_session, test_user2):
|
|
"""Test removing a user from an organization."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Remove",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add the primary test user as an owner
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
|
|
# Add the second test user as a developer
|
|
user2_org = UserOrgModel(
|
|
user_id=test_user2.id,
|
|
org_id=org.id,
|
|
role=OrgRoles.developer,
|
|
user_email=test_user2.email,
|
|
)
|
|
orm_session.add(user2_org)
|
|
orm_session.commit() # Commit to ensure both relationships are persisted
|
|
|
|
# Create the remove body using the second user's ID
|
|
body = OrgMemberRemoveSchema(user_id=str(test_user2.id))
|
|
|
|
# Fetch org again with relationships
|
|
org = orm_session.query(OrgModel).options(orm.selectinload(OrgModel.users)).filter_by(id=org.id).one()
|
|
|
|
# Verify both users are in the org
|
|
assert len(org.users) == 2, "Expected both users to be in the org"
|
|
|
|
# Call the endpoint function
|
|
result = remove_from_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "User removed from organization"
|
|
|
|
# Verify the user was removed
|
|
user_orgs = orm_session.query(UserOrgModel).filter_by(user_id=test_user2.id, org_id=org.id).all()
|
|
assert len(user_orgs) == 0, "Expected the second user to be removed from the org"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_from_org_owner(mock_request, orm_session, test_user, test_user2):
|
|
"""Test removing an owner from an organization (which should fail)."""
|
|
# First create a fresh org
|
|
org = OrgModel(name="Test Org for Remove Owner")
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add the primary test user as owner
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.flush()
|
|
|
|
# Add test_user2 as an owner
|
|
# We're using the existing test_user2 fixture which already exists in auth.users
|
|
second_user_org = UserOrgModel(
|
|
user_id=test_user2.id,
|
|
org_id=org.id,
|
|
role=OrgRoles.owner,
|
|
user_email=test_user2.email,
|
|
)
|
|
orm_session.add(second_user_org)
|
|
orm_session.commit() # Need to commit to ensure relationships are persisted
|
|
|
|
# Create the remove body - try to remove the second owner
|
|
body = OrgMemberRemoveSchema(user_id=str(test_user2.id))
|
|
|
|
# Expect an HTTP 400 exception
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
remove_from_org(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "User cannot be removed"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_from_org_self(mock_request, orm_session, test_user_org_owner):
|
|
"""Test removing yourself from an organization (which should fail)."""
|
|
# Create the remove body with the authenticated user's ID
|
|
body = OrgMemberRemoveSchema(user_id=str(mock_request.state.session.user_id))
|
|
|
|
# Expect an HTTP 400 exception
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
remove_from_org(request=mock_request, org_id=str(test_user_org_owner.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "User cannot be removed"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_member_role(mock_request, orm_session, test_user2):
|
|
"""Test changing a user's role in an organization."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Role Change",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add the primary test user as an owner
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
|
|
# Add the second test user as a developer
|
|
user2_org = UserOrgModel(
|
|
user_id=test_user2.id,
|
|
org_id=org.id,
|
|
role=OrgRoles.developer,
|
|
user_email=test_user2.email,
|
|
)
|
|
orm_session.add(user2_org)
|
|
orm_session.commit() # Commit to ensure both relationships are persisted
|
|
|
|
# Create the role change body using the second user's ID
|
|
body = OrgMemberRoleSchema(user_id=str(test_user2.id), role=OrgRoles.admin.value)
|
|
|
|
# Call the endpoint function
|
|
result = change_member_role(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "User role updated"
|
|
|
|
# Refresh the session to get the latest data from the database
|
|
orm_session.expire_all() # Clear cached objects
|
|
|
|
# Verify the role was updated
|
|
user_org = orm_session.query(UserOrgModel).filter_by(user_id=test_user2.id, org_id=org.id).one()
|
|
assert user_org.role == OrgRoles.admin, "Expected the user role to be updated to admin"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_member_role_to_owner(mock_request, orm_session, test_user2):
|
|
"""Test changing a user's role to owner."""
|
|
# Create a fresh organization for this test to avoid session issues
|
|
org = OrgModel(
|
|
name="Test Org for Owner Role Change",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add the primary test user as an owner
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
|
|
# Add test_user2 as a developer
|
|
user2_org = UserOrgModel(
|
|
user_id=test_user2.id,
|
|
org_id=org.id,
|
|
role=OrgRoles.developer,
|
|
user_email=test_user2.email,
|
|
)
|
|
orm_session.add(user2_org)
|
|
orm_session.commit() # Commit to ensure both relationships are persisted
|
|
|
|
# Create the role change body
|
|
body = OrgMemberRoleSchema(user_id=str(test_user2.id), role=OrgRoles.owner.value)
|
|
|
|
# Call the endpoint function
|
|
result = change_member_role(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "User role updated"
|
|
|
|
# Refresh the session to get the latest data from the database
|
|
orm_session.expire_all() # Clear cached objects
|
|
|
|
# Verify the role was updated
|
|
user_org = orm_session.query(UserOrgModel).filter_by(user_id=test_user2.id, org_id=org.id).one()
|
|
assert user_org.role == OrgRoles.owner
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_member_role_from_owner_to_developer(
|
|
mock_request, orm_session, test_user_org_owner, test_user2
|
|
):
|
|
"""Test changing a user's role from owner to developer (when there are multiple owners)."""
|
|
# Create a fresh organization for this test to avoid session issues
|
|
org = OrgModel(
|
|
name="Test Org for Role Change",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add the primary test user as an owner
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
|
|
# Add test_user2 as another owner
|
|
user2_org = UserOrgModel(
|
|
user_id=test_user2.id,
|
|
org_id=org.id,
|
|
role=OrgRoles.owner,
|
|
user_email=test_user2.email,
|
|
)
|
|
orm_session.add(user2_org)
|
|
orm_session.commit() # Commit to ensure both relationships are persisted
|
|
|
|
# Create the role change body
|
|
body = OrgMemberRoleSchema(user_id=str(test_user2.id), role=OrgRoles.developer.value)
|
|
|
|
# Call the endpoint function
|
|
result = change_member_role(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "User role updated"
|
|
|
|
# Refresh the session to get the latest data from the database
|
|
orm_session.expire_all() # Clear cached objects
|
|
|
|
# Verify the role was updated
|
|
user_org = orm_session.query(UserOrgModel).filter_by(user_id=test_user2.id, org_id=org.id).one()
|
|
assert user_org.role == OrgRoles.developer
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_last_owner_role(mock_request, orm_session):
|
|
"""Test changing the role of the last owner (which should fail)."""
|
|
# Create a fresh organization for this test to avoid session issues
|
|
org = OrgModel(
|
|
name="Test Org for Last Owner Role",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add the primary test user as the only owner
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit() # Commit to ensure the relationship is persisted
|
|
|
|
# Create the role change body to change the only owner to a developer
|
|
body = OrgMemberRoleSchema(user_id=str(mock_request.state.session.user_id), role=OrgRoles.developer.value)
|
|
|
|
# Expect an HTTP 400 exception when trying to change the only owner's role
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
change_member_role(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the error message
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "Cannot remove the last owner"
|
|
|
|
# Verify the role wasn't changed
|
|
orm_session.expire_all() # Clear cached objects
|
|
user_org = orm_session.query(UserOrgModel).filter_by(user_id=user_id, org_id=org.id).one()
|
|
assert user_org.role == OrgRoles.owner, "The user role should not have been changed"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_member_role_as_admin(mock_request, orm_session, test_user, test_user2, test_user3):
|
|
"""Test changing a user's role as an admin (not owner)."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Admin Role Change",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Add test_user2 as the owner
|
|
owner_org = UserOrgModel(
|
|
user_id=test_user2.id,
|
|
org_id=org.id,
|
|
role=OrgRoles.owner,
|
|
user_email=test_user2.email,
|
|
)
|
|
orm_session.add(owner_org)
|
|
|
|
# Add the primary test user as an admin (not owner)
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.admin, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
|
|
# Add test_user3 as a developer
|
|
dev_org = UserOrgModel(
|
|
user_id=test_user3.id, org_id=org.id, role=OrgRoles.developer, user_email=test_user3.email
|
|
)
|
|
orm_session.add(dev_org)
|
|
orm_session.commit() # Commit to ensure all relationships are persisted
|
|
|
|
# Create the role change body to promote developer to admin
|
|
body = OrgMemberRoleSchema(user_id=str(test_user3.id), role=OrgRoles.admin.value)
|
|
|
|
# Call the endpoint function with the admin user (mock_request contains test_user's ID)
|
|
result = change_member_role(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
# Verify the status response
|
|
assert result.success is True
|
|
assert result.message == "User role updated"
|
|
|
|
# Refresh the session to get the latest data from the database
|
|
orm_session.expire_all() # Clear cached objects
|
|
|
|
# Verify the role was updated
|
|
user_org = orm_session.query(UserOrgModel).filter_by(user_id=test_user3.id, org_id=org.id).one()
|
|
assert user_org.role == OrgRoles.admin
|
|
|
|
# Verify admin cannot promote someone to owner
|
|
body = OrgMemberRoleSchema(user_id=str(test_user3.id), role=OrgRoles.owner.value)
|
|
|
|
# Expect an HTTP 400 exception
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
change_member_role(request=mock_request, org_id=str(org.id), orm=orm_session, body=body)
|
|
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "Only owners can assign the owner role"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('stripe.checkout.Session.create')
|
|
@patch('stripe.Price.retrieve')
|
|
@patch('stripe.PromotionCode.list')
|
|
@patch('agentops.opsboard.views.orgs.STRIPE_SECRET_KEY', 'test_stripe_key')
|
|
async def test_create_checkout_session_with_valid_promotion_code(
|
|
mock_promo_list, mock_price_retrieve, mock_stripe_session, mock_request, orm_session, test_user
|
|
):
|
|
"""Test successful checkout with a promotion code."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Promotion Code",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit()
|
|
|
|
# Ensure the auth_user relationship is loaded (the test user should already have billing_email)
|
|
user_from_session = orm_session.get(UserModel, test_user.id)
|
|
if not user_from_session:
|
|
pytest.skip("Test user not found in session")
|
|
|
|
# Verify the user has billing_email from auth.users
|
|
if not user_from_session.billing_email:
|
|
pytest.skip("Test user does not have billing_email set up properly")
|
|
|
|
# Mock the promotion code lookup
|
|
mock_promo_code = type('obj', (object,), {'id': 'promo_123'})
|
|
mock_promo_list.return_value.data = [mock_promo_code]
|
|
|
|
# Mock the price retrieve call
|
|
mock_price = type('obj', (object,), {'recurring': type('obj', (object,), {'usage_type': 'licensed'})})
|
|
mock_price_retrieve.return_value = mock_price
|
|
|
|
# Mock the Stripe session creation
|
|
mock_stripe_session.return_value.client_secret = "test_client_secret_123"
|
|
|
|
# Create the request body with promotion code
|
|
body = CreateCheckoutSessionBody(price_id="price_test123", discount_code="SAVE20")
|
|
|
|
# Call the endpoint with the new signature
|
|
result = await create_checkout_session(
|
|
request=mock_request, org_id=str(org.id), body=body, orm=orm_session
|
|
)
|
|
|
|
# Verify the response
|
|
assert result.clientSecret == "test_client_secret_123"
|
|
|
|
# Verify promotion code lookup was called
|
|
mock_promo_list.assert_called_once_with(code="SAVE20", active=True, limit=1)
|
|
|
|
# Verify Stripe was called with the promotion code ID
|
|
mock_stripe_session.assert_called_once()
|
|
call_args = mock_stripe_session.call_args[1]
|
|
assert call_args['discounts'] == [{'promotion_code': 'promo_123'}]
|
|
assert call_args['customer_email'] == user_from_session.billing_email
|
|
assert call_args['line_items'][0]['price'] == "price_test123"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('stripe.checkout.Session.create')
|
|
@patch('stripe.Price.retrieve')
|
|
@patch('stripe.Coupon.retrieve')
|
|
@patch('stripe.PromotionCode.list')
|
|
@patch('agentops.opsboard.views.orgs.STRIPE_SECRET_KEY', 'test_stripe_key')
|
|
async def test_create_checkout_session_with_valid_coupon_id(
|
|
mock_promo_list,
|
|
mock_coupon_retrieve,
|
|
mock_price_retrieve,
|
|
mock_stripe_session,
|
|
mock_request,
|
|
orm_session,
|
|
test_user,
|
|
):
|
|
"""Test successful checkout with a direct coupon ID."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Coupon",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit()
|
|
|
|
# Ensure the auth_user relationship is loaded (the test user should already have billing_email)
|
|
user_from_session = orm_session.get(UserModel, test_user.id)
|
|
if not user_from_session:
|
|
pytest.skip("Test user not found in session")
|
|
|
|
# Verify the user has billing_email from auth.users
|
|
if not user_from_session.billing_email:
|
|
pytest.skip("Test user does not have billing_email set up properly")
|
|
|
|
# Mock promotion code lookup to return empty (not a promotion code)
|
|
mock_promo_list.return_value.data = []
|
|
|
|
# Mock coupon retrieval to return a valid coupon
|
|
mock_coupon = type('obj', (object,), {'valid': True})
|
|
mock_coupon_retrieve.return_value = mock_coupon
|
|
|
|
# Mock the price retrieve call
|
|
mock_price = type('obj', (object,), {'recurring': type('obj', (object,), {'usage_type': 'licensed'})})
|
|
mock_price_retrieve.return_value = mock_price
|
|
|
|
# Mock the Stripe session creation
|
|
mock_stripe_session.return_value.client_secret = "test_client_secret_456"
|
|
|
|
# Create the request body with coupon ID
|
|
body = CreateCheckoutSessionBody(price_id="price_test456", discount_code="SUMMER_SALE")
|
|
|
|
# Call the endpoint with the new signature
|
|
result = await create_checkout_session(
|
|
request=mock_request, org_id=str(org.id), body=body, orm=orm_session
|
|
)
|
|
|
|
# Verify the response
|
|
assert result.clientSecret == "test_client_secret_456"
|
|
|
|
# Verify promotion code lookup was called first
|
|
mock_promo_list.assert_called_once_with(code="SUMMER_SALE", active=True, limit=1)
|
|
|
|
# Verify coupon retrieval was called as fallback
|
|
mock_coupon_retrieve.assert_called_once_with("SUMMER_SALE")
|
|
|
|
# Verify Stripe was called with the coupon
|
|
mock_stripe_session.assert_called_once()
|
|
call_args = mock_stripe_session.call_args[1]
|
|
assert call_args['discounts'] == [{'coupon': "SUMMER_SALE"}]
|
|
assert call_args['customer_email'] == user_from_session.billing_email
|
|
assert call_args['line_items'][0]['price'] == "price_test456"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('stripe.checkout.Session.create')
|
|
@patch('stripe.Price.retrieve')
|
|
@patch('stripe.Coupon.retrieve')
|
|
@patch('stripe.PromotionCode.list')
|
|
@patch('agentops.opsboard.views.orgs.STRIPE_SECRET_KEY', 'test_stripe_key')
|
|
async def test_create_checkout_session_with_invalid_discount_code(
|
|
mock_promo_list,
|
|
mock_coupon_retrieve,
|
|
mock_price_retrieve,
|
|
mock_stripe_session,
|
|
mock_request,
|
|
orm_session,
|
|
test_user,
|
|
):
|
|
"""Test error handling for invalid discount codes."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org for Invalid Code",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit()
|
|
|
|
# Ensure the auth_user relationship is loaded (the test user should already have billing_email)
|
|
user_from_session = orm_session.get(UserModel, test_user.id)
|
|
if not user_from_session:
|
|
pytest.skip("Test user not found in session")
|
|
|
|
# Verify the user has billing_email from auth.users
|
|
if not user_from_session.billing_email:
|
|
pytest.skip("Test user does not have billing_email set up properly")
|
|
|
|
# Mock promotion code lookup to return empty (not a promotion code)
|
|
mock_promo_list.return_value.data = []
|
|
|
|
# Mock coupon retrieval to raise an error (invalid coupon)
|
|
mock_coupon_retrieve.side_effect = stripe.error.InvalidRequestError(
|
|
message="No such coupon: 'INVALID'", param="coupon"
|
|
)
|
|
|
|
# Mock the price retrieve call
|
|
mock_price = type('obj', (object,), {'recurring': type('obj', (object,), {'usage_type': 'licensed'})})
|
|
mock_price_retrieve.return_value = mock_price
|
|
|
|
# Create the request body with invalid discount code
|
|
body = CreateCheckoutSessionBody(price_id="price_test789", discount_code="INVALID")
|
|
|
|
from agentops.opsboard.views.orgs import create_checkout_session
|
|
|
|
# Expect an HTTP 400 exception for invalid discount code
|
|
with pytest.raises(HTTPException) as excinfo:
|
|
await create_checkout_session(
|
|
request=mock_request,
|
|
org_id=str(org.id),
|
|
body=body,
|
|
orm=orm_session,
|
|
)
|
|
|
|
assert excinfo.value.status_code == 400
|
|
assert excinfo.value.detail == "Invalid discount code"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('stripe.checkout.Session.create')
|
|
@patch('stripe.Price.retrieve')
|
|
@patch('agentops.opsboard.views.orgs.STRIPE_SECRET_KEY', 'test_stripe_key')
|
|
async def test_create_checkout_session_without_discount_code(
|
|
mock_price_retrieve, mock_stripe_session, mock_request, orm_session, test_user
|
|
):
|
|
"""Test that checkout works without a discount code."""
|
|
# Create a fresh organization for this test
|
|
org = OrgModel(
|
|
name="Test Org No Discount",
|
|
prem_status=PremStatus.free,
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit()
|
|
|
|
# Ensure the auth_user relationship is loaded (the test user should already have billing_email)
|
|
user_from_session = orm_session.get(UserModel, test_user.id)
|
|
if not user_from_session:
|
|
pytest.skip("Test user not found in session")
|
|
|
|
# Verify the user has billing_email from auth.users
|
|
if not user_from_session.billing_email:
|
|
pytest.skip("Test user does not have billing_email set up properly")
|
|
|
|
# Mock the price retrieve call
|
|
mock_price = type('obj', (object,), {'recurring': type('obj', (object,), {'usage_type': 'licensed'})})
|
|
mock_price_retrieve.return_value = mock_price
|
|
|
|
# Mock the Stripe session creation
|
|
mock_stripe_session.return_value.client_secret = "test_client_secret_no_discount"
|
|
|
|
# Create the request body without any discount
|
|
body = CreateCheckoutSessionBody(price_id="price_test999")
|
|
|
|
from agentops.opsboard.views.orgs import create_checkout_session
|
|
|
|
# Call the endpoint without discount parameters
|
|
result = await create_checkout_session(
|
|
request=mock_request, org_id=str(org.id), body=body, orm=orm_session
|
|
)
|
|
|
|
# Verify the response
|
|
assert result.clientSecret == "test_client_secret_no_discount"
|
|
|
|
# Verify Stripe was called without discounts
|
|
mock_stripe_session.assert_called_once()
|
|
call_args = mock_stripe_session.call_args[1]
|
|
assert 'discounts' not in call_args # No discounts field when not using discount codes
|
|
assert call_args['customer_email'] == user_from_session.billing_email
|
|
assert call_args['line_items'][0]['price'] == "price_test999"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('agentops.opsboard.views.orgs.STRIPE_SECRET_KEY', 'test_stripe_key')
|
|
@patch('stripe.Subscription.retrieve')
|
|
async def test_get_user_orgs_with_discount(mock_stripe_subscription, mock_request, orm_session, test_user):
|
|
"""Test that discount info is properly returned in organization list."""
|
|
# Create a fresh organization with pro status and subscription
|
|
org = OrgModel(
|
|
name="Test Org with Discount", prem_status=PremStatus.pro, subscription_id="sub_test_with_discount"
|
|
)
|
|
orm_session.add(org)
|
|
orm_session.flush()
|
|
|
|
# Create a user-org relationship
|
|
user_id = mock_request.state.session.user_id
|
|
user_org = UserOrgModel(
|
|
user_id=user_id, org_id=org.id, role=OrgRoles.owner, user_email="test@example.com"
|
|
)
|
|
orm_session.add(user_org)
|
|
orm_session.commit()
|
|
|
|
# Mock Stripe subscription with discount
|
|
mock_subscription = {
|
|
'id': 'sub_test_with_discount',
|
|
'status': 'active',
|
|
'current_period_start': 1734000000, # Some start timestamp
|
|
'current_period_end': 1735689600, # Some future timestamp
|
|
'cancel_at_period_end': False,
|
|
'discount': {
|
|
'coupon': {'id': 'SUMMER_SALE', 'percent_off': 20, 'valid': True},
|
|
'promotion_code': 'SAVE20',
|
|
},
|
|
}
|
|
mock_stripe_subscription.return_value = mock_subscription
|
|
|
|
from agentops.opsboard.views.orgs import get_user_orgs
|
|
|
|
# Call the endpoint
|
|
result = get_user_orgs(request=mock_request, orm=orm_session)
|
|
|
|
# Find our test org in the results
|
|
test_org_response = None
|
|
for org_response in result:
|
|
if org_response.id == str(org.id):
|
|
test_org_response = org_response
|
|
break
|
|
|
|
assert test_org_response is not None
|
|
assert test_org_response.subscription_end_date == 1735689600
|
|
assert test_org_response.subscription_cancel_at_period_end is False
|
|
|
|
# Note: The current implementation doesn't return discount info in the response
|
|
# This test verifies the subscription details are fetched correctly
|
|
# If discount info needs to be added to the response, the schema and view would need updates
|