5d1b2396a7
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
145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
"""Import/export tests — ACs 20-21: CSV import, dry-run preview."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
|
|
from tests.conftest import ORIGIN_HEADER, login_client, seed_tenant_and_users
|
|
|
|
CSV_COMPANIES = """name,industry,phone,email,website
|
|
ImportCorp,IT,123456,import@example.com,https://import.example
|
|
TechImport,Finance,654321,tech@example.com,https://tech.example
|
|
"""
|
|
|
|
CSV_COMPANIES_INVALID = """name,industry
|
|
,IT
|
|
ValidCorp,Finance
|
|
"""
|
|
|
|
CSV_CONTACTS = """first_name,last_name,email,phone,mobile,position,department
|
|
Alice,Wonderland,alice@example.com,123,456,Manager,Sales
|
|
Bob,Builder,bob@example.com,789,012,Developer,Tech
|
|
"""
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestImportCompanies:
|
|
"""AC 20: CSV import for companies."""
|
|
|
|
async def test_import_companies_csv_returns_200(self, client: AsyncClient, db_session):
|
|
"""AC 20: POST /api/v1/import CSV + entity_type=companies -> 200 + result."""
|
|
await seed_tenant_and_users(db_session)
|
|
await login_client(client, "admin@tenanta.com")
|
|
files = {"file": ("companies.csv", CSV_COMPANIES.encode(), "text/csv")}
|
|
data = {"entity_type": "companies"}
|
|
resp = await client.post(
|
|
"/api/v1/import",
|
|
files=files,
|
|
data=data,
|
|
headers=ORIGIN_HEADER,
|
|
)
|
|
assert resp.status_code == 200
|
|
result = resp.json()
|
|
assert result["total"] == 2
|
|
assert result["valid"] == 2
|
|
assert result["invalid"] == 0
|
|
assert len(result["created"]) == 2
|
|
assert result["dry_run"] is False
|
|
|
|
async def test_import_companies_with_invalid_rows(self, client: AsyncClient, db_session):
|
|
"""Import CSV with some invalid rows — should report errors but import valid ones."""
|
|
await seed_tenant_and_users(db_session)
|
|
await login_client(client, "admin@tenanta.com")
|
|
files = {"file": ("companies.csv", CSV_COMPANIES_INVALID.encode(), "text/csv")}
|
|
data = {"entity_type": "companies"}
|
|
resp = await client.post(
|
|
"/api/v1/import",
|
|
files=files,
|
|
data=data,
|
|
headers=ORIGIN_HEADER,
|
|
)
|
|
assert resp.status_code == 200
|
|
result = resp.json()
|
|
assert result["total"] == 2
|
|
assert result["valid"] == 1
|
|
assert result["invalid"] == 1
|
|
assert len(result["errors"]) == 1
|
|
assert len(result["created"]) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestImportPreview:
|
|
"""AC 21: Dry-run preview (no DB changes)."""
|
|
|
|
async def test_import_preview_no_db_changes(self, client: AsyncClient, db_session):
|
|
"""AC 21: POST /api/v1/import/preview CSV -> 200 + dry-run (no DB changes)."""
|
|
await seed_tenant_and_users(db_session)
|
|
await login_client(client, "admin@tenanta.com")
|
|
files = {"file": ("companies.csv", CSV_COMPANIES.encode(), "text/csv")}
|
|
data = {"entity_type": "companies"}
|
|
resp = await client.post(
|
|
"/api/v1/import/preview",
|
|
files=files,
|
|
data=data,
|
|
headers=ORIGIN_HEADER,
|
|
)
|
|
assert resp.status_code == 200
|
|
result = resp.json()
|
|
assert result["total"] == 2
|
|
assert result["valid"] == 2
|
|
assert result["dry_run"] is True
|
|
assert len(result["created"]) == 0 # No actual creations
|
|
|
|
# Verify no companies were actually created
|
|
list_resp = await client.get("/api/v1/companies", headers=ORIGIN_HEADER)
|
|
names = [item["name"] for item in list_resp.json()["items"]]
|
|
assert "ImportCorp" not in names
|
|
assert "TechImport" not in names
|
|
|
|
async def test_import_preview_contacts_no_db_changes(self, client: AsyncClient, db_session):
|
|
"""Preview import for contacts — dry-run."""
|
|
await seed_tenant_and_users(db_session)
|
|
await login_client(client, "admin@tenanta.com")
|
|
files = {"file": ("contacts.csv", CSV_CONTACTS.encode(), "text/csv")}
|
|
data = {"entity_type": "contacts"}
|
|
resp = await client.post(
|
|
"/api/v1/import/preview",
|
|
files=files,
|
|
data=data,
|
|
headers=ORIGIN_HEADER,
|
|
)
|
|
assert resp.status_code == 200
|
|
result = resp.json()
|
|
assert result["total"] == 2
|
|
assert result["valid"] == 2
|
|
assert result["dry_run"] is True
|
|
assert len(result["created"]) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestImportContacts:
|
|
"""Import contacts via CSV."""
|
|
|
|
async def test_import_contacts_csv_returns_200(self, client: AsyncClient, db_session):
|
|
"""Import contacts via CSV."""
|
|
await seed_tenant_and_users(db_session)
|
|
await login_client(client, "admin@tenanta.com")
|
|
files = {"file": ("contacts.csv", CSV_CONTACTS.encode(), "text/csv")}
|
|
data = {"entity_type": "contacts"}
|
|
resp = await client.post(
|
|
"/api/v1/import",
|
|
files=files,
|
|
data=data,
|
|
headers=ORIGIN_HEADER,
|
|
)
|
|
assert resp.status_code == 200
|
|
result = resp.json()
|
|
assert result["total"] == 2
|
|
assert result["valid"] == 2
|
|
assert result["invalid"] == 0
|
|
assert len(result["created"]) == 2
|
|
# Verify contacts appear in list
|
|
list_resp = await client.get("/api/v1/contacts", headers=ORIGIN_HEADER)
|
|
assert list_resp.json()["total"] >= 2
|