|
16 | 16 | package software.amazon.awssdk.http.apache.internal;
|
17 | 17 |
|
18 | 18 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
| 19 | +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; |
19 | 20 | import static org.junit.jupiter.api.Assertions.assertEquals;
|
20 | 21 | import static org.junit.jupiter.api.Assertions.assertFalse;
|
21 | 22 | import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
@@ -790,5 +791,161 @@ void multipleOperations_StatePreservation_WorksCorrectly() throws IOException {
|
790 | 791 | assertEquals(contentLength1, contentLength2);
|
791 | 792 | assertEquals(contentLength2, contentLength3);
|
792 | 793 | }
|
| 794 | + |
| 795 | + @Test |
| 796 | + @DisplayName("markSupported should be be called everytime") |
| 797 | + void markSupported_NotCachedDuringConstruction() { |
| 798 | + // Given |
| 799 | + AtomicInteger markSupportedCalls = new AtomicInteger(0); |
| 800 | + InputStream trackingStream = new ByteArrayInputStream("test".getBytes()) { |
| 801 | + @Override |
| 802 | + public boolean markSupported() { |
| 803 | + markSupportedCalls.incrementAndGet(); |
| 804 | + return true; |
| 805 | + } |
| 806 | + }; |
| 807 | + |
| 808 | + entity = createEntity(trackingStream); |
| 809 | + assertEquals(0, markSupportedCalls.get()); |
| 810 | + |
| 811 | + // Multiple isRepeatable calls trigger new markSupported calls |
| 812 | + assertTrue(entity.isRepeatable()); |
| 813 | + assertTrue(entity.isRepeatable()); |
| 814 | + assertEquals(2, markSupportedCalls.get()); |
| 815 | + } |
| 816 | + |
| 817 | + @Test |
| 818 | + @DisplayName("ContentStreamProvider.newStream() should only be called once") |
| 819 | + void contentStreamProvider_NewStreamCalledOnce() { |
| 820 | + AtomicInteger newStreamCalls = new AtomicInteger(0); |
| 821 | + ContentStreamProvider provider = () -> { |
| 822 | + if (newStreamCalls.incrementAndGet() > 1) { |
| 823 | + throw new RuntimeException("Could not create new stream: Already created"); |
| 824 | + } |
| 825 | + return new ByteArrayInputStream("test".getBytes()); |
| 826 | + }; |
| 827 | + |
| 828 | + entity = createEntity(provider); |
| 829 | + |
| 830 | + assertEquals(1, newStreamCalls.get()); |
| 831 | + assertTrue(entity.isRepeatable()); |
| 832 | + assertFalse(entity.isChunked()); |
| 833 | + } |
| 834 | + |
| 835 | + @Test |
| 836 | + @DisplayName("writeTo should use cached markSupported for reset decision") |
| 837 | + void writeTo_UsesCachedMarkSupported() throws IOException { |
| 838 | + // Given - Stream that changes markSupported behavior |
| 839 | + AtomicInteger markSupportedCalls = new AtomicInteger(0); |
| 840 | + ByteArrayInputStream baseStream = new ByteArrayInputStream("test".getBytes()); |
| 841 | + InputStream stream = new InputStream() { |
| 842 | + @Override |
| 843 | + public int read() throws IOException { |
| 844 | + return baseStream.read(); |
| 845 | + } |
| 846 | + |
| 847 | + @Override |
| 848 | + public boolean markSupported() { |
| 849 | + return markSupportedCalls.incrementAndGet() == 1; // Only first call returns true |
| 850 | + } |
| 851 | + |
| 852 | + @Override |
| 853 | + public synchronized void reset() throws IOException { |
| 854 | + baseStream.reset(); |
| 855 | + } |
| 856 | + }; |
| 857 | + |
| 858 | + entity = createEntity(stream); |
| 859 | + |
| 860 | + // Write twice |
| 861 | + ByteArrayOutputStream output1 = new ByteArrayOutputStream(); |
| 862 | + entity.writeTo(output1); |
| 863 | + |
| 864 | + ByteArrayOutputStream output2 = new ByteArrayOutputStream(); |
| 865 | + entity.writeTo(output2); |
| 866 | + |
| 867 | + // Then - Both writes succeed using cached markSupported value |
| 868 | + assertEquals("test", output1.toString()); |
| 869 | + assertEquals("test", output2.toString()); |
| 870 | + assertEquals(1, markSupportedCalls.get()); |
| 871 | + } |
| 872 | + |
| 873 | + @Test |
| 874 | + @DisplayName("Non-repeatable stream should not attempt reset") |
| 875 | + void nonRepeatableStream_NoResetAttempt() throws IOException { |
| 876 | + // Given |
| 877 | + AtomicInteger resetCalls = new AtomicInteger(0); |
| 878 | + InputStream nonRepeatableStream = new ByteArrayInputStream("test".getBytes()) { |
| 879 | + @Override |
| 880 | + public boolean markSupported() { |
| 881 | + return false; |
| 882 | + } |
| 883 | + |
| 884 | + @Override |
| 885 | + public synchronized void reset() { |
| 886 | + resetCalls.incrementAndGet(); |
| 887 | + throw new RuntimeException("Reset not supported"); |
| 888 | + } |
| 889 | + }; |
| 890 | + |
| 891 | + entity = createEntity(nonRepeatableStream); |
| 892 | + assertFalse(entity.isRepeatable()); |
| 893 | + entity.writeTo(new ByteArrayOutputStream()); |
| 894 | + entity.writeTo(new ByteArrayOutputStream()); |
| 895 | + assertEquals(0, resetCalls.get()); |
| 896 | + } |
| 897 | + |
| 898 | + @Test |
| 899 | + @DisplayName("Stream should not be read during construction") |
| 900 | + void constructor_DoesNotReadStream() { |
| 901 | + // Given |
| 902 | + InputStream nonReadableStream = new InputStream() { |
| 903 | + @Override |
| 904 | + public int read() throws IOException { |
| 905 | + throw new IOException("Stream should not be read during construction"); |
| 906 | + } |
| 907 | + |
| 908 | + @Override |
| 909 | + public boolean markSupported() { |
| 910 | + return true; |
| 911 | + } |
| 912 | + }; |
| 913 | + assertDoesNotThrow(() -> entity = createEntity(nonReadableStream)); |
| 914 | + assertTrue(entity.isRepeatable()); |
| 915 | + } |
| 916 | + |
| 917 | + @Test |
| 918 | + @DisplayName("getContent should reuse existing stream") |
| 919 | + void getContent_ReusesExistingStream() throws IOException { |
| 920 | + InputStream originalStream = new ByteArrayInputStream("content".getBytes()); |
| 921 | + entity = createEntity(originalStream); |
| 922 | + InputStream content1 = entity.getContent(); |
| 923 | + InputStream content2 = entity.getContent(); |
| 924 | + assertSame(content1, content2); |
| 925 | + } |
| 926 | + |
| 927 | + @Test |
| 928 | + @DisplayName("Empty stream should be repeatable") |
| 929 | + void emptyStream_IsRepeatable() { |
| 930 | + // Given - No content provider |
| 931 | + HttpExecuteRequest request = HttpExecuteRequest.builder() |
| 932 | + .request(httpRequestBuilder.build()) |
| 933 | + .build(); |
| 934 | + entity = new RepeatableInputStreamRequestEntity(request); |
| 935 | + assertTrue(entity.isRepeatable()); |
| 936 | + } |
| 937 | + |
| 938 | + // Helper methods |
| 939 | + private RepeatableInputStreamRequestEntity createEntity(InputStream stream) { |
| 940 | + return createEntity(() -> stream); |
| 941 | + } |
| 942 | + |
| 943 | + private RepeatableInputStreamRequestEntity createEntity(ContentStreamProvider provider) { |
| 944 | + HttpExecuteRequest request = HttpExecuteRequest.builder() |
| 945 | + .request(httpRequestBuilder.build()) |
| 946 | + .contentStreamProvider(provider) |
| 947 | + .build(); |
| 948 | + return new RepeatableInputStreamRequestEntity(request); |
| 949 | + } |
793 | 950 | }
|
794 | 951 |
|
0 commit comments