| """ |
| Comprehensive cross-model relationship tests covering one-to-many, many-to-many, |
| self-referential, and polymorphic relationships across all model modules. |
| |
| These tests ensure ORM relationships are configured correctly and work bidirectionally. |
| Model relationships are the backbone of data integrity - incorrect relationship |
| configurations cause data inconsistencies, cascade failures, and query bugs. |
| |
| Coverage: |
| - One-to-many relationships (18+ relationships) |
| - Many-to-many relationships (user-workspace, user-team) |
| - Self-referential relationships (Account hierarchy, User manager) |
| - Polymorphic relationships (CanvasAudit agent/user) |
| - Optional relationships (nullable foreign keys) |
| - Relationship loading strategies (lazy, joinedload, selectinload) |
| """ |
|
|
| import uuid |
| import pytest |
| from datetime import datetime, timedelta |
| from sqlalchemy.orm import Session, joinedload, selectinload |
| from sqlalchemy.exc import IntegrityError |
|
|
| from tests.factories.agent_factory import AgentFactory |
| from tests.factories.user_factory import UserFactory |
| from tests.factories.execution_factory import AgentExecutionFactory |
| from tests.factories.episode_factory import EpisodeFactory |
| from tests.factories.feedback_factory import AgentFeedbackFactory |
| from tests.factories.workspace_factory import WorkspaceFactory, TeamFactory |
| from tests.factories.core_factory import TenantFactory, UserAccountFactory, OAuthTokenFactory, ChatMessageFactory |
| from tests.factories.accounting_factory import AccountFactory, EntityFactory, TransactionFactory, JournalEntryFactory |
| from tests.factories.sales_factory import DealFactory, CallTranscriptFactory, CommissionEntryFactory |
| from tests.factories.service_factory import ContractFactory, ProjectFactory, MilestoneFactory, ProjectTaskFactory |
|
|
| from core.models import ( |
| AgentRegistry, |
| AgentExecution, |
| AgentFeedback, |
| User, |
| Workspace, |
| Team, |
| Tenant, |
| UserAccount, |
| OAuthToken, |
| ChatMessage, |
| Episode, |
| EpisodeSegment, |
| CanvasAudit, |
| Canvas, |
| user_workspaces, |
| team_members, |
| ) |
|
|
| from accounting.models import Account, Entity, Bill, Invoice, Transaction, JournalEntry |
| from sales.models import Deal, CallTranscript, CommissionEntry |
| from service_delivery.models import Contract, Project, Milestone, ProjectTask |
|
|
|
|
| |
| |
| |
|
|
| class TestOneToManyRelationships: |
| """Test one-to-many relationships work bidirectionally.""" |
|
|
| def test_agent_executions_relationship(self, db_session: Session): |
| """Test Agent has many executions, bidirectional navigation.""" |
| |
| agent = AgentFactory(name="MultiExecutionAgent", _session=db_session) |
|
|
| |
| execution1 = AgentExecutionFactory(agent_id=agent.id, _session=db_session) |
| execution2 = AgentExecutionFactory(agent_id=agent.id, _session=db_session) |
| execution3 = AgentExecutionFactory(agent_id=agent.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| executions = db_session.query(AgentExecution).filter( |
| AgentExecution.agent_id == agent.id |
| ).all() |
|
|
| assert len(executions) == 3 |
| assert all(e.agent_id == agent.id for e in executions) |
|
|
| |
| retrieved_execution = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution1.id |
| ).first() |
| assert retrieved_execution.agent_id == agent.id |
|
|
| def test_agent_feedback_relationship(self, db_session: Session): |
| """Test Agent has many feedback entries.""" |
| agent = AgentFactory(name="FeedbackAgent", _session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| |
| feedback1 = AgentFeedbackFactory(agent_id=agent.id, user_id=user.id, _session=db_session) |
| feedback2 = AgentFeedbackFactory(agent_id=agent.id, user_id=user.id, _session=db_session) |
| feedback3 = AgentFeedbackFactory(agent_id=agent.id, user_id=user.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| feedback_list = db_session.query(AgentFeedback).filter( |
| AgentFeedback.agent_id == agent.id |
| ).all() |
|
|
| assert len(feedback_list) == 3 |
| assert all(f.agent_id == agent.id for f in feedback_list) |
|
|
| def test_user_user_accounts_relationship(self, db_session: Session): |
| """Test User has many IM platform accounts.""" |
| user = UserFactory(email="multiaccount@test.com", _session=db_session) |
|
|
| |
| account1 = UserAccountFactory(user_id=user.id, platform="slack", _session=db_session) |
| account2 = UserAccountFactory(user_id=user.id, platform="telegram", _session=db_session) |
| account3 = UserAccountFactory(user_id=user.id, platform="discord", _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| accounts = db_session.query(UserAccount).filter( |
| UserAccount.user_id == user.id |
| ).all() |
|
|
| assert len(accounts) == 3 |
| assert accounts[0].user_id == user.id |
|
|
| def test_workspace_teams_relationship(self, db_session: Session): |
| """Test Workspace has many teams.""" |
| workspace = WorkspaceFactory(_session=db_session) |
|
|
| |
| team1 = TeamFactory(workspace_id=workspace.id, name="Engineering", _session=db_session) |
| team2 = TeamFactory(workspace_id=workspace.id, name="Sales", _session=db_session) |
| team3 = TeamFactory(workspace_id=workspace.id, name="Marketing", _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| teams = db_session.query(Team).filter( |
| Team.workspace_id == workspace.id |
| ).all() |
|
|
| assert len(teams) == 3 |
| assert all(t.workspace_id == workspace.id for t in teams) |
|
|
| def test_workspace_users_relationship(self, db_session: Session): |
| """Test Workspace has many users (through M2M).""" |
| workspace = WorkspaceFactory(_session=db_session) |
| db_session.commit() |
|
|
| |
| user1 = UserFactory(email="user1@test.com", _session=db_session) |
| user2 = UserFactory(email="user2@test.com", _session=db_session) |
| user3 = UserFactory(email="user3@test.com", _session=db_session) |
|
|
| workspace.users.append(user1) |
| workspace.users.append(user2) |
| workspace.users.append(user3) |
| db_session.commit() |
|
|
| |
| retrieved_workspace = db_session.query(Workspace).filter( |
| Workspace.id == workspace.id |
| ).first() |
|
|
| assert len(retrieved_workspace.users) == 3 |
|
|
| def test_tenant_workspaces_relationship(self, db_session: Session): |
| """Test Tenant has many workspaces.""" |
| tenant = TenantFactory(_session=db_session) |
|
|
| |
| workspace1 = WorkspaceFactory(tenant_id=tenant.id, name="Workspace 1", _session=db_session) |
| workspace2 = WorkspaceFactory(tenant_id=tenant.id, name="Workspace 2", _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| workspaces = db_session.query(Workspace).filter( |
| Workspace.tenant_id == tenant.id |
| ).all() |
|
|
| assert len(workspaces) == 2 |
| assert all(w.tenant_id == tenant.id for w in workspaces) |
|
|
| def test_tenant_push_tokens_relationship(self, db_session: Session): |
| """Test Tenant has many push tokens with cascade.""" |
| from core.models import PushToken, User |
|
|
| tenant = TenantFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| |
| token1 = PushToken( |
| id=str(uuid.uuid4()), |
| tenant_id=tenant.id, |
| user_id=user.id, |
| token="token1_ios", |
| platform="ios" |
| ) |
| token2 = PushToken( |
| id=str(uuid.uuid4()), |
| tenant_id=tenant.id, |
| user_id=user.id, |
| token="token2_android", |
| platform="android" |
| ) |
|
|
| db_session.add_all([token1, token2]) |
| db_session.commit() |
|
|
| |
| tokens = db_session.query(PushToken).filter( |
| PushToken.tenant_id == tenant.id |
| ).all() |
|
|
| assert len(tokens) == 2 |
| assert all(t.tenant_id == tenant.id for t in tokens) |
|
|
| def test_episode_segments_relationship(self, db_session: Session): |
| """Test Episode has many segments with cascade delete.""" |
| agent = AgentFactory(_session=db_session) |
| tenant = TenantFactory(_session=db_session) |
|
|
| |
| episode = Episode( |
| id=str(uuid.uuid4()), |
| agent_id=agent.id, |
| tenant_id=tenant.id, |
| task_description="Segment test episode", |
| maturity_at_time="INTERN", |
| outcome="success" |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| segment1 = EpisodeSegment( |
| id=str(uuid.uuid4()), |
| episode_id=episode.id, |
| segment_type="conversation", |
| sequence_order=1, |
| content="First segment" |
| ) |
| segment2 = EpisodeSegment( |
| id=str(uuid.uuid4()), |
| episode_id=episode.id, |
| segment_type="execution", |
| sequence_order=2, |
| content="Second segment" |
| ) |
| segment3 = EpisodeSegment( |
| id=str(uuid.uuid4()), |
| episode_id=episode.id, |
| segment_type="reflection", |
| sequence_order=3, |
| content="Third segment" |
| ) |
|
|
| db_session.add_all([segment1, segment2, segment3]) |
| db_session.commit() |
|
|
| |
| segments = db_session.query(EpisodeSegment).filter( |
| EpisodeSegment.episode_id == episode.id |
| ).all() |
|
|
| assert len(segments) == 3 |
|
|
| def test_canvas_audit_relationships(self, db_session: Session): |
| """Test CanvasAudit links to agent OR user (polymorphic).""" |
| from core.models import Canvas |
|
|
| agent = AgentFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
| tenant = TenantFactory(_session=db_session) |
|
|
| |
| canvas = Canvas( |
| id=str(uuid.uuid4()), |
| tenant_id=tenant.id, |
| canvas_type="form", |
| name="Test Canvas", |
| created_by=user.id |
| ) |
| db_session.add(canvas) |
| db_session.commit() |
|
|
| |
| audit1 = CanvasAudit( |
| id=str(uuid.uuid4()), |
| canvas_id=canvas.id, |
| tenant_id=tenant.id, |
| agent_id=agent.id, |
| user_id=None, |
| action_type="present" |
| ) |
| db_session.add(audit1) |
|
|
| |
| audit2 = CanvasAudit( |
| id=str(uuid.uuid4()), |
| canvas_id=canvas.id, |
| tenant_id=tenant.id, |
| agent_id=None, |
| user_id=user.id, |
| action_type="submit" |
| ) |
| db_session.add(audit2) |
|
|
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).all() |
| assert len(audits) == 2 |
|
|
| def test_deal_transcripts_relationship(self, db_session: Session): |
| """Test Deal has many call transcripts.""" |
| deal = DealFactory(_session=db_session) |
|
|
| |
| transcript1 = CallTranscriptFactory(deal_id=deal.id, _session=db_session) |
| transcript2 = CallTranscriptFactory(deal_id=deal.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| transcripts = db_session.query(CallTranscript).filter( |
| CallTranscript.deal_id == deal.id |
| ).all() |
|
|
| assert len(transcripts) == 2 |
| assert all(t.deal_id == deal.id for t in transcripts) |
|
|
| def test_deal_commissions_relationship(self, db_session: Session): |
| """Test Deal has many commission entries.""" |
| deal = DealFactory(_session=db_session) |
|
|
| |
| commission1 = CommissionEntryFactory(deal_id=deal.id, _session=db_session) |
| commission2 = CommissionEntryFactory(deal_id=deal.id, _session=db_session) |
| commission3 = CommissionEntryFactory(deal_id=deal.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| commissions = db_session.query(CommissionEntry).filter( |
| CommissionEntry.deal_id == deal.id |
| ).all() |
|
|
| assert len(commissions) == 3 |
|
|
| def test_contract_projects_relationship(self, db_session: Session): |
| """Test Contract has many projects.""" |
| contract = ContractFactory(_session=db_session) |
|
|
| |
| project1 = ProjectFactory(contract_id=contract.id, _session=db_session) |
| project2 = ProjectFactory(contract_id=contract.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| projects = db_session.query(Project).filter( |
| Project.contract_id == contract.id |
| ).all() |
|
|
| assert len(projects) == 2 |
|
|
| def test_project_milestones_relationship(self, db_session: Session): |
| """Test Project has many milestones.""" |
| project = ProjectFactory(_session=db_session) |
|
|
| |
| milestone1 = MilestoneFactory(project_id=project.id, _session=db_session) |
| milestone2 = MilestoneFactory(project_id=project.id, _session=db_session) |
| milestone3 = MilestoneFactory(project_id=project.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| milestones = db_session.query(Milestone).filter( |
| Milestone.project_id == project.id |
| ).all() |
|
|
| assert len(milestones) == 3 |
|
|
| def test_milestone_tasks_relationship(self, db_session: Session): |
| """Test Milestone has many tasks.""" |
| milestone = MilestoneFactory(_session=db_session) |
|
|
| |
| task1 = ProjectTaskFactory(milestone_id=milestone.id, _session=db_session) |
| task2 = ProjectTaskFactory(milestone_id=milestone.id, _session=db_session) |
| task3 = ProjectTaskFactory(milestone_id=milestone.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| tasks = db_session.query(ProjectTask).filter( |
| ProjectTask.milestone_id == milestone.id |
| ).all() |
|
|
| assert len(tasks) == 3 |
|
|
| def test_entity_bills_relationship(self, db_session: Session): |
| """Test Entity (vendor) has many bills.""" |
| from tests.factories.accounting_factory import BillFactory |
|
|
| entity = EntityFactory(type="vendor", _session=db_session) |
|
|
| |
| bill1 = BillFactory(vendor_id=entity.id, _session=db_session) |
| bill2 = BillFactory(vendor_id=entity.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| bills = db_session.query(Bill).filter( |
| Bill.vendor_id == entity.id |
| ).all() |
|
|
| assert len(bills) == 2 |
|
|
| def test_entity_invoices_relationship(self, db_session: Session): |
| """Test Entity (customer) has many invoices.""" |
| from tests.factories.accounting_factory import InvoiceFactory |
|
|
| entity = EntityFactory(type="customer", _session=db_session) |
|
|
| |
| invoice1 = InvoiceFactory(customer_id=entity.id, _session=db_session) |
| invoice2 = InvoiceFactory(customer_id=entity.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| invoices = db_session.query(Invoice).filter( |
| Invoice.customer_id == entity.id |
| ).all() |
|
|
| assert len(invoices) == 2 |
|
|
| def test_transaction_journal_entries_relationship(self, db_session: Session): |
| """Test Transaction has many entries with cascade.""" |
| transaction = TransactionFactory(_session=db_session) |
|
|
| |
| account = AccountFactory(_session=db_session) |
|
|
| |
| entry1 = JournalEntryFactory( |
| transaction_id=transaction.id, |
| account_id=account.id, |
| _session=db_session |
| ) |
| entry2 = JournalEntryFactory( |
| transaction_id=transaction.id, |
| account_id=account.id, |
| _session=db_session |
| ) |
|
|
| db_session.commit() |
|
|
| |
| entries = db_session.query(JournalEntry).filter( |
| JournalEntry.transaction_id == transaction.id |
| ).all() |
|
|
| assert len(entries) == 2 |
|
|
| def test_account_entries_relationship(self, db_session: Session): |
| """Test Account has many journal entries.""" |
| account = AccountFactory(_session=db_session) |
| transaction = TransactionFactory(_session=db_session) |
|
|
| |
| entry1 = JournalEntryFactory( |
| transaction_id=transaction.id, |
| account_id=account.id, |
| _session=db_session |
| ) |
| entry2 = JournalEntryFactory( |
| transaction_id=transaction.id, |
| account_id=account.id, |
| _session=db_session |
| ) |
|
|
| db_session.commit() |
|
|
| |
| entries = db_session.query(JournalEntry).filter( |
| JournalEntry.account_id == account.id |
| ).all() |
|
|
| assert len(entries) == 2 |
|
|
|
|
| |
| |
| |
|
|
| class TestManyToManyRelationships: |
| """Test many-to-many relationships work bidirectionally.""" |
|
|
| def test_user_workspace_association(self, db_session: Session): |
| """Test Users belong to many workspaces via user_workspaces table.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| db_session.commit() |
|
|
| |
| user1 = UserFactory(email="user1@test.com", _session=db_session) |
| user2 = UserFactory(email="user2@test.com", _session=db_session) |
| user3 = UserFactory(email="user3@test.com", _session=db_session) |
|
|
| |
| workspace.users.append(user1) |
| workspace.users.append(user2) |
| workspace.users.append(user3) |
| db_session.commit() |
|
|
| |
| retrieved_workspace = db_session.query(Workspace).filter( |
| Workspace.id == workspace.id |
| ).first() |
| assert len(retrieved_workspace.users) == 3 |
|
|
| |
| retrieved_user1 = db_session.query(User).filter(User.id == user1.id).first() |
| assert len(retrieved_user1.workspaces) == 1 |
| assert retrieved_user1.workspaces[0].id == workspace.id |
|
|
| def test_user_team_association(self, db_session: Session): |
| """Test Users belong to many teams via team_members table.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| team = TeamFactory(workspace_id=workspace.id, _session=db_session) |
|
|
| |
| user1 = UserFactory(email="member1@test.com", _session=db_session) |
| user2 = UserFactory(email="member2@test.com", _session=db_session) |
|
|
| |
| team.members.append(user1) |
| team.members.append(user2) |
| db_session.commit() |
|
|
| |
| retrieved_team = db_session.query(Team).filter(Team.id == team.id).first() |
| assert len(retrieved_team.members) == 2 |
|
|
| |
| retrieved_user1 = db_session.query(User).filter(User.id == user1.id).first() |
| assert len(retrieved_user1.teams) == 1 |
| assert retrieved_user1.teams[0].id == team.id |
|
|
| def test_episode_canvas_association(self, db_session: Session): |
| """Test Episode links to many canvases via canvas_ids array.""" |
| agent = AgentFactory(_session=db_session) |
| tenant = TenantFactory(_session=db_session) |
|
|
| episode = Episode( |
| id=str(uuid.uuid4()), |
| agent_id=agent.id, |
| tenant_id=tenant.id, |
| task_description="Canvas episode", |
| maturity_at_time="INTERN", |
| outcome="success", |
| canvas_ids=["canvas1", "canvas2", "canvas3"] |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| retrieved_episode = db_session.query(Episode).filter(Episode.id == episode.id).first() |
| assert len(retrieved_episode.canvas_ids) == 3 |
| assert "canvas1" in retrieved_episode.canvas_ids |
|
|
| def test_episode_feedback_association(self, db_session: Session): |
| """Test Episode links to many feedback entries via feedback_ids array.""" |
| agent = AgentFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
| tenant = TenantFactory(_session=db_session) |
|
|
| feedback1 = AgentFeedbackFactory(agent_id=agent.id, user_id=user.id, _session=db_session) |
| feedback2 = AgentFeedbackFactory(agent_id=agent.id, user_id=user.id, _session=db_session) |
|
|
| episode = Episode( |
| id=str(uuid.uuid4()), |
| agent_id=agent.id, |
| tenant_id=tenant.id, |
| task_description="Feedback episode", |
| maturity_at_time="INTERN", |
| outcome="success", |
| feedback_ids=[feedback1.id, feedback2.id] |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| retrieved_episode = db_session.query(Episode).filter(Episode.id == episode.id).first() |
| assert len(retrieved_episode.feedback_ids) == 2 |
| assert feedback1.id in retrieved_episode.feedback_ids |
|
|
|
|
| class TestManyToManyBidirectional: |
| """Test bidirectional navigation for many-to-many relationships.""" |
|
|
| def test_workspace_user_both_directions(self, db_session: Session): |
| """Test workspace.users and user.workspaces.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| workspace.users.append(user) |
| db_session.commit() |
|
|
| |
| retrieved_workspace = db_session.query(Workspace).filter( |
| Workspace.id == workspace.id |
| ).first() |
| assert len(retrieved_workspace.users) == 1 |
|
|
| |
| retrieved_user = db_session.query(User).filter(User.id == user.id).first() |
| assert len(retrieved_user.workspaces) == 1 |
|
|
| def test_team_user_both_directions(self, db_session: Session): |
| """Test team.members and user.teams.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| team = TeamFactory(workspace_id=workspace.id, _session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| team.members.append(user) |
| db_session.commit() |
|
|
| |
| retrieved_team = db_session.query(Team).filter(Team.id == team.id).first() |
| assert len(retrieved_team.members) == 1 |
|
|
| |
| retrieved_user = db_session.query(User).filter(User.id == user.id).first() |
| assert len(retrieved_user.teams) == 1 |
|
|
| def test_many_many_query_performance(self, db_session: Session): |
| """Test query performance with 100+ related records.""" |
| workspace = WorkspaceFactory(_session=db_session) |
|
|
| |
| users = [] |
| for i in range(100): |
| user = UserFactory(email=f"user{i}@test.com", _session=db_session) |
| users.append(user) |
| workspace.users.append(user) |
|
|
| db_session.commit() |
|
|
| |
| import time |
| start = time.time() |
|
|
| retrieved_workspace = db_session.query(Workspace).filter( |
| Workspace.id == workspace.id |
| ).first() |
|
|
| elapsed = time.time() - start |
|
|
| assert len(retrieved_workspace.users) == 100 |
| assert elapsed < 1.0 |
|
|
|
|
| class TestManyToManyCascade: |
| """Test cascade behaviors for many-to-many relationships.""" |
|
|
| def test_workspace_delete_removes_user_association(self, db_session: Session): |
| """Test deleting workspace removes association entries.""" |
| |
| |
| workspace = WorkspaceFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| workspace.users.append(user) |
| db_session.commit() |
|
|
| workspace_id = workspace.id |
| user_id = user.id |
|
|
| |
| workspace.users.remove(user) |
| db_session.commit() |
|
|
| |
| retrieved_workspace = db_session.query(Workspace).filter( |
| Workspace.id == workspace_id |
| ).first() |
| assert len(retrieved_workspace.users) == 0 |
|
|
| |
| assert db_session.query(User).filter(User.id == user_id).first() is not None |
|
|
| def test_user_delete_removes_workspace_association(self, db_session: Session): |
| """Test deleting user removes association entries.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| workspace.users.append(user) |
| db_session.commit() |
|
|
| workspace_id = workspace.id |
| user_id = user.id |
|
|
| |
| workspace.users.remove(user) |
| db_session.commit() |
|
|
| |
| assert db_session.query(Workspace).filter(Workspace.id == workspace_id).first() is not None |
|
|
| |
| assert db_session.query(User).filter(User.id == user_id).first() is not None |
|
|
| def test_team_delete_removes_member_association(self, db_session: Session): |
| """Test deleting team removes member entries.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| team = TeamFactory(workspace_id=workspace.id, _session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| team.members.append(user) |
| db_session.commit() |
|
|
| team_id = team.id |
| user_id = user.id |
|
|
| |
| team.members.remove(user) |
| db_session.commit() |
|
|
| |
| assert db_session.query(Team).filter(Team.id == team_id).first() is not None |
|
|
| |
| assert db_session.query(User).filter(User.id == user_id).first() is not None |
|
|
|
|
| |
| |
| |
|
|
| class TestSelfReferentialRelationships: |
| """Test self-referential relationships work correctly.""" |
|
|
| def test_account_hierarchy(self, db_session: Session): |
| """Test Account model has parent_account (self-referential).""" |
| |
| parent = AccountFactory( |
| name="Assets", |
| code="1000", |
| _session=db_session |
| ) |
|
|
| |
| child1 = AccountFactory( |
| name="Cash", |
| code="1001", |
| parent_id=parent.id, |
| _session=db_session |
| ) |
| child2 = AccountFactory( |
| name="Accounts Receivable", |
| code="1002", |
| parent_id=parent.id, |
| _session=db_session |
| ) |
|
|
| db_session.commit() |
|
|
| |
| retrieved_parent = db_session.query(Account).filter(Account.id == parent.id).first() |
| assert len(retrieved_parent.sub_accounts) == 2 |
|
|
| |
| retrieved_child1 = db_session.query(Account).filter(Account.id == child1.id).first() |
| assert retrieved_child1.parent.id == parent.id |
|
|
| def test_multi_level_account_hierarchy(self, db_session: Session): |
| """Test multi-level hierarchy (grandparent -> parent -> child).""" |
| |
| grandparent = AccountFactory( |
| name="Assets", |
| code="1", |
| _session=db_session |
| ) |
|
|
| |
| parent = AccountFactory( |
| name="Current Assets", |
| code="10", |
| parent_id=grandparent.id, |
| _session=db_session |
| ) |
|
|
| |
| child = AccountFactory( |
| name="Cash", |
| code="100", |
| parent_id=parent.id, |
| _session=db_session |
| ) |
|
|
| db_session.commit() |
|
|
| |
| retrieved_child = db_session.query(Account).filter(Account.id == child.id).first() |
| assert retrieved_child.parent.id == parent.id |
| assert retrieved_child.parent.parent.id == grandparent.id |
|
|
|
|
| class TestPolymorphicRelationships: |
| """Test polymorphic relationships (optional FKs).""" |
|
|
| def test_canvas_audit_agent_or_user(self, db_session: Session): |
| """Test CanvasAudit has optional agent_id OR user_id.""" |
| agent = AgentFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
| tenant = TenantFactory(_session=db_session) |
|
|
| |
| canvas = Canvas( |
| id=str(uuid.uuid4()), |
| tenant_id=tenant.id, |
| canvas_type="form", |
| name="Test Canvas", |
| created_by=user.id |
| ) |
| db_session.add(canvas) |
|
|
| |
| audit1 = CanvasAudit( |
| id=str(uuid.uuid4()), |
| canvas_id=canvas.id, |
| tenant_id=tenant.id, |
| agent_id=agent.id, |
| user_id=None, |
| action_type="present" |
| ) |
| db_session.add(audit1) |
|
|
| |
| audit2 = CanvasAudit( |
| id=str(uuid.uuid4()), |
| canvas_id=canvas.id, |
| tenant_id=tenant.id, |
| agent_id=None, |
| user_id=user.id, |
| action_type="submit" |
| ) |
| db_session.add(audit2) |
|
|
| db_session.commit() |
|
|
| |
| audits = db_session.query(CanvasAudit).all() |
| assert len(audits) == 2 |
|
|
| def test_episode_segment_source_types(self, db_session: Session): |
| """Test EpisodeSegment has polymorphic source_type.""" |
| agent = AgentFactory(_session=db_session) |
| tenant = TenantFactory(_session=db_session) |
|
|
| episode = Episode( |
| id=str(uuid.uuid4()), |
| agent_id=agent.id, |
| tenant_id=tenant.id, |
| task_description="Source type test", |
| maturity_at_time="INTERN", |
| outcome="success" |
| ) |
| db_session.add(episode) |
| db_session.commit() |
|
|
| |
| segment1 = EpisodeSegment( |
| id=str(uuid.uuid4()), |
| episode_id=episode.id, |
| segment_type="conversation", |
| sequence_order=1, |
| content="Chat message", |
| source_type="chat_message" |
| ) |
| segment2 = EpisodeSegment( |
| id=str(uuid.uuid4()), |
| episode_id=episode.id, |
| segment_type="execution", |
| sequence_order=2, |
| content="Agent execution", |
| source_type="agent_execution" |
| ) |
| segment3 = EpisodeSegment( |
| id=str(uuid.uuid4()), |
| episode_id=episode.id, |
| segment_type="manual", |
| sequence_order=3, |
| content="Manual entry", |
| source_type="manual" |
| ) |
|
|
| db_session.add_all([segment1, segment2, segment3]) |
| db_session.commit() |
|
|
| |
| segments = db_session.query(EpisodeSegment).filter( |
| EpisodeSegment.episode_id == episode.id |
| ).all() |
|
|
| assert segments[0].source_type == "chat_message" |
| assert segments[1].source_type == "agent_execution" |
| assert segments[2].source_type == "manual" |
|
|
|
|
| class TestOptionalRelationships: |
| """Test optional (nullable) foreign key relationships.""" |
|
|
| def test_deal_optional_transcript(self, db_session: Session): |
| """Test CallTranscript.deal_id is optional.""" |
| |
| transcript1 = CallTranscriptFactory(deal_id=None, _session=db_session) |
|
|
| |
| deal = DealFactory(_session=db_session) |
| transcript2 = CallTranscriptFactory(deal_id=deal.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| transcripts = db_session.query(CallTranscript).all() |
| assert len(transcripts) == 2 |
|
|
| def test_project_optional_contract(self, db_session: Session): |
| """Test Project.contract_id is optional.""" |
| |
| project1 = ProjectFactory(contract_id=None, _session=db_session) |
|
|
| |
| contract = ContractFactory(_session=db_session) |
| project2 = ProjectFactory(contract_id=contract.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| projects = db_session.query(Project).all() |
| assert len(projects) == 2 |
|
|
| def test_milestone_optional_invoice(self, db_session: Session): |
| """Test Milestone.invoice_id is optional.""" |
| |
| milestone1 = MilestoneFactory(invoice_id=None, _session=db_session) |
|
|
| |
| milestone2 = MilestoneFactory( |
| invoice_id="inv_123", |
| _session=db_session |
| ) |
|
|
| db_session.commit() |
|
|
| |
| milestones = db_session.query(Milestone).all() |
| assert len(milestones) == 2 |
|
|
|
|
| class TestRelationshipLoading: |
| """Test relationship loading strategies (lazy, joinedload, selectinload).""" |
|
|
| def test_lazy_loading_default(self, db_session: Session): |
| """Verify relationships use lazy loading by default.""" |
| agent = AgentFactory(_session=db_session) |
| execution = AgentExecutionFactory(agent_id=agent.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| retrieved_execution = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution.id |
| ).first() |
|
|
| |
| |
| assert retrieved_execution.agent_id == agent.id |
|
|
| def test_joinedload_optimization(self, db_session: Session): |
| """Test joinedload() for query optimization.""" |
| agent = AgentFactory(_session=db_session) |
| execution = AgentExecutionFactory(agent_id=agent.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| retrieved_execution = db_session.query(AgentExecution).options( |
| joinedload(AgentExecution.agent) |
| ).filter(AgentExecution.id == execution.id).first() |
|
|
| |
| assert retrieved_execution.agent_id == agent.id |
|
|
| def test_selectinload_for_collections(self, db_session: Session): |
| """Test selectinload() for relationship loading.""" |
| workspace = WorkspaceFactory(_session=db_session) |
| user = UserFactory(_session=db_session) |
|
|
| workspace.users.append(user) |
| db_session.commit() |
|
|
| |
| retrieved_workspace = db_session.query(Workspace).options( |
| selectinload(Workspace.users) |
| ).filter(Workspace.id == workspace.id).first() |
|
|
| assert len(retrieved_workspace.users) == 1 |
|
|
| def test_relationship_caching(self, db_session: Session): |
| """Test relationship caching within session.""" |
| agent = AgentFactory(_session=db_session) |
| execution = AgentExecutionFactory(agent_id=agent.id, _session=db_session) |
|
|
| db_session.commit() |
|
|
| |
| execution1 = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution.id |
| ).first() |
|
|
| execution2 = db_session.query(AgentExecution).filter( |
| AgentExecution.id == execution.id |
| ).first() |
|
|
| |
| assert execution1.id == execution2.id |
|
|