test_structured_extractors.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. from __future__ import annotations
  2. import io
  3. import pandas as pd
  4. import pytest
  5. def test_sql_extractor_returns_multi_table_candidates_with_statement_evidence():
  6. from app.core.data_research.extractors.base import ExtractionContext
  7. from app.core.data_research.extractors.sql import SqlExtractor
  8. content = b"""
  9. CREATE TABLE customer (id INTEGER PRIMARY KEY, name VARCHAR(50));
  10. CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL);
  11. """
  12. batch = SqlExtractor().extract(
  13. content,
  14. ExtractionContext(filename="schema.sql", media_type="application/sql"),
  15. )
  16. assert batch.parser_version == "sql-ddl-v1"
  17. assert len(batch.content_hash) == 64
  18. assert [item.data["table_info"]["name_en"] for item in batch.items] == [
  19. "customer",
  20. "orders",
  21. ]
  22. assert batch.items[0].evidence[0].locator == {
  23. "kind": "sql.statement",
  24. "statement": 1,
  25. }
  26. def test_csv_extractor_detects_bom_delimiter_and_column_types():
  27. from app.core.data_research.extractors.base import ExtractionContext
  28. from app.core.data_research.extractors.csv import CsvExtractor
  29. content = "\ufeffid;amount;active\n1;12.5;true\n2;9.0;false\n".encode("utf-8")
  30. batch = CsvExtractor().extract(
  31. content,
  32. ExtractionContext(filename="orders.csv", media_type="text/csv"),
  33. )
  34. table = batch.items[0]
  35. assert table.data["table_info"]["name_en"] == "orders"
  36. assert [(column["name_en"], column["data_type"]) for column in table.data["columns"]] == [
  37. ("id", "integer"),
  38. ("amount", "decimal"),
  39. ("active", "boolean"),
  40. ]
  41. assert table.data["columns"][1]["evidence_locator"] == {
  42. "kind": "csv.header",
  43. "column": 2,
  44. "name": "amount",
  45. }
  46. def test_csv_header_only_is_valid_and_empty_file_is_rejected():
  47. from app.core.data_research.extractors.base import ExtractionContext
  48. from app.core.data_research.extractors.csv import CsvExtractor
  49. context = ExtractionContext(filename="empty.csv", media_type="text/csv")
  50. batch = CsvExtractor().extract(b"id,name\n", context)
  51. assert [column["data_type"] for column in batch.items[0].data["columns"]] == [
  52. "string",
  53. "string",
  54. ]
  55. with pytest.raises(ValueError, match="header"):
  56. CsvExtractor().extract(b"", context)
  57. def test_excel_extractor_returns_one_candidate_per_sheet_with_cell_evidence():
  58. from app.core.data_research.extractors.base import ExtractionContext
  59. from app.core.data_research.extractors.excel import ExcelExtractor
  60. stream = io.BytesIO()
  61. with pd.ExcelWriter(stream, engine="openpyxl") as writer:
  62. pd.DataFrame({"customer_id": [1], "name": ["Ada"]}).to_excel(
  63. writer, sheet_name="Customers", index=False
  64. )
  65. pd.DataFrame({"order_id": [9], "total": [3.5]}).to_excel(
  66. writer, sheet_name="Orders", index=False
  67. )
  68. batch = ExcelExtractor().extract(
  69. stream.getvalue(),
  70. ExtractionContext(
  71. filename="dictionary.xlsx",
  72. media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  73. ),
  74. )
  75. assert [item.data["table_info"]["name_en"] for item in batch.items] == [
  76. "Customers",
  77. "Orders",
  78. ]
  79. assert batch.items[1].data["columns"][1]["evidence_locator"] == {
  80. "kind": "excel.cell_range",
  81. "sheet": "Orders",
  82. "range": "B1:B2",
  83. }
  84. def test_registry_selects_by_filename_and_enforces_input_limit():
  85. from app.core.data_research.extractors.base import ExtractionContext
  86. from app.core.data_research.extractors.registry import default_registry
  87. registry = default_registry()
  88. assert registry.resolve("text/csv", "orders.csv").parser_version == "csv-v1"
  89. assert registry.resolve("application/sql", "schema.sql").parser_version == "sql-ddl-v1"
  90. context = ExtractionContext(
  91. filename="orders.csv",
  92. media_type="text/csv",
  93. max_bytes=4,
  94. )
  95. with pytest.raises(ValueError, match="size limit"):
  96. registry.extract(b"id,name\n1,Ada\n", context)