Ensure details are included on all gRPC error statuses

This commit is contained in:
ravi-signal
2026-02-17 14:54:16 -05:00
committed by GitHub
parent d6a0129c5a
commit 81031b7b2f
25 changed files with 292 additions and 241 deletions

View File

@@ -34,26 +34,6 @@ public final class GrpcTestUtils {
// noop
}
public static void setupAuthenticatedExtension(
final GrpcServerExtension extension,
final MockAuthenticationInterceptor mockAuthenticationInterceptor,
final MockRequestAttributesInterceptor mockRequestAttributesInterceptor,
final UUID authenticatedAci,
final byte authenticatedDeviceId,
final BindableService service) {
mockAuthenticationInterceptor.setAuthenticatedDevice(authenticatedAci, authenticatedDeviceId);
extension.getServiceRegistry()
.addService(ServerInterceptors.intercept(service, new ValidatingInterceptor(), mockRequestAttributesInterceptor, mockAuthenticationInterceptor, new ErrorMappingInterceptor()));
}
public static void setupUnauthenticatedExtension(
final GrpcServerExtension extension,
final MockRequestAttributesInterceptor mockRequestAttributesInterceptor,
final BindableService service) {
extension.getServiceRegistry()
.addService(ServerInterceptors.intercept(service, new ValidatingInterceptor(), mockRequestAttributesInterceptor, new ErrorMappingInterceptor()));
}
public static void assertStatusException(final Status expected, final Executable serviceCall) {
final StatusRuntimeException exception = Assertions.assertThrows(StatusRuntimeException.class, serviceCall);
assertEquals(expected.getCode(), exception.getStatus().getCode());

View File

@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.grpc;
import com.google.protobuf.ByteString;
import io.grpc.Channel;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -34,6 +35,7 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@@ -302,4 +304,13 @@ public class KeyTransparencyGrpcServiceTest extends SimpleBaseGrpcTest<KeyTransp
.setEntryPosition(entryPosition)
.build();
}
@Override
protected List<ServerInterceptor> customizeInterceptors(List<ServerInterceptor> serverInterceptors) {
return serverInterceptors.stream()
// For now, don't validate conformance of KeyTransparency errors since they are forwarded directly from a
// backing service
.filter(interceptor -> !(interceptor instanceof ErrorConformanceInterceptor))
.toList();
}
}

View File

@@ -427,7 +427,7 @@ class MessagesAnonymousGrpcServiceTest extends
when(spamChecker.checkForIndividualRecipientSpamGrpc(any(), any(), any(), any()))
.thenReturn(new SpamCheckResult<>(
Optional.of(GrpcResponse.withStatusException(Status.RESOURCE_EXHAUSTED.asRuntimeException())),
Optional.of(GrpcResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
@@ -793,7 +793,7 @@ class MessagesAnonymousGrpcServiceTest extends
when(spamChecker.checkForMultiRecipientSpamGrpc(any()))
.thenReturn(new SpamCheckResult<>(
Optional.of(GrpcResponse.withStatusException(Status.RESOURCE_EXHAUSTED.asRuntimeException())),
Optional.of(GrpcResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
@@ -1052,7 +1052,7 @@ class MessagesAnonymousGrpcServiceTest extends
when(spamChecker.checkForIndividualRecipientSpamGrpc(any(), any(), any(), any()))
.thenReturn(new SpamCheckResult<>(
Optional.of(GrpcResponse.withStatusException(Status.RESOURCE_EXHAUSTED.asRuntimeException())),
Optional.of(GrpcResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored
@@ -1334,8 +1334,8 @@ class MessagesAnonymousGrpcServiceTest extends
.build();
when(spamChecker.checkForMultiRecipientSpamGrpc(any()))
.thenReturn(new SpamCheckResult<>(Optional.of(
GrpcResponse.withStatusException(Status.RESOURCE_EXHAUSTED.asRuntimeException())),
.thenReturn(new SpamCheckResult<>(
Optional.of(GrpcResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored

View File

@@ -359,7 +359,7 @@ class MessagesGrpcServiceTest extends SimpleBaseGrpcTest<MessagesGrpcService, Me
when(spamChecker.checkForIndividualRecipientSpamGrpc(any(), any(), any(), any()))
.thenReturn(new SpamCheckResult<>(
Optional.of(GrpcResponse.withStatusException(Status.RESOURCE_EXHAUSTED.asRuntimeException())),
Optional.of(GrpcResponse.withStatusException(GrpcExceptions.rateLimitExceeded(null))),
Optional.empty()));
//noinspection ResultOfMethodCallIgnored

View File

@@ -17,6 +17,7 @@ import static org.whispersystems.textsecuregcm.grpc.GrpcTestUtils.assertStatusEx
import com.google.common.net.InetAddresses;
import com.google.protobuf.ByteString;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -593,4 +594,13 @@ public class ProfileAnonymousGrpcServiceTest extends SimpleBaseGrpcTest<ProfileA
Arguments.of(IdentityType.IDENTITY_TYPE_ACI, CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY, true)
);
}
@Override
protected List<ServerInterceptor> customizeInterceptors(List<ServerInterceptor> serverInterceptors) {
return serverInterceptors.stream()
// For now, don't validate error conformance because the profiles gRPC service has not been converted to the
// updated error model
.filter(interceptor -> !(interceptor instanceof ErrorConformanceInterceptor))
.toList();
}
}

View File

@@ -29,6 +29,9 @@ import com.google.common.net.InetAddresses;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import com.google.protobuf.ByteString;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
@@ -753,4 +756,13 @@ public class ProfileGrpcServiceTest extends SimpleBaseGrpcTest<ProfileGrpcServic
Arguments.of(IdentityType.IDENTITY_TYPE_ACI, CredentialType.CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY, true)
);
}
@Override
protected List<ServerInterceptor> customizeInterceptors(List<ServerInterceptor> serverInterceptors) {
return serverInterceptors.stream()
// For now, don't validate error conformance because the profiles gRPC service has not been converted to the
// updated error model
.filter(interceptor -> !(interceptor instanceof ErrorConformanceInterceptor))
.toList();
}
}

View File

@@ -9,9 +9,12 @@ import static java.util.Objects.requireNonNull;
import io.grpc.BindableService;
import io.grpc.Channel;
import io.grpc.ServerInterceptor;
import io.grpc.ServerInterceptors;
import io.grpc.stub.AbstractBlockingStub;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -113,9 +116,18 @@ public abstract class SimpleBaseGrpcTest<SERVICE extends BindableService, STUB e
protected void baseSetup() {
mocksCloseable = MockitoAnnotations.openMocks(this);
service = requireNonNull(createServiceBeforeEachTest(), "created service must not be `null`");
GrpcTestUtils.setupAuthenticatedExtension(
GRPC_SERVER_EXTENSION_AUTHENTICATED, mockAuthenticationInterceptor, mockRequestAttributesInterceptor, AUTHENTICATED_ACI, AUTHENTICATED_DEVICE_ID, service);
GrpcTestUtils.setupUnauthenticatedExtension(GRPC_SERVER_EXTENSION_UNAUTHENTICATED, mockRequestAttributesInterceptor, service);
mockAuthenticationInterceptor.setAuthenticatedDevice(AUTHENTICATED_ACI, AUTHENTICATED_DEVICE_ID);
final List<ServerInterceptor> authenticatedInterceptors =
List.of(new ValidatingInterceptor(), mockRequestAttributesInterceptor, mockAuthenticationInterceptor, new ErrorMappingInterceptor(), new ErrorConformanceInterceptor());
GRPC_SERVER_EXTENSION_AUTHENTICATED
.getServiceRegistry()
.addService(ServerInterceptors.intercept(service, customizeInterceptors(authenticatedInterceptors)));
final List<ServerInterceptor> unauthenticatedInterceptors =
List.of(new ValidatingInterceptor(), mockRequestAttributesInterceptor, new ErrorMappingInterceptor(), new ErrorConformanceInterceptor());
GRPC_SERVER_EXTENSION_UNAUTHENTICATED.getServiceRegistry()
.addService(ServerInterceptors.intercept(service, customizeInterceptors(unauthenticatedInterceptors)));
try {
authenticatedServiceStub = createStub(GRPC_SERVER_EXTENSION_AUTHENTICATED.getChannel());
unauthenticatedServiceStub = createStub(GRPC_SERVER_EXTENSION_UNAUTHENTICATED.getChannel());
@@ -152,4 +164,8 @@ public abstract class SimpleBaseGrpcTest<SERVICE extends BindableService, STUB e
protected MockAuthenticationInterceptor getMockAuthenticationInterceptor() {
return mockAuthenticationInterceptor;
}
protected List<ServerInterceptor> customizeInterceptors(List<ServerInterceptor> serverInterceptors) {
return serverInterceptors;
}
}