Skip to content

Commit 75070ff

Browse files
committed
test: validate gRPC completion with status trailers and reject missing trailers
1 parent 19d2bb5 commit 75070ff

1 file changed

Lines changed: 59 additions & 39 deletions

File tree

‎rpc-transport-netty/src/test/java/com/xiaoyu/rpc/core/protocol/grpc/GrpcClientResponseHandlerTest.java‎

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,75 +3,95 @@
33
import com.google.protobuf.ByteString;
44
import com.xiaoyu.rpc.common.vo.RpcResponse;
55
import com.xiaoyu.rpc.core.client.NettyRpcClientHandler;
6+
import com.xiaoyu.rpc.core.client.RpcStreamResponseHandler;
67
import io.netty.buffer.ByteBuf;
78
import io.netty.buffer.Unpooled;
89
import io.netty.channel.embedded.EmbeddedChannel;
10+
import io.netty.handler.codec.CorruptedFrameException;
911
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
12+
import io.netty.handler.codec.http2.DefaultHttp2Headers;
1013
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
11-
import io.netty.handler.codec.http2.Http2Headers;
1214
import org.junit.jupiter.api.DisplayName;
1315
import org.junit.jupiter.api.Test;
1416

1517
import java.util.concurrent.CompletableFuture;
1618
import java.util.concurrent.ExecutionException;
19+
import java.util.concurrent.TimeUnit;
1720

1821
import static org.junit.jupiter.api.Assertions.*;
1922

2023
@DisplayName("gRPC 客户端响应处理器测试")
2124
class GrpcClientResponseHandlerTest {
2225

2326
@Test
24-
@DisplayName("应把 DataFrame 解码为 RpcResponse 并完成 future")
27+
@DisplayName("完整消息加成功状态尾帧才完成 future")
2528
void testDecodeDataFrameToRpcResponse() throws Exception {
2629
NettyRpcClientHandler clientHandler = new NettyRpcClientHandler();
27-
EmbeddedChannel channel = new EmbeddedChannel(
28-
new GrpcClientResponseHandler(clientHandler, "req-1"),
29-
clientHandler);
30-
30+
EmbeddedChannel channel = channel(clientHandler, "req-1");
3131
CompletableFuture<Object> future = new CompletableFuture<>();
3232
clientHandler.addFuture("req-1", future);
33-
34-
RpcResponse response = RpcResponse.newBuilder()
35-
.setRequestId("req-1")
36-
.setMessage("Success")
37-
.setData(ByteString.copyFromUtf8("ok"))
38-
.build();
39-
40-
byte[] payload = response.toByteArray();
41-
ByteBuf buf = Unpooled.buffer();
42-
buf.writeByte(0);
43-
buf.writeInt(payload.length);
44-
buf.writeBytes(payload);
45-
46-
channel.writeInbound(new DefaultHttp2DataFrame(buf, true));
47-
48-
assertTrue(future.isDone(), "Future should be completed");
49-
Object result = future.get();
50-
assertInstanceOf(RpcResponse.class, result);
51-
assertEquals("req-1", ((RpcResponse) result).getRequestId());
52-
assertEquals("Success", ((RpcResponse) result).getMessage());
33+
RpcResponse response = response("req-1");
34+
try {
35+
channel.writeInbound(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers()
36+
.status("200").set("content-type", "application/grpc"), false));
37+
channel.writeInbound(new DefaultHttp2DataFrame(framed(response), false));
38+
assertFalse(future.isDone(), "A DATA frame does not establish gRPC success");
39+
channel.writeInbound(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers()
40+
.set("grpc-status", "0"), true));
41+
assertEquals(response, future.get(1, TimeUnit.SECONDS));
42+
} finally {
43+
channel.finishAndReleaseAll();
44+
}
5345
}
5446

5547
@Test
5648
@DisplayName("收到 grpc-status 非 0 时应异常完成 future")
5749
void testFailFutureOnGrpcErrorStatus() {
5850
NettyRpcClientHandler clientHandler = new NettyRpcClientHandler();
59-
EmbeddedChannel channel = new EmbeddedChannel(
60-
new GrpcClientResponseHandler(clientHandler, "req-2"),
61-
clientHandler);
62-
51+
EmbeddedChannel channel = channel(clientHandler, "req-2");
6352
CompletableFuture<Object> future = new CompletableFuture<>();
6453
clientHandler.addFuture("req-2", future);
54+
try {
55+
channel.writeInbound(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers()
56+
.status("200").set("grpc-status", "13").set("grpc-message", "internal"), true));
57+
assertTrue(future.isCompletedExceptionally());
58+
ExecutionException error = assertThrows(ExecutionException.class,
59+
() -> future.get(1, TimeUnit.SECONDS));
60+
assertTrue(error.getCause().getMessage().contains("gRPC status=13"));
61+
} finally {
62+
channel.finishAndReleaseAll();
63+
}
64+
}
6565

66-
Http2Headers trailers = new io.netty.handler.codec.http2.DefaultHttp2Headers()
67-
.set("grpc-status", "13")
68-
.set("grpc-message", "internal");
69-
channel.writeInbound(new DefaultHttp2HeadersFrame(trailers, true));
66+
@Test
67+
@DisplayName("DATA 直接结束且缺少状态尾帧时必须失败")
68+
void testMissingTrailersCannotBeSuccess() {
69+
NettyRpcClientHandler clientHandler = new NettyRpcClientHandler();
70+
EmbeddedChannel channel = channel(clientHandler, "req-3");
71+
CompletableFuture<Object> future = new CompletableFuture<>();
72+
clientHandler.addFuture("req-3", future);
73+
try {
74+
channel.writeInbound(new DefaultHttp2DataFrame(framed(response("req-3")), true));
75+
ExecutionException error = assertThrows(ExecutionException.class,
76+
() -> future.get(1, TimeUnit.SECONDS));
77+
assertInstanceOf(CorruptedFrameException.class, error.getCause());
78+
} finally {
79+
channel.finishAndReleaseAll();
80+
}
81+
}
82+
83+
private static EmbeddedChannel channel(NettyRpcClientHandler handler, String requestId) {
84+
return new EmbeddedChannel(new GrpcClientResponseHandler(handler, requestId),
85+
new RpcStreamResponseHandler(handler, requestId));
86+
}
87+
88+
private static RpcResponse response(String requestId) {
89+
return RpcResponse.newBuilder().setRequestId(requestId).setMessage("Success")
90+
.setData(ByteString.copyFromUtf8("ok")).build();
91+
}
7092

71-
assertTrue(future.isCompletedExceptionally(), "Future should be completed exceptionally");
72-
ExecutionException ex = assertThrows(ExecutionException.class, future::get);
73-
String message = ex.getCause().getMessage();
74-
assertNotNull(message, "Error message should not be null");
75-
assertTrue(message.toLowerCase().contains("grpc"), "Error message should include grpc details");
93+
private static ByteBuf framed(RpcResponse response) {
94+
byte[] payload = response.toByteArray();
95+
return Unpooled.buffer(payload.length + 5).writeByte(0).writeInt(payload.length).writeBytes(payload);
7696
}
7797
}

0 commit comments

Comments
 (0)