@Override
  public Mono<SetUsernameLinkResponse> setUsernameLink(final SetUsernameLinkRequest request) {
    final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();

    if (request.getUsernameCiphertext().isEmpty() || request.getUsernameCiphertext().size() > EncryptedUsername.MAX_SIZE) {
      throw Status.INVALID_ARGUMENT
          .withDescription(String.format("Username ciphertext must not be empty and must be shorter than %d bytes", EncryptedUsername.MAX_SIZE))
          .asRuntimeException();
    }

    return rateLimiters.getUsernameLinkOperationLimiter().validateReactive(authenticatedDevice.accountIdentifier())
        .then(Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())))
        .map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
        .flatMap(account -> {
          if (account.getUsernameHash().isEmpty()) {
            return Mono.error(Status.FAILED_PRECONDITION
                .withDescription("Account does not have a username hash")
                .asRuntimeException());
          }

          final UUID linkHandle;
          if (request.getKeepLinkHandle() && account.getUsernameLinkHandle() != null) {
            linkHandle = account.getUsernameLinkHandle();
          } else {
            linkHandle = UUID.randomUUID();
          }

          return Mono.fromFuture(() -> accountsManager.updateAsync(account, a -> a.setUsernameLinkDetails(linkHandle, request.getUsernameCiphertext().toByteArray())))
              .thenReturn(linkHandle);
        })
        .map(linkHandle -> SetUsernameLinkResponse.newBuilder()
            .setUsernameLinkHandle(UUIDUtil.toByteString(linkHandle))
            .build());
  }
@Test
  void setUsernameLinkMissingUsernameHash() {
    final Account account = mock(Account.class);
    when(account.getUsernameHash()).thenReturn(Optional.empty());

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

    final byte[] usernameCiphertext = TestRandomUtil.nextBytes(EncryptedUsername.MAX_SIZE);

    //noinspection ResultOfMethodCallIgnored
    GrpcTestUtils.assertStatusException(Status.FAILED_PRECONDITION,
        () -> authenticatedServiceStub().setUsernameLink(SetUsernameLinkRequest.newBuilder()
            .setUsernameCiphertext(ByteString.copyFrom(usernameCiphertext))
            .build()));
  }