This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new be34dd1e7d fix(ai): apply the generated HTTP status in the AI response 
transformer (#7915)
be34dd1e7d is described below

commit be34dd1e7d37a44d3d2badf3f307410832cfb521
Author: hanzhe-one <[email protected]>
AuthorDate: Wed Oct 21 20:58:48 2026 +0800

    fix(ai): apply the generated HTTP status in the AI response transformer 
(#7915)
    
    The response-transformer system prompt asks the model for a complete
    HTTP/1.1 response including the status line, but the decorator only
    applied the generated headers and body. The generated status was parsed
    by nothing and silently discarded, so a model answering
    "HTTP/1.1 404 Not Found" still produced the upstream 200.
    
    Parse the status line before the response is committed and apply it:
    
    - The status line must be the first non-empty line of the model output
      (after stripping markdown fences); a status-looking header value
      further down cannot change the status.
    - The code must be one the HTTP specification defines, otherwise the
      generated status is ignored and the upstream status is retained.
    - 204/304 are applied without a body, dropping the generated
      Content-Type/Content-Length/Transfer-Encoding headers.
    - Omitted or invalid output, and the no-usable-body fallback, keep the
      upstream status.
    
    Also accept HTTP/1.0 status lines when extracting headers and document
    in the prompt that the status line is applied.
    
    Fixes #7907
---
 .../response/AiResponseTransformerPlugin.java      | 148 ++++++++++++++++-----
 .../template/AiResponseTransformerTemplate.java    |   2 +
 .../response/AiResponseTransformerPluginTest.java  | 147 ++++++++++++++++++++
 3 files changed, 262 insertions(+), 35 deletions(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPlugin.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPlugin.java
index 492a7bf4e3..97aea95529 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPlugin.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPlugin.java
@@ -40,6 +40,7 @@ import org.springframework.ai.chat.model.ChatModel;
 import org.springframework.core.io.buffer.DataBuffer;
 import org.springframework.core.io.buffer.DataBufferUtils;
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.codec.HttpMessageReader;
 import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
 import org.springframework.lang.NonNull;
@@ -58,6 +59,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import java.util.zip.GZIPInputStream;
@@ -76,6 +78,8 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
 
     private static final Pattern REQUEST_LINE_PATTERN = 
Pattern.compile("^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)\\s.*\\sHTTP/1.1$");
 
+    private static final Pattern STATUS_LINE_PATTERN = 
Pattern.compile("^HTTP/1\\.[01]\\s+(\\d{3})(?:\\s+.*)?$");
+
     private static final ObjectMapper MAPPER = new ObjectMapper();
 
     private final List<HttpMessageReader<?>> messageReaders;
@@ -168,32 +172,53 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
         return AiResponseParser.extractHeadersFromAiResponse(aiResponse);
     }
 
+    /**
+     * For unit test.
+     *
+     * @param aiResponse the AI response
+     * @return the extracted status code, or {@code null} when the response 
has no usable status line
+     */
+    public static Integer extractStatusFromAiResponse(final String aiResponse) 
{
+        return AiResponseParser.extractStatusFromAiResponse(aiResponse);
+    }
+
     /**
      * Utility class for parsing AI responses.
      */
     private static class AiResponseParser {
-        
+
         /**
-         * Extract body from AI response.
+         * Remove the surrounding markdown code block markers, if any.
          *
          * @param aiResponse the AI response
-         * @return the extracted body
+         * @return the response without code block markers
          */
-        static String extractBodyFromAiResponse(final String aiResponse) {
-            if (Objects.isNull(aiResponse) || aiResponse.isEmpty()) {
-                return null;
-            }
-
+        static String stripCodeBlockMarkers(final String aiResponse) {
             String cleanedResponse = aiResponse;
             if (cleanedResponse.contains("```")) {
                 int startIndex = cleanedResponse.indexOf("```");
                 int endIndex = cleanedResponse.lastIndexOf("```");
-                
+
                 if (startIndex != -1 && endIndex != -1 && endIndex > 
startIndex) {
                     cleanedResponse = cleanedResponse.substring(startIndex + 
3, endIndex).trim();
                     LOG.debug("Removed code block markers, cleaned response: 
{}", cleanedResponse);
                 }
             }
+            return cleanedResponse;
+        }
+
+        /**
+         * Extract body from AI response.
+         *
+         * @param aiResponse the AI response
+         * @return the extracted body
+         */
+        static String extractBodyFromAiResponse(final String aiResponse) {
+            if (Objects.isNull(aiResponse) || aiResponse.isEmpty()) {
+                return null;
+            }
+
+            String cleanedResponse = stripCodeBlockMarkers(aiResponse);
 
             if (cleanedResponse.startsWith("{") && 
cleanedResponse.endsWith("}")
                     || cleanedResponse.startsWith("[") && 
cleanedResponse.endsWith("]")) {
@@ -249,23 +274,14 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
                 return headers;
             }
 
-            String cleanedResponse = aiResponse;
-            if (cleanedResponse.contains("```")) {
-                int startIndex = cleanedResponse.indexOf("```");
-                int endIndex = cleanedResponse.lastIndexOf("```");
-                
-                if (startIndex != -1 && endIndex != -1 && endIndex > 
startIndex) {
-                    cleanedResponse = cleanedResponse.substring(startIndex + 
3, endIndex).trim();
-                    LOG.debug("Removed code block markers for header 
extraction, cleaned response: {}", cleanedResponse);
-                }
-            }
+            String cleanedResponse = stripCodeBlockMarkers(aiResponse);
 
             try (BufferedReader reader = new BufferedReader(new 
StringReader(cleanedResponse))) {
                 String line;
                 boolean headerSectionStarted = false;
                 while (Objects.nonNull(line = reader.readLine())) {
                     if (!headerSectionStarted) {
-                        if (line.startsWith("HTTP/1.1") || 
REQUEST_LINE_PATTERN.matcher(line).matches()) {
+                        if (STATUS_LINE_PATTERN.matcher(line.trim()).matches() 
|| REQUEST_LINE_PATTERN.matcher(line.trim()).matches()) {
                             headerSectionStarted = true;
                             continue;
                         }
@@ -286,6 +302,44 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
             }
             return headers;
         }
+
+        /**
+         * Extract the status code from the status line of an AI response.
+         * The status line is only accepted as the first non-empty line, so a 
status-looking
+         * header value further down cannot change the response status.
+         *
+         * @param aiResponse the AI response
+         * @return the extracted status code, or {@code null} when there is no 
valid status line
+         */
+        static Integer extractStatusFromAiResponse(final String aiResponse) {
+            if (Objects.isNull(aiResponse) || aiResponse.isEmpty()) {
+                return null;
+            }
+
+            String cleanedResponse = stripCodeBlockMarkers(aiResponse);
+            try (BufferedReader reader = new BufferedReader(new 
StringReader(cleanedResponse))) {
+                String line;
+                while (Objects.nonNull(line = reader.readLine())) {
+                    if (line.trim().isEmpty()) {
+                        continue;
+                    }
+                    Matcher matcher = STATUS_LINE_PATTERN.matcher(line.trim());
+                    if (!matcher.matches()) {
+                        return null;
+                    }
+                    int statusCode = Integer.parseInt(matcher.group(1));
+                    // HttpStatus resolves only codes defined by the HTTP 
specification.
+                    if (Objects.isNull(HttpStatus.resolve(statusCode))) {
+                        LOG.warn("AI response contains an invalid HTTP status 
code: {}", statusCode);
+                        return null;
+                    }
+                    return statusCode;
+                }
+            } catch (IOException | NumberFormatException e) {
+                LOG.warn("AI response transformer plugin: extract status from 
AiResponse fail");
+            }
+            return null;
+        }
     }
 
     static class AiResponseTransformerDecorator extends 
ServerHttpResponseDecorator {
@@ -358,21 +412,7 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
                                     .filter(Objects::nonNull)
                                     .collect(StringBuilder::new, 
StringBuilder::append)
                                     .map(StringBuilder::toString)
-                                    .flatMap(aiResponse -> {
-
-                                        HttpHeaders newHeaders = 
extractHeadersFromAiResponse(aiResponse);
-                                        String newBody = 
extractBodyFromAiResponse(aiResponse);
-
-                                        if (Objects.nonNull(newBody) && 
!newBody.isEmpty()) {
-                                            this.getHeaders().clear();
-                                            
this.getHeaders().putAll(newHeaders);
-                                            LOG.debug("Returning transformed 
response body: {}", newBody);
-                                            return 
WebFluxResultUtils.result(this.exchange, 
newBody.getBytes(StandardCharsets.UTF_8));
-                                        } else {
-                                            LOG.warn("response body is empty 
or null, returning original response: {}", finalResponseBody);
-                                            return 
WebFluxResultUtils.result(this.exchange, 
finalResponseBody.getBytes(StandardCharsets.UTF_8));
-                                        }
-                                    })
+                                    .flatMap(aiResponse -> 
applyTransformedResponse(aiResponse, finalResponseBody))
                                     .onErrorResume(throwable -> {
                                         LOG.error("response transformation 
failed", throwable);
                                         LOG.info("Returning original response 
due to error: {}", finalResponseBody);
@@ -389,6 +429,36 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
             return writeWith(Flux.from(body).concatMap(Flux::from));
         }
 
+        private Mono<Void> applyTransformedResponse(final String aiResponse, 
final String originalResponseBody) {
+            HttpHeaders newHeaders = extractHeadersFromAiResponse(aiResponse);
+            String newBody = extractBodyFromAiResponse(aiResponse);
+            Integer newStatus = extractStatusFromAiResponse(aiResponse);
+
+            if (isBodylessStatus(newStatus)) {
+                // 204/304 must not carry a body, so the generated status and 
headers are applied as-is
+                this.getHeaders().clear();
+                this.getHeaders().putAll(newHeaders);
+                this.getHeaders().remove(HttpHeaders.CONTENT_TYPE);
+                this.getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
+                this.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);
+                this.setStatusCode(HttpStatus.valueOf(newStatus));
+                LOG.debug("Returning transformed bodyless response with 
status: {}", newStatus);
+                return super.writeWith(Flux.empty());
+            }
+            if (Objects.nonNull(newBody) && !newBody.isEmpty()) {
+                this.getHeaders().clear();
+                this.getHeaders().putAll(newHeaders);
+                if (Objects.nonNull(newStatus)) {
+                    this.setStatusCode(HttpStatus.valueOf(newStatus));
+                }
+                LOG.debug("Returning transformed response body: {}", newBody);
+                return WebFluxResultUtils.result(this.exchange, 
newBody.getBytes(StandardCharsets.UTF_8));
+            }
+            // the generated response has no usable body, so the original 
response (and its status) is kept
+            LOG.warn("response body is empty or null, returning original 
response: {}", originalResponseBody);
+            return WebFluxResultUtils.result(this.exchange, 
originalResponseBody.getBytes(StandardCharsets.UTF_8));
+        }
+
         private String extractBodyFromAiResponse(final String aiResponse) {
             if (Objects.isNull(aiResponse) || aiResponse.isEmpty()) {
                 LOG.warn("response is null or empty");
@@ -404,6 +474,14 @@ public class AiResponseTransformerPlugin extends 
AbstractShenyuPlugin {
             return result;
         }
 
+        private static boolean isBodylessStatus(final Integer statusCode) {
+            if (Objects.isNull(statusCode)) {
+                return false;
+            }
+            HttpStatus status = HttpStatus.resolve(statusCode);
+            return HttpStatus.NO_CONTENT.equals(status) || 
HttpStatus.NOT_MODIFIED.equals(status);
+        }
+
         private HttpHeaders extractHeadersFromAiResponse(final String 
aiResponse) {
             if (Objects.isNull(aiResponse) || aiResponse.isEmpty()) {
                 LOG.warn("AI response is null or empty for header extraction");
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/template/AiResponseTransformerTemplate.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/template/AiResponseTransformerTemplate.java
index f9119e688c..2ec3b9593b 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/template/AiResponseTransformerTemplate.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/main/java/org/apache/shenyu/plugin/ai/transformer/response/template/AiResponseTransformerTemplate.java
@@ -56,6 +56,8 @@ public class AiResponseTransformerTemplate {
             + "- A blank line  \n"
             + "- A JSON-formatted body (the body must be valid JSON text)  \n"
             + "Do not include any extra explanations, comments, or text.  \n"
+            + "The status line is applied to the response, so only change the 
status when the transformation requires it; "
+            + "otherwise keep the upstream status. Do not send a body with 204 
or 304.  \n"
             + "Example:\n"
             + "\n"
             + "HTTP/1.1 200 OK\n"
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPluginTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPluginTest.java
index 63f1a802e4..7f68bdd52d 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPluginTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-response-transformer/src/test/java/org/apache/shenyu/plugin/ai/transformer/response/AiResponseTransformerPluginTest.java
@@ -233,6 +233,114 @@ class AiResponseTransformerPluginTest {
                 .verifyComplete();
     }
 
+    @Test
+    void testGeneratedStatusIsApplied() {
+        MockServerHttpResponse response = (MockServerHttpResponse) 
exchange.getResponse();
+        response.setStatusCode(HttpStatus.OK);
+        AiResponseTransformerTemplate template = 
mock(AiResponseTransformerTemplate.class);
+        
when(template.assembleMessage(exchange)).thenReturn(Mono.just("{\"response\":{\"body\":\"\"}}"));
+        ChatClient chatClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
+        when(chatClient.prompt().user(anyString()).stream().content())
+                .thenReturn(Flux.just("HTTP/1.1 404 Not Found\nContent-Type: 
application/json\n\n{\"error\":\"missing\"}"));
+        ConfigurableApplicationContext context = 
mock(ConfigurableApplicationContext.class);
+        when(context.getBean(ShenyuResult.class)).thenReturn(new 
DefaultShenyuResult());
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+        AiResponseTransformerPlugin.AiResponseTransformerDecorator decorator =
+                new 
AiResponseTransformerPlugin.AiResponseTransformerDecorator(exchange, template, 
chatClient);
+        DataBuffer responseBody = 
response.bufferFactory().wrap("original".getBytes(StandardCharsets.UTF_8));
+
+        
StepVerifier.create(decorator.writeWith(Mono.just(responseBody))).verifyComplete();
+
+        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+        assertEquals("{\"error\":\"missing\"}", 
response.getBodyAsString().block());
+    }
+
+    @Test
+    void testGeneratedSuccessStatusReplacesErrorStatus() {
+        MockServerHttpResponse response = (MockServerHttpResponse) 
exchange.getResponse();
+        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
+        AiResponseTransformerTemplate template = 
mock(AiResponseTransformerTemplate.class);
+        
when(template.assembleMessage(exchange)).thenReturn(Mono.just("{\"response\":{\"body\":\"\"}}"));
+        ChatClient chatClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
+        when(chatClient.prompt().user(anyString()).stream().content())
+                .thenReturn(Flux.just("HTTP/1.1 201 Created\nContent-Type: 
application/json\n\n{\"created\":true}"));
+        ConfigurableApplicationContext context = 
mock(ConfigurableApplicationContext.class);
+        when(context.getBean(ShenyuResult.class)).thenReturn(new 
DefaultShenyuResult());
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+        AiResponseTransformerPlugin.AiResponseTransformerDecorator decorator =
+                new 
AiResponseTransformerPlugin.AiResponseTransformerDecorator(exchange, template, 
chatClient);
+        DataBuffer responseBody = 
response.bufferFactory().wrap("failed".getBytes(StandardCharsets.UTF_8));
+
+        
StepVerifier.create(decorator.writeWith(Mono.just(responseBody))).verifyComplete();
+
+        assertEquals(HttpStatus.CREATED, response.getStatusCode());
+        assertEquals("{\"created\":true}", response.getBodyAsString().block());
+    }
+
+    @Test
+    void testUpstreamStatusRetainedWhenGeneratedStatusIsInvalid() {
+        MockServerHttpResponse response = (MockServerHttpResponse) 
exchange.getResponse();
+        response.setStatusCode(HttpStatus.ACCEPTED);
+        AiResponseTransformerTemplate template = 
mock(AiResponseTransformerTemplate.class);
+        
when(template.assembleMessage(exchange)).thenReturn(Mono.just("{\"response\":{\"body\":\"\"}}"));
+        ChatClient chatClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
+        when(chatClient.prompt().user(anyString()).stream().content())
+                .thenReturn(Flux.just("HTTP/1.1 999 Not Real\nContent-Type: 
application/json\n\n{\"ok\":true}"));
+        ConfigurableApplicationContext context = 
mock(ConfigurableApplicationContext.class);
+        when(context.getBean(ShenyuResult.class)).thenReturn(new 
DefaultShenyuResult());
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+        AiResponseTransformerPlugin.AiResponseTransformerDecorator decorator =
+                new 
AiResponseTransformerPlugin.AiResponseTransformerDecorator(exchange, template, 
chatClient);
+        DataBuffer responseBody = 
response.bufferFactory().wrap("original".getBytes(StandardCharsets.UTF_8));
+
+        
StepVerifier.create(decorator.writeWith(Mono.just(responseBody))).verifyComplete();
+
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        assertEquals("{\"ok\":true}", response.getBodyAsString().block());
+    }
+
+    @Test
+    void testUpstreamStatusRetainedWhenGeneratedStatusIsOmitted() {
+        MockServerHttpResponse response = (MockServerHttpResponse) 
exchange.getResponse();
+        response.setStatusCode(HttpStatus.ACCEPTED);
+        AiResponseTransformerTemplate template = 
mock(AiResponseTransformerTemplate.class);
+        
when(template.assembleMessage(exchange)).thenReturn(Mono.just("{\"response\":{\"body\":\"\"}}"));
+        ChatClient chatClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
+        when(chatClient.prompt().user(anyString()).stream().content())
+                .thenReturn(Flux.just("Content-Type: 
application/json\n\n{\"ok\":true}"));
+        ConfigurableApplicationContext context = 
mock(ConfigurableApplicationContext.class);
+        when(context.getBean(ShenyuResult.class)).thenReturn(new 
DefaultShenyuResult());
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+        AiResponseTransformerPlugin.AiResponseTransformerDecorator decorator =
+                new 
AiResponseTransformerPlugin.AiResponseTransformerDecorator(exchange, template, 
chatClient);
+        DataBuffer responseBody = 
response.bufferFactory().wrap("original".getBytes(StandardCharsets.UTF_8));
+
+        
StepVerifier.create(decorator.writeWith(Mono.just(responseBody))).verifyComplete();
+
+        assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+        assertEquals("{\"ok\":true}", response.getBodyAsString().block());
+    }
+
+    @Test
+    void testNoContentStatusDropsBody() {
+        MockServerHttpResponse response = (MockServerHttpResponse) 
exchange.getResponse();
+        response.setStatusCode(HttpStatus.OK);
+        AiResponseTransformerTemplate template = 
mock(AiResponseTransformerTemplate.class);
+        
when(template.assembleMessage(exchange)).thenReturn(Mono.just("{\"response\":{\"body\":\"\"}}"));
+        ChatClient chatClient = mock(ChatClient.class, RETURNS_DEEP_STUBS);
+        when(chatClient.prompt().user(anyString()).stream().content())
+                .thenReturn(Flux.just("HTTP/1.1 204 No Content\nContent-Type: 
application/json\n\n"));
+        AiResponseTransformerPlugin.AiResponseTransformerDecorator decorator =
+                new 
AiResponseTransformerPlugin.AiResponseTransformerDecorator(exchange, template, 
chatClient);
+        DataBuffer responseBody = 
response.bufferFactory().wrap("original".getBytes(StandardCharsets.UTF_8));
+
+        
StepVerifier.create(decorator.writeWith(Mono.just(responseBody))).verifyComplete();
+
+        assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
+        assertNull(response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+        assertEquals("", response.getBodyAsString().block());
+    }
+
     @Test
     void testOriginalHeadersRemainWhenTransformedBodyIsInvalid() {
         MockServerHttpResponse response = (MockServerHttpResponse) 
exchange.getResponse();
@@ -347,6 +455,45 @@ class AiResponseTransformerPluginTest {
         assertNull(body);
     }
 
+    @Test
+    void testExtractStatusFromAiResponse() {
+        assertEquals(HttpStatus.NOT_FOUND.value(),
+                
AiResponseTransformerPlugin.extractStatusFromAiResponse("HTTP/1.1 404 Not 
Found\nContent-Type: application/json\n\n{}"));
+        assertEquals(HttpStatus.NO_CONTENT.value(),
+                
AiResponseTransformerPlugin.extractStatusFromAiResponse("HTTP/1.1 204 No 
Content\n\n"));
+        assertEquals(HttpStatus.OK.value(),
+                
AiResponseTransformerPlugin.extractStatusFromAiResponse("HTTP/1.0 200 
OK\nContent-Type: application/json\n\n{}"));
+    }
+
+    @Test
+    void testExtractStatusFromAiResponseWithCodeBlock() {
+        assertEquals(HttpStatus.CREATED.value(), AiResponseTransformerPlugin
+                .extractStatusFromAiResponse("```\nHTTP/1.1 201 
Created\nContent-Type: application/json\n\n{\"id\":1}\n```"));
+    }
+
+    @Test
+    void testExtractStatusFromAiResponseWithInvalidOrMissingStatusLine() {
+        
assertNull(AiResponseTransformerPlugin.extractStatusFromAiResponse("HTTP/1.1 
999 Not Real\nContent-Type: application/json\n\n{}"));
+        
assertNull(AiResponseTransformerPlugin.extractStatusFromAiResponse("HTTP/1.1 
abc OK\nContent-Type: application/json\n\n{}"));
+        
assertNull(AiResponseTransformerPlugin.extractStatusFromAiResponse("Content-Type:
 application/json\n\n{}"));
+        
assertNull(AiResponseTransformerPlugin.extractStatusFromAiResponse("{\"status\":\"success\"}"));
+        
assertNull(AiResponseTransformerPlugin.extractStatusFromAiResponse(""));
+        
assertNull(AiResponseTransformerPlugin.extractStatusFromAiResponse(null));
+    }
+
+    @Test
+    void testExtractStatusFromAiResponseIgnoresStatusLookingHeaderValue() {
+        assertNull(AiResponseTransformerPlugin
+                .extractStatusFromAiResponse("Content-Type: 
application/json\nX-Debug: HTTP/1.1 500 Internal Server Error\n\n{}"));
+    }
+
+    @Test
+    void testExtractHeadersFromAiResponseWithHttp10StatusLine() {
+        HttpHeaders headers = AiResponseTransformerPlugin
+                .extractHeadersFromAiResponse("HTTP/1.0 200 OK\nContent-Type: 
application/json\n\n{\"status\":\"success\"}");
+        assertEquals("application/json", headers.getFirst("Content-Type"));
+    }
+
     @Test
     void testExtractHeadersFromAiResponseWithEmptyResponse() {
         String aiResponse = "";

Reply via email to