@Override
  public Mono<ReserveUsernameHashResponse> reserveUsernameHash(final ReserveUsernameHashRequest request) {
    final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();

    if (request.getUsernameHashesCount() == 0) {
      throw Status.INVALID_ARGUMENT
          .withDescription("List of username hashes must not be empty")
          .asRuntimeException();
    }

    if (request.getUsernameHashesCount() > AccountController.MAXIMUM_USERNAME_HASHES_LIST_LENGTH) {
      throw Status.INVALID_ARGUMENT
          .withDescription(String.format("List of username hashes may have at most %d elements, but actually had %d",
              AccountController.MAXIMUM_USERNAME_HASHES_LIST_LENGTH, request.getUsernameHashesCount()))
          .asRuntimeException();
    }

    final List<byte[]> usernameHashes = new ArrayList<>(request.getUsernameHashesCount());

    for (final ByteString usernameHash : request.getUsernameHashesList()) {
      if (usernameHash.size() != AccountController.USERNAME_HASH_LENGTH) {
        throw Status.INVALID_ARGUMENT
            .withDescription(String.format("Username hash length must be %d bytes, but was actually %d",
                AccountController.USERNAME_HASH_LENGTH, usernameHash.size()))
            .asRuntimeException();
      }

      usernameHashes.add(usernameHash.toByteArray());
    }

    return rateLimiters.getUsernameReserveLimiter().validateReactive(authenticatedDevice.accountIdentifier())
        .then(Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())))
        .map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
        .flatMap(account -> Mono.fromFuture(() -> accountsManager.reserveUsernameHash(account, usernameHashes)))
        .map(reservation -> ReserveUsernameHashResponse.newBuilder()
            .setUsernameHash(ByteString.copyFrom(reservation.reservedUsernameHash()))
            .build())
        .onErrorReturn(UsernameHashNotAvailableException.class, ReserveUsernameHashResponse.newBuilder()
            .setError(ReserveUsernameHashError.newBuilder()
                .setErrorType(ReserveUsernameHashErrorType.RESERVE_USERNAME_HASH_ERROR_TYPE_NO_HASHES_AVAILABLE)
                .build())
            .build());
  }
@Test
  void reserveUsernameHashNotAvailable() {
    final Account account = mock(Account.class);

    when(accountsManager.getByAccountIdentifierAsync(AUTHENTICATED_ACI))
        .thenReturn(CompletableFuture.completedFuture(Optional.of(account)));

    final byte[] usernameHash = TestRandomUtil.nextBytes(AccountController.USERNAME_HASH_LENGTH);

    when(accountsManager.reserveUsernameHash(any(), any()))
        .thenReturn(CompletableFuture.failedFuture(new UsernameHashNotAvailableException()));

    final ReserveUsernameHashResponse expectedResponse = ReserveUsernameHashResponse.newBuilder()
        .setError(ReserveUsernameHashError.newBuilder()
            .setErrorType(ReserveUsernameHashErrorType.RESERVE_USERNAME_HASH_ERROR_TYPE_NO_HASHES_AVAILABLE)
            .build())
        .build();

    assertEquals(expectedResponse,
        authenticatedServiceStub().reserveUsernameHash(ReserveUsernameHashRequest.newBuilder()
            .addUsernameHashes(ByteString.copyFrom(usernameHash))
            .build()));
  }