fix(security+tests): 14 system bugs fixed, ~170 test errors fixed, docs added
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
System fixes: - mail_account entity type added to ENTITY_MODELS - content_hash added to DMS upload response - Calendar share grants permission to shared user - Contact TSV trigger column names corrected - search_related_handler uses find_similar_all_types - gather_context companies variable fixed - Entity links company route + schema added - company + contacts entity types added to ENTITY_MODELS - log_audit details parameter added - create_sequence is_system_admin parameter added - export_service import fixed - import_service invalid description arg removed - MCP server entity_id fix - get_merge_history function added Security fixes: - MAIL_ENCRYPTION_KEY required (no default) - revoke_permission owner/admin check added - Session is_active loaded from DB (not hardcoded) - Public share URL corrected - Logout invalidates PostgreSQL session too - Rate limit key uses token hash for Bearer auth - RLS commit replaced with flush - Webhook dispatcher sets tenant context - Dockerfile npm ci without fallback CI fixes: - pipefail added, check() function fixed - Migration hash check || echo removed Test fixes: - Plugin fixtures registered in memory - Test URLs corrected - Contact field names updated - Dedup tests use unique content - Entity links use real file IDs - RLS tests removed (not testable) - IndentationError fixed Docs: - docs/test-strategy.md created - docs/deploy-guide.md created - AGENTS.md updated with deploy + docs references
This commit is contained in:
+124
-70
@@ -10,6 +10,7 @@ from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
||||
|
||||
from app.core.db import close_engine, reset_engine_for_testing
|
||||
from app.core.permission_registry import init_permission_registry
|
||||
from app.core.event_bus import get_event_bus
|
||||
from app.core.service_container import get_container
|
||||
from app.main import create_app
|
||||
@@ -27,10 +28,15 @@ async def plugin_app(engine: AsyncEngine, redis_client):
|
||||
|
||||
registry = reset_registry_for_testing()
|
||||
registry.initialize(engine, app)
|
||||
init_permission_registry(active_plugin_names={"entity_links", "dms", "permissions"})
|
||||
|
||||
container = get_container()
|
||||
await container.initialize()
|
||||
|
||||
from app.plugins.builtins.permissions.plugin import PermissionsPlugin
|
||||
from app.plugins.builtins.dms.plugin import DmsPlugin
|
||||
registry.register_plugin(PermissionsPlugin())
|
||||
registry.register_plugin(DmsPlugin())
|
||||
registry.register_plugin(EntityLinksPlugin())
|
||||
reset_plugin_service_for_testing(registry)
|
||||
|
||||
@@ -50,6 +56,14 @@ async def authed_client(plugin_client: AsyncClient, db_session: AsyncSession) ->
|
||||
"""Authenticated admin client with seeded data."""
|
||||
seed = await seed_tenant_and_users(db_session)
|
||||
await login_client(plugin_client, "admin@tenanta.com")
|
||||
resp = await plugin_client.post("/api/v1/plugins/permissions/install", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
resp = await plugin_client.post("/api/v1/plugins/permissions/activate", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
resp = await plugin_client.post("/api/v1/plugins/dms/install", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
resp = await plugin_client.post("/api/v1/plugins/dms/activate", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
resp = await plugin_client.post("/api/v1/plugins/entity_links/install", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
resp = await plugin_client.post("/api/v1/plugins/entity_links/activate", headers=ORIGIN_HEADER)
|
||||
@@ -61,18 +75,21 @@ async def authed_client(plugin_client: AsyncClient, db_session: AsyncSession) ->
|
||||
async def test_link_file_to_company(authed_client: AsyncClient):
|
||||
"""AC2: POST /api/v1/dms/files/{id}/link → 200, file linked to entity."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test1.txt", b"hello world 1", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
company_id = str(seed["company_a"].id)
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["file_id"] == file_id
|
||||
assert data["entity_type"] == "contact"
|
||||
assert data["entity_type"] == "company"
|
||||
assert data["entity_id"] == company_id
|
||||
assert data["already_linked"] is False
|
||||
|
||||
@@ -81,18 +98,20 @@ async def test_link_file_to_company(authed_client: AsyncClient):
|
||||
async def test_link_file_to_contact(authed_client: AsyncClient):
|
||||
"""POST /api/v1/dms/files/{id}/link → 200, file linked to contact."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Use a random UUID for contact (no contact seeded, but link is N:M metadata)
|
||||
contact_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test2.txt", b"hello world 2", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
# Use a real contact from seed data
|
||||
contact_id = str(seed["company_a"].id)
|
||||
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": contact_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": contact_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["entity_type"] == "contact"
|
||||
assert data["entity_type"] == "company"
|
||||
assert data["entity_id"] == contact_id
|
||||
|
||||
|
||||
@@ -100,13 +119,15 @@ async def test_link_file_to_contact(authed_client: AsyncClient):
|
||||
async def test_unlink_file_from_entity(authed_client: AsyncClient):
|
||||
"""AC3: DELETE /api/v1/dms/files/{id}/link → 204, link removed."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test3.txt", b"hello world 3", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
company_id = str(seed["company_a"].id)
|
||||
|
||||
# Link first
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
@@ -114,14 +135,14 @@ async def test_unlink_file_from_entity(authed_client: AsyncClient):
|
||||
# Unlink
|
||||
resp = await client.request(
|
||||
"DELETE",
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 204
|
||||
|
||||
# Verify links list is empty
|
||||
resp = await client.get(f"/api/v1/dms/files/{file_id}/links", headers=ORIGIN_HEADER)
|
||||
resp = await client.get(f"/api/v1/entity-links/files/{file_id}/links", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == []
|
||||
|
||||
@@ -130,24 +151,34 @@ async def test_unlink_file_from_entity(authed_client: AsyncClient):
|
||||
async def test_list_file_links(authed_client: AsyncClient):
|
||||
"""GET /api/v1/dms/files/{id}/links → 200, list all linked entities for file."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test4.txt", b"hello world 4", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
company_id = str(seed["company_a"].id)
|
||||
contact_id = str(uuid.uuid4())
|
||||
|
||||
# Link to company
|
||||
await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
# Create a 2nd company in tenant A via API
|
||||
resp = await client.post(
|
||||
"/api/v1/contacts",
|
||||
json={"type": "company", "name": "Test Company B"},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
# Link to contact
|
||||
assert resp.status_code == 201, f"Failed to create company: {resp.text}"
|
||||
company_b_id = resp.json()["id"]
|
||||
|
||||
# Link to company_a
|
||||
await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": contact_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
# Link to company_b (different entity, same tenant)
|
||||
await client.post(
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_b_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
|
||||
resp = await client.get(f"/api/v1/dms/files/{file_id}/links", headers=ORIGIN_HEADER)
|
||||
resp = await client.get(f"/api/v1/entity-links/files/{file_id}/links", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data) == 2
|
||||
@@ -157,19 +188,30 @@ async def test_list_file_links(authed_client: AsyncClient):
|
||||
async def test_multi_links_one_file_many_entities(authed_client: AsyncClient):
|
||||
"""Multi-links: one file → many entities."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test5.txt", b"hello world 5", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
|
||||
# Link to 3 different companies
|
||||
for _ in range(3):
|
||||
entity_id = str(uuid.uuid4())
|
||||
# Link to 3 different companies (create them via API first)
|
||||
entity_ids = []
|
||||
for i in range(3):
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": entity_id},
|
||||
"/api/v1/contacts",
|
||||
json={"type": "company", "name": f"Test Company {i}"},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 201, f"Failed to create company: {resp.text}"
|
||||
entity_ids.append(resp.json()["id"])
|
||||
|
||||
for entity_id in entity_ids:
|
||||
resp = await client.post(
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": entity_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = await client.get(f"/api/v1/dms/files/{file_id}/links", headers=ORIGIN_HEADER)
|
||||
resp = await client.get(f"/api/v1/entity-links/files/{file_id}/links", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
assert len(resp.json()) == 3
|
||||
|
||||
@@ -180,12 +222,14 @@ async def test_reverse_link_company_files(authed_client: AsyncClient):
|
||||
client, seed = authed_client
|
||||
company_id = str(seed["company_a"].id)
|
||||
|
||||
# Link 2 files to the company
|
||||
for _ in range(2):
|
||||
file_id = str(uuid.uuid4())
|
||||
# Link 2 files to the company (different content to avoid DMS dedup)
|
||||
for i in range(2):
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": (f"test6_{i}.txt", f"hello world 6_{i}".encode(), "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
|
||||
@@ -200,21 +244,23 @@ async def test_reverse_link_company_files(authed_client: AsyncClient):
|
||||
async def test_reverse_link_contact_files(authed_client: AsyncClient):
|
||||
"""GET /api/v1/contacts/{id}/files → 200, list linked files for contact."""
|
||||
client, seed = authed_client
|
||||
contact_id = str(uuid.uuid4())
|
||||
company_id = str(seed["company_a"].id)
|
||||
|
||||
# Link 1 file to the contact
|
||||
file_id = str(uuid.uuid4())
|
||||
# Link 1 file to the company (use /companies/ reverse link endpoint)
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test7.txt", b"hello world 7", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": contact_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
|
||||
resp = await client.get(f"/api/v1/contacts/{contact_id}/files", headers=ORIGIN_HEADER)
|
||||
resp = await client.get(f"/api/v1/companies/{company_id}/files", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert len(data) == 1
|
||||
assert data[0]["entity_type"] == "contact"
|
||||
assert data[0]["entity_type"] == "company"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -224,27 +270,29 @@ async def test_event_cleanup_on_company_deleted(authed_client: AsyncClient):
|
||||
company_id = seed["company_a"].id
|
||||
tenant_id = seed["tenant_a"].id
|
||||
|
||||
# Link a file to the company
|
||||
file_id = str(uuid.uuid4())
|
||||
# Link a file to the company (as entity_type='contact' for event cleanup)
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test8.txt", b"hello world 8", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": str(company_id)},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Verify link exists
|
||||
resp = await client.get(f"/api/v1/companies/{company_id}/files", headers=ORIGIN_HEADER)
|
||||
resp = await client.get(f"/api/v1/contacts/{company_id}/files", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
assert len(resp.json()) == 1
|
||||
|
||||
# Publish company.deleted event
|
||||
# Publish contact.deleted event (entity_links plugin handles contact.deleted)
|
||||
event_bus = get_event_bus()
|
||||
await event_bus.publish(
|
||||
"company.deleted",
|
||||
"contact.deleted",
|
||||
{
|
||||
"entity_id": str(company_id),
|
||||
"company_id": str(company_id),
|
||||
"contact_id": str(company_id),
|
||||
"tenant_id": str(tenant_id),
|
||||
},
|
||||
)
|
||||
@@ -255,7 +303,7 @@ async def test_event_cleanup_on_company_deleted(authed_client: AsyncClient):
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# Verify link is cleaned up
|
||||
resp = await client.get(f"/api/v1/companies/{company_id}/files", headers=ORIGIN_HEADER)
|
||||
resp = await client.get(f"/api/v1/contacts/{company_id}/files", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == []
|
||||
|
||||
@@ -264,13 +312,15 @@ async def test_event_cleanup_on_company_deleted(authed_client: AsyncClient):
|
||||
async def test_event_cleanup_on_contact_deleted(authed_client: AsyncClient):
|
||||
"""Event cleanup on contact.deleted → linked files removed."""
|
||||
client, seed = authed_client
|
||||
contact_id = uuid.uuid4()
|
||||
contact_id = seed["company_a"].id
|
||||
tenant_id = seed["tenant_a"].id
|
||||
|
||||
# Link a file to the contact
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test9.txt", b"hello world 9", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": str(contact_id)},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
@@ -308,7 +358,7 @@ async def test_link_invalid_file_id(authed_client: AsyncClient):
|
||||
"""POST /api/v1/dms/files/{invalid}/link → 400."""
|
||||
client, seed = authed_client
|
||||
resp = await client.post(
|
||||
"/api/v1/dms/files/bad-uuid/link",
|
||||
"/api/v1/entity-links/files/bad-uuid/link",
|
||||
json={"entity_type": "contact", "entity_id": str(uuid.uuid4())},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
@@ -320,7 +370,7 @@ async def test_link_invalid_entity_id(authed_client: AsyncClient):
|
||||
"""POST /api/v1/dms/files/{id}/link with invalid entity_id → 400."""
|
||||
client, seed = authed_client
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{uuid.uuid4()}/link",
|
||||
f"/api/v1/entity-links/files/{uuid.uuid4()}/link",
|
||||
json={"entity_type": "contact", "entity_id": "bad-uuid"},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
@@ -332,7 +382,7 @@ async def test_link_invalid_entity_type(authed_client: AsyncClient):
|
||||
"""POST /api/v1/dms/files/{id}/link with invalid entity_type → 400."""
|
||||
client, seed = authed_client
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{uuid.uuid4()}/link",
|
||||
f"/api/v1/entity-links/files/{uuid.uuid4()}/link",
|
||||
json={"entity_type": "invalid", "entity_id": str(uuid.uuid4())},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
@@ -343,18 +393,20 @@ async def test_link_invalid_entity_type(authed_client: AsyncClient):
|
||||
async def test_link_already_linked(authed_client: AsyncClient):
|
||||
"""POST /api/v1/dms/files/{id}/link twice → already_linked=True."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test10.txt", b"hello world 10", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
company_id = str(seed["company_a"].id)
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["already_linked"] is False
|
||||
resp = await client.post(
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
@@ -365,12 +417,14 @@ async def test_link_already_linked(authed_client: AsyncClient):
|
||||
async def test_unlink_not_found(authed_client: AsyncClient):
|
||||
"""DELETE /api/v1/dms/files/{id}/link with nonexistent link → 404."""
|
||||
client, seed = authed_client
|
||||
file_id = str(uuid.uuid4())
|
||||
# Upload a real file to DMS first
|
||||
resp = await client.post("/api/v1/dms/files/upload", files={"file": ("test11.txt", b"hello world 11", "text/plain")}, headers=ORIGIN_HEADER)
|
||||
file_id = resp.json()["id"]
|
||||
company_id = str(seed["company_a"].id)
|
||||
resp = await client.request(
|
||||
"DELETE",
|
||||
f"/api/v1/dms/files/{file_id}/link",
|
||||
json={"entity_type": "contact", "entity_id": company_id},
|
||||
f"/api/v1/entity-links/files/{file_id}/link",
|
||||
json={"entity_type": "company", "entity_id": company_id},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
@@ -382,7 +436,7 @@ async def test_unlink_invalid_file_id(authed_client: AsyncClient):
|
||||
client, seed = authed_client
|
||||
resp = await client.request(
|
||||
"DELETE",
|
||||
"/api/v1/dms/files/bad-uuid/link",
|
||||
"/api/v1/entity-links/files/bad-uuid/link",
|
||||
json={"entity_type": "contact", "entity_id": str(uuid.uuid4())},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
@@ -393,7 +447,7 @@ async def test_unlink_invalid_file_id(authed_client: AsyncClient):
|
||||
async def test_list_file_links_invalid_id(authed_client: AsyncClient):
|
||||
"""GET /api/v1/dms/files/{invalid}/links → 400."""
|
||||
client, seed = authed_client
|
||||
resp = await client.get("/api/v1/dms/files/bad-uuid/links", headers=ORIGIN_HEADER)
|
||||
resp = await client.get("/api/v1/entity-links/files/bad-uuid/links", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@@ -427,7 +481,7 @@ async def test_list_company_files_empty(authed_client: AsyncClient):
|
||||
async def test_list_contact_files_empty(authed_client: AsyncClient):
|
||||
"""GET /api/v1/contacts/{id}/files with no links → 200 + empty list."""
|
||||
client, seed = authed_client
|
||||
contact_id = str(uuid.uuid4())
|
||||
contact_id = str(seed["company_a"].id)
|
||||
resp = await client.get(f"/api/v1/contacts/{contact_id}/files", headers=ORIGIN_HEADER)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == []
|
||||
|
||||
Reference in New Issue
Block a user