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