package com.atlassian.bitbucket.mesh.auth;

import com.atlassian.bitbucket.mesh.rpc.v1.ManagementServiceGrpc;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcHeartbeat;
import io.grpc.*;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static com.atlassian.bitbucket.mesh.rpc.MetadataKeys.KEY_TOKEN;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class AuthenticationClientInterceptorTest {

    @Mock
    private Authenticator authenticator;
    @InjectMocks
    private AuthenticationClientInterceptor interceptor;

    @SuppressWarnings("unchecked")
    @Test
    public void testInterceptCall() {
        ClientCall<RpcHeartbeat, RpcHeartbeat> call = mock(ClientCall.class);
        MethodDescriptor<RpcHeartbeat, RpcHeartbeat> methodDescriptor = ManagementServiceGrpc.getHeartbeatMethod();

        Channel channel = mock(Channel.class);
        when(channel.newCall(same(methodDescriptor), any())).thenReturn(call);

        //First: Validate the interceptor applies CallCredentials to the options
        ClientCall<RpcHeartbeat, RpcHeartbeat> actual =
                interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
        assertSame("The call should not have been wrapped", call, actual);

        ArgumentCaptor<CallOptions> optionsCaptor = ArgumentCaptor.forClass(CallOptions.class);

        verify(channel).newCall(same(methodDescriptor), optionsCaptor.capture());
        verifyNoInteractions(authenticator);

        CallOptions options = optionsCaptor.getValue();
        CallCredentials credentials = options.getCredentials();
        assertNotNull("Credentials should have been applied to the options", credentials);

        CallCredentials.RequestInfo requestInfo = mock(CallCredentials.RequestInfo.class);
        doReturn(methodDescriptor).when(requestInfo).getMethodDescriptor();

        when(authenticator.sign(eq(methodDescriptor.getFullMethodName()))).thenReturn("token");

        //Second: Validate the CallCredentials add a token to the metadata
        credentials.applyRequestMetadata(requestInfo, null, new CallCredentials.MetadataApplier() {

            @Override
            public void apply(Metadata metadata) {
                assertEquals("Token metadata should have been added", "token", metadata.get(KEY_TOKEN));
            }

            @Override
            public void fail(Status status) {
                Assert.fail("Unexpected " + status + " error while applying credentials");
            }
        });

        verify(authenticator).sign(eq(methodDescriptor.getFullMethodName()));
    }
}