156 lines
3.5 KiB
Python
156 lines
3.5 KiB
Python
"""Unit tests for the Alteryx → DuckDB expression transpiler."""
|
|
from __future__ import annotations
|
|
import sys
|
|
from pathlib import Path
|
|
import pytest
|
|
|
|
PKG = Path(__file__).parent.parent # alteryx_runner/
|
|
if str(PKG) not in sys.path:
|
|
sys.path.insert(0, str(PKG))
|
|
|
|
from expression.transpiler import transpile, UnsupportedExpressionError
|
|
|
|
|
|
def t(expr: str) -> str:
|
|
return transpile(expr)
|
|
|
|
|
|
class TestLiterals:
|
|
def test_number(self):
|
|
assert t("42") == "42"
|
|
|
|
def test_float(self):
|
|
assert t("3.14") == "3.14"
|
|
|
|
def test_string(self):
|
|
assert t('"hello"') == "'hello'"
|
|
|
|
def test_null(self):
|
|
assert t("NULL()") == "NULL"
|
|
|
|
def test_true(self):
|
|
assert t("True") == "TRUE"
|
|
|
|
def test_false(self):
|
|
assert t("False") == "FALSE"
|
|
|
|
|
|
class TestColumnRef:
|
|
def test_simple(self):
|
|
assert t("[CustomerID]") == '"CustomerID"'
|
|
|
|
def test_spaces(self):
|
|
assert t("[First Name]") == '"First Name"'
|
|
|
|
|
|
class TestOperators:
|
|
def test_eq(self):
|
|
assert t("[A] == [B]") == '("A" = "B")'
|
|
|
|
def test_neq(self):
|
|
assert t("[A] != [B]") == '("A" <> "B")'
|
|
|
|
def test_gt(self):
|
|
assert t("[Score] > 50") == '("Score" > 50)'
|
|
|
|
def test_and(self):
|
|
sql = t('[A] > 0 AND [B] < 10')
|
|
assert "AND" in sql
|
|
|
|
def test_or(self):
|
|
sql = t('[A] > 0 OR [B] < 0')
|
|
assert "OR" in sql
|
|
|
|
def test_not(self):
|
|
sql = t('NOT [IsActive]')
|
|
assert "NOT" in sql
|
|
|
|
def test_bang(self):
|
|
sql = t('![IsActive]')
|
|
assert "NOT" in sql
|
|
|
|
|
|
class TestIfThenEndif:
|
|
def test_simple(self):
|
|
sql = t('IF [Score] > 50 THEN "Pass" ELSE "Fail" ENDIF')
|
|
assert "CASE WHEN" in sql
|
|
assert "'Pass'" in sql
|
|
assert "'Fail'" in sql
|
|
|
|
def test_elseif(self):
|
|
sql = t('IF [Score] > 90 THEN "A" ELSEIF [Score] > 70 THEN "B" ELSE "C" ENDIF')
|
|
assert sql.count("WHEN") == 2
|
|
|
|
def test_no_else(self):
|
|
sql = t('IF [Active] == "Y" THEN "Yes" ENDIF')
|
|
assert "CASE WHEN" in sql
|
|
|
|
|
|
class TestIIF:
|
|
def test_iif(self):
|
|
sql = t('IIF([Score] > 50, "Pass", "Fail")')
|
|
assert "CASE WHEN" in sql
|
|
|
|
|
|
class TestIsNull:
|
|
def test_isnull_keyword(self):
|
|
sql = t('IsNull([Field])')
|
|
assert "IS NULL" in sql
|
|
|
|
def test_not_isnull(self):
|
|
sql = t('!IsNull([Field])')
|
|
assert "NOT" in sql and "IS NULL" in sql
|
|
|
|
|
|
class TestFunctions:
|
|
def test_uppercase(self):
|
|
assert t('Uppercase([Name])') == "UPPER(\"Name\")"
|
|
|
|
def test_length(self):
|
|
assert "LENGTH" in t('Length([Name])')
|
|
|
|
def test_left(self):
|
|
assert "LEFT" in t('Left([Name], 3)')
|
|
|
|
def test_round(self):
|
|
assert "ROUND" in t('Round([Score], 2)')
|
|
|
|
def test_abs(self):
|
|
assert "ABS" in t('ABS([Val])')
|
|
|
|
def test_trim(self):
|
|
assert "TRIM" in t('Trim([Name])')
|
|
|
|
def test_nested(self):
|
|
sql = t('Uppercase(Trim([Name]))')
|
|
assert "UPPER" in sql
|
|
assert "TRIM" in sql
|
|
|
|
|
|
class TestArithmetic:
|
|
def test_add(self):
|
|
sql = t('[A] + [B]')
|
|
assert "+" in sql
|
|
|
|
def test_multiply(self):
|
|
sql = t('[A] * [B]')
|
|
assert "*" in sql
|
|
|
|
def test_divide(self):
|
|
sql = t('[A] / [B]')
|
|
assert "/" in sql
|
|
|
|
def test_complex(self):
|
|
sql = t('ROUND([Spend] / [Visits], 1)')
|
|
assert "ROUND" in sql
|
|
|
|
|
|
class TestDateFunctions:
|
|
def test_datetimenow(self):
|
|
sql = t('DateTimeNow()')
|
|
assert "NOW()" in sql
|
|
|
|
def test_datetimetoday(self):
|
|
sql = t('DateTimeToday()')
|
|
assert "CURRENT_DATE" in sql
|