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

Yoast / wordpress-seo / da4efbc5b07d9422360e8da09157e996aa6d4c8e

13 May 2026 09:45AM UTC coverage: 50.158%. First build
da4efbc5b07d9422360e8da09157e996aa6d4c8e

Pull #23265

github

web-flow
Merge c231415c9 into b17f347fb
Pull Request #23265: feat: authenticate yoast-ai requests with MyYoast OAuth tokens

129 of 246 new or added lines in 16 files covered. (52.44%)

20769 of 41407 relevant lines covered (50.16%)

4.0 hits per line

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

90.32
/src/ai/http-request/application/response-parser.php
1
<?php
2

3
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4

5
namespace Yoast\WP\SEO\AI\HTTP_Request\Application;
6

7
use Yoast\WP\SEO\AI\HTTP_Request\Domain\Response;
8

9
/**
10
 * Class Response_Parser
11
 * Parses the response from the AI API and creates a Response object.
12
 */
13
class Response_Parser implements Response_Parser_Interface {
14

15
        /**
16
         * Parses the response from the API.
17
         *
18
         * @param array<int|string|array<string>> $response The response from the API.
19
         *
20
         * @return Response The parsed response.
21
         */
22
        public function parse( $response ): Response {
20✔
23
                $response_code    = ( \wp_remote_retrieve_response_code( $response ) !== '' ) ? \wp_remote_retrieve_response_code( $response ) : 0;
20✔
24
                $response_message = \esc_html( \wp_remote_retrieve_response_message( $response ) );
20✔
25
                $error_code       = '';
20✔
26
                $missing_licenses = [];
20✔
27

28
                if ( $response_code !== 200 && $response_code !== 0 ) {
20✔
29
                        $json_body = \json_decode( \wp_remote_retrieve_body( $response ) );
16✔
30
                        if ( $json_body !== null ) {
16✔
31
                                $response_message = ( $json_body->message ?? $response_message );
14✔
32
                                $error_code       = ( $json_body->error_code ?? $this->map_message_to_code( $response_message ) );
14✔
33
                                if ( $response_code === 402 || $response_code === 429 ) {
14✔
34
                                        $missing_licenses = isset( $json_body->missing_licenses ) ? (array) $json_body->missing_licenses : [];
4✔
35
                                }
36
                        }
37
                }
38

39
                $headers = $this->normalize_headers( \wp_remote_retrieve_headers( $response ) );
20✔
40

41
                return new Response( $response['body'], $response_code, $response_message, $error_code, $missing_licenses, $headers );
20✔
42
        }
43

44
        /**
45
         * Normalizes wp_remote_retrieve_headers() output to a plain array keyed by lower-cased header name.
46
         *
47
         * @param object|array<string, string|array<string>>|string $raw_headers The raw return value of wp_remote_retrieve_headers(), which is a Requests_Utility_CaseInsensitiveDictionary, an array, or empty string.
48
         *
49
         * @return array<string, string|array<string>> The normalized headers.
50
         */
51
        private function normalize_headers( $raw_headers ): array {
20✔
52
                // wp_remote_retrieve_headers() returns a CaseInsensitiveDictionary in WP core; getAll() yields a plain array.
53
                if ( \is_object( $raw_headers ) && \method_exists( $raw_headers, 'getAll' ) ) {
20✔
NEW
54
                        $raw_headers = $raw_headers->getAll();
×
55
                }
56
                if ( ! \is_array( $raw_headers ) ) {
20✔
NEW
57
                        return [];
×
58
                }
59

60
                $normalized = [];
20✔
61
                foreach ( $raw_headers as $name => $value ) {
20✔
NEW
62
                        $normalized[ \strtolower( (string) $name ) ] = $value;
×
63
                }
64

65
                return $normalized;
20✔
66
        }
67

68
        /**
69
         * Maps the error message to a code.
70
         *
71
         * @param string $message The error message.
72
         *
73
         * @return string The mapped code.
74
         */
75
        private function map_message_to_code( string $message ): string {
8✔
76
                if ( \strpos( $message, 'must NOT have fewer than 1 characters' ) !== false ) {
8✔
77
                        return 'NOT_ENOUGH_CONTENT';
2✔
78
                }
79
                if ( \strpos( $message, 'Client timeout' ) !== false ) {
6✔
80
                        return 'CLIENT_TIMEOUT';
2✔
81
                }
82
                if ( \strpos( $message, 'Server timeout' ) !== false ) {
4✔
83
                        return 'SERVER_TIMEOUT';
2✔
84
                }
85

86
                return 'UNKNOWN';
2✔
87
        }
88
}
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

© 2026 Coveralls, Inc