/**
   * Calls each {@code AsyncServerAuthContext} in parallel to clean the client subject and only
   * return a successful promise if all complete successfully otherwise returns the first exception
   * in a failed promise.
   *
   * @param context {@inheritDoc}
   * @param clientSubject {@inheritDoc}
   * @return {@inheritDoc}
   */
  @Override
  public Promise<Void, AuthenticationException> cleanSubject(
      MessageContext context, Subject clientSubject) {

    List<Promise<Void, AuthenticationException>> promises = new ArrayList<>();
    for (AsyncServerAuthModule serverAuthModule : authModules) {
      promises.add(serverAuthModule.cleanSubject(context, clientSubject));
    }
    return Promises.when(promises).thenAsync(ON_SUCCESS_RETURN_VOID);
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptGetModuleIdCall() {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    String moduleId = asyncAuthModule.getModuleId();

    // Then
    Assertions.assertThat(moduleId).isEqualTo(authModule.getClass().getCanonicalName());
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptGetSupportedMessageTypesCall() {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);

    given(authModule.getSupportedMessageTypes())
        .willReturn(new Class<?>[] {Request.class, Response.class});

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    asyncAuthModule.getSupportedMessageTypes();

    // Then
    verify(authModule).getSupportedMessageTypes();
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptSuccessfulCleanSubjectCall()
      throws AuthException {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);
    MessageInfoContext messageInfo = mock(MessageInfoContext.class);
    Subject clientSubject = new Subject();

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    Promise<Void, AuthenticationException> promise =
        asyncAuthModule.cleanSubject(messageInfo, clientSubject);

    // Then
    assertThat(promise).succeeded().withObject().isNull();
    verify(authModule).cleanSubject(any(MessageInfo.class), eq(clientSubject));
  }
  /**
   * Secures the response message using the same {@code AsyncServerAuthModule} that authenticated
   * the incoming request message.
   *
   * <p>If no {@code AsyncServerAuthModule} authenticated the incoming request message, then this
   * method should not have been called and a failed promise will be return with an {@code
   * AuthenticationException}.
   *
   * @param context {@inheritDoc}
   * @param serviceSubject {@inheritDoc}
   * @return {@inheritDoc}
   */
  @Override
  public Promise<AuthStatus, AuthenticationException> secureResponse(
      MessageContext context, Subject serviceSubject) {
    FallbackAuthContextState state = context.getState(this);

    if (state.getAuthenticatedAuthModuleIndex() < 0) {
      return Promises.newExceptionPromise(
          new AuthenticationException(
              "No auth module authenticated the incoming request message. "
                  + "Cannot secure response message."));
    }
    AsyncServerAuthModule authModule = authModules.get(state.getAuthenticatedAuthModuleIndex());
    logger.debug(
        "Using authenticating auth module from private context map, {}, to secure the response",
        authModule.getModuleId());

    return authModule.secureResponse(context, serviceSubject);
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptFailedCleanSubjectCall() throws AuthException {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);
    MessageInfoContext messageInfo = mock(MessageInfoContext.class);
    Subject clientSubject = new Subject();

    doThrow(AuthException.class)
        .when(authModule)
        .cleanSubject(any(MessageInfo.class), eq(clientSubject));

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    Promise<Void, AuthenticationException> promise =
        asyncAuthModule.cleanSubject(messageInfo, clientSubject);

    // Then
    assertThat(promise).failedWithException().isInstanceOf(AuthenticationException.class);
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptSuccessfulSecureResponseCall()
      throws AuthException {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);
    MessageInfoContext messageInfo = mock(MessageInfoContext.class);
    Subject serviceSubject = new Subject();

    given(authModule.secureResponse(any(MessageInfo.class), eq(serviceSubject)))
        .willReturn(AuthStatus.SEND_SUCCESS);

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    Promise<AuthStatus, AuthenticationException> promise =
        asyncAuthModule.secureResponse(messageInfo, serviceSubject);

    // Then
    assertThat(promise).succeeded().withObject().isEqualTo(AuthStatus.SEND_SUCCESS);
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptSuccessfulGetInitializeCall()
      throws AuthException {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);
    MessagePolicy requestPolicy = mock(MessagePolicy.class);
    MessagePolicy responsePolicy = mock(MessagePolicy.class);
    CallbackHandler handler = mock(CallbackHandler.class);
    Map<String, Object> options = Collections.emptyMap();

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    Promise<Void, AuthenticationException> promise =
        asyncAuthModule.initialize(requestPolicy, responsePolicy, handler, options);

    // Then
    assertThat(promise).succeeded().withObject().isNull();
    verify(authModule).initialize(requestPolicy, responsePolicy, handler, options);
  }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptFailedSecureResponseCall()
      throws AuthException {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);
    MessageInfoContext messageInfo = mock(MessageInfoContext.class);
    Subject serviceSubject = new Subject();

    doThrow(AuthException.class)
        .when(authModule)
        .secureResponse(any(MessageInfo.class), eq(serviceSubject));

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    Promise<AuthStatus, AuthenticationException> promise =
        asyncAuthModule.secureResponse(messageInfo, serviceSubject);

    // Then
    assertThat(promise).failedWithException().isInstanceOf(AuthenticationException.class);
  }
 private Promise<AuthStatus, AuthenticationException> validateRequest(
     final MessageInfoContext messageInfo,
     final Subject clientSubject,
     final Subject serviceSubject) {
   if (position < authModules.size()) {
     final AsyncServerAuthModule authModule = authModules.get(position);
     return authModule
         .validateRequest(messageInfo, clientSubject, serviceSubject)
         .thenOnResult(
             new ResultHandler<AuthStatus>() {
               @Override
               public void handleResult(AuthStatus authStatus) {
                 if (isSuccess(authStatus)) {
                   /*
                    * Save the index of the authenticating module so that it can
                    * be retrieved when securing the response
                    */
                   logger.trace(
                       "Adding authenticating auth module to private context map, {}",
                       authModule.getClass().getSimpleName());
                   state.setAuthenticatedAuthModuleIndex(position);
                 }
               }
             })
         .thenAsync(
             new AsyncFunction<AuthStatus, AuthStatus, AuthenticationException>() {
               @Override
               public Promise<AuthStatus, AuthenticationException> apply(AuthStatus authStatus) {
                 if (isSendFailure(authStatus)) {
                   return next().validateRequest(messageInfo, clientSubject, serviceSubject);
                 } else {
                   return Promises.newResultPromise(authStatus);
                 }
               }
             });
   } else {
     return Promises.newResultPromise(AuthStatus.SEND_FAILURE);
   }
 }
  @Test
  public void adaptedAsyncServerAuthModuleShouldAdaptFailedGetInitializeCall()
      throws AuthException {

    // Given
    ServerAuthModule authModule = mock(ServerAuthModule.class);
    MessagePolicy requestPolicy = mock(MessagePolicy.class);
    MessagePolicy responsePolicy = mock(MessagePolicy.class);
    CallbackHandler handler = mock(CallbackHandler.class);
    Map<String, Object> options = Collections.emptyMap();

    doThrow(AuthException.class)
        .when(authModule)
        .initialize(requestPolicy, responsePolicy, handler, options);

    // When
    AsyncServerAuthModule asyncAuthModule = JaspiAdapters.adapt(authModule);
    Promise<Void, AuthenticationException> promise =
        asyncAuthModule.initialize(requestPolicy, responsePolicy, handler, options);

    // Then
    assertThat(promise).failedWithException().isInstanceOf(AuthenticationException.class);
  }