tigrbl_tests 0.3.8__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.
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/PKG-INFO +1 -1
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/pyproject.toml +1 -1
- tigrbl_tests-0.3.9.dev1/tests/i9n/test_mountable_favicon_uvicorn.py +51 -0
- tigrbl_tests-0.3.9.dev1/tests/mount/test_mount_favico.py +114 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_jsonrpc_openrpc.py +2 -2
- tigrbl_tests-0.3.9.dev1/tests/unit/test_middleware_http_and_cors.py +206 -0
- tigrbl_tests-0.3.9.dev1/tests/unit/test_system_docs_builders.py +23 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_configuration.py +12 -0
- tigrbl_tests-0.3.9.dev1/tests/unit/test_tigrbl_app_state.py +20 -0
- tigrbl_tests-0.3.9.dev1/tests/unit/test_v3_favicon_endpoint.py +22 -0
- tigrbl_tests-0.3.8/tests/i9n/test_mountable_favicon_uvicorn.py +0 -41
- tigrbl_tests-0.3.8/tests/mount/test_mount_favico.py +0 -78
- tigrbl_tests-0.3.8/tests/unit/test_v3_favicon_endpoint.py +0 -17
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/LICENSE +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/README.md +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/__init__.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/conftest.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/hooks/test_hook_ctx_phase_validation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/__init__.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_acronym_route_name.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_allow_anon.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_apikey_generation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_authn_provider_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_bindings_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_bindings_modules.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_bulk_docs_client.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_core_access.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_engine_install_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_engine_resolver_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_error_mappings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_field_spec_effects.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_header_io_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_healthz_methodz_hookz.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_hook_ctx_v3_i9n.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_hook_lifecycle.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_iospec_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_iospec_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_jsonrpc_batch_behavior.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_key_digest_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_list_filters_optional.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mixins.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_lens_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_openapi_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_openrpc_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_mountable_swagger_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_nested_path_schema_and_rpc.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_nested_routing_depth.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_op_ctx_alias_examples.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_op_ctx_behavior.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_op_ctx_core_crud_order.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_openapi_clear_response_schema.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_openapi_schema_examples_presence.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_opspec_effects_i9n_test.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_owner_tenant_policy.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_request_extras.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_request_extras_provider.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_response_extras_provider.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_rest_fallback_serialization.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_rest_row_serialization.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_rest_rpc_parity_v3.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_row_result_serialization.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema_ctx_attributes_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema_ctx_op_ctx_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_schema_ctx_spec_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_sqlite_attachments.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_storage_spec_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_symmetry_parity.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_api_app_usage_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_api_usage_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_api_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_include_api_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_multi_api_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_usage_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_tigrbl_app_uvicorn.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_bulk_rest_endpoints.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_default_rest_ops.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_default_rpc_ops.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_v3_opspec_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/test_verb_alias_policy.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/i9n/uvicorn_utils.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/mount/__init__.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/parity/__init__.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/parity/test_stdapi_openapi_docs.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/parity/test_stdapi_primitives.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/parity/test_stdapi_routing.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/perf/__init__.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/perf/test_collect_caching.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/perf/test_hookz_performance.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/perf/test_methodz_performance.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/request/test_request_dot_notation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/request/test_request_response_conveniences.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/request/test_request_transport_convenience_dot_notation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/requests/test_request_json_modes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/response/test_response_dot_notation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/response/test_response_transport_convenience_dot_notation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/security/test_dot_notation_schemes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/security/test_schemes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/__init__.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_alias_ctx_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_engine_ctx_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_hook_ctx_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_op_alias_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_op_ctx_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_response_ctx_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/decorators/test_schema_ctx_bindings.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/response_utils.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_emit_paired_post.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_emit_paired_pre.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_emit_readtime_alias.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_out_masking.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_refresh_demand.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_resolve_assemble.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_resolve_paired_gen.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_schema_collect_in.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_schema_collect_out.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_storage_to_stored.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_build_in.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_build_out.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_dump.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/atoms/test_wire_validate_in.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/runtime/test_events_phases.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_acol_vcol_knobs.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_alias_ctx_op_alias_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_alias_ctx_op_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_api_level_set_auth.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_app_model_defaults.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_app_reexport.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_base_columnspec_inheritance.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_base_facade_initialize.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_build_list_params_spec_model.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_bulk_body_annotation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_bulk_response_schema.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_colspec_map_isolation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_column_collect_mixins.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_column_rest_rpc_results.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_column_table_orm_binding.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_config_dataclass_none.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_crud_bulk_ops.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_crud_default_ops.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_crud_methods.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_core_wrap_memoization.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_db_dependency.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_decorator_and_collect.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_default_tags.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_api.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_app.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_op.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_install_table.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_spec_and_shortcuts.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_engine_usage_levels.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_field_spec_attrs.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_file_response.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_get_schema.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_handler_step_qualname.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hook_ctx_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hook_ctx_binding.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hookz_empty_phase.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_hybrid_session_run_sync.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_in_tx.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_include_model_columns_namespace.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_include_models_base_prefix.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_async_task.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_cross_ddl.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_mixed_engines.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_initialize_task_schedule.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_io_spec_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_iospec_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_iospec_effects.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_jsonrpc_id_example.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_jsonrpc_router_default_tag.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernel_invoke_ctx.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernel_opview_on_demand.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernel_plan_labels.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_kernelz_endpoint.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_make_column_shortcuts.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_mixins_sqlalchemy.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_alias.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_class_engine_binding.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_arity_paths.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_core_crud_integration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_dynamic_attach.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_op_ctx_persist_options.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_opspec_effects.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_postgres_engine_errors.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_postgres_env_vars.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_relationship_alias_cols.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_request_body_schema.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_request_response_examples.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_resolver_interning_warmup.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_resolver_precedence.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_alias_table_rpc.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_ctx_precedence.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_diagnostics_kernelz.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_html_jinja_behavior.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_parity.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_rest.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_rpc.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_template.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_response_uuid.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_all_default_op_verbs.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_bulk_delete_suppresses_clear.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_no_schema_jsonable.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_operation_id_uniqueness.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_rpc_parity_default_ops.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_rpc_prefixes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rest_rpc_symmetry.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_router_compatibility_shims.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rpc_all_default_op_verbs.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_rpc_default_ops.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schema_ctx_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schema_ctx_plain_class.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schema_spec_presence.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_schemas_binding.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_security_per_route.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_should_wire_canonical.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_api.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_app.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_column.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_engine.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_field.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_hook.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_io.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_op.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_storage.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_spec_table.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sqlite_attachments.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sqlite_uuid_type.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_stdapi_request_injection.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_stdapi_transport_asgi_wsgi.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_storage_spec_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_handler_crud.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_run_rollback.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_tx_async_begin.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_tx_begin.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_sys_tx_commit.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_base_exports.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_collect_spec.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_columns_namespace.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_namespace_init.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_table_namespace_isolation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_app_configuration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_app_instantiation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_app_subclass_definition.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_instantiation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_api_subclass_definition.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_configuration.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_event_handlers.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_instantiation.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_tigrbl_app_subclass_definition.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_healthz_endpoint.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_op_alias.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_op_ctx_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_schemas_and_decorators.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_v3_storage_spec_attributes.py +0 -0
- {tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/unit/test_verbosity.py +0 -0
|
@@ -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("/
|
|
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("/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tigrbl_tests-0.3.8 → tigrbl_tests-0.3.9.dev1}/tests/hooks/test_hook_ctx_phase_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|