tigrbl_tests 0.3.8.dev1__tar.gz → 0.3.9.dev1__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 (257) hide show
  1. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/PKG-INFO +1 -1
  2. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/pyproject.toml +1 -1
  3. tigrbl_tests-0.3.9.dev1/tests/i9n/test_mountable_favicon_uvicorn.py +51 -0
  4. tigrbl_tests-0.3.9.dev1/tests/mount/test_mount_favico.py +114 -0
  5. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_jsonrpc_openrpc.py +2 -2
  6. tigrbl_tests-0.3.9.dev1/tests/unit/test_middleware_http_and_cors.py +206 -0
  7. tigrbl_tests-0.3.9.dev1/tests/unit/test_system_docs_builders.py +23 -0
  8. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_configuration.py +12 -0
  9. tigrbl_tests-0.3.9.dev1/tests/unit/test_tigrbl_app_state.py +20 -0
  10. tigrbl_tests-0.3.9.dev1/tests/unit/test_v3_favicon_endpoint.py +22 -0
  11. tigrbl_tests-0.3.8.dev1/tests/i9n/test_mountable_favicon_uvicorn.py +0 -41
  12. tigrbl_tests-0.3.8.dev1/tests/mount/test_mount_favico.py +0 -78
  13. tigrbl_tests-0.3.8.dev1/tests/unit/test_v3_favicon_endpoint.py +0 -17
  14. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/LICENSE +0 -0
  15. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/README.md +0 -0
  16. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/__init__.py +0 -0
  17. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/conftest.py +0 -0
  18. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/hooks/test_hook_ctx_phase_validation.py +0 -0
  19. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/__init__.py +0 -0
  20. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_acronym_route_name.py +0 -0
  21. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_allow_anon.py +0 -0
  22. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_apikey_generation.py +0 -0
  23. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_authn_provider_integration.py +0 -0
  24. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_bindings_integration.py +0 -0
  25. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_bindings_modules.py +0 -0
  26. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_bulk_docs_client.py +0 -0
  27. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_core_access.py +0 -0
  28. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_engine_install_uvicorn.py +0 -0
  29. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_engine_resolver_uvicorn.py +0 -0
  30. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_error_mappings.py +0 -0
  31. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_field_spec_effects.py +0 -0
  32. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_header_io_uvicorn.py +0 -0
  33. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_healthz_methodz_hookz.py +0 -0
  34. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_hook_ctx_v3_i9n.py +0 -0
  35. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_hook_lifecycle.py +0 -0
  36. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_iospec_attributes.py +0 -0
  37. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_iospec_integration.py +0 -0
  38. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_jsonrpc_batch_behavior.py +0 -0
  39. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_key_digest_uvicorn.py +0 -0
  40. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_list_filters_optional.py +0 -0
  41. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mixins.py +0 -0
  42. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_lens_uvicorn.py +0 -0
  43. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_openapi_uvicorn.py +0 -0
  44. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_openrpc_uvicorn.py +0 -0
  45. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_swagger_uvicorn.py +0 -0
  46. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_nested_path_schema_and_rpc.py +0 -0
  47. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_nested_routing_depth.py +0 -0
  48. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_op_ctx_alias_examples.py +0 -0
  49. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_op_ctx_behavior.py +0 -0
  50. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_op_ctx_core_crud_order.py +0 -0
  51. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_openapi_clear_response_schema.py +0 -0
  52. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_openapi_schema_examples_presence.py +0 -0
  53. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_opspec_effects_i9n_test.py +0 -0
  54. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_owner_tenant_policy.py +0 -0
  55. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_request_extras.py +0 -0
  56. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_request_extras_provider.py +0 -0
  57. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_response_extras_provider.py +0 -0
  58. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_rest_fallback_serialization.py +0 -0
  59. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_rest_row_serialization.py +0 -0
  60. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_rest_rpc_parity_v3.py +0 -0
  61. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_row_result_serialization.py +0 -0
  62. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema.py +0 -0
  63. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema_ctx_attributes_integration.py +0 -0
  64. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema_ctx_op_ctx_integration.py +0 -0
  65. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema_ctx_spec_integration.py +0 -0
  66. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_sqlite_attachments.py +0 -0
  67. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_storage_spec_integration.py +0 -0
  68. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_symmetry_parity.py +0 -0
  69. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_api_app_usage_uvicorn.py +0 -0
  70. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_api_usage_uvicorn.py +0 -0
  71. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_api_uvicorn.py +0 -0
  72. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_include_api_uvicorn.py +0 -0
  73. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_multi_api_uvicorn.py +0 -0
  74. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_usage_uvicorn.py +0 -0
  75. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_uvicorn.py +0 -0
  76. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_bulk_rest_endpoints.py +0 -0
  77. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_default_rest_ops.py +0 -0
  78. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_default_rpc_ops.py +0 -0
  79. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_opspec_attributes.py +0 -0
  80. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_verb_alias_policy.py +0 -0
  81. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/i9n/uvicorn_utils.py +0 -0
  82. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/mount/__init__.py +0 -0
  83. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/parity/__init__.py +0 -0
  84. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/parity/test_stdapi_openapi_docs.py +0 -0
  85. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/parity/test_stdapi_primitives.py +0 -0
  86. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/parity/test_stdapi_routing.py +0 -0
  87. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/perf/__init__.py +0 -0
  88. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/perf/test_collect_caching.py +0 -0
  89. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/perf/test_hookz_performance.py +0 -0
  90. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/perf/test_methodz_performance.py +0 -0
  91. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/request/test_request_dot_notation.py +0 -0
  92. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/request/test_request_response_conveniences.py +0 -0
  93. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/request/test_request_transport_convenience_dot_notation.py +0 -0
  94. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/requests/test_request_json_modes.py +0 -0
  95. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/response/test_response_dot_notation.py +0 -0
  96. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/response/test_response_transport_convenience_dot_notation.py +0 -0
  97. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/security/test_dot_notation_schemes.py +0 -0
  98. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/security/test_schemes.py +0 -0
  99. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/__init__.py +0 -0
  100. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_alias_ctx_bindings.py +0 -0
  101. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_engine_ctx_bindings.py +0 -0
  102. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_hook_ctx_bindings.py +0 -0
  103. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_op_alias_bindings.py +0 -0
  104. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_op_ctx_bindings.py +0 -0
  105. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_response_ctx_bindings.py +0 -0
  106. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_schema_ctx_bindings.py +0 -0
  107. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/response_utils.py +0 -0
  108. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_emit_paired_post.py +0 -0
  109. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_emit_paired_pre.py +0 -0
  110. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_emit_readtime_alias.py +0 -0
  111. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_out_masking.py +0 -0
  112. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_refresh_demand.py +0 -0
  113. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_resolve_assemble.py +0 -0
  114. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_resolve_paired_gen.py +0 -0
  115. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_schema_collect_in.py +0 -0
  116. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_schema_collect_out.py +0 -0
  117. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_storage_to_stored.py +0 -0
  118. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_build_in.py +0 -0
  119. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_build_out.py +0 -0
  120. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_dump.py +0 -0
  121. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_validate_in.py +0 -0
  122. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/test_events_phases.py +0 -0
  123. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_acol_vcol_knobs.py +0 -0
  124. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_alias_ctx_op_alias_attributes.py +0 -0
  125. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_alias_ctx_op_attributes.py +0 -0
  126. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_api_level_set_auth.py +0 -0
  127. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_app_model_defaults.py +0 -0
  128. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_app_reexport.py +0 -0
  129. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_base_columnspec_inheritance.py +0 -0
  130. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_base_facade_initialize.py +0 -0
  131. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_build_list_params_spec_model.py +0 -0
  132. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_bulk_body_annotation.py +0 -0
  133. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_bulk_response_schema.py +0 -0
  134. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_colspec_map_isolation.py +0 -0
  135. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_column_collect_mixins.py +0 -0
  136. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_column_rest_rpc_results.py +0 -0
  137. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_column_table_orm_binding.py +0 -0
  138. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_config_dataclass_none.py +0 -0
  139. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_crud_bulk_ops.py +0 -0
  140. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_crud_default_ops.py +0 -0
  141. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_crud_methods.py +0 -0
  142. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_wrap_memoization.py +0 -0
  143. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_db_dependency.py +0 -0
  144. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_decorator_and_collect.py +0 -0
  145. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_default_tags.py +0 -0
  146. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_api.py +0 -0
  147. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_app.py +0 -0
  148. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_op.py +0 -0
  149. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_table.py +0 -0
  150. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_spec_and_shortcuts.py +0 -0
  151. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_usage_levels.py +0 -0
  152. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_field_spec_attrs.py +0 -0
  153. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_file_response.py +0 -0
  154. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_get_schema.py +0 -0
  155. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_handler_step_qualname.py +0 -0
  156. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hook_ctx_attributes.py +0 -0
  157. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hook_ctx_binding.py +0 -0
  158. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hookz_empty_phase.py +0 -0
  159. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hybrid_session_run_sync.py +0 -0
  160. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_in_tx.py +0 -0
  161. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_include_model_columns_namespace.py +0 -0
  162. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_include_models_base_prefix.py +0 -0
  163. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_async_task.py +0 -0
  164. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_cross_ddl.py +0 -0
  165. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_mixed_engines.py +0 -0
  166. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_task_schedule.py +0 -0
  167. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_io_spec_attributes.py +0 -0
  168. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_iospec_attributes.py +0 -0
  169. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_iospec_effects.py +0 -0
  170. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_jsonrpc_id_example.py +0 -0
  171. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_jsonrpc_router_default_tag.py +0 -0
  172. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernel_invoke_ctx.py +0 -0
  173. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernel_opview_on_demand.py +0 -0
  174. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernel_plan_labels.py +0 -0
  175. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernelz_endpoint.py +0 -0
  176. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_make_column_shortcuts.py +0 -0
  177. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_mixins_sqlalchemy.py +0 -0
  178. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_alias.py +0 -0
  179. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_class_engine_binding.py +0 -0
  180. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_arity_paths.py +0 -0
  181. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_attributes.py +0 -0
  182. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_core_crud_integration.py +0 -0
  183. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_dynamic_attach.py +0 -0
  184. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_persist_options.py +0 -0
  185. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_opspec_effects.py +0 -0
  186. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_postgres_engine_errors.py +0 -0
  187. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_postgres_env_vars.py +0 -0
  188. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_relationship_alias_cols.py +0 -0
  189. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_request_body_schema.py +0 -0
  190. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_request_response_examples.py +0 -0
  191. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_resolver_interning_warmup.py +0 -0
  192. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_resolver_precedence.py +0 -0
  193. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_alias_table_rpc.py +0 -0
  194. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_ctx_precedence.py +0 -0
  195. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_diagnostics_kernelz.py +0 -0
  196. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_html_jinja_behavior.py +0 -0
  197. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_parity.py +0 -0
  198. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_rest.py +0 -0
  199. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_rpc.py +0 -0
  200. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_template.py +0 -0
  201. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_uuid.py +0 -0
  202. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_all_default_op_verbs.py +0 -0
  203. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_bulk_delete_suppresses_clear.py +0 -0
  204. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_no_schema_jsonable.py +0 -0
  205. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_operation_id_uniqueness.py +0 -0
  206. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_rpc_parity_default_ops.py +0 -0
  207. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_rpc_prefixes.py +0 -0
  208. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_rpc_symmetry.py +0 -0
  209. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_router_compatibility_shims.py +0 -0
  210. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rpc_all_default_op_verbs.py +0 -0
  211. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rpc_default_ops.py +0 -0
  212. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schema_ctx_attributes.py +0 -0
  213. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schema_ctx_plain_class.py +0 -0
  214. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schema_spec_presence.py +0 -0
  215. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schemas_binding.py +0 -0
  216. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_security_per_route.py +0 -0
  217. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_should_wire_canonical.py +0 -0
  218. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_api.py +0 -0
  219. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_app.py +0 -0
  220. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_column.py +0 -0
  221. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_engine.py +0 -0
  222. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_field.py +0 -0
  223. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_hook.py +0 -0
  224. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_io.py +0 -0
  225. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_op.py +0 -0
  226. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_storage.py +0 -0
  227. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_table.py +0 -0
  228. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sqlite_attachments.py +0 -0
  229. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sqlite_uuid_type.py +0 -0
  230. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_stdapi_request_injection.py +0 -0
  231. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_stdapi_transport_asgi_wsgi.py +0 -0
  232. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_storage_spec_attributes.py +0 -0
  233. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_handler_crud.py +0 -0
  234. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_run_rollback.py +0 -0
  235. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_tx_async_begin.py +0 -0
  236. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_tx_begin.py +0 -0
  237. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_tx_commit.py +0 -0
  238. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_base_exports.py +0 -0
  239. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_collect_spec.py +0 -0
  240. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_columns_namespace.py +0 -0
  241. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_namespace_init.py +0 -0
  242. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_namespace_isolation.py +0 -0
  243. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_app_configuration.py +0 -0
  244. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_app_instantiation.py +0 -0
  245. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_app_subclass_definition.py +0 -0
  246. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_instantiation.py +0 -0
  247. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_subclass_definition.py +0 -0
  248. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_configuration.py +0 -0
  249. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_event_handlers.py +0 -0
  250. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_instantiation.py +0 -0
  251. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_subclass_definition.py +0 -0
  252. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_healthz_endpoint.py +0 -0
  253. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_op_alias.py +0 -0
  254. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_op_ctx_attributes.py +0 -0
  255. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_schemas_and_decorators.py +0 -0
  256. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_storage_spec_attributes.py +0 -0
  257. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_verbosity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl_tests
3
- Version: 0.3.8.dev1
3
+ Version: 0.3.9.dev1
4
4
  Summary: Test suite and fixtures for the Tigrbl framework.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tigrbl_tests"
3
- version = "0.3.8.dev1"
3
+ version = "0.3.9.dev1"
4
4
  description = "Test suite and fixtures for the Tigrbl framework."
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -0,0 +1,51 @@
1
+ import httpx
2
+ import pytest
3
+
4
+ from tigrbl import TigrblApi, TigrblApp
5
+ from tigrbl.system import mount_favicon
6
+
7
+ from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
8
+
9
+
10
+ @pytest.mark.i9n
11
+ @pytest.mark.asyncio
12
+ async def test_favicon_mountable_on_tigrbl_app_uvicorn():
13
+ app = TigrblApp()
14
+ mount_favicon(app, path="/custom/favicon.svg", name="favicon_custom")
15
+
16
+ base_url, server, task = await run_uvicorn_in_task(app)
17
+ try:
18
+ async with httpx.AsyncClient() as client:
19
+ svg_response = await client.get(f"{base_url}/custom/favicon.svg")
20
+ ico_response = await client.get(
21
+ f"{base_url}/favicon.ico", follow_redirects=False
22
+ )
23
+ assert svg_response.status_code == 200
24
+ assert svg_response.headers.get("content-type") == "image/svg+xml"
25
+ assert svg_response.content
26
+ assert ico_response.status_code == 307
27
+ assert ico_response.headers.get("location") == "/custom/favicon.svg"
28
+ finally:
29
+ await stop_uvicorn_server(server, task)
30
+
31
+
32
+ @pytest.mark.i9n
33
+ @pytest.mark.asyncio
34
+ async def test_favicon_mountable_on_tigrbl_api_uvicorn():
35
+ api = TigrblApi()
36
+ mount_favicon(api, path="/custom/favicon.svg", name="favicon_custom")
37
+
38
+ base_url, server, task = await run_uvicorn_in_task(api)
39
+ try:
40
+ async with httpx.AsyncClient() as client:
41
+ svg_response = await client.get(f"{base_url}/custom/favicon.svg")
42
+ ico_response = await client.get(
43
+ f"{base_url}/favicon.ico", follow_redirects=False
44
+ )
45
+ assert svg_response.status_code == 200
46
+ assert svg_response.headers.get("content-type") == "image/svg+xml"
47
+ assert svg_response.content
48
+ assert ico_response.status_code == 307
49
+ assert ico_response.headers.get("location") == "/custom/favicon.svg"
50
+ finally:
51
+ await stop_uvicorn_server(server, task)
@@ -0,0 +1,114 @@
1
+ """Focused mount tests for bound ``mount_favicon`` helpers.
2
+
3
+ These tests verify both ``TigrblApp`` and ``TigrblApi`` expose the system
4
+ mount method directly, with SVG redirect and ICO file fallback behavior intact.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+ from httpx import ASGITransport, AsyncClient
11
+
12
+ from tigrbl import TigrblApi, TigrblApp
13
+
14
+
15
+ class TestMountFaviconOnTigrblApp:
16
+ """Validate favicon mounting behavior for ``TigrblApp`` instances."""
17
+
18
+ @pytest.mark.asyncio
19
+ async def test_mount_favicon_svg_route_and_ico_redirect(self) -> None:
20
+ """The default mount serves ``/favicon.svg`` and redirects ``/favicon.ico``."""
21
+ app = TigrblApp()
22
+ app.mount_favicon(name="favicon_default_route")
23
+
24
+ transport = ASGITransport(app=app)
25
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
26
+ svg_response = await client.get("/favicon.svg")
27
+ ico_response = await client.get("/favicon.ico", follow_redirects=False)
28
+
29
+ assert svg_response.status_code == 200
30
+ assert svg_response.headers["content-type"].startswith("image/svg+xml")
31
+ assert svg_response.content
32
+
33
+ assert ico_response.status_code == 307
34
+ assert ico_response.headers["location"] == "/favicon.svg"
35
+
36
+ @pytest.mark.asyncio
37
+ async def test_mount_favicon_supports_custom_svg_path(self) -> None:
38
+ """A custom SVG mount path should be the target for ICO redirects."""
39
+ app = TigrblApp()
40
+ app.mount_favicon(path="/assets/favicon.svg", name="favicon_assets_route")
41
+
42
+ transport = ASGITransport(app=app)
43
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
44
+ svg_response = await client.get("/assets/favicon.svg")
45
+ ico_response = await client.get("/favicon.ico", follow_redirects=False)
46
+
47
+ assert svg_response.status_code == 200
48
+ assert svg_response.headers["content-type"].startswith("image/svg+xml")
49
+
50
+ assert ico_response.status_code == 307
51
+ assert ico_response.headers["location"] == "/assets/favicon.svg"
52
+
53
+ @pytest.mark.asyncio
54
+ async def test_mount_favicon_serves_ico_file_directly(self, tmp_path: Path) -> None:
55
+ """When an ICO file is configured, only ICO should be served directly."""
56
+ app = TigrblApp()
57
+ favicon_ico = tmp_path / "favicon.ico"
58
+ favicon_ico.write_bytes(b"\x00\x00\x01\x00")
59
+
60
+ app.mount_favicon(favicon_path=favicon_ico, name="favicon_ico_route")
61
+
62
+ transport = ASGITransport(app=app)
63
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
64
+ ico_response = await client.get("/favicon.ico")
65
+ svg_response = await client.get("/favicon.svg")
66
+
67
+ assert ico_response.status_code == 200
68
+ assert ico_response.headers["content-type"].startswith("image/x-icon")
69
+ assert ico_response.content == b"\x00\x00\x01\x00"
70
+
71
+ assert svg_response.status_code == 404
72
+
73
+
74
+ class TestMountFaviconOnTigrblApi:
75
+ """Validate favicon mounting behavior for ``TigrblApi`` instances."""
76
+
77
+ def test_tigrbl_api_binds_mount_favicon_method(self) -> None:
78
+ """``TigrblApi`` should expose ``mount_favicon`` as an instance API."""
79
+ api = TigrblApi()
80
+
81
+ assert hasattr(api, "mount_favicon")
82
+
83
+ @pytest.mark.asyncio
84
+ async def test_bound_mount_favicon_supports_api_instances(self) -> None:
85
+ """The bound method should mount favicon routes on APIs."""
86
+ api = TigrblApi()
87
+ api.mount_favicon(path="/branding/favicon.svg", name="api_branding_favicon")
88
+
89
+ transport = ASGITransport(app=api)
90
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
91
+ svg_response = await client.get("/branding/favicon.svg")
92
+ ico_response = await client.get("/favicon.ico", follow_redirects=False)
93
+
94
+ assert svg_response.status_code == 200
95
+ assert svg_response.headers["content-type"].startswith("image/svg+xml")
96
+ assert ico_response.status_code == 307
97
+ assert ico_response.headers["location"] == "/branding/favicon.svg"
98
+
99
+ @pytest.mark.asyncio
100
+ async def test_bound_mount_favicon_retains_default_fallback(self) -> None:
101
+ """Passing ``None`` for favicon path should use the default SVG asset."""
102
+ api = TigrblApi()
103
+ api.mount_favicon(path="/fallback/favicon.svg", favicon_path=None)
104
+
105
+ transport = ASGITransport(app=api)
106
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
107
+ svg_response = await client.get("/fallback/favicon.svg")
108
+ ico_response = await client.get("/favicon.ico", follow_redirects=False)
109
+
110
+ assert svg_response.status_code == 200
111
+ assert svg_response.headers["content-type"].startswith("image/svg+xml")
112
+ assert svg_response.content
113
+ assert ico_response.status_code == 307
114
+ assert ico_response.headers["location"] == "/fallback/favicon.svg"
@@ -21,7 +21,7 @@ def test_openrpc_endpoint_exposed():
21
21
  app, _ = _build_app()
22
22
  transport = ASGITransport(app=app)
23
23
  with Client(transport=transport, base_url="http://test") as client:
24
- response = client.get("/rpc/openrpc.json")
24
+ response = client.get("/openrpc.json")
25
25
 
26
26
  assert response.status_code == 200
27
27
  payload = response.json()
@@ -33,7 +33,7 @@ def test_openrpc_includes_method_schema():
33
33
  app, model = _build_app()
34
34
  transport = ASGITransport(app=app)
35
35
  with Client(transport=transport, base_url="http://test") as client:
36
- payload = client.get("/rpc/openrpc.json").json()
36
+ payload = client.get("/openrpc.json").json()
37
37
  methods = {method["name"]: method for method in payload["methods"]}
38
38
 
39
39
  create_method = methods[f"{model.__name__}.create"]
@@ -0,0 +1,206 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from tigrbl.middlewares import BaseHTTPMiddleware, CORSMiddleware
6
+ from tigrbl.requests import Request
7
+ from tigrbl.responses import Response
8
+
9
+
10
+ async def _run_asgi(app, scope: dict, body: bytes = b"") -> list[dict]:
11
+ sent: list[dict] = []
12
+ emitted = False
13
+
14
+ async def receive() -> dict:
15
+ nonlocal emitted
16
+ if emitted:
17
+ return {"type": "http.request", "body": b"", "more_body": False}
18
+ emitted = True
19
+ return {"type": "http.request", "body": body, "more_body": False}
20
+
21
+ async def send(message: dict) -> None:
22
+ sent.append(message)
23
+
24
+ await app(scope, receive, send)
25
+ return sent
26
+
27
+
28
+ class RewriteMiddleware(BaseHTTPMiddleware):
29
+ async def dispatch(self, request: Request, call_next):
30
+ rewritten = Request(
31
+ method="POST",
32
+ path="/rewritten",
33
+ headers={**request.headers, "x-forwarded": "yes"},
34
+ query={"changed": ["1"]},
35
+ path_params={},
36
+ body=b"forwarded",
37
+ script_name="",
38
+ scope=request.scope,
39
+ )
40
+ return await call_next(rewritten)
41
+
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_base_http_middleware_call_next_uses_forwarded_scope_and_body() -> None:
45
+ async def endpoint(scope, receive, send):
46
+ body_msg = await receive()
47
+ payload = (
48
+ f"{scope.get('method')}|{scope.get('path')}|"
49
+ f"{scope.get('query_string', b'').decode('latin-1')}|"
50
+ f"{dict((k.decode('latin-1'), v.decode('latin-1')) for k, v in scope['headers'])['x-forwarded']}|"
51
+ f"{body_msg.get('body', b'').decode('latin-1')}"
52
+ ).encode("utf-8")
53
+ await send(
54
+ {
55
+ "type": "http.response.start",
56
+ "status": 200,
57
+ "headers": [(b"content-type", b"text/plain")],
58
+ }
59
+ )
60
+ await send({"type": "http.response.body", "body": payload, "more_body": False})
61
+
62
+ app = RewriteMiddleware(endpoint)
63
+ messages = await _run_asgi(
64
+ app,
65
+ {
66
+ "type": "http",
67
+ "method": "GET",
68
+ "path": "/original",
69
+ "query_string": b"before=1",
70
+ "headers": [(b"host", b"test")],
71
+ },
72
+ body=b"ignored",
73
+ )
74
+
75
+ body = next(m for m in messages if m["type"] == "http.response.body")["body"]
76
+ assert body == b"POST|/rewritten|changed=1|yes|forwarded"
77
+
78
+
79
+ class ShortCircuitMiddleware(BaseHTTPMiddleware):
80
+ async def dispatch(self, request: Request, call_next):
81
+ return Response.text("should-be-stripped", status_code=204)
82
+
83
+
84
+ @pytest.mark.asyncio
85
+ async def test_base_http_middleware_normalizes_no_body_responses() -> None:
86
+ async def endpoint(scope, receive, send):
87
+ del scope, receive
88
+ await send({"type": "http.response.start", "status": 200, "headers": []})
89
+ await send({"type": "http.response.body", "body": b"ok", "more_body": False})
90
+
91
+ app = ShortCircuitMiddleware(endpoint)
92
+ messages = await _run_asgi(
93
+ app,
94
+ {
95
+ "type": "http",
96
+ "method": "GET",
97
+ "path": "/",
98
+ "query_string": b"",
99
+ "headers": [],
100
+ },
101
+ )
102
+
103
+ start = next(m for m in messages if m["type"] == "http.response.start")
104
+ body = next(m for m in messages if m["type"] == "http.response.body")
105
+ header_names = {k.lower() for k, _ in start["headers"]}
106
+
107
+ assert body["body"] == b""
108
+ assert b"content-length" not in header_names
109
+ assert b"content-type" not in header_names
110
+
111
+
112
+ @pytest.mark.asyncio
113
+ async def test_cors_middleware_handles_preflight_and_simple_requests() -> None:
114
+ async def endpoint(scope, receive, send):
115
+ del scope, receive
116
+ await send(
117
+ {
118
+ "type": "http.response.start",
119
+ "status": 200,
120
+ "headers": [(b"content-type", b"text/plain")],
121
+ }
122
+ )
123
+ await send({"type": "http.response.body", "body": b"ok", "more_body": False})
124
+
125
+ app = CORSMiddleware(endpoint, allow_origin="https://client.test")
126
+
127
+ preflight = await _run_asgi(
128
+ app,
129
+ {
130
+ "type": "http",
131
+ "method": "OPTIONS",
132
+ "path": "/",
133
+ "query_string": b"",
134
+ "headers": [
135
+ (b"origin", b"https://client.test"),
136
+ (b"access-control-request-method", b"GET"),
137
+ (b"access-control-request-headers", b"x-custom"),
138
+ ],
139
+ },
140
+ )
141
+ preflight_start = next(m for m in preflight if m["type"] == "http.response.start")
142
+ preflight_headers = {
143
+ k.decode("latin-1"): v.decode("latin-1") for k, v in preflight_start["headers"]
144
+ }
145
+ assert preflight_start["status"] == 204
146
+ assert preflight_headers["access-control-allow-origin"] == "https://client.test"
147
+ assert preflight_headers["access-control-allow-headers"] == "x-custom"
148
+
149
+ simple = await _run_asgi(
150
+ app,
151
+ {
152
+ "type": "http",
153
+ "method": "GET",
154
+ "path": "/",
155
+ "query_string": b"",
156
+ "headers": [(b"origin", b"https://client.test")],
157
+ },
158
+ )
159
+ simple_start = next(m for m in simple if m["type"] == "http.response.start")
160
+ simple_headers = {
161
+ k.decode("latin-1"): v.decode("latin-1") for k, v in simple_start["headers"]
162
+ }
163
+ assert simple_start["status"] == 200
164
+ assert simple_headers["access-control-allow-origin"] == "https://client.test"
165
+
166
+
167
+ def test_cors_middleware_wsgi_preserves_duplicate_headers() -> None:
168
+ captured: dict[str, object] = {}
169
+
170
+ def wsgi_app(environ, start_response):
171
+ del environ
172
+ start_response(
173
+ "200 OK",
174
+ [("set-cookie", "session=abc"), ("set-cookie", "theme=dark")],
175
+ )
176
+ return [b"ok"]
177
+
178
+ app = CORSMiddleware(wsgi_app, allow_origin="https://client.test")
179
+
180
+ def start_response(status: str, headers: list[tuple[str, str]], *args):
181
+ del args
182
+ captured["status"] = status
183
+ captured["headers"] = headers
184
+
185
+ body = app.wsgi(
186
+ {
187
+ "REQUEST_METHOD": "GET",
188
+ "HTTP_ORIGIN": "https://client.test",
189
+ },
190
+ start_response,
191
+ )
192
+
193
+ assert body == [b"ok"]
194
+ assert captured["status"] == "200 OK"
195
+ headers = captured["headers"]
196
+ assert isinstance(headers, list)
197
+ assert [value for key, value in headers if key.lower() == "set-cookie"] == [
198
+ "session=abc",
199
+ "theme=dark",
200
+ ]
201
+ assert (
202
+ dict((key.lower(), value) for key, value in headers)[
203
+ "access-control-allow-origin"
204
+ ]
205
+ == "https://client.test"
206
+ )
@@ -0,0 +1,23 @@
1
+ import pytest
2
+
3
+ from tigrbl import TigrblApp
4
+ from tigrbl.system import build_lens, build_openapi, build_openrpc_spec, build_swagger
5
+ from tigrbl.types import Request
6
+
7
+
8
+ @pytest.mark.unit
9
+ def test_system_build_helpers_return_documents() -> None:
10
+ app = TigrblApp()
11
+ request = Request(
12
+ method="GET", path="/docs", headers={}, query={}, path_params={}, body=b""
13
+ )
14
+
15
+ openapi_doc = build_openapi(app)
16
+ openrpc_doc = build_openrpc_spec(app)
17
+ swagger_html = build_swagger(app, request)
18
+ lens_html = build_lens(app, request, spec_path="/openrpc.json")
19
+
20
+ assert openapi_doc["openapi"].startswith("3.")
21
+ assert openrpc_doc["openrpc"] == "1.2.6"
22
+ assert "swagger-ui" in swagger_html
23
+ assert "tigrbl-lens" in lens_html
@@ -45,3 +45,15 @@ def test_tigrbl_api_post_instantiation_set_auth_updates_state() -> None:
45
45
  assert "_allow_anon" in api_dir
46
46
  assert api._authn is _auth_dependency
47
47
  assert api._allow_anon is False
48
+
49
+
50
+ @pytest.mark.unit
51
+ def test_tigrbl_api_class_prefix_defaults() -> None:
52
+ assert TigrblApi.REST_PREFIX == "/api"
53
+ assert TigrblApi.RPC_PREFIX == "/rpc"
54
+ assert TigrblApi.SYSTEM_PREFIX == "/system"
55
+
56
+ api = TigrblApi(engine=mem(async_=False))
57
+ assert api.rest_prefix == "/api"
58
+ assert api.rpc_prefix == "/rpc"
59
+ assert api.system_prefix == "/system"
@@ -0,0 +1,20 @@
1
+ import pytest
2
+
3
+ from tigrbl import TigrblApp
4
+ from tigrbl.engine.shortcuts import mem
5
+
6
+
7
+ @pytest.mark.unit
8
+ def test_tigrbl_app_exposes_state_namespace() -> None:
9
+ app = TigrblApp(engine=mem(async_=False))
10
+
11
+ assert hasattr(app, "state")
12
+
13
+
14
+ @pytest.mark.unit
15
+ def test_tigrbl_app_state_allows_attribute_assignment() -> None:
16
+ app = TigrblApp(engine=mem(async_=False))
17
+
18
+ app.state.tenant = "tenant-123"
19
+
20
+ assert app.state.tenant == "tenant-123"
@@ -0,0 +1,22 @@
1
+ import pytest
2
+ from httpx import ASGITransport, AsyncClient
3
+
4
+ from tigrbl import TigrblApp
5
+
6
+
7
+ @pytest.mark.asyncio
8
+ async def test_favicon_endpoint_serves_default_svg_and_redirects_ico():
9
+ app = TigrblApp()
10
+
11
+ async with AsyncClient(
12
+ transport=ASGITransport(app=app), base_url="http://test"
13
+ ) as client:
14
+ svg_res = await client.get("/favicon.svg")
15
+ ico_res = await client.get("/favicon.ico", follow_redirects=False)
16
+
17
+ assert svg_res.status_code == 200
18
+ assert svg_res.headers.get("content-type") == "image/svg+xml"
19
+ assert svg_res.content
20
+
21
+ assert ico_res.status_code == 307
22
+ assert ico_res.headers.get("location") == "/favicon.svg"
@@ -1,41 +0,0 @@
1
- import httpx
2
- import pytest
3
-
4
- from tigrbl import TigrblApi, TigrblApp
5
- from tigrbl.system import mount_favicon
6
-
7
- from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
8
-
9
-
10
- @pytest.mark.i9n
11
- @pytest.mark.asyncio
12
- async def test_favicon_mountable_on_tigrbl_app_uvicorn():
13
- app = TigrblApp()
14
- mount_favicon(app, path="/custom/favicon.ico", name="favicon_custom")
15
-
16
- base_url, server, task = await run_uvicorn_in_task(app)
17
- try:
18
- async with httpx.AsyncClient() as client:
19
- response = await client.get(f"{base_url}/custom/favicon.ico")
20
- assert response.status_code == 200
21
- assert response.headers.get("content-type") == "image/svg+xml"
22
- assert response.content
23
- finally:
24
- await stop_uvicorn_server(server, task)
25
-
26
-
27
- @pytest.mark.i9n
28
- @pytest.mark.asyncio
29
- async def test_favicon_mountable_on_tigrbl_api_uvicorn():
30
- api = TigrblApi()
31
- mount_favicon(api, path="/custom/favicon.ico", name="favicon_custom")
32
-
33
- base_url, server, task = await run_uvicorn_in_task(api)
34
- try:
35
- async with httpx.AsyncClient() as client:
36
- response = await client.get(f"{base_url}/custom/favicon.ico")
37
- assert response.status_code == 200
38
- assert response.headers.get("content-type") == "image/svg+xml"
39
- assert response.content
40
- finally:
41
- await stop_uvicorn_server(server, task)
@@ -1,78 +0,0 @@
1
- """Focused mount tests for bound ``mount_favicon`` helpers.
2
-
3
- These tests verify both ``TigrblApp`` and ``TigrblApi`` expose the system
4
- mount method directly, with default fallback behavior intact.
5
- """
6
-
7
- import pytest
8
- from httpx import ASGITransport, AsyncClient
9
-
10
- from tigrbl import TigrblApi, TigrblApp
11
-
12
-
13
- class TestMountFaviconOnTigrblApp:
14
- """Validate favicon mounting behavior for ``TigrblApp`` instances."""
15
-
16
- @pytest.mark.asyncio
17
- async def test_mount_favicon_exposes_default_favicon_route(self) -> None:
18
- """The default ``/favicon.ico`` route should return SVG content."""
19
- app = TigrblApp()
20
- app.mount_favicon(name="favicon_default_route")
21
-
22
- transport = ASGITransport(app=app)
23
- async with AsyncClient(transport=transport, base_url="http://test") as client:
24
- response = await client.get("/favicon.ico")
25
-
26
- assert response.status_code == 200
27
- assert response.headers["content-type"].startswith("image/svg+xml")
28
- assert response.content
29
-
30
- @pytest.mark.asyncio
31
- async def test_mount_favicon_supports_custom_path(self) -> None:
32
- """A custom mount path should serve the configured favicon endpoint."""
33
- app = TigrblApp()
34
- app.mount_favicon(path="/assets/favicon.ico", name="favicon_assets_route")
35
-
36
- transport = ASGITransport(app=app)
37
- async with AsyncClient(transport=transport, base_url="http://test") as client:
38
- response = await client.get("/assets/favicon.ico")
39
-
40
- assert response.status_code == 200
41
- assert response.headers["content-type"].startswith("image/svg+xml")
42
-
43
-
44
- class TestMountFaviconOnTigrblApi:
45
- """Validate favicon mounting behavior for ``TigrblApi`` instances."""
46
-
47
- def test_tigrbl_api_binds_mount_favicon_method(self) -> None:
48
- """``TigrblApi`` should expose ``mount_favicon`` as an instance API."""
49
- api = TigrblApi()
50
-
51
- assert hasattr(api, "mount_favicon")
52
-
53
- @pytest.mark.asyncio
54
- async def test_bound_mount_favicon_supports_api_instances(self) -> None:
55
- """The bound method should mount favicon routes on APIs."""
56
- api = TigrblApi()
57
- api.mount_favicon(path="/branding/favicon.ico", name="api_branding_favicon")
58
-
59
- transport = ASGITransport(app=api)
60
- async with AsyncClient(transport=transport, base_url="http://test") as client:
61
- response = await client.get("/branding/favicon.ico")
62
-
63
- assert response.status_code == 200
64
- assert response.headers["content-type"].startswith("image/svg+xml")
65
-
66
- @pytest.mark.asyncio
67
- async def test_bound_mount_favicon_retains_default_fallback(self) -> None:
68
- """Passing ``None`` for favicon path should use the default asset."""
69
- api = TigrblApi()
70
- api.mount_favicon(path="/fallback/favicon.ico", favicon_path=None)
71
-
72
- transport = ASGITransport(app=api)
73
- async with AsyncClient(transport=transport, base_url="http://test") as client:
74
- response = await client.get("/fallback/favicon.ico")
75
-
76
- assert response.status_code == 200
77
- assert response.headers["content-type"].startswith("image/svg+xml")
78
- assert response.content
@@ -1,17 +0,0 @@
1
- import pytest
2
- from tigrbl import TigrblApp
3
- from httpx import AsyncClient, ASGITransport
4
-
5
-
6
- @pytest.mark.asyncio
7
- async def test_favicon_endpoint_serves_default_icon():
8
- app = TigrblApp()
9
-
10
- async with AsyncClient(
11
- transport=ASGITransport(app=app), base_url="http://test"
12
- ) as client:
13
- res = await client.get("/favicon.ico")
14
-
15
- assert res.status_code == 200
16
- assert res.headers.get("content-type") == "image/svg+xml"
17
- assert res.content # file has some content