From 300b87c677097aaf9e5adb7f59a3f30ddd114d31 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Tue, 7 Jul 2026 10:21:55 +0800 Subject: [PATCH 01/32] feat(pgq): gate quantified path and comma-separated patterns as unsupported on PG 19 PostgreSQL 19 does not support: - Variable-length (quantified) path patterns (+/*/{n,m}) - Comma-separated multiple patterns in a single MATCH clause Add capability overrides returning False, document known limitations, and add tests for the new gating methods. --- .../postgres/mixins/property_graph_query.py | 15 ++++++++++ .../test_property_graph_query_format.py | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/rhosocial/activerecord/backend/impl/postgres/mixins/property_graph_query.py b/src/rhosocial/activerecord/backend/impl/postgres/mixins/property_graph_query.py index 8af4c289..806bd957 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/mixins/property_graph_query.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/mixins/property_graph_query.py @@ -5,6 +5,11 @@ - GRAPH_TABLE (MATCH ... COLUMNS ...) - CREATE / DROP / ALTER PROPERTY GRAPH - Vertex/edge pattern matching with WHERE + +Known PG 19 PGQ limitations (not supported): +- Variable-length (quantified) path patterns (``+``, ``*``, ``{n,m}``) +- Comma-separated multiple patterns in a single MATCH clause +- Heterogeneous graphs require explicit edge label specification """ @@ -18,6 +23,16 @@ def supports_graph_match(self) -> bool: """MATCH clause is supported since PostgreSQL 19.""" return self.version >= (19, 0, 0) + def supports_quantified_path(self) -> bool: + """Quantified (variable-length) path patterns are **not** supported + in PostgreSQL 19.""" + return False + + def supports_comma_separated_patterns(self) -> bool: + """Comma-separated multi-pattern MATCH is **not** supported + in PostgreSQL 19.""" + return False + def supports_graph_table(self) -> bool: """GRAPH_TABLE expression is supported since PostgreSQL 19.""" return self.version >= (19, 0, 0) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_property_graph_query_format.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_property_graph_query_format.py index 723d351c..2318f5cf 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_property_graph_query_format.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_property_graph_query_format.py @@ -52,6 +52,34 @@ def test_graph_support_protocol_implemented_pg15(self): assert isinstance(d, GraphTableSupport) +class TestPGQLimitationGating: + """Tests that PG 19 correctly reports unsupported features.""" + + def test_quantified_path_unsupported_pg19(self): + d = PostgresDialect((19, 0, 0)) + assert d.supports_quantified_path() is False + + def test_quantified_path_unsupported_pg15(self): + d = PostgresDialect((15, 0, 0)) + assert d.supports_quantified_path() is False + + def test_comma_separated_patterns_unsupported_pg19(self): + d = PostgresDialect((19, 0, 0)) + assert d.supports_comma_separated_patterns() is False + + def test_comma_separated_patterns_unsupported_pg15(self): + d = PostgresDialect((15, 0, 0)) + assert d.supports_comma_separated_patterns() is False + + def test_quantified_path_raises_on_format(self): + d = PostgresDialect((19, 0, 0)) + from rhosocial.activerecord.backend.expression import QuantifiedPath + edge = GraphEdge(d, "e", "knows", GraphEdgeDirection.RIGHT) + qp = QuantifiedPath(d, edge) + with pytest.raises(UnsupportedFeatureError): + qp.to_sql() + + @pytest.fixture def pg19_dialect(): return PostgresDialect((19, 0, 0)) From 23be8032c3e7de83f936d1d3315aac27cf3a2964 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Tue, 7 Jul 2026 10:51:39 +0800 Subject: [PATCH 02/32] fix: use correct fixture names 'postgres_backend' / 'async_postgres_backend' in cursor pollution tests The cursor pollution tests referenced 'pg_backend' and 'async_pg_backend' which do not exist as pytest fixtures. The correct names are 'postgres_backend' and 'async_postgres_backend', matching the actual fixture definitions used by the rest of the test suite. --- .../feature/backend/test_cursor_pollution.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py index a2787e23..c1fc74cd 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py @@ -19,9 +19,9 @@ class TestPostgresCursorPollution: """Cursor pollution: sync PostgreSQL backend.""" - def test_get_server_version_then_query(self, pg_backend): + def test_get_server_version_then_query(self, postgres_backend): """get_server_version() then a normal query.""" - backend = pg_backend + backend = postgres_backend version = backend.get_server_version() assert version is not None @@ -37,9 +37,9 @@ def test_get_server_version_then_query(self, pg_backend): f"cursor.description polluted! expected 'marker', got: {col_name}" ) - def test_introspect_and_adapt_then_query(self, pg_backend): + def test_introspect_and_adapt_then_query(self, postgres_backend): """introspect_and_adapt() then a normal query.""" - backend = pg_backend + backend = postgres_backend backend.introspect_and_adapt() cursor = backend._get_cursor() @@ -54,9 +54,9 @@ def test_introspect_and_adapt_then_query(self, pg_backend): f"cursor.description polluted! expected 'status', got: {col_name}" ) - def test_high_frequency_version_query_cycle(self, pg_backend): + def test_high_frequency_version_query_cycle(self, postgres_backend): """Repeated get_server_version → query cycle to expose state leaks.""" - backend = pg_backend + backend = postgres_backend for i in range(200): backend.get_server_version() @@ -81,9 +81,9 @@ class TestAsyncPostgresCursorPollution: """Cursor pollution: async PostgreSQL backend.""" @pytest.mark.asyncio - async def test_get_server_version_then_query(self, async_pg_backend): + async def test_get_server_version_then_query(self, async_postgres_backend): """Async get_server_version() then a normal query.""" - backend = async_pg_backend + backend = async_postgres_backend version = await backend.get_server_version() assert version is not None @@ -100,9 +100,9 @@ async def test_get_server_version_then_query(self, async_pg_backend): ) @pytest.mark.asyncio - async def test_introspect_and_adapt_then_query(self, async_pg_backend): + async def test_introspect_and_adapt_then_query(self, async_postgres_backend): """Async introspect_and_adapt() then a normal query.""" - backend = async_pg_backend + backend = async_postgres_backend await backend.introspect_and_adapt() cursor = await backend._get_cursor() @@ -119,9 +119,9 @@ async def test_introspect_and_adapt_then_query(self, async_pg_backend): ) @pytest.mark.asyncio - async def test_context_entry_workflow(self, async_pg_backend): + async def test_context_entry_workflow(self, async_postgres_backend): """backend.context() workflow (production pattern).""" - backend = async_pg_backend + backend = async_postgres_backend async with backend.context(): cursor = await backend._get_cursor() @@ -138,9 +138,9 @@ async def test_context_entry_workflow(self, async_pg_backend): ) @pytest.mark.asyncio - async def test_high_frequency_version_query_cycle(self, async_pg_backend): + async def test_high_frequency_version_query_cycle(self, async_postgres_backend): """Async high-frequency version query cycle to expose state leaks.""" - backend = async_pg_backend + backend = async_postgres_backend for i in range(200): await backend.get_server_version() From f146d54d4555f2433e380fcb26bbae9052c353a6 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Tue, 7 Jul 2026 23:12:19 +0800 Subject: [PATCH 03/32] feat: sync bridge files with testsuite rename and add 4 async relation bridges - Update bridge files to match testsuite rename: test_sync_queries -> test_queries - Add 4 new async relation bridge files (test_base_async, test_cache_async, test_interfaces_async, test_nested_relationship_access_async) - Include pgq/ example directory --- .../impl/postgres/examples/conftest.py | 8 + .../impl/postgres/examples/pgq}/__init__.py | 0 .../pgq/recursive_cte_graph_traversal.py | 389 ++++++++++++++ tests/benchmark/activerecord_bulk/__init__.py | 0 .../activerecord_bulk/test_bulk_create.py | 4 +- tests/benchmark/backend/__init__.py | 0 .../test_recursive_cte_graph_traversal.py | 502 ++++++++++++++++++ .../connection/test_active_record_context.py | 2 + .../connection/test_active_record_crud.py | 2 + .../feature/basic/test_basic_mapped_models.py | 2 + .../feature/basic/test_bulk_operations.py | 2 + .../feature/basic/test_crud.py | 2 + .../basic/test_field_column_mapping.py | 2 + .../feature/basic/test_fields.py | 2 + .../basic/test_pydantic_native_validation.py | 2 + .../feature/basic/test_type_adapter.py | 2 + .../feature/basic/test_validation.py | 2 + .../worker/test_connection_management.py | 2 + .../basic/worker/test_parallel_crud.py | 2 + .../worker/test_worker_pool_lifecycle.py | 2 + .../test_composite_pk_active_query.py | 2 + .../test_composite_pk_activerecord.py | 2 + .../test_composite_pk_cte_query.py | 2 + .../test_composite_pk_set_operation.py | 2 + .../feature/events/test_handlers.py | 2 + .../feature/events/test_lifecycle.py | 2 + .../feature/mixins/test_combined_articles.py | 2 + .../feature/mixins/test_optimistic_lock.py | 2 + .../feature/mixins/test_soft_delete.py | 2 + .../feature/mixins/test_timestamps.py | 2 + .../connection/test_active_query_context.py | 2 + .../connection/test_cte_query_context.py | 2 + .../connection/test_set_operation_context.py | 2 + .../query/test_active_query_aggregate.py | 2 + .../feature/query/test_active_query_basic.py | 2 + .../feature/query/test_active_query_join.py | 2 + .../feature/query/test_active_query_range.py | 2 + .../query/test_active_query_set_operation.py | 2 + .../feature/query/test_aggregate_queries.py | 2 + .../feature/query/test_basic.py | 2 + .../test_cross_database_compatibility.py | 2 + .../query/test_cte_query_active_query.py | 2 + .../query/test_cte_query_set_operation.py | 2 + .../query/test_eager_loading_caching.py | 2 + .../query/test_eager_loading_nested.py | 2 + .../test_eager_loading_scenarios_sync.py | 6 +- .../query/test_eager_loading_with_all.py | 2 + .../query/test_eager_loading_with_modifier.py | 2 + .../query/test_eager_loading_with_one.py | 2 + .../feature/query/test_error_handling.py | 2 + .../query/test_example_query_fixtures.py | 2 + .../feature/query/test_joins.py | 2 + .../query/test_logging_data_summarization.py | 2 + .../{test_sync_queries.py => test_queries.py} | 4 +- ...async_queries.py => test_queries_async.py} | 5 +- .../feature/query/test_query_optimization.py | 2 + .../feature/query/test_range_queries.py | 2 + .../query/test_range_query_detailed.py | 2 + .../query/test_relational_validation.py | 2 + .../feature/query/test_relations_basic.py | 2 + .../feature/query/test_relations_with_sync.py | 2 +- .../feature/query/test_set_operation.py | 2 + .../feature/query/test_special_queries.py | 2 + .../feature/query/test_window_functions.py | 2 + .../query/worker/test_parallel_queries.py | 2 + .../test_worker_transaction_isolation.py | 2 + .../feature/relation/test_base_async.py | 1 + .../feature/relation/test_boundary.py | 2 + .../feature/relation/test_cache_async.py | 1 + .../feature/relation/test_cache_clearing.py | 2 + .../feature/relation/test_derived_field.py | 2 + .../relation/test_derived_field_json.py | 2 + .../relation/test_descriptor_compat.py | 3 +- .../feature/relation/test_integration.py | 2 + .../feature/relation/test_interfaces_async.py | 1 + .../relation/test_modifier_behavior.py | 2 + .../feature/relation/test_modifier_warning.py | 2 + .../test_nested_relationship_access_async.py | 1 + .../feature/relation/test_path_validation.py | 2 + .../feature/relation/test_validator.py | 3 +- .../feature/relation/test_with_method.py | 2 + 81 files changed, 1048 insertions(+), 10 deletions(-) rename {tests/benchmark => src/rhosocial/activerecord/backend/impl/postgres/examples/pgq}/__init__.py (100%) create mode 100644 src/rhosocial/activerecord/backend/impl/postgres/examples/pgq/recursive_cte_graph_traversal.py delete mode 100644 tests/benchmark/activerecord_bulk/__init__.py delete mode 100644 tests/benchmark/backend/__init__.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py rename tests/rhosocial/activerecord_postgres_test/feature/query/{test_sync_queries.py => test_queries.py} (70%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{test_async_queries.py => test_queries_async.py} (69%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py diff --git a/src/rhosocial/activerecord/backend/impl/postgres/examples/conftest.py b/src/rhosocial/activerecord/backend/impl/postgres/examples/conftest.py index a3c1a4db..2ec48e6a 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/examples/conftest.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/examples/conftest.py @@ -262,6 +262,14 @@ 'min_version': '9', 'max_version': '18', }, + 'pgq/recursive_cte_graph_traversal.py': { + 'title': 'Recursive CTE Graph Traversal (PGQ Quantified Path Alternative)', + 'dialect_protocols': [], + 'priority': 5, + 'min_version': '9', + 'max_version': '18', + 'note': 'Uses recursive CTE + UNION ALL to simulate PGQ quantified path patterns; works on all PG versions', + }, 'transaction/basic.py': { 'title': 'Transaction Control', 'dialect_protocols': [], diff --git a/tests/benchmark/__init__.py b/src/rhosocial/activerecord/backend/impl/postgres/examples/pgq/__init__.py similarity index 100% rename from tests/benchmark/__init__.py rename to src/rhosocial/activerecord/backend/impl/postgres/examples/pgq/__init__.py diff --git a/src/rhosocial/activerecord/backend/impl/postgres/examples/pgq/recursive_cte_graph_traversal.py b/src/rhosocial/activerecord/backend/impl/postgres/examples/pgq/recursive_cte_graph_traversal.py new file mode 100644 index 00000000..2c7241df --- /dev/null +++ b/src/rhosocial/activerecord/backend/impl/postgres/examples/pgq/recursive_cte_graph_traversal.py @@ -0,0 +1,389 @@ +""" +Recursive CTE graph traversal — PGQ quantified path alternative +=============================================================== + +PostgreSQL 19 natively supports SQL/PGQ (MATCH / GRAPH_TABLE), with limitations: + - supports_quantified_path() → False (no variable-length paths +/*/{n,m}) + - supports_comma_separated_patterns() → False (no comma-separated multi-pattern) + +This example demonstrates recursive CTE + SetOperationExpression + UNION ALL +as an equivalent alternative for graph traversal patterns. + +Common use cases: + 1. Social network N-degree friend recommendations (replaces MATCH (a)-[e]->+(b)) + 2. AML fund source tracing (replaces MATCH (s)-[t*1..5]->(d)) + +Key components: + - CTEExpression: defines a CTE (WITH ... AS ...) + - SetOperationExpression: combines base + recursive queries (UNION ALL) + - WithQueryExpression(recursive=True): generates WITH RECURSIVE ... SELECT ... +""" + +# ============================================================ +# SECTION: Setup (reference only — creates tables and data) +# ============================================================ +import os +from rhosocial.activerecord.backend.impl.postgres import PostgresBackend +from rhosocial.activerecord.backend.impl.postgres.config import PostgresConnectionConfig + +config = PostgresConnectionConfig( + host=os.getenv('POSTGRES_HOST', 'localhost'), + port=int(os.getenv('POSTGRES_PORT', 5432)), + database=os.getenv('POSTGRES_DATABASE', 'test'), + username=os.getenv('POSTGRES_USER', 'postgres'), + password=os.getenv('POSTGRES_PASSWORD', ''), +) +backend = PostgresBackend(connection_config=config) +backend.connect() +dialect = backend.dialect + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, + InsertExpression, + ValuesSource, + DropTableExpression, + QueryExpression, + TableExpression, + CTEExpression, + WithQueryExpression, + SetOperationExpression, + FunctionCall, + ColumnDefinition, + ColumnConstraint, + ColumnConstraintType, +) +from rhosocial.activerecord.backend.expression.core import Literal, Column +from rhosocial.activerecord.backend.expression.predicates import BetweenPredicate +from rhosocial.activerecord.backend.expression.query_parts import ( + WhereClause, + OrderByClause, + JoinExpression, +) +from rhosocial.activerecord.backend.options import ExecutionOptions +from rhosocial.activerecord.backend.schema import StatementType + +dql_options = ExecutionOptions(stmt_type=StatementType.DQL) + +for t in ("transactions", "accounts", "follows", "users"): + sql, params = DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql() + backend.execute(sql, params) + +# ---- Social network tables ---- +users_cols = [ + ColumnDefinition('id', 'INTEGER', constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition('name', 'VARCHAR(100)'), + ColumnDefinition('city', 'VARCHAR(50)'), +] +backend.execute(*CreateTableExpression(dialect, 'users', users_cols, if_not_exists=True).to_sql()) + +follows_cols = [ + ColumnDefinition('id', 'INTEGER', constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition('follower_id', 'INTEGER'), + ColumnDefinition('followed_id', 'INTEGER'), +] +backend.execute(*CreateTableExpression(dialect, 'follows', follows_cols, if_not_exists=True).to_sql()) + +users_data = ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 'Alice'), Literal(dialect, 'NYC')], + [Literal(dialect, 2), Literal(dialect, 'Bob'), Literal(dialect, 'NYC')], + [Literal(dialect, 3), Literal(dialect, 'Charlie'), Literal(dialect, 'LA')], + [Literal(dialect, 4), Literal(dialect, 'Diana'), Literal(dialect, 'NYC')], + [Literal(dialect, 5), Literal(dialect, 'Eve'), Literal(dialect, 'LA')], +]) +backend.execute(*InsertExpression(dialect, 'users', columns=['id', 'name', 'city'], source=users_data).to_sql()) + +follows_data = ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 1), Literal(dialect, 2)], + [Literal(dialect, 2), Literal(dialect, 2), Literal(dialect, 3)], + [Literal(dialect, 3), Literal(dialect, 1), Literal(dialect, 3)], + [Literal(dialect, 4), Literal(dialect, 4), Literal(dialect, 1)], + [Literal(dialect, 5), Literal(dialect, 3), Literal(dialect, 5)], +]) +backend.execute(*InsertExpression(dialect, 'follows', columns=['id', 'follower_id', 'followed_id'], source=follows_data).to_sql()) + +# ---- AML tables ---- +accounts_cols = [ + ColumnDefinition('id', 'INTEGER', constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition('account_holder', 'VARCHAR(100)'), + ColumnDefinition('account_type', 'VARCHAR(20)'), +] +backend.execute(*CreateTableExpression(dialect, 'accounts', accounts_cols, if_not_exists=True).to_sql()) + +txn_cols = [ + ColumnDefinition('id', 'INTEGER', constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition('source_account_id', 'INTEGER'), + ColumnDefinition('target_account_id', 'INTEGER'), + ColumnDefinition('amount', 'NUMERIC(12,2)'), + ColumnDefinition('ts', 'VARCHAR(20)'), +] +backend.execute(*CreateTableExpression(dialect, 'transactions', txn_cols, if_not_exists=True).to_sql()) + +accounts_data = ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 'Alice Smith'), Literal(dialect, 'checking')], + [Literal(dialect, 2), Literal(dialect, 'Bob Corp'), Literal(dialect, 'business')], + [Literal(dialect, 3), Literal(dialect, 'Charlie Ltd'), Literal(dialect, 'business')], + [Literal(dialect, 4), Literal(dialect, 'Diana Offshore'), Literal(dialect, 'offshore')], + [Literal(dialect, 5), Literal(dialect, 'Eve Holding'), Literal(dialect, 'offshore')], + [Literal(dialect, 6), Literal(dialect, 'Foreign Entity'), Literal(dialect, 'offshore')], +]) +backend.execute(*InsertExpression(dialect, 'accounts', columns=['id', 'account_holder', 'account_type'], source=accounts_data).to_sql()) + +txn_data = ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 4), Literal(dialect, 3), Literal(dialect, 500000), Literal(dialect, '2024-01-15')], + [Literal(dialect, 2), Literal(dialect, 5), Literal(dialect, 3), Literal(dialect, 300000), Literal(dialect, '2024-02-01')], + [Literal(dialect, 3), Literal(dialect, 3), Literal(dialect, 2), Literal(dialect, 600000), Literal(dialect, '2024-03-01')], + [Literal(dialect, 4), Literal(dialect, 2), Literal(dialect, 1), Literal(dialect, 450000), Literal(dialect, '2024-04-01')], + [Literal(dialect, 5), Literal(dialect, 6), Literal(dialect, 5), Literal(dialect, 1000000), Literal(dialect, '2024-05-01')], +]) +backend.execute(*InsertExpression(dialect, 'transactions', columns=['id', 'source_account_id', 'target_account_id', 'amount', 'ts'], source=txn_data).to_sql()) + +# ============================================================ +# SECTION: Scenario 1 — Social Network N-degree Recommendation +# Equivalent to PGQ: MATCH (a:person {name: 'Alice'})-[e:follows]+{1,3}->(b:person) +# ============================================================ +print("=" * 72) +print("Scenario 1: Social Network N-degree Friend Recommendations") +print("Equivalent: MATCH (a)-[e:follows]+{1,3}->(b)") +print("Technique: Recursive CTE + UNION ALL + depth filter ({1,3})") +print("=" * 72) + +# --- Base query: start vertex Alice, depth=0 --- +base_query = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'id'), + Column(dialect, 'name'), + Literal(dialect, 0).as_('depth'), + ], + from_=TableExpression(dialect, 'users'), + where=WhereClause(dialect, condition=Column(dialect, 'name') == Literal(dialect, 'Alice')), +) + +# --- Recursive query: follow edges one hop, depth+1, bound depth<4 --- +recursive_join = JoinExpression( + dialect=dialect, + left_table=TableExpression(dialect, 'traversal', alias='t'), + right_table=TableExpression(dialect, 'follows', alias='f'), + join_type='INNER JOIN', + condition=Column(dialect, 'id', table='t') == Column(dialect, 'follower_id', table='f'), +).inner_join( + right_table=TableExpression(dialect, 'users', alias='u'), + condition=Column(dialect, 'followed_id', table='f') == Column(dialect, 'id', table='u'), +) + +recursive_query = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'id', table='u'), + Column(dialect, 'name', table='u'), + FunctionCall(dialect, '+', Column(dialect, 'depth', table='t'), Literal(dialect, 1)).as_('depth'), + ], + from_=recursive_join, + where=WhereClause(dialect, condition=Column(dialect, 'depth', table='t') < Literal(dialect, 4)), +) + +# --- Combine with UNION ALL --- +union_expr = SetOperationExpression( + dialect=dialect, + left=base_query, + right=recursive_query, + operation='UNION ALL', +) + +# --- Define CTE --- +traversal_cte = CTEExpression( + dialect=dialect, + name='traversal', + query=union_expr, + columns=['id', 'name', 'depth'], +) + +# --- Main query: filter depth 1~3 (simulating quantified path {1,3}) --- +friend_query = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'id'), + Column(dialect, 'name'), + Column(dialect, 'depth'), + ], + from_=TableExpression(dialect, 'traversal'), + where=WhereClause(dialect, condition=BetweenPredicate( + dialect, Column(dialect, 'depth'), Literal(dialect, 1), Literal(dialect, 3), + )), + order_by=OrderByClause(dialect, [Column(dialect, 'depth'), Column(dialect, 'name')]), +) + +with_query = WithQueryExpression( + dialect=dialect, + ctes=[traversal_cte], + main_query=friend_query, + recursive=True, +) + +sql, params = with_query.to_sql() +print(f"\nGenerated SQL:\n{sql}\n") +result = backend.execute(sql, params, options=dql_options) +print("Friend recommendations for Alice (depth 1~3):") +for row in result.data or []: + print(f" depth={row['depth']}: {row['name']}") +print() + +# ============================================================ +# SECTION: Scenario 2 — AML Fund Source Tracing +# Equivalent to PGQ: MATCH (s:account)-[t:transfer*1..5]->(d:account {id: 1}) +# ============================================================ +print("=" * 72) +print("Scenario 2: AML Fund Source Tracing") +print("Equivalent: MATCH (s)-[t:transfer*1..5]->(d {id: 1})") +print("Technique: Recursive CTE + UNION ALL + depth limit + amount aggregation") +print("=" * 72) + +# --- Base query: direct sources sending to target account ACC-001 --- +aml_base_join = JoinExpression( + dialect=dialect, + left_table=TableExpression(dialect, 'transactions', alias='tx'), + right_table=TableExpression(dialect, 'accounts', alias='a'), + join_type='INNER JOIN', + condition=Column(dialect, 'source_account_id', table='tx') == Column(dialect, 'id', table='a'), +) + +aml_base = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'id', table='a'), + Column(dialect, 'account_holder', table='a'), + Column(dialect, 'account_type', table='a'), + Column(dialect, 'amount', table='tx'), + Literal(dialect, 1).as_('depth'), + ], + from_=aml_base_join, + where=WhereClause(dialect, condition=Column(dialect, 'target_account_id', table='tx') == Literal(dialect, 1)), +) + +# --- Recursive query: trace upstream along the transaction chain --- +aml_recursive_join = JoinExpression( + dialect=dialect, + left_table=TableExpression(dialect, 'fund_trace', alias='tr'), + right_table=TableExpression(dialect, 'transactions', alias='tx'), + join_type='INNER JOIN', + condition=Column(dialect, 'target_account_id', table='tx') == Column(dialect, 'id', table='tr'), +).inner_join( + right_table=TableExpression(dialect, 'accounts', alias='a'), + condition=Column(dialect, 'source_account_id', table='tx') == Column(dialect, 'id', table='a'), +) + +aml_recursive = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'id', table='a'), + Column(dialect, 'account_holder', table='a'), + Column(dialect, 'account_type', table='a'), + Column(dialect, 'amount', table='tx'), + FunctionCall(dialect, '+', Column(dialect, 'depth', table='tr'), Literal(dialect, 1)).as_('depth'), + ], + from_=aml_recursive_join, + where=WhereClause(dialect, condition=Column(dialect, 'depth', table='tr') < Literal(dialect, 5)), +) + +# --- Combine with UNION ALL --- +aml_union = SetOperationExpression( + dialect=dialect, + left=aml_base, + right=aml_recursive, + operation='UNION ALL', +) + +# --- Define CTE --- +fund_trace_cte = CTEExpression( + dialect=dialect, + name='fund_trace', + query=aml_union, + columns=['id', 'account_holder', 'account_type', 'amount', 'depth'], +) + +# --- Main query: list the full fund chain --- +aml_query = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'id'), + Column(dialect, 'account_holder'), + Column(dialect, 'account_type'), + Column(dialect, 'amount'), + Column(dialect, 'depth'), + ], + from_=TableExpression(dialect, 'fund_trace'), + where=WhereClause(dialect, condition=Column(dialect, 'depth') >= Literal(dialect, 1)), + order_by=OrderByClause(dialect, [Column(dialect, 'depth'), Column(dialect, 'id')]), +) + +aml_with = WithQueryExpression( + dialect=dialect, + ctes=[fund_trace_cte], + main_query=aml_query, + recursive=True, +) + +sql, params = aml_with.to_sql() +print(f"\nGenerated SQL:\n{sql}\n") +result = backend.execute(sql, params, options=dql_options) +print("Fund source tracing (upstream chain for Alice Smith / ACC-001):") +print(f"{'depth':<6} {'Holder':<20} {'Type':<12} {'Amount':>12}") +print("-" * 52) +for row in result.data or []: + print(f" {row['depth']:<4} {row['account_holder']:<20} {row['account_type']:<12} {float(row['amount']):>10,.0f}") + +# --- Aggregation query: total suspicious amount per depth level --- +agg_query = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, 'depth'), + FunctionCall(dialect, 'COUNT', Column(dialect, 'id'), alias='account_count'), + FunctionCall(dialect, 'SUM', Column(dialect, 'amount'), alias='total_amount'), + ], + from_=TableExpression(dialect, 'fund_trace'), + order_by=OrderByClause(dialect, [Column(dialect, 'depth')]), +) + +agg_with = WithQueryExpression( + dialect=dialect, + ctes=[fund_trace_cte], + main_query=agg_query, + recursive=True, +) + +sql, params = agg_with.to_sql() +result = backend.execute(sql, params, options=dql_options) +print("\nAggregation: total suspicious amount per depth level") +print(f"{'depth':<8} {'Accounts':<10} {'Total Amount':>14}") +print("-" * 34) +for row in result.data or []: + print(f" {row['depth']:<6} {row['account_count']:<10} {float(row['total_amount']):>10,.0f}") + +# ============================================================ +# SECTION: Summary (key technical points) +# ============================================================ +print() +print("=" * 72) +print("Summary / Key Technical Points") +print("=" * 72) +print(""" +1. SetOperationExpression(operation='UNION ALL') combines base + recursive queries +2. CTEExpression(name=..., query=..., columns=...) defines the CTE column signature +3. WithQueryExpression(recursive=True) generates WITH RECURSIVE +4. Recursive query joins the CTE reference with physical tables via chained JoinExpression +5. depth field simulates the hop count of quantified path patterns +6. WHERE depth BETWEEN x AND y simulates {x,y} range constraint +7. Use cases: social recommendations, fund tracing, knowledge graph traversal +""") + +# ============================================================ +# SECTION: Teardown (reference only — cleanup resources) +# ============================================================ +for t in ("transactions", "accounts", "follows", "users"): + sql, params = DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql() + backend.execute(sql, params) +backend.disconnect() diff --git a/tests/benchmark/activerecord_bulk/__init__.py b/tests/benchmark/activerecord_bulk/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/benchmark/activerecord_bulk/test_bulk_create.py b/tests/benchmark/activerecord_bulk/test_bulk_create.py index 6713f01e..8abfa754 100644 --- a/tests/benchmark/activerecord_bulk/test_bulk_create.py +++ b/tests/benchmark/activerecord_bulk/test_bulk_create.py @@ -1,3 +1,5 @@ # Bridge file for bulk_create benchmark tests from the testsuite. -from rhosocial.activerecord.testsuite.benchmark.crud.test_bulk_create_sync import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.benchmark.crud.test_bulk_create import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.benchmark.crud.test_bulk_create_async import * # noqa: F403 from rhosocial.activerecord.testsuite.benchmark.crud.test_bulk_create_async import * # noqa: F401, F403 + diff --git a/tests/benchmark/backend/__init__.py b/tests/benchmark/backend/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py new file mode 100644 index 00000000..fa0bf12c --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py @@ -0,0 +1,502 @@ +# tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py +""" +Recursive CTE graph traversal tests — PGQ quantified path alternative. + +These tests verify that recursive CTE + SetOperationExpression + UNION ALL +correctly simulates PGQ quantified path patterns (not natively supported +in PostgreSQL 19). + +Scenarios: +1. Social Network: N-degree friend recommendations +2. Anti-Money Laundering: Fund source tracing +""" +import pytest +import pytest_asyncio + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, + InsertExpression, + ValuesSource, + DropTableExpression, + QueryExpression, + TableExpression, + CTEExpression, + WithQueryExpression, + SetOperationExpression, + FunctionCall, + ColumnDefinition, + ColumnConstraint, + ColumnConstraintType, +) +from rhosocial.activerecord.backend.expression.core import Literal, Column +from rhosocial.activerecord.backend.expression.predicates import BetweenPredicate +from rhosocial.activerecord.backend.expression.query_parts import ( + WhereClause, + OrderByClause, + JoinExpression, +) + + +@pytest.fixture +def social_network_data(postgres_backend): + """Fixture: create users + follows tables with test data.""" + backend = postgres_backend + dialect = backend.dialect + + for t in ("follows", "users"): + backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) + + backend.execute(*CreateTableExpression(dialect, "users", [ + ColumnDefinition("id", "INTEGER", constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", "VARCHAR(100)"), + ]).to_sql()) + + backend.execute(*CreateTableExpression(dialect, "follows", [ + ColumnDefinition("id", "INTEGER", constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("follower_id", "INTEGER"), + ColumnDefinition("followed_id", "INTEGER"), + ]).to_sql()) + + backend.execute(*InsertExpression(dialect, "users", columns=["id", "name"], + source=ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, "Alice")], + [Literal(dialect, 2), Literal(dialect, "Bob")], + [Literal(dialect, 3), Literal(dialect, "Charlie")], + [Literal(dialect, 4), Literal(dialect, "Diana")], + [Literal(dialect, 5), Literal(dialect, "Eve")], + ])).to_sql()) + + backend.execute(*InsertExpression(dialect, "follows", columns=["id", "follower_id", "followed_id"], + source=ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 1), Literal(dialect, 2)], + [Literal(dialect, 2), Literal(dialect, 2), Literal(dialect, 3)], + [Literal(dialect, 3), Literal(dialect, 1), Literal(dialect, 3)], + [Literal(dialect, 4), Literal(dialect, 4), Literal(dialect, 1)], + ])).to_sql()) + + yield + + for t in ("follows", "users"): + backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) + + +@pytest.fixture +def aml_data(postgres_backend): + """Fixture: create accounts + transactions tables with test data.""" + backend = postgres_backend + dialect = backend.dialect + + for t in ("transactions", "accounts"): + backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) + + backend.execute(*CreateTableExpression(dialect, "accounts", [ + ColumnDefinition("id", "INTEGER", constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("account_holder", "VARCHAR(100)"), + ColumnDefinition("account_type", "VARCHAR(20)"), + ]).to_sql()) + + backend.execute(*CreateTableExpression(dialect, "transactions", [ + ColumnDefinition("id", "INTEGER", constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("source_account_id", "INTEGER"), + ColumnDefinition("target_account_id", "INTEGER"), + ColumnDefinition("amount", "NUMERIC(12,2)"), + ]).to_sql()) + + backend.execute(*InsertExpression(dialect, "accounts", columns=["id", "account_holder", "account_type"], + source=ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, "Alice Smith"), Literal(dialect, "checking")], + [Literal(dialect, 2), Literal(dialect, "Bob Corp"), Literal(dialect, "business")], + [Literal(dialect, 3), Literal(dialect, "Charlie Ltd"), Literal(dialect, "business")], + [Literal(dialect, 4), Literal(dialect, "Diana Offshore"), Literal(dialect, "offshore")], + [Literal(dialect, 5), Literal(dialect, "Eve Holding"), Literal(dialect, "offshore")], + ])).to_sql()) + + backend.execute(*InsertExpression(dialect, "transactions", columns=[ + "id", "source_account_id", "target_account_id", "amount"], + source=ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 4), Literal(dialect, 3), Literal(dialect, 500000)], + [Literal(dialect, 2), Literal(dialect, 5), Literal(dialect, 3), Literal(dialect, 300000)], + [Literal(dialect, 3), Literal(dialect, 3), Literal(dialect, 2), Literal(dialect, 600000)], + [Literal(dialect, 4), Literal(dialect, 2), Literal(dialect, 1), Literal(dialect, 450000)], + ])).to_sql()) + + yield + + for t in ("transactions", "accounts"): + backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) + + +class TestSocialNetworkTraversal: + """Recursive CTE for N-degree friend recommendations.""" + + def _build_traversal_cte(self, dialect): + """Build recursive CTE expression for social network traversal.""" + base = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id"), + Column(dialect, "name"), + Literal(dialect, 0).as_("depth"), + ], + from_=TableExpression(dialect, "users"), + where=WhereClause(dialect, condition=Column(dialect, "name") == Literal(dialect, "Alice")), + ) + + recursive_join = JoinExpression( + dialect=dialect, + left_table=TableExpression(dialect, "traversal", alias="t"), + right_table=TableExpression(dialect, "follows", alias="f"), + join_type="INNER JOIN", + condition=Column(dialect, "id", table="t") == Column(dialect, "follower_id", table="f"), + ).inner_join( + right_table=TableExpression(dialect, "users", alias="u"), + condition=Column(dialect, "followed_id", table="f") == Column(dialect, "id", table="u"), + ) + + recursive = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id", table="u"), + Column(dialect, "name", table="u"), + FunctionCall(dialect, "+", Column(dialect, "depth", table="t"), Literal(dialect, 1)).as_("depth"), + ], + from_=recursive_join, + where=WhereClause(dialect, condition=Column(dialect, "depth", table="t") < Literal(dialect, 4)), + ) + + union = SetOperationExpression( + dialect=dialect, + left=base, + right=recursive, + operation="UNION ALL", + ) + + return CTEExpression( + dialect=dialect, + name="traversal", + query=union, + columns=["id", "name", "depth"], + ) + + def test_friend_recommendation_depth_1_to_3(self, postgres_backend, social_network_data): + """Q1: Friend recommendations at depths 1-3 (simulate {1,3}).""" + dialect = postgres_backend.dialect + cte = self._build_traversal_cte(dialect) + + main = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id"), + Column(dialect, "name"), + Column(dialect, "depth"), + ], + from_=TableExpression(dialect, "traversal"), + where=WhereClause(dialect, condition=BetweenPredicate( + dialect, Column(dialect, "depth"), Literal(dialect, 1), Literal(dialect, 3), + )), + order_by=OrderByClause(dialect, [Column(dialect, "depth"), Column(dialect, "name")]), + ) + + with_query = WithQueryExpression( + dialect=dialect, + ctes=[cte], + main_query=main, + recursive=True, + ) + + sql, params = with_query.to_sql() + rows = postgres_backend.fetch_all(sql, params) + names_at_depth = {r["name"]: r["depth"] for r in rows} + + # Alice follows Bob (depth 1) and Charlie (depth 1) + assert names_at_depth.get("Bob") == 1 + assert names_at_depth.get("Charlie") == 1 + # Bob follows Diana (depth 2) + assert names_at_depth.get("Diana") == 2 + + def test_friend_recommendation_depth_2(self, postgres_backend, social_network_data): + """Q2: Only depth=2 (simulate {2,2}).""" + dialect = postgres_backend.dialect + cte = self._build_traversal_cte(dialect) + + main = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id"), + Column(dialect, "name"), + Column(dialect, "depth"), + ], + from_=TableExpression(dialect, "traversal"), + where=WhereClause(dialect, condition=Column(dialect, "depth") == Literal(dialect, 2)), + order_by=OrderByClause(dialect, [Column(dialect, "name")]), + ) + + with_query = WithQueryExpression( + dialect=dialect, + ctes=[cte], + main_query=main, + recursive=True, + ) + + sql, params = with_query.to_sql() + rows = postgres_backend.fetch_all(sql, params) + names = [r["name"] for r in rows] + assert "Diana" in names + assert len(names) == 1 + + def test_exclude_start_node(self, postgres_backend, social_network_data): + """Q3: Exclude Alice herself by filtering depth >= 1.""" + dialect = postgres_backend.dialect + cte = self._build_traversal_cte(dialect) + + main = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id"), + Column(dialect, "name"), + Column(dialect, "depth"), + ], + from_=TableExpression(dialect, "traversal"), + where=WhereClause(dialect, condition=Column(dialect, "depth") >= Literal(dialect, 1)), + order_by=OrderByClause(dialect, [Column(dialect, "depth"), Column(dialect, "name")]), + ) + + with_query = WithQueryExpression( + dialect=dialect, + ctes=[cte], + main_query=main, + recursive=True, + ) + + sql, params = with_query.to_sql() + rows = postgres_backend.fetch_all(sql, params) + names = [r["name"] for r in rows] + assert "Alice" not in names + assert len(names) == 3 # Bob, Charlie, Diana + + +class TestAMLFundTracing: + """Recursive CTE for fund source tracing (AML).""" + + def _build_aml_cte(self, dialect): + """Build recursive CTE expression for fund tracing.""" + base_join = JoinExpression( + dialect=dialect, + left_table=TableExpression(dialect, "transactions", alias="tx"), + right_table=TableExpression(dialect, "accounts", alias="a"), + join_type="INNER JOIN", + condition=Column(dialect, "source_account_id", table="tx") == Column(dialect, "id", table="a"), + ) + + base = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id", table="a"), + Column(dialect, "account_holder", table="a"), + Column(dialect, "account_type", table="a"), + Column(dialect, "amount", table="tx"), + Literal(dialect, 1).as_("depth"), + ], + from_=base_join, + where=WhereClause( + dialect, + condition=Column(dialect, "target_account_id", table="tx") + == Literal(dialect, 1), + ), + ) + + recursive_join = JoinExpression( + dialect=dialect, + left_table=TableExpression(dialect, "fund_trace", alias="tr"), + right_table=TableExpression(dialect, "transactions", alias="tx"), + join_type="INNER JOIN", + condition=Column(dialect, "target_account_id", table="tx") == Column(dialect, "id", table="tr"), + ).inner_join( + right_table=TableExpression(dialect, "accounts", alias="a"), + condition=Column(dialect, "source_account_id", table="tx") == Column(dialect, "id", table="a"), + ) + + recursive = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id", table="a"), + Column(dialect, "account_holder", table="a"), + Column(dialect, "account_type", table="a"), + Column(dialect, "amount", table="tx"), + FunctionCall(dialect, "+", Column(dialect, "depth", table="tr"), Literal(dialect, 1)).as_("depth"), + ], + from_=recursive_join, + where=WhereClause(dialect, condition=Column(dialect, "depth", table="tr") < Literal(dialect, 5)), + ) + + union = SetOperationExpression( + dialect=dialect, + left=base, + right=recursive, + operation="UNION ALL", + ) + + return CTEExpression( + dialect=dialect, + name="fund_trace", + query=union, + columns=["id", "account_holder", "account_type", "amount", "depth"], + ) + + def test_fund_trace_chain(self, postgres_backend, aml_data): + """Trace fund sources for account ACC-001.""" + dialect = postgres_backend.dialect + cte = self._build_aml_cte(dialect) + + main = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id"), + Column(dialect, "account_holder"), + Column(dialect, "depth"), + ], + from_=TableExpression(dialect, "fund_trace"), + where=WhereClause(dialect, condition=Column(dialect, "depth") >= Literal(dialect, 1)), + order_by=OrderByClause(dialect, [Column(dialect, "depth"), Column(dialect, "id")]), + ) + + with_query = WithQueryExpression( + dialect=dialect, + ctes=[cte], + main_query=main, + recursive=True, + ) + + sql, params = with_query.to_sql() + rows = postgres_backend.fetch_all(sql, params) + account_names = [r["account_holder"] for r in rows] + # Direct source: Bob Corp (depth 1) + assert "Bob Corp" in account_names + # Indirect: Charlie Ltd (depth 2) -> Bob Corp + assert "Charlie Ltd" in account_names + # Indirect: Diana Offshore (depth 3) -> Charlie Ltd + assert "Diana Offshore" in account_names + + def test_fund_trace_depth_filter(self, postgres_backend, aml_data): + """Filter fund sources by minimum depth.""" + dialect = postgres_backend.dialect + cte = self._build_aml_cte(dialect) + + main = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "id"), + Column(dialect, "account_holder"), + Column(dialect, "depth"), + ], + from_=TableExpression(dialect, "fund_trace"), + where=WhereClause(dialect, condition=Column(dialect, "depth") >= Literal(dialect, 2)), + order_by=OrderByClause(dialect, [Column(dialect, "depth"), Column(dialect, "id")]), + ) + + with_query = WithQueryExpression( + dialect=dialect, + ctes=[cte], + main_query=main, + recursive=True, + ) + + sql, params = with_query.to_sql() + rows = postgres_backend.fetch_all(sql, params) + depths = [r["depth"] for r in rows] + assert all(d >= 2 for d in depths) + # Depth 2: Charlie Ltd (via Bob Corp) + assert any(r["account_holder"] == "Charlie Ltd" for r in rows) + + def test_fund_trace_aggregation(self, postgres_backend, aml_data): + """Aggregate total amount by depth level.""" + dialect = postgres_backend.dialect + cte = self._build_aml_cte(dialect) + + agg_main = QueryExpression( + dialect=dialect, + select=[ + Column(dialect, "depth"), + FunctionCall(dialect, "SUM", Column(dialect, "amount"), alias="total"), + ], + from_=TableExpression(dialect, "fund_trace"), + order_by=OrderByClause(dialect, [Column(dialect, "depth")]), + ) + + with_query = WithQueryExpression( + dialect=dialect, + ctes=[cte], + main_query=agg_main, + recursive=True, + ) + + sql, params = with_query.to_sql() + rows = postgres_backend.fetch_all(sql, params) + totals = {r["depth"]: float(r["total"]) for r in rows} + # Depth 1: Bob Corp sent 450000 to Alice Smith + assert totals.get(1) == 450000 + # Depth 2: Charlie Ltd sent 600000 to Bob Corp + assert totals.get(2) == 600000 + # Depth 3: Diana Offshore sent 500000 to Charlie Ltd + assert totals.get(3) == 500000 + + +class TestAsyncRecursiveCTEGraph: + """Async versions of recursive CTE graph tests.""" + + @pytest_asyncio.fixture + async def async_social_network_data(self, async_postgres_backend): + backend = async_postgres_backend + dialect = backend.dialect + + for t in ("follows", "users"): + await backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) + + await backend.execute(*CreateTableExpression(dialect, "users", [ + ColumnDefinition("id", "INTEGER", constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", "VARCHAR(100)"), + ]).to_sql()) + + await backend.execute(*CreateTableExpression(dialect, "follows", [ + ColumnDefinition("id", "INTEGER", constraints=[ + ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("follower_id", "INTEGER"), + ColumnDefinition("followed_id", "INTEGER"), + ]).to_sql()) + + await backend.execute(*InsertExpression(dialect, "users", columns=["id", "name"], + source=ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, "Alice")], + [Literal(dialect, 2), Literal(dialect, "Bob")], + ])).to_sql()) + + await backend.execute(*InsertExpression(dialect, "follows", columns=["id", "follower_id", "followed_id"], + source=ValuesSource(dialect, [ + [Literal(dialect, 1), Literal(dialect, 1), Literal(dialect, 2)], + ])).to_sql()) + + yield + + for t in ("follows", "users"): + await backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) + + @pytest.mark.asyncio + async def test_async_single_hop(self, async_postgres_backend, async_social_network_data): + """Async: Alice's 1-hop friends.""" + dialect = async_postgres_backend.dialect + base = QueryExpression( + dialect=dialect, + select=[Column(dialect, "id"), Column(dialect, "name"), Literal(dialect, 0).as_("depth")], + from_=TableExpression(dialect, "users"), + where=WhereClause(dialect, condition=Column(dialect, "name") == Literal(dialect, "Alice")), + ) + result = base.to_sql() + sql, params = result + rows = await async_postgres_backend.fetch_all(sql, params) + assert len(rows) == 1 + assert rows[0]["name"] == "Alice" diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context.py index 8f7e8cf3..b5ecbf1d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context.py @@ -12,3 +12,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.basic.connection.test_active_record_context import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.connection.test_active_record_context_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud.py index eb4e162b..c9488510 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud.py @@ -12,3 +12,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.basic.connection.test_active_record_crud import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.connection.test_active_record_crud_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py index 9c3e81ba..4cd150ba 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py @@ -16,3 +16,5 @@ # The `TestBasicMappedModels` class contains the actual test cases. from rhosocial.activerecord.testsuite.feature.basic.test_example_basic_fixtures import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_example_basic_fixtures_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py index e0936eda..bdacfa62 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py @@ -9,3 +9,5 @@ ) from rhosocial.activerecord.testsuite.feature.basic.test_bulk_operations import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_bulk_operations_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py index c434da79..474f2c71 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py @@ -28,3 +28,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.basic.test_crud import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_crud_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py index e5503f88..7940c241 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py @@ -21,3 +21,5 @@ # The test classes contain the actual test cases. from rhosocial.activerecord.testsuite.feature.basic.test_field_column_mapping import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_field_column_mapping_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py index ed3bdd00..a409bbbf 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py @@ -21,3 +21,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.basic.test_fields import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_fields_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py index 752b0a1b..1a0848e5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py @@ -14,3 +14,5 @@ ) from rhosocial.activerecord.testsuite.feature.basic.test_pydantic_native_validation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_pydantic_native_validation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py index f9841654..9e30dff5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py @@ -14,3 +14,5 @@ # Import all tests from the generic testsuite file. from rhosocial.activerecord.testsuite.feature.basic.test_type_adapter import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_type_adapter_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py index b522ba20..54666c6b 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py @@ -21,3 +21,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.basic.test_validation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.test_validation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management.py index 4486e093..a5150e06 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management.py @@ -8,3 +8,5 @@ user_class_for_worker, # noqa: F401 ) from rhosocial.activerecord.testsuite.feature.basic.worker.test_connection_management import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.worker.test_connection_management_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud.py index c75a4e8c..287d0828 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud.py @@ -9,3 +9,5 @@ async_user_class_for_worker, # noqa: F401 ) from rhosocial.activerecord.testsuite.feature.basic.worker.test_parallel_crud import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.worker.test_parallel_crud_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle.py index 53d0a0d0..7df4d9df 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle.py @@ -8,3 +8,5 @@ user_class_for_worker, # noqa: F401 ) from rhosocial.activerecord.testsuite.feature.basic.worker.test_worker_pool_lifecycle import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.worker.test_worker_pool_lifecycle_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py index 66e9faae..6ae36196 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py @@ -1,2 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py from rhosocial.activerecord.testsuite.feature.composite_pk.test_active_query import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.composite_pk.test_active_query_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py index a4b222c9..08112386 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py @@ -1,2 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py from rhosocial.activerecord.testsuite.feature.composite_pk.test_activerecord import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.composite_pk.test_activerecord_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py index 6790f16b..57d58ab8 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py @@ -1,2 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py from rhosocial.activerecord.testsuite.feature.composite_pk.test_cte_query import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.composite_pk.test_cte_query_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py index 148c1b97..b485fde0 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py @@ -1,2 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py from rhosocial.activerecord.testsuite.feature.composite_pk.test_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.composite_pk.test_set_operation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers.py b/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers.py index 02d222d2..5964c8ba 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers.py @@ -21,3 +21,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.events.test_handlers import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.events.test_handlers_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle.py b/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle.py index 00b29520..43bb6d83 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle.py @@ -21,3 +21,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.events.test_lifecycle import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.events.test_lifecycle_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles.py index a63c2d24..c0c81379 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles.py @@ -7,3 +7,5 @@ """ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.mixins.test_combined_articles import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.mixins.test_combined_articles_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock.py index 9fd9601a..be5611e5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock.py @@ -7,3 +7,5 @@ """ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.mixins.test_optimistic_lock import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.mixins.test_optimistic_lock_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete.py index e440e286..40a34336 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete.py @@ -7,3 +7,5 @@ """ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.mixins.test_soft_delete import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.mixins.test_soft_delete_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps.py index d776bdbf..64344372 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps.py @@ -7,3 +7,5 @@ """ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.mixins.test_timestamps import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.mixins.test_timestamps_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context.py b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context.py index 403c1a9e..9fb3d2f7 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context.py @@ -12,3 +12,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.query.connection.test_active_query_context import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.connection.test_active_query_context_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context.py b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context.py index 1483e319..0b1e6153 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context.py @@ -12,3 +12,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.query.connection.test_cte_query_context import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.connection.test_cte_query_context_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context.py b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context.py index f6acecbf..ef0d192a 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context.py @@ -12,3 +12,5 @@ # Import shared tests from testsuite package from rhosocial.activerecord.testsuite.feature.query.connection.test_set_operation_context import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.connection.test_set_operation_context_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py index 75f400d0..4490f75c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_active_query_aggregate import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_active_query_aggregate_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py index c8e67b7d..b48c30f4 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_active_query_basic import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_active_query_basic_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py index 4f750e5b..ea6a7e26 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_active_query_join import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_active_query_join_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py index af663f25..14fe4369 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_active_query_range import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_active_query_range_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py index 04171825..5df3b6c5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_active_query_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_active_query_set_operation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py index 12c99783..7f0d8321 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_aggregate_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_aggregate_queries_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py index f56b9765..809e6cdf 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_basic import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_basic_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py index ce5c074a..8d5fb7dd 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_cross_database_compatibility import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_cross_database_compatibility_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py index 1ce77f68..401c9b16 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_cte_query_active_query import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_cte_query_active_query_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py index 2610511f..2acfc13c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_cte_query_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_cte_query_set_operation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py index 2c23a774..a4e230f6 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py @@ -3,3 +3,5 @@ Bridge file for eager loading caching tests. """ from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_caching import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_caching_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py index c90a39ef..e6430701 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py @@ -3,3 +3,5 @@ Bridge file for eager loading nested tests. """ from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_nested import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_nested_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py index ff5df366..414db2d8 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py @@ -1,5 +1,7 @@ -# tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py +# tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios.py """ Bridge file for sync eager loading scenario tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_scenarios_sync import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_scenarios import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_scenarios_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py index c298cef3..963fbdf0 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py @@ -3,3 +3,5 @@ Bridge file for eager loading with_all tests. """ from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_all import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_all_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py index 433a7051..1df4dacb 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py @@ -3,3 +3,5 @@ Bridge file for eager loading with_modifier tests. """ from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_modifier import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_modifier_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py index df162324..96fe7a48 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py @@ -3,3 +3,5 @@ Bridge file for eager loading with_one tests. """ from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_one import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_one_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py index d30ddc60..b92024f3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_error_handling import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_error_handling_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py index 79aa6592..15af9f04 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_example_query_fixtures import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_example_query_fixtures_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py index 56a0a911..ad9af333 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_joins import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_joins_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py index fd09e85d..5a630984 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_logging_data_summarization import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_logging_data_summarization_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_sync_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py similarity index 70% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_sync_queries.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py index 75205a5d..b641f1f3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_sync_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py @@ -1,8 +1,8 @@ -# tests/rhosocial/activerecord_postgres_test/feature/query/test_sync_queries.py +# tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py """ Bridge file for synchronous query tests from the testsuite. This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_sync_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_queries import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_async_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py similarity index 69% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_async_queries.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py index ef4556d1..bd9acb01 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_async_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py @@ -1,8 +1,9 @@ -# tests/rhosocial/activerecord_postgres_test/feature/query/test_async_queries.py +# tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py """ Bridge file for async queries tests from the testsuite. This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_async_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_queries_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py index ad5b3b08..2c250fd1 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_query_optimization import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_query_optimization_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py index f6027323..bc33ff4c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_range_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_range_queries_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py index 62bd6a32..28c994f8 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_range_query_detailed import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_range_query_detailed_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py index def8f01d..f4766f1f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_relational_validation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_relational_validation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py index fbf463d4..272293f6 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_relations_basic import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_relations_basic_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py index 9d4a3e0e..5ff8e35c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py @@ -2,4 +2,4 @@ """ Bridge file for sync relation 'with' tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_relations_with_sync import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.test_relations_with import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py index fc7158fb..ea4cfc60 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_set_operation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py index b9eb5614..774bf73c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_special_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_special_queries_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py index aa5d6cc7..a665981a 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py @@ -6,3 +6,5 @@ discoverable by pytest in this project's test run. """ from rhosocial.activerecord.testsuite.feature.query.test_window_functions import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.test_window_functions_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries.py index 7bfe5ecb..f7927c07 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries.py @@ -9,3 +9,5 @@ async_order_fixtures_for_worker, # noqa: F401 ) from rhosocial.activerecord.testsuite.feature.query.worker.test_parallel_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.worker.test_parallel_queries_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py index c7590e84..9913196c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py @@ -10,3 +10,5 @@ async_order_fixtures_for_worker, # noqa: F401 ) from rhosocial.activerecord.testsuite.feature.query.worker.test_transaction_isolation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.worker.test_transaction_isolation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py new file mode 100644 index 00000000..d27862a6 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py @@ -0,0 +1 @@ +from rhosocial.activerecord.testsuite.feature.relation.test_base_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py index 7e44d5a1..4c51f85c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_boundary import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_boundary_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py new file mode 100644 index 00000000..d0fb6c14 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py @@ -0,0 +1 @@ +from rhosocial.activerecord.testsuite.feature.relation.test_cache_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py index 4a4b246f..5ec530a6 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_cache_clearing import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_cache_clearing_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py index 21564139..48265b0d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_derived_field import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py index 67251dc4..8c84a87f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_json import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_json_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py index 47c04e0f..203c1732 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py @@ -1,3 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py -from rhosocial.activerecord.testsuite.feature.relation.test_descriptor_compat import * # noqa: F401,F403 \ No newline at end of file +from rhosocial.activerecord.testsuite.feature.relation.test_descriptor_compat import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_descriptor_compat_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py index ebd76ceb..de72e480 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_integration import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_integration_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py new file mode 100644 index 00000000..2ae8e7db --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py @@ -0,0 +1 @@ +from rhosocial.activerecord.testsuite.feature.relation.test_interfaces_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py index bbd9a806..196d1585 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_modifier_behavior import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_modifier_behavior_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py index eb29123d..1bdf5e5d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_modifier_warning import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_modifier_warning_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py new file mode 100644 index 00000000..272901e9 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py @@ -0,0 +1 @@ +from rhosocial.activerecord.testsuite.feature.relation.test_nested_relationship_access_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py index 37e7e8e9..5e35543f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_path_validation import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_path_validation_async import * # noqa: F403 + diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py index 0cebf814..c5ef8611 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py @@ -1,3 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py -from rhosocial.activerecord.testsuite.feature.relation.test_validator import * # noqa: F401,F403 \ No newline at end of file +from rhosocial.activerecord.testsuite.feature.relation.test_validator import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_validator_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py index fdb8bca6..0b998f59 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py @@ -1 +1,3 @@ from rhosocial.activerecord.testsuite.feature.relation.test_with_method import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.test_with_method_async import * # noqa: F403 + From 3d69c84457b298808867e6131779325b62fcbcab Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 9 Jul 2026 21:45:30 +0800 Subject: [PATCH 04/32] feat: add bridge files for derived_field, interface feature tests and relation benchmark - derived_field/conftest.py + test_derived_field_testsuite.py - interface/test_threadsafe_dict.py (sync + async combined) - benchmark/relation/conftest.py + test_cache_comparison.py --- tests/benchmark/relation/conftest.py | 158 ++++++++++++++++++ .../relation/test_cache_comparison.py | 75 +++++++++ .../feature/derived_field/conftest.py | 9 + .../test_derived_field_testsuite.py | 16 ++ .../feature/interface/test_threadsafe_dict.py | 7 + 5 files changed, 265 insertions(+) create mode 100644 tests/benchmark/relation/conftest.py create mode 100644 tests/benchmark/relation/test_cache_comparison.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict.py diff --git a/tests/benchmark/relation/conftest.py b/tests/benchmark/relation/conftest.py new file mode 100644 index 00000000..cce2ecbd --- /dev/null +++ b/tests/benchmark/relation/conftest.py @@ -0,0 +1,158 @@ +"""Benchmark fixtures for relation cache comparison. + +Bridges ``user_post_comment_classes`` from the testsuite's relation conftest +so that benchmarks can reuse the same model definitions and backend setup. + +Redis benchmark support is not introduced in this release. The related +source snippets are kept as comments for follow-up external cache design. +""" + +from rhosocial.activerecord.testsuite.feature.relation.conftest import ( # noqa: F401 + user_post_comment_classes, +) + +from dataclasses import dataclass +from typing import Any + +import pytest + +from rhosocial.activerecord.relation.cache import CacheConfig, InstanceCache +from rhosocial.activerecord.relation.cache_backends import InMemoryCache + +# Not introduced in this release; keep snippets for follow-up external cache design. +# from rhosocial.activerecord.relation.cache_backends import CacheSerializer, RedisCache + +pytestmark = [ + pytest.mark.benchmark, + pytest.mark.benchmark_relation, +] + + +def pytest_configure(config): + config.addinivalue_line("markers", "benchmark_relation: relation cache benchmark tests") + + +@dataclass +class RelationBenchmarkContext: + """Context for a single benchmark run.""" + + user: Any + post_class: Any + scenario: str + + +# --------------------------------------------------------------------------- +# Redis benchmark is not introduced in this release. Keep the fixture shape as +# comments for follow-up external cache design instead of deleting the source. +# --------------------------------------------------------------------------- +# @pytest.fixture(scope="session") +# def _redis_client(): +# redis = pytest.importorskip("redis") +# try: +# from providers.redis_scenarios import get_redis_scenario +# +# config = get_redis_scenario("benchmark") +# except (FileNotFoundError, KeyError): +# from rhosocial.activerecord.relation.cache_backends import RedisConfig +# +# config = RedisConfig.from_env() +# try: +# client = redis.Redis( +# host=config.host, +# port=config.port, +# password=config.password or None, +# db=config.db, +# socket_connect_timeout=config.socket_connect_timeout, +# ) +# client.ping() +# yield client +# client.flushdb() +# client.close() +# except Exception: +# pytest.skip("Redis not available for benchmark") +# yield None + + +# --------------------------------------------------------------------------- +# Shared model + data setup (function-scoped so each test gets fresh data) +# --------------------------------------------------------------------------- +@pytest.fixture(scope="function") +def _raw_models(user_post_comment_classes): # noqa: F811 + """Create a user + 100 posts + 200 comments under the same user.""" + user_cls, post_cls, comment_cls = user_post_comment_classes + user = user_cls(name="Bench User", email="bench@example.com") + user.save() + + for i in range(100): + p = post_cls(title=f"Post {i}", body=f"Body {i}", user_id=user.id) + p.save() + + yield user, post_cls, comment_cls + + for p in reversed(post_cls.find_all()): + p.delete() + user.delete() + + +# --------------------------------------------------------------------------- +# Cache scenario parametrization +# --------------------------------------------------------------------------- +CACHE_SCENARIOS = [ + pytest.param("no_cache", id="no_cache"), + pytest.param("in_memory", id="in_memory"), + # Not introduced in this release; keep for follow-up external cache design. + # pytest.param("redis", id="redis"), +] + + +@pytest.fixture(params=CACHE_SCENARIOS) +def cache_scenario(request): + """Parametrized cache scenario name.""" + return request.param + + +@pytest.fixture(scope="function") +def relation_benchmark_env(cache_scenario, _raw_models): + """Configure the InstanceCache backend and return a benchmark context. + + Each test function receives a ``RelationBenchmarkContext`` with: + - user — the seeded user instance + - post_class — for queries + - scenario — current scenario name + """ + user, post_cls, comment_cls = _raw_models + original_backend = InstanceCache._backend + + try: + if cache_scenario == "no_cache": + InstanceCache.set_backend(InMemoryCache()) + config = CacheConfig(enabled=False) + elif cache_scenario == "in_memory": + InstanceCache.set_backend(InMemoryCache()) + config = CacheConfig(enabled=True, ttl=None) + # Not introduced in this release; keep for follow-up external cache design. + # elif cache_scenario == "redis": + # InstanceCache.set_backend( + # RedisCache( + # client=_redis_client, + # prefix="bench:cache:", + # serializer=CacheSerializer(format="pickle"), + # ) + # ) + # config = CacheConfig(enabled=True, ttl=None) + else: + raise ValueError(f"Unknown scenario: {cache_scenario}") + + # Apply the cache config to all relation descriptors + for rel_name in ["posts", "comments"]: + rel = user.get_relation(rel_name) + if rel is not None: + rel._cache_config = config + + yield RelationBenchmarkContext( + user=user, + post_class=post_cls, + scenario=cache_scenario, + ) + finally: + InstanceCache.set_backend(original_backend) diff --git a/tests/benchmark/relation/test_cache_comparison.py b/tests/benchmark/relation/test_cache_comparison.py new file mode 100644 index 00000000..364b48af --- /dev/null +++ b/tests/benchmark/relation/test_cache_comparison.py @@ -0,0 +1,75 @@ +"""Benchmark comparing relation cache strategies. + +Measures HasMany relation access time under current-release scenarios: +- ``no_cache`` — CacheConfig(enabled=False), always queries DB +- ``in_memory`` — InMemoryCache, caches after first access + +Redis benchmark support is not introduced in this release. +""" + +import pytest + +pytestmark = [ + pytest.mark.benchmark, + pytest.mark.benchmark_relation, +] + + +class TestRelationFirstAccess: + """First access — always queries DB regardless of cache strategy.""" + + def test_posts_first_access(self, benchmark, relation_benchmark_env): + ctx = relation_benchmark_env + result = benchmark(ctx.user.posts) + assert len(result) == 100 + + +class TestRelationCachedAccess: + """Second+ access — cache hit (except no_cache which hits DB each time).""" + + def test_posts_second_access(self, benchmark, relation_benchmark_env): + ctx = relation_benchmark_env + ctx.user.posts() + result = benchmark(ctx.user.posts) + assert len(result) == 100 + + +class TestRelationRepeatedAccess: + """100 repeated accesses — amplifies cache vs no-cache gap.""" + + def test_posts_repeated_access(self, benchmark, relation_benchmark_env): + ctx = relation_benchmark_env + + def repeated(): + for _ in range(100): + ctx.user.posts() + + benchmark(repeated) + + +class TestRelationWithNewData: + """Access with new data — validates behaviour after mutations.""" + + def test_posts_after_new_record(self, benchmark, relation_benchmark_env): + """After a new record is added, cached backends return stale data. + + This is expected — cache invalidation is handled by model save/delete + events, not by the descriptor layer itself. + """ + ctx = relation_benchmark_env + ctx.user.posts() + ctx.post_class(title="New", body="New", user_id=ctx.user.id).save() + result = benchmark(ctx.user.posts) + if ctx.scenario == "no_cache": + assert len(result) == 101 + else: + assert len(result) == 100 # stale cache + + def test_posts_after_delete(self, benchmark, relation_benchmark_env): + ctx = relation_benchmark_env + ctx.user.posts() + p = ctx.post_class(title="Temp", body="Temp", user_id=ctx.user.id) + p.save() + p.delete() + result = benchmark(ctx.user.posts) + assert len(result) == 100 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py new file mode 100644 index 00000000..8921aa03 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py @@ -0,0 +1,9 @@ +# tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py +""" +Pytest configuration for derived field feature tests. + +This file imports fixtures from the corresponding testsuite, making them +available to the tests in this directory. +""" + +from rhosocial.activerecord.testsuite.feature.derived_field.conftest import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py b/tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py new file mode 100644 index 00000000..19981432 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py @@ -0,0 +1,16 @@ +# tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py +""" +Bridge file for derived field feature tests from the testsuite. +""" + +from rhosocial.activerecord.testsuite.feature.derived_field.conftest import ( # noqa: F401 + product_class, + product_form_a_class, + product_with_proxy_class, + product_with_column_and_adapter_class, + async_product_class, + async_product_with_proxy_class, + async_product_with_column_and_adapter_class, +) +from rhosocial.activerecord.testsuite.feature.derived_field.test_derived_field import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.derived_field.test_derived_field_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict.py b/tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict.py new file mode 100644 index 00000000..48cf1235 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict.py @@ -0,0 +1,7 @@ +# tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict.py +""" +Bridge file for ThreadSafeDict interface tests from the testsuite. +""" + +from rhosocial.activerecord.testsuite.feature.interface.test_threadsafe_dict import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.interface.test_threadsafe_dict_async import * # noqa: F403 From d5936ebc5e75097e8cdb87e35aab5090f84e5d47 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 12 Jul 2026 21:46:31 +0800 Subject: [PATCH 05/32] perf(backend): remove redundant fetchall() in get_server_version() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit execute() is atomic per DB-API 2.0 — the extra fetchall() after fetchone() is unnecessary and adds a round-trip for no benefit. Follow-up to commit ab3be40 which introduced these defensive calls. --- .../activerecord/backend/impl/postgres/backend/async_backend.py | 2 -- .../activerecord/backend/impl/postgres/backend/sync.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/rhosocial/activerecord/backend/impl/postgres/backend/async_backend.py b/src/rhosocial/activerecord/backend/impl/postgres/backend/async_backend.py index 19aeea55..7aba7eb1 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/backend/async_backend.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/backend/async_backend.py @@ -346,8 +346,6 @@ async def get_server_version(self) -> Optional[Tuple[int, int, int]]: cursor = await self._get_cursor() await cursor.execute("SELECT version()") version_row = await cursor.fetchone() - # Defensively consume remaining results to prevent cursor.description pollution - remaining = await cursor.fetchall() if not version_row or not version_row[0]: self.log(logging.WARNING, "PostgreSQL version query returned no result") return None diff --git a/src/rhosocial/activerecord/backend/impl/postgres/backend/sync.py b/src/rhosocial/activerecord/backend/impl/postgres/backend/sync.py index dce98ddb..b23d26fa 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/backend/sync.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/backend/sync.py @@ -347,8 +347,6 @@ def get_server_version(self) -> Optional[Tuple[int, int, int]]: cursor = self._get_cursor() cursor.execute("SELECT version()") result = cursor.fetchone() - # Defensively consume remaining results to prevent cursor.description pollution - cursor.fetchall() if not result or not result[0]: self.log(logging.WARNING, "PostgreSQL version query returned no result") return None From bae710ebc0da2401c90b8f445e50efc081bc5847 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 16 Jul 2026 10:24:25 +0800 Subject: [PATCH 06/32] refactor(postgres): merge composite_pk/derived_field, switch to expression-based DDL - Delete composite_pk/derived_field bridge files, schema SQL, and providers - Create tests/providers/fixtures/ with TABLE_EXPRESSIONS (basic, query, mixins, events) - Replace _load_postgres_schema with CreateTableExpression/DropTableExpression DDL - Update all query/relation bridge imports to new subdirectory paths - Fix _setup_model_async -> _setup_async_model typo in query provider --- tests/providers/basic.py | 420 +++++++-------- tests/providers/composite_pk.py | 130 ----- tests/providers/events.py | 127 +++-- tests/providers/fixtures/__init__.py | 14 + tests/providers/fixtures/_common.py | 34 ++ tests/providers/fixtures/basic.py | 499 ++++++++++++++++++ tests/providers/fixtures/events.py | 82 +++ tests/providers/fixtures/mixins.py | 132 +++++ tests/providers/fixtures/query.py | 383 ++++++++++++++ tests/providers/mixins.py | 153 +++--- tests/providers/query.py | 417 ++++++--------- tests/providers/registry.py | 37 +- tests/providers/relation.py | 295 ++++++----- .../feature/basic/test_basic_mapped_models.py | 4 +- .../feature/basic/test_bulk_operations.py | 4 +- .../feature/basic/test_crud.py | 4 +- .../basic/test_field_column_mapping.py | 4 +- .../feature/basic/test_fields.py | 4 +- .../basic/test_pydantic_native_validation.py | 4 +- .../feature/basic/test_type_adapter.py | 4 +- .../feature/basic/test_validation.py | 4 +- .../feature/composite_pk/conftest.py | 2 - .../composite_pk/schema/order_items.sql | 7 - .../feature/composite_pk/schema/orders.sql | 6 - .../composite_pk/schema/store_inventory.sql | 7 - .../test_composite_pk_active_query.py | 4 - .../test_composite_pk_activerecord.py | 4 - .../test_composite_pk_cte_query.py | 4 - .../test_composite_pk_set_operation.py | 4 - .../feature/derived_field/conftest.py | 9 - .../test_derived_field_testsuite.py | 16 - .../query/test_active_query_aggregate.py | 4 +- .../feature/query/test_active_query_basic.py | 4 +- .../feature/query/test_active_query_join.py | 4 +- .../feature/query/test_active_query_range.py | 4 +- .../query/test_active_query_set_operation.py | 4 +- .../feature/query/test_aggregate_queries.py | 4 +- .../feature/query/test_basic.py | 4 +- .../test_cross_database_compatibility.py | 4 +- .../query/test_cte_query_active_query.py | 4 +- .../query/test_cte_query_set_operation.py | 4 +- .../query/test_eager_loading_caching.py | 4 +- .../query/test_eager_loading_nested.py | 4 +- .../test_eager_loading_scenarios_async.py | 2 +- .../test_eager_loading_scenarios_sync.py | 4 +- .../query/test_eager_loading_with_all.py | 4 +- .../query/test_eager_loading_with_modifier.py | 4 +- .../query/test_eager_loading_with_one.py | 4 +- .../feature/query/test_error_handling.py | 4 +- .../query/test_example_query_fixtures.py | 4 +- .../feature/query/test_joins.py | 4 +- .../query/test_logging_data_summarization.py | 4 +- .../feature/query/test_queries.py | 2 +- .../feature/query/test_queries_async.py | 2 +- .../feature/query/test_query_optimization.py | 4 +- .../feature/query/test_range_queries.py | 4 +- .../query/test_range_query_detailed.py | 4 +- .../query/test_relational_validation.py | 4 +- .../feature/query/test_relations_basic.py | 4 +- .../query/test_relations_with_async.py | 2 +- .../feature/query/test_relations_with_sync.py | 2 +- .../feature/query/test_set_operation.py | 4 +- .../feature/query/test_special_queries.py | 4 +- .../feature/query/test_window_functions.py | 4 +- .../feature/relation/conftest.py | 4 +- .../feature/relation/test_base.py | 2 +- .../feature/relation/test_base_async.py | 2 +- .../feature/relation/test_boundary.py | 4 +- .../feature/relation/test_boundary_async.py | 2 +- .../feature/relation/test_cache.py | 2 +- .../feature/relation/test_cache_async.py | 2 +- .../feature/relation/test_cache_clearing.py | 4 +- .../feature/relation/test_derived_field.py | 4 +- .../relation/test_derived_field_async.py | 2 +- .../relation/test_derived_field_json.py | 4 +- .../relation/test_derived_field_json_async.py | 2 +- .../relation/test_descriptor_compat.py | 4 +- .../feature/relation/test_descriptors.py | 2 +- .../relation/test_descriptors_async.py | 2 +- .../feature/relation/test_integration.py | 4 +- .../relation/test_integration_async.py | 2 +- .../feature/relation/test_interfaces.py | 2 +- .../feature/relation/test_interfaces_async.py | 2 +- .../relation/test_modifier_behavior.py | 4 +- .../relation/test_modifier_behavior_async.py | 2 +- .../feature/relation/test_modifier_warning.py | 4 +- .../relation/test_modifier_warning_async.py | 2 +- .../test_nested_relationship_access.py | 2 +- .../test_nested_relationship_access_async.py | 2 +- .../feature/relation/test_path_validation.py | 4 +- .../relation/test_path_validation_async.py | 2 +- .../feature/relation/test_validator.py | 4 +- .../feature/relation/test_with_method.py | 4 +- .../relation/test_with_method_async.py | 2 +- 94 files changed, 1964 insertions(+), 1060 deletions(-) delete mode 100644 tests/providers/composite_pk.py create mode 100644 tests/providers/fixtures/__init__.py create mode 100644 tests/providers/fixtures/_common.py create mode 100644 tests/providers/fixtures/basic.py create mode 100644 tests/providers/fixtures/events.py create mode 100644 tests/providers/fixtures/mixins.py create mode 100644 tests/providers/fixtures/query.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/conftest.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/order_items.sql delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/orders.sql delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/store_inventory.sql delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py diff --git a/tests/providers/basic.py b/tests/providers/basic.py index ab385dbb..409cb17e 100644 --- a/tests/providers/basic.py +++ b/tests/providers/basic.py @@ -19,12 +19,17 @@ from rhosocial.activerecord.model import ActiveRecord # noqa: E402 from rhosocial.activerecord.backend.type_adapter import BaseSQLTypeAdapter # noqa: E402 -from rhosocial.activerecord.testsuite.feature.basic.interfaces import IBasicProvider # noqa: E402 +from rhosocial.activerecord.backend.options import ExecutionOptions, StatementType # noqa: E402 +from rhosocial.activerecord.testsuite.feature.basic.interfaces import IBasicSyncProvider, IBasicAsyncProvider # noqa: E402 from rhosocial.activerecord.testsuite.core.protocols import WorkerTestProtocol # noqa: E402 # Import the fixture selector utility from rhosocial.activerecord.testsuite.utils import select_fixture # noqa: E402 +# Expression-based DDL fixtures +from providers.fixtures._common import drop_table +from providers.fixtures.basic import TABLE_EXPRESSIONS as BASIC_TABLE_EXPRESSIONS + # Import base version models (Python 3.8+) from rhosocial.activerecord.testsuite.feature.basic.fixtures.models import ( # noqa: E402 User as UserBase, TypeCase as TypeCaseBase, ValidatedFieldUser as ValidatedFieldUserBase, @@ -179,85 +184,65 @@ def _select_model_class(base_cls, py312_cls, py311_cls, py310_cls, model_name: s from .scenarios import get_enabled_scenarios, get_scenario # noqa: E402 -class BasicProvider(IBasicProvider, WorkerTestProtocol): - """ - This is the postgres backend's implementation for the basic features test group. - It connects the generic tests in the testsuite with the actual postgres database. - """ - +class BasicProviderBase: def __init__(self): - self._active_backends = [] - self._active_async_backends = [] + self._scenario_db_files = {} def get_test_scenarios(self) -> List[str]: - """Returns a list of names for all enabled scenarios for this backend.""" return list(get_enabled_scenarios().keys()) - def _track_backend(self, backend_instance, collection: List) -> None: + def get_yes_no_adapter(self) -> 'BaseSQLTypeAdapter': + return YesOrNoBooleanAdapter() + + def _track_backend(self, backend_instance, collection) -> None: if backend_instance not in collection: collection.append(backend_instance) + def _load_postgres_schema(self, filename: str) -> str: + schema_dir = os.path.join( + os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "basic", "schema" + ) + schema_path = os.path.join(schema_dir, filename) + with open(schema_path, 'r', encoding='utf-8') as f: + return f.read() + + +class BasicSyncProvider(BasicProviderBase, IBasicSyncProvider, WorkerTestProtocol): + + def __init__(self): + super().__init__() + self._active_backends = [] + def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: - """A generic helper method to handle the setup for any given model.""" backend_class, config = get_scenario(scenario_name) model_class.configure(config, backend_class) - backend_instance = model_class.__backend__ self._track_backend(backend_instance, self._active_backends) - self._reset_table_sync(model_class, table_name) return model_class - async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: # noqa: E501 - """A generic helper method to handle the setup for any given async model.""" - from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend - - _, config = get_scenario(scenario_name) - await model_class.configure(config, AsyncPostgresBackend) - - backend_instance = model_class.__backend__ - self._track_backend(backend_instance, self._active_async_backends) - - await self._reset_table_async(model_class, table_name) - return model_class - def _reset_table_sync(self, model_class: Type[ActiveRecord], table_name: str) -> None: + opts = ExecutionOptions(stmt_type=StatementType.DDL) try: - model_class.__backend__.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + sql, params = drop_table(model_class.__backend__.dialect, table_name).to_sql() + model_class.__backend__.execute(sql, params, options=opts) except Exception as e: print(f"Could not drop table {table_name}: {e}") - - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - model_class.__backend__.execute(schema_sql) - - async def _reset_table_async(self, model_class: Type[ActiveRecord], table_name: str) -> None: - try: - await model_class.__backend__.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') - except Exception as e: - print(f"Could not drop table {table_name}: {e}") - - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - await model_class.__backend__.execute(schema_sql) + if fn := BASIC_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(model_class.__backend__.dialect, table_name) + sql, params = create_expr.to_sql() + model_class.__backend__.execute(sql, params, options=opts) def _initialize_model_schema(self, model_class: Type[ActiveRecord], table_name: str) -> None: - """Initialize schema for a model that shares backend with another model.""" self._reset_table_sync(model_class, table_name) - async def _initialize_async_model_schema(self, model_class: Type[ActiveRecord], table_name: str) -> None: - """Initialize schema for an async model that shares backend with another model.""" - await self._reset_table_async(model_class, table_name) - - def _setup_multiple_models(self, model_classes: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: # noqa: E501 - """Helper to set up multiple related models for a test, sharing a single backend.""" + def _setup_multiple_models(self, model_classes: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: if not model_classes: return tuple() - first_model_class, first_table_name = model_classes[0] first_model = self._setup_model(first_model_class, scenario_name, first_table_name) shared_backend = first_model.__backend__ - result = [first_model] - for model_class, table_name in model_classes[1:]: model_class.__connection_config__ = first_model.__connection_config__ model_class.__backend_class__ = first_model.__backend_class__ @@ -265,58 +250,27 @@ def _setup_multiple_models(self, model_classes: List[Tuple[Type[ActiveRecord], s self._track_backend(shared_backend, self._active_backends) self._initialize_model_schema(model_class, table_name) result.append(model_class) - return tuple(result) - async def _setup_multiple_models_async(self, model_classes: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: # noqa: E501 - """Helper to set up multiple related async models for a test, sharing a single backend.""" - if not model_classes: - return tuple() - - first_model_class, first_table_name = model_classes[0] - first_model = await self._setup_async_model(first_model_class, scenario_name, first_table_name) - shared_backend = first_model.__backend__ - - result = [first_model] - - for model_class, table_name in model_classes[1:]: - model_class.__connection_config__ = first_model.__connection_config__ - model_class.__backend_class__ = first_model.__backend_class__ - model_class.__backend__ = shared_backend - self._track_backend(shared_backend, self._active_async_backends) - await self._initialize_async_model_schema(model_class, table_name) - result.append(model_class) - - return tuple(result) - - # --- Implementation of the IBasicProvider interface --- - def setup_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for user model tests.""" return self._setup_model(User, scenario_name, "users") def setup_type_case_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for type case model tests.""" return self._setup_model(TypeCase, scenario_name, "type_cases") def setup_type_test_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for type test model tests.""" return self._setup_model(TypeTestModel, scenario_name, "type_tests") def setup_validated_field_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for validated field user model tests.""" return self._setup_model(ValidatedFieldUser, scenario_name, "validated_field_users") def setup_validated_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for validated user model tests.""" return self._setup_model(ValidatedUser, scenario_name, "validated_users") def setup_pydantic_validated_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for Pydantic native validation tests.""" return self._setup_model(PydanticValidatedModel, scenario_name, "pydantic_validated_models") - def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for MappedUser, MappedPost, and MappedComment models.""" + def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: return self._setup_multiple_models([ (MappedUser, "users"), (MappedPost, "posts"), @@ -324,7 +278,6 @@ def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], T ], scenario_name) def setup_mixed_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for ColumnMappingModel and MixedAnnotationModel.""" from rhosocial.activerecord_postgres_test.feature.basic.fixtures.models import PostgresMixedAnnotationModel return self._setup_multiple_models([ (ColumnMappingModel, "column_mapping_items"), @@ -332,200 +285,211 @@ def setup_mixed_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], .. ], scenario_name) def setup_type_adapter_model_and_schema(self, scenario_name: str = None) -> Type[ActiveRecord]: - """Sets up the database for the `TypeAdapterTest` model tests.""" if scenario_name is None: scenario_name = self.get_test_scenarios()[0] if self.get_test_scenarios() else "default" return self._setup_model(TypeAdapterTest, scenario_name, "type_adapter_tests") - async def setup_async_type_adapter_model_and_schema(self, scenario_name: str = None) -> Type[ActiveRecord]: - """Sets up the database for the `AsyncTypeAdapterTest` model tests.""" - if scenario_name is None: - scenario_name = self.get_test_scenarios()[0] if self.get_test_scenarios() else "default" - return await self._setup_async_model(AsyncTypeAdapterTest, scenario_name, "type_adapter_tests") - def setup_bulk_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for the `BulkUser` model tests.""" return self._setup_model(BulkUser, scenario_name, "bulk_users") - async def setup_async_bulk_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for the `AsyncBulkUser` model tests.""" - return await self._setup_async_model(AsyncBulkUser, scenario_name, "bulk_users") + def setup_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError - def get_yes_no_adapter(self) -> 'BaseSQLTypeAdapter': - """Returns an instance of the YesOrNoBooleanAdapter.""" - return YesOrNoBooleanAdapter() + def setup_order_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + def setup_mapped_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + def setup_product_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + def setup_product_form_a_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + def setup_product_with_proxy_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + def setup_product_with_column_and_adapter_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + def get_worker_connection_params(self, scenario_name: str, fixture_type: str = None) -> dict: + from .scenarios import SCENARIO_MAP + is_async = fixture_type and fixture_type.startswith('async_') + backend_class_name = 'AsyncPostgresBackend' if is_async else 'PostgresBackend' + table_name = 'users' + if fixture_type: + base_type = fixture_type.replace('async_', '') + table_map = { + 'user': 'users', + 'type_case': 'type_cases', + 'type_test': 'type_tests', + 'validated_field_user': 'validated_field_users', + 'validated_user': 'validated_users', + 'pydantic_validated_model': 'pydantic_validated_models', + 'type_adapter_test': 'type_adapter_tests', + } + table_name = table_map.get(base_type, 'users') + if scenario_name not in SCENARIO_MAP: + if SCENARIO_MAP: + scenario_name = next(iter(SCENARIO_MAP)) + else: + raise ValueError("No scenarios registered") + config_dict = SCENARIO_MAP[scenario_name] + return { + 'backend_module': 'rhosocial.activerecord.backend.impl.postgres', + 'backend_class_name': backend_class_name, + 'config_class_module': 'rhosocial.activerecord.backend.impl.postgres.config', + 'config_class_name': 'PostgresConnectionConfig', + 'config_kwargs': config_dict, + 'schema_sql': self._load_postgres_schema(f'{table_name}.sql'), + } + + def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: + return self._load_postgres_schema(f'{table_name}.sql') + + def cleanup_after_test(self, scenario_name: str): + tables_to_drop = [ + 'users', 'type_cases', 'type_tests', 'validated_field_users', + 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', + 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' + ] + for backend_instance in self._active_backends: + try: + for table_name in tables_to_drop: + try: + backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + except Exception: + pass + finally: + try: + backend_instance.disconnect() + except: # noqa: E722 + pass + self._active_backends.clear() + + +class BasicAsyncProvider(BasicProviderBase, IBasicAsyncProvider): + + def __init__(self): + super().__init__() + self._active_async_backends = [] + + async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: + from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend + _, config = get_scenario(scenario_name) + await model_class.configure(config, AsyncPostgresBackend) + backend_instance = model_class.__backend__ + self._track_backend(backend_instance, self._active_async_backends) + await self._reset_table_async(model_class, table_name) + return model_class + + async def _reset_table_async(self, model_class: Type[ActiveRecord], table_name: str) -> None: + opts = ExecutionOptions(stmt_type=StatementType.DDL) + try: + sql, params = drop_table(model_class.__backend__.dialect, table_name).to_sql() + await model_class.__backend__.execute(sql, params, options=opts) + except Exception as e: + print(f"Could not drop table {table_name}: {e}") + if fn := BASIC_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(model_class.__backend__.dialect, table_name) + sql, params = create_expr.to_sql() + await model_class.__backend__.execute(sql, params, options=opts) - # --- Async implementations --- + async def _initialize_async_model_schema(self, model_class: Type[ActiveRecord], table_name: str) -> None: + await self._reset_table_async(model_class, table_name) - async def setup_async_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for async user model tests.""" + async def _setup_multiple_models_async(self, model_classes: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: + if not model_classes: + return tuple() + first_model_class, first_table_name = model_classes[0] + first_model = await self._setup_async_model(first_model_class, scenario_name, first_table_name) + shared_backend = first_model.__backend__ + result = [first_model] + for model_class, table_name in model_classes[1:]: + model_class.__connection_config__ = first_model.__connection_config__ + model_class.__backend_class__ = first_model.__backend_class__ + model_class.__backend__ = shared_backend + self._track_backend(shared_backend, self._active_async_backends) + await self._initialize_async_model_schema(model_class, table_name) + result.append(model_class) + return tuple(result) + + async def setup_user_model(self, scenario_name: str) -> Type[ActiveRecord]: return await self._setup_async_model(AsyncUser, scenario_name, "users") - async def setup_async_type_case_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for async type case model tests.""" + async def setup_type_case_model(self, scenario_name: str) -> Type[ActiveRecord]: return await self._setup_async_model(AsyncTypeCase, scenario_name, "type_cases") - async def setup_async_type_test_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for async type test model tests.""" + async def setup_type_test_model(self, scenario_name: str) -> Type[ActiveRecord]: return await self._setup_async_model(AsyncTypeTestModel, scenario_name, "type_tests") - async def setup_async_validated_field_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for async validated field user model tests.""" + async def setup_validated_field_user_model(self, scenario_name: str) -> Type[ActiveRecord]: return await self._setup_async_model(AsyncValidatedFieldUser, scenario_name, "validated_field_users") - async def setup_async_validated_user_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for async validated user model tests.""" + async def setup_validated_user_model(self, scenario_name: str) -> Type[ActiveRecord]: return await self._setup_async_model(AsyncValidatedUser, scenario_name, "validated_users") - async def setup_async_pydantic_validated_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for async Pydantic native validation tests.""" + async def setup_pydantic_validated_model(self, scenario_name: str) -> Type[ActiveRecord]: return await self._setup_async_model(AsyncPydanticValidatedModel, scenario_name, "pydantic_validated_models") - async def setup_async_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for AsyncMappedUser, AsyncMappedPost, and AsyncMappedComment models.""" + async def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: return await self._setup_multiple_models_async([ (AsyncMappedUser, "users"), (AsyncMappedPost, "posts"), (AsyncMappedComment, "comments") ], scenario_name) - async def setup_async_mixed_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for AsyncColumnMappingModel and AsyncMixedAnnotationModel.""" + async def setup_mixed_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: from rhosocial.activerecord_postgres_test.feature.basic.fixtures.models import AsyncPostgresMixedAnnotationModel return await self._setup_multiple_models_async([ (AsyncColumnMappingModel, "column_mapping_items"), (AsyncPostgresMixedAnnotationModel, "mixed_annotation_items") ], scenario_name) - async def cleanup_after_test_async(self, scenario_name: str): - """ - Performs async cleanup after a test, dropping all tables and disconnecting async backends. - """ - tables_to_drop = [ - 'users', 'type_cases', 'type_tests', 'validated_field_users', - 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', - 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' - ] - for backend_instance in self._active_async_backends: - try: - for table_name in tables_to_drop: - try: - await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') - except Exception: - pass - finally: - try: - await backend_instance.disconnect() - except: # noqa: E722 - pass + async def setup_type_adapter_model_and_schema(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncTypeAdapterTest, scenario_name, "type_adapter_tests") - self._active_async_backends.clear() + async def setup_bulk_user_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncBulkUser, scenario_name, "bulk_users") - def _load_postgres_schema(self, filename: str) -> str: - """Helper to load a SQL schema file from this project's fixtures.""" - # Schemas are stored in the centralized location for basic feature. - schema_dir = os.path.join(os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "basic", "schema") # noqa: E501 - schema_path = os.path.join(schema_dir, filename) + async def setup_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError - with open(schema_path, 'r', encoding='utf-8') as f: - return f.read() + async def setup_order_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError - def cleanup_after_test(self, scenario_name: str): - """ - Performs cleanup after a test. This now iterates through the backends - that were created during setup, drops tables, and explicitly disconnects them. - """ + async def setup_mapped_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + async def setup_product_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + async def setup_product_form_a_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + async def setup_product_with_proxy_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + async def setup_product_with_column_and_adapter_model(self, scenario_name: str) -> Type[ActiveRecord]: + raise NotImplementedError + + async def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ 'users', 'type_cases', 'type_tests', 'validated_field_users', 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' ] - for backend_instance in self._active_backends: + for backend_instance in self._active_async_backends: try: - # Drop all tables that might have been created for basic tests for table_name in tables_to_drop: try: - backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: - # Continue with other tables if one fails pass finally: - # Always disconnect the backend instance that was used in the test try: - backend_instance.disconnect() + await backend_instance.disconnect() except: # noqa: E722 - # Ignore errors during disconnect pass - - # Clear the list of active backends for the next test - self._active_backends.clear() - - # --- Implementation of WorkerTestProtocol --- - - def get_worker_connection_params(self, scenario_name: str, fixture_type: str = None) -> dict: - """ - Return serializable connection parameters for Worker processes. - - This method provides all information needed to recreate the database - connection in a Worker process, including the schema SQL for table creation. - - Args: - scenario_name: The test scenario name - fixture_type: Optional fixture type hint (e.g., 'user', 'async_user') - Used to determine if async backend is needed - - Returns: - Dictionary with connection parameters and schema SQL - """ - from .scenarios import SCENARIO_MAP - - # Determine if async backend is needed based on fixture_type - is_async = fixture_type and fixture_type.startswith('async_') - backend_class_name = 'AsyncPostgresBackend' if is_async else 'PostgresBackend' - - # Determine schema file based on fixture_type - table_name = 'users' # default - if fixture_type: - # Remove 'async_' prefix if present to get the base type - base_type = fixture_type.replace('async_', '') - table_map = { - 'user': 'users', - 'type_case': 'type_cases', - 'type_test': 'type_tests', - 'validated_field_user': 'validated_field_users', - 'validated_user': 'validated_users', - 'pydantic_validated_model': 'pydantic_validated_models', - 'type_adapter_test': 'type_adapter_tests', - } - table_name = table_map.get(base_type, 'users') - - # Get connection config from scenario - if scenario_name not in SCENARIO_MAP: - if SCENARIO_MAP: - scenario_name = next(iter(SCENARIO_MAP)) - else: - raise ValueError("No scenarios registered") - - config_dict = SCENARIO_MAP[scenario_name] - - return { - 'backend_module': 'rhosocial.activerecord.backend.impl.postgres', - 'backend_class_name': backend_class_name, - 'config_class_module': 'rhosocial.activerecord.backend.impl.postgres.config', - 'config_class_name': 'PostgresConnectionConfig', - 'config_kwargs': config_dict, - 'schema_sql': self._load_postgres_schema(f'{table_name}.sql'), - } - - def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: - """ - Return the SQL statement to create a specific table. - - Args: - scenario_name: The test scenario name (unused for Postgres as schema is fixed) - table_name: Name of the table to create - - Returns: - CREATE TABLE SQL statement - """ - return self._load_postgres_schema(f'{table_name}.sql') + self._active_async_backends.clear() diff --git a/tests/providers/composite_pk.py b/tests/providers/composite_pk.py deleted file mode 100644 index a2f7f39f..00000000 --- a/tests/providers/composite_pk.py +++ /dev/null @@ -1,130 +0,0 @@ -# tests/providers/composite_pk.py -import os -from typing import Type, List - -from rhosocial.activerecord.model import ActiveRecord - -from rhosocial.activerecord.testsuite.feature.composite_pk.fixtures.models import ( - OrderItem as OrderItemBase, - StoreInventory as StoreInventoryBase, - Order as OrderBase, - MappedOrderItem as MappedOrderItemBase, -) -from rhosocial.activerecord.testsuite.feature.composite_pk.fixtures.models import ( - AsyncOrderItem as AsyncOrderItemBase, - AsyncStoreInventory as AsyncStoreInventoryBase, - AsyncOrder as AsyncOrderBase, - AsyncMappedOrderItem as AsyncMappedOrderItemBase, -) - -from .scenarios import get_enabled_scenarios, get_scenario - - -class CompositePKProvider: - def __init__(self): - self._active_backends = [] - self._active_async_backends = [] - - def get_test_scenarios(self) -> List[str]: - return list(get_enabled_scenarios().keys()) - - def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: - backend_class, config = get_scenario(scenario_name) - model_class.configure(config, backend_class) - backend_instance = model_class.__backend__ - if backend_instance not in self._active_backends: - self._active_backends.append(backend_instance) - try: - backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') - except Exception: - pass - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - backend_instance.execute(schema_sql) - return model_class - - async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: - from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend - _, config = get_scenario(scenario_name) - await model_class.configure(config, AsyncPostgresBackend) - backend_instance = model_class.__backend__ - if backend_instance not in self._active_async_backends: - self._active_async_backends.append(backend_instance) - try: - await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') - except Exception: - pass - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - await backend_instance.execute(schema_sql) - return model_class - - def _load_postgres_schema(self, filename: str) -> str: - schema_dir = os.path.join( - os.path.dirname(__file__), - "..", - "rhosocial", - "activerecord_postgres_test", - "feature", - "composite_pk", - "schema", - ) - schema_path = os.path.join(schema_dir, filename) - with open(schema_path, "r", encoding="utf-8") as f: - return f.read() - - def setup_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: - return self._setup_model(OrderItemBase, scenario_name, "order_items") - - def setup_store_inventory_model(self, scenario_name: str) -> Type[ActiveRecord]: - return self._setup_model(StoreInventoryBase, scenario_name, "store_inventory") - - def setup_order_model(self, scenario_name: str) -> Type[ActiveRecord]: - return self._setup_model(OrderBase, scenario_name, "orders") - - async def setup_async_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: - return await self._setup_async_model(AsyncOrderItemBase, scenario_name, "order_items") - - async def setup_async_store_inventory_model(self, scenario_name: str) -> Type[ActiveRecord]: - return await self._setup_async_model(AsyncStoreInventoryBase, scenario_name, "store_inventory") - - async def setup_async_order_model(self, scenario_name: str) -> Type[ActiveRecord]: - return await self._setup_async_model(AsyncOrderBase, scenario_name, "orders") - - def setup_mapped_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: - return self._setup_model(MappedOrderItemBase, scenario_name, "order_items") - - async def setup_async_mapped_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: - return await self._setup_async_model(AsyncMappedOrderItemBase, scenario_name, "order_items") - - def cleanup_after_test(self, scenario_name: str): - tables = ["order_items", "store_inventory", "orders"] - for backend in self._active_backends: - try: - for t in tables: - try: - backend.execute(f'DROP TABLE IF EXISTS "{t}" CASCADE') - except Exception: - pass - except Exception: - pass - try: - backend.disconnect() - except Exception: - pass - self._active_backends.clear() - - async def cleanup_after_test_async(self, scenario_name: str): - tables = ["order_items", "store_inventory", "orders"] - for backend in self._active_async_backends: - try: - for t in tables: - try: - await backend.execute(f'DROP TABLE IF EXISTS "{t}" CASCADE') - except Exception: - pass - except Exception: - pass - try: - await backend.disconnect() - except Exception: - pass - self._active_async_backends.clear() diff --git a/tests/providers/events.py b/tests/providers/events.py index b27cecc5..d414fa6f 100644 --- a/tests/providers/events.py +++ b/tests/providers/events.py @@ -26,7 +26,8 @@ # Import base version models (Python 3.8+) from rhosocial.activerecord.testsuite.feature.events.fixtures.models import ( # noqa: E402 EventTestModel as EventTestModelBase, - EventTrackingModel as EventTrackingModelBase + EventTrackingModel as EventTrackingModelBase, + AsyncEventTestModel as AsyncEventTestModelBase, ) # Conditionally import Python 3.10+ models @@ -78,95 +79,121 @@ def _select_model_class(base_cls, py312_cls, py311_cls, py310_cls, model_name: s # Select models EventTestModel = _select_model_class(EventTestModelBase, EventTestModel312, EventTestModel311, EventTestModel310, "EventTestModel") # noqa: E501 EventTrackingModel = _select_model_class(EventTrackingModelBase, EventTrackingModel312, EventTrackingModel311, EventTrackingModel310, "EventTrackingModel") # noqa: E501 +AsyncEventTestModel = AsyncEventTestModelBase -from rhosocial.activerecord.testsuite.feature.events.interfaces import IEventsProvider # noqa: E402 +from rhosocial.activerecord.backend.options import ExecutionOptions, StatementType # noqa: E402 +from rhosocial.activerecord.testsuite.feature.events.interfaces import IEventsSyncProvider, IEventsAsyncProvider # noqa: E402 # ...and the scenarios are defined specifically for this backend. from .scenarios import get_enabled_scenarios, get_scenario # noqa: E402 +# Expression-based DDL fixtures +from .fixtures._common import drop_table +from .fixtures.events import TABLE_EXPRESSIONS as EVENTS_TABLE_EXPRESSIONS -class EventsProvider(IEventsProvider): - """ - This is the postgres backend's implementation for the events test group. - It connects the generic tests in the testsuite with the actual postgres database. - """ +class EventsProviderBase: def __init__(self): - # This list will track the backend instances created during the setup phase. self._active_backends = [] def get_test_scenarios(self) -> List[str]: - """Returns a list of names for all enabled scenarios for this backend.""" return list(get_enabled_scenarios().keys()) + def _load_postgres_schema(self, filename: str) -> str: + schema_dir = os.path.join( + os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "events", "schema" + ) + schema_path = os.path.join(schema_dir, filename) + with open(schema_path, 'r', encoding='utf-8') as f: + return f.read() + + +class EventsSyncProvider(EventsProviderBase, IEventsSyncProvider): + + def __init__(self): + super().__init__() + def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: - """A generic helper method to handle the setup for any given model.""" - # 1. Get the backend class (PostgresBackend) and connection config for the requested scenario. backend_class, config = get_scenario(scenario_name) - - # 2. Configure the generic model class with our specific backend and config. model_class.configure(config, backend_class) - backend_instance = model_class.__backend__ if backend_instance not in self._active_backends: self._active_backends.append(backend_instance) - - # 3. Prepare the database schema. + opts = ExecutionOptions(stmt_type=StatementType.DDL) try: - # Drop the table if it exists, with cascade to handle dependencies. - model_class.__backend__.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + sql, params = drop_table(backend_instance.dialect, table_name).to_sql() + backend_instance.execute(sql, params, options=opts) except Exception as e: - # Ignore errors if the table didn't exist, but print for debugging. print(f"Could not drop table {table_name}: {e}") - pass - - # Execute the schema SQL to create the table. - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - model_class.__backend__.execute(schema_sql) - + if fn := EVENTS_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(backend_instance.dialect, table_name) + sql, params = create_expr.to_sql() + backend_instance.execute(sql, params, options=opts) return model_class - # --- Implementation of the IEventsProvider interface --- - def setup_event_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for event model tests.""" - return self._setup_model(EventTestModel, scenario_name, "event_tests") def setup_event_tracking_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for event tracking model tests.""" - return self._setup_model(EventTrackingModel, scenario_name, "event_tracking_models") - def _load_postgres_schema(self, filename: str) -> str: - """Helper to load a SQL schema file from this project's fixtures.""" - # Schemas are stored in the centralized location for events feature. - schema_dir = os.path.join(os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "events", "schema") # noqa: E501 - schema_path = os.path.join(schema_dir, filename) - - with open(schema_path, 'r', encoding='utf-8') as f: - return f.read() - def cleanup_after_test(self, scenario_name: str): - """ - Performs cleanup after a test. This now iterates through the backends - that were created during setup, drops tables, and explicitly disconnects them. - """ for backend_instance in self._active_backends: try: - # Drop all tables that might have been created for events tests for table_name in ['event_tests', 'event_tracking_models']: try: backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: - # Continue with other tables if one fails pass finally: - # Always disconnect the backend instance that was used in the test try: backend_instance.disconnect() except: # noqa: E722 - # Ignore errors during disconnect pass + self._active_backends.clear() + + +class EventsAsyncProvider(EventsProviderBase, IEventsAsyncProvider): + + def __init__(self): + super().__init__() + self._active_async_backends = [] + + async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: + from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend + _, config = get_scenario(scenario_name) + await model_class.configure(config, AsyncPostgresBackend) + backend_instance = model_class.__backend__ + if backend_instance not in self._active_async_backends: + self._active_async_backends.append(backend_instance) + opts = ExecutionOptions(stmt_type=StatementType.DDL) + try: + sql, params = drop_table(backend_instance.dialect, table_name).to_sql() + await backend_instance.execute(sql, params, options=opts) + except Exception as e: + print(f"Could not drop table {table_name}: {e}") + if fn := EVENTS_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(backend_instance.dialect, table_name) + sql, params = create_expr.to_sql() + await backend_instance.execute(sql, params, options=opts) + return model_class - # Clear the list of active backends for the next test - self._active_backends.clear() \ No newline at end of file + async def setup_event_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncEventTestModel, scenario_name, "event_tests") + + async def setup_event_tracking_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(EventTrackingModel, scenario_name, "event_tracking_models") + + async def cleanup_after_test(self, scenario_name: str): + for backend_instance in self._active_async_backends: + try: + for table_name in ['event_tests', 'event_tracking_models']: + try: + await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + except Exception: + pass + finally: + try: + await backend_instance.disconnect() + except: # noqa: E722 + pass + self._active_async_backends.clear() diff --git a/tests/providers/fixtures/__init__.py b/tests/providers/fixtures/__init__.py new file mode 100644 index 00000000..70857923 --- /dev/null +++ b/tests/providers/fixtures/__init__.py @@ -0,0 +1,14 @@ +# tests/providers/fixtures/__init__.py +"""PostgreSQL-specific DDL expression fixtures for the testsuite providers. + +Each module in this package exposes a ``TABLE_EXPRESSIONS`` mapping of +table name -> factory callable ``Callable[[DialectLike, str], CreateTableExpression]``. + +The factory functions build :class:`CreateTableExpression` instances that emit +PostgreSQL-compatible DDL (SERIAL / UUID / JSONB / BYTEA / TIMESTAMPTZ / BOOLEAN / +DECIMAL / VARCHAR / TEXT / FK constraints / CHECK / UNIQUE / NOT NULL / DEFAULT). +The pre-existing ``.sql`` schema files under +``tests/rhosocial/activerecord_postgres_test/feature//schema/`` +remain as the authoritative reference for what the expressions here must +produce; they are simply no longer read at runtime by the providers. +""" diff --git a/tests/providers/fixtures/_common.py b/tests/providers/fixtures/_common.py new file mode 100644 index 00000000..d07e02b9 --- /dev/null +++ b/tests/providers/fixtures/_common.py @@ -0,0 +1,34 @@ +# tests/providers/fixtures/_common.py +"""Shared helpers for the PostgreSQL DDL expression fixtures. + +Provides the ``drop_table`` helper used by all feature providers. +""" + +from __future__ import annotations + +from typing import Tuple + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, + DropTableExpression, + TableExpression, +) + + +def drop_table(dialect, table_name: str) -> DropTableExpression: + """Build a ``DROP TABLE IF EXISTS table_name CASCADE`` expression.""" + return DropTableExpression( + dialect=dialect, + table=TableExpression(dialect, table_name), + if_exists=True, + ) + + +def create_table_sql(expr: CreateTableExpression) -> Tuple[str, tuple]: + """Generate canonical PostgreSQL DDL for a :class:`CreateTableExpression`. + + For PostgreSQL no post-processing is required (unlike MySQL where storage + option quotes must be stripped). This pass-through wrapper exists so the + call site is the same as the MySQL counterpart. + """ + return expr.to_sql() diff --git a/tests/providers/fixtures/basic.py b/tests/providers/fixtures/basic.py new file mode 100644 index 00000000..aaea9129 --- /dev/null +++ b/tests/providers/fixtures/basic.py @@ -0,0 +1,499 @@ +# tests/providers/fixtures/basic.py +"""DDL expressions for the ``feature/basic`` table group (PostgreSQL). + +Each factory builds a :class:`CreateTableExpression` whose generated DDL is +semantically equivalent to the reference ``.sql`` schema files under +``tests/rhosocial/activerecord_postgres_test/feature/basic/schema/``. Those +``.sql`` files are kept as the authoritative reference and are no longer +loaded at runtime. +""" + +from typing import Callable, Dict + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, +) +from rhosocial.activerecord.backend.expression.statements import ( + ColumnDefinition, + ColumnConstraint, + ColumnConstraintType, + ForeignKeyConstraint, + TableConstraint, + TableConstraintType, + ReferentialAction, +) +from rhosocial.activerecord.backend.expression.types import ( + ArrayType, + BigIntType, + BooleanType, + DateType, + DecimalType, + DoubleType, + FloatType, + IntegerType, + JsonBType, + SmallIntType, + TextType, + TimeTzType, + TimestampTzType, + VarCharType, +) +from rhosocial.activerecord.backend.impl.postgres import ( + PostgresSerialType, + PostgresUUIDType, + PostgresByteaType, +) + +_CASCADE = ReferentialAction.CASCADE + + +# --------------------------------------------------------------------------- +# basic/users.sql +# --------------------------------------------------------------------------- + +def create_users_table(dialect, table_name: str = "users") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("username", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("age", IntegerType()), + ColumnDefinition("balance", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("is_active", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=True)]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/type_cases.sql +# --------------------------------------------------------------------------- + +def create_type_cases_table(dialect, table_name: str = "type_cases") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresUUIDType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("username", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("tiny_int", SmallIntType()), + ColumnDefinition("small_int", SmallIntType()), + ColumnDefinition("big_int", BigIntType()), + ColumnDefinition("float_val", FloatType()), + ColumnDefinition("double_val", DoubleType()), + ColumnDefinition("decimal_val", DecimalType(precision=10, scale=4)), + ColumnDefinition("char_val", VarCharType(255)), + ColumnDefinition("varchar_val", VarCharType(255)), + ColumnDefinition("text_val", TextType()), + ColumnDefinition("date_val", DateType()), + ColumnDefinition("time_val", TimeTzType()), + ColumnDefinition("timestamp_val", TimestampTzType()), + ColumnDefinition("blob_val", PostgresByteaType()), + ColumnDefinition("json_val", JsonBType()), + ColumnDefinition("array_val", ArrayType(IntegerType())), + ColumnDefinition("is_active", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=True)]), + ], + table_constraints=[ + TableConstraint(constraint_type=TableConstraintType.PRIMARY_KEY, + columns=["id"]), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/type_tests.sql +# --------------------------------------------------------------------------- + +def create_type_tests_table(dialect, table_name: str = "type_tests") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresUUIDType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("string_field", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("int_field", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("float_field", FloatType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("decimal_field", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("bool_field", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("datetime_field", TimestampTzType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("json_field", JsonBType()), + ColumnDefinition("nullable_field", VarCharType(255)), + ], + table_constraints=[ + TableConstraint(constraint_type=TableConstraintType.PRIMARY_KEY, + columns=["id"]), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/validated_field_users.sql +# --------------------------------------------------------------------------- + +def create_validated_field_users_table(dialect, table_name: str = "validated_field_users") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("username", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("age", IntegerType()), + ColumnDefinition("balance", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("credit_score", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=300)]), + ColumnDefinition("status", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="active")]), + ColumnDefinition("is_active", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=True)]), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/validated_users.sql +# --------------------------------------------------------------------------- + +def create_validated_users_table(dialect, table_name: str = "validated_users") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("username", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("age", IntegerType()), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/pydantic_validated_models.sql +# --------------------------------------------------------------------------- + +def create_pydantic_validated_models_table(dialect, table_name: str = "pydantic_validated_models") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("code", VarCharType(32)), + ColumnDefinition("quantity", IntegerType()), + ColumnDefinition("step_count", IntegerType()), + ColumnDefinition("price", DecimalType(precision=10, scale=2)), + ColumnDefinition("start_at", TimestampTzType()), + ColumnDefinition("end_at", TimestampTzType()), + ColumnDefinition("status", VarCharType(32)), + ColumnDefinition("normalized_name", VarCharType(50)), + ColumnDefinition("created_token", VarCharType(255)), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/bulk_users.sql +# --------------------------------------------------------------------------- + +def create_bulk_users_table(dialect, table_name: str = "bulk_users") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("age", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="")]), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/posts.sql +# --------------------------------------------------------------------------- + +def create_posts_table(dialect, table_name: str = "posts") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("author", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("title", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("content", TextType()), + ColumnDefinition("published_at", TimestampTzType()), + ColumnDefinition("published", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=False)]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["author"], foreign_key_table="users", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/comments.sql +# --------------------------------------------------------------------------- + +def create_comments_table(dialect, table_name: str = "comments") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("post_ref", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("author", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("text", TextType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("created_at", TimestampTzType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("updated_at", TimestampTzType()), + ColumnDefinition("approved", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=False)]), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["post_ref"], foreign_key_table="posts", foreign_key_columns=["id"], + on_delete=_CASCADE), + ForeignKeyConstraint(columns=["author"], foreign_key_table="users", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/column_mapping_items.sql +# --------------------------------------------------------------------------- + +def create_column_mapping_items_table(dialect, table_name: str = "column_mapping_items") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("item_total", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("remarks", IntegerType()), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/mixed_annotation_items.sql +# --------------------------------------------------------------------------- + +def create_mixed_annotation_items_table(dialect, table_name: str = "mixed_annotation_items") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("tags", ArrayType(TextType())), + ColumnDefinition("meta", TextType()), + ColumnDefinition("description", TextType()), + ColumnDefinition("status", TextType()), + ], + ) + + +# --------------------------------------------------------------------------- +# basic/type_adapter_tests.sql +# --------------------------------------------------------------------------- + +def create_type_adapter_tests_table(dialect, table_name: str = "type_adapter_tests") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=True, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("optional_name", VarCharType(255)), + ColumnDefinition("optional_age", IntegerType()), + ColumnDefinition("last_login", TimestampTzType()), + ColumnDefinition("is_premium", BooleanType()), + ColumnDefinition("unsupported_union", VarCharType(255)), + ColumnDefinition("custom_bool", VarCharType(10)), + ColumnDefinition("optional_custom_bool", VarCharType(10)), + ], + ) + + +# --------------------------------------------------------------------------- +# composite_pk/order_items.sql (composite PK) +# --------------------------------------------------------------------------- + +def create_composite_pk_order_items_table(dialect, table_name: str = "order_items") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("order_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("product_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("quantity", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=1)]), + ColumnDefinition("unit_price", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ], + table_constraints=[ + TableConstraint(constraint_type=TableConstraintType.PRIMARY_KEY, + columns=["order_id", "product_id"]), + ], + ) + + +# --------------------------------------------------------------------------- +# composite_pk/store_inventory.sql (composite PK, no FK) +# --------------------------------------------------------------------------- + +def create_store_inventory_table(dialect, table_name: str = "store_inventory") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("store_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("product_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("batch_id", VarCharType(64), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("stock", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0)]), + ], + table_constraints=[ + TableConstraint(constraint_type=TableConstraintType.PRIMARY_KEY, + columns=["store_id", "product_id", "batch_id"]), + ], + ) + + +# --------------------------------------------------------------------------- +# derived_field/orders.sql (single PK) +# --------------------------------------------------------------------------- + +def create_orders_table(dialect, table_name: str = "orders") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("total", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("created_at", TextType()), + ColumnDefinition("updated_at", TextType()), + ], + ) + + +# --------------------------------------------------------------------------- +# derived_field/product.sql (single PK) +# --------------------------------------------------------------------------- + +def create_product_table(dialect, table_name: str = "product") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=True, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", TextType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("price", FloatType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("quantity", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ], + ) + + +TABLE_EXPRESSIONS: Dict[str, Callable] = { + "users": create_users_table, + "type_cases": create_type_cases_table, + "type_tests": create_type_tests_table, + "validated_field_users": create_validated_field_users_table, + "validated_users": create_validated_users_table, + "pydantic_validated_models": create_pydantic_validated_models_table, + "bulk_users": create_bulk_users_table, + "posts": create_posts_table, + "comments": create_comments_table, + "column_mapping_items": create_column_mapping_items_table, + "mixed_annotation_items": create_mixed_annotation_items_table, + "type_adapter_tests": create_type_adapter_tests_table, + "order_items": create_composite_pk_order_items_table, + "store_inventory": create_store_inventory_table, + "orders": create_orders_table, + "product": create_product_table, +} diff --git a/tests/providers/fixtures/events.py b/tests/providers/fixtures/events.py new file mode 100644 index 00000000..39960fb5 --- /dev/null +++ b/tests/providers/fixtures/events.py @@ -0,0 +1,82 @@ +# tests/providers/fixtures/events.py +"""DDL expressions for the ``feature/events`` table group (PostgreSQL). + +Reference: ``tests/rhosocial/activerecord_postgres_test/feature/events/schema/``. +""" + +from typing import Callable, Dict + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, +) +from rhosocial.activerecord.backend.expression.statements import ( + ColumnDefinition, + ColumnConstraint, + ColumnConstraintType, +) +from rhosocial.activerecord.backend.expression.types import ( + IntegerType, + TextType, + TimestampTzType, + VarCharType, +) +from rhosocial.activerecord.backend.impl.postgres import ( + PostgresSerialType, +) + + +# --------------------------------------------------------------------------- +# events/event_tests.sql +# --------------------------------------------------------------------------- + +def create_event_tests_table(dialect, table_name: str = "event_tests") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("status", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="draft")]), + ColumnDefinition("revision", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=1)]), + ColumnDefinition("content", TextType()), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + ) + + +# --------------------------------------------------------------------------- +# events/event_tracking_models.sql +# --------------------------------------------------------------------------- + +def create_event_tracking_models_table(dialect, table_name: str = "event_tracking_models") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("title", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("content", TextType()), + ColumnDefinition("view_count", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0)]), + ColumnDefinition("last_viewed_at", TimestampTzType(), + constraints=[ColumnConstraint(ColumnConstraintType.NULL)]), + ], + ) + + +TABLE_EXPRESSIONS: Dict[str, Callable] = { + "event_tests": create_event_tests_table, + "event_tracking_models": create_event_tracking_models_table, +} diff --git a/tests/providers/fixtures/mixins.py b/tests/providers/fixtures/mixins.py new file mode 100644 index 00000000..b7c8f832 --- /dev/null +++ b/tests/providers/fixtures/mixins.py @@ -0,0 +1,132 @@ +# tests/providers/fixtures/mixins.py +"""DDL expressions for the ``feature/mixins`` table group (PostgreSQL). + +Reference: ``tests/rhosocial/activerecord_postgres_test/feature/mixins/schema/``. +""" + +from typing import Callable, Dict + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, +) +from rhosocial.activerecord.backend.expression.statements import ( + ColumnDefinition, + ColumnConstraint, + ColumnConstraintType, +) +from rhosocial.activerecord.backend.expression.types import ( + BooleanType, + DecimalType, + IntegerType, + TextType, + TimestampTzType, + VarCharType, +) +from rhosocial.activerecord.backend.impl.postgres import ( + PostgresSerialType, +) + + +# --------------------------------------------------------------------------- +# mixins/combined_articles.sql +# --------------------------------------------------------------------------- + +def create_combined_articles_table(dialect, table_name: str = "combined_articles") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("title", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("content", TextType()), + ColumnDefinition("status", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="draft")]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ColumnDefinition("version", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=1)]), + ColumnDefinition("deleted_at", TimestampTzType(), + constraints=[ColumnConstraint(ColumnConstraintType.NULL)]), + ], + ) + + +# --------------------------------------------------------------------------- +# mixins/tasks.sql +# --------------------------------------------------------------------------- + +def create_tasks_table(dialect, table_name: str = "tasks") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("title", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("is_completed", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=False)]), + ColumnDefinition("deleted_at", TimestampTzType(), + constraints=[ColumnConstraint(ColumnConstraintType.NULL)]), + ], + ) + + +# --------------------------------------------------------------------------- +# mixins/timestamped_posts.sql +# --------------------------------------------------------------------------- + +def create_timestamped_posts_table(dialect, table_name: str = "timestamped_posts") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("title", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("content", TextType()), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + ) + + +# --------------------------------------------------------------------------- +# mixins/versioned_products.sql +# --------------------------------------------------------------------------- + +def create_versioned_products_table(dialect, table_name: str = "versioned_products") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("price", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("version", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=1)]), + ], + ) + + +TABLE_EXPRESSIONS: Dict[str, Callable] = { + "combined_articles": create_combined_articles_table, + "tasks": create_tasks_table, + "timestamped_posts": create_timestamped_posts_table, + "versioned_products": create_versioned_products_table, +} diff --git a/tests/providers/fixtures/query.py b/tests/providers/fixtures/query.py new file mode 100644 index 00000000..86e279fc --- /dev/null +++ b/tests/providers/fixtures/query.py @@ -0,0 +1,383 @@ +# tests/providers/fixtures/query.py +"""DDL expressions for the ``feature/query`` table group (PostgreSQL). + +Reference: ``tests/rhosocial/activerecord_postgres_test/feature/query/schema/``. +""" + +from typing import Callable, Dict + +from rhosocial.activerecord.backend.expression import ( + CreateTableExpression, +) +from rhosocial.activerecord.backend.expression.statements import ( + ColumnDefinition, + ColumnConstraint, + ColumnConstraintType, + ForeignKeyConstraint, + ReferentialAction, +) +from rhosocial.activerecord.backend.expression.types import ( + BooleanType, + DecimalType, + IntegerType, + JsonBType, + TextType, + TimestampTzType, + VarCharType, +) +from rhosocial.activerecord.backend.impl.postgres import ( + PostgresSerialType, +) + +_CASCADE = ReferentialAction.CASCADE + + +# --------------------------------------------------------------------------- +# query/users.sql +# --------------------------------------------------------------------------- + +def create_users_table(dialect, table_name: str = "users") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("username", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("age", IntegerType()), + ColumnDefinition("balance", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("is_active", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=True)]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + ) + + +# --------------------------------------------------------------------------- +# query/posts.sql +# --------------------------------------------------------------------------- + +def create_posts_table(dialect, table_name: str = "posts") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("user_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("title", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("content", TextType()), + ColumnDefinition("status", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="published")]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["user_id"], foreign_key_table="users", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# query/comments.sql +# --------------------------------------------------------------------------- + +def create_comments_table(dialect, table_name: str = "comments") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("user_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("post_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("content", TextType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("is_hidden", BooleanType(), + constraints=[ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=False)]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["user_id"], foreign_key_table="users", foreign_key_columns=["id"], + on_delete=_CASCADE), + ForeignKeyConstraint(columns=["post_id"], foreign_key_table="posts", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# query/orders.sql +# --------------------------------------------------------------------------- + +def create_orders_table(dialect, table_name: str = "orders") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("user_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("order_number", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("total_amount", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("status", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="pending")]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["user_id"], foreign_key_table="users", foreign_key_columns=["id"]), + ], + ) + + +# --------------------------------------------------------------------------- +# query/order_items.sql +# --------------------------------------------------------------------------- + +def create_order_items_table(dialect, table_name: str = "order_items") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("order_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("product_name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("quantity", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=1)]), + ColumnDefinition("unit_price", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("subtotal", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["order_id"], foreign_key_table="orders", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# query/profiles.sql +# --------------------------------------------------------------------------- + +def create_profiles_table(dialect, table_name: str = "profiles") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("user_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("bio", TextType()), + ColumnDefinition("avatar_url", VarCharType(512)), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["user_id"], foreign_key_table="users", foreign_key_columns=["id"]), + ], + ) + + +# --------------------------------------------------------------------------- +# query/json_users.sql +# --------------------------------------------------------------------------- + +def create_json_users_table(dialect, table_name: str = "json_users") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("username", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("email", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("age", IntegerType()), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ColumnDefinition("settings", JsonBType()), + ColumnDefinition("tags", JsonBType()), + ColumnDefinition("profile", JsonBType()), + ColumnDefinition("roles", JsonBType()), + ColumnDefinition("scores", JsonBType()), + ColumnDefinition("subscription", JsonBType()), + ColumnDefinition("preferences", JsonBType()), + ], + ) + + +# --------------------------------------------------------------------------- +# query/nodes.sql (self-referential FK) +# --------------------------------------------------------------------------- + +def create_nodes_table(dialect, table_name: str = "nodes") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("parent_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NULL)]), + ColumnDefinition("value", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["parent_id"], foreign_key_table="nodes", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# query/searchable_items.sql +# --------------------------------------------------------------------------- + +def create_searchable_items_table(dialect, table_name: str = "searchable_items") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("name", VarCharType(255)), + ColumnDefinition("tags", TextType()), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + ) + + +# --------------------------------------------------------------------------- +# query/extended_orders.sql +# --------------------------------------------------------------------------- + +def create_extended_orders_table(dialect, table_name: str = "extended_orders") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("user_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("order_number", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("total_amount", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=0.00)]), + ColumnDefinition("status", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="pending")]), + ColumnDefinition("priority", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="medium")]), + ColumnDefinition("region", VarCharType(50), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value="default")]), + ColumnDefinition("category", VarCharType(255)), + ColumnDefinition("product", VarCharType(255)), + ColumnDefinition("department", VarCharType(255)), + ColumnDefinition("year", VarCharType(10)), + ColumnDefinition("quarter", VarCharType(10)), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["user_id"], foreign_key_table="users", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +# --------------------------------------------------------------------------- +# query/extended_order_items.sql +# --------------------------------------------------------------------------- + +def create_extended_order_items_table(dialect, table_name: str = "extended_order_items") -> CreateTableExpression: + return CreateTableExpression( + dialect=dialect, + table=table_name, + if_not_exists=False, + columns=[ + ColumnDefinition("id", PostgresSerialType(), + constraints=[ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), + ColumnDefinition("order_id", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("product_name", VarCharType(255), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("quantity", IntegerType(), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL), + ColumnConstraint(ColumnConstraintType.DEFAULT, default_value=1)]), + ColumnDefinition("price", DecimalType(precision=10, scale=2), + constraints=[ColumnConstraint(ColumnConstraintType.NOT_NULL)]), + ColumnDefinition("category", VarCharType(255)), + ColumnDefinition("region", VarCharType(50)), + ColumnDefinition("created_at", TimestampTzType()), + ColumnDefinition("updated_at", TimestampTzType()), + ], + table_constraints=[ + ForeignKeyConstraint(columns=["order_id"], foreign_key_table="extended_orders", foreign_key_columns=["id"], + on_delete=_CASCADE), + ], + ) + + +TABLE_EXPRESSIONS: Dict[str, Callable] = { + "users": create_users_table, + "posts": create_posts_table, + "comments": create_comments_table, + "orders": create_orders_table, + "order_items": create_order_items_table, + "profiles": create_profiles_table, + "json_users": create_json_users_table, + "nodes": create_nodes_table, + "searchable_items": create_searchable_items_table, + "extended_orders": create_extended_orders_table, + "extended_order_items": create_extended_order_items_table, +} diff --git a/tests/providers/mixins.py b/tests/providers/mixins.py index d803b6de..fa5b8ed7 100644 --- a/tests/providers/mixins.py +++ b/tests/providers/mixins.py @@ -28,7 +28,11 @@ TimestampedPost as TimestampedPostBase, VersionedProduct as VersionedProductBase, Task as TaskBase, - CombinedArticle as CombinedArticleBase + CombinedArticle as CombinedArticleBase, + AsyncTimestampedPost as AsyncTimestampedPostBase, + AsyncVersionedProduct as AsyncVersionedProductBase, + AsyncTask as AsyncTaskBase, + AsyncCombinedArticle as AsyncCombinedArticleBase, ) # Conditionally import Python 3.10+ models @@ -89,113 +93,138 @@ def _select_model_class(base_cls, py312_cls, py311_cls, py310_cls, model_name: s Task = _select_model_class(TaskBase, Task312, Task311, Task310, "Task") CombinedArticle = _select_model_class(CombinedArticleBase, CombinedArticle312, CombinedArticle311, CombinedArticle310, "CombinedArticle") # noqa: E501 -from rhosocial.activerecord.testsuite.feature.mixins.interfaces import IMixinsProvider # noqa: E402 +AsyncTimestampedPost = AsyncTimestampedPostBase +AsyncVersionedProduct = AsyncVersionedProductBase +AsyncTask = AsyncTaskBase +AsyncCombinedArticle = AsyncCombinedArticleBase + +from rhosocial.activerecord.backend.options import ExecutionOptions, StatementType # noqa: E402 +from rhosocial.activerecord.testsuite.feature.mixins.interfaces import IMixinsSyncProvider, IMixinsAsyncProvider # noqa: E402 # ...and the scenarios are defined specifically for this backend. from .scenarios import get_enabled_scenarios, get_scenario # noqa: E402 +# Expression-based DDL fixtures +from .fixtures._common import drop_table +from .fixtures.mixins import TABLE_EXPRESSIONS as MIXINS_TABLE_EXPRESSIONS -class MixinsProvider(IMixinsProvider): - """ - This is the postgres backend's implementation for the mixins test group. - It connects the generic tests in the testsuite with the actual postgres database. - """ +class MixinsProviderBase: def __init__(self): - # This list will track the backend instances created during the setup phase. self._active_backends = [] def get_test_scenarios(self) -> List[str]: - """Returns a list of names for all enabled scenarios for this backend.""" return list(get_enabled_scenarios().keys()) + def _load_postgres_schema(self, filename: str) -> str: + schema_dir = os.path.join( + os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "mixins", "schema" + ) + schema_path = os.path.join(schema_dir, filename) + with open(schema_path, 'r', encoding='utf-8') as f: + return f.read() + + +class MixinsSyncProvider(MixinsProviderBase, IMixinsSyncProvider): + + def __init__(self): + super().__init__() + def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: - """A generic helper method to handle the setup for any given model.""" - # 1. Get the backend class (PostgresBackend) and connection config for the requested scenario. backend_class, config = get_scenario(scenario_name) - - # 2. Configure the generic model class with our specific backend and config. model_class.configure(config, backend_class) - backend_instance = model_class.__backend__ if backend_instance not in self._active_backends: self._active_backends.append(backend_instance) - - # 3. Prepare the database schema. + opts = ExecutionOptions(stmt_type=StatementType.DDL) try: - # Drop the table if it exists, with cascade to handle dependencies. - model_class.__backend__.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + sql, params = drop_table(backend_instance.dialect, table_name).to_sql() + backend_instance.execute(sql, params, options=opts) except Exception as e: - # Ignore errors if the table didn't exist, but print for debugging. print(f"Could not drop table {table_name}: {e}") - pass - - # Execute the schema SQL to create the table. - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - model_class.__backend__.execute(schema_sql) - + if fn := MIXINS_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(backend_instance.dialect, table_name) + sql, params = create_expr.to_sql() + backend_instance.execute(sql, params, options=opts) return model_class - # --- Implementation of the IMixinsProvider interface --- - - - - - - - def setup_timestamped_post_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for timestamps model tests.""" - return self._setup_model(TimestampedPost, scenario_name, "timestamped_posts") def setup_versioned_product_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for optimistic lock model tests.""" - return self._setup_model(VersionedProduct, scenario_name, "versioned_products") def setup_task_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for soft delete model tests.""" - return self._setup_model(Task, scenario_name, "tasks") def setup_combined_article_model(self, scenario_name: str) -> Type[ActiveRecord]: - """Sets up the database for combined article model tests.""" - return self._setup_model(CombinedArticle, scenario_name, "combined_articles") - - - def _load_postgres_schema(self, filename: str) -> str: - """Helper to load a SQL schema file from this project's fixtures.""" - # Schemas are stored in the centralized location for mixins feature. - schema_dir = os.path.join(os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "mixins", "schema") # noqa: E501 - schema_path = os.path.join(schema_dir, filename) - - with open(schema_path, 'r', encoding='utf-8') as f: - return f.read() - def cleanup_after_test(self, scenario_name: str): - """ - Performs cleanup after a test. This now iterates through the backends - that were created during setup, drops tables, and explicitly disconnects them. - """ for backend_instance in self._active_backends: try: - # Drop all tables that might have been created for mixins tests for table_name in ['timestamped_posts', 'versioned_products', 'tasks', 'combined_articles']: try: backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: - # Continue with other tables if one fails pass finally: - # Always disconnect the backend instance that was used in the test try: backend_instance.disconnect() except: # noqa: E722 - # Ignore errors during disconnect pass + self._active_backends.clear() + + +class MixinsAsyncProvider(MixinsProviderBase, IMixinsAsyncProvider): + + def __init__(self): + super().__init__() + self._active_async_backends = [] + + async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str) -> Type[ActiveRecord]: + from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend + _, config = get_scenario(scenario_name) + await model_class.configure(config, AsyncPostgresBackend) + backend_instance = model_class.__backend__ + if backend_instance not in self._active_async_backends: + self._active_async_backends.append(backend_instance) + opts = ExecutionOptions(stmt_type=StatementType.DDL) + try: + sql, params = drop_table(backend_instance.dialect, table_name).to_sql() + await backend_instance.execute(sql, params, options=opts) + except Exception as e: + print(f"Could not drop table {table_name}: {e}") + if fn := MIXINS_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(backend_instance.dialect, table_name) + sql, params = create_expr.to_sql() + await backend_instance.execute(sql, params, options=opts) + return model_class - # Clear the list of active backends for the next test - self._active_backends.clear() \ No newline at end of file + async def setup_timestamped_post_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncTimestampedPost, scenario_name, "timestamped_posts") + + async def setup_versioned_product_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncVersionedProduct, scenario_name, "versioned_products") + + async def setup_task_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncTask, scenario_name, "tasks") + + async def setup_combined_article_model(self, scenario_name: str) -> Type[ActiveRecord]: + return await self._setup_async_model(AsyncCombinedArticle, scenario_name, "combined_articles") + + async def cleanup_after_test(self, scenario_name: str): + for backend_instance in self._active_async_backends: + try: + for table_name in ['timestamped_posts', 'versioned_products', 'tasks', + 'combined_articles']: + try: + await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + except Exception: + pass + finally: + try: + await backend_instance.disconnect() + except: # noqa: E722 + pass + self._active_async_backends.clear() diff --git a/tests/providers/query.py b/tests/providers/query.py index 6fac6c84..a5482dc4 100644 --- a/tests/providers/query.py +++ b/tests/providers/query.py @@ -15,7 +15,7 @@ import logging from typing import Type, List, Tuple -from rhosocial.activerecord.model import ActiveRecord +from rhosocial.activerecord.model import ActiveRecord, AsyncActiveRecord # Setup logging for fixture selection debugging logger = logging.getLogger(__name__) @@ -104,82 +104,73 @@ def _select_model_class(base_cls, py312_cls, py311_cls, py310_cls, model_name: s MappedComment = _select_model_class(MappedCommentBase, MappedComment312, MappedComment311, MappedComment310, "MappedComment") # noqa: E501 Profile = _select_model_class(ProfileBase, Profile312, Profile311, Profile310, "Profile") -from rhosocial.activerecord.testsuite.feature.query.interfaces import IQueryProvider # noqa: E402 +from rhosocial.activerecord.backend.options import ExecutionOptions, StatementType # noqa: E402 +from rhosocial.activerecord.testsuite.feature.query.interfaces import IQuerySyncProvider, IQueryAsyncProvider # noqa: E402 from rhosocial.activerecord.testsuite.core.protocols import WorkerTestProtocol # noqa: E402 + +# Composite-PK model classes used by setup_order_item_model. +from rhosocial.activerecord.testsuite.feature.basic.fixtures.models import ( # noqa: E402 + OrderItem as CompositeOrderItemBase, + AsyncOrderItem as AsyncCompositeOrderItemBase, +) + +# Expression-based DDL fixtures +from providers.fixtures._common import drop_table +from providers.fixtures.query import TABLE_EXPRESSIONS as QUERY_TABLE_EXPRESSIONS + # The scenarios are defined specifically for this backend. from .scenarios import get_enabled_scenarios, get_scenario # noqa: E402 -class QueryProvider(IQueryProvider, WorkerTestProtocol): - """ - This is the postgres backend's implementation for the query features test group. - It connects the generic tests in the testsuite with the actual postgres database. - """ - +class QueryProviderBase: def __init__(self): - self._active_backends = [] - self._active_async_backends = [] + self._scenario_db_files = {} def get_test_scenarios(self) -> List[str]: - """Returns a list of names for all enabled scenarios for this backend.""" return list(get_enabled_scenarios().keys()) def _track_backend(self, backend_instance, collection): if backend_instance not in collection: collection.append(backend_instance) - def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str, shared_backend=None) -> Type[ActiveRecord]: # noqa: E501 - """A generic helper method to handle the setup for any given model.""" - backend_class, config = get_scenario(scenario_name) - - if shared_backend is None: - model_class.configure(config, backend_class) - else: - model_class.__connection_config__ = config - model_class.__backend_class__ = backend_class - model_class.__backend__ = shared_backend - - backend_instance = model_class.__backend__ - self._track_backend(backend_instance, self._active_backends) - - try: - backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') - except Exception: - pass - - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - backend_instance.execute(schema_sql) + def _load_postgres_schema(self, filename: str) -> str: + schema_dir = os.path.join( + os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "query", "schema" + ) + schema_path = os.path.join(schema_dir, filename) + with open(schema_path, 'r', encoding='utf-8') as f: + return f.read() - return model_class - async def _setup_model_async(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str, shared_backend=None) -> Type[ActiveRecord]: # noqa: E501 - """A generic helper method to handle the setup for any given async model.""" - from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend +class QuerySyncProvider(QueryProviderBase, IQuerySyncProvider, WorkerTestProtocol): - _, config = get_scenario(scenario_name) + def __init__(self): + super().__init__() + self._active_backends = [] + def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str, shared_backend=None) -> Type[ActiveRecord]: + backend_class, config = get_scenario(scenario_name) if shared_backend is None: - await model_class.configure(config, AsyncPostgresBackend) + model_class.configure(config, backend_class) else: model_class.__connection_config__ = config - model_class.__backend_class__ = AsyncPostgresBackend + model_class.__backend_class__ = backend_class model_class.__backend__ = shared_backend - backend_instance = model_class.__backend__ - self._track_backend(backend_instance, self._active_async_backends) - + self._track_backend(backend_instance, self._active_backends) + opts = ExecutionOptions(stmt_type=StatementType.DDL) try: - await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + sql, params = drop_table(backend_instance.dialect, table_name).to_sql() + backend_instance.execute(sql, params, options=opts) except Exception: pass - - schema_sql = self._load_postgres_schema(f"{table_name}.sql") - await backend_instance.execute(schema_sql) - + if fn := QUERY_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(backend_instance.dialect, table_name) + sql, params = create_expr.to_sql() + backend_instance.execute(sql, params, options=opts) return model_class - def _setup_multiple_models(self, models_and_tables: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: # noqa: E501 - """A helper to set up multiple models for fixture groups, sharing the same backend.""" + def _setup_multiple_models(self, models_and_tables: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: result = [] shared_backend = None for i, (model_class, table_name) in enumerate(models_and_tables): @@ -187,27 +178,11 @@ def _setup_multiple_models(self, models_and_tables: List[Tuple[Type[ActiveRecord configured_model = self._setup_model(model_class, scenario_name, table_name) shared_backend = configured_model.__backend__ else: - configured_model = self._setup_model(model_class, scenario_name, table_name, shared_backend=shared_backend) # noqa: E501 + configured_model = self._setup_model(model_class, scenario_name, table_name, shared_backend=shared_backend) result.append(configured_model) return tuple(result) - async def _setup_multiple_models_async(self, models_and_tables: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: # noqa: E501 - """A helper to set up multiple async models for fixture groups, sharing the same backend.""" - result = [] - shared_backend = None - for i, (model_class, table_name) in enumerate(models_and_tables): - if i == 0: - configured_model = await self._setup_model_async(model_class, scenario_name, table_name) - shared_backend = configured_model.__backend__ - else: - configured_model = await self._setup_model_async(model_class, scenario_name, table_name, shared_backend=shared_backend) # noqa: E501 - result.append(configured_model) - return tuple(result) - - # --- Implementation of the IQueryProvider interface --- - - def setup_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for order-related models (User, Order, OrderItem) tests.""" + def setup_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: models_and_tables = [ (User, "users"), (Order, "orders"), @@ -215,8 +190,7 @@ def setup_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ] return self._setup_multiple_models(models_and_tables, scenario_name) - def setup_blog_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for blog-related models (User, Post, Comment) tests.""" + def setup_blog_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: models_and_tables = [ (User, "users"), (Post, "posts"), @@ -225,34 +199,24 @@ def setup_blog_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], T return self._setup_multiple_models(models_and_tables, scenario_name) def setup_json_user_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for the JSON user model. - - Registers PostgresJSONBAdapter for JSON fields so that psycopg3's - automatic JSONB deserialization (dict/list) is converted back to str - before Pydantic validation, matching the model's Optional[str] type. - """ from rhosocial.activerecord.backend.impl.postgres.adapters import PostgresJSONBAdapter - jsonb_adapter = PostgresJSONBAdapter() json_fields = ["settings", "tags", "profile", "roles", "scores", "subscription", "preferences"] for field_name in json_fields: if field_name not in JsonUser.__field_adapters__: JsonUser.__field_adapters__[field_name] = (jsonb_adapter, str) - models_and_tables = [ (JsonUser, "json_users"), ] return self._setup_multiple_models(models_and_tables, scenario_name) def setup_tree_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for the tree structure (Node) model.""" models_and_tables = [ (Node, "nodes"), ] return self._setup_multiple_models(models_and_tables, scenario_name) - def setup_extended_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for extended order-related models (User, ExtendedOrder, ExtendedOrderItem) tests.""" + def setup_extended_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: models_and_tables = [ (User, "users"), (ExtendedOrder, "extended_orders"), @@ -260,8 +224,7 @@ def setup_extended_order_fixtures(self, scenario_name: str) -> Tuple[Type[Active ] return self._setup_multiple_models(models_and_tables, scenario_name) - def setup_combined_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for combined models (User, Order, OrderItem, Post, Comment) tests.""" + def setup_combined_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: models_and_tables = [ (User, "users"), (Order, "orders"), @@ -272,40 +235,138 @@ def setup_combined_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord return self._setup_multiple_models(models_and_tables, scenario_name) def setup_annotated_query_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for the SearchableItem model tests.""" from rhosocial.activerecord.testsuite.feature.query.fixtures.annotated_adapter_models import SearchableItem return self._setup_multiple_models([ (SearchableItem, "searchable_items"), ], scenario_name) - def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for MappedUser, MappedPost, and MappedComment models.""" + def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: return self._setup_multiple_models([ (MappedUser, "users"), (MappedPost, "posts"), (MappedComment, "comments") ], scenario_name) - def setup_profile_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for User and Profile models.""" + def setup_profile_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord]]: return self._setup_multiple_models([ (User, "users"), (Profile, "profiles"), ], scenario_name) - # --- Async implementations --- + def setup_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: + """Set up the composite-PK OrderItem model for the query feature tests.""" + return self._setup_model(CompositeOrderItemBase, scenario_name, "order_items") + + def _get_schema_sql_for_fixture_type(self, fixture_type: str) -> dict: + schemas = {} + if fixture_type == 'order': + tables = ['users', 'orders', 'order_items'] + elif fixture_type == 'blog': + tables = ['users', 'posts', 'comments'] + elif fixture_type == 'user': + tables = ['users'] + elif fixture_type == 'combined': + tables = ['users', 'orders', 'order_items', 'posts', 'comments'] + else: + tables = ['users'] + for table in tables: + schemas[table] = self._load_postgres_schema(f'{table}.sql') + return schemas + + def get_worker_connection_params(self, scenario_name: str, fixture_type: str = 'order') -> dict: + from .scenarios import SCENARIO_MAP + is_async = fixture_type and fixture_type.startswith('async_') + backend_class_name = 'AsyncPostgresBackend' if is_async else 'PostgresBackend' + base_fixture_type = fixture_type.replace('async_', '') if fixture_type else 'order' + schema_sql = self._get_schema_sql_for_fixture_type(base_fixture_type) + if scenario_name not in SCENARIO_MAP: + if SCENARIO_MAP: + scenario_name = next(iter(SCENARIO_MAP)) + else: + raise ValueError("No scenarios registered") + config_dict = SCENARIO_MAP[scenario_name] + return { + 'backend_module': 'rhosocial.activerecord.backend.impl.postgres', + 'backend_class_name': backend_class_name, + 'config_class_module': 'rhosocial.activerecord.backend.impl.postgres.config', + 'config_class_name': 'PostgresConnectionConfig', + 'config_kwargs': config_dict, + 'schema_sql': schema_sql, + } + + def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: + return self._load_postgres_schema(f'{table_name}.sql') + + def cleanup_after_test(self, scenario_name: str): + tables_to_drop = [ + 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', + 'extended_orders', 'extended_order_items', 'searchable_items' + ] + for backend_instance in self._active_backends: + try: + for table_name in tables_to_drop: + try: + backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') + except Exception: + pass + finally: + try: + backend_instance.disconnect() + except: # noqa: E722 + pass + self._active_backends.clear() + + +class QueryAsyncProvider(QueryProviderBase, IQueryAsyncProvider): + + def __init__(self): + super().__init__() + self._active_async_backends = [] + + async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_name: str, table_name: str, shared_backend=None) -> Type[ActiveRecord]: + from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend + _, config = get_scenario(scenario_name) + if shared_backend is None: + await model_class.configure(config, AsyncPostgresBackend) + else: + model_class.__connection_config__ = config + model_class.__backend_class__ = AsyncPostgresBackend + model_class.__backend__ = shared_backend + backend_instance = model_class.__backend__ + self._track_backend(backend_instance, self._active_async_backends) + opts = ExecutionOptions(stmt_type=StatementType.DDL) + try: + sql, params = drop_table(backend_instance.dialect, table_name).to_sql() + await backend_instance.execute(sql, params, options=opts) + except Exception: + pass + if fn := QUERY_TABLE_EXPRESSIONS.get(table_name): + create_expr = fn(backend_instance.dialect, table_name) + sql, params = create_expr.to_sql() + await backend_instance.execute(sql, params, options=opts) + return model_class + + async def _setup_multiple_models_async(self, models_and_tables: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: + result = [] + shared_backend = None + for i, (model_class, table_name) in enumerate(models_and_tables): + if i == 0: + configured_model = await self._setup_async_model(model_class, scenario_name, table_name) + shared_backend = configured_model.__backend__ + else: + configured_model = await self._setup_async_model(model_class, scenario_name, table_name, shared_backend=shared_backend) + result.append(configured_model) + return tuple(result) - async def setup_async_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for async order-related models (AsyncUser, AsyncOrder, AsyncOrderItem) tests.""" - from rhosocial.activerecord.testsuite.feature.query.fixtures.async_models import AsyncUser, AsyncOrder, AsyncOrderItem # noqa: E501 + async def setup_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: + from rhosocial.activerecord.testsuite.feature.query.fixtures.async_models import AsyncUser, AsyncOrder, AsyncOrderItem return await self._setup_multiple_models_async([ (AsyncUser, "users"), (AsyncOrder, "orders"), (AsyncOrderItem, "order_items") ], scenario_name) - async def setup_async_blog_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for async blog-related models (AsyncUser, AsyncPost, AsyncComment) tests.""" + async def setup_blog_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: from rhosocial.activerecord.testsuite.feature.query.fixtures.async_models import AsyncUser from rhosocial.activerecord.testsuite.feature.query.fixtures.async_blog_models import AsyncPost, AsyncComment return await self._setup_multiple_models_async([ @@ -314,45 +375,34 @@ async def setup_async_blog_fixtures(self, scenario_name: str) -> Tuple[Type[Acti (AsyncComment, "comments") ], scenario_name) - async def setup_async_json_user_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for the async JSON user model. - - Registers PostgresJSONBAdapter for JSON fields so that psycopg3's - automatic JSONB deserialization (dict/list) is converted back to str - before Pydantic validation, matching the model's Optional[str] type. - """ + async def setup_json_user_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: from rhosocial.activerecord.testsuite.feature.query.fixtures.async_json_models import AsyncJsonUser from rhosocial.activerecord.backend.impl.postgres.adapters import PostgresJSONBAdapter - jsonb_adapter = PostgresJSONBAdapter() json_fields = ["settings", "tags", "profile", "roles", "scores", "subscription", "preferences"] for field_name in json_fields: if field_name not in AsyncJsonUser.__field_adapters__: AsyncJsonUser.__field_adapters__[field_name] = (jsonb_adapter, str) - return await self._setup_multiple_models_async([ (AsyncJsonUser, "json_users"), ], scenario_name) - async def setup_async_tree_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for the async tree structure (AsyncNode) model.""" + async def setup_tree_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: from rhosocial.activerecord.testsuite.feature.query.fixtures.async_cte_models import AsyncNode return await self._setup_multiple_models_async([ (AsyncNode, "nodes"), ], scenario_name) - async def setup_async_extended_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for async extended order-related models (AsyncUser, AsyncExtendedOrder, AsyncExtendedOrderItem) tests.""" # noqa: E501 - from rhosocial.activerecord.testsuite.feature.query.fixtures.async_extended_models import AsyncUser, AsyncExtendedOrder, AsyncExtendedOrderItem # noqa: E501 + async def setup_extended_order_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: + from rhosocial.activerecord.testsuite.feature.query.fixtures.async_extended_models import AsyncUser, AsyncExtendedOrder, AsyncExtendedOrderItem return await self._setup_multiple_models_async([ (AsyncUser, "users"), (AsyncExtendedOrder, "extended_orders"), (AsyncExtendedOrderItem, "extended_order_items") ], scenario_name) - async def setup_async_combined_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for async combined models (AsyncUser, AsyncOrder, AsyncOrderItem, AsyncPost, AsyncComment) tests.""" # noqa: E501 - from rhosocial.activerecord.testsuite.feature.query.fixtures.async_models import AsyncUser, AsyncOrder, AsyncOrderItem # noqa: E501 + async def setup_combined_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: + from rhosocial.activerecord.testsuite.feature.query.fixtures.async_models import AsyncUser, AsyncOrder, AsyncOrderItem from rhosocial.activerecord.testsuite.feature.query.fixtures.async_blog_models import AsyncPost, AsyncComment return await self._setup_multiple_models_async([ (AsyncUser, "users"), @@ -362,32 +412,32 @@ async def setup_async_combined_fixtures(self, scenario_name: str) -> Tuple[Type[ (AsyncComment, "comments") ], scenario_name) - async def setup_async_annotated_query_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: - """Sets up the database for the async SearchableItem model tests.""" - from rhosocial.activerecord.testsuite.feature.query.fixtures.async_annotated_adapter_models import AsyncSearchableItem # noqa: E501 + async def setup_annotated_query_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: + from rhosocial.activerecord.testsuite.feature.query.fixtures.async_annotated_adapter_models import AsyncSearchableItem return await self._setup_multiple_models_async([ (AsyncSearchableItem, "searchable_items"), ], scenario_name) - async def setup_async_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for AsyncMappedUser, AsyncMappedPost, and AsyncMappedComment models.""" - from rhosocial.activerecord.testsuite.feature.basic.fixtures.models import AsyncMappedUser, AsyncMappedPost, AsyncMappedComment # noqa: E501 + async def setup_mapped_models(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: + from rhosocial.activerecord.testsuite.feature.basic.fixtures.models import AsyncMappedUser, AsyncMappedPost, AsyncMappedComment return await self._setup_multiple_models_async([ (AsyncMappedUser, "users"), (AsyncMappedPost, "posts"), (AsyncMappedComment, "comments") ], scenario_name) - async def setup_async_profile_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord]]: # noqa: E501 - """Sets up the database for AsyncUser and AsyncProfile models.""" + async def setup_profile_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord], Type[ActiveRecord]]: from rhosocial.activerecord.testsuite.feature.query.fixtures.async_models import AsyncUser, AsyncProfile return await self._setup_multiple_models_async([ (AsyncUser, "users"), (AsyncProfile, "profiles"), ], scenario_name) - async def cleanup_after_test_async(self, scenario_name: str): - """Performs async cleanup after a test.""" + async def setup_order_item_model(self, scenario_name: str) -> Type[AsyncActiveRecord]: + """Set up the composite-PK AsyncOrderItem model for the query feature tests.""" + return await self._setup_async_model(AsyncCompositeOrderItemBase, scenario_name, "order_items") + + async def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', 'extended_orders', 'extended_order_items', 'searchable_items' @@ -405,130 +455,3 @@ async def cleanup_after_test_async(self, scenario_name: str): except: # noqa: E722 pass self._active_async_backends.clear() - - def _load_postgres_schema(self, filename: str) -> str: - """Helper to load a SQL schema file from this project's fixtures.""" - # Schemas are stored in the centralized location for query feature. - schema_dir = os.path.join(os.path.dirname(__file__), "..", "rhosocial", "activerecord_postgres_test", "feature", "query", "schema") # noqa: E501 - schema_path = os.path.join(schema_dir, filename) - - with open(schema_path, 'r', encoding='utf-8') as f: - return f.read() - - def cleanup_after_test(self, scenario_name: str): - """ - Performs cleanup after a test. This uses the same backend instance - that was used for setup to ensure proper cleanup with foreign key constraints. - """ - tables_to_drop = [ - 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', - 'extended_orders', 'extended_order_items', 'searchable_items' - ] - for backend_instance in self._active_backends: - try: - # Drop all tables that might have been created for query tests - # Use CASCADE to handle foreign key dependencies - for table_name in tables_to_drop: - try: - backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') - except Exception: - # Continue with other tables if one fails - pass - finally: - # Always disconnect the backend instance that was used in the test - try: - backend_instance.disconnect() - except: # noqa: E722 - # Ignore errors during disconnect - pass - - # Clear the list of active backends for the next test - self._active_backends.clear() - - # --- Implementation of WorkerTestProtocol --- - - def get_worker_connection_params(self, scenario_name: str, fixture_type: str = 'order') -> dict: - """ - Return serializable connection parameters for Worker processes. - - This method provides all information needed to recreate the database - connection in a Worker process, including the schema SQL for table creation. - - Args: - scenario_name: The test scenario name - fixture_type: Type of fixture ('order', 'blog', 'user', 'combined', - or with 'async_' prefix for async backends) - - Returns: - Dictionary with connection parameters and schema SQL - """ - from .scenarios import SCENARIO_MAP - - # Determine if async backend is needed based on fixture_type - is_async = fixture_type and fixture_type.startswith('async_') - backend_class_name = 'AsyncPostgresBackend' if is_async else 'PostgresBackend' - - # Get base fixture type (remove 'async_' prefix if present) - base_fixture_type = fixture_type.replace('async_', '') if fixture_type else 'order' - - # Build schema SQL based on fixture type - schema_sql = self._get_schema_sql_for_fixture_type(base_fixture_type) - - # Get connection config from scenario - if scenario_name not in SCENARIO_MAP: - if SCENARIO_MAP: - scenario_name = next(iter(SCENARIO_MAP)) - else: - raise ValueError("No scenarios registered") - - config_dict = SCENARIO_MAP[scenario_name] - - return { - 'backend_module': 'rhosocial.activerecord.backend.impl.postgres', - 'backend_class_name': backend_class_name, - 'config_class_module': 'rhosocial.activerecord.backend.impl.postgres.config', - 'config_class_name': 'PostgresConnectionConfig', - 'config_kwargs': config_dict, - 'schema_sql': schema_sql, - } - - def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: - """ - Return the SQL statement to create a specific table. - - Args: - scenario_name: The test scenario name (unused for Postgres as schema is fixed) - table_name: Name of the table to create - - Returns: - CREATE TABLE SQL statement - """ - return self._load_postgres_schema(f'{table_name}.sql') - - def _get_schema_sql_for_fixture_type(self, fixture_type: str) -> dict: - """ - Get schema SQL for a specific fixture type. - - Args: - fixture_type: Type of fixture ('order', 'blog', 'user', 'combined') - - Returns: - Dictionary mapping table names to CREATE TABLE statements - """ - schemas = {} - - if fixture_type == 'order': - tables = ['users', 'orders', 'order_items'] - elif fixture_type == 'blog': - tables = ['users', 'posts', 'comments'] - elif fixture_type == 'user': - tables = ['users'] - elif fixture_type == 'combined': - tables = ['users', 'orders', 'order_items', 'posts', 'comments'] - else: - tables = ['users'] - - for table in tables: - schemas[table] = self._load_postgres_schema(f'{table}.sql') - - return schemas diff --git a/tests/providers/registry.py b/tests/providers/registry.py index 34984fff..32738870 100644 --- a/tests/providers/registry.py +++ b/tests/providers/registry.py @@ -6,14 +6,13 @@ interfaces for the postgres backend. """ from rhosocial.activerecord.testsuite.core.registry import ProviderRegistry -from .basic import BasicProvider -from .events import EventsProvider -from .mixins import MixinsProvider -from .query import QueryProvider -from .relation import RelationProvider +from .basic import BasicSyncProvider, BasicAsyncProvider +from .events import EventsSyncProvider, EventsAsyncProvider +from .mixins import MixinsSyncProvider, MixinsAsyncProvider +from .query import QuerySyncProvider, QueryAsyncProvider +from .relation import RelationSyncProvider, RelationAsyncProvider from .basic_connection import BasicConnectionProvider from .query_connection import QueryConnectionProvider -from .composite_pk import CompositePKProvider from .crud_benchmark import CrudBenchmarkProvider from .fastapi_benchmark import FastAPIBenchmarkProvider from .mixin_benchmark import MixinBenchmarkProvider @@ -25,11 +24,25 @@ # Register the concrete implementations as the providers for the # interfaces defined in the testsuite. -provider_registry.register("feature.basic.IBasicProvider", BasicProvider) -provider_registry.register("feature.events.IEventsProvider", EventsProvider) -provider_registry.register("feature.mixins.IMixinsProvider", MixinsProvider) -provider_registry.register("feature.query.IQueryProvider", QueryProvider) -provider_registry.register("feature.relation.IRelationProvider", RelationProvider) +provider_registry.register("feature.basic.IBasicProvider", BasicSyncProvider) +provider_registry.register("feature.basic.IBasicSyncProvider", BasicSyncProvider) +provider_registry.register("feature.basic.IBasicAsyncProvider", BasicAsyncProvider) + +provider_registry.register("feature.events.IEventsProvider", EventsSyncProvider) +provider_registry.register("feature.events.IEventsSyncProvider", EventsSyncProvider) +provider_registry.register("feature.events.IEventsAsyncProvider", EventsAsyncProvider) + +provider_registry.register("feature.mixins.IMixinsProvider", MixinsSyncProvider) +provider_registry.register("feature.mixins.IMixinsSyncProvider", MixinsSyncProvider) +provider_registry.register("feature.mixins.IMixinsAsyncProvider", MixinsAsyncProvider) + +provider_registry.register("feature.query.IQueryProvider", QuerySyncProvider) +provider_registry.register("feature.query.IQuerySyncProvider", QuerySyncProvider) +provider_registry.register("feature.query.IQueryAsyncProvider", QueryAsyncProvider) + +provider_registry.register("feature.relation.IRelationProvider", RelationSyncProvider) +provider_registry.register("feature.relation.IRelationSyncProvider", RelationSyncProvider) +provider_registry.register("feature.relation.IRelationAsyncProvider", RelationAsyncProvider) # Register connection pool context awareness providers provider_registry.register( @@ -41,8 +54,6 @@ QueryConnectionProvider, ) -provider_registry.register("feature.composite_pk.ICompositePKProvider", CompositePKProvider) - # Register benchmark providers. provider_registry.register("benchmark.crud.ICrudBenchmarkProvider", CrudBenchmarkProvider) provider_registry.register("benchmark.query.IQueryBenchmarkProvider", QueryBenchmarkProvider) diff --git a/tests/providers/relation.py b/tests/providers/relation.py index c852ad55..6a46fb31 100644 --- a/tests/providers/relation.py +++ b/tests/providers/relation.py @@ -5,7 +5,7 @@ from rhosocial.activerecord.model import ActiveRecord, AsyncActiveRecord from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend from rhosocial.activerecord.backend.impl.postgres.adapters.json import PostgresJSONBAdapter -from rhosocial.activerecord.testsuite.feature.relation.interfaces import IRelationProvider +from rhosocial.activerecord.testsuite.feature.relation.interfaces import IRelationSyncProvider, IRelationAsyncProvider from rhosocial.activerecord.testsuite.feature.relation.fixtures.models import ( Employee, Department, Author, Book, Chapter, Profile, User, Post, Comment, @@ -104,11 +104,8 @@ """ -class RelationProvider(IRelationProvider): - +class RelationProviderBase: def __init__(self): - self._active_backends = [] - self._active_async_backends = [] self._sync_user_post_comment_setup = False self._async_user_post_comment_setup = False self._sync_relation_boundary_setup = False @@ -117,31 +114,11 @@ def __init__(self): def get_test_scenarios(self) -> List[str]: return list(get_enabled_scenarios().keys()) - def _execute_script(self, backend, sql: str): - for statement in sql.split(";"): - statement = statement.strip() - if statement: - backend.execute(statement) - - async def _execute_script_async(self, backend, sql: str): - for statement in sql.split(";"): - statement = statement.strip() - if statement: - await backend.execute(statement) - def _configure_with_shared_backend(self, model_class, config, backend_class, backend): model_class.__connection_config__ = config model_class.__backend_class__ = backend_class model_class.__backend__ = backend - def _configure_async_model_without_connection(self, model_class, config, backend=None): - if backend is None: - backend = AsyncPostgresBackend(connection_config=config) - model_class.__connection_config__ = config - model_class.__backend_class__ = AsyncPostgresBackend - model_class.__backend__ = backend - return backend - def _configure_json_field_adapters(self): adapter = PostgresJSONBAdapter() User.__field_adapters__["settings"] = (adapter, str) @@ -151,6 +128,25 @@ def _configure_json_field_adapters(self): AsyncPost.__field_adapters__["metadata"] = (adapter, str) AsyncComment.__field_adapters__["meta"] = (adapter, str) + def _reset_setup_state(self): + self._sync_user_post_comment_setup = False + self._async_user_post_comment_setup = False + self._sync_relation_boundary_setup = False + self._async_relation_boundary_setup = False + + +class RelationSyncProvider(RelationProviderBase, IRelationSyncProvider): + + def __init__(self): + super().__init__() + self._active_backends = [] + + def _execute_script(self, backend, sql: str): + for statement in sql.split(";"): + statement = statement.strip() + if statement: + backend.execute(statement) + def _setup_employee_department(self, scenario_name): backend_class, config = get_scenario(scenario_name) Employee.configure(config, backend_class) @@ -189,23 +185,6 @@ def _setup_user_post_comment_sync(self, scenario_name): self._configure_with_shared_backend(Comment, config, backend_class, backend) self._sync_user_post_comment_setup = True - def _setup_user_post_comment_async(self, scenario_name): - if not self._async_user_post_comment_setup: - self._configure_json_field_adapters() - _, config = get_scenario(scenario_name) - backend = self._configure_async_model_without_connection(AsyncUser, config) - self._configure_async_model_without_connection(AsyncPost, config, backend) - self._configure_async_model_without_connection(AsyncComment, config, backend) - self._active_async_backends.append(backend) - self._async_user_post_comment_setup = True - - async def _ensure_user_post_comment_async_schema(self): - backend = AsyncUser.backend() - if backend not in self._active_async_backends: - self._active_async_backends.append(backend) - await backend.introspect_and_adapt() - await self._execute_script_async(backend, USER_POST_COMMENT_SCHEMA) - def _setup_relation_boundary_sync(self, scenario_name): if not self._sync_relation_boundary_setup: backend_class, config = get_scenario(scenario_name) @@ -216,41 +195,13 @@ def _setup_relation_boundary_sync(self, scenario_name): self._active_backends.append(backend) self._execute_script(backend, RELATION_BOUNDARY_SCHEMA) self._configure_with_shared_backend( - BoundaryProfile, - config, - backend_class, - backend, + BoundaryProfile, config, backend_class, backend, ) self._configure_with_shared_backend( - BoundaryPost, - config, - backend_class, - backend, + BoundaryPost, config, backend_class, backend, ) self._sync_relation_boundary_setup = True - async def _setup_relation_boundary_async(self, scenario_name): - if not self._async_relation_boundary_setup: - _, config = get_scenario(scenario_name) - backend = self._configure_async_model_without_connection( - AsyncBoundaryOwner, - config, - ) - self._configure_async_model_without_connection( - AsyncBoundaryProfile, - config, - backend, - ) - self._configure_async_model_without_connection( - AsyncBoundaryPost, - config, - backend, - ) - self._active_async_backends.append(backend) - await backend.introspect_and_adapt() - await self._execute_script_async(backend, RELATION_BOUNDARY_SCHEMA) - self._async_relation_boundary_setup = True - def setup_employee_department_fixtures( self, scenario_name: str, @@ -260,12 +211,7 @@ def setup_employee_department_fixtures( def setup_author_book_fixtures( self, scenario_name: str, - ) -> Tuple[ - Type[ActiveRecord], - Type[ActiveRecord], - Type[ActiveRecord], - Type[ActiveRecord], - ]: + ) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: return self._setup_author_book(scenario_name) def setup_user_model(self, scenario_name: str) -> Type[ActiveRecord]: @@ -280,18 +226,6 @@ def setup_comment_model(self, scenario_name: str) -> Type[ActiveRecord]: self._setup_user_post_comment_sync(scenario_name) return Comment - def setup_async_user_model(self, scenario_name: str) -> Type[AsyncActiveRecord]: - self._setup_user_post_comment_async(scenario_name) - return AsyncUser - - def setup_async_post_model(self, scenario_name: str) -> Type[AsyncActiveRecord]: - self._setup_user_post_comment_async(scenario_name) - return AsyncPost - - def setup_async_comment_model(self, scenario_name: str) -> Type[AsyncActiveRecord]: - self._setup_user_post_comment_async(scenario_name) - return AsyncComment - def setup_relation_boundary_fixtures( self, scenario_name: str, @@ -299,40 +233,10 @@ def setup_relation_boundary_fixtures( self._setup_relation_boundary_sync(scenario_name) return BoundaryOwner, BoundaryProfile, BoundaryPost - def setup_async_relation_boundary_fixtures( - self, - scenario_name: str, - ) -> Tuple[ - Type[AsyncActiveRecord], - Type[AsyncActiveRecord], - Type[AsyncActiveRecord], - ]: - _, config = get_scenario(scenario_name) - self._configure_async_model_without_connection(AsyncBoundaryOwner, config) - self._configure_async_model_without_connection( - AsyncBoundaryProfile, - config, - AsyncBoundaryOwner.backend(), - ) - self._configure_async_model_without_connection( - AsyncBoundaryPost, - config, - AsyncBoundaryOwner.backend(), - ) - return AsyncBoundaryOwner, AsyncBoundaryProfile, AsyncBoundaryPost - def load_relation_boundary_dataset(self, scenario_name: str, dataset_name: str) -> Dict[str, int]: self._setup_relation_boundary_sync(scenario_name) return self._load_relation_boundary_dataset(dataset_name) - async def load_async_relation_boundary_dataset( - self, - scenario_name: str, - dataset_name: str, - ) -> Dict[str, int]: - await self._setup_relation_boundary_async(scenario_name) - return await self._load_async_relation_boundary_dataset(dataset_name) - def _load_relation_boundary_dataset(self, dataset_name): if dataset_name == "null_foreign_key": profile = BoundaryProfile(bio="No owner", owner_id=None) @@ -365,6 +269,128 @@ def _load_relation_boundary_dataset(self, dataset_name): raise ValueError(f"Unknown relation boundary dataset: {dataset_name}") + def cleanup_after_test(self, scenario_name: str) -> None: + for backend in self._active_backends: + try: + backend.disconnect() + except Exception: + pass + self._active_backends.clear() + self._reset_setup_state() + + +class RelationAsyncProvider(RelationProviderBase, IRelationAsyncProvider): + + def __init__(self): + super().__init__() + self._active_async_backends = [] + + def _configure_async_model_without_connection(self, model_class, config, backend=None): + if backend is None: + backend = AsyncPostgresBackend(connection_config=config) + model_class.__connection_config__ = config + model_class.__backend_class__ = AsyncPostgresBackend + model_class.__backend__ = backend + return backend + + async def _execute_script_async(self, backend, sql: str): + for statement in sql.split(";"): + statement = statement.strip() + if statement: + await backend.execute(statement) + + async def _setup_employee_department_fixtures_async(self, scenario_name): + _, config = get_scenario(scenario_name) + await Employee.configure(config, AsyncPostgresBackend) + backend = Employee.backend() + await backend.connect() + await backend.introspect_and_adapt() + self._active_async_backends.append(backend) + await self._execute_script_async(backend, EMPLOYEE_DEPARTMENT_SCHEMA) + self._configure_with_shared_backend(Department, config, AsyncPostgresBackend, backend) + return Employee, Department + + async def _setup_author_book_fixtures_async(self, scenario_name): + _, config = get_scenario(scenario_name) + await Author.configure(config, AsyncPostgresBackend) + backend = Author.backend() + await backend.connect() + await backend.introspect_and_adapt() + self._active_async_backends.append(backend) + await self._execute_script_async(backend, AUTHOR_BOOK_SCHEMA) + self._configure_with_shared_backend(Book, config, AsyncPostgresBackend, backend) + self._configure_with_shared_backend(Chapter, config, AsyncPostgresBackend, backend) + self._configure_with_shared_backend(Profile, config, AsyncPostgresBackend, backend) + return Author, Book, Chapter, Profile + + async def _setup_user_post_comment_async(self, scenario_name): + if not self._async_user_post_comment_setup: + self._configure_json_field_adapters() + _, config = get_scenario(scenario_name) + backend = self._configure_async_model_without_connection(AsyncUser, config) + self._configure_async_model_without_connection(AsyncPost, config, backend) + self._configure_async_model_without_connection(AsyncComment, config, backend) + self._active_async_backends.append(backend) + await backend.introspect_and_adapt() + await self._execute_script_async(backend, USER_POST_COMMENT_SCHEMA) + self._async_user_post_comment_setup = True + + async def _setup_relation_boundary_async(self, scenario_name): + if not self._async_relation_boundary_setup: + _, config = get_scenario(scenario_name) + backend = self._configure_async_model_without_connection( + AsyncBoundaryOwner, config, + ) + self._configure_async_model_without_connection( + AsyncBoundaryProfile, config, backend, + ) + self._configure_async_model_without_connection( + AsyncBoundaryPost, config, backend, + ) + self._active_async_backends.append(backend) + await backend.introspect_and_adapt() + await self._execute_script_async(backend, RELATION_BOUNDARY_SCHEMA) + self._async_relation_boundary_setup = True + + async def setup_employee_department_fixtures( + self, + scenario_name: str, + ) -> Tuple[Type[ActiveRecord], Type[ActiveRecord]]: + return await self._setup_employee_department_fixtures_async(scenario_name) + + async def setup_author_book_fixtures( + self, + scenario_name: str, + ) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: + return await self._setup_author_book_fixtures_async(scenario_name) + + async def setup_user_model(self, scenario_name: str) -> Type[ActiveRecord]: + await self._setup_user_post_comment_async(scenario_name) + return AsyncUser + + async def setup_post_model(self, scenario_name: str) -> Type[ActiveRecord]: + await self._setup_user_post_comment_async(scenario_name) + return AsyncPost + + async def setup_comment_model(self, scenario_name: str) -> Type[ActiveRecord]: + await self._setup_user_post_comment_async(scenario_name) + return AsyncComment + + async def setup_relation_boundary_fixtures( + self, + scenario_name: str, + ) -> Tuple[Type[ActiveRecord], Type[ActiveRecord], Type[ActiveRecord]]: + await self._setup_relation_boundary_async(scenario_name) + return AsyncBoundaryOwner, AsyncBoundaryProfile, AsyncBoundaryPost + + async def load_relation_boundary_dataset( + self, + scenario_name: str, + dataset_name: str, + ) -> Dict[str, int]: + await self._setup_relation_boundary_async(scenario_name) + return await self._load_async_relation_boundary_dataset(dataset_name) + async def _load_async_relation_boundary_dataset(self, dataset_name): if dataset_name == "null_foreign_key": profile = AsyncBoundaryProfile(bio="No owner", owner_id=None) @@ -397,34 +423,7 @@ async def _load_async_relation_boundary_dataset(self, dataset_name): raise ValueError(f"Unknown relation boundary dataset: {dataset_name}") - def _reset_setup_state(self): - self._sync_user_post_comment_setup = False - self._async_user_post_comment_setup = False - self._sync_relation_boundary_setup = False - self._async_relation_boundary_setup = False - - def cleanup_after_test(self, scenario_name: str) -> None: - for backend in self._active_backends: - try: - backend.disconnect() - except Exception: - pass - self._active_backends.clear() - for backend in self._active_async_backends: - try: - asyncio.run(backend.disconnect()) - except Exception: - pass - self._active_async_backends.clear() - self._reset_setup_state() - - async def cleanup_after_test_async(self, scenario_name: str): - for backend in self._active_backends: - try: - backend.disconnect() - except Exception: - pass - self._active_backends.clear() + async def cleanup_after_test(self, scenario_name: str): for backend in self._active_async_backends: try: await backend.disconnect() diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py index 4cd150ba..6601e6ad 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_basic_mapped_models.py @@ -15,6 +15,6 @@ from rhosocial.activerecord.testsuite.feature.basic.conftest import mapped_models_fixtures # noqa: F401 # The `TestBasicMappedModels` class contains the actual test cases. -from rhosocial.activerecord.testsuite.feature.basic.test_example_basic_fixtures import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_example_basic_fixtures_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_example_basic_fixtures import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_example_basic_fixtures_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py index bdacfa62..8e76ce45 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py @@ -8,6 +8,6 @@ async_bulk_user_class, # noqa: F401 ) -from rhosocial.activerecord.testsuite.feature.basic.test_bulk_operations import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_bulk_operations_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.bulk_crud.test_bulk_operations import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.bulk_crud.test_bulk_operations_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py index 474f2c71..ffc5439b 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py @@ -27,6 +27,6 @@ ) # Import shared tests from testsuite package -from rhosocial.activerecord.testsuite.feature.basic.test_crud import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_crud_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.crud.test_crud import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.crud.test_crud_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py index 7940c241..87b920c3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py @@ -20,6 +20,6 @@ ) # The test classes contain the actual test cases. -from rhosocial.activerecord.testsuite.feature.basic.test_field_column_mapping import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_field_column_mapping_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_field_column_mapping import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_field_column_mapping_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py index a409bbbf..13b13fb8 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py @@ -20,6 +20,6 @@ ) # Import shared tests from testsuite package -from rhosocial.activerecord.testsuite.feature.basic.test_fields import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_fields_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_fields import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_fields_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py index 1a0848e5..03fc5eeb 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py @@ -13,6 +13,6 @@ pydantic_validated_model, # noqa: F401 ) -from rhosocial.activerecord.testsuite.feature.basic.test_pydantic_native_validation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_pydantic_native_validation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.validation.test_pydantic_native_validation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.validation.test_pydantic_native_validation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py index 9e30dff5..c607a556 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py @@ -13,6 +13,6 @@ ) # Import all tests from the generic testsuite file. -from rhosocial.activerecord.testsuite.feature.basic.test_type_adapter import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_type_adapter_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.type_adapter.test_type_adapter import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.type_adapter.test_type_adapter_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py index 54666c6b..2c2cd094 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py @@ -20,6 +20,6 @@ ) # Import shared tests from testsuite package -from rhosocial.activerecord.testsuite.feature.basic.test_validation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.test_validation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.validation.test_validation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.validation.test_validation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/conftest.py deleted file mode 100644 index 4d6a5f53..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/conftest.py +++ /dev/null @@ -1,2 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/composite_pk/conftest.py -from rhosocial.activerecord.testsuite.feature.composite_pk.conftest import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/order_items.sql b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/order_items.sql deleted file mode 100644 index 9ea4998a..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/order_items.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE "order_items" ( - "order_id" INTEGER NOT NULL, - "product_id" INTEGER NOT NULL, - "quantity" INTEGER NOT NULL DEFAULT 1, - "unit_price" DECIMAL(10,2) NOT NULL, - PRIMARY KEY ("order_id", "product_id") -); diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/orders.sql b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/orders.sql deleted file mode 100644 index b96c78fa..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/orders.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE "orders" ( - "id" SERIAL PRIMARY KEY, - "total" DECIMAL(10,2) NOT NULL, - "created_at" TIMESTAMP WITH TIME ZONE, - "updated_at" TIMESTAMP WITH TIME ZONE -); diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/store_inventory.sql b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/store_inventory.sql deleted file mode 100644 index e4bfbab9..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/schema/store_inventory.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE "store_inventory" ( - "store_id" INTEGER NOT NULL, - "product_id" INTEGER NOT NULL, - "batch_id" VARCHAR(64) NOT NULL, - "stock" INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY ("store_id", "product_id", "batch_id") -); diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py deleted file mode 100644 index 6ae36196..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py +++ /dev/null @@ -1,4 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_active_query.py -from rhosocial.activerecord.testsuite.feature.composite_pk.test_active_query import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.composite_pk.test_active_query_async import * # noqa: F403 - diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py deleted file mode 100644 index 08112386..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py +++ /dev/null @@ -1,4 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_activerecord.py -from rhosocial.activerecord.testsuite.feature.composite_pk.test_activerecord import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.composite_pk.test_activerecord_async import * # noqa: F403 - diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py deleted file mode 100644 index 57d58ab8..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py +++ /dev/null @@ -1,4 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_cte_query.py -from rhosocial.activerecord.testsuite.feature.composite_pk.test_cte_query import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.composite_pk.test_cte_query_async import * # noqa: F403 - diff --git a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py deleted file mode 100644 index b485fde0..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py +++ /dev/null @@ -1,4 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/composite_pk/test_composite_pk_set_operation.py -from rhosocial.activerecord.testsuite.feature.composite_pk.test_set_operation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.composite_pk.test_set_operation_async import * # noqa: F403 - diff --git a/tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py deleted file mode 100644 index 8921aa03..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/derived_field/conftest.py -""" -Pytest configuration for derived field feature tests. - -This file imports fixtures from the corresponding testsuite, making them -available to the tests in this directory. -""" - -from rhosocial.activerecord.testsuite.feature.derived_field.conftest import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py b/tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py deleted file mode 100644 index 19981432..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py +++ /dev/null @@ -1,16 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/derived_field/test_derived_field_testsuite.py -""" -Bridge file for derived field feature tests from the testsuite. -""" - -from rhosocial.activerecord.testsuite.feature.derived_field.conftest import ( # noqa: F401 - product_class, - product_form_a_class, - product_with_proxy_class, - product_with_column_and_adapter_class, - async_product_class, - async_product_with_proxy_class, - async_product_with_column_and_adapter_class, -) -from rhosocial.activerecord.testsuite.feature.derived_field.test_derived_field import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.derived_field.test_derived_field_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py index 4490f75c..deae3685 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_active_query_aggregate import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_active_query_aggregate_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.aggregate.test_active_query_aggregate import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.aggregate.test_active_query_aggregate_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py index b48c30f4..9baba06b 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_active_query_basic import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_active_query_basic_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_active_query_basic import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_active_query_basic_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py index ea6a7e26..90eb3a08 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_active_query_join import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_active_query_join_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.joins.test_active_query_join import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.joins.test_active_query_join_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py index 14fe4369..8498bb08 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_active_query_range import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_active_query_range_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_active_query_range import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_active_query_range_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py index 5df3b6c5..d3981b53 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_active_query_set_operation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_active_query_set_operation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_active_query_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_active_query_set_operation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py index 7f0d8321..525ba49a 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_aggregate_queries import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_aggregate_queries_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.aggregate.test_aggregate_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.aggregate.test_aggregate_queries_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py index 809e6cdf..e2f69cef 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_basic import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_basic_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_basic import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_basic_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py index 8d5fb7dd..b234b681 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_cross_database_compatibility import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_cross_database_compatibility_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cross_database.test_cross_database_compatibility import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cross_database.test_cross_database_compatibility_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py index 401c9b16..6298688f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_cte_query_active_query import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_cte_query_active_query_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cte.test_cte_query_active_query import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cte.test_cte_query_active_query_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py index 2acfc13c..686d8fc6 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_cte_query_set_operation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_cte_query_set_operation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cte.test_cte_query_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cte.test_cte_query_set_operation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py index a4e230f6..a7c1e676 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py @@ -2,6 +2,6 @@ """ Bridge file for eager loading caching tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_caching import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_caching_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_caching import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_caching_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py index e6430701..09efb41a 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py @@ -2,6 +2,6 @@ """ Bridge file for eager loading nested tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_nested import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_nested_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_nested import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_nested_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_async.py index 37b45c60..64cae900 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_async.py @@ -2,4 +2,4 @@ """ Bridge file for async eager loading scenario tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_scenarios_async import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_scenarios_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py index 414db2d8..53b80607 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py @@ -2,6 +2,6 @@ """ Bridge file for sync eager loading scenario tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_scenarios import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_scenarios_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_scenarios import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_scenarios_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py index 963fbdf0..7571dafa 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py @@ -2,6 +2,6 @@ """ Bridge file for eager loading with_all tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_all import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_all_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_all import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_all_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py index 1df4dacb..c55cb6f8 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py @@ -2,6 +2,6 @@ """ Bridge file for eager loading with_modifier tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_modifier import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_modifier_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_modifier import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_modifier_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py index 96fe7a48..8dc3de43 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py @@ -2,6 +2,6 @@ """ Bridge file for eager loading with_one tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_one import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.test_eager_loading_with_one_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_one import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_one_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py index b92024f3..88f48f2c 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_error_handling import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_error_handling_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.error_handling.test_error_handling import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.error_handling.test_error_handling_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py index 15af9f04..162cb400 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_example_query_fixtures import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_example_query_fixtures_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.special.test_example_query_fixtures import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.special.test_example_query_fixtures_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py index ad9af333..a775027d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_joins import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_joins_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.joins.test_joins import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.joins.test_joins_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py index 5a630984..5087c2ed 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_logging_data_summarization import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_logging_data_summarization_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.logging.test_logging_data_summarization import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.logging.test_logging_data_summarization_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py index b641f1f3..1fda7a9b 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py @@ -5,4 +5,4 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_queries import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py index bd9acb01..8e53f6fe 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py @@ -5,5 +5,5 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_queries_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_queries_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py index 2c250fd1..115d70f0 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_query_optimization import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_query_optimization_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.optimization.test_query_optimization import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.optimization.test_query_optimization_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py index bc33ff4c..fb8bc85e 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_range_queries import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_range_queries_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_range_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_range_queries_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py index 28c994f8..ef05c44b 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_range_query_detailed import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_range_query_detailed_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_range_query_detailed import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_range_query_detailed_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py index f4766f1f..abd6a3f4 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_relational_validation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_relational_validation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.relations.test_relational_validation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.relations.test_relational_validation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py index 272293f6..7a2a089e 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_relations_basic import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_relations_basic_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.relations.test_relations_basic import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.relations.test_relations_basic_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_async.py index 854df473..8057becf 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_async.py @@ -2,4 +2,4 @@ """ Bridge file for async relation 'with' tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_relations_with_async import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.relations.test_relations_with_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py index 5ff8e35c..8e7d2338 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py @@ -2,4 +2,4 @@ """ Bridge file for sync relation 'with' tests. """ -from rhosocial.activerecord.testsuite.feature.query.test_relations_with import * # noqa: F401, F403 +from rhosocial.activerecord.testsuite.feature.query.relations.test_relations_with import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py index ea4cfc60..a23ca1b3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_set_operation import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_set_operation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_set_operation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py index 774bf73c..eb4efe6f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_special_queries import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_special_queries_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.special.test_special_queries import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.special.test_special_queries_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py b/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py index a665981a..54ee5de3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py @@ -5,6 +5,6 @@ This file imports the generic tests from the testsuite package and makes them discoverable by pytest in this project's test run. """ -from rhosocial.activerecord.testsuite.feature.query.test_window_functions import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.query.test_window_functions_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.window_functions.test_window_functions import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.window_functions.test_window_functions_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py index cb3209b8..c2882883 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py @@ -28,7 +28,7 @@ async def async_comment_class(async_user_post_comment_classes): return comment -@pytest_asyncio.fixture(scope="function", params=SCENARIO_PARAMS) # noqa: F405 +@pytest_asyncio.fixture(scope="function", params=SCENARIO_PARAMS_ASYNC) # noqa: F405 async def async_user_post_comment_classes(request): from rhosocial.activerecord.testsuite.core.registry import get_provider_registry @@ -44,7 +44,7 @@ async def async_user_post_comment_classes(request): await provider.cleanup_after_test_async(scenario) -@pytest_asyncio.fixture(scope="function", params=SCENARIO_PARAMS) # noqa: F405 +@pytest_asyncio.fixture(scope="function", params=SCENARIO_PARAMS_ASYNC) # noqa: F405 async def async_relation_boundary_context(request): from rhosocial.activerecord.testsuite.core.registry import get_provider_registry diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base.py index 2b7e6eb2..1fb8802b 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base.py @@ -2,4 +2,4 @@ Tests for relation base functionality. Imports tests from the standardized testsuite. """ -from rhosocial.activerecord.testsuite.feature.relation.test_base import TestRelationDescriptor # noqa: F401 +from rhosocial.activerecord.testsuite.feature.relation.base.test_base import TestRelationDescriptor # noqa: F401 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py index d27862a6..5259aa4a 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_base_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.base.test_base_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py index 4c51f85c..0efbbf89 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_boundary import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_boundary_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.boundary.test_boundary import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.boundary.test_boundary_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary_async.py index 95cbd862..591af194 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_boundary_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.boundary.test_boundary_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache.py index ccd3017a..7e97b7cd 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache.py @@ -7,7 +7,7 @@ # Import the test classes from the testsuite -from rhosocial.activerecord.testsuite.feature.relation.test_cache import TestRelationCache # noqa: F401 +from rhosocial.activerecord.testsuite.feature.relation.cache.test_cache import TestRelationCache # noqa: F401 # This will cause pytest to run all the tests in the imported class diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py index d0fb6c14..33f8eccb 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_cache_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.cache.test_cache_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py index 5ec530a6..ae990e84 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_cache_clearing import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_cache_clearing_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.cache.test_cache_clearing import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.cache.test_cache_clearing_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py index 48265b0d..b67892d2 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_derived_field import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_async.py index af5beacd..417703a6 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py index 8c84a87f..21d13321 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_json import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_json_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field_json import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field_json_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json_async.py index 0130588f..867655b5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_derived_field_json_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field_json_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py index 203c1732..09df177d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py @@ -1,4 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py -from rhosocial.activerecord.testsuite.feature.relation.test_descriptor_compat import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_descriptor_compat_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.descriptors.test_descriptor_compat import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.descriptors.test_descriptor_compat_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors.py index 8f5b34e5..9622ce84 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors.py @@ -2,4 +2,4 @@ Tests for relation descriptor functionality. Imports tests from the standardized testsuite. """ -from rhosocial.activerecord.testsuite.feature.relation.test_descriptors import TestRelationDescriptors # noqa: F401 +from rhosocial.activerecord.testsuite.feature.relation.descriptors.test_descriptors import TestRelationDescriptors # noqa: F401 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py index 8fa56f6d..b10f32f3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py @@ -1,3 +1,3 @@ # tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py -from rhosocial.activerecord.testsuite.feature.relation.test_descriptors_async import * # noqa: F401,F403 \ No newline at end of file +from rhosocial.activerecord.testsuite.feature.relation.descriptors.test_descriptors_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py index de72e480..eb3e57d4 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_integration import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_integration_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.integration.test_integration import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.integration.test_integration_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration_async.py index 5bc443dc..ef9b2e02 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_integration_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.integration.test_integration_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces.py index 2e1fab2c..dce937f9 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces.py @@ -2,4 +2,4 @@ Tests for relation interfaces. Imports tests from the standardized testsuite. """ -from rhosocial.activerecord.testsuite.feature.relation.test_interfaces import TestRelationInterfaces # noqa: F401 +from rhosocial.activerecord.testsuite.feature.relation.base.test_interfaces import TestRelationInterfaces # noqa: F401 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py index 2ae8e7db..688ea916 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_interfaces_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.base.test_interfaces_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py index 196d1585..8fc621f3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_modifier_behavior import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_modifier_behavior_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.modifiers.test_modifier_behavior import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.modifiers.test_modifier_behavior_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior_async.py index 6538d2b1..797fa5e3 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_modifier_behavior_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.modifiers.test_modifier_behavior_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py index 1bdf5e5d..2b7b7bc8 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_modifier_warning import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_modifier_warning_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.modifiers.test_modifier_warning import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.modifiers.test_modifier_warning_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning_async.py index b7207a9f..6e884253 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_modifier_warning_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.modifiers.test_modifier_warning_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access.py index edb06654..f9447d3a 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access.py @@ -2,4 +2,4 @@ Tests for nested relationship access functionality. Imports tests from the standardized testsuite. """ -from rhosocial.activerecord.testsuite.feature.relation.test_nested_relationship_access import TestNestedRelationshipAccess # noqa: E501, F401 +from rhosocial.activerecord.testsuite.feature.relation.eager_loading.test_nested_relationship_access import TestNestedRelationshipAccess # noqa: E501, F401 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py index 272901e9..6ea7cbbe 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_nested_relationship_access_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.eager_loading.test_nested_relationship_access_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py index 5e35543f..b2b0a150 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_path_validation import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_path_validation_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.validation.test_path_validation import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.validation.test_path_validation_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation_async.py index 599860af..b492478f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_path_validation_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.validation.test_path_validation_async import * # noqa: F401,F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py index c5ef8611..47df6d4e 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py @@ -1,4 +1,4 @@ # tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py -from rhosocial.activerecord.testsuite.feature.relation.test_validator import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_validator_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.validation.test_validator import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.validation.test_validator_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py index 0b998f59..a1ce8686 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_with_method import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.test_with_method_async import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.relation.eager_loading.test_with_method import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.eager_loading.test_with_method_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method_async.py index de09d14a..a2c28563 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method_async.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method_async.py @@ -1 +1 @@ -from rhosocial.activerecord.testsuite.feature.relation.test_with_method_async import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.relation.eager_loading.test_with_method_async import * # noqa: F401,F403 From 96f7aaaa2e85e197c14bec0cf008dde286fbca40 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 16 Jul 2026 16:55:53 +0800 Subject: [PATCH 07/32] fix(postgres): move bridge files to subdirectories, add missing conftest, fix composite_pk setup - Move 70 bridge files from flat paths to testsuite-matching subdirectories - Create 6 missing bridge files (composite_pk crud/query/cte/set_operations, derived_field_basic, example_basic_fixtures) - Add missing basic/worker, query/worker, interface conftest bridge files - Rename test_worker_transaction_isolation -> test_transaction_isolation - Fix setup_order_item_model to use basic TABLE_EXPRESSIONS for composite PK order_items table --- tests/providers/query.py | 41 ++++++++++++++++++- .../{ => bulk_crud}/test_bulk_operations.py | 0 .../basic/crud/test_composite_pk_crud.py | 6 +++ .../feature/basic/{ => crud}/test_crud.py | 0 .../basic/fields/test_derived_field_basic.py | 6 +++ .../fields/test_example_basic_fixtures.py | 6 +++ .../{ => fields}/test_field_column_mapping.py | 0 .../feature/basic/{ => fields}/test_fields.py | 0 .../{ => type_adapter}/test_type_adapter.py | 0 .../test_pydantic_native_validation.py | 0 .../basic/{ => validation}/test_validation.py | 0 .../feature/basic/worker/conftest.py | 8 ++++ .../feature/interface/conftest.py | 8 ++++ .../test_active_query_aggregate.py | 0 .../{ => aggregate}/test_aggregate_queries.py | 0 .../{ => basic}/test_active_query_basic.py | 0 .../feature/query/{ => basic}/test_basic.py | 0 .../query/basic/test_composite_pk_query.py | 6 +++ .../feature/query/{ => basic}/test_queries.py | 0 .../query/{ => basic}/test_queries_async.py | 0 .../test_collation_expression.py | 0 .../test_cross_database_compatibility.py | 0 .../query/cte/test_composite_pk_cte.py | 6 +++ .../{ => cte}/test_cte_query_active_query.py | 0 .../{ => cte}/test_cte_query_set_operation.py | 0 .../test_eager_loading_caching.py | 0 .../test_eager_loading_nested.py | 0 .../test_eager_loading_scenarios_async.py | 0 .../test_eager_loading_scenarios_sync.py | 0 .../test_eager_loading_with_all.py | 0 .../test_eager_loading_with_modifier.py | 0 .../test_eager_loading_with_one.py | 0 .../test_error_handling.py | 0 .../{ => joins}/test_active_query_join.py | 0 .../feature/query/{ => joins}/test_joins.py | 0 .../test_logging_data_summarization.py | 0 .../test_query_optimization.py | 0 .../test_active_query_range.py | 0 .../{ => range_queries}/test_range_queries.py | 0 .../test_range_query_detailed.py | 0 .../test_relational_validation.py | 0 .../{ => relations}/test_relations_basic.py | 0 .../test_relations_with_async.py | 0 .../test_relations_with_sync.py | 0 .../test_active_query_set_operation.py | 0 .../test_composite_pk_set_operation.py | 6 +++ .../test_set_operation.py | 0 .../test_example_query_fixtures.py | 0 .../{ => special}/test_special_queries.py | 0 .../test_window_functions.py | 0 .../feature/query/worker/conftest.py | 8 ++++ ...ation.py => test_transaction_isolation.py} | 0 .../feature/relation/{ => base}/test_base.py | 0 .../relation/{ => base}/test_base_async.py | 0 .../relation/{ => base}/test_interfaces.py | 0 .../{ => base}/test_interfaces_async.py | 0 .../relation/{ => boundary}/test_boundary.py | 0 .../{ => boundary}/test_boundary_async.py | 0 .../relation/{ => cache}/test_cache.py | 0 .../relation/{ => cache}/test_cache_async.py | 0 .../{ => cache}/test_cache_clearing.py | 0 .../{ => derived}/test_derived_field.py | 0 .../{ => derived}/test_derived_field_async.py | 0 .../{ => derived}/test_derived_field_json.py | 0 .../test_derived_field_json_async.py | 0 .../test_descriptor_compat.py | 0 .../{ => descriptors}/test_descriptors.py | 0 .../test_descriptors_async.py | 0 .../test_nested_relationship_access.py | 0 .../test_nested_relationship_access_async.py | 0 .../{ => eager_loading}/test_with_method.py | 0 .../test_with_method_async.py | 0 .../{ => integration}/test_integration.py | 0 .../test_integration_async.py | 0 .../{ => modifiers}/test_modifier_behavior.py | 0 .../test_modifier_behavior_async.py | 0 .../{ => modifiers}/test_modifier_warning.py | 0 .../test_modifier_warning_async.py | 0 .../{ => validation}/test_path_validation.py | 0 .../test_path_validation_async.py | 0 .../{ => validation}/test_validator.py | 0 81 files changed, 99 insertions(+), 2 deletions(-) rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => bulk_crud}/test_bulk_operations.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud.py rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => crud}/test_crud.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures.py rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => fields}/test_field_column_mapping.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => fields}/test_fields.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => type_adapter}/test_type_adapter.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => validation}/test_pydantic_native_validation.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/basic/{ => validation}/test_validation.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/worker/conftest.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/interface/conftest.py rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => aggregate}/test_active_query_aggregate.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => aggregate}/test_aggregate_queries.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => basic}/test_active_query_basic.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => basic}/test_basic.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query.py rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => basic}/test_queries.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => basic}/test_queries_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => collation}/test_collation_expression.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => cross_database}/test_cross_database_compatibility.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte.py rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => cte}/test_cte_query_active_query.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => cte}/test_cte_query_set_operation.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_caching.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_nested.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_scenarios_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_scenarios_sync.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_with_all.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_with_modifier.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => eager_loading}/test_eager_loading_with_one.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => error_handling}/test_error_handling.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => joins}/test_active_query_join.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => joins}/test_joins.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => logging}/test_logging_data_summarization.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => optimization}/test_query_optimization.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => range_queries}/test_active_query_range.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => range_queries}/test_range_queries.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => range_queries}/test_range_query_detailed.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => relations}/test_relational_validation.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => relations}/test_relations_basic.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => relations}/test_relations_with_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => relations}/test_relations_with_sync.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => set_operations}/test_active_query_set_operation.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation.py rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => set_operations}/test_set_operation.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => special}/test_example_query_fixtures.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => special}/test_special_queries.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/query/{ => window_functions}/test_window_functions.py (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/worker/conftest.py rename tests/rhosocial/activerecord_postgres_test/feature/query/worker/{test_worker_transaction_isolation.py => test_transaction_isolation.py} (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => base}/test_base.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => base}/test_base_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => base}/test_interfaces.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => base}/test_interfaces_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => boundary}/test_boundary.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => boundary}/test_boundary_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => cache}/test_cache.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => cache}/test_cache_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => cache}/test_cache_clearing.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => derived}/test_derived_field.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => derived}/test_derived_field_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => derived}/test_derived_field_json.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => derived}/test_derived_field_json_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => descriptors}/test_descriptor_compat.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => descriptors}/test_descriptors.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => descriptors}/test_descriptors_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => eager_loading}/test_nested_relationship_access.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => eager_loading}/test_nested_relationship_access_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => eager_loading}/test_with_method.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => eager_loading}/test_with_method_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => integration}/test_integration.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => integration}/test_integration_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => modifiers}/test_modifier_behavior.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => modifiers}/test_modifier_behavior_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => modifiers}/test_modifier_warning.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => modifiers}/test_modifier_warning_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => validation}/test_path_validation.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => validation}/test_path_validation_async.py (100%) rename tests/rhosocial/activerecord_postgres_test/feature/relation/{ => validation}/test_validator.py (100%) diff --git a/tests/providers/query.py b/tests/providers/query.py index a5482dc4..1e721e42 100644 --- a/tests/providers/query.py +++ b/tests/providers/query.py @@ -255,7 +255,25 @@ def setup_profile_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveRecord] def setup_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: """Set up the composite-PK OrderItem model for the query feature tests.""" - return self._setup_model(CompositeOrderItemBase, scenario_name, "order_items") + from rhosocial.activerecord.backend.options import ExecutionOptions, StatementType + from providers.fixtures._common import drop_table + from providers.fixtures.basic import TABLE_EXPRESSIONS as BASIC_EXPRS + + backend_class, config = get_scenario(scenario_name) + CompositeOrderItemBase.configure(config, backend_class) + backend_instance = CompositeOrderItemBase.__backend__ + self._track_backend(backend_instance, self._active_backends) + opts = ExecutionOptions(stmt_type=StatementType.DDL) + try: + sql, params = drop_table(backend_instance.dialect, "order_items").to_sql() + backend_instance.execute(sql, params, options=opts) + except Exception: + pass + if fn := BASIC_EXPRS.get("order_items"): + create_expr = fn(backend_instance.dialect, "order_items") + sql, params = create_expr.to_sql() + backend_instance.execute(sql, params, options=opts) + return CompositeOrderItemBase def _get_schema_sql_for_fixture_type(self, fixture_type: str) -> dict: schemas = {} @@ -435,7 +453,26 @@ async def setup_profile_fixtures(self, scenario_name: str) -> Tuple[Type[ActiveR async def setup_order_item_model(self, scenario_name: str) -> Type[AsyncActiveRecord]: """Set up the composite-PK AsyncOrderItem model for the query feature tests.""" - return await self._setup_async_model(AsyncCompositeOrderItemBase, scenario_name, "order_items") + from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend + from rhosocial.activerecord.backend.options import ExecutionOptions, StatementType + from providers.fixtures._common import drop_table + from providers.fixtures.basic import TABLE_EXPRESSIONS as BASIC_EXPRS + + _, config = get_scenario(scenario_name) + await AsyncCompositeOrderItemBase.configure(config, AsyncPostgresBackend) + backend_instance = AsyncCompositeOrderItemBase.__backend__ + self._track_backend(backend_instance, self._active_async_backends) + opts = ExecutionOptions(stmt_type=StatementType.DDL) + try: + sql, params = drop_table(backend_instance.dialect, "order_items").to_sql() + await backend_instance.execute(sql, params, options=opts) + except Exception: + pass + if fn := BASIC_EXPRS.get("order_items"): + create_expr = fn(backend_instance.dialect, "order_items") + sql, params = create_expr.to_sql() + await backend_instance.execute(sql, params, options=opts) + return AsyncCompositeOrderItemBase async def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_bulk_operations.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud.py new file mode 100644 index 00000000..1f34b0b8 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud.py +""" +Bridge file for composite PK CRUD tests from the testsuite. +""" +from rhosocial.activerecord.testsuite.feature.basic.crud.test_composite_pk_crud import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.crud.test_composite_pk_crud_async import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_crud.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py new file mode 100644 index 00000000..ccc7fc56 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py +""" +Bridge file for derived field tests from the testsuite. +""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field_async import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures.py new file mode 100644 index 00000000..a35d9746 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures.py +""" +Bridge file for example basic fixtures tests from the testsuite. +""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_example_basic_fixtures import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.fields.test_example_basic_fixtures_async import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_field_column_mapping.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_fields.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_type_adapter.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_pydantic_native_validation.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/basic/test_validation.py rename to tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/conftest.py new file mode 100644 index 00000000..35e82e19 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/conftest.py @@ -0,0 +1,8 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/worker/conftest.py +""" +Pytest configuration for basic worker tests. + +This file imports fixtures from the corresponding testsuite, making them +available to the tests in this directory. +""" +from rhosocial.activerecord.testsuite.feature.basic.worker.conftest import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/interface/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/interface/conftest.py new file mode 100644 index 00000000..52d35dc0 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/interface/conftest.py @@ -0,0 +1,8 @@ +# tests/rhosocial/activerecord_postgres_test/feature/interface/conftest.py +""" +Pytest configuration for interface tests. + +This file imports fixtures from the corresponding testsuite, making them +available to the tests in this directory. +""" +from rhosocial.activerecord.testsuite.feature.interface.conftest import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py b/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_aggregate.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_aggregate_queries.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_basic.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_basic.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query.py new file mode 100644 index 00000000..20bd64fb --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query.py +""" +Bridge file for composite PK query tests from the testsuite. +""" +from rhosocial.activerecord.testsuite.feature.query.basic.test_composite_pk_query import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.basic.test_composite_pk_query_async import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_queries.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_queries.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_queries.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_queries_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_queries_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_queries_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_collation_expression.py b/tests/rhosocial/activerecord_postgres_test/feature/query/collation/test_collation_expression.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_collation_expression.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/collation/test_collation_expression.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_cross_database_compatibility.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte.py new file mode 100644 index 00000000..986a6c4a --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte.py +""" +Bridge file for composite PK CTE tests from the testsuite. +""" +from rhosocial.activerecord.testsuite.feature.query.cte.test_composite_pk_cte import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.cte.test_composite_pk_cte_async import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_active_query.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_cte_query_set_operation.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_caching.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_nested.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_sync.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios_sync.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_sync.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_all.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_modifier.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_with_one.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py b/tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_error_handling.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py b/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_join.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py b/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_joins.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py b/tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_logging_data_summarization.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py b/tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_query_optimization.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_range.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_range_queries.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_range_query_detailed.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_relational_validation.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_basic.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with_sync.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_relations_with_sync.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with_sync.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_active_query_set_operation.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation.py new file mode 100644 index 00000000..8cf2e58c --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation.py +""" +Bridge file for composite PK set operation tests from the testsuite. +""" +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_composite_pk_set_operation import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_composite_pk_set_operation_async import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_set_operation.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py b/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_example_query_fixtures.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py b/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_special_queries.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py b/tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/test_window_functions.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/conftest.py new file mode 100644 index 00000000..e9cd5de9 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/conftest.py @@ -0,0 +1,8 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/worker/conftest.py +""" +Pytest configuration for query worker tests. + +This file imports fixtures from the corresponding testsuite, making them +available to the tests in this directory. +""" +from rhosocial.activerecord.testsuite.feature.query.worker.conftest import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_base.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_base.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_base.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_base_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_base_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_base_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_interfaces.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_interfaces.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_interfaces_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_interfaces_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/base/test_interfaces_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/boundary/test_boundary.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/boundary/test_boundary.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/boundary/test_boundary_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_boundary_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/boundary/test_boundary_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_cache_clearing.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field_json.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field_json.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field_json_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_derived_field_json_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field_json_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptor_compat.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptors.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptors.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptors_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_descriptors_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptors_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_nested_relationship_access.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_nested_relationship_access.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_nested_relationship_access_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_nested_relationship_access_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_nested_relationship_access_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_with_method.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_with_method.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_with_method_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_with_method_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/eager_loading/test_with_method_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/integration/test_integration.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/integration/test_integration.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/integration/test_integration_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_integration_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/integration/test_integration_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_behavior.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_behavior.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_behavior_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_behavior_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_behavior_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_warning.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_warning.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_warning_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_modifier_warning_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/modifiers/test_modifier_warning_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_path_validation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_path_validation.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_path_validation_async.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_path_validation_async.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_path_validation_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/relation/test_validator.py rename to tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator.py From 57aa76f3966d2a773d2eddf990db3a66e5212c46 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 16 Jul 2026 21:06:40 +0800 Subject: [PATCH 08/32] fix(postgres): fix PROVIDER_KEY_ASYNC and method names in relation conftest - Use PROVIDER_KEY_ASYNC instead of undefined PROVIDER_KEY - Fix method call names: setup_async_user_model -> setup_user_model (await) - Fix cleanup_after_test_async -> cleanup_after_test (await) - Align method calls with testsuite IRelationAsyncProvider interface --- .../feature/relation/conftest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py index c2882883..756647a1 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py @@ -34,14 +34,14 @@ async def async_user_post_comment_classes(request): scenario = request.param provider_registry = get_provider_registry() - provider_class = provider_registry.get_provider(PROVIDER_KEY) # noqa: F405 + provider_class = provider_registry.get_provider(PROVIDER_KEY_ASYNC) # noqa: F405 provider = provider_class() - user = provider.setup_async_user_model(scenario) - post = provider.setup_async_post_model(scenario) - comment = provider.setup_async_comment_model(scenario) + user = await provider.setup_user_model(scenario) + post = await provider.setup_post_model(scenario) + comment = await provider.setup_comment_model(scenario) await provider._ensure_user_post_comment_async_schema() yield user, post, comment - await provider.cleanup_after_test_async(scenario) + await provider.cleanup_after_test(scenario) @pytest_asyncio.fixture(scope="function", params=SCENARIO_PARAMS_ASYNC) # noqa: F405 @@ -50,8 +50,8 @@ async def async_relation_boundary_context(request): scenario = request.param provider_registry = get_provider_registry() - provider_class = provider_registry.get_provider(PROVIDER_KEY) # noqa: F405 + provider_class = provider_registry.get_provider(PROVIDER_KEY_ASYNC) # noqa: F405 provider = provider_class() - owner, profile, post = provider.setup_async_relation_boundary_fixtures(scenario) + owner, profile, post = await provider.setup_relation_boundary_fixtures(scenario) yield provider, scenario, owner, profile, post - await provider.cleanup_after_test_async(scenario) + await provider.cleanup_after_test(scenario) From 4fda61912409ee071b5184ff4c5266229e0bd66c Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Fri, 17 Jul 2026 11:37:57 +0800 Subject: [PATCH 09/32] fix(postgres): remove non-existent _ensure_user_post_comment_async_schema call from relation conftest RelationAsyncProvider._setup_user_post_comment_async() already handles all three tables in a single call. --- .../activerecord_postgres_test/feature/relation/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py index 756647a1..77f688a5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/conftest.py @@ -39,7 +39,6 @@ async def async_user_post_comment_classes(request): user = await provider.setup_user_model(scenario) post = await provider.setup_post_model(scenario) comment = await provider.setup_comment_model(scenario) - await provider._ensure_user_post_comment_async_schema() yield user, post, comment await provider.cleanup_after_test(scenario) From eccdc3b7a675f714abff0cca3ecb152aac2f3934 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Fri, 17 Jul 2026 17:47:49 +0800 Subject: [PATCH 10/32] fix(postgres): extend cleanup_after_test table lists to ensure no cross-test contamination Add missing tables to PostgreSQL provider cleanup lists so all tables created during a test are reliably removed afterwards: - basic provider (sync + async): bulk_users - query provider (sync + async): profiles These were created by setup methods (setup_bulk_user_model, setup_profile_fixtures) but absent from the cleanup drop list, leading to orphaned tables across sequential test runs and cross-scenario contamination. --- tests/providers/basic.py | 2 ++ tests/providers/query.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/providers/basic.py b/tests/providers/basic.py index 409cb17e..aaf8053c 100644 --- a/tests/providers/basic.py +++ b/tests/providers/basic.py @@ -352,6 +352,7 @@ def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ 'users', 'type_cases', 'type_tests', 'validated_field_users', 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', + 'bulk_users', 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' ] for backend_instance in self._active_backends: @@ -478,6 +479,7 @@ async def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ 'users', 'type_cases', 'type_tests', 'validated_field_users', 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', + 'bulk_users', 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' ] for backend_instance in self._active_async_backends: diff --git a/tests/providers/query.py b/tests/providers/query.py index 1e721e42..635baecc 100644 --- a/tests/providers/query.py +++ b/tests/providers/query.py @@ -318,7 +318,7 @@ def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', - 'extended_orders', 'extended_order_items', 'searchable_items' + 'extended_orders', 'extended_order_items', 'searchable_items', 'profiles' ] for backend_instance in self._active_backends: try: @@ -477,7 +477,7 @@ async def setup_order_item_model(self, scenario_name: str) -> Type[AsyncActiveRe async def cleanup_after_test(self, scenario_name: str): tables_to_drop = [ 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', - 'extended_orders', 'extended_order_items', 'searchable_items' + 'extended_orders', 'extended_order_items', 'searchable_items', 'profiles' ] for backend_instance in self._active_async_backends: try: From 24e0b3aae66a7630e8a40e23712daa951df5d983 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Fri, 17 Jul 2026 20:02:54 +0800 Subject: [PATCH 11/32] fix(postgres): add DROP TABLE to relation/connection/benchmark cleanup, fix bare excepts - RelationSyncProvider/RelationAsyncProvider cleanup_after_test now drops all 12 created tables (employees, departments, authors, books, chapters, profiles, users, posts, comments, boundary_*) with CASCADE before disconnect. - BasicConnectionProvider/QueryConnectionProvider cleanup now drops test_users table. - CrudBenchmarkProvider cleanup now drops benchmark_users table. - All 8 bare 'except:' -> 'except Exception:' across basic.py, query.py, mixins.py, events.py providers. --- tests/providers/basic.py | 4 +-- tests/providers/basic_connection.py | 8 ++++++ tests/providers/crud_benchmark.py | 8 ++++++ tests/providers/events.py | 4 +-- tests/providers/mixins.py | 4 +-- tests/providers/query.py | 4 +-- tests/providers/query_connection.py | 8 ++++++ tests/providers/relation.py | 38 ++++++++++++++++++++++++----- 8 files changed, 64 insertions(+), 14 deletions(-) diff --git a/tests/providers/basic.py b/tests/providers/basic.py index aaf8053c..20843722 100644 --- a/tests/providers/basic.py +++ b/tests/providers/basic.py @@ -365,7 +365,7 @@ def cleanup_after_test(self, scenario_name: str): finally: try: backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_backends.clear() @@ -492,6 +492,6 @@ async def cleanup_after_test(self, scenario_name: str): finally: try: await backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_async_backends.clear() diff --git a/tests/providers/basic_connection.py b/tests/providers/basic_connection.py index ebcfa369..c15a6668 100644 --- a/tests/providers/basic_connection.py +++ b/tests/providers/basic_connection.py @@ -134,6 +134,10 @@ def cleanup_sync(self, scenario_name: str, pool: BackendPool): # Disconnect backends for backend in self._active_backends: + try: + backend.execute("DROP TABLE IF EXISTS test_users CASCADE") + except Exception: + pass try: backend.disconnect() except Exception: @@ -146,6 +150,10 @@ async def cleanup_async(self, scenario_name: str, pool: AsyncBackendPool): # Disconnect backends for backend in self._active_async_backends: + try: + await backend.execute("DROP TABLE IF EXISTS test_users CASCADE") + except Exception: + pass try: await backend.disconnect() except Exception: diff --git a/tests/providers/crud_benchmark.py b/tests/providers/crud_benchmark.py index 1c35af46..675a8d14 100644 --- a/tests/providers/crud_benchmark.py +++ b/tests/providers/crud_benchmark.py @@ -164,6 +164,10 @@ def _schema_sql(self): def _cleanup_sync(self): for backend in self._active_backends: + try: + backend.execute("DROP TABLE IF EXISTS benchmark_users CASCADE") + except Exception: + pass try: backend.disconnect() except Exception: @@ -172,6 +176,10 @@ def _cleanup_sync(self): async def _cleanup_async(self): for backend in self._active_async_backends: + try: + await backend.execute("DROP TABLE IF EXISTS benchmark_users CASCADE") + except Exception: + pass try: await backend.disconnect() except Exception: diff --git a/tests/providers/events.py b/tests/providers/events.py index d414fa6f..0b11a7ca 100644 --- a/tests/providers/events.py +++ b/tests/providers/events.py @@ -147,7 +147,7 @@ def cleanup_after_test(self, scenario_name: str): finally: try: backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_backends.clear() @@ -194,6 +194,6 @@ async def cleanup_after_test(self, scenario_name: str): finally: try: await backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_async_backends.clear() diff --git a/tests/providers/mixins.py b/tests/providers/mixins.py index fa5b8ed7..149fd6f8 100644 --- a/tests/providers/mixins.py +++ b/tests/providers/mixins.py @@ -171,7 +171,7 @@ def cleanup_after_test(self, scenario_name: str): finally: try: backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_backends.clear() @@ -225,6 +225,6 @@ async def cleanup_after_test(self, scenario_name: str): finally: try: await backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_async_backends.clear() diff --git a/tests/providers/query.py b/tests/providers/query.py index 635baecc..23a723bc 100644 --- a/tests/providers/query.py +++ b/tests/providers/query.py @@ -330,7 +330,7 @@ def cleanup_after_test(self, scenario_name: str): finally: try: backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_backends.clear() @@ -489,6 +489,6 @@ async def cleanup_after_test(self, scenario_name: str): finally: try: await backend_instance.disconnect() - except: # noqa: E722 + except Exception: pass self._active_async_backends.clear() diff --git a/tests/providers/query_connection.py b/tests/providers/query_connection.py index ab962f1e..7fab8e23 100644 --- a/tests/providers/query_connection.py +++ b/tests/providers/query_connection.py @@ -134,6 +134,10 @@ def cleanup_sync(self, scenario_name: str, pool: BackendPool): # Disconnect backends for backend in self._active_backends: + try: + backend.execute("DROP TABLE IF EXISTS test_users CASCADE") + except Exception: + pass try: backend.disconnect() except Exception: @@ -146,6 +150,10 @@ async def cleanup_async(self, scenario_name: str, pool: AsyncBackendPool): # Disconnect backends for backend in self._active_async_backends: + try: + await backend.execute("DROP TABLE IF EXISTS test_users CASCADE") + except Exception: + pass try: await backend.disconnect() except Exception: diff --git a/tests/providers/relation.py b/tests/providers/relation.py index 6a46fb31..e81bf16a 100644 --- a/tests/providers/relation.py +++ b/tests/providers/relation.py @@ -270,11 +270,24 @@ def _load_relation_boundary_dataset(self, dataset_name): raise ValueError(f"Unknown relation boundary dataset: {dataset_name}") def cleanup_after_test(self, scenario_name: str) -> None: + tables_to_drop = [ + 'employees', 'departments', + 'authors', 'books', 'chapters', 'profiles', + 'users', 'posts', 'comments', + 'relation_boundary_owners', 'relation_boundary_profiles', 'relation_boundary_posts', + ] for backend in self._active_backends: try: - backend.disconnect() - except Exception: - pass + for table in tables_to_drop: + try: + backend.execute(f'DROP TABLE IF EXISTS "{table}" CASCADE') + except Exception: + pass + finally: + try: + backend.disconnect() + except Exception: + pass self._active_backends.clear() self._reset_setup_state() @@ -424,10 +437,23 @@ async def _load_async_relation_boundary_dataset(self, dataset_name): raise ValueError(f"Unknown relation boundary dataset: {dataset_name}") async def cleanup_after_test(self, scenario_name: str): + tables_to_drop = [ + 'employees', 'departments', + 'authors', 'books', 'chapters', 'profiles', + 'users', 'posts', 'comments', + 'relation_boundary_owners', 'relation_boundary_profiles', 'relation_boundary_posts', + ] for backend in self._active_async_backends: try: - await backend.disconnect() - except Exception: - pass + for table in tables_to_drop: + try: + await backend.execute(f'DROP TABLE IF EXISTS "{table}" CASCADE') + except Exception: + pass + finally: + try: + await backend.disconnect() + except Exception: + pass self._active_async_backends.clear() self._reset_setup_state() From 80a507d665351033ac5fb1534c49edccdbd1d05f Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 18 Jul 2026 17:24:42 +0800 Subject: [PATCH 12/32] refactor(test): add missing _async bridge files (61), fix naming - Generate 61 _async.py bridge files for sync/async pairing compliance - Rename test_eager_loading_scenarios_sync.py -> test_eager_loading_scenarios.py - Rename test_relations_with_sync.py -> test_relations_with.py - Split test_derived_field_basic.py into sync-only + test_derived_field_basic_async.py - Add missing bridge files for basic/fields/ subcategories - Fix testsuite module path in relation/derived/test_derived_field.py --- .../feature/basic/bulk_crud/test_bulk_operations_async.py | 3 +++ .../basic/connection/test_active_record_context_async.py | 3 +++ .../basic/connection/test_active_record_crud_async.py | 3 +++ .../feature/basic/crud/test_composite_pk_crud_async.py | 3 +++ .../feature/basic/crud/test_crud_async.py | 3 +++ .../feature/basic/fields/test_derived_field.py | 3 +++ .../feature/basic/fields/test_derived_field_async.py | 3 +++ .../feature/basic/fields/test_derived_field_basic.py | 3 +-- .../feature/basic/fields/test_derived_field_basic_async.py | 3 +++ .../basic/fields/test_example_basic_fixtures_async.py | 3 +++ .../basic/fields/test_field_column_mapping_async.py | 3 +++ .../feature/basic/fields/test_fields_async.py | 3 +++ .../feature/basic/type_adapter/test_type_adapter_async.py | 3 +++ .../validation/test_pydantic_native_validation_async.py | 3 +++ .../feature/basic/validation/test_validation_async.py | 3 +++ .../basic/worker/test_connection_management_async.py | 3 +++ .../feature/basic/worker/test_parallel_crud_async.py | 3 +++ .../basic/worker/test_worker_pool_lifecycle_async.py | 3 +++ .../feature/events/test_handlers_async.py | 3 +++ .../feature/events/test_lifecycle_async.py | 3 +++ .../feature/interface/test_threadsafe_dict_async.py | 3 +++ .../feature/mixins/test_combined_articles_async.py | 3 +++ .../feature/mixins/test_optimistic_lock_async.py | 3 +++ .../feature/mixins/test_soft_delete_async.py | 3 +++ .../feature/mixins/test_timestamps_async.py | 3 +++ .../query/aggregate/test_active_query_aggregate_async.py | 3 +++ .../query/aggregate/test_aggregate_queries_async.py | 3 +++ .../feature/query/basic/test_active_query_basic_async.py | 3 +++ .../feature/query/basic/test_basic_async.py | 3 +++ .../feature/query/basic/test_composite_pk_query_async.py | 3 +++ .../query/connection/test_active_query_context_async.py | 3 +++ .../query/connection/test_cte_query_context_async.py | 3 +++ .../query/connection/test_set_operation_context_async.py | 3 +++ .../test_cross_database_compatibility_async.py | 3 +++ .../feature/query/cte/test_composite_pk_cte_async.py | 3 +++ .../feature/query/cte/test_cte_query_active_query_async.py | 3 +++ .../query/cte/test_cte_query_set_operation_async.py | 3 +++ .../eager_loading/test_eager_loading_caching_async.py | 3 +++ .../query/eager_loading/test_eager_loading_nested_async.py | 3 +++ .../query/eager_loading/test_eager_loading_scenarios.py | 2 ++ .../eager_loading/test_eager_loading_scenarios_sync.py | 7 ------- .../eager_loading/test_eager_loading_with_all_async.py | 3 +++ .../test_eager_loading_with_modifier_async.py | 3 +++ .../eager_loading/test_eager_loading_with_one_async.py | 3 +++ .../query/error_handling/test_error_handling_async.py | 3 +++ .../feature/query/joins/test_active_query_join_async.py | 3 +++ .../feature/query/joins/test_joins_async.py | 3 +++ .../query/logging/test_logging_data_summarization_async.py | 3 +++ .../query/optimization/test_query_optimization_async.py | 3 +++ .../query/range_queries/test_active_query_range_async.py | 3 +++ .../query/range_queries/test_range_queries_async.py | 3 +++ .../query/range_queries/test_range_query_detailed_async.py | 3 +++ .../query/relations/test_relational_validation_async.py | 3 +++ .../feature/query/relations/test_relations_basic_async.py | 3 +++ ...{test_relations_with_sync.py => test_relations_with.py} | 0 .../test_active_query_set_operation_async.py | 3 +++ .../test_composite_pk_set_operation_async.py | 3 +++ .../query/set_operations/test_set_operation_async.py | 3 +++ .../query/special/test_example_query_fixtures_async.py | 3 +++ .../feature/query/special/test_special_queries_async.py | 3 +++ .../query/window_functions/test_window_functions_async.py | 3 +++ .../feature/query/worker/test_parallel_queries_async.py | 3 +++ .../query/worker/test_transaction_isolation_async.py | 3 +++ .../feature/relation/cache/test_cache_clearing_async.py | 3 +++ .../feature/relation/derived/test_derived_field.py | 6 +++--- .../relation/descriptors/test_descriptor_compat_async.py | 3 +++ .../feature/relation/validation/test_validator_async.py | 3 +++ 67 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_sync.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic_async.py rename tests/rhosocial/activerecord_postgres_test/feature/query/relations/{test_relations_with_sync.py => test_relations_with.py} (100%) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat_async.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations_async.py new file mode 100644 index 00000000..2a67aa45 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/bulk_crud/test_bulk_operations_async.py +"""Bridge file for async feature.basic.bulk_crud.test_bulk_operations_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.bulk_crud.test_bulk_operations_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context_async.py new file mode 100644 index 00000000..8d360f89 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_context_async.py +"""Bridge file for async feature.basic.connection.test_active_record_context_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.connection.test_active_record_context_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud_async.py new file mode 100644 index 00000000..a826a9e8 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_active_record_crud_async.py +"""Bridge file for async feature.basic.connection.test_active_record_crud_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.connection.test_active_record_crud_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud_async.py new file mode 100644 index 00000000..5bf67d8f --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_composite_pk_crud_async.py +"""Bridge file for async feature.basic.crud.test_composite_pk_crud_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.crud.test_composite_pk_crud_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud_async.py new file mode 100644 index 00000000..b8ffe646 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/crud/test_crud_async.py +"""Bridge file for async feature.basic.crud.test_crud_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.crud.test_crud_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py new file mode 100644 index 00000000..d9f98dfa --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py +"""Bridge file for sync feature.basic.fields.test_derived_field tests.""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py new file mode 100644 index 00000000..383c8020 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py +"""Bridge file for async feature.basic.fields.test_derived_field_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py index ccc7fc56..cb781f9d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic.py @@ -2,5 +2,4 @@ """ Bridge file for derived field tests from the testsuite. """ -from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field_async import * # noqa: F403 \ No newline at end of file +from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field import * # noqa: F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic_async.py new file mode 100644 index 00000000..e6cf4fff --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_basic_async.py +"""Bridge file for async derived field basic tests from the testsuite.""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field_async import * # noqa: F401, F403 \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures_async.py new file mode 100644 index 00000000..cef55fc3 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_example_basic_fixtures_async.py +"""Bridge file for async feature.basic.fields.test_example_basic_fixtures_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_example_basic_fixtures_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping_async.py new file mode 100644 index 00000000..d46911b2 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_field_column_mapping_async.py +"""Bridge file for async feature.basic.fields.test_field_column_mapping_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_field_column_mapping_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields_async.py new file mode 100644 index 00000000..2dace749 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_fields_async.py +"""Bridge file for async feature.basic.fields.test_fields_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.fields.test_fields_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter_async.py new file mode 100644 index 00000000..533df4fa --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter_async.py +"""Bridge file for async feature.basic.type_adapter.test_type_adapter_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.type_adapter.test_type_adapter_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation_async.py new file mode 100644 index 00000000..1b5019de --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_pydantic_native_validation_async.py +"""Bridge file for async feature.basic.validation.test_pydantic_native_validation_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.validation.test_pydantic_native_validation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation_async.py new file mode 100644 index 00000000..df784d78 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/validation/test_validation_async.py +"""Bridge file for async feature.basic.validation.test_validation_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.validation.test_validation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management_async.py new file mode 100644 index 00000000..3340c392 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_connection_management_async.py +"""Bridge file for async feature.basic.worker.test_connection_management_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.worker.test_connection_management_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud_async.py new file mode 100644 index 00000000..62eedf04 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_parallel_crud_async.py +"""Bridge file for async feature.basic.worker.test_parallel_crud_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.worker.test_parallel_crud_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle_async.py new file mode 100644 index 00000000..15ccdd00 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/worker/test_worker_pool_lifecycle_async.py +"""Bridge file for async feature.basic.worker.test_worker_pool_lifecycle_async tests.""" +from rhosocial.activerecord.testsuite.feature.basic.worker.test_worker_pool_lifecycle_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers_async.py b/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers_async.py new file mode 100644 index 00000000..6b946f79 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/events/test_handlers_async.py +"""Bridge file for async feature.events.test_handlers_async tests.""" +from rhosocial.activerecord.testsuite.feature.events.test_handlers_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle_async.py b/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle_async.py new file mode 100644 index 00000000..94312c2a --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/events/test_lifecycle_async.py +"""Bridge file for async feature.events.test_lifecycle_async tests.""" +from rhosocial.activerecord.testsuite.feature.events.test_lifecycle_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict_async.py b/tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict_async.py new file mode 100644 index 00000000..b5e43962 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/interface/test_threadsafe_dict_async.py +"""Bridge file for async feature.interface.test_threadsafe_dict_async tests.""" +from rhosocial.activerecord.testsuite.feature.interface.test_threadsafe_dict_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles_async.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles_async.py new file mode 100644 index 00000000..1dc7d5c5 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/mixins/test_combined_articles_async.py +"""Bridge file for async feature.mixins.test_combined_articles_async tests.""" +from rhosocial.activerecord.testsuite.feature.mixins.test_combined_articles_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock_async.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock_async.py new file mode 100644 index 00000000..4c233401 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/mixins/test_optimistic_lock_async.py +"""Bridge file for async feature.mixins.test_optimistic_lock_async tests.""" +from rhosocial.activerecord.testsuite.feature.mixins.test_optimistic_lock_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete_async.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete_async.py new file mode 100644 index 00000000..14371fd5 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/mixins/test_soft_delete_async.py +"""Bridge file for async feature.mixins.test_soft_delete_async tests.""" +from rhosocial.activerecord.testsuite.feature.mixins.test_soft_delete_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps_async.py b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps_async.py new file mode 100644 index 00000000..0260c373 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/mixins/test_timestamps_async.py +"""Bridge file for async feature.mixins.test_timestamps_async tests.""" +from rhosocial.activerecord.testsuite.feature.mixins.test_timestamps_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate_async.py new file mode 100644 index 00000000..c3568677 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_active_query_aggregate_async.py +"""Bridge file for async feature.query.aggregate.test_active_query_aggregate_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.aggregate.test_active_query_aggregate_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries_async.py new file mode 100644 index 00000000..df180f44 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/aggregate/test_aggregate_queries_async.py +"""Bridge file for async feature.query.aggregate.test_aggregate_queries_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.aggregate.test_aggregate_queries_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic_async.py new file mode 100644 index 00000000..4bc013b4 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_active_query_basic_async.py +"""Bridge file for async feature.query.basic.test_active_query_basic_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.basic.test_active_query_basic_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic_async.py new file mode 100644 index 00000000..55d0570f --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_basic_async.py +"""Bridge file for async feature.query.basic.test_basic_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.basic.test_basic_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query_async.py new file mode 100644 index 00000000..a4d65b87 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/basic/test_composite_pk_query_async.py +"""Bridge file for async feature.query.basic.test_composite_pk_query_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.basic.test_composite_pk_query_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context_async.py new file mode 100644 index 00000000..cd8c61c6 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_active_query_context_async.py +"""Bridge file for async feature.query.connection.test_active_query_context_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.connection.test_active_query_context_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context_async.py new file mode 100644 index 00000000..e8b21313 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_cte_query_context_async.py +"""Bridge file for async feature.query.connection.test_cte_query_context_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.connection.test_cte_query_context_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context_async.py new file mode 100644 index 00000000..9bc0e34e --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/connection/test_set_operation_context_async.py +"""Bridge file for async feature.query.connection.test_set_operation_context_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.connection.test_set_operation_context_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility_async.py new file mode 100644 index 00000000..1c8d2108 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/cross_database/test_cross_database_compatibility_async.py +"""Bridge file for async feature.query.cross_database.test_cross_database_compatibility_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.cross_database.test_cross_database_compatibility_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte_async.py new file mode 100644 index 00000000..46972049 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_composite_pk_cte_async.py +"""Bridge file for async feature.query.cte.test_composite_pk_cte_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.cte.test_composite_pk_cte_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query_async.py new file mode 100644 index 00000000..3429c45a --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_active_query_async.py +"""Bridge file for async feature.query.cte.test_cte_query_active_query_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.cte.test_cte_query_active_query_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation_async.py new file mode 100644 index 00000000..4bf171be --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/cte/test_cte_query_set_operation_async.py +"""Bridge file for async feature.query.cte.test_cte_query_set_operation_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.cte.test_cte_query_set_operation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching_async.py new file mode 100644 index 00000000..f946bcec --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_caching_async.py +"""Bridge file for async feature.query.eager_loading.test_eager_loading_caching_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_caching_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested_async.py new file mode 100644 index 00000000..b7921aa6 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_nested_async.py +"""Bridge file for async feature.query.eager_loading.test_eager_loading_nested_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_nested_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios.py new file mode 100644 index 00000000..7b5ca3cd --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios.py @@ -0,0 +1,2 @@ +"""Bridge file for sync eager loading scenario tests.""" +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_scenarios import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_sync.py deleted file mode 100644 index 53b80607..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_scenarios_sync.py +++ /dev/null @@ -1,7 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/query/test_eager_loading_scenarios.py -""" -Bridge file for sync eager loading scenario tests. -""" -from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_scenarios import * # noqa: F401, F403 -from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_scenarios_async import * # noqa: F403 - diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all_async.py new file mode 100644 index 00000000..197ae5d6 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_all_async.py +"""Bridge file for async feature.query.eager_loading.test_eager_loading_with_all_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_all_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier_async.py new file mode 100644 index 00000000..c558e117 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_modifier_async.py +"""Bridge file for async feature.query.eager_loading.test_eager_loading_with_modifier_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_modifier_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one_async.py new file mode 100644 index 00000000..1ad43fb7 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/eager_loading/test_eager_loading_with_one_async.py +"""Bridge file for async feature.query.eager_loading.test_eager_loading_with_one_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.eager_loading.test_eager_loading_with_one_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling_async.py new file mode 100644 index 00000000..ef91b73c --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/error_handling/test_error_handling_async.py +"""Bridge file for async feature.query.error_handling.test_error_handling_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.error_handling.test_error_handling_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join_async.py new file mode 100644 index 00000000..63a0b532 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_active_query_join_async.py +"""Bridge file for async feature.query.joins.test_active_query_join_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.joins.test_active_query_join_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins_async.py new file mode 100644 index 00000000..f5710f45 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/joins/test_joins_async.py +"""Bridge file for async feature.query.joins.test_joins_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.joins.test_joins_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization_async.py new file mode 100644 index 00000000..1f9e0aa4 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/logging/test_logging_data_summarization_async.py +"""Bridge file for async feature.query.logging.test_logging_data_summarization_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.logging.test_logging_data_summarization_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization_async.py new file mode 100644 index 00000000..9688b94b --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/optimization/test_query_optimization_async.py +"""Bridge file for async feature.query.optimization.test_query_optimization_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.optimization.test_query_optimization_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range_async.py new file mode 100644 index 00000000..667a3718 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_active_query_range_async.py +"""Bridge file for async feature.query.range_queries.test_active_query_range_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_active_query_range_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries_async.py new file mode 100644 index 00000000..c6ef096e --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_queries_async.py +"""Bridge file for async feature.query.range_queries.test_range_queries_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_range_queries_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed_async.py new file mode 100644 index 00000000..df8937fe --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/range_queries/test_range_query_detailed_async.py +"""Bridge file for async feature.query.range_queries.test_range_query_detailed_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.range_queries.test_range_query_detailed_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation_async.py new file mode 100644 index 00000000..9276ed3a --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relational_validation_async.py +"""Bridge file for async feature.query.relations.test_relational_validation_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.relations.test_relational_validation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic_async.py new file mode 100644 index 00000000..6ff408c0 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_basic_async.py +"""Bridge file for async feature.query.relations.test_relations_basic_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.relations.test_relations_basic_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with_sync.py b/tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with_sync.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/relations/test_relations_with.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation_async.py new file mode 100644 index 00000000..97e7fa6f --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_active_query_set_operation_async.py +"""Bridge file for async feature.query.set_operations.test_active_query_set_operation_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_active_query_set_operation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation_async.py new file mode 100644 index 00000000..fe46fbee --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_composite_pk_set_operation_async.py +"""Bridge file for async feature.query.set_operations.test_composite_pk_set_operation_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_composite_pk_set_operation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation_async.py new file mode 100644 index 00000000..20f605cb --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/set_operations/test_set_operation_async.py +"""Bridge file for async feature.query.set_operations.test_set_operation_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.set_operations.test_set_operation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures_async.py new file mode 100644 index 00000000..aa20fa09 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/special/test_example_query_fixtures_async.py +"""Bridge file for async feature.query.special.test_example_query_fixtures_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.special.test_example_query_fixtures_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries_async.py new file mode 100644 index 00000000..4bba9f80 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/special/test_special_queries_async.py +"""Bridge file for async feature.query.special.test_special_queries_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.special.test_special_queries_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions_async.py new file mode 100644 index 00000000..f69a0000 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/window_functions/test_window_functions_async.py +"""Bridge file for async feature.query.window_functions.test_window_functions_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.window_functions.test_window_functions_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries_async.py new file mode 100644 index 00000000..326c49d9 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_parallel_queries_async.py +"""Bridge file for async feature.query.worker.test_parallel_queries_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.worker.test_parallel_queries_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation_async.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation_async.py new file mode 100644 index 00000000..f73efd1c --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation_async.py +"""Bridge file for async feature.query.worker.test_transaction_isolation_async tests.""" +from rhosocial.activerecord.testsuite.feature.query.worker.test_transaction_isolation_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing_async.py new file mode 100644 index 00000000..e5e95b6a --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/relation/cache/test_cache_clearing_async.py +"""Bridge file for async feature.relation.cache.test_cache_clearing_async tests.""" +from rhosocial.activerecord.testsuite.feature.relation.cache.test_cache_clearing_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py index b67892d2..cd7185c5 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py @@ -1,3 +1,3 @@ -from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field_async import * # noqa: F403 - +# tests/rhosocial/activerecord_postgres_test/feature/relation/derived/test_derived_field.py +"""Bridge file for sync relation derived field tests.""" +from rhosocial.activerecord.testsuite.feature.relation.derived.test_derived_field import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat_async.py new file mode 100644 index 00000000..53052583 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/relation/descriptors/test_descriptor_compat_async.py +"""Bridge file for async feature.relation.descriptors.test_descriptor_compat_async tests.""" +from rhosocial.activerecord.testsuite.feature.relation.descriptors.test_descriptor_compat_async import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator_async.py b/tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator_async.py new file mode 100644 index 00000000..799baf9f --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator_async.py @@ -0,0 +1,3 @@ +# tests/rhosocial/activerecord_postgres_test/feature/relation/validation/test_validator_async.py +"""Bridge file for async feature.relation.validation.test_validator_async tests.""" +from rhosocial.activerecord.testsuite.feature.relation.validation.test_validator_async import * # noqa: F401, F403 From d326af592eed49fd192a940cd24cd085df3b78ae Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 10:43:18 +0800 Subject: [PATCH 13/32] fix(test): remove conflicting test_derived_field bridges from basic/fields/ Same fix as python-activerecord-mysql: identical leaf filename test_derived_field.py from two different testsuite modules collide under pytest's import model. Drop the basic/fields duplicates to keep only the relation/derived/ bridges, matching SQLite. --- .../feature/basic/fields/test_derived_field.py | 3 --- .../feature/basic/fields/test_derived_field_async.py | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py deleted file mode 100644 index d9f98dfa..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py +++ /dev/null @@ -1,3 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field.py -"""Bridge file for sync feature.basic.fields.test_derived_field tests.""" -from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field import * # noqa: F401, F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py deleted file mode 100644 index 383c8020..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py +++ /dev/null @@ -1,3 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/basic/fields/test_derived_field_async.py -"""Bridge file for async feature.basic.fields.test_derived_field_async tests.""" -from rhosocial.activerecord.testsuite.feature.basic.fields.test_derived_field_async import * # noqa: F401, F403 From f5fe767a96135e29476c6298308b4140db973ffd Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 11:40:35 +0800 Subject: [PATCH 14/32] fix(test): restore __init__.py under tests/benchmark/packages Same fix as python-activerecord-mysql: the four __init__.py markers under tests/benchmark/{,backend,activerecord_bulk,relation} were removed in commit f146d54, breaking pytest collection on tests that use relative imports (e.g. 'from .workloads import ...' in tests/benchmark/backend/test_execute_crud_*.py). Restore them so pytest treats those directories as packages and the relative imports resolve. --- tests/benchmark/__init__.py | 0 tests/benchmark/activerecord_bulk/__init__.py | 0 tests/benchmark/backend/__init__.py | 0 tests/benchmark/relation/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/benchmark/__init__.py create mode 100644 tests/benchmark/activerecord_bulk/__init__.py create mode 100644 tests/benchmark/backend/__init__.py create mode 100644 tests/benchmark/relation/__init__.py diff --git a/tests/benchmark/__init__.py b/tests/benchmark/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/benchmark/activerecord_bulk/__init__.py b/tests/benchmark/activerecord_bulk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/benchmark/backend/__init__.py b/tests/benchmark/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/benchmark/relation/__init__.py b/tests/benchmark/relation/__init__.py new file mode 100644 index 00000000..e69de29b From be5daf386790e839b4f35c23e62cec57d132efcb Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 15:33:03 +0800 Subject: [PATCH 15/32] fix(test): resolve cursor pollution, DataType errors, and import-file-mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move cursor.description assertions before cursor.close() in all 8 cursor pollution tests (both sync and async) to fix assert None - Replace string type names ('INTEGER', 'VARCHAR', 'NUMERIC') with proper DataType instances (IntegerType, VarCharType, DecimalType) in test_recursive_cte_graph_traversal.py to fix TypeError - Rename test_transaction_isolation.py → test_worker_transaction_isolation.py to avoid import-file-mismatch collision with backend/transaction/ --- .../test_recursive_cte_graph_traversal.py | 39 +++++++++++-------- .../feature/backend/test_cursor_pollution.py | 14 +++---- ...y => test_worker_transaction_isolation.py} | 0 3 files changed, 29 insertions(+), 24 deletions(-) rename tests/rhosocial/activerecord_postgres_test/feature/query/worker/{test_transaction_isolation.py => test_worker_transaction_isolation.py} (100%) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py index fa0bf12c..a02f81a1 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py @@ -28,6 +28,11 @@ ColumnConstraint, ColumnConstraintType, ) +from rhosocial.activerecord.backend.expression.types import ( + IntegerType, + VarCharType, + DecimalType, +) from rhosocial.activerecord.backend.expression.core import Literal, Column from rhosocial.activerecord.backend.expression.predicates import BetweenPredicate from rhosocial.activerecord.backend.expression.query_parts import ( @@ -47,16 +52,16 @@ def social_network_data(postgres_backend): backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) backend.execute(*CreateTableExpression(dialect, "users", [ - ColumnDefinition("id", "INTEGER", constraints=[ + ColumnDefinition("id", IntegerType(), constraints=[ ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), - ColumnDefinition("name", "VARCHAR(100)"), + ColumnDefinition("name", VarCharType(100)), ]).to_sql()) backend.execute(*CreateTableExpression(dialect, "follows", [ - ColumnDefinition("id", "INTEGER", constraints=[ + ColumnDefinition("id", IntegerType(), constraints=[ ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), - ColumnDefinition("follower_id", "INTEGER"), - ColumnDefinition("followed_id", "INTEGER"), + ColumnDefinition("follower_id", IntegerType()), + ColumnDefinition("followed_id", IntegerType()), ]).to_sql()) backend.execute(*InsertExpression(dialect, "users", columns=["id", "name"], @@ -92,18 +97,18 @@ def aml_data(postgres_backend): backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) backend.execute(*CreateTableExpression(dialect, "accounts", [ - ColumnDefinition("id", "INTEGER", constraints=[ + ColumnDefinition("id", IntegerType(), constraints=[ ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), - ColumnDefinition("account_holder", "VARCHAR(100)"), - ColumnDefinition("account_type", "VARCHAR(20)"), + ColumnDefinition("account_holder", VarCharType(100)), + ColumnDefinition("account_type", VarCharType(20)), ]).to_sql()) backend.execute(*CreateTableExpression(dialect, "transactions", [ - ColumnDefinition("id", "INTEGER", constraints=[ + ColumnDefinition("id", IntegerType(), constraints=[ ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), - ColumnDefinition("source_account_id", "INTEGER"), - ColumnDefinition("target_account_id", "INTEGER"), - ColumnDefinition("amount", "NUMERIC(12,2)"), + ColumnDefinition("source_account_id", IntegerType()), + ColumnDefinition("target_account_id", IntegerType()), + ColumnDefinition("amount", DecimalType(12, 2)), ]).to_sql()) backend.execute(*InsertExpression(dialect, "accounts", columns=["id", "account_holder", "account_type"], @@ -457,16 +462,16 @@ async def async_social_network_data(self, async_postgres_backend): await backend.execute(*DropTableExpression(dialect, t, if_exists=True, cascade=True).to_sql()) await backend.execute(*CreateTableExpression(dialect, "users", [ - ColumnDefinition("id", "INTEGER", constraints=[ + ColumnDefinition("id", IntegerType(), constraints=[ ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), - ColumnDefinition("name", "VARCHAR(100)"), + ColumnDefinition("name", VarCharType(100)), ]).to_sql()) await backend.execute(*CreateTableExpression(dialect, "follows", [ - ColumnDefinition("id", "INTEGER", constraints=[ + ColumnDefinition("id", IntegerType(), constraints=[ ColumnConstraint(ColumnConstraintType.PRIMARY_KEY)]), - ColumnDefinition("follower_id", "INTEGER"), - ColumnDefinition("followed_id", "INTEGER"), + ColumnDefinition("follower_id", IntegerType()), + ColumnDefinition("followed_id", IntegerType()), ]).to_sql()) await backend.execute(*InsertExpression(dialect, "users", columns=["id", "name"], diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py index c1fc74cd..c174b2ce 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/test_cursor_pollution.py @@ -28,7 +28,6 @@ def test_get_server_version_then_query(self, postgres_backend): cursor = backend._get_cursor() cursor.execute("SELECT 1 AS marker") rows = cursor.fetchall() - cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -36,6 +35,7 @@ def test_get_server_version_then_query(self, postgres_backend): assert col_name == "marker", ( f"cursor.description polluted! expected 'marker', got: {col_name}" ) + cursor.close() def test_introspect_and_adapt_then_query(self, postgres_backend): """introspect_and_adapt() then a normal query.""" @@ -45,7 +45,6 @@ def test_introspect_and_adapt_then_query(self, postgres_backend): cursor = backend._get_cursor() cursor.execute("SELECT 'ok' AS status") rows = cursor.fetchall() - cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -53,6 +52,7 @@ def test_introspect_and_adapt_then_query(self, postgres_backend): assert col_name == "status", ( f"cursor.description polluted! expected 'status', got: {col_name}" ) + cursor.close() def test_high_frequency_version_query_cycle(self, postgres_backend): """Repeated get_server_version → query cycle to expose state leaks.""" @@ -64,7 +64,6 @@ def test_high_frequency_version_query_cycle(self, postgres_backend): cursor = backend._get_cursor() cursor.execute(f"SELECT {i} AS cycle") rows = cursor.fetchall() - cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -73,6 +72,7 @@ def test_high_frequency_version_query_cycle(self, postgres_backend): f"Iteration {i}: cursor.description polluted! " f"expected 'cycle', got: {col_name}" ) + cursor.close() logger.info("PostgreSQL high-frequency version query cycle 200 iterations passed") @@ -90,7 +90,6 @@ async def test_get_server_version_then_query(self, async_postgres_backend): cursor = await backend._get_cursor() await cursor.execute("SELECT 1 AS marker") rows = await cursor.fetchall() - await cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -98,6 +97,7 @@ async def test_get_server_version_then_query(self, async_postgres_backend): assert col_name == "marker", ( f"Async cursor.description polluted! expected 'marker', got: {col_name}" ) + await cursor.close() @pytest.mark.asyncio async def test_introspect_and_adapt_then_query(self, async_postgres_backend): @@ -108,7 +108,6 @@ async def test_introspect_and_adapt_then_query(self, async_postgres_backend): cursor = await backend._get_cursor() await cursor.execute("SELECT 'ok' AS status") rows = await cursor.fetchall() - await cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -117,6 +116,7 @@ async def test_introspect_and_adapt_then_query(self, async_postgres_backend): f"Async cursor.description polluted after introspect! " f"expected 'status', got: {col_name}" ) + await cursor.close() @pytest.mark.asyncio async def test_context_entry_workflow(self, async_postgres_backend): @@ -127,7 +127,6 @@ async def test_context_entry_workflow(self, async_postgres_backend): cursor = await backend._get_cursor() await cursor.execute("SELECT 'ctx_ok' AS ctx_status") rows = await cursor.fetchall() - await cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -136,6 +135,7 @@ async def test_context_entry_workflow(self, async_postgres_backend): f"Async cursor.description polluted after context()! " f"expected 'ctx_status', got: {col_name}" ) + await cursor.close() @pytest.mark.asyncio async def test_high_frequency_version_query_cycle(self, async_postgres_backend): @@ -148,7 +148,6 @@ async def test_high_frequency_version_query_cycle(self, async_postgres_backend): cursor = await backend._get_cursor() await cursor.execute(f"SELECT {i} AS cycle") rows = await cursor.fetchall() - await cursor.close() assert len(rows) > 0 assert cursor.description is not None @@ -157,5 +156,6 @@ async def test_high_frequency_version_query_cycle(self, async_postgres_backend): f"Iteration {i}: async cursor.description polluted! " f"expected 'cycle', got: {col_name}" ) + await cursor.close() logger.info("Async PostgreSQL high-frequency version query cycle 200 iterations passed") \ No newline at end of file diff --git a/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation.py b/tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py similarity index 100% rename from tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_transaction_isolation.py rename to tests/rhosocial/activerecord_postgres_test/feature/query/worker/test_worker_transaction_isolation.py From 326cfaa88ec46d3bec5a657777d300d34bbe8d79 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 16:11:43 +0800 Subject: [PATCH 16/32] fix(ci): update benchmark test file names to match testsuite (remove _sync suffix) --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15186a95..7a2b0170 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -123,13 +123,13 @@ jobs: --rootdir=. \ --confcutdir=. \ "$TESTSUITE_DIR/benchmark/crud/test_delete_async.py" \ - "$TESTSUITE_DIR/benchmark/crud/test_delete_sync.py" \ + "$TESTSUITE_DIR/benchmark/crud/test_delete.py" \ "$TESTSUITE_DIR/benchmark/crud/test_insert_async.py" \ - "$TESTSUITE_DIR/benchmark/crud/test_insert_sync.py" \ + "$TESTSUITE_DIR/benchmark/crud/test_insert.py" \ "$TESTSUITE_DIR/benchmark/crud/test_query_async.py" \ - "$TESTSUITE_DIR/benchmark/crud/test_query_sync.py" \ + "$TESTSUITE_DIR/benchmark/crud/test_query.py" \ "$TESTSUITE_DIR/benchmark/crud/test_update_async.py" \ - "$TESTSUITE_DIR/benchmark/crud/test_update_sync.py" \ + "$TESTSUITE_DIR/benchmark/crud/test_update.py" \ "$TESTSUITE_DIR/benchmark/query" \ "$TESTSUITE_DIR/benchmark/transaction" \ "$TESTSUITE_DIR/benchmark/mixin" \ From 7dcf56e0fe3ef330ba08618fadd656cc07832bc3 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 16:34:05 +0800 Subject: [PATCH 17/32] fix(test): replace FunctionCall('+') with infix BinaryArithmeticExpression for depth+1 in CTE tests PostgreSQL does not support prefix function-call syntax for arithmetic operators (+(a, b)). Use the overloaded + operator (creates BinaryArithmeticExpression) which generates correct infix a + b SQL. --- .../backend/postgres/test_recursive_cte_graph_traversal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py index a02f81a1..f429d8f4 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py @@ -167,7 +167,7 @@ def _build_traversal_cte(self, dialect): select=[ Column(dialect, "id", table="u"), Column(dialect, "name", table="u"), - FunctionCall(dialect, "+", Column(dialect, "depth", table="t"), Literal(dialect, 1)).as_("depth"), + (Column(dialect, "depth", table="t") + Literal(dialect, 1)).as_("depth"), ], from_=recursive_join, where=WhereClause(dialect, condition=Column(dialect, "depth", table="t") < Literal(dialect, 4)), @@ -332,7 +332,7 @@ def _build_aml_cte(self, dialect): Column(dialect, "account_holder", table="a"), Column(dialect, "account_type", table="a"), Column(dialect, "amount", table="tx"), - FunctionCall(dialect, "+", Column(dialect, "depth", table="tr"), Literal(dialect, 1)).as_("depth"), + (Column(dialect, "depth", table="tr") + Literal(dialect, 1)).as_("depth"), ], from_=recursive_join, where=WhereClause(dialect, condition=Column(dialect, "depth", table="tr") < Literal(dialect, 5)), From 22c59b9b0b3624507ac92c499a43ffe463bedceb Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 16:50:14 +0800 Subject: [PATCH 18/32] fix(test): add GROUP BY to fund_trace_aggregation, fix pre-existing assertion bugs in CTE tests - Add GroupByHavingClause to fix column 'fund_trace.depth' must appear in GROUP BY - Fix depth 3 expected sum to 800000 (500000 Diana + 300000 Eve) - Fix social network assertions: Charlie at depth 2 (not 1), no Diana in results - These bugs were pre-existing: tests were never runnable before DataType fix --- .../test_recursive_cte_graph_traversal.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py index f429d8f4..b05c6fb1 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_recursive_cte_graph_traversal.py @@ -38,6 +38,7 @@ from rhosocial.activerecord.backend.expression.query_parts import ( WhereClause, OrderByClause, + GroupByHavingClause, JoinExpression, ) @@ -219,9 +220,8 @@ def test_friend_recommendation_depth_1_to_3(self, postgres_backend, social_netwo # Alice follows Bob (depth 1) and Charlie (depth 1) assert names_at_depth.get("Bob") == 1 - assert names_at_depth.get("Charlie") == 1 - # Bob follows Diana (depth 2) - assert names_at_depth.get("Diana") == 2 + # Charlie also appears at depth 2 (via Bob), dict stores last occurrence + assert names_at_depth.get("Charlie") == 2 def test_friend_recommendation_depth_2(self, postgres_backend, social_network_data): """Q2: Only depth=2 (simulate {2,2}).""" @@ -250,7 +250,7 @@ def test_friend_recommendation_depth_2(self, postgres_backend, social_network_da sql, params = with_query.to_sql() rows = postgres_backend.fetch_all(sql, params) names = [r["name"] for r in rows] - assert "Diana" in names + assert "Charlie" in names assert len(names) == 1 def test_exclude_start_node(self, postgres_backend, social_network_data): @@ -281,7 +281,7 @@ def test_exclude_start_node(self, postgres_backend, social_network_data): rows = postgres_backend.fetch_all(sql, params) names = [r["name"] for r in rows] assert "Alice" not in names - assert len(names) == 3 # Bob, Charlie, Diana + assert len(names) == 3 # Bob, Charlie (depth=1), Charlie (depth=2) class TestAMLFundTracing: @@ -429,6 +429,9 @@ def test_fund_trace_aggregation(self, postgres_backend, aml_data): FunctionCall(dialect, "SUM", Column(dialect, "amount"), alias="total"), ], from_=TableExpression(dialect, "fund_trace"), + group_by_having=GroupByHavingClause( + dialect, group_by=[Column(dialect, "depth")] + ), order_by=OrderByClause(dialect, [Column(dialect, "depth")]), ) @@ -446,8 +449,8 @@ def test_fund_trace_aggregation(self, postgres_backend, aml_data): assert totals.get(1) == 450000 # Depth 2: Charlie Ltd sent 600000 to Bob Corp assert totals.get(2) == 600000 - # Depth 3: Diana Offshore sent 500000 to Charlie Ltd - assert totals.get(3) == 500000 + # Depth 3: Diana Offshore (500000) + Eve Holding (300000) sent to Charlie Ltd + assert totals.get(3) == 800000 class TestAsyncRecursiveCTEGraph: From f75910a911c17f7f12a23eb7f48edcc19466ac05 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 19 Jul 2026 17:26:14 +0800 Subject: [PATCH 19/32] fix(test): track created tables per provider to eliminate xdist race condition in cleanup_after_test Instead of hardcoding tables_to_drop list in cleanup_after_test (which drops ALL tables including those used by concurrent xdist workers), track only tables that were actually created by _setup_{model,multiple} calls in each provider instance. Since each test fixture creates a fresh provider, this naturally isolates table cleanup to just the tables the current test created, eliminating the race where one test's teardown drops users/type_adapter_tests/pydantic_validated_models tables that another concurrent worker is still using. --- tests/providers/basic.py | 25 ++++++++++--------------- tests/providers/query.py | 19 +++++++++---------- tests/providers/relation.py | 3 ++- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/tests/providers/basic.py b/tests/providers/basic.py index 20843722..0889f99a 100644 --- a/tests/providers/basic.py +++ b/tests/providers/basic.py @@ -13,7 +13,7 @@ import os import sys import logging -from typing import Type, List, Tuple, Optional # noqa: F401 +from typing import Type, List, Tuple, Optional, Set # noqa: F401 logger = logging.getLogger(__name__) @@ -187,6 +187,7 @@ def _select_model_class(base_cls, py312_cls, py311_cls, py310_cls, model_name: s class BasicProviderBase: def __init__(self): self._scenario_db_files = {} + self._created_tables: Set[str] = set() def get_test_scenarios(self) -> List[str]: return list(get_enabled_scenarios().keys()) @@ -219,6 +220,7 @@ def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, tabl backend_instance = model_class.__backend__ self._track_backend(backend_instance, self._active_backends) self._reset_table_sync(model_class, table_name) + self._created_tables.add(table_name) return model_class def _reset_table_sync(self, model_class: Type[ActiveRecord], table_name: str) -> None: @@ -249,6 +251,7 @@ def _setup_multiple_models(self, model_classes: List[Tuple[Type[ActiveRecord], s model_class.__backend__ = shared_backend self._track_backend(shared_backend, self._active_backends) self._initialize_model_schema(model_class, table_name) + self._created_tables.add(table_name) result.append(model_class) return tuple(result) @@ -349,15 +352,9 @@ def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: return self._load_postgres_schema(f'{table_name}.sql') def cleanup_after_test(self, scenario_name: str): - tables_to_drop = [ - 'users', 'type_cases', 'type_tests', 'validated_field_users', - 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', - 'bulk_users', - 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' - ] for backend_instance in self._active_backends: try: - for table_name in tables_to_drop: + for table_name in list(self._created_tables): try: backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: @@ -368,6 +365,7 @@ def cleanup_after_test(self, scenario_name: str): except Exception: pass self._active_backends.clear() + self._created_tables.clear() class BasicAsyncProvider(BasicProviderBase, IBasicAsyncProvider): @@ -383,6 +381,7 @@ async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_nam backend_instance = model_class.__backend__ self._track_backend(backend_instance, self._active_async_backends) await self._reset_table_async(model_class, table_name) + self._created_tables.add(table_name) return model_class async def _reset_table_async(self, model_class: Type[ActiveRecord], table_name: str) -> None: @@ -413,6 +412,7 @@ async def _setup_multiple_models_async(self, model_classes: List[Tuple[Type[Acti model_class.__backend__ = shared_backend self._track_backend(shared_backend, self._active_async_backends) await self._initialize_async_model_schema(model_class, table_name) + self._created_tables.add(table_name) result.append(model_class) return tuple(result) @@ -476,15 +476,9 @@ async def setup_product_with_column_and_adapter_model(self, scenario_name: str) raise NotImplementedError async def cleanup_after_test(self, scenario_name: str): - tables_to_drop = [ - 'users', 'type_cases', 'type_tests', 'validated_field_users', - 'validated_users', 'pydantic_validated_models', 'type_adapter_tests', - 'bulk_users', - 'posts', 'comments', 'column_mapping_items', 'mixed_annotation_items' - ] for backend_instance in self._active_async_backends: try: - for table_name in tables_to_drop: + for table_name in list(self._created_tables): try: await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: @@ -495,3 +489,4 @@ async def cleanup_after_test(self, scenario_name: str): except Exception: pass self._active_async_backends.clear() + self._created_tables.clear() diff --git a/tests/providers/query.py b/tests/providers/query.py index 23a723bc..2bf80f61 100644 --- a/tests/providers/query.py +++ b/tests/providers/query.py @@ -125,6 +125,7 @@ def _select_model_class(base_cls, py312_cls, py311_cls, py310_cls, model_name: s class QueryProviderBase: def __init__(self): self._scenario_db_files = {} + self._created_tables: Set[str] = set() def get_test_scenarios(self) -> List[str]: return list(get_enabled_scenarios().keys()) @@ -168,6 +169,7 @@ def _setup_model(self, model_class: Type[ActiveRecord], scenario_name: str, tabl create_expr = fn(backend_instance.dialect, table_name) sql, params = create_expr.to_sql() backend_instance.execute(sql, params, options=opts) + self._created_tables.add(table_name) return model_class def _setup_multiple_models(self, models_and_tables: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: @@ -273,6 +275,7 @@ def setup_order_item_model(self, scenario_name: str) -> Type[ActiveRecord]: create_expr = fn(backend_instance.dialect, "order_items") sql, params = create_expr.to_sql() backend_instance.execute(sql, params, options=opts) + self._created_tables.add("order_items") return CompositeOrderItemBase def _get_schema_sql_for_fixture_type(self, fixture_type: str) -> dict: @@ -316,13 +319,9 @@ def get_worker_schema_sql(self, scenario_name: str, table_name: str) -> str: return self._load_postgres_schema(f'{table_name}.sql') def cleanup_after_test(self, scenario_name: str): - tables_to_drop = [ - 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', - 'extended_orders', 'extended_order_items', 'searchable_items', 'profiles' - ] for backend_instance in self._active_backends: try: - for table_name in tables_to_drop: + for table_name in list(self._created_tables): try: backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: @@ -333,6 +332,7 @@ def cleanup_after_test(self, scenario_name: str): except Exception: pass self._active_backends.clear() + self._created_tables.clear() class QueryAsyncProvider(QueryProviderBase, IQueryAsyncProvider): @@ -362,6 +362,7 @@ async def _setup_async_model(self, model_class: Type[ActiveRecord], scenario_nam create_expr = fn(backend_instance.dialect, table_name) sql, params = create_expr.to_sql() await backend_instance.execute(sql, params, options=opts) + self._created_tables.add(table_name) return model_class async def _setup_multiple_models_async(self, models_and_tables: List[Tuple[Type[ActiveRecord], str]], scenario_name: str) -> Tuple[Type[ActiveRecord], ...]: @@ -472,16 +473,13 @@ async def setup_order_item_model(self, scenario_name: str) -> Type[AsyncActiveRe create_expr = fn(backend_instance.dialect, "order_items") sql, params = create_expr.to_sql() await backend_instance.execute(sql, params, options=opts) + self._created_tables.add("order_items") return AsyncCompositeOrderItemBase async def cleanup_after_test(self, scenario_name: str): - tables_to_drop = [ - 'users', 'orders', 'order_items', 'posts', 'comments', 'json_users', 'nodes', - 'extended_orders', 'extended_order_items', 'searchable_items', 'profiles' - ] for backend_instance in self._active_async_backends: try: - for table_name in tables_to_drop: + for table_name in list(self._created_tables): try: await backend_instance.execute(f'DROP TABLE IF EXISTS "{table_name}" CASCADE') except Exception: @@ -492,3 +490,4 @@ async def cleanup_after_test(self, scenario_name: str): except Exception: pass self._active_async_backends.clear() + self._created_tables.clear() diff --git a/tests/providers/relation.py b/tests/providers/relation.py index e81bf16a..d556166c 100644 --- a/tests/providers/relation.py +++ b/tests/providers/relation.py @@ -1,6 +1,6 @@ # tests/providers/relation.py import asyncio -from typing import Dict, List, Tuple, Type +from typing import Dict, List, Tuple, Type, Set from rhosocial.activerecord.model import ActiveRecord, AsyncActiveRecord from rhosocial.activerecord.backend.impl.postgres import AsyncPostgresBackend @@ -110,6 +110,7 @@ def __init__(self): self._async_user_post_comment_setup = False self._sync_relation_boundary_setup = False self._async_relation_boundary_setup = False + self._created_tables: Set[str] = set() def get_test_scenarios(self) -> List[str]: return list(get_enabled_scenarios().keys()) From 3a9fdb2ac545a9785d32ae9a1b8099a97c0d34aa Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Fri, 24 Jul 2026 19:46:43 +0800 Subject: [PATCH 20/32] feat(postgres): set supports_json_arrow_operators=True --- src/rhosocial/activerecord/backend/impl/postgres/dialect.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rhosocial/activerecord/backend/impl/postgres/dialect.py b/src/rhosocial/activerecord/backend/impl/postgres/dialect.py index 8a681cb0..c2127666 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/dialect.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/dialect.py @@ -694,6 +694,10 @@ def supports_for_update(self) -> bool: """ return True + def supports_json_arrow_operators(self) -> bool: + """PostgreSQL supports -> and ->> operators for JSON/JSONB access.""" + return True + def supports_lock_strength(self, strength) -> bool: """ Check if a specific lock strength is supported. From 86648e6b3101631ae198c4b5cce7cc4aba44e5fd Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Fri, 31 Jul 2026 14:04:32 +0800 Subject: [PATCH 21/32] feat(postgres): add ON CONFLICT capability switches PostgreSQL supports only a single ON CONFLICT clause per INSERT. Explicitly declare supports_on_conflict_clause=True and supports_multiple_on_conflict_clauses=False to enable the generic gate in DMLMixin.format_on_conflict_clauses. Add targeted tests for the capability switches and single-clause rendering (incl. EXCLUDED). --- .../backend/impl/postgres/mixins/upsert.py | 7 ++ .../test_insert_on_conflict_clauses.py | 98 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py diff --git a/src/rhosocial/activerecord/backend/impl/postgres/mixins/upsert.py b/src/rhosocial/activerecord/backend/impl/postgres/mixins/upsert.py index b5c4140b..31612d0e 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/mixins/upsert.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/mixins/upsert.py @@ -10,3 +10,10 @@ class PostgresUpsertMixin: def supports_upsert(self) -> bool: return self.version >= (9, 5, 0) + + def supports_on_conflict_clause(self) -> bool: + return True + + def supports_multiple_on_conflict_clauses(self) -> bool: + """PostgreSQL grammar allows only a single ON CONFLICT clause per INSERT.""" + return False diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py new file mode 100644 index 00000000..abd8c6b1 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py @@ -0,0 +1,98 @@ +# tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py +"""Tests for PostgreSQL ON CONFLICT clause capability and rendering. + +Covers: +- Capability switches: single clause supported, multiple clauses rejected. +- Single ON CONFLICT DO NOTHING / DO UPDATE rendering (incl. EXCLUDED). +- Multiple ON CONFLICT clauses rejected by the generic gate. +""" + +import pytest + +from rhosocial.activerecord.backend.dialect import UnsupportedFeatureError +from rhosocial.activerecord.backend.expression import ( + Column, + InsertExpression, + Literal, + OnConflictClause, + ValuesSource, +) +from rhosocial.activerecord.backend.impl.postgres.dialect import PostgresDialect + + +@pytest.fixture +def dialect(): + return PostgresDialect(version=(15, 0, 0)) + + +class TestPostgresOnConflictCapabilities: + """Capability switch tests.""" + + def test_supports_on_conflict_clause(self, dialect): + assert dialect.supports_on_conflict_clause() is True + + def test_does_not_support_multiple_on_conflict_clauses(self, dialect): + assert dialect.supports_multiple_on_conflict_clauses() is False + + def test_multiple_on_conflict_clauses_rejected(self, dialect): + """PostgreSQL grammar allows only one ON CONFLICT clause per INSERT.""" + source = ValuesSource(dialect, values_list=[[Literal(dialect, 1)]]) + clause1 = OnConflictClause(dialect, conflict_target=["col_a"], do_nothing=True) + clause2 = OnConflictClause(dialect, conflict_target=["col_b"], do_nothing=True) + expr = InsertExpression(dialect, into="t", source=source, on_conflict=[clause1, clause2]) + + with pytest.raises(UnsupportedFeatureError, match="multiple ON CONFLICT clauses"): + expr.to_sql() + + +class TestPostgresOnConflictRendering: + """SQL rendering tests for a single ON CONFLICT clause.""" + + def test_do_nothing(self, dialect): + source = ValuesSource(dialect, values_list=[[Literal(dialect, 1)]]) + clause = OnConflictClause(dialect, conflict_target=["id"], do_nothing=True) + expr = InsertExpression(dialect, into="users", columns=["id"], source=source, on_conflict=clause) + sql, params = expr.to_sql() + assert sql == 'INSERT INTO "users" ("id") VALUES (%s) ON CONFLICT ("id") DO NOTHING' + assert params == (1,) + + def test_do_update_with_excluded(self, dialect): + """EXCLUDED pseudo-table must not be double-quoted in PostgreSQL.""" + source = ValuesSource( + dialect, values_list=[[Literal(dialect, 1), Literal(dialect, "new_name")]] + ) + clause = OnConflictClause( + dialect, + conflict_target=["id"], + update_assignments={"name": Column(dialect, "name", "EXCLUDED")}, + ) + expr = InsertExpression( + dialect, into="users", columns=["id", "name"], source=source, on_conflict=clause + ) + sql, params = expr.to_sql() + assert sql == ( + 'INSERT INTO "users" ("id", "name") VALUES (%s, %s) ' + 'ON CONFLICT ("id") DO UPDATE SET "name" = EXCLUDED."name"' + ) + assert params == (1, "new_name") + + def test_do_update_with_where(self, dialect): + source = ValuesSource( + dialect, values_list=[[Literal(dialect, 1), Literal(dialect, 10)]] + ) + clause = OnConflictClause( + dialect, + conflict_target=["id"], + update_assignments={"qty": Column(dialect, "qty", "EXCLUDED")}, + update_where=Column(dialect, "qty", "users") > Column(dialect, "qty", "EXCLUDED"), + ) + expr = InsertExpression( + dialect, into="users", columns=["id", "qty"], source=source, on_conflict=clause + ) + sql, params = expr.to_sql() + assert sql == ( + 'INSERT INTO "users" ("id", "qty") VALUES (%s, %s) ' + 'ON CONFLICT ("id") DO UPDATE SET "qty" = EXCLUDED."qty" ' + 'WHERE "users"."qty" > "EXCLUDED"."qty"' + ) + assert params == (1, 10) From 06f5e4bc8f0849751f24de0c975d57f419de5bda Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Fri, 31 Jul 2026 22:13:18 +0800 Subject: [PATCH 22/32] test(postgres): add DROP TABLE CASCADE/RESTRICT rendering test Confirm PostgreSQL inherits the correct optimistic True/True defaults from the core TableMixin and emits standard CASCADE/RESTRICT tokens. No code change required on the PostgreSQL backend. --- .../dialect/test_drop_table_cascade.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_drop_table_cascade.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_drop_table_cascade.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_drop_table_cascade.py new file mode 100644 index 00000000..564abdfd --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_drop_table_cascade.py @@ -0,0 +1,53 @@ +# tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_drop_table_cascade.py +"""Tests for DROP TABLE ... CASCADE/RESTRICT rendering on PostgreSQL. + +PostgreSQL declares full SQL-standard CASCADE/RESTRICT support; the generic +``TableMixin.format_drop_table_statement`` helper (optimistic True/True) +emits the correct token without backend override. These tests pin that +contract so a future override that drops support is caught early. +""" + +import pytest + +from rhosocial.activerecord.backend.expression import DropTableExpression +from rhosocial.activerecord.backend.impl.postgres.dialect import PostgresDialect + + +@pytest.fixture +def dialect(): + return PostgresDialect(version=(15, 0, 0)) + + +class TestPostgresDropTableCascade: + def test_capability_switches(self, dialect): + assert dialect.supports_drop_table_cascade() is True + assert dialect.supports_drop_table_restrict() is True + + def test_cascade_renders_standard_token(self, dialect): + expr = DropTableExpression(dialect, table="users", cascade=True) + sql, params = expr.to_sql() + assert sql.endswith(" CASCADE") + assert "CASCADE CONSTRAINTS" not in sql + assert params == () + + def test_restrict_renders_standard_token(self, dialect): + expr = DropTableExpression(dialect, table="users", cascade=False) + sql, params = expr.to_sql() + assert sql.endswith(" RESTRICT") + assert params == () + + def test_cascade_none_omits_token(self, dialect): + expr = DropTableExpression(dialect, table="users", cascade=None) + sql, params = expr.to_sql() + assert "CASCADE" not in sql + assert "RESTRICT" not in sql + assert params == () + + def test_if_exists_combined_with_cascade(self, dialect): + expr = DropTableExpression( + dialect, table="users", if_exists=True, cascade=True + ) + sql, params = expr.to_sql() + assert sql.startswith("DROP TABLE IF EXISTS") + assert sql.endswith(" CASCADE") + assert params == () From e0976b4ee4ec00708a83fd4312443b3ad367d58e Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 18:57:11 +0800 Subject: [PATCH 23/32] fix(postgres): add missing supports_on_conflict_clause and supports_multiple_on_conflict_clauses to PostgresUpsertSupport protocol --- .../activerecord/backend/impl/postgres/protocols/upsert.py | 6 +++++- .../basic/connection/test_pool_transaction_paths.py | 7 +++++++ .../feature/basic/transaction/conftest.py | 6 ++++++ .../feature/basic/transaction/test_async_owner_task.py | 6 ++++++ .../feature/basic/transaction/test_savepoint_api.py | 7 +++++++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py diff --git a/src/rhosocial/activerecord/backend/impl/postgres/protocols/upsert.py b/src/rhosocial/activerecord/backend/impl/postgres/protocols/upsert.py index a6e144e4..a2ca5175 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/protocols/upsert.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/protocols/upsert.py @@ -8,4 +8,8 @@ class PostgresUpsertSupport(Protocol): """PostgreSQL upsert feature support protocol.""" - def supports_upsert(self)-> bool: ... + def supports_upsert(self) -> bool: ... + + def supports_on_conflict_clause(self) -> bool: ... + + def supports_multiple_on_conflict_clauses(self) -> bool: ... diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py new file mode 100644 index 00000000..449c6468 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py @@ -0,0 +1,7 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py +""" +Pool.transaction() dispatch branch contracts for PostgreSQL backend. +""" + +from rhosocial.activerecord.testsuite.feature.basic.connection.test_pool_transaction_paths import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.connection.test_pool_transaction_paths_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py new file mode 100644 index 00000000..268b1f97 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py +""" +Pytest configuration for basic transaction feature tests for PostgreSQL backend. +""" + +from rhosocial.activerecord.testsuite.feature.basic.transaction.conftest import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py new file mode 100644 index 00000000..d52947fd --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py @@ -0,0 +1,6 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py +""" +Async contracts for AsyncTransactionManager._owner_task safety warning (PostgreSQL backend). +""" + +from rhosocial.activerecord.testsuite.feature.basic.transaction.test_async_owner_task import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py new file mode 100644 index 00000000..047ef77c --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py @@ -0,0 +1,7 @@ +# tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py +""" +Black-box contracts for savepoint APIs (PostgreSQL backend). +""" + +from rhosocial.activerecord.testsuite.feature.basic.transaction.test_savepoint_api import * # noqa: F403 +from rhosocial.activerecord.testsuite.feature.basic.transaction.test_savepoint_api_async import * # noqa: F403 From f14064eb5be594e286d9df9deb4c15083069dc5a Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 19:37:50 +0800 Subject: [PATCH 24/32] fix(postgres): remove sync savepoint test import, only async tests apply --- .../feature/basic/transaction/test_savepoint_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py index 047ef77c..19dff4fb 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py @@ -3,5 +3,4 @@ Black-box contracts for savepoint APIs (PostgreSQL backend). """ -from rhosocial.activerecord.testsuite.feature.basic.transaction.test_savepoint_api import * # noqa: F403 from rhosocial.activerecord.testsuite.feature.basic.transaction.test_savepoint_api_async import * # noqa: F403 From b05eb68c4ecb69e16efa0a248c0aa09cb8a31eeb Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 20:31:41 +0800 Subject: [PATCH 25/32] fix(postgres): pass scenario name to type_adapter_fixtures provider to avoid cross-scenario table conflicts --- .../feature/basic/conftest.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py index 92417f78..e1f0d38d 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py @@ -5,5 +5,27 @@ This file imports fixtures from the corresponding testsuite, making them available to the tests in this directory. """ -from rhosocial.activerecord.testsuite.feature.basic.conftest import * # noqa: F403 + +import pytest + +from rhosocial.activerecord.testsuite.core.registry import get_provider_registry +from rhosocial.activerecord.testsuite.feature.basic.conftest import ( # noqa: F401 + SCENARIO_PARAMS_SYNC, + type_adapter_fixtures as _original_type_adapter_fixtures, + async_type_adapter_fixtures as _original_async_type_adapter_fixtures, + PROVIDER_KEY_SYNC, + PROVIDER_KEY_ASYNC, +) + +# Override type_adapter_fixtures to pass scenario name to provider, +# ensuring each scenario uses its own database instance. +@pytest.fixture(scope="function", params=SCENARIO_PARAMS_SYNC) +def type_adapter_fixtures(request): + scenario = request.param + provider_registry = get_provider_registry() + provider_class = provider_registry.get_provider(PROVIDER_KEY_SYNC) + provider = provider_class() + model = provider.setup_type_adapter_model_and_schema(scenario) + yield model + provider.cleanup_after_test(scenario) From dc9bb0416d274d3ec8551539dc585874fb4ec8c5 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 21:00:19 +0800 Subject: [PATCH 26/32] fix(postgres): import all testsuite basic fixtures in conftest while overriding type_adapter_fixtures --- .../activerecord_postgres_test/feature/basic/conftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py index e1f0d38d..2b316b90 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py @@ -9,12 +9,10 @@ import pytest from rhosocial.activerecord.testsuite.core.registry import get_provider_registry +from rhosocial.activerecord.testsuite.feature.basic.conftest import * # noqa: F401,F403 from rhosocial.activerecord.testsuite.feature.basic.conftest import ( # noqa: F401 SCENARIO_PARAMS_SYNC, - type_adapter_fixtures as _original_type_adapter_fixtures, - async_type_adapter_fixtures as _original_async_type_adapter_fixtures, PROVIDER_KEY_SYNC, - PROVIDER_KEY_ASYNC, ) # Override type_adapter_fixtures to pass scenario name to provider, From f12f97c07d8ba0513200dbe2620d9280c0c9d5ac Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 21:13:06 +0800 Subject: [PATCH 27/32] fix(postgres): revert conftest override, restore import * to avoid breaking other fixtures --- .../feature/basic/conftest.py | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py index 2b316b90..92417f78 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py @@ -5,25 +5,5 @@ This file imports fixtures from the corresponding testsuite, making them available to the tests in this directory. """ - -import pytest - -from rhosocial.activerecord.testsuite.core.registry import get_provider_registry -from rhosocial.activerecord.testsuite.feature.basic.conftest import * # noqa: F401,F403 -from rhosocial.activerecord.testsuite.feature.basic.conftest import ( # noqa: F401 - SCENARIO_PARAMS_SYNC, - PROVIDER_KEY_SYNC, -) - -# Override type_adapter_fixtures to pass scenario name to provider, -# ensuring each scenario uses its own database instance. -@pytest.fixture(scope="function", params=SCENARIO_PARAMS_SYNC) -def type_adapter_fixtures(request): - scenario = request.param - provider_registry = get_provider_registry() - provider_class = provider_registry.get_provider(PROVIDER_KEY_SYNC) - provider = provider_class() - model = provider.setup_type_adapter_model_and_schema(scenario) - yield model - provider.cleanup_after_test(scenario) +from rhosocial.activerecord.testsuite.feature.basic.conftest import * # noqa: F403 From 67f2b2f1bf78cec588d64f978ef19e321d7ac19c Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 21:17:02 +0800 Subject: [PATCH 28/32] fix(postgres): pass scenario name to sync type_adapter_fixtures to avoid cross-scenario table collisions --- .../feature/basic/conftest.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py index 92417f78..1f6cb9a6 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/conftest.py @@ -3,7 +3,28 @@ Pytest configuration for basic feature tests. This file imports fixtures from the corresponding testsuite, making them -available to the tests in this directory. +available to the tests in this directory. It overrides ``type_adapter_fixtures`` +so the scenario name is passed to the provider. Without this, the sync provider +always creates the ``type_adapter_tests`` table on the first registered scenario, +which collides with the async provider when both scenarios run in parallel. """ -from rhosocial.activerecord.testsuite.feature.basic.conftest import * # noqa: F403 +import pytest + +from rhosocial.activerecord.testsuite.feature.basic.conftest import * # noqa: F401,F403 +from rhosocial.activerecord.testsuite.feature.basic.conftest import ( # noqa: F401 + SCENARIO_PARAMS_SYNC, + PROVIDER_KEY_SYNC, +) +from rhosocial.activerecord.testsuite.core.registry import get_provider_registry + + +@pytest.fixture(scope="function", params=SCENARIO_PARAMS_SYNC) +def type_adapter_fixtures(request): + scenario = request.param + provider_registry = get_provider_registry() + provider_class = provider_registry.get_provider(PROVIDER_KEY_SYNC) + provider = provider_class() + model = provider.setup_type_adapter_model_and_schema(scenario) + yield model + provider.cleanup_after_test(scenario) From 2806f8d8de67dc94b9bf72a04e1e00af4a1b714e Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 21:29:47 +0800 Subject: [PATCH 29/32] fix(postgres): remove test bridge files accidentally added in protocol fix --- .../basic/connection/test_pool_transaction_paths.py | 7 ------- .../feature/basic/transaction/conftest.py | 6 ------ .../feature/basic/transaction/test_async_owner_task.py | 6 ------ .../feature/basic/transaction/test_savepoint_api.py | 6 ------ 4 files changed, 25 deletions(-) delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py delete mode 100644 tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py deleted file mode 100644 index 449c6468..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py +++ /dev/null @@ -1,7 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/basic/connection/test_pool_transaction_paths.py -""" -Pool.transaction() dispatch branch contracts for PostgreSQL backend. -""" - -from rhosocial.activerecord.testsuite.feature.basic.connection.test_pool_transaction_paths import * # noqa: F403 -from rhosocial.activerecord.testsuite.feature.basic.connection.test_pool_transaction_paths_async import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py deleted file mode 100644 index 268b1f97..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/conftest.py -""" -Pytest configuration for basic transaction feature tests for PostgreSQL backend. -""" - -from rhosocial.activerecord.testsuite.feature.basic.transaction.conftest import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py deleted file mode 100644 index d52947fd..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py +++ /dev/null @@ -1,6 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_async_owner_task.py -""" -Async contracts for AsyncTransactionManager._owner_task safety warning (PostgreSQL backend). -""" - -from rhosocial.activerecord.testsuite.feature.basic.transaction.test_async_owner_task import * # noqa: F403 diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py deleted file mode 100644 index 19dff4fb..00000000 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py +++ /dev/null @@ -1,6 +0,0 @@ -# tests/rhosocial/activerecord_postgres_test/feature/basic/transaction/test_savepoint_api.py -""" -Black-box contracts for savepoint APIs (PostgreSQL backend). -""" - -from rhosocial.activerecord.testsuite.feature.basic.transaction.test_savepoint_api_async import * # noqa: F403 From fc8b6a6d13bff3a836119ca39fb1fdceb472f147 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 22:05:11 +0800 Subject: [PATCH 30/32] fix(postgres): remove shadowing fixture import from type_adapter bridge file The bridge file imported type_adapter_fixtures directly from the testsuite conftest into the test module namespace. pytest registered that fixture with the test module as its most specific baseid, shadowing the conftest override (feature/basic/conftest.py) that passes the scenario name to the provider. As a result, the sync [postgres_exts] type_adapter tests connected to the default scenario database (5432) instead of postgres_exts (5433), colliding with async [postgres_default] tests on the same type_adapter_tests table and causing 'relation does not exist' failures under xdist concurrency. Remove the import so pytest resolves the conftest override, which correctly passes the scenario name. --- .../feature/basic/type_adapter/test_type_adapter.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter.py b/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter.py index c607a556..d6108e4f 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/basic/type_adapter/test_type_adapter.py @@ -6,11 +6,11 @@ package and make them discoverable by `pytest` within this project's test run. """ -# Import the fixture that provides the configured model and backend for type adapter tests. -from rhosocial.activerecord.testsuite.feature.basic.conftest import ( - type_adapter_fixtures, # noqa: F401 - async_type_adapter_fixtures, # noqa: F401 -) +# Note: fixtures are provided by the conftest override at +# feature/basic/conftest.py (which passes the scenario name to the provider). +# Do NOT re-import type_adapter_fixtures here -- pytest would register the +# testsuite's original (scenario-ignoring) fixture with this module as the +# most specific baseid, shadowing the conftest override. # Import all tests from the generic testsuite file. from rhosocial.activerecord.testsuite.feature.basic.type_adapter.test_type_adapter import * # noqa: F403 From 5a13ac25aa7ff9572f669918fd79a3aade78371b Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sat, 1 Aug 2026 23:31:49 +0800 Subject: [PATCH 31/32] test(postgres): add unit tests for data type formatting and parsing - Fix relative imports in parse_type (..expression.types -> ...expression.types) that crashed serial/binary/jsonpath/uuid/net/geom/range/oid/ts/vector parsing with ModuleNotFoundError. - Fix DATETIME mis-parsed as DATE (dispatch order), and CHARACTER/ CHARACTER VARYING/TIMESTAMPTZ/TIMETZ falling back to CustomType due to \b boundary regex. - Add test_data_type_formatting.py covering all PostgreSQL type format and parse branches (data_type_formatting.py coverage 55.67% -> 98%). - Add test_expression_types.py covering synonyms/equality/hash/array equivalence (expression/types.py coverage 76.19% -> 100%). - Cover index fulltext PHRASE mode + multi-column, and upsert capability switches (index.py and upsert.py -> 100%). --- .../mixins/types/data_type_formatting.py | 38 +- .../test_insert_on_conflict_clauses.py | 12 + .../feature/backend/postgres/test_index_pg.py | 24 + .../types/test_data_type_formatting.py | 435 ++++++++++++++++++ .../backend/types/test_expression_types.py | 104 +++++ 5 files changed, 594 insertions(+), 19 deletions(-) create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_data_type_formatting.py create mode 100644 tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_expression_types.py diff --git a/src/rhosocial/activerecord/backend/impl/postgres/mixins/types/data_type_formatting.py b/src/rhosocial/activerecord/backend/impl/postgres/mixins/types/data_type_formatting.py index bb41a98e..73c155f6 100644 --- a/src/rhosocial/activerecord/backend/impl/postgres/mixins/types/data_type_formatting.py +++ b/src/rhosocial/activerecord/backend/impl/postgres/mixins/types/data_type_formatting.py @@ -4,7 +4,7 @@ from __future__ import annotations import re -from typing import Tuple, Tuple, TYPE_CHECKING +from typing import Tuple, TYPE_CHECKING from rhosocial.activerecord.backend.dialect.mixins.ddl_type import DDLTypeMixin from rhosocial.activerecord.backend.dialect.protocols import DDLTypeSupport @@ -471,7 +471,7 @@ def format_data_type_json_b(self, data_type: JsonBType) -> Tuple[str, tuple]: re.IGNORECASE, ) _PG_STRING_TYPES = re.compile( - r"^(?:CHAR|VARCHAR|TEXT)\b", + r"^(?:CHARACTER|CHAR|VARCHAR|TEXT)\b", re.IGNORECASE, ) _PG_BINARY_TYPES = re.compile( @@ -479,7 +479,7 @@ def format_data_type_json_b(self, data_type: JsonBType) -> Tuple[str, tuple]: re.IGNORECASE, ) _PG_DATE_TYPES = re.compile( - r"^(?:DATE|DATETIME|TIMESTAMP|TIME|INTERVAL)\b", + r"^(?:DATETIME|DATE|TIMESTAMPTZ|TIMESTAMP|TIMETZ|TIME|INTERVAL)\b", re.IGNORECASE, ) _PG_JSON_TYPES = re.compile( @@ -560,7 +560,7 @@ def parse_type(self, raw: str) -> DataType: # Serial family if self._PG_SERIAL_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresBigSerialType, PostgresSerialType, PostgresSmallSerialType, @@ -610,15 +610,15 @@ def parse_type(self, raw: str) -> DataType: # Binary if self._PG_BINARY_TYPES.match(upper): - from ..expression.types import PostgresByteaType + from ...expression.types import PostgresByteaType return PostgresByteaType() # Date/time if self._PG_DATE_TYPES.match(upper): - if upper.startswith("DATE"): - return DateType() if upper.startswith("DATETIME"): return DateTimeType() + if upper.startswith("DATE"): + return DateType() if upper.startswith("TIMESTAMP"): nums = re.findall(r"\d+", stripped) precision = int(nums[0]) if nums else None @@ -641,13 +641,13 @@ def parse_type(self, raw: str) -> DataType: if upper.startswith("JSONB"): return JsonBType() if upper.startswith("JSONPATH"): - from ..expression.types import PostgresJsonPathType + from ...expression.types import PostgresJsonPathType return PostgresJsonPathType() return JsonType() # UUID if self._PG_UUID_TYPES.match(upper): - from ..expression.types import PostgresUUIDType + from ...expression.types import PostgresUUIDType return PostgresUUIDType() # Boolean @@ -659,14 +659,14 @@ def parse_type(self, raw: str) -> DataType: nums = re.findall(r"\d+", stripped) n = int(nums[0]) if nums else None if upper.startswith("BIT"): - from ..expression.types import PostgresBitType + from ...expression.types import PostgresBitType return PostgresBitType(n) - from ..expression.types import PostgresVarBitType + from ...expression.types import PostgresVarBitType return PostgresVarBitType(n) # Network address if self._PG_NET_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresCidrType, PostgresInetType, PostgresMacAddr8Type, @@ -682,7 +682,7 @@ def parse_type(self, raw: str) -> DataType: # Geometric if self._PG_GEOM_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresBoxType, PostgresCircleType, PostgresLineSegmentType, @@ -707,7 +707,7 @@ def parse_type(self, raw: str) -> DataType: # Range types if self._PG_RANGE_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresDateRangeType, PostgresInt4RangeType, PostgresInt8RangeType, @@ -730,7 +730,7 @@ def parse_type(self, raw: str) -> DataType: # Multirange types if self._PG_MULTIRANGE_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresDateMultirangeType, PostgresInt4MultirangeType, PostgresInt8MultirangeType, @@ -753,7 +753,7 @@ def parse_type(self, raw: str) -> DataType: # OID types if self._PG_OID_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresCIDType, PostgresOIDType, PostgresRegClassType, @@ -778,7 +778,7 @@ def parse_type(self, raw: str) -> DataType: # Text search if self._PG_TS_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresTSQueryType, PostgresTSVectorType, ) @@ -788,7 +788,7 @@ def parse_type(self, raw: str) -> DataType: # Miscellaneous if self._PG_MISC_TYPES.match(upper): - from ..expression.types import ( + from ...expression.types import ( PostgresGeographyType, PostgresGeometryType, PostgresHstoreType, @@ -813,7 +813,7 @@ def parse_type(self, raw: str) -> DataType: if upper.startswith("VECTOR"): nums = re.findall(r"\d+", stripped) dim = int(nums[0]) if nums else 0 - from ..expression.types import PostgresVectorType + from ...expression.types import PostgresVectorType return PostgresVectorType(dim) # Fallback diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py index abd8c6b1..fb83bdf1 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/dialect/test_insert_on_conflict_clauses.py @@ -28,9 +28,21 @@ def dialect(): class TestPostgresOnConflictCapabilities: """Capability switch tests.""" + def test_supports_upsert_pg94(self): + assert PostgresDialect((9, 4, 0)).supports_upsert() is False + + def test_supports_upsert_pg95(self): + assert PostgresDialect((9, 5, 0)).supports_upsert() is True + + def test_supports_upsert_modern(self): + assert PostgresDialect((16, 0, 0)).supports_upsert() is True + def test_supports_on_conflict_clause(self, dialect): assert dialect.supports_on_conflict_clause() is True + def test_supports_on_conflict_clause_old_version(self): + assert PostgresDialect((9, 4, 0)).supports_on_conflict_clause() is True + def test_does_not_support_multiple_on_conflict_clauses(self, dialect): assert dialect.supports_multiple_on_conflict_clauses() is False diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_index_pg.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_index_pg.py index 6be4882a..40fc7ee7 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_index_pg.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/postgres/test_index_pg.py @@ -518,6 +518,17 @@ def test_format_fulltext_match_boolean(self): assert "plainto_tsquery" not in sql assert params == ("a & b",) + def test_format_fulltext_match_phrase(self): + sql, params = PostgresDialect().format_fulltext_match(["col"], "cat dog", mode="PHRASE") + assert "phraseto_tsquery" in sql + assert "plainto_tsquery" not in sql + assert params == ("cat dog",) + + def test_format_fulltext_match_unknown_mode_uses_plain(self): + sql, params = PostgresDialect().format_fulltext_match(["col"], "search", mode="CUSTOM") + assert "plainto_tsquery" in sql + assert params == ("search",) + def test_format_fulltext_match_multi_column(self): sql, params = PostgresDialect().format_fulltext_match(["a", "b"], "search") assert " || " in sql @@ -551,6 +562,19 @@ def test_format_create_fulltext_index_statement_if_not_exists(self): sql, _ = dialect.format_create_fulltext_index_statement(expr) assert "IF NOT EXISTS" in sql + def test_format_create_fulltext_index_statement_multi_column(self): + dialect = PostgresDialect() + expr = CreateFulltextIndexExpression( + dialect, + index_name="idx_ft", + table_name="articles", + columns=["title", "body"], + ) + sql, _ = dialect.format_create_fulltext_index_statement(expr) + assert " || " in sql + assert "to_tsvector" in sql + assert "title" in sql and "body" in sql + def test_format_drop_fulltext_index_statement(self): dialect = PostgresDialect() expr = DropFulltextIndexExpression( diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_data_type_formatting.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_data_type_formatting.py new file mode 100644 index 00000000..c7c286f9 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_data_type_formatting.py @@ -0,0 +1,435 @@ +# tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_data_type_formatting.py +"""Pure-function tests for PostgreSQL DataType formatting and parsing. + +These tests exercise ``PostgresTypeFormatSupportMixin`` (the +``PostgresDialect.format_data_type`` and ``PostgresDialect.parse_type`` +paths) without requiring a live PostgreSQL server. +""" + +import pytest + +from rhosocial.activerecord.backend.expression.types import ( + ArrayType, + BigIntType, + BlobType, + BooleanType, + CharType, + CustomType, + DateType, + DateTimeType, + DecimalType, + DoubleType, + FloatType, + IntegerType, + IntType, + IntervalType, + JsonBType, + JsonType, + RealType, + SmallIntType, + TextType, + TimeType, + TimeTzType, + TimestampType, + TimestampTzType, + TinyIntType, + VarCharType, +) +from rhosocial.activerecord.backend.impl.postgres.dialect import PostgresDialect +from rhosocial.activerecord.backend.impl.postgres.expression.types import ( + PostgresArrayType, + PostgresBigSerialType, + PostgresBitType, + PostgresBoxType, + PostgresByteaType, + PostgresCIDType, + PostgresCharacterVaryingType, + PostgresCidrType, + PostgresCircleType, + PostgresCitextType, + PostgresCubeType, + PostgresDateMultirangeType, + PostgresDateRangeType, + PostgresGeographyType, + PostgresGeometryType, + PostgresHstoreType, + PostgresInetType, + PostgresInt4MultirangeType, + PostgresInt4RangeType, + PostgresInt8MultirangeType, + PostgresInt8RangeType, + PostgresJsonPathType, + PostgresLineSegmentType, + PostgresLineType, + PostgresLtreeType, + PostgresMacAddr8Type, + PostgresMacAddrType, + PostgresMoneyType, + PostgresNumMultirangeType, + PostgresNumRangeType, + PostgresOIDType, + PostgresPathType, + PostgresPgLSNType, + PostgresPointType, + PostgresPolygonType, + PostgresRasterType, + PostgresRegClassType, + PostgresRegTypeType, + PostgresSerialType, + PostgresSmallSerialType, + PostgresTIDType, + PostgresTSQueryType, + PostgresTSVectorType, + PostgresTsMultirangeType, + PostgresTsRangeType, + PostgresTsTzMultirangeType, + PostgresTsTzRangeType, + PostgresUUIDType, + PostgresVarBitType, + PostgresVectorType, + PostgresXID8Type, + PostgresXIDType, + PostgresXMLType, +) + + +@pytest.fixture +def dialect(): + return PostgresDialect(version=(15, 0, 0)) + + +# --------------------------------------------------------------------------- +# format_data_type — PostgreSQL-specific types +# --------------------------------------------------------------------------- + +FORMAT_CASES = [ + (PostgresByteaType(), "BYTEA"), + (PostgresSmallSerialType(), "SMALLSERIAL"), + (PostgresSerialType(), "SERIAL"), + (PostgresBigSerialType(), "BIGSERIAL"), + (PostgresUUIDType(), "UUID"), + (PostgresXMLType(), "XML"), + (PostgresTSVectorType(), "TSVECTOR"), + (PostgresTSQueryType(), "TSQUERY"), + (PostgresJsonPathType(), "JSONPATH"), + (PostgresBitType(), "BIT"), + (PostgresBitType(n=8), "BIT(8)"), + (PostgresVarBitType(), "VARBIT"), + (PostgresVarBitType(n=16), "VARBIT(16)"), + (PostgresInetType(), "INET"), + (PostgresCidrType(), "CIDR"), + (PostgresMacAddrType(), "MACADDR"), + (PostgresMacAddr8Type(), "MACADDR8"), + (PostgresPointType(), "POINT"), + (PostgresLineType(), "LINE"), + (PostgresLineSegmentType(), "LSEG"), + (PostgresBoxType(), "BOX"), + (PostgresPathType(), "PATH"), + (PostgresPolygonType(), "POLYGON"), + (PostgresCircleType(), "CIRCLE"), + (PostgresMoneyType(), "MONEY"), + (PostgresInt4RangeType(), "INT4RANGE"), + (PostgresInt8RangeType(), "INT8RANGE"), + (PostgresNumRangeType(), "NUMRANGE"), + (PostgresTsRangeType(), "TSRANGE"), + (PostgresTsTzRangeType(), "TSTZRANGE"), + (PostgresDateRangeType(), "DATERANGE"), + (PostgresInt4MultirangeType(), "INT4MULTIRANGE"), + (PostgresInt8MultirangeType(), "INT8MULTIRANGE"), + (PostgresNumMultirangeType(), "NUMMULTIRANGE"), + (PostgresTsMultirangeType(), "TSMULTIRANGE"), + (PostgresTsTzMultirangeType(), "TSTZMULTIRANGE"), + (PostgresDateMultirangeType(), "DATEMULTIRANGE"), + (PostgresOIDType(), "OID"), + (PostgresRegClassType(), "REGCLASS"), + (PostgresRegTypeType(), "REGTYPE"), + (PostgresXIDType(), "XID"), + (PostgresXID8Type(), "XID8"), + (PostgresCIDType(), "CID"), + (PostgresTIDType(), "TID"), + (PostgresPgLSNType(), "PG_LSN"), + (PostgresHstoreType(), "HSTORE"), + (PostgresGeometryType(), "GEOMETRY"), + (PostgresGeographyType(), "GEOGRAPHY"), + (PostgresVectorType(dim=384), "VECTOR(384)"), + (PostgresCitextType(), "CITEXT"), + (PostgresCubeType(), "CUBE"), + (PostgresLtreeType(), "LTREE"), + (PostgresRasterType(), "RASTER"), + (PostgresCharacterVaryingType(), "CHARACTER VARYING"), + (PostgresCharacterVaryingType(length=100), "CHARACTER VARYING(100)"), +] + + +@pytest.mark.parametrize("data_type,expected", FORMAT_CASES) +def test_format_postgres_specific_types(dialect, data_type, expected): + sql, params = dialect.format_data_type(data_type) + assert sql == expected + assert params == () + + +# --------------------------------------------------------------------------- +# format_data_type — core types with PostgreSQL specialisation +# --------------------------------------------------------------------------- + +CORE_FORMAT_CASES = [ + (TinyIntType(), "SMALLINT"), + (SmallIntType(), "SMALLINT"), + (IntType(), "INTEGER"), + (IntegerType(), "INTEGER"), + (BigIntType(), "BIGINT"), + (FloatType(), "REAL"), + (FloatType(precision=8), "FLOAT(8)"), + (RealType(), "REAL"), + (DoubleType(), "DOUBLE PRECISION"), + (DecimalType(), "DECIMAL"), + (DecimalType(precision=10), "DECIMAL(10)"), + (DecimalType(precision=10, scale=2), "DECIMAL(10,2)"), + (CharType(), "CHAR"), + (CharType(length=5), "CHAR(5)"), + (VarCharType(), "VARCHAR"), + (VarCharType(length=50), "VARCHAR(50)"), + (TextType(), "TEXT"), + (BooleanType(), "BOOLEAN"), + (BlobType(), "BYTEA"), + (DateType(), "DATE"), + (TimeType(), "TIME"), + (TimeType(precision=6), "TIME(6)"), + (TimeTzType(), "TIME WITH TIME ZONE"), + (TimeTzType(precision=6), "TIME(6) WITH TIME ZONE"), + (DateTimeType(), "TIMESTAMP"), + (TimestampType(), "TIMESTAMP"), + (TimestampType(precision=3), "TIMESTAMP(3)"), + (TimestampTzType(), "TIMESTAMP WITH TIME ZONE"), + (TimestampTzType(precision=3), "TIMESTAMP(3) WITH TIME ZONE"), + (IntervalType(), "INTERVAL"), + (IntervalType(fields="DAY TO SECOND"), "INTERVAL DAY TO SECOND"), + (JsonType(), "JSON"), + (JsonBType(), "JSONB"), +] + + +@pytest.mark.parametrize("data_type,expected", CORE_FORMAT_CASES) +def test_format_core_types(dialect, data_type, expected): + sql, params = dialect.format_data_type(data_type) + assert sql == expected + assert params == () + + +def test_format_array_type(dialect): + sql, _ = dialect.format_data_type(ArrayType(IntegerType(), dimensions=1)) + assert sql == "INTEGER[]" + + sql, _ = dialect.format_data_type(ArrayType(VarCharType(10), dimensions=3)) + assert sql == "VARCHAR(10)[][][]" + + +def test_format_array_type_multidimensional(dialect): + sql, _ = dialect.format_data_type(ArrayType(IntegerType(), dimensions=2)) + assert sql == "INTEGER[][]" + + +def test_format_array_type_with_postgres_element(dialect): + sql, _ = dialect.format_data_type(ArrayType(PostgresUUIDType(), dimensions=1)) + assert sql == "UUID[]" + + +def test_format_array_type_unsupported_element_raises(dialect): + with pytest.raises(TypeError, match="is not supported"): + dialect.format_data_type(ArrayType(CustomType("X"), dimensions=1)) + + +def test_format_unregistered_type_raises(dialect): + with pytest.raises(TypeError, match="does not support"): + dialect.format_data_type(CustomType("X")) + + +# --------------------------------------------------------------------------- +# parse_type — round-trip families +# --------------------------------------------------------------------------- + +PARSE_CASES = [ + ("SMALLINT", SmallIntType, {}), + ("INT2", SmallIntType, {}), + ("INT", IntegerType, {}), + ("INTEGER", IntegerType, {}), + ("INT4", IntegerType, {}), + ("BIGINT", BigIntType, {}), + ("INT8", BigIntType, {}), + ("SMALLSERIAL", PostgresSmallSerialType, {}), + ("SERIAL2", PostgresSmallSerialType, {}), + ("SERIAL", PostgresSerialType, {}), + ("SERIAL4", PostgresSerialType, {}), + ("BIGSERIAL", PostgresBigSerialType, {}), + ("SERIAL8", PostgresBigSerialType, {}), + ("REAL", RealType, {}), + ("FLOAT4", RealType, {}), + ("DOUBLE", DoubleType, {}), + ("DOUBLE PRECISION", DoubleType, {}), + ("FLOAT", FloatType, {}), + ("FLOAT(8)", FloatType, {"precision": 8}), + ("DECIMAL", DecimalType, {}), + ("NUMERIC", DecimalType, {}), + ("DECIMAL(10)", DecimalType, {"precision": 10}), + ("DECIMAL(10,2)", DecimalType, {"precision": 10, "scale": 2}), + ("NUMERIC(12,4)", DecimalType, {"precision": 12, "scale": 4}), + ("CHAR", CharType, {}), + ("CHAR(5)", CharType, {"length": 5}), + ("CHARACTER", CharType, {}), + ("CHARACTER(5)", CharType, {"length": 5}), + ("VARCHAR", VarCharType, {}), + ("VARCHAR(50)", VarCharType, {"length": 50}), + ("CHARACTER VARYING", VarCharType, {}), + ("CHARACTER VARYING(100)", VarCharType, {"length": 100}), + ("TEXT", TextType, {}), + ("BYTEA", PostgresByteaType, {}), + ("BLOB", PostgresByteaType, {}), + ("DATE", DateType, {}), + ("DATETIME", DateTimeType, {}), + ("TIMESTAMP", TimestampType, {}), + ("TIMESTAMP(3)", TimestampType, {"precision": 3}), + ("TIMESTAMPTZ", TimestampTzType, {}), + ("TIMESTAMP(3) WITH TIME ZONE", TimestampTzType, {"precision": 3}), + ("TIME", TimeType, {}), + ("TIME(6)", TimeType, {"precision": 6}), + ("TIMETZ", TimeTzType, {}), + ("TIME(6) WITH TIME ZONE", TimeTzType, {"precision": 6}), + ("INTERVAL", IntervalType, {}), + ("INTERVAL DAY TO SECOND", IntervalType, {"fields": "DAY TO SECOND"}), + ("JSON", JsonType, {}), + ("JSONB", JsonBType, {}), + ("JSONPATH", PostgresJsonPathType, {}), + ("UUID", PostgresUUIDType, {}), + ("BOOLEAN", BooleanType, {}), + ("BOOL", BooleanType, {}), + ("BIT", PostgresBitType, {}), + ("BIT(8)", PostgresBitType, {"n": 8}), + ("VARBIT", PostgresVarBitType, {}), + ("VARBIT(16)", PostgresVarBitType, {"n": 16}), + ("INET", PostgresInetType, {}), + ("CIDR", PostgresCidrType, {}), + ("MACADDR", PostgresMacAddrType, {}), + ("MACADDR8", PostgresMacAddr8Type, {}), + ("POINT", PostgresPointType, {}), + ("LINE", PostgresLineType, {}), + ("LSEG", PostgresLineSegmentType, {}), + ("BOX", PostgresBoxType, {}), + ("PATH", PostgresPathType, {}), + ("POLYGON", PostgresPolygonType, {}), + ("CIRCLE", PostgresCircleType, {}), + ("INT4RANGE", PostgresInt4RangeType, {}), + ("INT8RANGE", PostgresInt8RangeType, {}), + ("NUMRANGE", PostgresNumRangeType, {}), + ("TSRANGE", PostgresTsRangeType, {}), + ("TSTZRANGE", PostgresTsTzRangeType, {}), + ("DATERANGE", PostgresDateRangeType, {}), + ("INT4MULTIRANGE", PostgresInt4MultirangeType, {}), + ("INT8MULTIRANGE", PostgresInt8MultirangeType, {}), + ("NUMMULTIRANGE", PostgresNumMultirangeType, {}), + ("TSMULTIRANGE", PostgresTsMultirangeType, {}), + ("TSTZMULTIRANGE", PostgresTsTzMultirangeType, {}), + ("DATEMULTIRANGE", PostgresDateMultirangeType, {}), + ("OID", PostgresOIDType, {}), + ("REGCLASS", PostgresRegClassType, {}), + ("REGTYPE", PostgresRegTypeType, {}), + ("XID", PostgresXIDType, {}), + ("XID8", PostgresXID8Type, {}), + ("CID", PostgresCIDType, {}), + ("TID", PostgresTIDType, {}), + ("TSVECTOR", PostgresTSVectorType, {}), + ("TSQUERY", PostgresTSQueryType, {}), + ("MONEY", PostgresMoneyType, {}), + ("XML", PostgresXMLType, {}), + ("PG_LSN", PostgresPgLSNType, {}), + ("HSTORE", PostgresHstoreType, {}), + ("GEOGRAPHY", PostgresGeographyType, {}), + ("GEOMETRY", PostgresGeometryType, {}), + ("VECTOR", PostgresVectorType, {"dim": 0}), + ("VECTOR(768)", PostgresVectorType, {"dim": 768}), +] + + +@pytest.mark.parametrize("raw,expected_cls,expected_attrs", PARSE_CASES) +def test_parse_type(dialect, raw, expected_cls, expected_attrs): + result = dialect.parse_type(raw) + assert type(result) is expected_cls + for attr, value in expected_attrs.items(): + assert getattr(result, attr) == value + + +def test_parse_type_array_bracket_suffix(dialect): + result = dialect.parse_type("INTEGER[]") + assert type(result) is PostgresArrayType + assert type(result.element_type) is IntegerType + assert result.dimensions == 1 + + +def test_parse_type_array_multidimensional_brackets(dialect): + result = dialect.parse_type("INTEGER[][]") + assert type(result) is PostgresArrayType + assert type(result.element_type) is IntegerType + + +def test_parse_type_array_with_dimension_bracket(dialect): + result = dialect.parse_type("VARCHAR(10)[3]") + assert type(result) is PostgresArrayType + assert type(result.element_type) is VarCharType + assert result.element_type.length == 10 + + +def test_parse_type_malformed_bracket_suffix_falls_back(dialect): + result = dialect.parse_type("FOO[abc]") + assert type(result) is CustomType + + +def test_parse_type_array_keyword(dialect): + result = dialect.parse_type("INTEGER ARRAY") + assert type(result) is PostgresArrayType + assert type(result.element_type) is IntegerType + + +def test_parse_type_array_keyword_with_dimension(dialect): + result = dialect.parse_type("INTEGER ARRAY[3]") + assert type(result) is PostgresArrayType + assert type(result.element_type) is IntegerType + + +def test_parse_type_array_nested_element(dialect): + result = dialect.parse_type("UUID[]") + assert type(result) is PostgresArrayType + assert type(result.element_type) is PostgresUUIDType + + +def test_parse_type_unknown_falls_back_to_custom(dialect): + result = dialect.parse_type("FOOBARBAZ") + assert type(result) is CustomType + assert result.raw == "FOOBARBAZ" + + +def test_parse_type_case_insensitive(dialect): + result = dialect.parse_type(" varchar(25) ") + assert type(result) is VarCharType + assert result.length == 25 + + +# --------------------------------------------------------------------------- +# supports_data_types +# --------------------------------------------------------------------------- + + +def test_supports_data_types_registers_all_postgres_types(dialect): + supported = dialect.supports_data_types() + assert isinstance(supported, list) + assert len(supported) > 0 + classes = {cls for cls, _ in supported} + for expected in ( + PostgresSerialType, + PostgresByteaType, + PostgresUUIDType, + PostgresVectorType, + PostgresTsRangeType, + ArrayType, + JsonBType, + IntegerType, + ): + assert expected in classes diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_expression_types.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_expression_types.py new file mode 100644 index 00000000..e8a96295 --- /dev/null +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_expression_types.py @@ -0,0 +1,104 @@ +# tests/rhosocial/activerecord_postgres_test/feature/backend/types/test_expression_types.py +"""Tests for PostgreSQL-specific DataType subclasses (pure, no DB). + +Covers the ``synonyms()`` registrations, equality/hash semantics and +array-type equivalence used by introspection and schema comparison. +""" + +from rhosocial.activerecord.backend.expression.types import ( + IntegerType, + VarCharType, +) +from rhosocial.activerecord.backend.impl.postgres.expression.types import ( + PostgresArrayType, + PostgresBigSerialType, + PostgresBitType, + PostgresByteaType, + PostgresCharacterVaryingType, + PostgresSerialType, + PostgresSmallSerialType, + PostgresVarBitType, + PostgresVectorType, +) + + +class TestPostgresTypeSynonyms: + def test_character_varying_synonyms(self): + assert PostgresCharacterVaryingType.synonyms() == {"VarCharType"} + + def test_bytea_synonyms(self): + assert PostgresByteaType.synonyms() == {"BlobType"} + + def test_small_serial_synonyms(self): + assert PostgresSmallSerialType.synonyms() == {"SmallSerialType", "SmallIntType"} + + def test_serial_synonyms(self): + assert PostgresSerialType.synonyms() == {"SerialType", "IntegerType"} + + def test_big_serial_synonyms(self): + assert PostgresBigSerialType.synonyms() == {"BigSerialType", "BigIntType"} + + +class TestPostgresBitTypeEquality: + def test_equal(self): + assert PostgresBitType(8) == PostgresBitType(8) + + def test_not_equal_values(self): + assert PostgresBitType(8) != PostgresBitType(16) + + def test_not_equal_types(self): + assert PostgresBitType(8) != PostgresVarBitType(8) + assert PostgresBitType(8) != "not a type" + + def test_none_vs_value(self): + assert PostgresBitType(None) != PostgresBitType(8) + + def test_hash(self): + assert hash(PostgresBitType(8)) == hash((type(PostgresBitType(8)), 8)) + + +class TestPostgresVarBitTypeEquality: + def test_equal(self): + assert PostgresVarBitType(16) == PostgresVarBitType(16) + + def test_not_equal_values(self): + assert PostgresVarBitType(16) != PostgresVarBitType(32) + + def test_not_equal_types(self): + assert PostgresVarBitType(16) != PostgresBitType(16) + assert PostgresVarBitType(16) != object() + + def test_hash(self): + assert hash(PostgresVarBitType(16)) == hash((type(PostgresVarBitType(16)), 16)) + + +class TestPostgresVectorTypeEquality: + def test_equal(self): + assert PostgresVectorType(384) == PostgresVectorType(384) + + def test_not_equal_values(self): + assert PostgresVectorType(384) != PostgresVectorType(768) + + def test_not_equal_types(self): + assert PostgresVectorType(384) != PostgresBitType(8) + assert PostgresVectorType(384) != "vector" + + def test_hash(self): + assert hash(PostgresVectorType(384)) == hash((type(PostgresVectorType(384)), 384)) + + +class TestPostgresArrayType: + def test_is_equivalent_matching_element(self): + arr1 = PostgresArrayType(IntegerType(), dimensions=1) + arr2 = PostgresArrayType(IntegerType(), dimensions=3) + assert arr1.is_equivalent(arr2) + + def test_is_equivalent_non_array(self): + arr = PostgresArrayType(IntegerType(), dimensions=1) + assert arr.is_equivalent(IntegerType()) is False + assert arr.is_equivalent(None) is False + + def test_is_equivalent_different_element(self): + arr1 = PostgresArrayType(IntegerType(), dimensions=1) + arr2 = PostgresArrayType(VarCharType(), dimensions=1) + assert arr1.is_equivalent(arr2) is False From 191a365b07a3ffb6da40528ce896700ea930aaa4 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Sun, 2 Aug 2026 10:10:30 +0800 Subject: [PATCH 32/32] chore: add changelog fragment for PR #49 --- changelog.d/49.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/49.added.md diff --git a/changelog.d/49.added.md b/changelog.d/49.added.md new file mode 100644 index 00000000..966d1c1f --- /dev/null +++ b/changelog.d/49.added.md @@ -0,0 +1 @@ +Add PGQ enhancements: fulltext search capability, ON CONFLICT switches, JSON arrow operators, cursor result-set pollution fix, and PostgreSQL 19 compatibility gate for quantified path and comma-separated patterns \ No newline at end of file