blob: cfd697219833be16b8e6e6f966b0a1d719dff3dc [file] [log] [blame]
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc.internal;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.grpc.Attributes;
import io.grpc.CallCredentials;
import io.grpc.CallCredentials.MetadataApplier;
import io.grpc.CallOptions;
import io.grpc.IntegerMarshaller;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.SecurityLevel;
import io.grpc.Status;
import io.grpc.StringMarshaller;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Unit test for {@link CallCredentials} applying functionality implemented by {@link
* CallCredentialsApplyingTransportFactory} and {@link MetadataApplierImpl}.
*/
@RunWith(JUnit4.class)
public class CallCredentialsApplyingTest {
@Mock
private ClientTransportFactory mockTransportFactory;
@Mock
private ConnectionClientTransport mockTransport;
@Mock
private ClientStream mockStream;
@Mock
private CallCredentials mockCreds;
@Mock
private Executor mockExecutor;
@Mock
private SocketAddress address;
private static final String AUTHORITY = "testauthority";
private static final String USER_AGENT = "testuseragent";
private static final Attributes.Key<String> ATTR_KEY = Attributes.Key.of("somekey");
private static final String ATTR_VALUE = "somevalue";
private static final MethodDescriptor<String, Integer> method =
MethodDescriptor.<String, Integer>newBuilder()
.setType(MethodDescriptor.MethodType.UNKNOWN)
.setFullMethodName("/service/method")
.setRequestMarshaller(new StringMarshaller())
.setResponseMarshaller(new IntegerMarshaller())
.build();
private static final Metadata.Key<String> ORIG_HEADER_KEY =
Metadata.Key.of("header1", Metadata.ASCII_STRING_MARSHALLER);
private static final String ORIG_HEADER_VALUE = "some original header value";
private static final Metadata.Key<String> CREDS_KEY =
Metadata.Key.of("test-creds", Metadata.ASCII_STRING_MARSHALLER);
private static final String CREDS_VALUE = "some credentials";
private final Metadata origHeaders = new Metadata();
private final StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(
method.getFullMethodName(), NoopStatsContextFactory.INSTANCE, GrpcUtil.STOPWATCH_SUPPLIER);
private ForwardingConnectionClientTransport transport;
private CallOptions callOptions;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
origHeaders.put(ORIG_HEADER_KEY, ORIG_HEADER_VALUE);
when(mockTransportFactory.newClientTransport(address, AUTHORITY, USER_AGENT))
.thenReturn(mockTransport);
when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class),
any(StatsTraceContext.class)))
.thenReturn(mockStream);
ClientTransportFactory transportFactory = new CallCredentialsApplyingTransportFactory(
mockTransportFactory, mockExecutor);
transport = (ForwardingConnectionClientTransport) transportFactory.newClientTransport(
address, AUTHORITY, USER_AGENT);
callOptions = CallOptions.DEFAULT.withCallCredentials(mockCreds);
verify(mockTransportFactory).newClientTransport(address, AUTHORITY, USER_AGENT);
assertSame(mockTransport, transport.delegate());
}
@Test
public void parameterPropagation_base() {
Attributes transportAttrs = Attributes.newBuilder().set(ATTR_KEY, ATTR_VALUE).build();
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
transport.newStream(method, origHeaders, callOptions, statsTraceCtx);
ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(), same(mockExecutor),
any(MetadataApplier.class));
Attributes attrs = attrsCaptor.getValue();
assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
assertSame(AUTHORITY, attrs.get(CallCredentials.ATTR_AUTHORITY));
assertSame(SecurityLevel.NONE, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
}
@Test
public void parameterPropagation_overrideByTransport() {
Attributes transportAttrs = Attributes.newBuilder()
.set(ATTR_KEY, ATTR_VALUE)
.set(CallCredentials.ATTR_AUTHORITY, "transport-override-authority")
.set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY)
.build();
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
transport.newStream(method, origHeaders, callOptions, statsTraceCtx);
ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(), same(mockExecutor),
any(MetadataApplier.class));
Attributes attrs = attrsCaptor.getValue();
assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
assertEquals("transport-override-authority", attrs.get(CallCredentials.ATTR_AUTHORITY));
assertSame(SecurityLevel.INTEGRITY, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
}
@Test
public void parameterPropagation_overrideByCallOptions() {
Attributes transportAttrs = Attributes.newBuilder()
.set(ATTR_KEY, ATTR_VALUE)
.set(CallCredentials.ATTR_AUTHORITY, "transport-override-authority")
.set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY)
.build();
when(mockTransport.getAttributes()).thenReturn(transportAttrs);
Executor anotherExecutor = mock(Executor.class);
transport.newStream(method, origHeaders,
callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor),
statsTraceCtx);
ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(),
same(anotherExecutor), any(MetadataApplier.class));
Attributes attrs = attrsCaptor.getValue();
assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
assertEquals("calloptions-authority", attrs.get(CallCredentials.ATTR_AUTHORITY));
assertSame(SecurityLevel.INTEGRITY, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
}
@Test
public void applyMetadata_inline() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
MetadataApplier applier = (MetadataApplier) invocation.getArguments()[3];
Metadata headers = new Metadata();
headers.put(CREDS_KEY, CREDS_VALUE);
applier.apply(headers);
return null;
}
}).when(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
same(mockExecutor), any(MetadataApplier.class));
ClientStream stream = transport.newStream(method, origHeaders, callOptions, statsTraceCtx);
verify(mockTransport).newStream(method, origHeaders, callOptions, statsTraceCtx);
assertSame(mockStream, stream);
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
}
@Test
public void fail_inline() {
final Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds");
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
MetadataApplier applier = (MetadataApplier) invocation.getArguments()[3];
applier.fail(error);
return null;
}
}).when(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
same(mockExecutor), any(MetadataApplier.class));
FailingClientStream stream =
(FailingClientStream) transport.newStream(method, origHeaders, callOptions, statsTraceCtx);
verify(mockTransport, never()).newStream(method, origHeaders, callOptions, statsTraceCtx);
assertSame(error, stream.getError());
}
@Test
public void applyMetadata_delayed() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
// Will call applyRequestMetadata(), which is no-op.
DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions,
statsTraceCtx);
ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
same(mockExecutor), applierCaptor.capture());
verify(mockTransport, never()).newStream(method, origHeaders, callOptions, statsTraceCtx);
Metadata headers = new Metadata();
headers.put(CREDS_KEY, CREDS_VALUE);
applierCaptor.getValue().apply(headers);
verify(mockTransport).newStream(method, origHeaders, callOptions, statsTraceCtx);
assertSame(mockStream, stream.getRealStream());
assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
}
@Test
public void fail_delayed() {
when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
// Will call applyRequestMetadata(), which is no-op.
DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions,
statsTraceCtx);
ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
verify(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
same(mockExecutor), applierCaptor.capture());
Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds");
applierCaptor.getValue().fail(error);
verify(mockTransport, never()).newStream(method, origHeaders, callOptions, statsTraceCtx);
FailingClientStream failingStream = (FailingClientStream) stream.getRealStream();
assertSame(error, failingStream.getError());
}
@Test
public void noCreds() {
callOptions = callOptions.withCallCredentials(null);
ClientStream stream = transport.newStream(method, origHeaders, callOptions, statsTraceCtx);
verify(mockTransport).newStream(method, origHeaders, callOptions, statsTraceCtx);
assertSame(mockStream, stream);
assertNull(origHeaders.get(CREDS_KEY));
assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
}
}