๐ฏ Tracking: contract test coverage for WPGraphQL actions and filters
#3,929 ๅๅปบไบ 2026ๅนด6ๆ12ๆฅ
ไปๅบๆๆ
- ๆๆ
- ย (3,780 ไธชๆๆ )
- PR ๅๅนถๆๆ
- ย (ๅนณๅๅๅนถ 2ๅคฉ 12ๅฐๆถ)ย (30 ๅคฉๅ ๅๅนถ 114 ไธช PR)
ๆ่ฟฐ
Context
WPGraphQL core defines roughly 219 public hooks (apply_filters / do_action). These are public API, extensions and sites build on them, but most have no test coverage at all. A scan of the codebase (method below) found:
| Bucket | Count |
|---|---|
| Hooks defined in core | 219 |
Hook name referenced somewhere in tests/ |
59 |
| No test references at all | 154 |
| Dynamic hook names (need manual verification) | 6 |
The caveat on the 59: "referenced in tests" only means the hook name appears somewhere in the suite, sometimes that is a real contract test, sometimes it is incidental usage in another test. The 154 are definitive, nothing in the suite touches them.
This matters more with a 3.0 on the horizon. Hooks are where breaking changes hide. A contract test per hook means CI tells us when a filter or action changes behavior, signature, or timing, so we can document it properly instead of finding out from extension authors after release.
This consolidates #330 and #331 (both from 2017). Since those were opened, every hook in the codebase has gained a docblock (enforced by static analysis), and wpgraphql.com publishes action and filter references. Worth being transparent about the state of those reference pages though: they are manually maintained in WordPress (partly as a dogfooding exercise, WordPress is the CMS for wpgraphql.com), there is no automation generating them from the codebase yet, and the content has drifted compared to the code. Automating that generation from the docblocks has been explored locally and deserves its own issue, it is not in scope here. The test coverage goal of #330/#331 is this issue. The naming/consistency audit half of #331 is also out of scope, that is breaking-change territory and should be proposed per-hook against a major release.
Definition of done (per hook)
A wpunit test that hooks the filter/action and asserts the contract: the hook fires when expected, receives the documented args, and (for filters) the returned value affects behavior. See tests/wpunit/FiltersTest.php for the existing pattern. Checking an item off means a dedicated test exists, not just incidental usage.
How the scan was produced
cd plugins/wp-graphql
grep -rhoE "(do_action|apply_filters)\(\s*['\"][^'\"]+['\"]" src/ access-functions.php | \
sed -E "s/\(\s*/|/; s/['\"]//g" | sort -u | grep -E "\|graph(i)?ql"
# then check each name for presence under tests/
Re-run it after chipping away to refresh the lists.
Untested hooks by subsystem (154)
plugin root (access-functions.php, activation.php, deactivation.php)
-
graphql_get_setting_section_field_value(filter) โaccess-functions.php:882 -
graphql_get_setting_section_fields(filter) โaccess-functions.php:863 -
graphql_activate(action) โactivation.php:6 -
graphql_deactivate(action) โdeactivation.php:16 -
graphql_delete_data(action) โdeactivation.php:57
src/Admin/Extensions
-
graphql_get_extensions(filter) โsrc/Admin/Extensions/Extensions.php:402
src/Admin/GraphiQL
-
graphiql_external_fragments(filter) โsrc/Admin/GraphiQL/GraphiQL.php:302 -
graphql_render_admin_page(filter) โsrc/Admin/GraphiQL/GraphiQL.php:266
src/Admin/Settings
-
graphql_setting_field_config(filter) โsrc/Admin/Settings/SettingsRegistry.php:147 -
graphql_settings_sections(filter) โsrc/Admin/Settings/SettingsRegistry.php:185 -
graphql_init_settings(action) โsrc/Admin/Settings/SettingsRegistry.php:178 -
graphql_settings_enqueue_scripts(action) โsrc/Admin/Settings/SettingsRegistry.php:82 -
graphql_settings_form_bottom(action) โsrc/Admin/Settings/SettingsRegistry.php:718 -
graphql_settings_form_top(action) โsrc/Admin/Settings/SettingsRegistry.php:715
src/Admin
-
graphql_add_admin_notice(filter) โsrc/Admin/AdminNotices.php:161 -
graphql_admin_notices_is_allowed_admin_page(filter) โsrc/Admin/AdminNotices.php:387 -
graphql_enable_graphiql(filter) โsrc/Admin/Admin.php:50 -
graphql_show_admin(filter) โsrc/Admin/Admin.php:49 -
graphql_admin_notices_init(action) โsrc/Admin/AdminNotices.php:99 -
graphql_admin_notices_render_notice(action) โsrc/Admin/AdminNotices.php:309 -
graphql_admin_notices_render_notices(action) โsrc/Admin/AdminNotices.php:309
src/Data/Connection
-
graphql_comment_connection_args(filter) โsrc/Data/Connection/CommentConnectionResolver.php:238 -
graphql_comment_connection_query_args(filter) โsrc/Data/Connection/CommentConnectionResolver.php:135 -
graphql_connection_amount_requested(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:629 -
graphql_connection_args(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:225 -
graphql_connection_edge(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:1014 -
graphql_connection_edges(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:1014 -
graphql_connection_ids(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:1102 -
graphql_connection_loader_name(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:559 -
graphql_connection_nodes(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:1004 -
graphql_connection_page_info(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:812 -
graphql_connection_pre_get_query(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:716 -
graphql_connection_pre_should_execute(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:887 -
graphql_connection_query_class(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:683 -
graphql_connection_should_execute(filter) โsrc/Data/Connection/AbstractConnectionResolver.php:1065 -
graphql_map_input_fields_to_get_terms(filter) โsrc/Data/Connection/TermObjectConnectionResolver.php:234 -
graphql_map_input_fields_to_wp_comment_query(filter) โsrc/Data/Connection/CommentConnectionResolver.php:294 -
graphql_menu_item_connection_args(filter) โsrc/Data/Connection/MenuItemConnectionResolver.php:107 -
graphql_post_object_connection_args(filter) โsrc/Data/Connection/PostObjectConnectionResolver.php:588 -
graphql_term_object_connection_args(filter) โsrc/Data/Connection/TermObjectConnectionResolver.php:282 -
graphql_term_object_connection_query_args(filter) โsrc/Data/Connection/TermObjectConnectionResolver.php:140
src/Data/Cursor
-
graphql_post_object_cursor_meta_key(filter) โsrc/Data/Cursor/PostObjectCursor.php:250 -
graphql_pre_comment_cursor_node(filter) โsrc/Data/Cursor/CommentObjectCursor.php:64 -
graphql_pre_post_cursor_node(filter) โsrc/Data/Cursor/PostObjectCursor.php:66 -
graphql_pre_term_cursor_node(filter) โsrc/Data/Cursor/TermObjectCursor.php:48 -
graphql_pre_user_cursor_node(filter) โsrc/Data/Cursor/UserCursor.php:83
src/Data/Loader
-
graphql_dataloader_get_model(filter) โsrc/Data/Loader/AbstractDataLoader.php:377 -
graphql_dataloader_pre_get_model(filter) โsrc/Data/Loader/AbstractDataLoader.php:354
src/Data
-
graphql_allowed_hosts(filter) โsrc/Data/NodeResolver.php:386 -
graphql_allowed_settings_by_group(filter) โsrc/Data/DataSource.php:388 -
graphql_comment_insert_post_args(filter) โsrc/Data/CommentMutation.php:110 -
graphql_media_item_insert_post_args(filter) โsrc/Data/MediaItemMutation.php:103 -
graphql_post_object_insert_post_args(filter) โsrc/Data/PostObjectMutation.php:112 -
graphql_post_object_mutation_set_edit_lock(filter) โsrc/Data/PostObjectMutation.php:150 -
graphql_post_object_mutations_allow_term_creation(filter) โsrc/Data/PostObjectMutation.php:282 -
graphql_post_resolve_uri(filter) โsrc/Data/NodeResolver.php:336 -
graphql_pre_resolve_uri(filter) โsrc/Data/NodeResolver.php:123 -
graphql_pre_wp_comments_query_cursor_pagination_support(filter) โsrc/Data/Config.php:465 -
graphql_pre_wp_query_cursor_pagination_stability(filter) โsrc/Data/Config.php:184 -
graphql_pre_wp_query_cursor_pagination_support(filter) โsrc/Data/Config.php:235 -
graphql_pre_wp_term_query_cursor_pagination_support(filter) โsrc/Data/Config.php:390 -
graphql_pre_wp_user_query_cursor_pagination_stability(filter) โsrc/Data/Config.php:287 -
graphql_pre_wp_user_query_cursor_pagination_support(filter) โsrc/Data/Config.php:338 -
graphql_resolve_node_type(filter) โsrc/Data/DataSource.php:552 -
graphql_resolve_uri_query_class(filter) โsrc/Data/NodeResolver.php:170 -
graphql_resolve_uri(filter) โsrc/Data/NodeResolver.php:170 -
graphql_term_object_insert_term_args(filter) โsrc/Data/TermObjectMutation.php:108 -
graphql_user_insert_post_args(filter) โsrc/Data/UserMutation.php:233 -
graphql_user_mutation_input_fields(filter) โsrc/Data/UserMutation.php:134 -
graphql_comment_object_mutation_update_additional_data(action) โsrc/Data/CommentMutation.php:134 -
graphql_media_item_mutation_update_additional_data(action) โsrc/Data/MediaItemMutation.php:141 -
graphql_post_object_mutation_set_object_terms(action) โsrc/Data/PostObjectMutation.php:241 -
graphql_user_object_mutation_update_additional_data(action) โsrc/Data/UserMutation.php:266
src/Experimental
-
graphql_experimental_features_cap(filter) โsrc/Experimental/ExperimentRegistry.php:112 -
graphql_experiments_loaded(action) โsrc/Experimental/ExperimentRegistry.php:326
src/Model
-
graphql_allowed_fields_on_restricted_type(filter) โsrc/Model/Model.php:321 -
graphql_html_entity_decoding_enabled(filter) โsrc/Model/Model.php:524 -
graphql_model_field_capability(filter) โsrc/Model/Model.php:450 -
graphql_model_prepare_fields(filter) โsrc/Model/Model.php:499 -
graphql_object_visibility(filter) โsrc/Model/Model.php:276 -
graphql_restricted_data_cap(filter) โsrc/Model/Model.php:215 -
graphql_return_field_from_model(filter) โsrc/Model/Model.php:420 -
graphql_after_return_field_from_model(action) โsrc/Model/Model.php:373
src/Mutation
-
graphql_media_item_create_allowed_protocols(filter) โsrc/Mutation/MediaItemCreate.php:230 -
graphql_post_object_create_default_post_status(filter) โsrc/Mutation/PostObjectCreate.php:289 -
graphql_post_object_create_should_set_intended_post_status(filter) โsrc/Mutation/PostObjectCreate.php:370 -
graphql_insert_post_object(action) โsrc/Mutation/PostObjectUpdate.php:197 -
graphql_insert_term(action) โsrc/Mutation/TermObjectCreate.php:197 -
graphql_update_term(action) โsrc/Mutation/TermObjectUpdate.php:165
src/Registry
-
graphql_excluded_connections(filter) โsrc/Registry/TypeRegistry.php:1840 -
graphql_excluded_mutations(filter) โsrc/Registry/TypeRegistry.php:1866 -
graphql_excluded_types(filter) โsrc/Registry/TypeRegistry.php:1813 -
graphql_introspection_keys(filter) โsrc/Registry/TypeRegistry.php:916 -
graphql_type_registry(filter) โsrc/Registry/TypeRegistry.php:286 -
graphql_register_initial_types(action) โsrc/Registry/TypeRegistry.php:305
src/Server/ValidationRules
-
graphql_pre_restrict_endpoint(filter) โsrc/Server/ValidationRules/RequireAuthentication.php:31 -
graphql_require_authentication_allowed_fields(filter) โsrc/Server/ValidationRules/RequireAuthentication.php:71
src/Type
-
graphql_custom_scalar_config(filter) โsrc/Type/WPScalar.php:51 -
graphql_interface_fields(filter) โsrc/Type/WPInterfaceType.php:117 -
graphql_interface_resolve_type(filter) โsrc/Type/WPInterfaceType.php:69 -
graphql_mutation_input(filter) โsrc/Type/WPMutationType.php:216 -
graphql_mutation_payload(filter) โsrc/Type/WPMutationType.php:246 -
graphql_pre_mutate_and_get_payload(filter) โsrc/Type/WPMutationType.php:230 -
graphql_type_name(filter) โsrc/Type/WPInterfaceType.php:50 -
graphql_union_possible_types(filter) โsrc/Type/WPUnionType.php:68 -
graphql_union_resolve_type(filter) โsrc/Type/WPUnionType.php:65 -
graphql_wp_interface_type_config(filter) โsrc/Type/WPInterfaceType.php:78 -
graphql_wp_mutation_type_config(filter) โsrc/Type/WPMutationType.php:88 -
graphql_wp_union_type_config(filter) โsrc/Type/WPUnionType.php:77 -
graphql_mutation_response(action) โsrc/Type/WPMutationType.php:259 -
graphql_wp_mutation_type(action) โsrc/Type/WPMutationType.php:88 -
graphql_wp_union_type(action) โsrc/Type/WPUnionType.php:77
src/Utils
-
graphql_cache_collection_get_list_types(filter) โsrc/Utils/QueryAnalyzer.php:527 -
graphql_cache_collection_get_query_models(filter) โsrc/Utils/QueryAnalyzer.php:698 -
graphql_cache_collection_get_query_types(filter) โsrc/Utils/QueryAnalyzer.php:610 -
graphql_debug_log_entry(filter) โsrc/Utils/DebugLog.php:101 -
graphql_debug_log(filter) โsrc/Utils/DebugLog.php:42 -
graphql_debug_logs_enabled(filter) โsrc/Utils/DebugLog.php:42 -
graphql_field_definition(filter) โsrc/Utils/InstrumentSchema.php:69 -
graphql_field_resolver_auth_error_message(filter) โsrc/Utils/InstrumentSchema.php:223 -
graphql_pre_query_analyzer_get_list_types(filter) โsrc/Utils/QueryAnalyzer.php:444 -
graphql_pre_query_analyzer_get_models(filter) โsrc/Utils/QueryAnalyzer.php:631 -
graphql_pre_query_analyzer_get_query_types(filter) โsrc/Utils/QueryAnalyzer.php:548 -
graphql_query_analyzer_get_headers(filter) โsrc/Utils/QueryAnalyzer.php:862 -
graphql_query_analyzer_get_runtime_nodes(filter) โsrc/Utils/QueryAnalyzer.php:329 -
graphql_query_analyzer_header_length_limit(filter) โsrc/Utils/QueryAnalyzer.php:241 -
graphql_query_analyzer_runtime_node(filter) โsrc/Utils/QueryAnalyzer.php:723 -
graphql_query_id_hash_algorithm(filter) โsrc/Utils/Utils.php:23 -
graphql_query_log_entry(filter) โsrc/Utils/QueryLog.php:202 -
graphql_should_analyze_queries(filter) โsrc/Utils/QueryAnalyzer.php:161 -
graphql_should_analyze_query(filter) โsrc/Utils/QueryAnalyzer.php:200 -
graphql_should_show_query_analyzer_in_extensions(filter) โsrc/Utils/QueryAnalyzer.php:887 -
graphql_tracing_response(filter) โsrc/Utils/QueryLog.php:164 -
graphql_user_can_see_query_logs(filter) โsrc/Utils/QueryLog.php:90 -
graphql_user_can_see_trace_data(filter) โsrc/Utils/Tracing.php:357 -
graphql_determine_graphql_keys(action) โsrc/Utils/QueryAnalyzer.php:298 -
graphql_get_debug_log(action) โsrc/Utils/DebugLog.php:119
src root (Router, Request, WPGraphQL, AppContext, WPSchema)
-
graphql_cookie_auth_require_nonce(filter) โsrc/Router.php:721 -
graphql_data_loader_classes(filter) โsrc/AppContext.php:197 -
graphql_invalid_content_type_status_code(filter) โsrc/Request.php:811 -
graphql_is_batch_queries_enabled(filter) โsrc/Request.php:875 -
graphql_is_graphql_http_request(filter) โsrc/Router.php:205 -
graphql_is_valid_http_content_type(filter) โsrc/Request.php:779 -
graphql_response_headers_to_send(filter) โsrc/Router.php:369 -
graphql_response_status_code(filter) โsrc/Router.php:622 -
graphql_schema_config(filter) โsrc/WPSchema.php:52 -
graphql_send_header(filter) โsrc/Router.php:271 -
graphql_validation_rules(filter) โsrc/Request.php:214 -
graphql_after_execute(action) โsrc/Request.php:443 -
graphql_do_update_routine(action) โsrc/WPGraphQL.php:498 -
graphql_execute_batch_queries(action) โsrc/Request.php:321 -
graphql_get_schema(action) โsrc/WPGraphQL.php:909 -
graphql_get_type_registry(action) โsrc/WPGraphQL.php:960 -
graphql_server_config(action) โsrc/Request.php:910
Dynamic hook names (6), verify per-variant coverage manually
-
graphql_insert_{$post_type_object->name}(action) โsrc/Data/PostObjectMutation.php -
graphql_insert_{$taxonomy->name}(action) โsrc/Mutation/TermObjectCreate.php -
graphql_update_{$taxonomy->name}(action) โsrc/Mutation/TermObjectUpdate.php -
graphql_{$lc_type_name}_fields(filter) โsrc/Type/WPInputObjectType.php -
graphql_{$uc_type_name}_fields(filter) โsrc/Type/WPInputObjectType.php -
graphql_wp_connection_{$this->from_type}_from_field_name(filter) โsrc/Type/WPConnectionType.php
Hooks with existing test references (59), verify these are real contract tests over time
graphql_(filter)graphql_allowed_setting_groups(filter)graphql_app_context_config(filter)graphql_authentication_error_status_code(filter)graphql_authentication_errors(filter)graphql_connection(filter)graphql_connection_default_query_amount(filter)graphql_connection_is_valid_model(filter)graphql_connection_max_query_amount(filter)graphql_connection_query(filter)graphql_connection_query_args(filter)graphql_data_is_private(filter)graphql_debug_enabled(filter)graphql_endpoint(filter)graphql_enum_values(filter)graphql_experimental_features_override(filter)graphql_experiments_registered_classes(filter)graphql_get_dependents(filter)graphql_get_possible_dependents(filter)graphql_get_type(filter)graphql_input_fields(filter)graphql_map_input_fields_to_wp_query(filter)graphql_map_input_fields_to_wp_user_query(filter)graphql_object_fields(filter)graphql_post_entities_allowed_post_types(filter)graphql_post_object_connection_query_args(filter)graphql_pre_format_name(filter)graphql_pre_is_graphql_http_request(filter)graphql_pre_model_data_is_private(filter)graphql_pre_resolve_field(filter)graphql_pre_resolve_menu_item_connected_node(filter)graphql_pre_return_field_from_model(filter)graphql_request_data(filter)graphql_request_results(filter)graphql_resolve_field(filter)graphql_resolve_revision_meta_from_parent(filter)graphql_root_value(filter)graphql_schema(filter)graphql_send_nocache_headers(filter)graphql_term_entities_allowed_taxonomies(filter)graphql_type_interfaces(filter)graphql_wp_connection_type_config(filter)graphql_wp_object_type_config(filter)graphql_after_resolve_field(action)graphql_before_execute(action)graphql_before_resolve_field(action)graphql_execute(action)graphql_experiments_registered(action)graphql_init(action)graphql_post_object_mutation_update_additional_data(action)graphql_process_http_request(action)graphql_process_http_request_response(action)graphql_register_settings(action)graphql_register_types(action)graphql_register_types_late(action)graphql_response_set_headers(action)graphql_return_response(action)graphql_wp_connection_type(action)graphql_wp_object_type(action)