View Javadoc
1   /*
2    * The MIT License (MIT)
3    *
4    * Copyright (c) 2013-2026 The Coveralls Maven Plugin Project Contributors:
5    *     https://github.com/hazendaz/coveralls-maven-plugin/graphs/contributors
6    *
7    * Permission is hereby granted, free of charge, to any person obtaining a copy
8    * of this software and associated documentation files (the "Software"), to deal
9    * in the Software without restriction, including without limitation the rights
10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11   * copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in
15   * all copies or substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23   * THE SOFTWARE.
24   */
25  package org.eluder.coveralls.maven.plugin.httpclient;
26  
27  import com.fasterxml.jackson.core.JsonProcessingException;
28  import com.fasterxml.jackson.databind.ObjectMapper;
29  
30  import java.io.BufferedReader;
31  import java.io.ByteArrayInputStream;
32  import java.io.File;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.net.http.HttpClient;
36  import java.net.http.HttpRequest;
37  import java.net.http.HttpResponse;
38  import java.nio.charset.StandardCharsets;
39  import java.nio.file.Files;
40  import java.nio.file.Path;
41  
42  import org.apache.commons.io.IOUtils;
43  import org.eluder.coveralls.maven.plugin.ProcessingException;
44  import org.eluder.coveralls.maven.plugin.domain.CoverallsResponse;
45  import org.junit.jupiter.api.Assertions;
46  import org.junit.jupiter.api.BeforeEach;
47  import org.junit.jupiter.api.Test;
48  import org.junit.jupiter.api.extension.ExtendWith;
49  import org.junit.jupiter.api.io.CleanupMode;
50  import org.junit.jupiter.api.io.TempDir;
51  import org.mockito.ArgumentMatchers;
52  import org.mockito.Mock;
53  import org.mockito.Mockito;
54  import org.mockito.junit.jupiter.MockitoExtension;
55  
56  /**
57   * The Class CoverallsClientTest.
58   */
59  @ExtendWith(MockitoExtension.class)
60  class CoverallsClientTest {
61  
62      /** The http client mock. */
63      @Mock
64      HttpClient httpClientMock;
65  
66      /** The http response mock. */
67      @Mock
68      HttpResponse<InputStream> httpResponseMock;
69  
70      /** The folder. */
71      @TempDir(cleanup = CleanupMode.ON_SUCCESS)
72      Path folder;
73  
74      /** The file. */
75      File file;
76  
77      /**
78       * Inits the Coveralls Client.
79       *
80       * @throws IOException
81       *             Signals that an I/O exception has occurred.
82       */
83      @BeforeEach
84      void init() throws IOException {
85          this.file = Files.createFile(this.folder.resolve("coverallsClientTest.tmp")).toFile();
86      }
87  
88      /**
89       * Constructors.
90       */
91      @Test
92      void constructors() {
93          Assertions.assertNotNull(new CoverallsClient("https://test.com/coveralls"));
94          Assertions.assertNotNull(
95                  new CoverallsClient("https://test.com/coveralls", this.httpClientMock, new ObjectMapper()));
96      }
97  
98      /**
99       * Test submit.
100      *
101      * @throws IOException
102      *             Signals that an I/O exception has occurred.
103      * @throws InterruptedException
104      *             the interrupted exception
105      * @throws ProcessingException
106      *             the processing exception
107      */
108     @Test
109     void submit() throws IOException, InterruptedException, ProcessingException {
110         Mockito.when(this.httpClientMock.send(ArgumentMatchers.any(HttpRequest.class),
111                 ArgumentMatchers.any(HttpResponse.BodyHandler.class))).thenReturn(this.httpResponseMock);
112         Mockito.when(this.httpResponseMock.statusCode()).thenReturn(200);
113         Mockito.when(this.httpResponseMock.body())
114                 .thenReturn(this.coverallsResponse(new CoverallsResponse("success", false, "")));
115         final var client = new CoverallsClient("https://test.com/coveralls", this.httpClientMock, new ObjectMapper());
116         Assertions.assertDoesNotThrow(() -> client.submit(this.file));
117     }
118 
119     /**
120      * Fail on service error.
121      *
122      * @throws IOException
123      *             Signals that an I/O exception has occurred.
124      * @throws InterruptedException
125      *             the interrupted exception
126      */
127     @Test
128     void failOnServiceError() throws IOException, InterruptedException {
129         Mockito.when(this.httpClientMock.send(ArgumentMatchers.any(HttpRequest.class),
130                 ArgumentMatchers.any(HttpResponse.BodyHandler.class))).thenReturn(this.httpResponseMock);
131         Mockito.when(this.httpResponseMock.statusCode()).thenReturn(500);
132         final var client = new CoverallsClient("https://test.com/coveralls", this.httpClientMock, new ObjectMapper());
133         Assertions.assertThrows(IOException.class, () -> client.submit(this.file));
134     }
135 
136     /**
137      * Parses the invalid response.
138      *
139      * @throws IOException
140      *             Signals that an I/O exception has occurred.
141      * @throws InterruptedException
142      *             the interrupted exception
143      */
144     @Test
145     void parseInvalidResponse() throws IOException, InterruptedException {
146         Mockito.when(this.httpClientMock.send(ArgumentMatchers.any(HttpRequest.class),
147                 ArgumentMatchers.any(HttpResponse.BodyHandler.class))).thenReturn(this.httpResponseMock);
148         Mockito.when(this.httpResponseMock.statusCode()).thenReturn(200);
149         Mockito.when(this.httpResponseMock.body())
150                 .thenReturn(new ByteArrayInputStream("{bogus}".getBytes(StandardCharsets.UTF_8)));
151         final var client = new CoverallsClient("https://test.com/coveralls", this.httpClientMock, new ObjectMapper());
152         Assertions.assertThrows(ProcessingException.class, () -> client.submit(this.file));
153     }
154 
155     /**
156      * Parses the errorous response.
157      *
158      * @throws IOException
159      *             Signals that an I/O exception has occurred.
160      * @throws InterruptedException
161      *             the interrupted exception
162      */
163     @Test
164     void parseErrorousResponse() throws IOException, InterruptedException {
165         Mockito.when(this.httpClientMock.send(ArgumentMatchers.any(HttpRequest.class),
166                 ArgumentMatchers.any(HttpResponse.BodyHandler.class))).thenReturn(this.httpResponseMock);
167         Mockito.when(this.httpResponseMock.statusCode()).thenReturn(400);
168         Mockito.when(this.httpResponseMock.body())
169                 .thenReturn(this.coverallsResponse(new CoverallsResponse("failure", true, "submission failed")));
170         final var client = new CoverallsClient("https://test.com/coveralls", this.httpClientMock, new ObjectMapper());
171         Assertions.assertThrows(ProcessingException.class, () -> client.submit(this.file));
172     }
173 
174     /**
175      * Parses the failing entity.
176      *
177      * @throws IOException
178      *             Signals that an I/O exception has occurred.
179      * @throws InterruptedException
180      *             the interrupted exception
181      */
182     @Test
183     void parseFailingEntity() throws IOException, InterruptedException {
184         Mockito.when(this.httpClientMock.send(ArgumentMatchers.any(HttpRequest.class),
185                 ArgumentMatchers.any(HttpResponse.BodyHandler.class))).thenReturn(this.httpResponseMock);
186         Mockito.when(this.httpResponseMock.statusCode()).thenReturn(200);
187         Mockito.when(this.httpResponseMock.body()).thenReturn(IOUtils.toInputStream("{}", StandardCharsets.UTF_8));
188         // Jackson's object mapper can potentially throw exception
189         final var mockMapper = Mockito.mock(ObjectMapper.class);
190         Mockito.when(mockMapper.readValue(ArgumentMatchers.any(BufferedReader.class),
191                 ArgumentMatchers.eq(CoverallsResponse.class))).thenThrow(IOException.class);
192         final var client = new CoverallsClient("https://test.com/coveralls", this.httpClientMock, mockMapper);
193         Assertions.assertThrows(ProcessingException.class, () -> client.submit(this.file));
194     }
195 
196     /**
197      * Coveralls response.
198      *
199      * @param coverallsResponse
200      *            the coveralls response
201      *
202      * @return the input stream
203      *
204      * @throws JsonProcessingException
205      *             the json processing exception
206      */
207     InputStream coverallsResponse(final CoverallsResponse coverallsResponse) throws JsonProcessingException {
208         final var content = new ObjectMapper().writeValueAsString(coverallsResponse);
209         return new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
210     }
211 
212 }