| """ |
| tests/integration/test_upload_api.py |
| FastAPI integration tests for file upload and schema endpoints. |
| Storage and schema ingestion are mocked. |
| """ |
|
|
| import io |
| import pytest |
| from unittest.mock import patch, MagicMock |
|
|
|
|
| @pytest.fixture |
| def mock_upload_and_ingest(mocker): |
| mocker.patch( |
| "api.routers.upload.upload_file", |
| return_value="https://test.supabase.co/storage/v1/object/public/user-uploads/uuid/test.csv", |
| ) |
| mocker.patch("api.routers.upload.ingest_schema", return_value=1) |
|
|
|
|
| @pytest.mark.integration |
| class TestUploadEndpoint: |
| def test_upload_csv_returns_200(self, api_client, mock_upload_and_ingest): |
| csv_data = b"id,name,amount\n1,Alice,100\n2,Bob,200\n" |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("data.csv", io.BytesIO(csv_data), "text/csv")}, |
| data={"user_id": "test-user"}, |
| ) |
| assert resp.status_code == 200 |
|
|
| def test_upload_returns_connector_id(self, api_client, mock_upload_and_ingest): |
| csv_data = b"id,name\n1,Alice\n" |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("data.csv", io.BytesIO(csv_data), "text/csv")}, |
| data={"user_id": "test-user"}, |
| ) |
| body = resp.json() |
| assert "connector_id" in body |
| assert body["connector_id"].startswith("csv:") |
|
|
| def test_upload_returns_tables_ingested(self, api_client, mock_upload_and_ingest): |
| csv_data = b"x,y\n1,2\n" |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("data.csv", io.BytesIO(csv_data), "text/csv")}, |
| data={"user_id": "test-user"}, |
| ) |
| assert resp.json()["tables_ingested"] == 1 |
|
|
| def test_unsupported_file_type_rejected(self, api_client): |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("report.pdf", io.BytesIO(b"%PDF"), "application/pdf")}, |
| data={"user_id": "test-user"}, |
| ) |
| assert resp.status_code == 415 |
|
|
| def test_sqlite_upload_accepted(self, api_client, mocker): |
| mocker.patch( |
| "api.routers.upload.upload_file", |
| return_value="https://test.supabase.co/storage/v1/object/public/user-uploads/uuid/db.sqlite", |
| ) |
| mocker.patch("api.routers.upload.ingest_schema", return_value=3) |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("db.sqlite", io.BytesIO(b"SQLite format 3\x00"), "application/x-sqlite3")}, |
| data={"user_id": "test-user"}, |
| ) |
| assert resp.status_code == 200 |
| assert resp.json()["file_type"] == "sqlite" |
|
|
| def test_file_too_large_rejected(self, api_client): |
| |
| big = b"a,b\n" + b"1,2\n" * (13 * 1024 * 1024) |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("big.csv", io.BytesIO(big), "text/csv")}, |
| data={"user_id": "test-user"}, |
| ) |
| assert resp.status_code == 413 |
|
|
| def test_extension_detection_for_csv(self, api_client, mock_upload_and_ingest): |
| """CSV detected by .csv extension even with generic content-type.""" |
| csv_data = b"a,b\n1,2\n" |
| resp = api_client.post( |
| "/api/upload/file", |
| files={"file": ("data.csv", io.BytesIO(csv_data), "application/octet-stream")}, |
| data={"user_id": "test-user"}, |
| ) |
| assert resp.status_code == 200 |
| assert resp.json()["file_type"] == "csv" |
|
|
|
|
| @pytest.mark.integration |
| class TestSchemaEndpoint: |
| def test_get_schema_returns_tables(self, api_client, in_memory_sqlite_connector, mocker): |
| mocker.patch("api.routers.schema.get_connector", return_value=in_memory_sqlite_connector) |
| resp = api_client.get("/api/schema/csv:http://fake") |
| assert resp.status_code == 200 |
| body = resp.json() |
| assert "tables" in body |
| assert len(body["tables"]) > 0 |
|
|
| def test_ingest_schema_endpoint(self, api_client, mocker): |
| mocker.patch("api.routers.schema.ingest_schema", return_value=2) |
| resp = api_client.post("/api/schema/ingest", json={"connector_id": "neon:public"}) |
| assert resp.status_code == 200 |
| assert resp.json()["tables_ingested"] == 2 |
|
|
| def test_invalid_connector_returns_400(self, api_client): |
| resp = api_client.get("/api/schema/unknown:bad_connector") |
| assert resp.status_code == 400 |
|
|