/* * 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 com.google.common.base.Charsets.US_ASCII; import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.same; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import io.grpc.Metadata; import io.grpc.Status; import io.grpc.Status.Code; 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.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** Unit tests for {@link Http2ClientStreamTransportState}. */ @RunWith(JUnit4.class) public class Http2ClientStreamTransportStateTest { @Mock private ClientStreamListener mockListener; @Captor private ArgumentCaptor<Status> statusCaptor; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void transportHeadersReceived_notifiesListener() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); verify(mockListener, never()).closed(any(Status.class), any(Metadata.class)); verify(mockListener).headersRead(headers); } @Test public void transportHeadersReceived_doesntRequire200() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "500"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); verify(mockListener, never()).closed(any(Status.class), any(Metadata.class)); verify(mockListener).headersRead(headers); } @Test public void transportHeadersReceived_noHttpStatus() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); state.transportDataReceived(ReadableBuffers.empty(), true); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(headers)); assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode()); } @Test public void transportHeadersReceived_wrongContentType_200() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); state.transportDataReceived(ReadableBuffers.empty(), true); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(headers)); assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode()); assertTrue(statusCaptor.getValue().getDescription().contains("200")); } @Test public void transportHeadersReceived_wrongContentType_401() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); state.transportDataReceived(ReadableBuffers.empty(), true); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(headers)); assertEquals(Code.UNAUTHENTICATED, statusCaptor.getValue().getCode()); assertTrue(statusCaptor.getValue().getDescription().contains("401")); assertTrue(statusCaptor.getValue().getDescription().contains("text/html")); } @Test public void transportHeadersReceived_handles_1xx() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata infoHeaders = new Metadata(); infoHeaders.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "100"); state.transportHeadersReceived(infoHeaders); Metadata infoHeaders2 = new Metadata(); infoHeaders2.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "101"); state.transportHeadersReceived(infoHeaders2); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); verify(mockListener, never()).closed(any(Status.class), any(Metadata.class)); verify(mockListener).headersRead(headers); } @Test public void transportHeadersReceived_twice() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); Metadata headersAgain = new Metadata(); state.transportHeadersReceived(headersAgain); state.transportDataReceived(ReadableBuffers.empty(), true); verify(mockListener).headersRead(headers); verify(mockListener).closed(statusCaptor.capture(), same(headersAgain)); assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode()); assertTrue(statusCaptor.getValue().getDescription().contains("twice")); } @Test public void transportHeadersReceived_unknownAndTwiceLogsSecondHeaders() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); Metadata headersAgain = new Metadata(); String testString = "This is a test"; headersAgain.put(Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER), testString); state.transportHeadersReceived(headersAgain); state.transportDataReceived(ReadableBuffers.empty(), true); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(headers)); assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode()); assertTrue(statusCaptor.getValue().getDescription().contains(testString)); } @Test public void transportDataReceived_noHeaderReceived() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); String testString = "This is a test"; state.transportDataReceived(ReadableBuffers.wrap(testString.getBytes(US_ASCII)), true); verify(mockListener).closed(statusCaptor.capture(), any(Metadata.class)); assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode()); } @Test public void transportDataReceived_debugData() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html"); state.transportHeadersReceived(headers); String testString = "This is a test"; state.transportDataReceived(ReadableBuffers.wrap(testString.getBytes(US_ASCII)), true); verify(mockListener).closed(statusCaptor.capture(), same(headers)); assertTrue(statusCaptor.getValue().getDescription().contains(testString)); } @Test public void transportTrailersReceived_notifiesListener() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0"); state.transportTrailersReceived(trailers); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(Status.OK, trailers); } @Test public void transportTrailersReceived_afterHeaders() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0"); state.transportTrailersReceived(trailers); verify(mockListener).headersRead(headers); verify(mockListener).closed(Status.OK, trailers); } @Test public void transportTrailersReceived_observesStatus() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "1"); state.transportTrailersReceived(trailers); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(Status.CANCELLED, trailers); } @Test public void transportTrailersReceived_missingStatusUsesHttpStatus() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportTrailersReceived(trailers); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(trailers)); assertEquals(Code.UNAUTHENTICATED, statusCaptor.getValue().getCode()); assertTrue(statusCaptor.getValue().getDescription().contains("401")); } @Test public void transportTrailersReceived_missingHttpStatus() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0"); state.transportTrailersReceived(trailers); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(trailers)); assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode()); } @Test public void transportTrailersReceived_missingStatusAndMissingHttpStatus() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportTrailersReceived(trailers); verify(mockListener, never()).headersRead(any(Metadata.class)); verify(mockListener).closed(statusCaptor.capture(), same(trailers)); assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode()); } @Test public void transportTrailersReceived_missingStatusAfterHeadersIgnoresHttpStatus() { BaseTransportState state = new BaseTransportState(); state.setListener(mockListener); Metadata headers = new Metadata(); headers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "200"); headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "application/grpc"); state.transportHeadersReceived(headers); Metadata trailers = new Metadata(); trailers.put(Metadata.Key.of(":status", Metadata.ASCII_STRING_MARSHALLER), "401"); state.transportTrailersReceived(trailers); verify(mockListener).headersRead(headers); verify(mockListener).closed(statusCaptor.capture(), same(trailers)); assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode()); } private static class BaseTransportState extends Http2ClientStreamTransportState { public BaseTransportState() { super(DEFAULT_MAX_MESSAGE_SIZE, StatsTraceContext.NOOP); } @Override protected void http2ProcessingFailed(Status status, Metadata trailers) { transportReportStatus(status, false, trailers); } @Override protected void deframeFailed(Throwable cause) {} @Override public void bytesRead(int processedBytes) {} } }