tigrbl_tests 0.3.8.dev1__tar.gz → 0.3.9__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 (258) hide show
  1. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/PKG-INFO +1 -1
  2. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/pyproject.toml +1 -1
  3. tigrbl_tests-0.3.9/tests/i9n/test_mountable_favicon_uvicorn.py +51 -0
  4. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_mountable_lens_uvicorn.py +2 -2
  5. tigrbl_tests-0.3.9/tests/mount/test_mount_favico.py +114 -0
  6. tigrbl_tests-0.3.9/tests/request/test_request_asgi_scope_compat.py +41 -0
  7. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_jsonrpc_openrpc.py +14 -2
  8. tigrbl_tests-0.3.9/tests/unit/test_middleware_http_and_cors.py +266 -0
  9. tigrbl_tests-0.3.9/tests/unit/test_system_docs_builders.py +122 -0
  10. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_api_configuration.py +12 -0
  11. tigrbl_tests-0.3.9/tests/unit/test_tigrbl_app_state.py +20 -0
  12. tigrbl_tests-0.3.9/tests/unit/test_v3_favicon_endpoint.py +22 -0
  13. tigrbl_tests-0.3.8.dev1/tests/i9n/test_mountable_favicon_uvicorn.py +0 -41
  14. tigrbl_tests-0.3.8.dev1/tests/mount/test_mount_favico.py +0 -78
  15. tigrbl_tests-0.3.8.dev1/tests/unit/test_v3_favicon_endpoint.py +0 -17
  16. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/LICENSE +0 -0
  17. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/README.md +0 -0
  18. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/__init__.py +0 -0
  19. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/conftest.py +0 -0
  20. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/hooks/test_hook_ctx_phase_validation.py +0 -0
  21. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/__init__.py +0 -0
  22. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_acronym_route_name.py +0 -0
  23. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_allow_anon.py +0 -0
  24. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_apikey_generation.py +0 -0
  25. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_authn_provider_integration.py +0 -0
  26. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_bindings_integration.py +0 -0
  27. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_bindings_modules.py +0 -0
  28. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_bulk_docs_client.py +0 -0
  29. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_core_access.py +0 -0
  30. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_engine_install_uvicorn.py +0 -0
  31. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_engine_resolver_uvicorn.py +0 -0
  32. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_error_mappings.py +0 -0
  33. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_field_spec_effects.py +0 -0
  34. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_header_io_uvicorn.py +0 -0
  35. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_healthz_methodz_hookz.py +0 -0
  36. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_hook_ctx_v3_i9n.py +0 -0
  37. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_hook_lifecycle.py +0 -0
  38. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_iospec_attributes.py +0 -0
  39. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_iospec_integration.py +0 -0
  40. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_jsonrpc_batch_behavior.py +0 -0
  41. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_key_digest_uvicorn.py +0 -0
  42. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_list_filters_optional.py +0 -0
  43. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_mixins.py +0 -0
  44. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_mountable_openapi_uvicorn.py +0 -0
  45. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_mountable_openrpc_uvicorn.py +0 -0
  46. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_mountable_swagger_uvicorn.py +0 -0
  47. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_nested_path_schema_and_rpc.py +0 -0
  48. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_nested_routing_depth.py +0 -0
  49. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_op_ctx_alias_examples.py +0 -0
  50. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_op_ctx_behavior.py +0 -0
  51. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_op_ctx_core_crud_order.py +0 -0
  52. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_openapi_clear_response_schema.py +0 -0
  53. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_openapi_schema_examples_presence.py +0 -0
  54. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_opspec_effects_i9n_test.py +0 -0
  55. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_owner_tenant_policy.py +0 -0
  56. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_request_extras.py +0 -0
  57. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_request_extras_provider.py +0 -0
  58. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_response_extras_provider.py +0 -0
  59. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_rest_fallback_serialization.py +0 -0
  60. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_rest_row_serialization.py +0 -0
  61. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_rest_rpc_parity_v3.py +0 -0
  62. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_row_result_serialization.py +0 -0
  63. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_schema.py +0 -0
  64. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_schema_ctx_attributes_integration.py +0 -0
  65. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_schema_ctx_op_ctx_integration.py +0 -0
  66. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_schema_ctx_spec_integration.py +0 -0
  67. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_sqlite_attachments.py +0 -0
  68. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_storage_spec_integration.py +0 -0
  69. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_symmetry_parity.py +0 -0
  70. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_api_app_usage_uvicorn.py +0 -0
  71. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_api_usage_uvicorn.py +0 -0
  72. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_api_uvicorn.py +0 -0
  73. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_app_include_api_uvicorn.py +0 -0
  74. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_app_multi_api_uvicorn.py +0 -0
  75. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_app_usage_uvicorn.py +0 -0
  76. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_tigrbl_app_uvicorn.py +0 -0
  77. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_v3_bulk_rest_endpoints.py +0 -0
  78. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_v3_default_rest_ops.py +0 -0
  79. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_v3_default_rpc_ops.py +0 -0
  80. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_v3_opspec_attributes.py +0 -0
  81. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/test_verb_alias_policy.py +0 -0
  82. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/i9n/uvicorn_utils.py +0 -0
  83. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/mount/__init__.py +0 -0
  84. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/parity/__init__.py +0 -0
  85. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/parity/test_stdapi_openapi_docs.py +0 -0
  86. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/parity/test_stdapi_primitives.py +0 -0
  87. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/parity/test_stdapi_routing.py +0 -0
  88. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/perf/__init__.py +0 -0
  89. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/perf/test_collect_caching.py +0 -0
  90. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/perf/test_hookz_performance.py +0 -0
  91. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/perf/test_methodz_performance.py +0 -0
  92. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/request/test_request_dot_notation.py +0 -0
  93. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/request/test_request_response_conveniences.py +0 -0
  94. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/request/test_request_transport_convenience_dot_notation.py +0 -0
  95. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/requests/test_request_json_modes.py +0 -0
  96. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/response/test_response_dot_notation.py +0 -0
  97. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/response/test_response_transport_convenience_dot_notation.py +0 -0
  98. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/security/test_dot_notation_schemes.py +0 -0
  99. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/security/test_schemes.py +0 -0
  100. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/__init__.py +0 -0
  101. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_alias_ctx_bindings.py +0 -0
  102. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_engine_ctx_bindings.py +0 -0
  103. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_hook_ctx_bindings.py +0 -0
  104. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_op_alias_bindings.py +0 -0
  105. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_op_ctx_bindings.py +0 -0
  106. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_response_ctx_bindings.py +0 -0
  107. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/decorators/test_schema_ctx_bindings.py +0 -0
  108. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/response_utils.py +0 -0
  109. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_emit_paired_post.py +0 -0
  110. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_emit_paired_pre.py +0 -0
  111. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_emit_readtime_alias.py +0 -0
  112. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_out_masking.py +0 -0
  113. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_refresh_demand.py +0 -0
  114. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_resolve_assemble.py +0 -0
  115. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_resolve_paired_gen.py +0 -0
  116. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_schema_collect_in.py +0 -0
  117. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_schema_collect_out.py +0 -0
  118. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_storage_to_stored.py +0 -0
  119. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_wire_build_in.py +0 -0
  120. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_wire_build_out.py +0 -0
  121. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_wire_dump.py +0 -0
  122. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/atoms/test_wire_validate_in.py +0 -0
  123. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/runtime/test_events_phases.py +0 -0
  124. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_acol_vcol_knobs.py +0 -0
  125. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_alias_ctx_op_alias_attributes.py +0 -0
  126. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_alias_ctx_op_attributes.py +0 -0
  127. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_api_level_set_auth.py +0 -0
  128. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_app_model_defaults.py +0 -0
  129. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_app_reexport.py +0 -0
  130. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_base_columnspec_inheritance.py +0 -0
  131. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_base_facade_initialize.py +0 -0
  132. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_build_list_params_spec_model.py +0 -0
  133. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_bulk_body_annotation.py +0 -0
  134. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_bulk_response_schema.py +0 -0
  135. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_colspec_map_isolation.py +0 -0
  136. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_column_collect_mixins.py +0 -0
  137. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_column_rest_rpc_results.py +0 -0
  138. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_column_table_orm_binding.py +0 -0
  139. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_config_dataclass_none.py +0 -0
  140. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_core_crud_bulk_ops.py +0 -0
  141. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_core_crud_default_ops.py +0 -0
  142. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_core_crud_methods.py +0 -0
  143. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_core_wrap_memoization.py +0 -0
  144. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_db_dependency.py +0 -0
  145. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_decorator_and_collect.py +0 -0
  146. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_default_tags.py +0 -0
  147. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_engine_install_api.py +0 -0
  148. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_engine_install_app.py +0 -0
  149. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_engine_install_op.py +0 -0
  150. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_engine_install_table.py +0 -0
  151. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_engine_spec_and_shortcuts.py +0 -0
  152. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_engine_usage_levels.py +0 -0
  153. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_field_spec_attrs.py +0 -0
  154. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_file_response.py +0 -0
  155. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_get_schema.py +0 -0
  156. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_handler_step_qualname.py +0 -0
  157. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_hook_ctx_attributes.py +0 -0
  158. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_hook_ctx_binding.py +0 -0
  159. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_hookz_empty_phase.py +0 -0
  160. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_hybrid_session_run_sync.py +0 -0
  161. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_in_tx.py +0 -0
  162. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_include_model_columns_namespace.py +0 -0
  163. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_include_models_base_prefix.py +0 -0
  164. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_initialize_async_task.py +0 -0
  165. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_initialize_cross_ddl.py +0 -0
  166. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_initialize_mixed_engines.py +0 -0
  167. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_initialize_task_schedule.py +0 -0
  168. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_io_spec_attributes.py +0 -0
  169. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_iospec_attributes.py +0 -0
  170. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_iospec_effects.py +0 -0
  171. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_jsonrpc_id_example.py +0 -0
  172. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_jsonrpc_router_default_tag.py +0 -0
  173. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_kernel_invoke_ctx.py +0 -0
  174. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_kernel_opview_on_demand.py +0 -0
  175. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_kernel_plan_labels.py +0 -0
  176. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_kernelz_endpoint.py +0 -0
  177. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_make_column_shortcuts.py +0 -0
  178. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_mixins_sqlalchemy.py +0 -0
  179. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_alias.py +0 -0
  180. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_class_engine_binding.py +0 -0
  181. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_ctx_arity_paths.py +0 -0
  182. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_ctx_attributes.py +0 -0
  183. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_ctx_core_crud_integration.py +0 -0
  184. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_ctx_dynamic_attach.py +0 -0
  185. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_op_ctx_persist_options.py +0 -0
  186. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_opspec_effects.py +0 -0
  187. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_postgres_engine_errors.py +0 -0
  188. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_postgres_env_vars.py +0 -0
  189. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_relationship_alias_cols.py +0 -0
  190. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_request_body_schema.py +0 -0
  191. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_request_response_examples.py +0 -0
  192. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_resolver_interning_warmup.py +0 -0
  193. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_resolver_precedence.py +0 -0
  194. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_alias_table_rpc.py +0 -0
  195. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_ctx_precedence.py +0 -0
  196. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_diagnostics_kernelz.py +0 -0
  197. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_html_jinja_behavior.py +0 -0
  198. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_parity.py +0 -0
  199. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_rest.py +0 -0
  200. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_rpc.py +0 -0
  201. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_template.py +0 -0
  202. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_response_uuid.py +0 -0
  203. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_all_default_op_verbs.py +0 -0
  204. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_bulk_delete_suppresses_clear.py +0 -0
  205. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_no_schema_jsonable.py +0 -0
  206. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_operation_id_uniqueness.py +0 -0
  207. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_rpc_parity_default_ops.py +0 -0
  208. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_rpc_prefixes.py +0 -0
  209. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rest_rpc_symmetry.py +0 -0
  210. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_router_compatibility_shims.py +0 -0
  211. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rpc_all_default_op_verbs.py +0 -0
  212. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_rpc_default_ops.py +0 -0
  213. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_schema_ctx_attributes.py +0 -0
  214. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_schema_ctx_plain_class.py +0 -0
  215. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_schema_spec_presence.py +0 -0
  216. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_schemas_binding.py +0 -0
  217. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_security_per_route.py +0 -0
  218. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_should_wire_canonical.py +0 -0
  219. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_api.py +0 -0
  220. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_app.py +0 -0
  221. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_column.py +0 -0
  222. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_engine.py +0 -0
  223. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_field.py +0 -0
  224. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_hook.py +0 -0
  225. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_io.py +0 -0
  226. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_op.py +0 -0
  227. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_storage.py +0 -0
  228. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_spec_table.py +0 -0
  229. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sqlite_attachments.py +0 -0
  230. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sqlite_uuid_type.py +0 -0
  231. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_stdapi_request_injection.py +0 -0
  232. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_stdapi_transport_asgi_wsgi.py +0 -0
  233. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_storage_spec_attributes.py +0 -0
  234. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sys_handler_crud.py +0 -0
  235. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sys_run_rollback.py +0 -0
  236. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sys_tx_async_begin.py +0 -0
  237. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sys_tx_begin.py +0 -0
  238. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_sys_tx_commit.py +0 -0
  239. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_table_base_exports.py +0 -0
  240. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_table_collect_spec.py +0 -0
  241. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_table_columns_namespace.py +0 -0
  242. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_table_namespace_init.py +0 -0
  243. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_table_namespace_isolation.py +0 -0
  244. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_api_app_configuration.py +0 -0
  245. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_api_app_instantiation.py +0 -0
  246. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_api_app_subclass_definition.py +0 -0
  247. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_api_instantiation.py +0 -0
  248. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_api_subclass_definition.py +0 -0
  249. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_app_configuration.py +0 -0
  250. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_app_event_handlers.py +0 -0
  251. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_app_instantiation.py +0 -0
  252. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_tigrbl_app_subclass_definition.py +0 -0
  253. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_v3_healthz_endpoint.py +0 -0
  254. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_v3_op_alias.py +0 -0
  255. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_v3_op_ctx_attributes.py +0 -0
  256. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_v3_schemas_and_decorators.py +0 -0
  257. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/tests/unit/test_v3_storage_spec_attributes.py +0 -0
  258. {tigrbl_tests-0.3.8.dev1 → tigrbl_tests-0.3.9}/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
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"
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)
@@ -22,11 +22,11 @@ async def test_lens_mountable_on_tigrbl_app_uvicorn():
22
22
  assert 'id="root"' in response.text
23
23
  assert 'type="importmap"' in response.text
24
24
  assert (
25
- '"@tigrbljs/tigrbl-lens": "https://esm.sh/@tigrbljs/tigrbl-lens@0.0.14"'
25
+ '"@tigrbljs/tigrbl-lens": "https://esm.sh/@tigrbljs/tigrbl-lens@latest"'
26
26
  in response.text
27
27
  )
28
28
  assert (
29
- 'href="https://esm.sh/@tigrbljs/tigrbl-lens@0.0.14/dist/tigrbl-lens.css"'
29
+ 'href="https://esm.sh/@tigrbljs/tigrbl-lens@latest/dist/tigrbl-lens.css"'
30
30
  in response.text
31
31
  )
32
32
  assert 'import { createRoot } from "react-dom/client";' in response.text
@@ -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"
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from tigrbl.requests import Request
4
+
5
+
6
+ def test_request_accepts_scope_with_receive_kwarg_for_compatibility() -> None:
7
+ scope = {
8
+ "type": "http",
9
+ "method": "post",
10
+ "path": "/rpc",
11
+ "root_path": "/api",
12
+ "headers": [(b"authorization", b"Bearer test-token")],
13
+ "query_string": b"a=1&a=2&b=3",
14
+ "path_params": {"tenant_id": "abc"},
15
+ "client": ("198.51.100.77", 443),
16
+ }
17
+
18
+ async def _receive() -> dict[str, object]:
19
+ return {"type": "http.request", "body": b"", "more_body": False}
20
+
21
+ request = Request(scope, receive=_receive)
22
+
23
+ assert request.method == "POST"
24
+ assert request.path == "/rpc"
25
+ assert str(request.url) == "/api/rpc?a=1&a=2&b=3"
26
+ assert request.query == {"a": ["1", "2"], "b": ["3"]}
27
+ assert request.path_params == {"tenant_id": "abc"}
28
+ assert request.headers.authorization == "Bearer test-token"
29
+ assert request.bearer_token == "test-token"
30
+ assert request.client.ip == "198.51.100.77"
31
+
32
+
33
+ def test_request_rejects_duplicated_scope_inputs() -> None:
34
+ scope = {"type": "http", "method": "GET", "path": "/"}
35
+
36
+ try:
37
+ Request(scope, scope=scope)
38
+ except TypeError as exc:
39
+ assert "scope cannot be provided" in str(exc)
40
+ else: # pragma: no cover - explicit failure branch
41
+ raise AssertionError("expected Request to reject duplicate scope inputs")
@@ -21,19 +21,31 @@ 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()
28
28
  assert payload["openrpc"] == "1.2.6"
29
+ assert payload["servers"] == [{"name": app.title, "url": "/rpc"}]
29
30
  assert "methods" in payload
30
31
 
31
32
 
33
+ def test_openrpc_server_url_respects_jsonrpc_prefix():
34
+ app, _ = _build_app()
35
+ app.jsonrpc_prefix = "/rpc/custom"
36
+ transport = ASGITransport(app=app)
37
+
38
+ with Client(transport=transport, base_url="http://test") as client:
39
+ payload = client.get("/openrpc.json").json()
40
+
41
+ assert payload["servers"] == [{"name": app.title, "url": "/rpc/custom"}]
42
+
43
+
32
44
  def test_openrpc_includes_method_schema():
33
45
  app, model = _build_app()
34
46
  transport = ASGITransport(app=app)
35
47
  with Client(transport=transport, base_url="http://test") as client:
36
- payload = client.get("/rpc/openrpc.json").json()
48
+ payload = client.get("/openrpc.json").json()
37
49
  methods = {method["name"]: method for method in payload["methods"]}
38
50
 
39
51
  create_method = methods[f"{model.__name__}.create"]
@@ -0,0 +1,266 @@
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
+ )
207
+
208
+
209
+ @pytest.mark.asyncio
210
+ async def test_cors_middleware_accepts_list_origins_and_regex() -> None:
211
+ async def endpoint(scope, receive, send):
212
+ del scope, receive
213
+ await send(
214
+ {
215
+ "type": "http.response.start",
216
+ "status": 200,
217
+ "headers": [],
218
+ }
219
+ )
220
+ await send({"type": "http.response.body", "body": b"ok", "more_body": False})
221
+
222
+ app = CORSMiddleware(
223
+ endpoint,
224
+ allow_origins=["https://allowed.example", "https://other.example"],
225
+ allow_origin_regex=r"https://.*\.trusted\.example",
226
+ allow_methods=["GET", "POST"],
227
+ allow_headers=["x-custom", "authorization"],
228
+ )
229
+
230
+ allowed = await _run_asgi(
231
+ app,
232
+ {
233
+ "type": "http",
234
+ "method": "GET",
235
+ "path": "/",
236
+ "query_string": b"",
237
+ "headers": [(b"origin", b"https://allowed.example")],
238
+ },
239
+ )
240
+ allowed_headers = {
241
+ k.decode("latin-1"): v.decode("latin-1")
242
+ for k, v in next(m for m in allowed if m["type"] == "http.response.start")[
243
+ "headers"
244
+ ]
245
+ }
246
+ assert allowed_headers["access-control-allow-origin"] == "https://allowed.example"
247
+ assert allowed_headers["access-control-allow-methods"] == "GET,POST"
248
+ assert allowed_headers["access-control-allow-headers"] == "x-custom,authorization"
249
+
250
+ regex_allowed = await _run_asgi(
251
+ app,
252
+ {
253
+ "type": "http",
254
+ "method": "GET",
255
+ "path": "/",
256
+ "query_string": b"",
257
+ "headers": [(b"origin", b"https://api.trusted.example")],
258
+ },
259
+ )
260
+ regex_headers = {
261
+ k.decode("latin-1"): v.decode("latin-1")
262
+ for k, v in next(
263
+ m for m in regex_allowed if m["type"] == "http.response.start"
264
+ )["headers"]
265
+ }
266
+ assert regex_headers["access-control-allow-origin"] == "https://api.trusted.example"
@@ -0,0 +1,122 @@
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 openrpc_doc["servers"] == [{"name": app.title, "url": "/rpc"}]
23
+ assert "swagger-ui" in swagger_html
24
+ assert "tigrbl-lens" in lens_html
25
+
26
+
27
+ @pytest.mark.unit
28
+ def test_openrpc_builder_uses_request_origin_and_jsonrpc_prefix() -> None:
29
+ app = TigrblApp(jsonrpc_prefix="/gateway/rpc")
30
+ request = Request(
31
+ method="GET",
32
+ path="/openrpc.json",
33
+ headers={"host": "api.example.com", "x-forwarded-proto": "https"},
34
+ query={},
35
+ path_params={},
36
+ body=b"",
37
+ )
38
+
39
+ openrpc_doc = build_openrpc_spec(app, request=request)
40
+
41
+ assert openrpc_doc["servers"] == [
42
+ {"name": app.title, "url": "https://api.example.com/gateway/rpc"}
43
+ ]
44
+
45
+
46
+ @pytest.mark.unit
47
+ def test_openrpc_builder_normalizes_forwarded_host_header_values() -> None:
48
+ app = TigrblApp(jsonrpc_prefix="/gateway/rpc")
49
+ request = Request(
50
+ method="GET",
51
+ path="/openrpc.json",
52
+ headers={
53
+ "x-forwarded-host": "api.example.com,edge.proxy.internal",
54
+ "x-forwarded-proto": "https, http",
55
+ },
56
+ query={},
57
+ path_params={},
58
+ body=b"",
59
+ )
60
+
61
+ openrpc_doc = build_openrpc_spec(app, request=request)
62
+
63
+ assert openrpc_doc["servers"] == [
64
+ {"name": app.title, "url": "https://api.example.com/gateway/rpc"}
65
+ ]
66
+
67
+
68
+ @pytest.mark.unit
69
+ def test_openrpc_builder_prefers_request_scheme_without_forwarded_proto() -> None:
70
+ app = TigrblApp(jsonrpc_prefix="/gateway/rpc")
71
+ request = Request(
72
+ method="GET",
73
+ path="/openrpc.json",
74
+ headers={"x-forwarded-host": "api.example.com"},
75
+ query={},
76
+ path_params={},
77
+ body=b"",
78
+ scope={"scheme": "https"},
79
+ )
80
+
81
+ openrpc_doc = build_openrpc_spec(app, request=request)
82
+
83
+ assert openrpc_doc["servers"] == [
84
+ {"name": app.title, "url": "https://api.example.com/gateway/rpc"}
85
+ ]
86
+
87
+
88
+ @pytest.mark.unit
89
+ def test_openrpc_builder_prefers_forwarded_proto_over_request_scheme() -> None:
90
+ app = TigrblApp(jsonrpc_prefix="/gateway/rpc")
91
+ request = Request(
92
+ method="GET",
93
+ path="/openrpc.json",
94
+ headers={"host": "api.example.com", "x-forwarded-proto": "https"},
95
+ query={},
96
+ path_params={},
97
+ body=b"",
98
+ scope={"scheme": "http"},
99
+ )
100
+
101
+ openrpc_doc = build_openrpc_spec(app, request=request)
102
+
103
+ assert openrpc_doc["servers"] == [
104
+ {"name": app.title, "url": "https://api.example.com/gateway/rpc"}
105
+ ]
106
+
107
+
108
+ @pytest.mark.unit
109
+ def test_openrpc_builder_keeps_relative_server_url_without_forwarding_headers() -> None:
110
+ app = TigrblApp(jsonrpc_prefix="/gateway/rpc")
111
+ request = Request(
112
+ method="GET",
113
+ path="/openrpc.json",
114
+ headers={"host": "api.example.com"},
115
+ query={},
116
+ path_params={},
117
+ body=b"",
118
+ )
119
+
120
+ openrpc_doc = build_openrpc_spec(app, request=request)
121
+
122
+ assert openrpc_doc["servers"] == [{"name": app.title, "url": "/gateway/rpc"}]
@@ -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"