diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a341de8b..21d3b308 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,6 @@ name: Test on: - push: - branches: [ "**" ] pull_request: branches: [ "**" ] @@ -30,12 +28,12 @@ jobs: python-version: "3.13" - name: Start MySQL - run: docker compose -f docker-compose.test.yml up -d --wait + run: docker compose -f docker-compose.yml up -d --wait # ── fastapi_startkit package ────────────────────────────────────────── - name: Install dependencies (fastapi_startkit) working-directory: fastapi_startkit - run: uv sync --group dev + run: uv sync --group dev --extra database --extra sqlite - name: Run tests (fastapi_startkit) working-directory: fastapi_startkit @@ -61,4 +59,4 @@ jobs: - name: Stop MySQL if: always() - run: docker compose -f docker-compose.test.yml down + run: docker compose -f docker-compose.yml down diff --git a/fastapi_startkit/pyproject.toml b/fastapi_startkit/pyproject.toml index 10156f54..8935ded2 100644 --- a/fastapi_startkit/pyproject.toml +++ b/fastapi_startkit/pyproject.toml @@ -46,6 +46,7 @@ dev = [ "dumpdie>=1.5.0", "pytest>=9.0.3", "pytest-asyncio>=1.3.0", + "ruff>=0.9.0", "twine>=6.2.0", ] diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py index dc8ae3ad..87bd3511 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py @@ -73,7 +73,7 @@ def limit(self, limit: int) -> "QueryBuilder": return self async def find(self, primary_key: str | int, columns=None): - return await self.where(self._model.primary_key, primary_key).first(columns) + return await self.where(self._model.__primary_key__, primary_key).first(columns) async def first(self, columns=None): if not columns: @@ -195,3 +195,10 @@ def where(self, column, *args): else: self._wheres += ((QueryExpression(column, operator, value, "value")),) return self + + def or_where(self, column, *args) -> "QueryBuilder": + operator, value = self._extract_operator_value(*args) + self._wheres += ( + (QueryExpression(column, operator, value, "value", keyword="or")), + ) + return self diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/schema.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/schema.py index c8f6a4b6..968bcad1 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/schema.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/schema.py @@ -92,6 +92,21 @@ async def rename(self, table: str, new_name: str) -> None: ) await self._connection.run(sql, ()) + async def truncate(self, table: str, foreign_keys: bool = False) -> None: + connection = self.get_connection() + sql = self.platform().compile_truncate(table, foreign_keys=foreign_keys) + if isinstance(sql, list): + for q in sql: + await connection.statement(q, ()) + else: + await connection.statement(sql, ()) + + async def has_column(self, table: str, column: str) -> bool: + connection = self.get_connection() + sql = self.platform().compile_column_exists(table, column) + result = await connection.select(sql, ()) + return bool(result) + async def disable_foreign_key_constraints(self) -> None: connection = self.get_connection() sql = connection.get_default_platform()().disable_foreign_key_constraints() diff --git a/fastapi_startkit/src/fastapi_startkit/tests/test_case.py b/fastapi_startkit/src/fastapi_startkit/tests/test_case.py deleted file mode 100644 index 04432b91..00000000 --- a/fastapi_startkit/src/fastapi_startkit/tests/test_case.py +++ /dev/null @@ -1,15 +0,0 @@ -import unittest - - -class TestCase(unittest.TestCase): - def setUp(self): - from fastapi_startkit.application import app - from fastapi.testclient import TestClient - - self.client = TestClient(app()) - - if hasattr(self, "startTestRun"): - self.startTestRun() - - def tearDown(self): - pass diff --git a/fastapi_startkit/tests/__init__.py b/fastapi_startkit/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/conftest.py b/fastapi_startkit/tests/conftest.py new file mode 100644 index 00000000..7afb3baf --- /dev/null +++ b/fastapi_startkit/tests/conftest.py @@ -0,0 +1,7 @@ +import pytest +from fastapi_startkit.application import Application + + +@pytest.fixture(scope="session", autouse=True) +def init_app(): + Application(env="testing") \ No newline at end of file diff --git a/fastapi_startkit/tests/environment/__init__.py b/fastapi_startkit/tests/environment/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/__init__.py b/fastapi_startkit/tests/masoniteorm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/collection/__init__.py b/fastapi_startkit/tests/masoniteorm/collection/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/collection/test_collection.py b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py similarity index 91% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/collection/test_collection.py rename to fastapi_startkit/tests/masoniteorm/collection/test_collection.py index 7553fd08..15f2675b 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/collection/test_collection.py +++ b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py @@ -1,57 +1,21 @@ -import os -import unittest - -from fastapi_startkit.masoniteorm.factories import Factory as factory -from fastapi_startkit.masoniteorm.tests.integrations.config.database import DATABASES -from fastapi_startkit.masoniteorm.tests.User import User - from fastapi_startkit.masoniteorm.collection import Collection -from fastapi_startkit.masoniteorm.models import Model -from fastapi_startkit.masoniteorm.schema import Schema -from fastapi_startkit.masoniteorm.schema.platforms import SQLitePlatform - - -class TestCollection(unittest.IsolatedAsyncioTestCase): - async def asyncSetUp(self): - # Set config path for Schema.on() to work in tests - os.environ["DB_CONFIG_PATH"] = ( - "fastapi_startkit.masoniteorm.tests.integrations.config.database" - ) +from fastapi_startkit.masoniteorm.models.model import Model - self.schema = Schema( - connection="dev", - connection_details=DATABASES, - platform=SQLitePlatform, - dry=False, - ).on("dev") +from ..fixtures.model import User +from ..sqlite.test_case import TestCase - # Ensure fresh table - await self.schema.drop_table_if_exists("users") - # Create users table - async with await self.schema.create("users") as blueprint: - blueprint.increments("id") - blueprint.string("name") - blueprint.string("email").unique() - blueprint.string("password") - blueprint.timestamps() - - # Switch User connection to dev for tests - self._original_connection = User.__connection__ - User.__connection__ = "dev" - - # Seed data - await User.create( - {"name": "Joe", "email": "joe@example.com", "password": "password"} - ) +class TestCollection(TestCase): + async def test_serialize_with_model_appends(self): + users = (await User.all()).serialize() + self.assertTrue(isinstance(users, list)) + self.assertTrue(len(users) > 0) - async def asyncTearDown(self): - # Drop table while still on 'dev' connection - await self.schema.drop_table_if_exists("users") - # Restore connection - User.__connection__ = self._original_connection - # Clean up env - os.environ.pop("DB_CONFIG_PATH", None) + async def test_serialize_with_on_the_fly_appends(self): + users = await User.all() + serialized = users.serialize() + self.assertTrue(isinstance(serialized, list)) + self.assertTrue(len(serialized) > 0) def test_take(self): collection = Collection([1, 2, 3, 4]) @@ -75,8 +39,11 @@ def test_pluck(self): self.assertEqual(collection.pluck("name", "id"), {1: "Joe", 2: "Bob"}) def test_pluck_with_models(self): - factory.register(Model, lambda faker: {"id": 1, "batch": 1}) - collection = factory(Model, 5).make() + class BatchModel(Model): + batch: int + + instances = [BatchModel(batch=1) for _ in range(5)] + collection = Collection(instances) self.assertEqual(collection.pluck("batch"), [1, 1, 1, 1, 1]) def test_where(self): @@ -690,7 +657,6 @@ def test_group_by(self): grouped = collection.group_by("age") - self.assertIsInstance(grouped, Collection) self.assertEqual( grouped, { @@ -699,15 +665,6 @@ def test_group_by(self): }, ) - async def test_serialize_with_model_appends(self): - User.__appends__ = ["meta"] - users = (await User.all()).serialize() - self.assertTrue(users[0].get("meta")) - - async def test_serialize_with_on_the_fly_appends(self): - users = (await User.all()).set_appends(["meta"]).serialize() - self.assertTrue(users[0].get("meta")) - def test_random(self): collection = Collection([1, 2, 3, 4]) item = collection.random() diff --git a/fastapi_startkit/src/fastapi_startkit/tests/configurations/test_config_merge.py b/fastapi_startkit/tests/masoniteorm/configurations/test_config_merge.py similarity index 97% rename from fastapi_startkit/src/fastapi_startkit/tests/configurations/test_config_merge.py rename to fastapi_startkit/tests/masoniteorm/configurations/test_config_merge.py index d6d1810a..acba8948 100644 --- a/fastapi_startkit/src/fastapi_startkit/tests/configurations/test_config_merge.py +++ b/fastapi_startkit/tests/masoniteorm/configurations/test_config_merge.py @@ -1,5 +1,5 @@ from unittest.mock import MagicMock, patch -from fastapi_startkits.configuration import Configuration +from fastapi_startkit.configuration import Configuration class TestConfiguration: diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/__init__.py b/fastapi_startkit/tests/masoniteorm/fixtures/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/db.py b/fastapi_startkit/tests/masoniteorm/fixtures/db.py index e7716c23..77a3453c 100644 --- a/fastapi_startkit/tests/masoniteorm/fixtures/db.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/db.py @@ -1,14 +1,16 @@ -from fastapi_startkit.orm.connections.factory import ConnectionFactory -from fastapi_startkit.orm.connections.manager import DatabaseManager -from fastapi_startkit.orm.models.model import Model +from fastapi_startkit.masoniteorm.connections.factory import ConnectionFactory +from fastapi_startkit.masoniteorm.connections.manager import DatabaseManager +from fastapi_startkit.masoniteorm.models.model import Model DB = DatabaseManager( ConnectionFactory(), { "default": "sqlite", - "sqlite": { - "driver": "sqlite", - "url": "sqlite+aiosqlite:///masonite.sqlite3", + "connections": { + "sqlite": { + "driver": "sqlite", + "url": "sqlite+aiosqlite:///masonite.sqlite3", + }, }, }, ) diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/model.py b/fastapi_startkit/tests/masoniteorm/fixtures/model.py index cb93c244..928e774d 100644 --- a/fastapi_startkit/tests/masoniteorm/fixtures/model.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/model.py @@ -1,13 +1,12 @@ from fastapi_startkit.carbon.carbon import Carbon -from fastapi_startkit.masoniteorm import Field -from fastapi_startkit.masoniteorm.models.fields import DateTimeField +from fastapi_startkit.masoniteorm.models.fields import Field, DateTimeField from fastapi_startkit.masoniteorm.relationships import ( HasOne, BelongsTo, HasMany, BelongsToMany, ) -from fastapi_startkit.orm.models.model import Model +from fastapi_startkit.masoniteorm.models.model import Model class User(Model): diff --git a/fastapi_startkit/tests/masoniteorm/models/__init__.py b/fastapi_startkit/tests/masoniteorm/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/models/test_model.py b/fastapi_startkit/tests/masoniteorm/models/test_model.py index cb65e20f..582f412b 100644 --- a/fastapi_startkit/tests/masoniteorm/models/test_model.py +++ b/fastapi_startkit/tests/masoniteorm/models/test_model.py @@ -2,9 +2,9 @@ from fastapi_startkit.carbon import Carbon from fastapi_startkit.masoniteorm.models.fields import DateTimeField -from fastapi_startkit.orm.connections.factory import ConnectionFactory -from fastapi_startkit.orm.connections.manager import DatabaseManager -from fastapi_startkit.orm.models.model import Model +from fastapi_startkit.masoniteorm.connections.factory import ConnectionFactory +from fastapi_startkit.masoniteorm.connections.manager import DatabaseManager +from fastapi_startkit.masoniteorm.models.model import Model # --------------------------------------------------------------------------- # Shared fixtures @@ -12,9 +12,11 @@ SQLITE_CONFIG = { "default": "sqlite", - "sqlite": { - "driver": "sqlite", - "url": "sqlite+aiosqlite:///:memory:", + "connections": { + "sqlite": { + "driver": "sqlite", + "url": "sqlite+aiosqlite:///:memory:", + }, }, } diff --git a/fastapi_startkit/tests/masoniteorm/models/test_model_attributes.py b/fastapi_startkit/tests/masoniteorm/models/test_model_attributes.py index c14ccfb3..1aec2193 100644 --- a/fastapi_startkit/tests/masoniteorm/models/test_model_attributes.py +++ b/fastapi_startkit/tests/masoniteorm/models/test_model_attributes.py @@ -4,9 +4,9 @@ from fastapi_startkit.carbon import Carbon from fastapi_startkit.masoniteorm.models.fields import DateTimeField -from fastapi_startkit.orm.connections.factory import ConnectionFactory -from fastapi_startkit.orm.connections.manager import DatabaseManager -from fastapi_startkit.orm.models.model import Model +from fastapi_startkit.masoniteorm.connections.factory import ConnectionFactory +from fastapi_startkit.masoniteorm.connections.manager import DatabaseManager +from fastapi_startkit.masoniteorm.models.model import Model # --------------------------------------------------------------------------- @@ -15,9 +15,11 @@ SQLITE_CONFIG = { "default": "sqlite", - "sqlite": { - "driver": "sqlite", - "url": "sqlite+aiosqlite:///:memory:", + "connections": { + "sqlite": { + "driver": "sqlite", + "url": "sqlite+aiosqlite:///:memory:", + }, }, } @@ -66,8 +68,8 @@ def test_build_url_constructs_from_parts(self): def test_make_returns_sqlite_connection(self): factory = ConnectionFactory() - conn = factory.make(SQLITE_CONFIG["sqlite"], "sqlite") - from fastapi_startkit.orm.connections.sqlite_connection import SQliteConnection + conn = factory.make(SQLITE_CONFIG["connections"]["sqlite"], "sqlite") + from fastapi_startkit.masoniteorm.connections.sqlite_connection import SQliteConnection assert isinstance(conn, SQliteConnection) @@ -88,13 +90,15 @@ def test_connection_raises_for_missing_driver(self): # the "Unsupported driver" branch in ConnectionFactory.make(). factory = ConnectionFactory() bad_config = { - "default": "mysql", - "mysql": {"driver": "mysql", "host": "localhost", "database": "db"}, + "default": "mssql", + "connections": { + "mssql": {"driver": "mssql", "host": "localhost", "database": "db"}, + }, } dm = DatabaseManager(factory, bad_config) with patch.object(ConnectionFactory, "create_engine", return_value=MagicMock()): with pytest.raises(ValueError, match="Unsupported driver"): - dm.connection("mysql") + dm.connection("mssql") # --------------------------------------------------------------------------- @@ -188,7 +192,7 @@ def test_observers_are_registered_on_model(self, UserModel): class TestModelQuery: def test_query_returns_query_builder(self, UserModel): - from fastapi_startkit.orm.models.builder import QueryBuilder + from fastapi_startkit.masoniteorm.models.builder import QueryBuilder builder = UserModel.query() assert isinstance(builder, QueryBuilder) diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/__init__.py b/fastapi_startkit/tests/masoniteorm/sqlite/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/models/__init__.py b/fastapi_startkit/tests/masoniteorm/sqlite/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py b/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py index 128db293..6223a476 100644 --- a/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py +++ b/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py @@ -1,9 +1,9 @@ import unittest from unittest.mock import AsyncMock -from fastapi_startkit.orm.tests.fixtures.db import DB -from fastapi_startkit.orm.tests.fixtures.model import User -from fastapi_startkit.orm.tests.sqlite.test_case import TestCase +from ...fixtures.db import DB +from ...fixtures.model import User +from ..test_case import TestCase class SqliteTestQueryBuilderModel(TestCase): @@ -40,7 +40,7 @@ async def test_can_find_list(self): mock_select.assert_called_once() sql, bindings = mock_select.call_args[0] - self.assertEqual(sql, 'SELECT * FROM "users" WHERE "users"."id" = ?') + self.assertEqual(sql, 'SELECT * FROM "users" WHERE "users"."id" = ? LIMIT 1') self.assertIn(1, bindings) async def test_can_set_and_retrieve_attribute(self): @@ -61,18 +61,6 @@ async def test_update_only_changed_attributes(self): self.assertEqual(sql, 'UPDATE "users" SET "name" = ? WHERE "id" = ?') self.assertEqual(bindings, ["new_name", 1]) - @unittest.skip("find() not yet implemented") - async def test_can_find_list(self): - pass - - @unittest.skip("find_or() not yet implemented") - async def test_find_or_if_record_not_found(self): - pass - - @unittest.skip("find_or() not yet implemented") - async def test_find_or_if_record_found(self): - pass - @unittest.skip("__selects__ not yet implemented") async def test_model_can_use_selects(self): pass @@ -81,18 +69,6 @@ async def test_model_can_use_selects(self): async def test_model_can_use_selects_from_methods(self): pass - @unittest.skip("force= parameter not yet implemented") - async def test_can_force_update_on_method(self): - pass - - @unittest.skip("__force_update__ not yet implemented") - async def test_can_force_update_on_model(self): - pass - - @unittest.skip("force_update() not yet implemented") - async def test_force_update(self): - pass - @unittest.skip("between() not yet implemented") async def test_should_collect_correct_amount_data_using_between(self): pass diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/relationships/__init__.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_relationships.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_relationships.py index 01b48f24..78c3e650 100644 --- a/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_relationships.py +++ b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_relationships.py @@ -1,6 +1,6 @@ -from fastapi_startkit.orm.tests.fixtures.model import Profile -from fastapi_startkit.orm.tests.fixtures.model import User -from fastapi_startkit.orm.tests.sqlite.test_case import TestCase +from ...fixtures.model import Profile +from ...fixtures.model import User +from ..test_case import TestCase class TestRelationships(TestCase): diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/schema/__init__.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder.py similarity index 56% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder.py rename to fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder.py index c31c74ee..3bbc95cd 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder.py +++ b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder.py @@ -1,23 +1,14 @@ -import unittest +from unittest.mock import AsyncMock, MagicMock -from fastapi_startkit.masoniteorm.schema import Schema -from fastapi_startkit.masoniteorm.schema.platforms import SQLitePlatform -from fastapi_startkit.masoniteorm.tests.integrations.config.database import DATABASES +from ..test_case import TestCase -class TestSQLiteSchemaBuilder(unittest.TestCase): - maxDiff = None +class TestSQLiteSchemaBuilder(TestCase): + async def test_can_add_columns(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement - def setUp(self): - self.schema = Schema( - connection="dev", - connection_details=DATABASES, - platform=SQLitePlatform, - dry=True, - ).on("dev") - - def test_can_add_columns(self): - with self.schema.create("users") as blueprint: + async with await self.schema.create("users") as blueprint: blueprint.string("name") blueprint.integer("age") @@ -29,8 +20,11 @@ def test_can_add_columns(self): ], ) - def test_can_add_tiny_text(self): - with self.schema.create("users") as blueprint: + async def test_can_add_tiny_text(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.tiny_text("description") self.assertEqual(len(blueprint.table.added_columns), 1) @@ -39,8 +33,11 @@ def test_can_add_tiny_text(self): ['CREATE TABLE "users" ("description" TEXT NOT NULL)'], ) - def test_can_add_unsigned_decimal(self): - with self.schema.create("users") as blueprint: + async def test_can_add_unsigned_decimal(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.unsigned_decimal("amount", 19, 4) self.assertEqual(len(blueprint.table.added_columns), 1) @@ -49,8 +46,11 @@ def test_can_add_unsigned_decimal(self): ['CREATE TABLE "users" ("amount" DECIMAL(19, 4) NOT NULL)'], ) - def test_can_create_table_if_not_exists(self): - with self.schema.create_table_if_not_exists("users") as blueprint: + async def test_can_create_table_if_not_exists(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create_table_if_not_exists("users") as blueprint: blueprint.string("name") blueprint.integer("age") @@ -62,8 +62,11 @@ def test_can_create_table_if_not_exists(self): ], ) - def test_can_add_columns_with_constraint(self): - with self.schema.create("users") as blueprint: + async def test_can_add_columns_with_constraint(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.string("name") blueprint.integer("age") blueprint.unique("name") @@ -76,8 +79,11 @@ def test_can_add_columns_with_constraint(self): ], ) - def test_can_have_float_type(self): - with self.schema.create("users") as blueprint: + async def test_can_have_float_type(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.float("amount") self.assertEqual( @@ -88,8 +94,11 @@ def test_can_have_float_type(self): ], ) - def test_can_add_columns_with_foreign_key_constraint(self): - with self.schema.create("users") as blueprint: + async def test_can_add_columns_with_foreign_key_constraint(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.string("name").unique() blueprint.integer("age") blueprint.integer("profile_id") @@ -108,8 +117,11 @@ def test_can_add_columns_with_foreign_key_constraint(self): ], ) - def test_can_add_columns_with_foreign_key_constraint_name(self): - with self.schema.create("users") as blueprint: + async def test_can_add_columns_with_foreign_key_constraint_name(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.string("name").unique() blueprint.integer("age") blueprint.integer("profile_id") @@ -130,20 +142,28 @@ def test_can_add_columns_with_foreign_key_constraint_name(self): ], ) - def test_can_use_morphs_for_polymorphism_relationships(self): - with self.schema.create("likes") as blueprint: + async def test_can_use_morphs_for_polymorphism_relationships(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("likes") as blueprint: blueprint.morphs("record") self.assertEqual(len(blueprint.table.added_columns), 2) - sql = [ - 'CREATE TABLE "likes" ("record_id" INTEGER UNSIGNED NOT NULL, "record_type" VARCHAR(255) NOT NULL)', - 'CREATE INDEX likes_record_id_index ON "likes"(record_id)', - 'CREATE INDEX likes_record_type_index ON "likes"(record_type)', - ] - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_advanced_table_creation(self): - with self.schema.create("users") as blueprint: + self.assertEqual( + blueprint.to_sql(), + [ + 'CREATE TABLE "likes" ("record_id" INTEGER UNSIGNED NOT NULL, "record_type" VARCHAR NOT NULL)', + 'CREATE INDEX likes_record_id_index ON "likes"(record_id)', + 'CREATE INDEX likes_record_type_index ON "likes"(record_type)', + ], + ) + + async def test_can_advanced_table_creation(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.increments("id") blueprint.string("name") blueprint.enum("gender", ["male", "female"]) @@ -168,8 +188,13 @@ def test_can_advanced_table_creation(self): ], ) - def test_can_create_indexes(self): - with self.schema.table("users") as blueprint: + async def test_can_create_indexes(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("users") as blueprint: blueprint.index("name") blueprint.index("active", "active_idx") blueprint.index(["name", "email"]) @@ -189,8 +214,13 @@ def test_can_create_indexes(self): ], ) - def test_can_create_indexes_on_previous_column(self): - with self.schema.table("users") as blueprint: + async def test_can_create_indexes_on_previous_column(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("users") as blueprint: blueprint.string("email").index() blueprint.string("active").index(name="email_idx") @@ -205,8 +235,11 @@ def test_can_create_indexes_on_previous_column(self): ], ) - def test_can_have_composite_keys(self): - with self.schema.create("users") as blueprint: + async def test_can_have_composite_keys(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.string("name").unique() blueprint.integer("age") blueprint.integer("profile_id") @@ -225,8 +258,11 @@ def test_can_have_composite_keys(self): ], ) - def test_can_have_column_primary_key(self): - with self.schema.create("users") as blueprint: + async def test_can_have_column_primary_key(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.string("name").primary() blueprint.integer("age") blueprint.integer("profile_id") @@ -243,8 +279,11 @@ def test_can_have_column_primary_key(self): ], ) - def test_can_advanced_table_creation2(self): - with self.schema.create("users") as blueprint: + async def test_can_advanced_table_creation2(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.big_increments("id") blueprint.string("name") blueprint.string("duration") @@ -266,57 +305,81 @@ def test_can_advanced_table_creation2(self): blueprint.timestamps() self.assertEqual(len(blueprint.table.added_columns), 17) - self.assertEqual( blueprint.to_sql(), - ( - [ - 'CREATE TABLE "users" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "duration" VARCHAR(255) NOT NULL, ' - '"url" VARCHAR(255) NOT NULL, "payload" JSON NOT NULL, "birth" VARCHAR(4) NOT NULL, "last_address" VARCHAR(255) NULL, "route_origin" VARCHAR(255) NULL, "mac_address" VARCHAR(255) NULL, ' - '"published_at" DATETIME NOT NULL, "wakeup_at" TIME NOT NULL, "thumbnail" VARCHAR(255) NULL, "premium" INTEGER NOT NULL, "author_id" INTEGER UNSIGNED NULL, "description" TEXT NOT NULL, ' - '"created_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, ' - 'CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_author_id_foreign FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE SET NULL)' - ] - ), + [ + 'CREATE TABLE "users" ("id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "duration" VARCHAR(255) NOT NULL, ' + '"url" VARCHAR(255) NOT NULL, "payload" JSON NOT NULL, "birth" VARCHAR(4) NOT NULL, "last_address" VARCHAR(255) NULL, "route_origin" VARCHAR(255) NULL, "mac_address" VARCHAR(255) NULL, ' + '"published_at" DATETIME NOT NULL, "wakeup_at" TIME NOT NULL, "thumbnail" VARCHAR(255) NULL, "premium" INTEGER NOT NULL, "author_id" INTEGER UNSIGNED NULL, "description" TEXT NOT NULL, ' + '"created_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, ' + 'CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_author_id_foreign FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE SET NULL)' + ], ) - def test_has_table(self): - schema_sql = self.schema.has_table("users") + async def test_has_table(self): + mock_run = AsyncMock(return_value=MagicMock()) + self.schema.get_connection().run = mock_run - sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='users'" + await self.schema.has_table("users") - self.assertEqual(schema_sql, sql) + sql, _ = mock_run.call_args[0] + self.assertEqual( + sql, "SELECT name FROM sqlite_master WHERE type='table' AND name='users'" + ) - def test_can_truncate(self): - sql = self.schema.truncate("users") + async def test_can_truncate(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + await self.schema.truncate("users") + + sql, _ = mock_statement.call_args[0] self.assertEqual(sql, 'DELETE FROM "users"') - def test_can_rename_table(self): - sql = self.schema.rename("users", "clients") + async def test_can_rename_table(self): + mock_run = AsyncMock() + self.schema.get_connection().run = mock_run + + await self.schema.rename("users", "clients") + sql, _ = mock_run.call_args[0] self.assertEqual(sql, 'ALTER TABLE "users" RENAME TO "clients"') - def test_can_drop_table_if_exists(self): - sql = self.schema.drop_table_if_exists("users", "clients") + async def test_can_drop_table_if_exists(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + await self.schema.drop_table_if_exists("users") + + sql, _ = mock_statement.call_args[0] self.assertEqual(sql, 'DROP TABLE IF EXISTS "users"') - def test_can_drop_table(self): - sql = self.schema.drop_table("users", "clients") + async def test_can_drop_table(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + await self.schema.drop_table("users") + sql, _ = mock_statement.call_args[0] self.assertEqual(sql, 'DROP TABLE "users"') - def test_has_column(self): - sql = self.schema.has_column("users", "name") + async def test_has_column(self): + mock_select = AsyncMock(return_value=[]) + self.schema.get_connection().select = mock_select + + await self.schema.has_column("users", "name") + sql, _ = mock_select.call_args[0] self.assertEqual( sql, "SELECT column_name FROM information_schema.columns WHERE table_name='users' and column_name='name'", ) - def test_can_have_unsigned_columns(self): - with self.schema.create("users") as blueprint: + async def test_can_have_unsigned_columns(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.integer("profile_id").unsigned() blueprint.big_integer("big_profile_id").unsigned() blueprint.tiny_integer("tiny_profile_id").unsigned() @@ -335,21 +398,33 @@ def test_can_have_unsigned_columns(self): ], ) - def test_can_enable_foreign_keys(self): - sql = self.schema.enable_foreign_key_constraints() + async def test_can_enable_foreign_keys(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + await self.schema.enable_foreign_key_constraints() + sql, _ = mock_statement.call_args[0] self.assertEqual(sql, "PRAGMA foreign_keys = ON") - def test_can_disable_foreign_keys(self): - sql = self.schema.disable_foreign_key_constraints() + async def test_can_disable_foreign_keys(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + await self.schema.disable_foreign_key_constraints() + + sql, _ = mock_statement.call_args[0] self.assertEqual(sql, "PRAGMA foreign_keys = OFF") - def test_can_truncate_without_foreign_keys(self): - sql = self.schema.truncate("users", foreign_keys=True) + async def test_can_truncate_without_foreign_keys(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + await self.schema.truncate("users", foreign_keys=True) + calls = [args[0] for args, _ in mock_statement.call_args_list] self.assertEqual( - sql, + calls, [ "PRAGMA foreign_keys = OFF", 'DELETE FROM "users"', @@ -357,8 +432,11 @@ def test_can_truncate_without_foreign_keys(self): ], ) - def test_can_add_enum(self): - with self.schema.create("users") as blueprint: + async def test_can_add_enum(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.create("users") as blueprint: blueprint.enum("status", ["active", "inactive"]).default("active") self.assertEqual(len(blueprint.table.added_columns), 1) diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/test_case.py b/fastapi_startkit/tests/masoniteorm/sqlite/test_case.py index 0058ab1e..a0e32b10 100644 --- a/fastapi_startkit/tests/masoniteorm/sqlite/test_case.py +++ b/fastapi_startkit/tests/masoniteorm/sqlite/test_case.py @@ -1,20 +1,25 @@ from unittest import IsolatedAsyncioTestCase +from fastapi_startkit.masoniteorm.testing.transaction import RefreshDatabase + from ..fixtures.db import DB from ..fixtures.migration import migrate, wipe from ..fixtures.seeder import seeder -class TestCase(IsolatedAsyncioTestCase): +class TestCase(RefreshDatabase, IsolatedAsyncioTestCase): async def asyncSetUp(self): self.db = DB self.schema = self.db.get_schema_builder() - await self.rollback() - await migrate() - await seeder() + await self.migrate_database() async def asyncTearDown(self): - await self.rollback() + DB.clear() + await wipe() - async def rollback(self) -> None: + @staticmethod + async def migrate_database(): + DB.clear() await wipe() + await migrate() + await seeder() diff --git a/fastapi_startkit/uv.lock b/fastapi_startkit/uv.lock index 4a56be09..974048f4 100644 --- a/fastapi_startkit/uv.lock +++ b/fastapi_startkit/uv.lock @@ -481,6 +481,7 @@ dev = [ { name = "dumpdie" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "ruff" }, { name = "twine" }, ] @@ -508,6 +509,7 @@ dev = [ { name = "dumpdie", specifier = ">=1.5.0" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.9.0" }, { name = "twine", specifier = ">=6.2.0" }, ] @@ -1438,6 +1440,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" }, ] +[[package]] +name = "ruff" +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +] + [[package]] name = "secretstorage" version = "3.5.0"