/* Copyright (c) 2015 LinkedIn Corp. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.linkedin.multipart; import com.linkedin.data.ByteString; import com.linkedin.multipart.exceptions.MultiPartReaderFinishedException; import com.linkedin.multipart.exceptions.SinglePartBindException; import com.linkedin.multipart.exceptions.SinglePartFinishedException; import com.linkedin.multipart.exceptions.SinglePartNotInitializedException; import com.linkedin.multipart.exceptions.StreamBusyException; import com.linkedin.r2.message.stream.StreamRequest; import com.linkedin.r2.message.stream.entitystream.EntityStream; import java.util.Collections; import org.testng.Assert; import org.testng.annotations.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * These tests will verify that the correct exceptions are thrown in the face of unorthodox clients. * * @author Karim Vidhani */ public class TestMIMEReaderStateTransitions { private static final EmptyMultiPartMIMEReaderCallback EMPTY_MULTI_PART_MIME_READER_CALLBACK = new EmptyMultiPartMIMEReaderCallback(); private static final EmptySinglePartMIMEReaderCallback EMPTY_SINGLE_PART_MIME_READER_CALLBACK = new EmptySinglePartMIMEReaderCallback(); //MultiPartMIMEReader exceptions: @Test public void testRegisterCallbackMultiPartMIMEReader() { final EntityStream entityStream = mock(EntityStream.class); final StreamRequest streamRequest = mock(StreamRequest.class); when(streamRequest.getEntityStream()).thenReturn(entityStream); when(streamRequest.getHeader(MultiPartMIMEUtils.CONTENT_TYPE_HEADER)).thenReturn("multipart/mixed; boundary=\"--123\""); MultiPartMIMEReader reader = MultiPartMIMEReader.createAndAcquireStream(streamRequest); //Test each possible exception: reader.setState(MultiPartMIMEReader.MultiPartReaderState.FINISHED); try { reader.registerReaderCallback(EMPTY_MULTI_PART_MIME_READER_CALLBACK); Assert.fail(); } catch (MultiPartReaderFinishedException multiPartReaderFinishedException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.READING_EPILOGUE); try { reader.registerReaderCallback(EMPTY_MULTI_PART_MIME_READER_CALLBACK); Assert.fail(); } catch (MultiPartReaderFinishedException multiPartReaderFinishedException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.CALLBACK_BOUND_AND_READING_PREAMBLE); try { reader.registerReaderCallback(EMPTY_MULTI_PART_MIME_READER_CALLBACK); Assert.fail(); } catch (StreamBusyException streamBusyException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.DRAINING); try { reader.registerReaderCallback(EMPTY_MULTI_PART_MIME_READER_CALLBACK); Assert.fail(); } catch (StreamBusyException streamBusyException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.READING_PARTS); //This is a desired top level reader state final MultiPartMIMEReader.SinglePartMIMEReader singlePartMIMEReader = reader.new SinglePartMIMEReader(Collections.<String, String>emptyMap()); singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.REQUESTED_DATA); //This is a undesired single part state reader.setCurrentSinglePartMIMEReader(singlePartMIMEReader); try { reader.registerReaderCallback(EMPTY_MULTI_PART_MIME_READER_CALLBACK); Assert.fail(); } catch (StreamBusyException streamBusyException) { } } @Test public void testDrainAllPartsMultiPartMIMEReader() { final EntityStream entityStream = mock(EntityStream.class); final StreamRequest streamRequest = mock(StreamRequest.class); when(streamRequest.getEntityStream()).thenReturn(entityStream); when(streamRequest.getHeader(MultiPartMIMEUtils.CONTENT_TYPE_HEADER)).thenReturn("multipart/mixed; boundary=\"--123\""); MultiPartMIMEReader reader = MultiPartMIMEReader.createAndAcquireStream(streamRequest); //Test each possible exception: reader.setState(MultiPartMIMEReader.MultiPartReaderState.FINISHED); try { reader.drainAllParts(); Assert.fail(); } catch (MultiPartReaderFinishedException multiPartReaderFinishedException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.READING_EPILOGUE); try { reader.drainAllParts(); Assert.fail(); } catch (MultiPartReaderFinishedException multiPartReaderFinishedException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.CALLBACK_BOUND_AND_READING_PREAMBLE); try { reader.drainAllParts(); Assert.fail(); } catch (StreamBusyException streamBusyException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.DRAINING); try { reader.drainAllParts(); Assert.fail(); } catch (StreamBusyException streamBusyException) { } reader.setState(MultiPartMIMEReader.MultiPartReaderState.READING_PARTS); //This is the desired top level reader state final MultiPartMIMEReader.SinglePartMIMEReader singlePartMIMEReader = reader.new SinglePartMIMEReader(Collections.<String, String>emptyMap()); singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.REQUESTED_DATA); //This is a undesired single part state reader.setCurrentSinglePartMIMEReader(singlePartMIMEReader); try { reader.drainAllParts(); Assert.fail(); } catch (StreamBusyException streamBusyException) { } } /////////////////////////////////////////////////////////////////////////////////////// //SinglePartMIMEReader exceptions: @Test public void testRegisterSinglePartMIMEReaderCallbackTwice() { final EntityStream entityStream = mock(EntityStream.class); final StreamRequest streamRequest = mock(StreamRequest.class); when(streamRequest.getEntityStream()).thenReturn(entityStream); when(streamRequest.getHeader(MultiPartMIMEUtils.CONTENT_TYPE_HEADER)).thenReturn("multipart/mixed; boundary=\"--123\""); MultiPartMIMEReader reader = MultiPartMIMEReader.createAndAcquireStream(streamRequest); final MultiPartMIMEReader.SinglePartMIMEReader singlePartMIMEReader = reader.new SinglePartMIMEReader(Collections.<String, String>emptyMap()); singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.REQUESTED_DATA); //This is a undesired single part state try { singlePartMIMEReader.registerReaderCallback(EMPTY_SINGLE_PART_MIME_READER_CALLBACK); Assert.fail(); } catch (SinglePartBindException singlePartBindException) { } } @Test public void testSinglePartMIMEReaderVerifyState() { //This will cover drainPart() and most of requestPartData(). //The caveat is that requestPartData() requires a callback to be registered. This //will be covered in the next test. final EntityStream entityStream = mock(EntityStream.class); final StreamRequest streamRequest = mock(StreamRequest.class); when(streamRequest.getEntityStream()).thenReturn(entityStream); when(streamRequest.getHeader(MultiPartMIMEUtils.CONTENT_TYPE_HEADER)).thenReturn("multipart/mixed; boundary=\"--123\""); MultiPartMIMEReader reader = MultiPartMIMEReader.createAndAcquireStream(streamRequest); final MultiPartMIMEReader.SinglePartMIMEReader singlePartMIMEReader = reader.new SinglePartMIMEReader(Collections.<String, String>emptyMap()); singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.FINISHED); try { singlePartMIMEReader.verifyUsableState(); Assert.fail(); } catch (SinglePartFinishedException singlePartFinishedException) { } singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.REQUESTED_DATA); try { singlePartMIMEReader.verifyUsableState(); Assert.fail(); } catch (StreamBusyException streamBusyException) { } singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.REQUESTED_DRAIN); try { singlePartMIMEReader.verifyUsableState(); Assert.fail(); } catch (StreamBusyException streamBusyException) { } } @Test public void testSinglePartMIMEReaderRequestData() { final EntityStream entityStream = mock(EntityStream.class); final StreamRequest streamRequest = mock(StreamRequest.class); when(streamRequest.getEntityStream()).thenReturn(entityStream); when(streamRequest.getHeader(MultiPartMIMEUtils.CONTENT_TYPE_HEADER)).thenReturn("multipart/mixed; boundary=\"--123\""); MultiPartMIMEReader reader = MultiPartMIMEReader.createAndAcquireStream(streamRequest); final MultiPartMIMEReader.SinglePartMIMEReader singlePartMIMEReader = reader.new SinglePartMIMEReader(Collections.<String, String>emptyMap()); singlePartMIMEReader.setState(MultiPartMIMEReader.SingleReaderState.CREATED); try { singlePartMIMEReader.requestPartData(); Assert.fail(); } catch (SinglePartNotInitializedException singlePartNotInitializedException) { } } private static final class EmptyMultiPartMIMEReaderCallback implements MultiPartMIMEReaderCallback { @Override public void onNewPart(MultiPartMIMEReader.SinglePartMIMEReader singlePartMIMEReader) { } @Override public void onFinished() { } @Override public void onDrainComplete() { } @Override public void onStreamError(Throwable throwable) { } } private static final class EmptySinglePartMIMEReaderCallback implements SinglePartMIMEReaderCallback { @Override public void onPartDataAvailable(ByteString partData) { } @Override public void onFinished() { } @Override public void onDrainComplete() { } @Override public void onStreamError(Throwable throwable) { } } }