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/example/database-app/providers/app_provider.py b/example/database-app/providers/app_provider.py deleted file mode 100644 index 8701a2ba..00000000 --- a/example/database-app/providers/app_provider.py +++ /dev/null @@ -1,5 +0,0 @@ -class AppProvider: - def register(self): - from config.app import CONFIG - - self.merge_config_from(CONFIG, 'app') 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.backup/tests/sqlite/relationships/test_sqlite_has_one_through_relationship.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_one_through_relationship.py deleted file mode 100644 index 37b11e92..00000000 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_one_through_relationship.py +++ /dev/null @@ -1,162 +0,0 @@ -import pytest_asyncio - -from fastapi_startkit.masoniteorm.connections.sqlite_connection import SQLiteConnection -from fastapi_startkit.masoniteorm.models import Model -from fastapi_startkit.masoniteorm.relationships import HasOneThrough -from fastapi_startkit.masoniteorm.schema import Schema -from fastapi_startkit.masoniteorm.schema.platforms import SQLitePlatform - - -class Port(Model): - __table__ = "ports" - __connection__ = "dev" - - -class Country(Model): - __table__ = "countries" - __connection__ = "dev" - - -class IncomingShipment(Model): - __table__ = "incoming_shipments" - __connection__ = "dev" - - from_country: "Country" = HasOneThrough( - ["Country", "Port"], - "from_port_id", # FK on IncomingShipment → Port - "port_country_id", # FK on Port → Country - "port_id", # PK on Port - "country_id", # PK on Country - ) - - -class TestHasOneThroughRelationship: - @pytest_asyncio.fixture(autouse=True) - async def setup(self): - # Reset shared engine cache so each test class gets a fresh in-memory DB. - SQLiteConnection._shared_engines.clear() - - self.schema = Schema( - connection="dev", - platform=SQLitePlatform, - config_path="fastapi_startkit/masoniteorm/tests/integrations/config/database", - ).on("dev") - - async with await self.schema.create_table_if_not_exists( - "incoming_shipments" - ) as table: - table.integer("shipment_id").primary() - table.string("name") - table.integer("from_port_id") - - async with await self.schema.create_table_if_not_exists("ports") as table: - table.integer("port_id").primary() - table.string("name") - table.integer("port_country_id") - - async with await self.schema.create_table_if_not_exists("countries") as table: - table.integer("country_id").primary() - table.string("name") - - await ( - Country() - .get_builder() - .bulk_create( - [ - {"country_id": 10, "name": "Australia"}, - {"country_id": 20, "name": "USA"}, - {"country_id": 30, "name": "Canada"}, - {"country_id": 40, "name": "United Kingdom"}, - ] - ) - ) - - await ( - Port() - .get_builder() - .bulk_create( - [ - {"port_id": 100, "name": "Melbourne", "port_country_id": 10}, - {"port_id": 200, "name": "Darwin", "port_country_id": 10}, - {"port_id": 300, "name": "South Louisiana", "port_country_id": 20}, - {"port_id": 400, "name": "Houston", "port_country_id": 20}, - {"port_id": 500, "name": "Montreal", "port_country_id": 30}, - {"port_id": 600, "name": "Vancouver", "port_country_id": 30}, - {"port_id": 700, "name": "Southampton", "port_country_id": 40}, - {"port_id": 800, "name": "London Gateway", "port_country_id": 40}, - ] - ) - ) - - await ( - IncomingShipment() - .get_builder() - .bulk_create( - [ - {"name": "Bread", "from_port_id": 300}, - {"name": "Milk", "from_port_id": 100}, - {"name": "Tractor Parts", "from_port_id": 100}, - {"name": "Fridges", "from_port_id": 700}, - {"name": "Wheat", "from_port_id": 600}, - {"name": "Kettles", "from_port_id": 400}, - {"name": "Bread", "from_port_id": 700}, - ] - ) - ) - - yield - - await self.schema.drop_table_if_exists("incoming_shipments") - await self.schema.drop_table_if_exists("ports") - await self.schema.drop_table_if_exists("countries") - SQLiteConnection._shared_engines.clear() - - async def test_has_one_through_can_eager_load(self): - shipments = await ( - IncomingShipment.where("name", "Bread").with_("from_country").get() - ) - assert shipments.count() == 2 - - shipment1 = shipments.shift() - assert isinstance(shipment1.from_country, Country) - assert shipment1.from_country.country_id == 20 - - shipment2 = shipments.shift() - assert isinstance(shipment2.from_country, Country) - assert shipment2.from_country.country_id == 40 - - # check .first() and .get() produce the same result - single = await ( - IncomingShipment.where("name", "Tractor Parts") - .with_("from_country") - .first() - ) - single_get = await ( - IncomingShipment.where("name", "Tractor Parts").with_("from_country").get() - ) - assert single.from_country.country_id == 10 - assert single_get.count() == 1 - assert ( - single.from_country.country_id == single_get.first().from_country.country_id - ) - - async def test_has_one_through_eager_load_can_be_empty(self): - shipments = await ( - IncomingShipment.where("name", "Bread") - .where_has("from_country", lambda query: query.where("name", "Uruguay")) - .with_("from_country") - .get() - ) - assert shipments.count() == 0 - - async def test_has_one_through_can_get_related(self): - shipment = await IncomingShipment.where("name", "Milk").first() - country = await shipment.from_country - assert isinstance(country, Country) - assert country.country_id == 10 - - async def test_has_one_through_has_query(self): - shipments = await IncomingShipment.where_has( - "from_country", lambda query: query.where("name", "USA") - ).get() - assert shipments.count() == 2 diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder_alter.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder_alter.py deleted file mode 100644 index 45179ced..00000000 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder_alter.py +++ /dev/null @@ -1,240 +0,0 @@ -import unittest - -from src.masoniteorm.schema import Schema -from src.masoniteorm.schema.platforms import SQLitePlatform -from src.masoniteorm.schema.Table import Table -from tests.integrations.config.database import DATABASES - - -class TestSQLiteSchemaBuilderAlter(unittest.TestCase): - maxDiff = None - - 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.table("users") as blueprint: - blueprint.string("name") - blueprint.string("external_type").default("external") - blueprint.integer("age") - - self.assertEqual(len(blueprint.table.added_columns), 3) - - sql = [ - 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', - """ALTER TABLE "users" ADD COLUMN "external_type" VARCHAR NOT NULL DEFAULT 'external'""", - 'ALTER TABLE "users" ADD COLUMN "age" INTEGER NOT NULL', - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_add_constraints(self): - with self.schema.table("users") as blueprint: - blueprint.unique("name", name="table_unique") - - self.assertEqual(len(blueprint.table.added_columns), 0) - - sql = ['CREATE UNIQUE INDEX table_unique ON "users"(name)'] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_rename(self): - with self.schema.table("users") as blueprint: - blueprint.rename("post", "comment", "integer") - - table = Table("users") - table.add_column("post", "integer") - blueprint.table.from_table = table - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT post FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("comment" INTEGER NOT NULL)', - 'INSERT INTO "users" ("comment") SELECT post FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_drop(self): - with self.schema.table("users") as blueprint: - blueprint.drop_column("post") - - table = Table("users") - table.add_column("post", "string") - table.add_column("name", "string") - table.add_column("email", "string") - blueprint.table.from_table = table - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT name, email FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("name" VARCHAR NOT NULL, "email" VARCHAR NOT NULL)', - 'INSERT INTO "users" ("name", "email") SELECT name, email FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_change(self): - with self.schema.table("users") as blueprint: - blueprint.integer("age").change() - blueprint.string("name") - - self.assertEqual(len(blueprint.table.added_columns), 1) - self.assertEqual(len(blueprint.table.changed_columns), 1) - table = Table("users") - table.add_column("age", "string") - - blueprint.table.from_table = table - - sql = [ - 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', - "CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("age" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL)', - 'INSERT INTO "users" ("age") SELECT age FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_drop_add_and_change(self): - with self.schema.table("users") as blueprint: - blueprint.integer("age").change() - blueprint.string("name") - blueprint.drop_column("email") - - self.assertEqual(len(blueprint.table.added_columns), 1) - self.assertEqual(len(blueprint.table.changed_columns), 1) - table = Table("users") - table.add_column("age", "string") - table.add_column("email", "string") - - blueprint.table.from_table = table - - # sql = [ - # 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR', - # "CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users", - # 'DROP TABLE "users"', - # 'CREATE TABLE "users" ("age" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL)', - # 'INSERT INTO "users" ("age") SELECT age FROM __temp__users', - # "DROP TABLE __temp__users", - # ] - - def test_timestamp_alter_add_nullable_column(self): - with self.schema.table("users") as blueprint: - blueprint.timestamp("due_date").nullable() - - self.assertEqual(len(blueprint.table.added_columns), 1) - - table = Table("users") - table.add_column("age", "string") - - blueprint.table.from_table = table - - sql = ['ALTER TABLE "users" ADD COLUMN "due_date" TIMESTAMP NULL'] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_drop_on_table_schema_table(self): - schema = Schema(connection="dev", connection_details=DATABASES).on("dev") - - with schema.table("table_schema") as blueprint: - blueprint.drop_column("name") - - with schema.table("table_schema") as blueprint: - blueprint.string("name").nullable() - - def test_alter_add_primary(self): - with self.schema.table("users") as blueprint: - blueprint.primary("playlist_id") - - sql = [ - 'ALTER TABLE "users" ADD CONSTRAINT users_playlist_id_primary PRIMARY KEY (playlist_id)' - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_add_column_and_foreign_key(self): - with self.schema.table("users") as blueprint: - blueprint.unsigned_integer("playlist_id").nullable() - blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( - "cascade" - ).on_update("SET NULL") - - table = Table("users") - table.add_column("age", "string") - table.add_column("email", "string") - - blueprint.table.from_table = table - - sql = [ - 'ALTER TABLE "users" ADD COLUMN "playlist_id" INTEGER UNSIGNED NULL REFERENCES "playlists"("id")', - "CREATE TEMPORARY TABLE __temp__users AS SELECT age, email FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("age" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, "playlist_id" INTEGER UNSIGNED NULL, ' - 'CONSTRAINT users_playlist_id_foreign FOREIGN KEY ("playlist_id") REFERENCES "playlists"("id") ON DELETE CASCADE ON UPDATE SET NULL)', - 'INSERT INTO "users" ("age", "email") SELECT age, email FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_add_foreign_key_only(self): - with self.schema.table("users") as blueprint: - blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( - "cascade" - ).on_update("set null") - - table = Table("users") - table.add_column("age", "string") - table.add_column("email", "string") - - blueprint.table.from_table = table - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT age, email FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("age" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, ' - 'CONSTRAINT users_playlist_id_foreign FOREIGN KEY ("playlist_id") REFERENCES "playlists"("id") ON DELETE CASCADE ON UPDATE SET NULL)', - 'INSERT INTO "users" ("age", "email") SELECT age, email FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_add_column_enum(self): - with self.schema.table("users") as blueprint: - blueprint.enum("status", ["active", "inactive"]).default("active") - - self.assertEqual(len(blueprint.table.added_columns), 1) - - sql = [ - "ALTER TABLE \"users\" ADD COLUMN \"status\" VARCHAR CHECK('status' IN('active', 'inactive')) NOT NULL DEFAULT 'active'" - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_change_column_enum(self): - with self.schema.table("users") as blueprint: - blueprint.enum("status", ["active", "inactive"]).default("active").change() - - blueprint.table.from_table = Table("users") - - self.assertEqual(len(blueprint.table.changed_columns), 1) - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT FROM users", - 'DROP TABLE "users"', - "CREATE TABLE \"users\" (\"status\" VARCHAR(255) CHECK(status IN ('active', 'inactive')) NOT NULL DEFAULT 'active')", - 'INSERT INTO "users" ("status") SELECT status FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py index dc8ae3ad..768bac01 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from fastapi_startkit.masoniteorm.expressions.expressions import ( + JoinClause, QueryExpression, SelectExpression, UpdateQueryExpression, @@ -27,6 +28,7 @@ def __init__(self, connection: "Connection", grammar, processor): self._table = "" self._limit = False self._wheres = [] + self._joins = () self._sql = "" self._bindings = () @@ -73,7 +75,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: @@ -116,6 +118,7 @@ def get_grammar(self): table=self._table, limit=self._limit, wheres=self._wheres, + joins=self._joins, ) def to_qmark(self) -> str: @@ -195,3 +198,51 @@ 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 + + def join(self, table: str, column1: str, equality: str, column2: str, clause: str = "join") -> "QueryBuilder": + join_clause = JoinClause(table, clause=clause) + join_clause.on(column1, equality, column2) + self._joins += (join_clause,) + return self + + def where_column(self, column1: str, column2: str) -> "QueryBuilder": + self._wheres += (QueryExpression(column1, "=", column2, "value_equals"),) + return self + + def when(self, condition, callback) -> "QueryBuilder": + if condition: + callback(self) + return self + + def where_exists(self, builder: "QueryBuilder") -> "QueryBuilder": + self._wheres += (QueryExpression(None, "EXISTS", SubSelectExpression(builder)),) + return self + + def or_where_exists(self, builder: "QueryBuilder") -> "QueryBuilder": + self._wheres += ( + QueryExpression(None, "EXISTS", SubSelectExpression(builder), keyword="or"), + ) + return self + + def where_has(self, relation: str, callback=None) -> "QueryBuilder": + related = getattr(self._model.__class__, relation) + if callback: + related.query_where_exists(self, callback, method="where_exists") + else: + related.query_has(self, method="where_exists") + return self + + def or_where_has(self, relation: str, callback=None) -> "QueryBuilder": + related = getattr(self._model.__class__, relation) + if callback: + related.query_where_exists(self, callback, method="or_where_exists") + else: + related.query_has(self, method="or_where_exists") + return self diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py index d86e5d60..8d48e8a2 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py @@ -50,7 +50,7 @@ def __init_subclass__(cls, **kwargs): def __init__(self, attributes: dict = None, **kwargs): super().__init__(attributes, **kwargs) - self.connection = "default" + self.connection = getattr(self.__class__, "__connection__", "default") self._global_scopes = {} self.__with__ = {} self._exists = False @@ -82,6 +82,14 @@ def get_related(self, key: str): def with_(cls, *eagers) -> "QueryBuilder": return cls.query().with_(*eagers) + @classmethod + def where_has(cls, relation: str, callback=None) -> "QueryBuilder": + return cls.query().where_has(relation, callback) + + @classmethod + def or_where_has(cls, relation: str, callback=None) -> "QueryBuilder": + return cls.query().or_where_has(relation, callback) + @classmethod async def find(cls, primary_key: str | int, columns=None): return await cls.query().find(primary_key, columns) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py index 36181512..b2d566eb 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py @@ -754,7 +754,7 @@ def to_sql(self): elif self._action == "create_table_if_not_exists": return self.platform().compile_create_sql(self.table, if_not_exists=True) else: - if not self._dry: + if not self._dry and self.table.from_table is None: # get current table schema table = self.platform().get_current_schema( self.connection, self.table.name, schema=self.schema 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/masoniteorm/tests/fixtures/db.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/db.py deleted file mode 100644 index e7716c23..00000000 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/db.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi_startkit.orm.connections.factory import ConnectionFactory -from fastapi_startkit.orm.connections.manager import DatabaseManager -from fastapi_startkit.orm.models.model import Model - -DB = DatabaseManager( - ConnectionFactory(), - { - "default": "sqlite", - "sqlite": { - "driver": "sqlite", - "url": "sqlite+aiosqlite:///masonite.sqlite3", - }, - }, -) - -Model.db_manager = DB diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/seeder.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/seeder.py deleted file mode 100644 index f0a87c42..00000000 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/seeder.py +++ /dev/null @@ -1,18 +0,0 @@ -from .model import User, Profile, Articles, Logo - - -async def seeder(): - user = await User.query().create( - {"email": "admin@admin.com", "name": "Joe", "is_admin": True} - ) - await Profile.create({"name": "Joe Profile", "user_id": user.id}) - article = await Articles.create( - { - "title": "Masonite ORM", - "user_id": user.id, - "published_date": "2020-01-01 00:00:00", - } - ) - await Logo.create( - {"article_id": article.id, "published_date": "2020-01-01 00:00:00"} - ) 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 new file mode 100644 index 00000000..25a88f94 --- /dev/null +++ b/fastapi_startkit/tests/masoniteorm/fixtures/db.py @@ -0,0 +1,22 @@ +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", + "connections": { + "sqlite": { + "driver": "sqlite", + "url": "sqlite+aiosqlite:///masonite.sqlite3", + }, + "dev": { + "driver": "sqlite", + "url": "sqlite+aiosqlite:///masonite_dev.sqlite3", + }, + }, + }, +) + +Model.db_manager = DB diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/factory.py b/fastapi_startkit/tests/masoniteorm/fixtures/factory.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/factory.py rename to fastapi_startkit/tests/masoniteorm/fixtures/factory.py diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/migration.py b/fastapi_startkit/tests/masoniteorm/fixtures/migration.py similarity index 70% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/migration.py rename to fastapi_startkit/tests/masoniteorm/fixtures/migration.py index 39839b0f..71764712 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/migration.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/migration.py @@ -4,9 +4,10 @@ async def wipe(): - tables = await schema.on("default").get_all_tables() - for table in tables: - await schema.on("default").drop_table_if_exists(table) + for connection in ("default", "dev"): + tables = await schema.on(connection).get_all_tables() + for table in tables: + await schema.on(connection).drop_table_if_exists(table) async def migrate(): @@ -60,3 +61,17 @@ async def migrate(): table.integer("store_id") table.integer("product_id") table.timestamps() + + async with await schema.on("dev").create_table_if_not_exists("countries") as table: + table.integer("country_id").primary() + table.string("name") + + async with await schema.on("dev").create_table_if_not_exists("ports") as table: + table.integer("port_id").primary() + table.string("name") + table.integer("port_country_id") + + async with await schema.on("dev").create_table_if_not_exists("incoming_shipments") as table: + table.integer("shipment_id").primary() + table.string("name") + table.integer("from_port_id") diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/model.py b/fastapi_startkit/tests/masoniteorm/fixtures/model.py similarity index 67% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/model.py rename to fastapi_startkit/tests/masoniteorm/fixtures/model.py index cb93c244..035ee6d7 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/fixtures/model.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/model.py @@ -1,13 +1,13 @@ 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, + HasOneThrough, ) -from fastapi_startkit.orm.models.model import Model +from fastapi_startkit.masoniteorm.models.model import Model class User(Model): @@ -56,3 +56,26 @@ class Store(Model): class Product(Model): __table__ = "products" + + +class Port(Model): + __table__ = "ports" + __connection__ = "dev" + + +class Country(Model): + __table__ = "countries" + __connection__ = "dev" + + +class IncomingShipment(Model): + __table__ = "incoming_shipments" + __connection__ = "dev" + + from_country: "Country" = HasOneThrough( + ["Country", "Port"], + "from_port_id", # FK on IncomingShipment → Port + "port_country_id", # FK on Port → Country + "port_id", # PK on Port + "country_id", # PK on Country + ) diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py b/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py new file mode 100644 index 00000000..8d4d5f47 --- /dev/null +++ b/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py @@ -0,0 +1,52 @@ +from .model import User, Profile, Articles, Logo, Country, Port, IncomingShipment + + +async def seeder(): + user = await User.query().create( + {"email": "admin@admin.com", "name": "Joe", "is_admin": True} + ) + await Profile.create({"name": "Joe Profile", "user_id": user.id}) + article = await Articles.create( + { + "title": "Masonite ORM", + "user_id": user.id, + "published_date": "2020-01-01 00:00:00", + } + ) + await Logo.create( + {"article_id": article.id, "published_date": "2020-01-01 00:00:00"} + ) + + await Country.query().insert( + [ + {"country_id": 10, "name": "Australia"}, + {"country_id": 20, "name": "USA"}, + {"country_id": 30, "name": "Canada"}, + {"country_id": 40, "name": "United Kingdom"}, + ] + ) + + await Port.query().insert( + [ + {"port_id": 100, "name": "Melbourne", "port_country_id": 10}, + {"port_id": 200, "name": "Darwin", "port_country_id": 10}, + {"port_id": 300, "name": "South Louisiana", "port_country_id": 20}, + {"port_id": 400, "name": "Houston", "port_country_id": 20}, + {"port_id": 500, "name": "Montreal", "port_country_id": 30}, + {"port_id": 600, "name": "Vancouver", "port_country_id": 30}, + {"port_id": 700, "name": "Southampton", "port_country_id": 40}, + {"port_id": 800, "name": "London Gateway", "port_country_id": 40}, + ] + ) + + await IncomingShipment.query().insert( + [ + {"name": "Bread", "from_port_id": 300}, + {"name": "Milk", "from_port_id": 100}, + {"name": "Tractor Parts", "from_port_id": 100}, + {"name": "Fridges", "from_port_id": 700}, + {"name": "Wheat", "from_port_id": 600}, + {"name": "Kettles", "from_port_id": 400}, + {"name": "Bread", "from_port_id": 700}, + ] + ) 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/src/fastapi_startkit/masoniteorm/tests/models/test_model.py b/fastapi_startkit/tests/masoniteorm/models/test_model.py similarity index 93% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/models/test_model.py rename to fastapi_startkit/tests/masoniteorm/models/test_model.py index cb65e20f..582f412b 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/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/src/fastapi_startkit/masoniteorm/tests/models/test_model_attributes.py b/fastapi_startkit/tests/masoniteorm/models/test_model_attributes.py similarity index 89% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/models/test_model_attributes.py rename to fastapi_startkit/tests/masoniteorm/models/test_model_attributes.py index c14ccfb3..1aec2193 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/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/src/fastapi_startkit/masoniteorm/tests/models/test_model_query.py b/fastapi_startkit/tests/masoniteorm/models/test_model_query.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/models/test_model_query.py rename to fastapi_startkit/tests/masoniteorm/models/test_model_query.py 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/src/fastapi_startkit/masoniteorm/tests/sqlite/models/test_sqlite_model.py b/fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py similarity index 75% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/sqlite/models/test_sqlite_model.py rename to fastapi_startkit/tests/masoniteorm/sqlite/models/test_sqlite_model.py index 128db293..6223a476 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/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/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_many_through_relationship.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_many_through_relationship.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_many_through_relationship.py rename to fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_many_through_relationship.py diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_one_through_relationship.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_one_through_relationship.py new file mode 100644 index 00000000..cf7bed29 --- /dev/null +++ b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_one_through_relationship.py @@ -0,0 +1,54 @@ +from ...fixtures.model import Country, IncomingShipment +from ..test_case import TestCase + + +class TestHasOneThroughRelationship(TestCase): + async def test_has_one_through_can_eager_load(self): + shipments = await ( + IncomingShipment.where("name", "Bread").with_("from_country").get() + ) + assert shipments.count() == 2 + + shipment1 = shipments.shift() + assert isinstance(shipment1.from_country, Country) + assert shipment1.from_country.country_id == 20 + + shipment2 = shipments.shift() + assert isinstance(shipment2.from_country, Country) + assert shipment2.from_country.country_id == 40 + + # check .first() and .get() produce the same result + single = await ( + IncomingShipment.where("name", "Tractor Parts") + .with_("from_country") + .first() + ) + single_get = await ( + IncomingShipment.where("name", "Tractor Parts").with_("from_country").get() + ) + assert single.from_country.country_id == 10 + assert single_get.count() == 1 + assert ( + single.from_country.country_id == single_get.first().from_country.country_id + ) + + async def test_has_one_through_eager_load_can_be_empty(self): + shipments = await ( + IncomingShipment.where("name", "Bread") + .where_has("from_country", lambda query: query.where("name", "Uruguay")) + .with_("from_country") + .get() + ) + assert shipments.count() == 0 + + async def test_has_one_through_can_get_related(self): + shipment = await IncomingShipment.where("name", "Milk").first() + country = await shipment.from_country + assert isinstance(country, Country) + assert country.country_id == 10 + + async def test_has_one_through_has_query(self): + shipments = await IncomingShipment.where_has( + "from_country", lambda query: query.where("name", "USA") + ).get() + assert shipments.count() == 2 diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_polymorphic.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_polymorphic.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_polymorphic.py rename to fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_polymorphic.py diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/sqlite/relationships/test_sqlite_relationships.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_relationships.py similarity index 90% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/sqlite/relationships/test_sqlite_relationships.py rename to fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_relationships.py index 01b48f24..78c3e650 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/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/schema/test_sqlite_schema_builder_alter.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder_alter.py new file mode 100644 index 00000000..005804c9 --- /dev/null +++ b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder_alter.py @@ -0,0 +1,268 @@ +from unittest.mock import AsyncMock, MagicMock + +from fastapi_startkit.masoniteorm.schema.Table import Table + +from ..test_case import TestCase + + +class TestSQLiteSchemaBuilderAlter(TestCase): + async def test_can_add_columns(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("name") + blueprint.string("external_type").default("external") + blueprint.integer("age") + + self.assertEqual(len(blueprint.table.added_columns), 3) + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', + """ALTER TABLE "users" ADD COLUMN "external_type" VARCHAR NOT NULL DEFAULT 'external'""", + 'ALTER TABLE "users" ADD COLUMN "age" INTEGER NOT NULL', + ], + ) + + async def test_can_add_constraints(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.unique("name", name="table_unique") + + self.assertEqual(len(blueprint.table.added_columns), 0) + self.assertEqual( + blueprint.to_sql(), + ['CREATE UNIQUE INDEX table_unique ON "users"(name)'], + ) + + async def test_alter_rename(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("post", "integer") + + async with await self.schema.table("users") as blueprint: + blueprint.rename("post", "comment", "integer") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT post FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("comment" INTEGER NOT NULL)', + 'INSERT INTO "users" ("comment") SELECT post FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_alter_drop(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("post", "string") + table.add_column("name", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.drop_column("post") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT name, email FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("name" VARCHAR NOT NULL, "email" VARCHAR NOT NULL)', + 'INSERT INTO "users" ("name", "email") SELECT name, email FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_change(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.integer("age").change() + blueprint.string("name") + blueprint.table.from_table = table + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual(len(blueprint.table.changed_columns), 1) + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', + "CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("age" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL)', + 'INSERT INTO "users" ("age") SELECT age FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_drop_add_and_change(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.integer("age").change() + blueprint.string("name") + blueprint.drop_column("email") + blueprint.table.from_table = table + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual(len(blueprint.table.changed_columns), 1) + + async def test_timestamp_alter_add_nullable_column(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.timestamp("due_date").nullable() + blueprint.table.from_table = table + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual( + blueprint.to_sql(), + ['ALTER TABLE "users" ADD COLUMN "due_date" TIMESTAMP NULL'], + ) + + async def test_alter_drop_on_table_schema_table(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("table_schema") as blueprint: + blueprint.drop_column("name") + + async with await self.schema.table("table_schema") as blueprint: + blueprint.string("name").nullable() + + async def test_alter_add_primary(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.primary("playlist_id") + + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD CONSTRAINT users_playlist_id_primary PRIMARY KEY (playlist_id)' + ], + ) + + async def test_alter_add_column_and_foreign_key(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.unsigned_integer("playlist_id").nullable() + blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( + "cascade" + ).on_update("SET NULL") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD COLUMN "playlist_id" INTEGER UNSIGNED NULL REFERENCES "playlists"("id")', + "CREATE TEMPORARY TABLE __temp__users AS SELECT age, email FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("age" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, "playlist_id" INTEGER UNSIGNED NULL, ' + 'CONSTRAINT users_playlist_id_foreign FOREIGN KEY ("playlist_id") REFERENCES "playlists"("id") ON DELETE CASCADE ON UPDATE SET NULL)', + 'INSERT INTO "users" ("age", "email") SELECT age, email FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_alter_add_foreign_key_only(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( + "cascade" + ).on_update("set null") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT age, email FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("age" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, ' + 'CONSTRAINT users_playlist_id_foreign FOREIGN KEY ("playlist_id") REFERENCES "playlists"("id") ON DELETE CASCADE ON UPDATE SET NULL)', + 'INSERT INTO "users" ("age", "email") SELECT age, email FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_can_add_column_enum(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.enum("status", ["active", "inactive"]).default("active") + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual( + blueprint.to_sql(), + [ + "ALTER TABLE \"users\" ADD COLUMN \"status\" VARCHAR CHECK('status' IN('active', 'inactive')) NOT NULL DEFAULT 'active'" + ], + ) + + async def test_can_change_column_enum(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.table("users") as blueprint: + blueprint.enum("status", ["active", "inactive"]).default("active").change() + blueprint.table.from_table = Table("users") + + self.assertEqual(len(blueprint.table.changed_columns), 1) + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT FROM users", + 'DROP TABLE "users"', + "CREATE TABLE \"users\" (\"status\" VARCHAR(255) CHECK(status IN ('active', 'inactive')) NOT NULL DEFAULT 'active')", + 'INSERT INTO "users" ("status") SELECT status FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table.py rename to fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table.py diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table_diff.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table_diff.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table_diff.py rename to fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table_diff.py diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/sqlite/test_case.py b/fastapi_startkit/tests/masoniteorm/sqlite/test_case.py similarity index 56% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/sqlite/test_case.py rename to fastapi_startkit/tests/masoniteorm/sqlite/test_case.py index 0058ab1e..a0e32b10 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/tests/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"