# #region test_sql_table_extractor [C:3] [TYPE TestModule] [SEMANTICS test, sql, table, extractor, pytest] # @BRIEF Unit tests for SqlTableExtractor — T017. Verifies three-phase extraction of schema.table references from SQL+Jinja. # @LAYER Tests # @RELATION BINDS_TO -> [SqlTableExtractorModule] # @TEST_CONTRACT: extract_tables_from_sql(str) -> set[str] # @TEST_EDGE: empty_input -> returns empty set # @TEST_EDGE: no_schema_qualified_tables -> returns empty set # @TEST_EDGE: string_literal_false_positive -> filtered out by sqlparse # @TEST_FIXTURE: simple_select -> INLINE_SQL: "SELECT * FROM raw.sales" # @TEST_FIXTURE: jinja_expr -> INLINE_SQL: "SELECT * FROM {{ source('raw', 'sales') }}" from src.services.sql_table_extractor import extract_tables_from_sql class TestExtractTablesFromSql: """T017: Unit tests for SqlTableExtractor.extract_tables_from_sql().""" # #region test_simple_select [C:2] [TYPE Function] # @BRIEF Happy path: simple SELECT with FROM schema.table. def test_simple_select(self): sql = "SELECT * FROM raw.sales" result = extract_tables_from_sql(sql) assert result == {"raw.sales"} # #endregion test_simple_select # #region test_multiple_tables [C:2] [TYPE Function] # @BRIEF JOIN with multiple schema-qualified tables. def test_multiple_tables(self): sql = "SELECT a.id, b.name FROM raw.sales a JOIN raw.inventory b ON a.id = b.sale_id" result = extract_tables_from_sql(sql) assert result == {"raw.sales", "raw.inventory"} # #endregion test_multiple_tables # #region test_empty_input [C:2] [TYPE Function] # @BRIEF Empty string returns empty set. def test_empty_input(self): assert extract_tables_from_sql("") == set() assert extract_tables_from_sql(" ") == set() assert extract_tables_from_sql(None) == set() # #endregion test_empty_input # #region test_no_schema_qualified_tables [C:2] [TYPE Function] # @BRIEF Unqualified table names are NOT matched. def test_no_schema_qualified_tables(self): sql = "SELECT * FROM sales" result = extract_tables_from_sql(sql) assert result == set() # #endregion test_no_schema_qualified_tables # #region test_case_insensitive [C:2] [TYPE Function] # @BRIEF Matching is case-insensitive; result is lowercased. def test_case_insensitive(self): sql = "SELECT * FROM RAW.Sales JOIN Raw.Inventory ON ..." result = extract_tables_from_sql(sql) assert result == {"raw.sales", "raw.inventory"} # #endregion test_case_insensitive # #region test_string_literal_false_positive [C:2] [TYPE Function] # @BRIEF Date literal like '2026.04.30' must NOT be matched as schema.table. def test_string_literal_false_positive(self): sql = "SELECT * FROM raw.sales WHERE date = '2026.04.30'" result = extract_tables_from_sql(sql) assert result == {"raw.sales"} # #endregion test_string_literal_false_positive # #region test_jinja_string_concat [C:2] [TYPE Function] # @BRIEF Table reference inside Jinja {{ 'raw.sales' }} as a direct string. def test_jinja_string_concat(self): sql = "SELECT * FROM {{ 'raw.sales' }}" result = extract_tables_from_sql(sql) assert result == {"raw.sales"} # #endregion test_jinja_string_concat # #region test_jinja_set_block [C:2] [TYPE Function] # @BRIEF Table name in Jinja {% set %} block string value. def test_jinja_set_block(self): sql = """ {% set tables = ['raw.sales', 'raw.inventory'] %} SELECT * FROM raw.sales """ result = extract_tables_from_sql(sql) assert result == {"raw.sales", "raw.inventory"} # #endregion test_jinja_set_block # #region test_mixed_jinja_and_sql [C:2] [TYPE Function] # @BRIEF Mixed Jinja string and plain SQL schema.table references. def test_mixed_jinja_and_sql(self): sql = """ {% set source_table = 'raw.orders' %} SELECT * FROM raw.orders o JOIN raw.products p ON o.id = p.product_id """ result = extract_tables_from_sql(sql) assert result == {"raw.orders", "raw.products"} # #endregion test_mixed_jinja_and_sql # #region test_cte_with_schema_tables [C:2] [TYPE Function] # @BRIEF CTE body with schema-qualified table references. def test_cte_with_schema_tables(self): sql = """ WITH active_sales AS ( SELECT * FROM raw.sales WHERE amount > 100 ) SELECT * FROM active_sales """ result = extract_tables_from_sql(sql) assert result == {"raw.sales"} # #endregion test_cte_with_schema_tables # #region test_jinja_comment_excluded [C:2] [TYPE Function] # @BRIEF Jinja comment {# ... #} content should not produce table extraction (no string patterns inside comments). def test_jinja_comment_excluded(self): sql = "SELECT * FROM raw.sales {# this references raw.archived #}" result = extract_tables_from_sql(sql) assert result == {"raw.sales"} # #endregion test_jinja_comment_excluded # #endregion test_sql_table_extractor