Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fastapi_startkit/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ omit = [
[tool.coverage.report]
show_missing = true
skip_covered = false
fail_under = 68
fail_under = 80
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
Expand Down
229 changes: 229 additions & 0 deletions fastapi_startkit/tests/masoniteorm/query/test_expressions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
"""Unit tests for ORM query expression helper classes (task #1214).

These classes carry the parsing/normalisation logic the grammars rely on
(alias splitting, direction inference, ON-clause construction), so the tests
assert on that behaviour rather than merely instantiating the objects.
"""

import warnings

import pytest

from fastapi_startkit.masoniteorm.expressions.expressions import (
AggregateExpression,
BetweenExpression,
GroupByExpression,
HavingExpression,
JoinClause,
OnClause,
OnValueClause,
OrderByExpression,
QueryExpression,
Raw,
SelectExpression,
SubGroupExpression,
SubSelectExpression,
UpdateQueryExpression,
)


class TestQueryExpression:
def test_stores_all_attributes(self):
expr = QueryExpression("age", ">", 18, value_type="value", keyword="where")
assert expr.column == "age"
assert expr.equality == ">"
assert expr.value == 18
assert expr.value_type == "value"
assert expr.keyword == "where"
assert expr.raw is False
assert expr.bindings == ()


class TestHavingExpression:
def test_infers_equality_when_only_value_given(self):
expr = HavingExpression("total", 100)
assert expr.equality == "="
assert expr.value == 100
assert expr.value_type == "having"

def test_keeps_explicit_equality_and_value(self):
expr = HavingExpression("total", ">=", 100)
assert expr.equality == ">="
assert expr.value == 100


class TestBetweenExpression:
def test_defaults(self):
expr = BetweenExpression("age", 18, 30)
assert expr.low == 18
assert expr.high == 30
assert expr.equality == "BETWEEN"
assert expr.value_type == "BETWEEN"
assert expr.value is None
assert expr.raw is False


class TestSelectExpression:
def test_splits_column_and_alias(self):
expr = SelectExpression("name as full_name")
assert expr.column == "name"
assert expr.alias == "full_name"

def test_strips_surrounding_whitespace(self):
expr = SelectExpression(" email ")
assert expr.column == "email"
assert expr.alias is None

def test_raw_column_is_not_split(self):
expr = SelectExpression("count(*) as total", raw=True)
assert expr.column == "count(*) as total"
assert expr.alias is None


class TestOrderByExpression:
def test_defaults_to_ascending(self):
expr = OrderByExpression("name")
assert expr.column == "name"
assert expr.direction == "ASC"

def test_infers_descending_from_suffix(self):
expr = OrderByExpression("created_at desc")
assert expr.column == "created_at"
assert expr.direction == "DESC"

def test_infers_ascending_from_suffix(self):
expr = OrderByExpression("name asc")
assert expr.column == "name"
assert expr.direction == "ASC"

def test_raw_disables_suffix_parsing(self):
expr = OrderByExpression("name desc", raw=True)
assert expr.column == "name desc"
assert expr.direction == "ASC"


class TestGroupByExpression:
def test_strips_column(self):
expr = GroupByExpression(" category ")
assert expr.column == "category"
assert expr.raw is False


class TestAggregateExpression:
def test_plain_column(self):
expr = AggregateExpression(aggregate="SUM", column="amount")
assert expr.aggregate == "SUM"
assert expr.column == "amount"
assert expr.alias is False

def test_splits_alias(self):
expr = AggregateExpression(aggregate="SUM", column="amount as total")
assert expr.column == "amount"
assert expr.alias == "total"


class TestRaw:
def test_stores_expression(self):
assert Raw("NOW()").expression == "NOW()"


class TestUpdateQueryExpression:
def test_defaults(self):
expr = UpdateQueryExpression("name", "bob")
assert expr.column == "name"
assert expr.value == "bob"
assert expr.update_type == "keyvalue"


class TestSubExpressions:
def test_sub_select_holds_builder(self):
sentinel = object()
assert SubSelectExpression(sentinel).builder is sentinel

def test_sub_group_default_alias(self):
sentinel = object()
expr = SubGroupExpression(sentinel)
assert expr.builder is sentinel
assert expr.alias == "group"


class TestJoinClause:
def test_parses_table_alias(self):
clause = JoinClause("users as u")
assert clause.table == "users"
assert clause.alias == "u"
assert clause.clause == "join"

def test_no_alias(self):
clause = JoinClause("users", clause="left")
assert clause.table == "users"
assert clause.alias is None
assert clause.clause == "left"

def test_on_builds_and_clause(self):
clause = JoinClause("users").on("users.id", "=", "posts.user_id")
[on] = clause.get_on_clauses()
assert isinstance(on, OnClause)
assert on.column1 == "users.id"
assert on.column2 == "posts.user_id"
assert on.operator == "and"

def test_or_on_builds_or_clause(self):
clause = JoinClause("users").or_on("a", "=", "b")
assert clause.get_on_clauses()[0].operator == "or"

def test_chaining_returns_self(self):
clause = JoinClause("users")
assert clause.on("a", "=", "b") is clause

def test_on_value_with_operator_and_value(self):
clause = JoinClause("users").on_value("age", ">", 18)
on = clause.get_on_clauses()[0]
assert isinstance(on, OnValueClause)
assert on.equality == ">"
assert on.value == 18
assert on.operator == "and"

def test_on_value_with_single_value_defaults_operator(self):
clause = JoinClause("users").on_value("active", 1)
on = clause.get_on_clauses()[0]
assert on.equality == "="
assert on.value == 1

def test_or_on_value_sets_or_operator(self):
clause = JoinClause("users").or_on_value("age", ">", 18)
assert clause.get_on_clauses()[0].operator == "or"

def test_on_null(self):
clause = JoinClause("users").on_null("deleted_at")
on = clause.get_on_clauses()[0]
assert on.value_type == "NULL"
assert on.value is None

def test_on_not_null(self):
clause = JoinClause("users").on_not_null("verified_at")
on = clause.get_on_clauses()[0]
assert on.value_type == "NOT NULL"
assert on.value is True

def test_or_on_null(self):
clause = JoinClause("users").or_on_null("deleted_at")
assert clause.get_on_clauses()[0].operator == "or"

def test_or_on_not_null(self):
clause = JoinClause("users").or_on_not_null("verified_at")
on = clause.get_on_clauses()[0]
assert on.operator == "or"
assert on.value_type == "NOT NULL"

def test_invalid_operator_raises(self):
with pytest.raises(ValueError):
JoinClause("users").on_value("age", "bogus", 18)

def test_where_is_deprecated_alias_of_on_value(self):
clause = JoinClause("users")
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
result = clause.where("age", ">", 18)
assert result is clause
assert clause.get_on_clauses()[0].equality == ">"
103 changes: 103 additions & 0 deletions fastapi_startkit/tests/masoniteorm/schema/test_column.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Unit tests for the schema Column builder (task #1214)."""

from fastapi_startkit.masoniteorm.schema.Column import Column


def make_column(**kwargs):
defaults = {"name": "email", "column_type": "string"}
defaults.update(kwargs)
return Column(**defaults)


class TestColumnDefaults:
def test_initial_state(self):
col = make_column(length=255)
assert col.name == "email"
assert col.column_type == "string"
assert col.length == 255
assert col.values == []
assert col.is_null is False
assert col.primary is False
assert col.comment is None

def test_values_defaults_to_empty_list(self):
assert make_column(values=None).values == []
assert make_column(values=["a", "b"]).values == ["a", "b"]


class TestNullability:
def test_nullable_sets_flag_and_returns_self(self):
col = make_column()
assert col.nullable() is col
assert col.is_null is True

def test_not_nullable_clears_flag(self):
col = make_column(nullable=True)
assert col.not_nullable() is col
assert col.is_null is False


class TestSignedness:
def test_signed(self):
col = make_column()
assert col.signed() is col
assert col._signed == "signed"

def test_unsigned(self):
col = make_column()
assert col.unsigned() is col
assert col._signed == "unsigned"


class TestPrimaryAndComment:
def test_set_as_primary(self):
col = make_column()
col.set_as_primary()
assert col.primary is True

def test_add_comment_returns_self(self):
col = make_column()
assert col.add_comment("the user email") is col
assert col.comment == "the user email"


class TestRenameAndPositioning:
def test_rename_records_old_column(self):
col = make_column()
assert col.rename("old_email") is col
assert col.old_column == "old_email"

def test_after_sets_and_get_after_column_reads(self):
col = make_column()
assert col.after("created_at") is col
assert col.get_after_column() == "created_at"

def test_get_after_column_defaults_to_none(self):
assert make_column().get_after_column() is None


class TestChangeAndCurrent:
def test_change_marks_modify_action(self):
col = make_column()
assert col.change() is col
assert col._action == "modify"

def test_use_current_sets_default_current(self):
col = make_column()
assert col.use_current() is col
assert col.default == "current"


class TestDefaultValue:
def test_default_value_is_stored_as_attribute_from_constructor(self):
col = make_column(default="anon", default_is_raw=True)
assert col.default == "anon"
assert col.default_is_raw is True

def test_default_method_is_reachable_on_the_class(self):
# The constructor assigns ``self.default`` as an attribute, shadowing the
# method on instances; the method is still invocable via the class.
col = make_column()
assert Column.default(col, "seed", raw=True) is col
assert col.default == "seed"
assert col.default_is_raw is True
Loading
Loading