Skip to content

Commit 545202b

Browse files
committed
rework grpc
1 parent 6bc4500 commit 545202b

File tree

3 files changed

+49
-118
lines changed

3 files changed

+49
-118
lines changed

allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import org.slf4j.Logger;
4646
import org.slf4j.LoggerFactory;
4747

48-
4948
/**
5049
* Allure interceptor logger for gRPC.
5150
*
@@ -132,6 +131,22 @@ public void sendMessage(final T message) {
132131
};
133132
}
134133

134+
private void addRawJsonAttachment(
135+
final String stepUuid,
136+
final String attachmentName,
137+
final String jsonBody,
138+
final AllureLifecycle lifecycle
139+
) {
140+
if (jsonBody == null || jsonBody.isEmpty()) {
141+
return;
142+
}
143+
final String source = UUID.randomUUID() + ".json";
144+
lifecycle.updateStep(stepUuid, step -> step.getAttachments().add(
145+
new Attachment().setName(attachmentName).setSource(source).setType("application/json")
146+
));
147+
lifecycle.writeAttachment(source, new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8)));
148+
}
149+
135150
private static final class StepContext<T, R> {
136151
final String stepUuid;
137152
final MethodDescriptor<T, R> methodDescriptor;
@@ -226,6 +241,7 @@ private <T, R> void attachRequestIfPresent(
226241
.setBody(body)
227242
.build();
228243
addRenderedAttachmentToStep(stepUuid, requestAttachment.getName(), requestAttachment, requestTemplatePath, lifecycle);
244+
addRawJsonAttachment(stepUuid, name + " (json)", body, lifecycle);
229245
}
230246

231247
private void attachResponse(
@@ -259,6 +275,9 @@ private void attachResponse(
259275
final GrpcResponseAttachment responseAttachment = builder.build();
260276
addRenderedAttachmentToStep(stepUuid, responseAttachment.getName(),
261277
responseAttachment, responseTemplatePath, lifecycle);
278+
if (body != null) {
279+
addRawJsonAttachment(stepUuid, name + " (json)", body, lifecycle);
280+
}
262281
}
263282

264283
private void stopStepSafely(final AllureLifecycle lifecycle, final String stepUuid) {

allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java

Lines changed: 27 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
package io.qameta.allure.grpc;
17+
1718
import com.fasterxml.jackson.databind.JsonNode;
1819
import com.fasterxml.jackson.databind.ObjectMapper;
1920
import io.grpc.ManagedChannel;
@@ -24,6 +25,7 @@
2425
import io.qameta.allure.Allure;
2526
import io.qameta.allure.model.Attachment;
2627
import io.qameta.allure.model.StepResult;
28+
import io.qameta.allure.model.TestResult;
2729
import io.qameta.allure.test.AllureResults;
2830
import org.grpcmock.GrpcMock;
2931
import org.grpcmock.junit5.GrpcMockExtension;
@@ -77,7 +79,7 @@ void configureMockServer() {
7779
.willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build()));
7880

7981
GrpcMock.stubFor(bidiStreamingMethod(TestServiceGrpc.getCalculateBidiStreamMethod())
80-
.willProxyTo(responseObserver -> new StreamObserver<>() {
82+
.willProxyTo(responseObserver -> new StreamObserver<Request>() {
8183
@Override
8284
public void onNext(Request request) {
8385
responseObserver.onNext(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build());
@@ -174,10 +176,10 @@ void shouldCreateAttachmentsForClientStreamingWithAsynchronousStub() {
174176
TestServiceGrpc.TestServiceStub asynchronousStub =
175177
TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc());
176178

177-
final List<Response> receivedResponses = new ArrayList<>();
179+
final List<Response> receivedResponses = new ArrayList<Response>();
178180

179181
Allure.step("async-root-client-stream", () -> {
180-
StreamObserver<Response> responseObserver = new StreamObserver<>() {
182+
StreamObserver<Response> responseObserver = new StreamObserver<Response>() {
181183
@Override
182184
public void onNext(Response value) {
183185
receivedResponses.add(value);
@@ -213,7 +215,7 @@ void shouldCreateAttachmentsForBidirectionalStreamingWithAsynchronousStub() {
213215
List<Response> receivedResponses = new ArrayList<>();
214216

215217
Allure.step("async-root-bidi-stream", () -> {
216-
StreamObserver<Response> responseObserver = new StreamObserver<>() {
218+
StreamObserver<Response> responseObserver = new StreamObserver<Response>() {
217219
@Override public void onNext(Response value) { receivedResponses.add(value); }
218220
@Override public void onError(Throwable throwable) { }
219221
@Override public void onCompleted() { }
@@ -245,8 +247,7 @@ void unaryRequestBodyIsCapturedAsJsonObject() throws Exception {
245247
assertThat(response.getMessage()).isEqualTo("ok");
246248
});
247249

248-
String attachmentHtmlContent = readAttachmentContentByName(allureResults, "gRPC request");
249-
String jsonPayload = extractJsonPayload(attachmentHtmlContent);
250+
String jsonPayload = readJsonAttachmentByName(allureResults, "gRPC request (json)");
250251
JsonNode actualJsonNode = JSON.readTree(jsonPayload);
251252
JsonNode expectedJsonNode = JSON.createObjectNode().put("topic", "topic-1");
252253

@@ -267,8 +268,7 @@ void unaryResponseBodyIsCapturedAsJsonObject() throws Exception {
267268
assertThat(response.getMessage()).isEqualTo("hello-world");
268269
});
269270

270-
String attachmentHtmlContent = readAttachmentContentByName(allureResults, "gRPC response");
271-
String jsonPayload = extractJsonPayload(attachmentHtmlContent);
271+
String jsonPayload = readJsonAttachmentByName(allureResults, "gRPC response (json)");
272272
JsonNode actualJsonNode = JSON.readTree(jsonPayload);
273273
JsonNode expectedJsonNode = JSON.createObjectNode().put("message", "hello-world");
274274

@@ -296,18 +296,33 @@ void serverStreamingResponseBodyIsJsonArrayInOrder() throws Exception {
296296
assertThat(responseIterator.hasNext()).isFalse();
297297
});
298298

299-
String attachmentHtmlContent = readAttachmentContentByName(
300-
allureResults,
301-
"gRPC response (collection of elements from Server stream)"
299+
String jsonPayload = readJsonAttachmentByName(
300+
allureResults, "gRPC response (collection of elements from Server stream) (json)"
302301
);
303-
String jsonPayload = extractJsonPayload(attachmentHtmlContent);
304302
JsonNode actualJsonArray = JSON.readTree(jsonPayload);
305303

306304
assertThat(actualJsonArray.isArray()).isTrue();
307305
assertThat(actualJsonArray.size()).isEqualTo(2);
308306
assertThat(actualJsonArray.get(0)).isEqualTo(JSON.createObjectNode().put("message", "first"));
309307
assertThat(actualJsonArray.get(1)).isEqualTo(JSON.createObjectNode().put("message", "second"));
310308
}
309+
private static String readJsonAttachmentByName(AllureResults allureResults, String jsonAttachmentName) {
310+
TestResult test = allureResults.getTestResults().get(0);
311+
312+
Attachment matchedAttachment = flattenSteps(test.getSteps()).stream()
313+
.flatMap(step -> step.getAttachments().stream())
314+
.filter(attachment -> jsonAttachmentName.equals(attachment.getName()))
315+
.findFirst()
316+
.orElseThrow(() -> new IllegalStateException("Attachment not found: " + jsonAttachmentName));
317+
318+
String attachmentSourceKey = matchedAttachment.getSource();
319+
Map<String, byte[]> attachmentsContent = allureResults.getAttachments();
320+
byte[] rawAttachmentContent = attachmentsContent.get(attachmentSourceKey);
321+
if (rawAttachmentContent == null) {
322+
throw new IllegalStateException("Attachment content not found by source: " + attachmentSourceKey);
323+
}
324+
return new String(rawAttachmentContent, StandardCharsets.UTF_8);
325+
}
311326

312327
protected final AllureResults executeUnary(Request request) {
313328
return runWithinTestContext(() -> {
@@ -352,111 +367,6 @@ protected final AllureResults executeUnaryExpectingException(Request request) {
352367
);
353368
}
354369

355-
private static String readAttachmentContentByName(AllureResults allureResults, String attachmentName) {
356-
var test = allureResults.getTestResults().get(0);
357-
358-
Attachment matchedAttachment = flattenSteps(test.getSteps()).stream()
359-
.flatMap(step -> step.getAttachments().stream())
360-
.filter(attachment -> attachmentName.equals(attachment.getName()))
361-
.findFirst()
362-
.orElseThrow(() -> new IllegalStateException("Attachment not found: " + attachmentName));
363-
364-
String attachmentSourceKey = matchedAttachment.getSource();
365-
Map<String, byte[]> attachmentsContent = allureResults.getAttachments();
366-
byte[] rawAttachmentContent = attachmentsContent.get(attachmentSourceKey);
367-
if (rawAttachmentContent == null) {
368-
throw new IllegalStateException("Attachment content not found by source: " + attachmentSourceKey);
369-
}
370-
return new String(rawAttachmentContent, StandardCharsets.UTF_8);
371-
}
372-
373-
private static String extractJsonPayload(String htmlContent) {
374-
String textWithoutHtml = stripHtmlTags(unescapeHtml(htmlContent));
375-
int fullLength = textWithoutHtml.length();
376-
for (int currentIndex = 0; currentIndex < fullLength; currentIndex++) {
377-
char currentChar = textWithoutHtml.charAt(currentIndex);
378-
if (currentChar == '{' || currentChar == '[') {
379-
int matchingBracketIndex = findMatchingBracket(textWithoutHtml, currentIndex);
380-
if (matchingBracketIndex > currentIndex) {
381-
String candidateJson = textWithoutHtml.substring(currentIndex, matchingBracketIndex + 1).trim();
382-
if (looksLikeJson(candidateJson) && canParseJson(candidateJson)) {
383-
return candidateJson;
384-
}
385-
}
386-
}
387-
}
388-
throw new IllegalStateException("JSON payload not found or not valid inside attachment");
389-
}
390-
391-
private static boolean canParseJson(String candidateJson) {
392-
try {
393-
JSON.readTree(candidateJson);
394-
return true;
395-
} catch (Exception ignore) {
396-
return false;
397-
}
398-
}
399-
400-
private static boolean looksLikeJson(String input) {
401-
if (input == null) {
402-
return false;
403-
}
404-
String trimmed = input.trim();
405-
if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) {
406-
return false;
407-
}
408-
return trimmed.matches("(?s).*\"[^\"]+\"\\s*:\\s*.*");
409-
}
410-
411-
private static int findMatchingBracket(String input, int startIndex) {
412-
char openingBracket = input.charAt(startIndex);
413-
char closingBracket = (openingBracket == '{') ? '}' : ']';
414-
int nestingDepth = 0;
415-
boolean insideString = false;
416-
for (int index = startIndex; index < input.length(); index++) {
417-
char symbol = input.charAt(index);
418-
if (symbol == '"' && (index == 0 || input.charAt(index - 1) != '\\')) {
419-
insideString = !insideString;
420-
}
421-
if (insideString) {
422-
continue;
423-
}
424-
if (symbol == openingBracket) {
425-
nestingDepth++;
426-
} else if (symbol == closingBracket) {
427-
nestingDepth--;
428-
if (nestingDepth == 0) {
429-
return index;
430-
}
431-
}
432-
}
433-
return -1;
434-
}
435-
436-
private static String stripHtmlTags(String input) {
437-
String withoutTags = input.replaceAll("(?is)<script.*?</script>", "")
438-
.replaceAll("(?is)<style.*?</style>", "")
439-
.replaceAll("(?s)<[^>]*>", " ");
440-
return withoutTags
441-
.replace("\r", " ")
442-
.replace("\n", " ")
443-
.replaceAll("[ \\t\\x0B\\f\\r]+", " ")
444-
.trim();
445-
}
446-
447-
private static String unescapeHtml(String input) {
448-
return input.replace("&quot;", "\"")
449-
.replace("&lt;", "<")
450-
.replace("&gt;", ">")
451-
.replace("&amp;", "&")
452-
.replace("&#123;", "{")
453-
.replace("&#125;", "}")
454-
.replace("&#91;", "[")
455-
.replace("&#93;", "]")
456-
.replace("&#58;", ":")
457-
.replace("&#44;", ",");
458-
}
459-
460370
private static List<StepResult> flattenSteps(List<StepResult> rootSteps) {
461371
List<StepResult> allSteps = new ArrayList<>();
462372
if (rootSteps == null) {

allure-grpc/src/test/proto/api.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ option java_package = "io.qameta.allure.grpc";
66
service TestService {
77
rpc Calculate (Request) returns (Response);
88
rpc CalculateServerStream (Request) returns (stream Response);
9+
rpc CalculateClientStream (stream Request) returns (Response);
10+
rpc CalculateBidiStream (stream Request) returns (stream Response);
911
}
1012

1113
message Request {

0 commit comments

Comments
 (0)