quadkit-testing 0.0.44__tar.gz → 0.0.45__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/PKG-INFO +4 -4
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/README.md +3 -3
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/pyproject.toml +1 -1
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/web/client.py +13 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/__init__.py +2 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/cache.py +51 -30
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/database.py +87 -3
- quadkit_testing-0.0.45/src/quadkit/testing/compliance/pool.py +165 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/clock.py +27 -0
- quadkit_testing-0.0.45/src/quadkit/testing/memory/cache.py +143 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_fakes_cluster.py +35 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_memory_backends.py +29 -18
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_small_modules.py +32 -1
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_web_clients.py +23 -0
- quadkit_testing-0.0.44/src/quadkit/testing/memory/cache.py +0 -114
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/.gitignore +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/LICENSE +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/NOTICE +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/py.typed +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/README.md +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/_lazy_exports.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/ai/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/ai/bed.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/ai/client.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/ai/data.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/db/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/db/bed.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/db/client.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/web/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/web/bed.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clients/web/py.typed +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/clock.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/audit.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/blob_store.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/distributed_lock.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/event_bus.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/flags.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/middleware.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/notification.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/queue_backend.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/repository.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/search.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/secrets.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/task_queue.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/vector_store.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/webhook.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/config.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/constants.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/decorators.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/di/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/events.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/exceptions.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/audit.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/buses.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/cache.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/config.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/events.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/governance.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/lifecycle.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/logging.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/monitoring.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/redis.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fakes/tracing.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/bed.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/container.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/containers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/core.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/db.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/py.typed +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/fixtures/web.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/generators.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/generators_contract.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/harness/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/harness/container.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/harness/decorators.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/harness/overrides.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/harness/testbed.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/hooks.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/integration/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/integration/cleanup.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/integration/config.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/integration/fixtures.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/integration/markers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/integration/probes.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/admin_helpers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/assertions.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/async_helper.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/factory.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/smoke.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/snapshots.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/lib/stubs.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/audit.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/blob_store.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/config/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/config/models.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/constants.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/cqrs.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/di/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/di/provider.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/event_bus.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/exceptions.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/lock.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/notification.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/outbox.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/queue.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/repository.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/search.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/memory/uow.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/mocks/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/mocks/base.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/mocks/test_provider.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/module.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/plugins/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/plugins/pytest/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/plugins/pytest/plugin.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/protocols.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/py.typed +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/testkit/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/testkit/assertions.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/testkit/environment.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/testkit/fakes.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/testkit/fixtures.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/types.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/websocket/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/websocket/client.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/workflow/__init__.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/workflow/harness.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/__snapshots__/test_snapshot_function.json +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/conftest.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/integration/test_web_client.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/test_package_structure_p1.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/__snapshots__/test_snapshot_function.json +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/_integration_helpers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/conftest.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_ai_client_budget.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_ai_clients.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_app_testbed.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_app_testbed_from_factory_overrides.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_assertion_helpers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_async_helper.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_container.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_container_bind.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_container_factory_auto_registration.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_container_try_resolve.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_db_clients.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_db_transaction_fixture.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_environment_lifecycle.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_exceptions.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_factory.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_fake_audit.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_fake_event_bus_ordering.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_fake_tracer.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_fakes.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_fakes_lifecycle.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_generators_util.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_harness_testkit.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_integration_cleanup.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_integration_cluster.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_integration_fixtures.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_integration_probes.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_lib_admin_client.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_lib_async_helpers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_lib_helpers.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_memory_cqrs.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_memory_event_bus.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_memory_lock.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_memory_outbox.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_memory_repository.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_mocks.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_mocks_cluster.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_override_context_manager_revert.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_plugin.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_public_exports.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_pytest_plugin_fixtures.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_service_probe_connectivity.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_smoke.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_snapshot_asserter_create_compare.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_snapshots.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_stubs.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_subpackage_exports.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_test_environment.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_test_environment_facade.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_test_environment_overrides.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_testing_constants.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_testing_module.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_testing_utils.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_testkit_surface.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_utilities.py +0 -0
- {quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/tests/unit/test_workflow_websocket_plugins.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quadkit-testing
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.45
|
|
4
4
|
Summary: Centralized testing infrastructure for Quadkit Framework - Fixtures, factories, and utilities
|
|
5
5
|
Project-URL: Homepage, https://dbtinoy-.github.io/quadkit/
|
|
6
6
|
Project-URL: Repository, https://github.com/dbtinoy-/quadkit
|
|
@@ -234,10 +234,10 @@ vulnerabilities privately per [SECURITY.md](https://github.com/dbtinoy-/quadkit/
|
|
|
234
234
|
|
|
235
235
|
## Stability
|
|
236
236
|
|
|
237
|
-
Version `0.0.
|
|
237
|
+
Version `0.0.45` in the `0.x` series, released in lockstep with the
|
|
238
238
|
other four distributions; APIs may change between minor versions until
|
|
239
|
-
1.0 — pin an exact version (`quadkit-testing==0.0.
|
|
240
|
-
(`>=0.0.
|
|
239
|
+
1.0 — pin an exact version (`quadkit-testing==0.0.45`) or a tight range
|
|
240
|
+
(`>=0.0.45,<0.1.0`). Full policy:
|
|
241
241
|
[stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
|
|
242
242
|
|
|
243
243
|
## Links
|
|
@@ -178,10 +178,10 @@ vulnerabilities privately per [SECURITY.md](https://github.com/dbtinoy-/quadkit/
|
|
|
178
178
|
|
|
179
179
|
## Stability
|
|
180
180
|
|
|
181
|
-
Version `0.0.
|
|
181
|
+
Version `0.0.45` in the `0.x` series, released in lockstep with the
|
|
182
182
|
other four distributions; APIs may change between minor versions until
|
|
183
|
-
1.0 — pin an exact version (`quadkit-testing==0.0.
|
|
184
|
-
(`>=0.0.
|
|
183
|
+
1.0 — pin an exact version (`quadkit-testing==0.0.45`) or a tight range
|
|
184
|
+
(`>=0.0.45,<0.1.0`). Full policy:
|
|
185
185
|
[stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
|
|
186
186
|
|
|
187
187
|
## Links
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "quadkit-testing"
|
|
9
|
-
version = "0.0.
|
|
9
|
+
version = "0.0.45"
|
|
10
10
|
description = "Centralized testing infrastructure for Quadkit Framework - Fixtures, factories, and utilities"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
@@ -61,6 +61,19 @@ class TestResponse:
|
|
|
61
61
|
"""Get the response headers."""
|
|
62
62
|
return dict(self._response.headers)
|
|
63
63
|
|
|
64
|
+
@property
|
|
65
|
+
def cookies(self) -> dict[str, str]:
|
|
66
|
+
"""Get the cookies the response set.
|
|
67
|
+
|
|
68
|
+
Without this an application whose behaviour *is* a cookie cannot be
|
|
69
|
+
tested through this client at all — a session that issues an
|
|
70
|
+
identity, a per-visitor scope, anything that proves two callers are
|
|
71
|
+
kept apart. ``headers`` carries only the raw ``set-cookie`` line,
|
|
72
|
+
which a test then has to parse, and the underlying response is
|
|
73
|
+
private on purpose.
|
|
74
|
+
"""
|
|
75
|
+
return dict(self._response.cookies)
|
|
76
|
+
|
|
64
77
|
def assert_status(self, expected: int) -> TestResponse:
|
|
65
78
|
"""Assert the response status code.
|
|
66
79
|
|
{quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/__init__.py
RENAMED
|
@@ -29,6 +29,7 @@ from quadkit.testing.compliance.event_bus import EventBusCompliance
|
|
|
29
29
|
from quadkit.testing.compliance.flags import FlagProviderCompliance
|
|
30
30
|
from quadkit.testing.compliance.middleware import MiddlewareCompliance
|
|
31
31
|
from quadkit.testing.compliance.notification import NotificationChannelCompliance
|
|
32
|
+
from quadkit.testing.compliance.pool import ConnectionPoolCompliance
|
|
32
33
|
from quadkit.testing.compliance.queue_backend import QueueBackendCompliance
|
|
33
34
|
from quadkit.testing.compliance.repository import RepositoryCompliance
|
|
34
35
|
from quadkit.testing.compliance.search import SearchEngineCompliance
|
|
@@ -77,6 +78,7 @@ __all__ = [
|
|
|
77
78
|
"AuditStoreCompliance",
|
|
78
79
|
"BlobStoreCompliance",
|
|
79
80
|
"CacheBackendCompliance",
|
|
81
|
+
"ConnectionPoolCompliance",
|
|
80
82
|
"DatabaseProviderCompliance",
|
|
81
83
|
"DistributedLockCompliance",
|
|
82
84
|
"EventBusCompliance",
|
|
@@ -22,6 +22,19 @@ import pytest
|
|
|
22
22
|
__all__ = ["CacheBackendCompliance"]
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _value(result: Any) -> Any:
|
|
26
|
+
"""Unwrap an ``Ok`` result, failing the test when it is ``Err``.
|
|
27
|
+
|
|
28
|
+
``CacheBackendProtocol`` answers every operation with a
|
|
29
|
+
``Result[T, CacheError]``: a cache that cannot reach its store reports that
|
|
30
|
+
rather than raising. Unwrapping in one place keeps each test about the
|
|
31
|
+
behaviour it names, and a failure message carries the error instead of a
|
|
32
|
+
bare assertion.
|
|
33
|
+
"""
|
|
34
|
+
assert result.is_ok(), f"expected Ok, got {result!r}"
|
|
35
|
+
return result.unwrap()
|
|
36
|
+
|
|
37
|
+
|
|
25
38
|
class CacheBackendCompliance:
|
|
26
39
|
"""Reusable test suite for any ``CacheBackendProtocol`` implementation.
|
|
27
40
|
|
|
@@ -47,39 +60,28 @@ class CacheBackendCompliance:
|
|
|
47
60
|
async def test_set_and_get(self) -> None:
|
|
48
61
|
"""set then get returns the stored value."""
|
|
49
62
|
backend = await self.create_backend()
|
|
50
|
-
await backend.set("key", "value")
|
|
51
|
-
|
|
52
|
-
assert result == "value"
|
|
63
|
+
_value(await backend.set("key", "value"))
|
|
64
|
+
assert _value(await backend.get("key")) == "value"
|
|
53
65
|
|
|
54
66
|
@pytest.mark.asyncio
|
|
55
|
-
async def
|
|
56
|
-
"""get on a missing key returns
|
|
67
|
+
async def test_get_missing_returns_none(self) -> None:
|
|
68
|
+
"""get on a missing key returns Ok(None)."""
|
|
57
69
|
backend = await self.create_backend()
|
|
58
|
-
|
|
59
|
-
assert result == "fallback"
|
|
60
|
-
|
|
61
|
-
@pytest.mark.asyncio
|
|
62
|
-
async def test_get_missing_returns_none_without_default(self) -> None:
|
|
63
|
-
"""get on a missing key returns None when no default is given."""
|
|
64
|
-
backend = await self.create_backend()
|
|
65
|
-
result = await backend.get("nonexistent")
|
|
66
|
-
assert result is None
|
|
70
|
+
assert _value(await backend.get("nonexistent")) is None
|
|
67
71
|
|
|
68
72
|
@pytest.mark.asyncio
|
|
69
73
|
async def test_delete_existing_key(self) -> None:
|
|
70
|
-
"""delete returns True and removes the key."""
|
|
74
|
+
"""delete returns Ok(True) and removes the key."""
|
|
71
75
|
backend = await self.create_backend()
|
|
72
76
|
await backend.set("key", "value")
|
|
73
|
-
|
|
74
|
-
assert
|
|
75
|
-
assert await backend.get("key") is None
|
|
77
|
+
assert _value(await backend.delete("key")) is True
|
|
78
|
+
assert _value(await backend.get("key")) is None
|
|
76
79
|
|
|
77
80
|
@pytest.mark.asyncio
|
|
78
81
|
async def test_delete_missing_key(self) -> None:
|
|
79
|
-
"""delete on a missing key returns False."""
|
|
82
|
+
"""delete on a missing key returns Ok(False)."""
|
|
80
83
|
backend = await self.create_backend()
|
|
81
|
-
|
|
82
|
-
assert deleted is False
|
|
84
|
+
assert _value(await backend.delete("nonexistent")) is False
|
|
83
85
|
|
|
84
86
|
@pytest.mark.asyncio
|
|
85
87
|
async def test_clear(self) -> None:
|
|
@@ -87,9 +89,9 @@ class CacheBackendCompliance:
|
|
|
87
89
|
backend = await self.create_backend()
|
|
88
90
|
await backend.set("a", 1)
|
|
89
91
|
await backend.set("b", 2)
|
|
90
|
-
await backend.clear()
|
|
91
|
-
assert await backend.get("a") is None
|
|
92
|
-
assert await backend.get("b") is None
|
|
92
|
+
_value(await backend.clear())
|
|
93
|
+
assert _value(await backend.get("a")) is None
|
|
94
|
+
assert _value(await backend.get("b")) is None
|
|
93
95
|
|
|
94
96
|
@pytest.mark.asyncio
|
|
95
97
|
async def test_overwrite_existing_key(self) -> None:
|
|
@@ -97,7 +99,7 @@ class CacheBackendCompliance:
|
|
|
97
99
|
backend = await self.create_backend()
|
|
98
100
|
await backend.set("key", "first")
|
|
99
101
|
await backend.set("key", "second")
|
|
100
|
-
assert await backend.get("key") == "second"
|
|
102
|
+
assert _value(await backend.get("key")) == "second"
|
|
101
103
|
|
|
102
104
|
@pytest.mark.asyncio
|
|
103
105
|
async def test_ttl_expiry(self) -> None:
|
|
@@ -105,8 +107,7 @@ class CacheBackendCompliance:
|
|
|
105
107
|
backend = await self.create_backend()
|
|
106
108
|
await backend.set("key", "value", ttl=0.05)
|
|
107
109
|
await asyncio.sleep(0.1)
|
|
108
|
-
|
|
109
|
-
assert result is None
|
|
110
|
+
assert _value(await backend.get("key")) is None
|
|
110
111
|
|
|
111
112
|
@pytest.mark.asyncio
|
|
112
113
|
async def test_no_ttl_does_not_expire(self) -> None:
|
|
@@ -114,8 +115,7 @@ class CacheBackendCompliance:
|
|
|
114
115
|
backend = await self.create_backend()
|
|
115
116
|
await backend.set("key", "value")
|
|
116
117
|
await asyncio.sleep(0.1) # small delay
|
|
117
|
-
|
|
118
|
-
assert result == "value"
|
|
118
|
+
assert _value(await backend.get("key")) == "value"
|
|
119
119
|
|
|
120
120
|
@pytest.mark.asyncio
|
|
121
121
|
async def test_stores_various_value_types(self) -> None:
|
|
@@ -128,4 +128,25 @@ class CacheBackendCompliance:
|
|
|
128
128
|
("list", [1, 2, 3]),
|
|
129
129
|
]:
|
|
130
130
|
await backend.set(key, value)
|
|
131
|
-
assert await backend.get(key) == value
|
|
131
|
+
assert _value(await backend.get(key)) == value
|
|
132
|
+
|
|
133
|
+
@pytest.mark.asyncio
|
|
134
|
+
async def test_errors_are_returned_not_raised(self) -> None:
|
|
135
|
+
"""A backend that fails reports Err(CacheError) rather than raising.
|
|
136
|
+
|
|
137
|
+
The protocol's whole point is that a cache miss or an unreachable
|
|
138
|
+
cache is a value the caller handles; a suite that only ever sees a
|
|
139
|
+
happy backend would certify a backend that raises on failure.
|
|
140
|
+
"""
|
|
141
|
+
backend = await self.create_backend()
|
|
142
|
+
for result in (
|
|
143
|
+
await backend.get("key"),
|
|
144
|
+
await backend.set("key", "value"),
|
|
145
|
+
await backend.delete("key"),
|
|
146
|
+
await backend.exists("key"),
|
|
147
|
+
await backend.clear(),
|
|
148
|
+
):
|
|
149
|
+
assert hasattr(result, "is_ok"), (
|
|
150
|
+
f"{type(backend).__name__} returned {type(result).__name__} "
|
|
151
|
+
"where CacheBackendProtocol promises Result"
|
|
152
|
+
)
|
{quadkit_testing-0.0.44 → quadkit_testing-0.0.45}/src/quadkit/testing/compliance/database.py
RENAMED
|
@@ -87,7 +87,7 @@ class DatabaseProviderCompliance:
|
|
|
87
87
|
|
|
88
88
|
@pytest.mark.asyncio
|
|
89
89
|
async def test_execute_and_fetch(self) -> None:
|
|
90
|
-
"""Data inserted via execute is retrievable via
|
|
90
|
+
"""Data inserted via execute is retrievable via the contract's fetchrow."""
|
|
91
91
|
provider = await self.create_provider()
|
|
92
92
|
async with provider.scoped_context():
|
|
93
93
|
conn = await provider.get_scoped_connection()
|
|
@@ -96,11 +96,95 @@ class DatabaseProviderCompliance:
|
|
|
96
96
|
"(id INTEGER PRIMARY KEY, val TEXT)"
|
|
97
97
|
)
|
|
98
98
|
await conn.execute("INSERT INTO _compliance_rw(id, val) VALUES(1, 'hello')")
|
|
99
|
-
row = await conn.
|
|
99
|
+
row = await conn.fetchrow("SELECT val FROM _compliance_rw WHERE id = 1")
|
|
100
100
|
assert row is not None
|
|
101
101
|
val = row["val"] if hasattr(row, "__getitem__") else row.val
|
|
102
102
|
assert val == "hello"
|
|
103
103
|
|
|
104
|
+
# ------------------------------------------------------------------
|
|
105
|
+
# Provider-level CRUD surface
|
|
106
|
+
#
|
|
107
|
+
# The connection-level tests above cover the ``ConnectionProtocol`` path.
|
|
108
|
+
# The framework's own stores (inbox, delivery state, audit) do not use it:
|
|
109
|
+
# they provision a table and write rows through the provider-level
|
|
110
|
+
# ``execute_query``/``execute_insert`` surface. Two contract rules live
|
|
111
|
+
# only there, and both have been violated in practice by a provider that
|
|
112
|
+
# passed the connection-level suite:
|
|
113
|
+
#
|
|
114
|
+
# 1. a statement that returns no rows (DDL) is a success with no rows, and
|
|
115
|
+
# 2. JSON-shaped bind values (``dict``/``list``) are encoded before binding.
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
@pytest.mark.asyncio
|
|
119
|
+
async def test_execute_query_ddl_is_not_a_failure(self) -> None:
|
|
120
|
+
"""A row-less statement through execute_query succeeds with no rows.
|
|
121
|
+
|
|
122
|
+
Stores provision their tables this way (``CREATE TABLE IF NOT
|
|
123
|
+
EXISTS``); an implementation that reports "no result set" as failure
|
|
124
|
+
makes provisioning look broken even though the table was created.
|
|
125
|
+
"""
|
|
126
|
+
provider = await self.create_provider()
|
|
127
|
+
result = await provider.execute_query(
|
|
128
|
+
"CREATE TABLE IF NOT EXISTS _compliance_ddl (id INTEGER PRIMARY KEY)"
|
|
129
|
+
)
|
|
130
|
+
assert result.success is True
|
|
131
|
+
assert result.rows == []
|
|
132
|
+
|
|
133
|
+
@pytest.mark.asyncio
|
|
134
|
+
async def test_execute_insert_binds_json_shaped_values(self) -> None:
|
|
135
|
+
"""A ``dict``/``list`` column value round-trips as JSON text.
|
|
136
|
+
|
|
137
|
+
Relational drivers cannot bind a Python container. Stores that declare
|
|
138
|
+
JSON columns — an inbox message's ``metadata``, a delivery record's
|
|
139
|
+
``message`` — pass containers, so the provider encodes them and the
|
|
140
|
+
value comes back as the JSON text the schema stores.
|
|
141
|
+
"""
|
|
142
|
+
provider = await self.create_provider()
|
|
143
|
+
created = await provider.execute_query(
|
|
144
|
+
"CREATE TABLE IF NOT EXISTS _compliance_json "
|
|
145
|
+
"(id TEXT PRIMARY KEY, metadata TEXT NOT NULL DEFAULT '{}')"
|
|
146
|
+
)
|
|
147
|
+
assert created.success is True
|
|
148
|
+
|
|
149
|
+
inserted = await provider.execute_insert(
|
|
150
|
+
"_compliance_json",
|
|
151
|
+
{"id": "row-1", "metadata": {"order_id": "7", "tags": ["a", "b"]}},
|
|
152
|
+
)
|
|
153
|
+
assert inserted.success is True, inserted.error_message
|
|
154
|
+
|
|
155
|
+
fetched = await provider.execute_query(
|
|
156
|
+
"SELECT metadata FROM _compliance_json WHERE id = ?", ["row-1"]
|
|
157
|
+
)
|
|
158
|
+
assert fetched.success is True, fetched.error_message
|
|
159
|
+
assert fetched.rows, "the inserted row was not readable"
|
|
160
|
+
stored = fetched.rows[0]["metadata"]
|
|
161
|
+
assert stored == '{"order_id":"7","tags":["a","b"]}'
|
|
162
|
+
|
|
163
|
+
@pytest.mark.asyncio
|
|
164
|
+
async def test_execute_update_binds_json_shaped_values(self) -> None:
|
|
165
|
+
"""``execute_update`` encodes JSON-shaped values in its data mapping."""
|
|
166
|
+
provider = await self.create_provider()
|
|
167
|
+
await provider.execute_query(
|
|
168
|
+
"CREATE TABLE IF NOT EXISTS _compliance_json_update "
|
|
169
|
+
"(id TEXT PRIMARY KEY, metadata TEXT NOT NULL DEFAULT '{}')"
|
|
170
|
+
)
|
|
171
|
+
await provider.execute_insert(
|
|
172
|
+
"_compliance_json_update", {"id": "row-1", "metadata": "{}"}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
updated = await provider.execute_update(
|
|
176
|
+
"_compliance_json_update",
|
|
177
|
+
{"metadata": {"patched": True}},
|
|
178
|
+
"id = ?",
|
|
179
|
+
["row-1"],
|
|
180
|
+
)
|
|
181
|
+
assert updated.success is True, updated.error_message
|
|
182
|
+
|
|
183
|
+
fetched = await provider.execute_query(
|
|
184
|
+
"SELECT metadata FROM _compliance_json_update WHERE id = ?", ["row-1"]
|
|
185
|
+
)
|
|
186
|
+
assert fetched.rows[0]["metadata"] == '{"patched":true}'
|
|
187
|
+
|
|
104
188
|
# ------------------------------------------------------------------
|
|
105
189
|
# Transaction rollback
|
|
106
190
|
# ------------------------------------------------------------------
|
|
@@ -122,5 +206,5 @@ class DatabaseProviderCompliance:
|
|
|
122
206
|
)
|
|
123
207
|
await conn.execute("ROLLBACK")
|
|
124
208
|
|
|
125
|
-
row = await conn.
|
|
209
|
+
row = await conn.fetchrow("SELECT val FROM _compliance_tx WHERE id = 42")
|
|
126
210
|
assert row is None
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Contract compliance suite for ``ConnectionPoolProtocol`` implementations.
|
|
2
|
+
|
|
3
|
+
Subclass :class:`ConnectionPoolCompliance` and implement
|
|
4
|
+
:meth:`create_pool` to verify any connection pool satisfies the
|
|
5
|
+
``ConnectionPoolProtocol`` contract::
|
|
6
|
+
|
|
7
|
+
from quadkit.testing.compliance import ConnectionPoolCompliance
|
|
8
|
+
|
|
9
|
+
class TestMyPool(ConnectionPoolCompliance):
|
|
10
|
+
async def create_pool(self):
|
|
11
|
+
return await MyProvider().get_primary_pool()
|
|
12
|
+
|
|
13
|
+
The suite owns shutdown: a pool that is never disposed keeps its engine's
|
|
14
|
+
threads alive past the event loop, so the final test in the class closes the
|
|
15
|
+
pool it built and the earlier ones close theirs as they go.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from abc import abstractmethod
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
__all__ = ["ConnectionPoolCompliance"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConnectionPoolCompliance:
|
|
29
|
+
"""Reusable test suite for any ``ConnectionPoolProtocol`` implementation."""
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
async def create_pool(self) -> Any:
|
|
33
|
+
"""Return a ready-to-use pool instance under test."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
# Lifecycle
|
|
38
|
+
# ------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
@pytest.mark.asyncio
|
|
41
|
+
async def test_initialize_is_idempotent(self) -> None:
|
|
42
|
+
"""initialize() can be called twice without raising."""
|
|
43
|
+
pool = await self.create_pool()
|
|
44
|
+
try:
|
|
45
|
+
await pool.initialize()
|
|
46
|
+
await pool.initialize()
|
|
47
|
+
finally:
|
|
48
|
+
await pool.shutdown()
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_shutdown_is_idempotent(self) -> None:
|
|
52
|
+
"""shutdown() can be called twice — a restart must not crash on it."""
|
|
53
|
+
pool = await self.create_pool()
|
|
54
|
+
await pool.initialize()
|
|
55
|
+
await pool.shutdown()
|
|
56
|
+
await pool.shutdown()
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_pool_is_usable_after_shutdown(self) -> None:
|
|
60
|
+
"""The pool re-opens connections after shutdown().
|
|
61
|
+
|
|
62
|
+
``shutdown()`` closes holders, not the engine: a process that restarts
|
|
63
|
+
its pool (a worker recycling between jobs, a graceful reload) must be
|
|
64
|
+
able to use it again. A pool that only works once would turn a routine
|
|
65
|
+
restart into an outage.
|
|
66
|
+
"""
|
|
67
|
+
pool = await self.create_pool()
|
|
68
|
+
await pool.initialize()
|
|
69
|
+
await pool.shutdown()
|
|
70
|
+
await pool.initialize()
|
|
71
|
+
async with pool.get_connection() as connection:
|
|
72
|
+
assert connection is not None
|
|
73
|
+
await pool.shutdown()
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
# Properties and statistics
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_capacity_properties_are_sane(self) -> None:
|
|
81
|
+
"""Capacity properties are non-negative.
|
|
82
|
+
|
|
83
|
+
``max_connections`` reports ``0`` for a pool with no ceiling — the
|
|
84
|
+
framework's own ``AsyncEnginePool`` defines it that way, and the
|
|
85
|
+
SQLite-backed default has exactly that shape. The suite therefore
|
|
86
|
+
checks the type and sign, not that a bound exists: a bounded-pool
|
|
87
|
+
assertion here would fail every unbounded deployment, which is a
|
|
88
|
+
configuration, not a defect.
|
|
89
|
+
"""
|
|
90
|
+
pool = await self.create_pool()
|
|
91
|
+
try:
|
|
92
|
+
assert isinstance(pool.max_connections, int)
|
|
93
|
+
assert pool.max_connections >= 0
|
|
94
|
+
assert pool.connection_timeout >= 0
|
|
95
|
+
finally:
|
|
96
|
+
await pool.shutdown()
|
|
97
|
+
|
|
98
|
+
@pytest.mark.asyncio
|
|
99
|
+
async def test_pool_stats_returns_dict(self) -> None:
|
|
100
|
+
"""get_pool_stats() returns a mapping of counters."""
|
|
101
|
+
pool = await self.create_pool()
|
|
102
|
+
try:
|
|
103
|
+
await pool.initialize()
|
|
104
|
+
stats = await pool.get_pool_stats()
|
|
105
|
+
assert isinstance(stats, dict)
|
|
106
|
+
finally:
|
|
107
|
+
await pool.shutdown()
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_query_stats_returns_dict(self) -> None:
|
|
111
|
+
"""get_query_stats() returns a mapping for the requested window."""
|
|
112
|
+
pool = await self.create_pool()
|
|
113
|
+
try:
|
|
114
|
+
await pool.initialize()
|
|
115
|
+
stats = await pool.get_query_stats(time_range_seconds=60)
|
|
116
|
+
assert isinstance(stats, dict)
|
|
117
|
+
finally:
|
|
118
|
+
await pool.shutdown()
|
|
119
|
+
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
# Connections
|
|
122
|
+
# ------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
async def test_get_connection_yields_usable_connection(self) -> None:
|
|
126
|
+
"""get_connection() is an async context manager yielding a connection."""
|
|
127
|
+
pool = await self.create_pool()
|
|
128
|
+
try:
|
|
129
|
+
await pool.initialize()
|
|
130
|
+
async with pool.get_connection() as connection:
|
|
131
|
+
assert connection is not None
|
|
132
|
+
# The contract's pool hands out SQLAlchemy-style connections
|
|
133
|
+
# (``execute``), which is what framework pool consumers drive.
|
|
134
|
+
await connection.execute(__import__("sqlalchemy").text("SELECT 1"))
|
|
135
|
+
finally:
|
|
136
|
+
await pool.shutdown()
|
|
137
|
+
|
|
138
|
+
@pytest.mark.asyncio
|
|
139
|
+
async def test_warm_and_validate(self) -> None:
|
|
140
|
+
"""warm() pre-creates connections and validate_connections() proves one works."""
|
|
141
|
+
pool = await self.create_pool()
|
|
142
|
+
try:
|
|
143
|
+
await pool.initialize()
|
|
144
|
+
await pool.warm(count=1)
|
|
145
|
+
assert await pool.validate_connections() == 1
|
|
146
|
+
finally:
|
|
147
|
+
await pool.shutdown()
|
|
148
|
+
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
# Health
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
@pytest.mark.asyncio
|
|
154
|
+
async def test_health_check_is_healthy_when_reachable(self) -> None:
|
|
155
|
+
"""health_check() reports a result carrying a status and component."""
|
|
156
|
+
from quadkit.contracts.core.health import HealthStatus
|
|
157
|
+
|
|
158
|
+
pool = await self.create_pool()
|
|
159
|
+
try:
|
|
160
|
+
await pool.initialize()
|
|
161
|
+
result = await pool.health_check(timeout=5.0)
|
|
162
|
+
assert result.status is HealthStatus.HEALTHY
|
|
163
|
+
assert result.component
|
|
164
|
+
finally:
|
|
165
|
+
await pool.shutdown()
|
|
@@ -39,6 +39,16 @@ class SystemClock:
|
|
|
39
39
|
"""Return the current Unix timestamp."""
|
|
40
40
|
return time.time()
|
|
41
41
|
|
|
42
|
+
def timestamp(self) -> float:
|
|
43
|
+
"""Return the current Unix timestamp.
|
|
44
|
+
|
|
45
|
+
Present so :class:`SystemClock` satisfies
|
|
46
|
+
:class:`~quadkit.contracts.core.clock.ClockProtocol`, which requires
|
|
47
|
+
``timestamp()`` alongside ``time()`` and so can back
|
|
48
|
+
:func:`quadkit.primitives.clock.use` and ``install()``.
|
|
49
|
+
"""
|
|
50
|
+
return time.time()
|
|
51
|
+
|
|
42
52
|
|
|
43
53
|
class FakeClock:
|
|
44
54
|
"""Controllable clock for deterministic time-based testing.
|
|
@@ -70,6 +80,23 @@ class FakeClock:
|
|
|
70
80
|
"""Return Unix timestamp of the current fake time."""
|
|
71
81
|
return self.now().timestamp()
|
|
72
82
|
|
|
83
|
+
def timestamp(self) -> float:
|
|
84
|
+
"""Return Unix timestamp of the current fake time.
|
|
85
|
+
|
|
86
|
+
Present so :class:`FakeClock` satisfies
|
|
87
|
+
:class:`~quadkit.contracts.core.clock.ClockProtocol`, which requires
|
|
88
|
+
``timestamp()`` alongside ``time()``. Without it a ``FakeClock`` cannot
|
|
89
|
+
back the ambient seam: :func:`quadkit.primitives.clock.use` accepts it,
|
|
90
|
+
but any caller reaching ``clock.timestamp()`` then fails with
|
|
91
|
+
``AttributeError``.
|
|
92
|
+
|
|
93
|
+
Like :meth:`time`, this derives from :meth:`now`, so constructing a
|
|
94
|
+
``FakeClock`` from a *naive* datetime makes both interpret it as local
|
|
95
|
+
time rather than UTC. Pass an aware datetime — or lift a stored naive-UTC
|
|
96
|
+
value first — for deterministic results.
|
|
97
|
+
"""
|
|
98
|
+
return self.now().timestamp()
|
|
99
|
+
|
|
73
100
|
def advance(self, seconds: float) -> None:
|
|
74
101
|
"""Move time forward by *seconds*."""
|
|
75
102
|
self._offset_seconds += seconds
|