quadkit-testing 0.0.44__tar.gz → 0.0.46__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.
Files changed (194) hide show
  1. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/PKG-INFO +12 -12
  2. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/README.md +7 -7
  3. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/pyproject.toml +5 -5
  4. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/web/client.py +13 -0
  5. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/__init__.py +2 -0
  6. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/cache.py +51 -30
  7. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/database.py +87 -3
  8. quadkit_testing-0.0.46/src/quadkit/testing/compliance/pool.py +165 -0
  9. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/clock.py +27 -0
  10. quadkit_testing-0.0.46/src/quadkit/testing/memory/cache.py +143 -0
  11. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_fakes_cluster.py +35 -0
  12. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_memory_backends.py +29 -18
  13. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_small_modules.py +32 -1
  14. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_web_clients.py +23 -0
  15. quadkit_testing-0.0.44/src/quadkit/testing/memory/cache.py +0 -114
  16. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/.gitignore +0 -0
  17. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/LICENSE +0 -0
  18. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/NOTICE +0 -0
  19. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/py.typed +0 -0
  20. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/README.md +0 -0
  21. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/__init__.py +0 -0
  22. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/_lazy_exports.py +0 -0
  23. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/ai/__init__.py +0 -0
  24. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/ai/bed.py +0 -0
  25. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/ai/client.py +0 -0
  26. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/ai/data.py +0 -0
  27. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/db/__init__.py +0 -0
  28. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/db/bed.py +0 -0
  29. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/db/client.py +0 -0
  30. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/web/__init__.py +0 -0
  31. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/web/bed.py +0 -0
  32. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clients/web/py.typed +0 -0
  33. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/clock.py +0 -0
  34. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/audit.py +0 -0
  35. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/blob_store.py +0 -0
  36. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/distributed_lock.py +0 -0
  37. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/event_bus.py +0 -0
  38. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/flags.py +0 -0
  39. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/middleware.py +0 -0
  40. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/notification.py +0 -0
  41. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/queue_backend.py +0 -0
  42. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/repository.py +0 -0
  43. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/search.py +0 -0
  44. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/secrets.py +0 -0
  45. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/task_queue.py +0 -0
  46. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/vector_store.py +0 -0
  47. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/compliance/webhook.py +0 -0
  48. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/config.py +0 -0
  49. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/constants.py +0 -0
  50. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/decorators.py +0 -0
  51. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/di/__init__.py +0 -0
  52. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/events.py +0 -0
  53. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/exceptions.py +0 -0
  54. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/__init__.py +0 -0
  55. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/audit.py +0 -0
  56. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/buses.py +0 -0
  57. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/cache.py +0 -0
  58. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/config.py +0 -0
  59. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/events.py +0 -0
  60. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/governance.py +0 -0
  61. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/lifecycle.py +0 -0
  62. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/logging.py +0 -0
  63. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/monitoring.py +0 -0
  64. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/redis.py +0 -0
  65. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fakes/tracing.py +0 -0
  66. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/__init__.py +0 -0
  67. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/bed.py +0 -0
  68. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/container.py +0 -0
  69. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/containers.py +0 -0
  70. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/core.py +0 -0
  71. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/db.py +0 -0
  72. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/py.typed +0 -0
  73. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/fixtures/web.py +0 -0
  74. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/generators.py +0 -0
  75. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/generators_contract.py +0 -0
  76. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/harness/__init__.py +0 -0
  77. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/harness/container.py +0 -0
  78. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/harness/decorators.py +0 -0
  79. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/harness/overrides.py +0 -0
  80. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/harness/testbed.py +0 -0
  81. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/hooks.py +0 -0
  82. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/integration/__init__.py +0 -0
  83. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/integration/cleanup.py +0 -0
  84. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/integration/config.py +0 -0
  85. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/integration/fixtures.py +0 -0
  86. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/integration/markers.py +0 -0
  87. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/integration/probes.py +0 -0
  88. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/__init__.py +0 -0
  89. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/admin_helpers.py +0 -0
  90. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/assertions.py +0 -0
  91. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/async_helper.py +0 -0
  92. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/factory.py +0 -0
  93. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/smoke.py +0 -0
  94. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/snapshots.py +0 -0
  95. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/lib/stubs.py +0 -0
  96. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/__init__.py +0 -0
  97. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/audit.py +0 -0
  98. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/blob_store.py +0 -0
  99. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/config/__init__.py +0 -0
  100. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/config/models.py +0 -0
  101. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/constants.py +0 -0
  102. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/cqrs.py +0 -0
  103. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/di/__init__.py +0 -0
  104. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/di/provider.py +0 -0
  105. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/event_bus.py +0 -0
  106. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/exceptions.py +0 -0
  107. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/lock.py +0 -0
  108. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/notification.py +0 -0
  109. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/outbox.py +0 -0
  110. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/queue.py +0 -0
  111. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/repository.py +0 -0
  112. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/search.py +0 -0
  113. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/memory/uow.py +0 -0
  114. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/mocks/__init__.py +0 -0
  115. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/mocks/base.py +0 -0
  116. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/mocks/test_provider.py +0 -0
  117. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/module.py +0 -0
  118. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/plugins/__init__.py +0 -0
  119. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/plugins/pytest/__init__.py +0 -0
  120. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/plugins/pytest/plugin.py +0 -0
  121. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/protocols.py +0 -0
  122. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/py.typed +0 -0
  123. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/testkit/__init__.py +0 -0
  124. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/testkit/assertions.py +0 -0
  125. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/testkit/environment.py +0 -0
  126. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/testkit/fakes.py +0 -0
  127. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/testkit/fixtures.py +0 -0
  128. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/types.py +0 -0
  129. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/websocket/__init__.py +0 -0
  130. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/websocket/client.py +0 -0
  131. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/workflow/__init__.py +0 -0
  132. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/src/quadkit/testing/workflow/harness.py +0 -0
  133. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/__snapshots__/test_snapshot_function.json +0 -0
  134. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/conftest.py +0 -0
  135. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/integration/test_web_client.py +0 -0
  136. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/test_package_structure_p1.py +0 -0
  137. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/__snapshots__/test_snapshot_function.json +0 -0
  138. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/_integration_helpers.py +0 -0
  139. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/conftest.py +0 -0
  140. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_ai_client_budget.py +0 -0
  141. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_ai_clients.py +0 -0
  142. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_app_testbed.py +0 -0
  143. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_app_testbed_from_factory_overrides.py +0 -0
  144. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_assertion_helpers.py +0 -0
  145. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_async_helper.py +0 -0
  146. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_container.py +0 -0
  147. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_container_bind.py +0 -0
  148. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_container_factory_auto_registration.py +0 -0
  149. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_container_try_resolve.py +0 -0
  150. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_db_clients.py +0 -0
  151. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_db_transaction_fixture.py +0 -0
  152. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_environment_lifecycle.py +0 -0
  153. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_exceptions.py +0 -0
  154. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_factory.py +0 -0
  155. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_fake_audit.py +0 -0
  156. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_fake_event_bus_ordering.py +0 -0
  157. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_fake_tracer.py +0 -0
  158. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_fakes.py +0 -0
  159. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_fakes_lifecycle.py +0 -0
  160. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_generators_util.py +0 -0
  161. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_harness_testkit.py +0 -0
  162. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_integration_cleanup.py +0 -0
  163. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_integration_cluster.py +0 -0
  164. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_integration_fixtures.py +0 -0
  165. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_integration_probes.py +0 -0
  166. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_lib_admin_client.py +0 -0
  167. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_lib_async_helpers.py +0 -0
  168. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_lib_helpers.py +0 -0
  169. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_memory_cqrs.py +0 -0
  170. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_memory_event_bus.py +0 -0
  171. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_memory_lock.py +0 -0
  172. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_memory_outbox.py +0 -0
  173. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_memory_repository.py +0 -0
  174. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_mocks.py +0 -0
  175. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_mocks_cluster.py +0 -0
  176. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_override_context_manager_revert.py +0 -0
  177. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_plugin.py +0 -0
  178. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_public_exports.py +0 -0
  179. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_pytest_plugin_fixtures.py +0 -0
  180. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_service_probe_connectivity.py +0 -0
  181. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_smoke.py +0 -0
  182. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_snapshot_asserter_create_compare.py +0 -0
  183. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_snapshots.py +0 -0
  184. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_stubs.py +0 -0
  185. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_subpackage_exports.py +0 -0
  186. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_test_environment.py +0 -0
  187. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_test_environment_facade.py +0 -0
  188. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_test_environment_overrides.py +0 -0
  189. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_testing_constants.py +0 -0
  190. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_testing_module.py +0 -0
  191. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_testing_utils.py +0 -0
  192. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_testkit_surface.py +0 -0
  193. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_utilities.py +0 -0
  194. {quadkit_testing-0.0.44 → quadkit_testing-0.0.46}/tests/unit/test_workflow_websocket_plugins.py +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quadkit-testing
3
- Version: 0.0.44
3
+ Version: 0.0.46
4
4
  Summary: Centralized testing infrastructure for Quadkit Framework - Fixtures, factories, and utilities
5
- Project-URL: Homepage, https://dbtinoy-.github.io/quadkit/
5
+ Project-URL: Homepage, https://oridecon.dev
6
6
  Project-URL: Repository, https://github.com/dbtinoy-/quadkit
7
- Project-URL: Documentation, https://dbtinoy-.github.io/quadkit/
7
+ Project-URL: Documentation, https://oridecon.dev/quadkit/
8
8
  Project-URL: Issues, https://github.com/dbtinoy-/quadkit/issues
9
9
  Project-URL: Changelog, https://github.com/dbtinoy-/quadkit/blob/main/CHANGELOG.md
10
- Author-email: Quadkit Framework Team <team@quadkit.dev>
11
- Maintainer-email: Quadkit Framework Team <team@quadkit.dev>
10
+ Author-email: Quadkit Framework Team <hello@oridecon.dev>
11
+ Maintainer-email: Quadkit Framework Team <hello@oridecon.dev>
12
12
  License: Apache-2.0
13
13
  License-File: LICENSE
14
14
  License-File: NOTICE
@@ -234,16 +234,16 @@ vulnerabilities privately per [SECURITY.md](https://github.com/dbtinoy-/quadkit/
234
234
 
235
235
  ## Stability
236
236
 
237
- Version `0.0.44` in the `0.x` series, released in lockstep with the
238
- other four distributions; APIs may change between minor versions until
239
- 1.0 — pin an exact version (`quadkit-testing==0.0.44`) or a tight range
240
- (`>=0.0.44,<0.1.0`). Full policy:
241
- [stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
237
+ Version `0.0.46` in the `0.x` series, released in lockstep with the
238
+ other twelve distributions; APIs may change between minor versions until
239
+ 1.0 — pin an exact version (`quadkit-testing==0.0.46`) or a tight range
240
+ (`>=0.0.46,<0.1.0`). Full policy:
241
+ [stability and compatibility](https://oridecon.dev/quadkit/ecosystem/compatibility/).
242
242
 
243
243
  ## Links
244
244
 
245
- - **Documentation** — <https://dbtinoy-.github.io/quadkit/>
246
- - **Getting started** — <https://dbtinoy-.github.io/quadkit/getting-started/installation/>
245
+ - **Documentation** — <https://oridecon.dev/quadkit/>
246
+ - **Getting started** — <https://oridecon.dev/quadkit/getting-started/installation/>
247
247
  - **Changelog** — <https://github.com/dbtinoy-/quadkit/blob/main/CHANGELOG.md>
248
248
  - **Issues** — <https://github.com/dbtinoy-/quadkit/issues>
249
249
  - **Security** — report privately per [SECURITY.md](https://github.com/dbtinoy-/quadkit/blob/main/SECURITY.md)
@@ -178,16 +178,16 @@ vulnerabilities privately per [SECURITY.md](https://github.com/dbtinoy-/quadkit/
178
178
 
179
179
  ## Stability
180
180
 
181
- Version `0.0.44` in the `0.x` series, released in lockstep with the
182
- other four distributions; APIs may change between minor versions until
183
- 1.0 — pin an exact version (`quadkit-testing==0.0.44`) or a tight range
184
- (`>=0.0.44,<0.1.0`). Full policy:
185
- [stability and compatibility](https://dbtinoy-.github.io/quadkit/reference/stability/).
181
+ Version `0.0.46` in the `0.x` series, released in lockstep with the
182
+ other twelve distributions; APIs may change between minor versions until
183
+ 1.0 — pin an exact version (`quadkit-testing==0.0.46`) or a tight range
184
+ (`>=0.0.46,<0.1.0`). Full policy:
185
+ [stability and compatibility](https://oridecon.dev/quadkit/ecosystem/compatibility/).
186
186
 
187
187
  ## Links
188
188
 
189
- - **Documentation** — <https://dbtinoy-.github.io/quadkit/>
190
- - **Getting started** — <https://dbtinoy-.github.io/quadkit/getting-started/installation/>
189
+ - **Documentation** — <https://oridecon.dev/quadkit/>
190
+ - **Getting started** — <https://oridecon.dev/quadkit/getting-started/installation/>
191
191
  - **Changelog** — <https://github.com/dbtinoy-/quadkit/blob/main/CHANGELOG.md>
192
192
  - **Issues** — <https://github.com/dbtinoy-/quadkit/issues>
193
193
  - **Security** — report privately per [SECURITY.md](https://github.com/dbtinoy-/quadkit/blob/main/SECURITY.md)
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "quadkit-testing"
9
- version = "0.0.44"
9
+ version = "0.0.46"
10
10
  description = "Centralized testing infrastructure for Quadkit Framework - Fixtures, factories, and utilities"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.11"
@@ -42,10 +42,10 @@ dependencies = [
42
42
  "pytest-mock>=3.10.0,<4",
43
43
  ]
44
44
  authors = [
45
- { name = "Quadkit Framework Team", email = "team@quadkit.dev" },
45
+ { name = "Quadkit Framework Team", email = "hello@oridecon.dev" },
46
46
  ]
47
47
  maintainers = [
48
- { name = "Quadkit Framework Team", email = "team@quadkit.dev" },
48
+ { name = "Quadkit Framework Team", email = "hello@oridecon.dev" },
49
49
  ]
50
50
 
51
51
  [project.license]
@@ -78,9 +78,9 @@ integration = [
78
78
  ]
79
79
 
80
80
  [project.urls]
81
- Homepage = "https://dbtinoy-.github.io/quadkit/"
81
+ Homepage = "https://oridecon.dev"
82
82
  Repository = "https://github.com/dbtinoy-/quadkit"
83
- Documentation = "https://dbtinoy-.github.io/quadkit/"
83
+ Documentation = "https://oridecon.dev/quadkit/"
84
84
  Issues = "https://github.com/dbtinoy-/quadkit/issues"
85
85
  Changelog = "https://github.com/dbtinoy-/quadkit/blob/main/CHANGELOG.md"
86
86
 
@@ -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
 
@@ -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
- result = await backend.get("key")
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 test_get_missing_returns_default(self) -> None:
56
- """get on a missing key returns the default."""
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
- result = await backend.get("nonexistent", "fallback")
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
- deleted = await backend.delete("key")
74
- assert deleted is True
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
- deleted = await backend.delete("nonexistent")
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
- result = await backend.get("key")
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
- result = await backend.get("key")
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
+ )
@@ -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 fetch_one."""
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.fetch_one("SELECT val FROM _compliance_rw WHERE id = 1")
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.fetch_one("SELECT val FROM _compliance_tx WHERE id = 42")
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