|
|
|
|
@@ -24,6 +24,12 @@ from pathlib import Path
|
|
|
|
|
import asyncpg
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
# All tests in this module use module-scoped async fixtures (admin_conn,
|
|
|
|
|
# seed_rls_test_data) which run on the module event loop. Without this marker,
|
|
|
|
|
# pytest-asyncio 0.23+ defaults tests to function-scoped loops, causing
|
|
|
|
|
# "Future attached to a different loop" errors on the asyncpg connections.
|
|
|
|
|
pytestmark = pytest.mark.asyncio(loop_scope="module")
|
|
|
|
|
|
|
|
|
|
_DB_HOST = os.getenv("TEST_DB_HOST", "localhost")
|
|
|
|
|
_DB_PORT = int(os.getenv("TEST_DB_PORT", "5432"))
|
|
|
|
|
_DB_NAME = os.getenv("TEST_DB_NAME", "patherly_test") # matches conftest.py
|
|
|
|
|
@@ -191,7 +197,6 @@ async def conn_no_context():
|
|
|
|
|
# trees
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_trees_account_a_cannot_see_account_b_rows(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM trees WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -199,7 +204,6 @@ async def test_trees_account_a_cannot_see_account_b_rows(conn_a):
|
|
|
|
|
assert len(rows) == 0, "Account A should not see Account B trees"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_trees_account_a_can_see_own_rows(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM trees WHERE account_id = '{ACCOUNT_A_ID}'"
|
|
|
|
|
@@ -207,7 +211,6 @@ async def test_trees_account_a_can_see_own_rows(conn_a):
|
|
|
|
|
assert len(rows) >= 1, "Account A should see its own trees"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_trees_no_context_sees_no_private_trees(conn_no_context):
|
|
|
|
|
rows = await conn_no_context.fetch(
|
|
|
|
|
"SELECT id FROM trees WHERE is_default = FALSE AND is_public = FALSE"
|
|
|
|
|
@@ -219,7 +222,6 @@ async def test_trees_no_context_sees_no_private_trees(conn_no_context):
|
|
|
|
|
# tree_tags — platform visibility
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_tree_tags_account_a_cannot_see_account_b_tags(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM tree_tags WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -227,7 +229,6 @@ async def test_tree_tags_account_a_cannot_see_account_b_tags(conn_a):
|
|
|
|
|
assert len(rows) == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_tree_tags_both_tenants_see_platform_tags(conn_a, conn_b):
|
|
|
|
|
rows_a = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM tree_tags WHERE account_id = '{PLATFORM_ACCOUNT_ID}'"
|
|
|
|
|
@@ -243,7 +244,6 @@ async def test_tree_tags_both_tenants_see_platform_tags(conn_a, conn_b):
|
|
|
|
|
# tree_categories — platform visibility
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_tree_categories_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM tree_categories WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -255,7 +255,6 @@ async def test_tree_categories_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
# step_categories — platform visibility
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_step_categories_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM step_categories WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -267,7 +266,6 @@ async def test_step_categories_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
# psa_connections — tenant-only
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_psa_connections_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM psa_connections WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -279,7 +277,6 @@ async def test_psa_connections_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
# flow_proposals — tenant-only
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_flow_proposals_account_a_cannot_see_account_b(conn_a):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM flow_proposals WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -513,7 +510,6 @@ async def session_row_ids(admin_conn):
|
|
|
|
|
# sessions
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_sessions_account_a_cannot_see_account_b_sessions(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM sessions WHERE id = '{session_row_ids['session_b']}'"
|
|
|
|
|
@@ -521,7 +517,6 @@ async def test_sessions_account_a_cannot_see_account_b_sessions(conn_a, session_
|
|
|
|
|
assert len(rows) == 0, "Account A should not see Account B sessions"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_sessions_account_a_can_see_own_sessions(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM sessions WHERE id = '{session_row_ids['session_a']}'"
|
|
|
|
|
@@ -529,7 +524,6 @@ async def test_sessions_account_a_can_see_own_sessions(conn_a, session_row_ids):
|
|
|
|
|
assert len(rows) == 1, "Account A should see its own sessions"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_sessions_no_context_sees_nothing(conn_no_context, session_row_ids):
|
|
|
|
|
rows = await conn_no_context.fetch(
|
|
|
|
|
f"SELECT id FROM sessions WHERE id IN "
|
|
|
|
|
@@ -542,7 +536,6 @@ async def test_sessions_no_context_sees_nothing(conn_no_context, session_row_ids
|
|
|
|
|
# ai_sessions
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_ai_sessions_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM ai_sessions WHERE id = '{session_row_ids['ai_session_b']}'"
|
|
|
|
|
@@ -550,7 +543,6 @@ async def test_ai_sessions_account_a_cannot_see_account_b(conn_a, session_row_id
|
|
|
|
|
assert len(rows) == 0, "Account A should not see Account B ai_sessions"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_ai_sessions_account_a_can_see_own(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM ai_sessions WHERE id = '{session_row_ids['ai_session_a']}'"
|
|
|
|
|
@@ -562,7 +554,6 @@ async def test_ai_sessions_account_a_can_see_own(conn_a, session_row_ids):
|
|
|
|
|
# session_branches
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_session_branches_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM session_branches WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -574,7 +565,6 @@ async def test_session_branches_account_a_cannot_see_account_b(conn_a, session_r
|
|
|
|
|
# session_supporting_data
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_session_supporting_data_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM session_supporting_data WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -586,7 +576,6 @@ async def test_session_supporting_data_account_a_cannot_see_account_b(conn_a, se
|
|
|
|
|
# session_resolution_outputs
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_session_resolution_outputs_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM session_resolution_outputs WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -598,7 +587,6 @@ async def test_session_resolution_outputs_account_a_cannot_see_account_b(conn_a,
|
|
|
|
|
# session_handoffs
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_session_handoffs_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM session_handoffs WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -610,7 +598,6 @@ async def test_session_handoffs_account_a_cannot_see_account_b(conn_a, session_r
|
|
|
|
|
# script_templates
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_script_templates_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM script_templates WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -622,7 +609,6 @@ async def test_script_templates_account_a_cannot_see_account_b(conn_a, session_r
|
|
|
|
|
# script_generations
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_script_generations_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM script_generations WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -634,7 +620,6 @@ async def test_script_generations_account_a_cannot_see_account_b(conn_a, session
|
|
|
|
|
# maintenance_schedules
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_maintenance_schedules_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM maintenance_schedules WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -646,7 +631,6 @@ async def test_maintenance_schedules_account_a_cannot_see_account_b(conn_a, sess
|
|
|
|
|
# psa_post_log
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_psa_post_log_account_a_cannot_see_account_b(conn_a, session_row_ids):
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
f"SELECT id FROM psa_post_log WHERE account_id = '{ACCOUNT_B_ID}'"
|
|
|
|
|
@@ -658,7 +642,6 @@ async def test_psa_post_log_account_a_cannot_see_account_b(conn_a, session_row_i
|
|
|
|
|
# step_library — visibility-aware policy
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_step_library_account_a_cannot_see_account_b_private_steps(admin_conn, conn_a):
|
|
|
|
|
"""Private/non-public steps owned by Account B must not be visible to Account A."""
|
|
|
|
|
private_step_id = str(uuid.uuid4())
|
|
|
|
|
@@ -683,7 +666,6 @@ async def test_step_library_account_a_cannot_see_account_b_private_steps(admin_c
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_step_library_account_a_can_see_account_b_public_steps(admin_conn, conn_a):
|
|
|
|
|
"""Public steps owned by Account B MUST be visible to Account A (cross-tenant visibility)."""
|
|
|
|
|
public_step_id = str(uuid.uuid4())
|
|
|
|
|
@@ -738,7 +720,6 @@ async def _get_tree_b_id(admin_conn) -> str:
|
|
|
|
|
# step_ratings
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_step_ratings_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see step ratings belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -779,7 +760,6 @@ async def test_step_ratings_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
# step_usage_log
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_step_usage_log_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see step usage logs belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -832,7 +812,6 @@ async def test_step_usage_log_account_a_cannot_see_account_b(admin_conn, conn_a)
|
|
|
|
|
# target_lists
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_target_lists_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see target lists belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -859,7 +838,6 @@ async def test_target_lists_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
# session_shares
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_session_shares_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see session shares belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -903,7 +881,6 @@ async def test_session_shares_account_a_cannot_see_account_b(admin_conn, conn_a)
|
|
|
|
|
# audit_logs
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_audit_logs_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see audit logs belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -930,7 +907,6 @@ async def test_audit_logs_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
# tree_shares
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_tree_shares_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see tree shares belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -968,7 +944,6 @@ async def test_tree_shares_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
# users
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_users_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see users belonging to Account B."""
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
@@ -977,7 +952,6 @@ async def test_users_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
assert len(rows) == 0, "Account A should not see Account B users"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_users_account_a_can_see_own(admin_conn, conn_a):
|
|
|
|
|
"""Account A must be able to see its own users."""
|
|
|
|
|
rows = await conn_a.fetch(
|
|
|
|
|
@@ -990,7 +964,6 @@ async def test_users_account_a_can_see_own(admin_conn, conn_a):
|
|
|
|
|
# script_builder_sessions
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_script_builder_sessions_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see script builder sessions belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -1019,7 +992,6 @@ async def test_script_builder_sessions_account_a_cannot_see_account_b(admin_conn
|
|
|
|
|
# ai_session_steps
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_ai_session_steps_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see ai_session_steps belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
@@ -1061,7 +1033,6 @@ async def test_ai_session_steps_account_a_cannot_see_account_b(admin_conn, conn_
|
|
|
|
|
# notifications
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_notifications_account_a_cannot_see_account_b(admin_conn, conn_a):
|
|
|
|
|
"""Account A must not see notifications belonging to Account B."""
|
|
|
|
|
user_b_id = await _get_user_b_id(admin_conn)
|
|
|
|
|
|