"""Tests for Query Builder.""" import pytest from mes_dashboard.sql.builder import QueryBuilder class TestQueryBuilder: """Test QueryBuilder class.""" def test_add_param_condition(self): """Test adding a parameterized condition.""" builder = QueryBuilder() builder.add_param_condition("status", "RUN") assert len(builder.conditions) == 1 assert "status = :p0" in builder.conditions[0] assert builder.params["p0"] == "RUN" def test_add_param_condition_with_operator(self): """Test adding a parameterized condition with custom operator.""" builder = QueryBuilder() builder.add_param_condition("count", 10, operator=">=") assert "count >= :p0" in builder.conditions[0] assert builder.params["p0"] == 10 def test_add_in_condition(self): """Test adding an IN condition.""" builder = QueryBuilder() builder.add_in_condition("status", ["RUN", "QUEUE", "HOLD"]) assert len(builder.conditions) == 1 assert "status IN (:p0, :p1, :p2)" in builder.conditions[0] assert builder.params["p0"] == "RUN" assert builder.params["p1"] == "QUEUE" assert builder.params["p2"] == "HOLD" def test_add_in_condition_empty_list(self): """Test that empty list doesn't add condition.""" builder = QueryBuilder() builder.add_in_condition("status", []) assert len(builder.conditions) == 0 assert len(builder.params) == 0 def test_add_not_in_condition(self): """Test adding a NOT IN condition.""" builder = QueryBuilder() builder.add_not_in_condition("location", ["ATEC", "F區"]) assert len(builder.conditions) == 1 assert "location NOT IN (:p0, :p1)" in builder.conditions[0] assert builder.params["p0"] == "ATEC" assert builder.params["p1"] == "F區" def test_add_not_in_condition_with_null(self): """Test NOT IN condition allowing NULL values.""" builder = QueryBuilder() builder.add_not_in_condition("location", ["ATEC"], allow_null=True) assert len(builder.conditions) == 1 assert "(location IS NULL OR location NOT IN (:p0))" in builder.conditions[0] def test_add_like_condition_both(self): """Test LIKE condition with wildcards on both sides.""" builder = QueryBuilder() builder.add_like_condition("name", "test") assert "name LIKE :p0 ESCAPE '\\'" in builder.conditions[0] assert builder.params["p0"] == "%test%" def test_add_like_condition_start(self): """Test LIKE condition with wildcard at end only.""" builder = QueryBuilder() builder.add_like_condition("name", "prefix", position="start") assert builder.params["p0"] == "prefix%" def test_add_like_condition_end(self): """Test LIKE condition with wildcard at start only.""" builder = QueryBuilder() builder.add_like_condition("name", "suffix", position="end") assert builder.params["p0"] == "%suffix" def test_add_like_condition_escapes_wildcards(self): """Test that LIKE condition escapes SQL wildcards.""" builder = QueryBuilder() builder.add_like_condition("name", "test%value") assert builder.params["p0"] == "%test\\%value%" def test_add_like_condition_escapes_underscore(self): """Test that LIKE condition escapes underscores.""" builder = QueryBuilder() builder.add_like_condition("name", "test_value") assert builder.params["p0"] == "%test\\_value%" def test_build_with_conditions(self): """Test building SQL with multiple conditions.""" builder = QueryBuilder("SELECT * FROM t {{ WHERE_CLAUSE }}") builder.add_param_condition("status", "RUN") builder.add_in_condition("type", ["A", "B"]) sql, params = builder.build() assert "WHERE" in sql assert "status = :p0" in sql assert "type IN (:p1, :p2)" in sql assert "AND" in sql assert params["p0"] == "RUN" assert params["p1"] == "A" assert params["p2"] == "B" def test_build_without_conditions(self): """Test building SQL with no conditions.""" builder = QueryBuilder("SELECT * FROM t {{ WHERE_CLAUSE }}") sql, params = builder.build() assert "WHERE" not in sql assert "{{ WHERE_CLAUSE }}" not in sql assert params == {} def test_build_where_only(self): """Test building only the WHERE clause.""" builder = QueryBuilder() builder.add_param_condition("status", "RUN") where_clause, params = builder.build_where_only() assert where_clause.startswith("WHERE") assert "status = :p0" in where_clause def test_get_conditions_sql(self): """Test getting conditions as string.""" builder = QueryBuilder() builder.add_param_condition("a", 1) builder.add_param_condition("b", 2) conditions = builder.get_conditions_sql() assert "a = :p0 AND b = :p1" == conditions def test_reset(self): """Test resetting the builder.""" builder = QueryBuilder("SELECT * FROM t") builder.add_param_condition("status", "RUN") builder.reset() assert len(builder.conditions) == 0 assert len(builder.params) == 0 assert builder._param_counter == 0 assert builder.base_sql == "SELECT * FROM t" def test_method_chaining(self): """Test that methods support chaining.""" builder = ( QueryBuilder("SELECT * FROM t {{ WHERE_CLAUSE }}") .add_param_condition("status", "RUN") .add_in_condition("type", ["A", "B"]) .add_like_condition("name", "test") ) assert len(builder.conditions) == 3 def test_add_is_null(self): """Test adding IS NULL condition.""" builder = QueryBuilder() builder.add_is_null("deleted_at") assert "deleted_at IS NULL" in builder.conditions[0] def test_add_is_not_null(self): """Test adding IS NOT NULL condition.""" builder = QueryBuilder() builder.add_is_not_null("updated_at") assert "updated_at IS NOT NULL" in builder.conditions[0] def test_add_condition_fixed(self): """Test adding a fixed condition.""" builder = QueryBuilder() builder.add_condition("1=1") assert "1=1" in builder.conditions[0] assert len(builder.params) == 0 def test_add_or_like_conditions(self): """Test adding multiple LIKE conditions combined with OR.""" builder = QueryBuilder() builder.add_or_like_conditions("name", ["foo", "bar", "baz"]) assert len(builder.conditions) == 1 condition = builder.conditions[0] assert "name LIKE :p0 ESCAPE '\\'" in condition assert "name LIKE :p1 ESCAPE '\\'" in condition assert "name LIKE :p2 ESCAPE '\\'" in condition assert " OR " in condition assert condition.startswith("(") assert condition.endswith(")") assert builder.params["p0"] == "%foo%" assert builder.params["p1"] == "%bar%" assert builder.params["p2"] == "%baz%" def test_add_or_like_conditions_case_insensitive(self): """Test OR LIKE conditions with case insensitive matching.""" builder = QueryBuilder() builder.add_or_like_conditions("name", ["Foo", "BAR"], case_insensitive=True) condition = builder.conditions[0] assert "UPPER(name)" in condition assert builder.params["p0"] == "%FOO%" assert builder.params["p1"] == "%BAR%" def test_add_or_like_conditions_escapes_wildcards(self): """Test OR LIKE conditions escape SQL wildcards.""" builder = QueryBuilder() builder.add_or_like_conditions("name", ["test%val", "foo_bar"]) assert builder.params["p0"] == "%test\\%val%" assert builder.params["p1"] == "%foo\\_bar%" def test_add_or_like_conditions_empty_list(self): """Test that empty list doesn't add condition.""" builder = QueryBuilder() builder.add_or_like_conditions("name", []) assert len(builder.conditions) == 0 assert len(builder.params) == 0 def test_add_or_like_conditions_position(self): """Test OR LIKE conditions with different positions.""" builder = QueryBuilder() builder.add_or_like_conditions("name", ["test"], position="start") assert builder.params["p0"] == "test%"