Add controllers/service implementations for receiving call quality survey responses

This commit is contained in:
Jon Chambers
2025-10-09 13:30:21 -04:00
committed by Jon Chambers
parent c68e3103c4
commit 9378b9a6e6
6 changed files with 282 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.controllers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import io.dropwizard.auth.AuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.metrics.CallQualitySurveyManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestRemoteAddressFilterProvider;
@ExtendWith(DropwizardExtensionsSupport.class)
class CallQualitySurveyControllerTest {
private static final CallQualitySurveyManager CALL_QUALITY_SURVEY_MANAGER = mock(CallQualitySurveyManager.class);
private static final String USER_AGENT = "Signal-iOS/7.78.0.1041 iOS/18.3.2 libsignal/0.80.3";
private static final String REMOTE_ADDRESS = "127.0.0.1";
private static final ResourceExtension RESOURCE_EXTENSION = ResourceExtension.builder()
.addProvider(AuthHelper.getAuthFilter())
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class))
.addProvider(new RateLimitExceededExceptionMapper())
.addProvider(new TestRemoteAddressFilterProvider(REMOTE_ADDRESS))
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new CallQualitySurveyController(CALL_QUALITY_SURVEY_MANAGER))
.build();
@BeforeEach
void setUp() {
reset(CALL_QUALITY_SURVEY_MANAGER);
}
@Test
void submitCallQualitySurvey() {
final SubmitCallQualitySurveyRequest request = SubmitCallQualitySurveyRequest.getDefaultInstance();
try (final Response response = RESOURCE_EXTENSION.getJerseyTest()
.target("/v1/call_quality_survey")
.request()
.header("User-Agent", USER_AGENT)
.put(Entity.entity(request.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE))) {
assertEquals(204, response.getStatus());
verify(CALL_QUALITY_SURVEY_MANAGER).submitCallQualitySurvey(request, REMOTE_ADDRESS, USER_AGENT);
}
}
@Test
void submitCallQualitySurveyAuthenticated() {
final SubmitCallQualitySurveyRequest request = SubmitCallQualitySurveyRequest.getDefaultInstance();
try (final Response response = RESOURCE_EXTENSION.getJerseyTest()
.target("/v1/call_quality_survey")
.request()
.header("User-Agent", USER_AGENT)
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(request.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE))) {
assertEquals(403, response.getStatus());
verify(CALL_QUALITY_SURVEY_MANAGER, never()).submitCallQualitySurvey(any(), any(), any());
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.grpc;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.net.InetAddresses;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.signal.chat.calling.quality.CallQualityGrpc;
import org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.CallQualitySurveyManager;
class CallQualitySurveyGrpcServiceTest extends SimpleBaseGrpcTest<CallQualitySurveyGrpcService, CallQualityGrpc.CallQualityBlockingStub> {
@Mock
private CallQualitySurveyManager callQualitySurveyManager;
@Mock
private RateLimiter rateLimiter;
private static final String USER_AGENT = "Signal-iOS/7.78.0.1041 iOS/18.3.2 libsignal/0.80.3";
private static final String REMOTE_ADDRESS = "127.0.0.1";
@BeforeEach
void setUp() {
getMockRequestAttributesInterceptor()
.setRequestAttributes(new RequestAttributes(InetAddresses.forString(REMOTE_ADDRESS), USER_AGENT, null));
}
@Override
protected CallQualitySurveyGrpcService createServiceBeforeEachTest() {
final RateLimiters rateLimiters = mock(RateLimiters.class);
when(rateLimiters.getSubmitCallQualitySurveyLimiter()).thenReturn(rateLimiter);
return new CallQualitySurveyGrpcService(callQualitySurveyManager, rateLimiters);
}
@Test
void submitCallQualitySurvey() {
final SubmitCallQualitySurveyRequest request = SubmitCallQualitySurveyRequest.getDefaultInstance();
assertDoesNotThrow(() -> unauthenticatedServiceStub().submitCallQualitySurvey(request));
verify(callQualitySurveyManager).submitCallQualitySurvey(request, REMOTE_ADDRESS, USER_AGENT);
}
@Test
void submitCallQualitySurveyRateLimited() throws RateLimitExceededException {
final Duration retryAfter = Duration.ofMinutes(17);
doThrow(new RateLimitExceededException(retryAfter))
.when(rateLimiter).validate(REMOTE_ADDRESS);
//noinspection ResultOfMethodCallIgnored
GrpcTestUtils.assertRateLimitExceeded(retryAfter,
() -> unauthenticatedServiceStub().submitCallQualitySurvey(SubmitCallQualitySurveyRequest.getDefaultInstance()));
}
}