• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql-testcase / #252

14 May 2024 09:55PM UTC coverage: 79.076% (-1.3%) from 80.342%
#252

push

web-flow
Merge pull request #34 from wp-graphql/feat/wp-graphql-codeception-module

feat: WPGraphQL Codeception Module for E2E testing implemented and tested

19 of 38 new or added lines in 3 files covered. (50.0%)

2 existing lines in 2 files now uncovered.

291 of 368 relevant lines covered (79.08%)

3.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.0
/src/Constraint/QueryConstraint.php
1
<?php
2
/**
3
 * QueryConstraint interface
4
 *
5
 * Defines shared logic for QueryConstraint classes.
6
 * @since v3.0.0
7
 * @package Tests\WPGraphQL\Constraint
8
 */
9

10
namespace Tests\WPGraphQL\Constraint;
11

12
use PHPUnit\Framework\Exception;
13
use PHPUnit\Framework\Constraint\Constraint;
14
use Tests\WPGraphQL\Logger\CodeceptLogger;
15
use Tests\WPGraphQL\Logger\PHPUnitLogger;
16
use Tests\WPGraphQL\Utils\Utils;
17

18
class QueryConstraint extends Constraint {
19

20
    /**
21
     * Logger for logging debug messages.
22
     *
23
     * @var PHPUnitLogger|CodeceptLogger
24
     */
25
    protected $logger;
26

27
    /**
28
     * Stores the validation steps for the assertion.
29
     * 
30
     * @var array $validationRules 
31
     */
32
    protected $validationRules = [];
33

34
    /**
35
     * Stores subset of response data to be evaluated.
36
     *
37
     * @var mixed
38
     */
39
    private $actual = null;
40

41
        /**
42
         * Error message for assertion failure.
43
         *
44
         * @var string
45
         */
46
        protected $error_message = null;
47

48
    /**
49
     * List of reasons for assertion failure.
50
     *
51
     * @var string[]
52
     */
53
    protected $error_details = [];
54

55
    /**
56
     * Constructor
57
     *
58
     * @param array $expected  Expected validation rules.
59
     */
60
    public function __construct($logger, array $expected = []) {
61
        $this->logger = $logger;
22✔
62
        $this->validationRules = $expected;
22✔
63
    }
64

65
    /**
66
         * Reports an error identified by $message if $response is not a valid GraphQL response object.
67
         *
68
         * @param array  $response  GraphQL query response object.
69
         * @param string $message   References that outputs error message.
70
         *
71
         * @return bool
72
         */
73
        protected function responseIsValid( $response, &$message = null ) {
74
                if ( empty( $response ) ) {
19✔
NEW
75
            $this->error_details[] = 'GraphQL query response is invalid.';
×
76
            return false;
×
77
                }
78

79
                if ( array_keys( $response ) === range( 0, count( $response ) - 1 ) ) {
19✔
80
            $this->error_details[] = 'The GraphQL query response must be provided as an associative array.';
4✔
81
            return false;
4✔
82
                }
83

84
                if ( 0 === count( array_intersect( array_keys( $response ), [ 'data', 'errors' ] ) ) ) {
16✔
85
            $this->error_details[] = 'A valid GraphQL query response must contain a "data" or "errors" object.';
1✔
86
            return false;
1✔
87
                }
88

89
                return true;
15✔
90
        }
91

92
    /**
93
         * Evaluates the response "data" against a validation rule.
94
         *
95
         * @param array  $response       GraphQL query response object
96
         * @param array  $expected_data  Validation Rule.valid rule object provided for evaluation.
97
         *
98
         * @return bool
99
         */
100
        protected function expectedDataFound( array $response, array $expected_data, string $current_path = null ) {
101
                // Throw if "$expected_data" invalid.
102
                if ( empty( $expected_data['type'] ) ) {
7✔
103
                        $this->logger->logData( [ 'INVALID_DATA_OBJECT' => $expected_data ] );
×
NEW
104
                        $this->error_details[] = "Invalid rule object provided for evaluation: \n\t " . json_encode( $expected_data, JSON_PRETTY_PRINT );
×
105
                        return false;
×
106
                }
107

108
                // Deconstruct $expected_data.
109
                extract( $expected_data );
7✔
110

111
                // Get flags.
112
                $check_order = isset( $expected_index ) && ! is_null( $expected_index );
7✔
113

114
                // Set current path in response.
115
                if ( empty( $current_path ) ) {
7✔
116
                        $path = "data.{$path}";
7✔
117
                } else {
118
                        $path = "{$current_path}.{$path}";
4✔
119
                }
120

121
                // Add index to path if provided.
122
                $full_path = $check_order ? "{$path}.{$expected_index}" : "{$path}";
7✔
123

124
                // Get data at path for evaluation.
125
                $actual_data = $this->getPossibleDataAtPath( $response, $full_path, $is_group );
7✔
126

127
                // Set actual data for final assertion
128
                $this->actual = $actual_data;
7✔
129

130
                // Handle if "$expected_value" set to field value constants.
131
                $reverse             = str_starts_with( $type, '!' );
7✔
132
                $possible_constraint = is_array( $expected_value ) ? $expected_value : "{$expected_value}";
7✔
133
                switch( $possible_constraint ) {
134
                        case $this->logger::IS_NULL:
7✔
135
                                // Set "expected_value" to "null" for later comparison.
136
                                $expected_value = null;
3✔
137
                                break;
3✔
138

139
                        case $this->logger::NOT_FALSY:
7✔
140
                                // Fail if data found at path is a falsy value (null, false, []).
141
                                if ( empty( $actual_data ) && ! $reverse ) {
2✔
NEW
142
                                        $this->error_details[] = sprintf(
×
143
                        'Expected data at path "%s" not to be falsy value. "%s" Given',
×
144
                        $full_path,
×
145
                        is_array( $actual_data ) ? '[]' : (string) $actual_data
×
146
                    );
×
147

148
                                        return false;
×
149
                                } elseif ( ! empty( $actual_data ) && $reverse ) {
2✔
NEW
150
                                        $this->error_details[] = sprintf(
×
151
                                                'Expected data at path "%s" to be falsy value. "%s" Given',
×
152
                                                $full_path,
×
153
                                                is_array( $actual_data ) ? "\n\n" . json_encode( $actual_data, JSON_PRETTY_PRINT ) : $actual_data
×
154
                                        );
×
155

156
                                        return false;
×
157
                                }
158

159
                                // Return true because target value not falsy.
160
                                return true;
2✔
161

162
                        case $this->logger::IS_FALSY:
7✔
163
                                // Fail if data found at path is not falsy value (null, false, 0, []).
164
                                if ( ! empty( $actual_data ) && ! $reverse ) {
1✔
NEW
165
                                        $this->error_details[] = sprintf(
×
166
                                                'Expected data at path "%s" to be falsy value. "%s" Given',
×
167
                                                $full_path,
×
168
                                                is_array( $actual_data ) ? "\n\n" .json_encode( $actual_data, JSON_PRETTY_PRINT ) : $actual_data
×
169
                                        );
×
170

171
                                        return false;
×
172
                                } elseif ( empty( $actual_data ) && $reverse ) {
1✔
NEW
173
                                        $this->error_details[] = sprintf(
×
174
                                                'Expected data at path "%s" not to be falsy value. "%s" Given',
×
175
                                                $full_path,
×
176
                                                is_array( $actual_data ) ? "\n\n" .json_encode( $actual_data, JSON_PRETTY_PRINT ) : $actual_data
×
177
                                        );
×
178

179
                                        return false;
×
180
                                }
181

182
                                // Return true because target value is falsy.
183
                                return true;
1✔
184

185
                        case $this->logger::NOT_NULL:
7✔
186
                        default: // Check if "$expected_value" is not null if comparing to provided value.
187
                                // Fail if no data found at path.
188
                                if ( is_null( $actual_data ) && ! $reverse ) {
7✔
NEW
189
                                        $this->error_details[] = sprintf( 'No data found at path "%s"', $full_path );
×
190

191
                                        return false;
×
192
                                } elseif (
193
                                        ! is_null( $actual_data )
7✔
194
                                        && $reverse
195
                                        && $expected_value === $this->logger::NOT_NULL
7✔
196
                                ) {
NEW
197
                                        $this->error_details[] = sprintf( 'Unexpected data found at path "%s"', $full_path );
×
198

199
                                        return false;
×
200
                                }
201

202
                                // Return true because target value not null.
203
                                if ( $expected_value === $this->logger::NOT_NULL ) {
7✔
204
                                        return true;
2✔
205
                                }
206
                }
207

208
                $match_wanted   = ! str_starts_with( $type, '!' );
7✔
209
                $is_field_rule  = str_ends_with( $type, 'FIELD' );
7✔
210
                $is_object_rule = str_ends_with( $type, 'OBJECT' );
7✔
211
                $is_node_rule   = str_ends_with( $type, 'NODE' );
7✔
212
                $is_edge_rule   = str_ends_with( $type, 'EDGE' );
7✔
213

214
                // Set matcher and constraint.
215
                $matcher                 = ( ( $is_group && $is_field_rule ) || ( ! $check_order && ! $is_field_rule ) )
7✔
216
                        ? 'doesFieldMatchGroup'
6✔
217
                        : 'doesFieldMatch';
5✔
218

219
                // Evaluate rule by type.
220
                switch( true ) {
221
                        case $is_field_rule:
7✔
222
                                // Fail if matcher fails
223
                                if ( ! $this->{$matcher}( $actual_data, $expected_value, $match_wanted, $path ) ) {
7✔
224
                                        $this->error_details[] = sprintf(
1✔
225
                        'Data found at path "%1$s" %2$s the provided value',
1✔
226
                        $path,
1✔
227
                        $match_wanted ? 'doesn\'t match' : 'shouldn\'t match'
1✔
228
                    );
1✔
229

230
                                        return false;
1✔
231
                                }
232

233
                                // Pass if matcher passes.
234
                                return true;
6✔
235
                        case $is_object_rule:
4✔
236
                        case $is_node_rule:
4✔
237
                        case $is_edge_rule:
1✔
238
                                // Handle nested rules recursively.
239
                                if ( is_array( $expected_value ) && $this->isNested( $expected_value ) ) {
4✔
240
                                        foreach ( $expected_value as $nested_rule ) {
4✔
241
                                                $next_path           = ( $check_order || $is_object_rule ) ? $full_path : "{$full_path}.#";
4✔
242
                                                $next_path          .= $is_edge_rule ? '.node' : '';
4✔
243
                                                $nested_rule_passing = $this->expectedDataFound( $response, $nested_rule, $next_path );
4✔
244

245
                                                if ( ! $nested_rule_passing ) {
4✔
NEW
246
                                                        $this->error_details[] = sprintf(
×
NEW
247
                                                                "Data found at path \"%1\$s\" %2\$s fails the following rules: \n\t\t %3\$s",
×
NEW
248
                                                                $next_path,
×
NEW
249
                                                                $match_wanted ? 'doesn\'t match' : 'shouldn\'t match',
×
NEW
250
                                                                \json_encode( $nested_rule, JSON_PRETTY_PRINT )
×
NEW
251
                                                        );
×
UNCOV
252
                                                        return false;
×
253
                                                }
254
                                        }
255
                                        return true;
4✔
256
                                }
257

258
                                // Fail if matcher fails.
259
                                if ( ! $this->{$matcher}( $actual_data, $expected_value, $match_wanted, $path ) ) {
1✔
260
                                        if ( $check_order ) {
×
NEW
261
                                                $this->error_details[] = sprintf(
×
262
                            'Data found at path "%1$s" %2$s the provided value',
×
263
                            $full_path,
×
264
                            $match_wanted ? 'doesn\'t match' : 'shouldn\'t match'
×
265
                        );
×
266
                                        } else {
NEW
267
                                                $this->error_details[] = sprintf(
×
268
                            '%1$s found in %2$s list at path "%3$s"',
×
269
                            $match_wanted ? 'Unexpected data ' : 'Expected data not ',
×
270
                            strtolower( $type ),
×
271
                            $full_path
×
272
                        );
×
273
                                        }
274

275
                                        return false;
×
276
                                }
277

278
                                // Pass if matcher passes.
279
                                return true;
1✔
280
                        default:
281
                                $this->logger->logData( ['INVALID_DATA_OBJECT', $expected_data ] );
×
NEW
282
                                $this->error_details[] = "Invalid data object provided for evaluation. \n\t" . json_encode( $expected_data, JSON_PRETTY_PRINT );
×
283
                                return false;
×
284
                }
285
        }
286

287
    /**
288
         * Evaluates the response "errors" against a validation rule.
289
         *
290
         * @param array  $response       GraphQL query response object
291
         * @param array  $expected_data  Expected data object to be evaluated.
292
         * @param string $message        Error message.
293
     * 
294
     * @throws Exception Invalid data object provided for evaluation.
295
     * 
296
     * @return bool
297
         */
298
        protected function expectedErrorFound( array $response, array $expected_data ) {
299
                $search_type_messages = [
3✔
300
                        $this->logger::MESSAGE_EQUALS      => 'equals',
3✔
301
                        $this->logger::MESSAGE_CONTAINS    => 'contains',
3✔
302
                        $this->logger::MESSAGE_STARTS_WITH => 'starts with',
3✔
303
                        $this->logger::MESSAGE_ENDS_WITH   => 'ends with',
3✔
304
                ];
3✔
305

306
                // Deconstruct $expected_data.
307
                extract( $expected_data );
3✔
308

309
                switch( $type ) {
310
                        case 'ERROR_PATH':
3✔
311
                                $target_path = array_map(
3✔
312
                                        function( $v ) {
3✔
313
                                                return is_numeric( $v ) ? absint( $v ) : $v;
3✔
314
                                        },
3✔
315
                                        explode( '.', $path )
3✔
316
                                );
3✔
317

318
                                // Set constraint.
319
                                $this->actual = $this->getPossibleDataAtPath( $response['errors'], '.#.path' );
3✔
320
                                $this->logger->logData(
3✔
321
                                        [
3✔
322
                                                'TARGET_ERROR_PATH' => $target_path,
3✔
323
                                                'POSSIBLE_ERRORS'   => $this->actual,
3✔
324
                                        ]
3✔
325
                                );
3✔
326
                                foreach ( $response['errors'] as $error ) {
3✔
327
                                        if ( empty( $error['path'] ) ) {
3✔
328
                                                continue;
×
329
                                        }
330

331
                                        // Pass if match found.
332
                                        // TODO: Add support for group path searches using "#" for index.
333
                                        if ( $target_path === $error['path'] ) {
3✔
334
                                                $this->logger->logData(
2✔
335
                                                        [
2✔
336
                                                                'TARGET_ERROR_PATH' => $target_path,
2✔
337
                                                                'CURRENT_PATH'      => $error['path'],
2✔
338
                                                        ]
2✔
339
                                                );
2✔
340
                                                return true;
2✔
341
                                        }
342
                                }
343

344
                                // Fail if no match found.
345
                                $this->error_details[] = sprintf( 'No errors found that occured at path "%1$s"', $path );
1✔
346
                                return false;
1✔
347
                        case 'ERROR_MESSAGE':
3✔
348
                                $this->logger->logData(
3✔
349
                                        [
3✔
350
                                                'TARGET_ERROR_MESSAGE' => $needle,
3✔
351
                                                'SEARCH_TYPE'          => $search_type_messages[ $search_type ],
3✔
352
                                                'POSSIBLE_ERRORS'      => $response['errors'],
3✔
353
                                        ]
3✔
354
                                );
3✔
355
                                foreach ( $response['errors'] as $error ) {
3✔
356
                                        // Set constraint.
357
                                        $this->actual = $this->getPossibleDataAtPath( $response['errors'], '.#.message' );
3✔
358
                                        
359
                                        if ( empty( $error['message'] ) ) {
3✔
360
                                                continue;
×
361
                                        }
362

363
                                        // Pass if match found.
364
                                        $this->logger->logData(
3✔
365
                                                [
3✔
366
                                                        'TARGET_ERROR_MESSAGE'  => $needle,
3✔
367
                                                        'SEARCH_TYPE'           => $search_type_messages[ $search_type ],
3✔
368
                                                        'CURRENT_ERROR_MESSAGE' => $error['message'],
3✔
369
                                                ]
3✔
370
                                        );
3✔
371
                                        if ( $this->findSubstring( $error['message'], $needle, $search_type ) ) {
3✔
372
                                                return true;
2✔
373
                                        }
374
                                }
375

376
                                // Fail if no match found.
377
                                $this->error_details[] = sprintf(
1✔
378
                    'No errors found with a message that %1$s "%2$s"',
1✔
379
                    $search_type_messages[ $search_type ],
1✔
380
                    $needle
1✔
381
                );
1✔
382

383
                                return false;
1✔
384
                        default:
385
                                $this->logger->logData( ['INVALID_DATA_OBJECT', $expected_data ] );
1✔
386
                                $this->error_details[] = "Invalid data object provided for evaluation. \n\t" . json_encode( $expected_data, JSON_PRETTY_PRINT );
1✔
387
                                return false;
1✔
388
                }
389
        }
390

391
    /**
392
         * Returns array of possible values for paths where "#" is being used instead of numeric index
393
         * in $path.
394
         *
395
         * @param array $data        Data to be search
396
         * @param string $path       Formatted lodash path.
397
         * @param boolean $is_group  Function passback.
398
         *
399
         * @return mixed
400
         */
401
        protected function getPossibleDataAtPath( array $data, string $path, &$is_group = false ) {
402
                $branches = explode( '.#', $path );
7✔
403

404
                if ( 1 < count( $branches ) ) {
7✔
405
                        $is_group      = true;
7✔
406
                        $possible_data = $this->lodashGet( $data, $branches[0] );
7✔
407

408
                        // Loop throw top branches and build out the possible data options.
409
                        if ( ! empty( $possible_data ) && is_array( $possible_data ) ) {
7✔
410
                                foreach ( $possible_data as &$next_data ) {
6✔
411
                                        if ( ! is_array( $next_data ) ) {
6✔
412
                                                continue;
×
413
                                        }
414

415
                                        $next_data = $this->getPossibleDataAtPath(
6✔
416
                                                $next_data,
6✔
417
                                                ltrim( implode( '.#', array_slice( $branches, 1 ) ), '.' ),
6✔
418
                                                $is_group
6✔
419
                                        );
6✔
420
                                }
421
                        }
422

423
                        return $possible_data;
7✔
424
                }
425

426
                return $this->lodashGet( $data, $path, null );
7✔
427
        }
428

429
    /**
430
         * The value returned for undefined resolved values.
431
         *
432
         * Clone of the "get" function from the Lodash JS libra
433
         *
434
         * @param array  $object   The object to query.
435
         * @param string $path     The path of the property to get.
436
         * @param mixed  $default  The value returned for undefined resolved values.
437
         * 
438
         * @return mixed
439
         */
440
        protected function lodashGet( array $data, string $string, $default = null ) {
441
                return Utils::lodashGet( $data, $string, $default );
7✔
442
        }
443

444
    /**
445
         * Checks if the provided is a expected data rule object.
446
         *
447
         * @param array $expected_data
448
         *
449
         * @return bool
450
         */
451
        protected function isNested( array $expected_data ) {
452
                $rule_keys = [ 'type', 'path', 'expected_value' ];
4✔
453

454
                return ! empty( $expected_data[0] )
4✔
455
                        && is_array( $expected_data[0] )
4✔
456
                        && 3 === count( array_intersect( array_keys( $expected_data[0] ), $rule_keys ) );
4✔
457
        }
458

459
    /**
460
         * Asserts if $expected_value matches $data.
461
         *
462
         * @param array  $data            Data object be evaluted.
463
         * @param mixed  $expected_value  Value $data is expected to evalute to.
464
         * @param bool   $match_wanted    Whether $expected_value and $data should be equal or different.
465
         * @param string $path The path of the property to get.
466
         *
467
         * @return bool
468
         */
469
        protected function doesFieldMatch( $data, $expected_value, $match_wanted, $path ) {
470
                // Get data/value type and log assertion.
471
                $log_type   = is_array( $data ) ? 'ACTUAL_DATA_OBJECT' : 'ACTUAL_DATA';
7✔
472
                $value_type = $match_wanted ? 'WANTED_VALUE': 'UNWANTED_VALUE';
7✔
473
                $this->logger->logData(
7✔
474
                        array(
7✔
475
                                'PATH'      => $path,
7✔
476
                                $value_type => $expected_value,
7✔
477
                                $log_type   => $data,
7✔
478
                        )
7✔
479
                );
7✔
480

481
                // If match wanted, matching condition set other not matching condition is set.
482
                $condition = $match_wanted
7✔
483
                        ? $data === $expected_value
7✔
484
                        : $data !== $expected_value;
2✔
485

486
                // Return condtion.
487
                return $condition;
7✔
488
        }
489

490
        /**
491
         * Asserts if $expected_value matches one of the entries in $data.
492
         *
493
         * @param array  $data            Data object be evaluted.
494
         * @param mixed  $expected_value  Value $data is expected to evalute to.
495
         * @param bool   $match_wanted    Whether $expected_value and $data should be equal or different.
496
         * @param string $path The path of the property to get.
497
         *
498
         * @return bool
499
         */
500
        protected function doesFieldMatchGroup( $data, $expected_value, $match_wanted, $path ) {
501
                $item_type  = $match_wanted ? 'WANTED VALUE' : 'UNWANTED VALUE';
6✔
502

503
                // Log data objects before the coming assertion.
504
                $assertion_log = [
6✔
505
                        'PATH'               => $path,
6✔
506
                        $item_type           => $expected_value,
6✔
507
                        'VALUES_AT_LOCATION' => $data,
6✔
508
                ];
6✔
509
                $this->logger->logData( $assertion_log );
6✔
510

511
                if ( ! is_array( $data ) ) {
6✔
512
                        return $this->doesFieldMatch(
×
513
                                $data,
×
514
                                $expected_value,
×
515
                                $match_wanted,
×
516
                                $path
×
517
                        );
×
518
                }
519

520
                // Loop through possible node/edge values for the field.
521
                foreach ( $data as $item ) {
6✔
522
                        // Check if field value matches $expected_value.
523
                        $field_matches = $this->doesFieldMatch(
6✔
524
                                $item,
6✔
525
                                $expected_value,
6✔
526
                                $match_wanted,
6✔
527
                                $path
6✔
528
                        );
6✔
529

530
                        // Pass if match found and match wanted.
531
                        if ( $field_matches && $match_wanted ) {
6✔
532
                                return true;
4✔
533

534
                                // Fail if match found and no matches wanted.
535
                        } elseif ( ! $field_matches && ! $match_wanted ) {
4✔
536
                                return false;
×
537
                        }
538
                }
539

540
                // Fail if no matches found but matches wanted.
541
                if ( $match_wanted ) {
3✔
542
                        return false;
1✔
543
                }
544

545
                // Pass if no matches found and no matches wanted.
546
                return true;
2✔
547
        }
548

549
    /**
550
         * Processes substring searches
551
         *
552
         * @param string $needle       String being searched for.
553
         * @param string $haystack     String being searched.
554
         * @param int    $search_type  Search operation enumeration.
555
         *
556
         * @return boolean
557
         */
558
        protected function findSubstring( $haystack, $needle, $search_type ) {
559
                switch( $search_type ) {
560
                        case $this->logger::MESSAGE_EQUALS:
3✔
561
                                return $needle === $haystack;
1✔
562
                        case $this->logger::MESSAGE_CONTAINS:
3✔
563
                                return false !== strpos( $haystack, $needle );
3✔
564
                        case $this->logger::MESSAGE_STARTS_WITH:
1✔
565
                                return str_starts_with( $haystack, $needle );
1✔
566
                        case $this->logger::MESSAGE_ENDS_WITH:
1✔
567
                                return str_ends_with( $haystack, $needle );
1✔
568
                }
569
        }
570

571
    /**
572
     * Evaluates the response against the validation rules.
573
     *
574
     * @param array $response
575
     *
576
     * @throws Exception
577
     * 
578
     * @return boolean
579
     */
580
    public function matches($response): bool {
581
        // Ensure response is valid.
582
        if ( ! $this->responseIsValid( $response ) ) {
5✔
583
                        $this->error_message = 'GraphQL response is invalid';
2✔
584
            return false;
2✔
585
        }
586

587
        return true;
3✔
588
    }
589

590
    public function failureDescription($other): string {
591
                $output = '';
1✔
592

593
                if ( ! empty( $this->error_message ) ) {
1✔
594
                        $output .= $this->error_message;
1✔
595
                } else {
NEW
596
                        $output .= 'GraphQL response failed validation';
×
597
                }
598

599
                if ( ! empty( $this->error_details ) ) {
1✔
600
                        $output .= ": \n\n\t• " . implode( "\n\n\t• ", $this->error_details );
1✔
601
                }
602

603
                return $output;
1✔
604
    }
605

606
    /**
607
     * Returns a string representation of the constraint object.
608
     *
609
     * @return string
610
     */
611
    public function toString(): string {
612
        return 'is a valid WPGraphQL response';
1✔
613
    }
614
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc