Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
19 | class ClientSpec extends ObjectBehavior |
||
20 | { |
||
21 | const BASE_URL = 'http://api.host'; |
||
22 | |||
23 | function it_is_initializable() |
||
24 | { |
||
25 | $this->shouldHaveType(Client::class); |
||
26 | } |
||
27 | |||
28 | function let( |
||
29 | HttpClient $httpClient, |
||
30 | MessageFactory $messageFactory, |
||
31 | RequestInterface $tokenRequest, |
||
32 | ResponseInterface $tokenResponse, |
||
33 | StreamInterface $tokenStream |
||
34 | ) { |
||
35 | $tokenHeaders = ['Content-Type' => 'application/x-www-form-urlencoded']; |
||
36 | $tokenRequestUri = sprintf('%s/login_check', self::BASE_URL); |
||
37 | $tokenRawRequest = http_build_query(['api_key' => 'abcd']); |
||
38 | $tokenRawResponse = json_encode(['token' => 'efgh']); |
||
39 | |||
40 | $messageFactory->createRequest('POST', $tokenRequestUri, $tokenHeaders, $tokenRawRequest)->willReturn($tokenRequest); |
||
41 | $httpClient->sendRequest($tokenRequest)->willReturn($tokenResponse); |
||
42 | $tokenResponse->getStatusCode()->willReturn(200); |
||
43 | $tokenResponse->getBody()->willReturn($tokenStream); |
||
44 | $tokenStream->__toString()->willReturn($tokenRawResponse); |
||
45 | |||
46 | $this->beConstructedWith($httpClient, 'abcd', self::BASE_URL, $messageFactory); |
||
47 | } |
||
48 | |||
49 | function it_should_send_get_request( |
||
50 | HttpClient $httpClient, |
||
51 | MessageFactory $messageFactory, |
||
52 | RequestInterface $request, |
||
53 | ResponseInterface $response, |
||
54 | StreamInterface $stream |
||
55 | ) { |
||
56 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
57 | $rawQuery = http_build_query(['query' => 'test']); |
||
58 | $requestUri = sprintf('%s/example?%s', self::BASE_URL, $rawQuery); |
||
59 | $rawResponse = json_encode(['foo' => 'bar']); |
||
60 | |||
61 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
62 | $httpClient->sendRequest($request)->willReturn($response); |
||
63 | $response->getStatusCode()->willReturn(200); |
||
64 | $response->getBody()->willReturn($stream); |
||
65 | $stream->__toString()->willReturn($rawResponse); |
||
66 | |||
67 | $httpClient->sendRequest($request)->shouldBeCalled(); |
||
68 | |||
69 | $this->sendRequest('GET', 'example', ['query' => 'test']); |
||
70 | } |
||
71 | |||
72 | function it_should_send_post_request( |
||
73 | HttpClient $httpClient, |
||
74 | MessageFactory $messageFactory, |
||
75 | RequestInterface $request, |
||
76 | ResponseInterface $response, |
||
77 | StreamInterface $stream |
||
78 | ) { |
||
79 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
80 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
81 | $rawRequest = http_build_query(['query' => 'test']); |
||
82 | $rawResponse = json_encode(['foo' => 'bar']); |
||
83 | |||
84 | $messageFactory->createRequest('POST', $requestUri, $headers, $rawRequest)->willReturn($request); |
||
85 | $httpClient->sendRequest($request)->willReturn($response); |
||
86 | $response->getStatusCode()->willReturn(200); |
||
87 | $response->getBody()->willReturn($stream); |
||
88 | $stream->__toString()->willReturn($rawResponse); |
||
89 | |||
90 | $httpClient->sendRequest($request)->shouldBeCalled(); |
||
91 | |||
92 | $this->sendRequest('POST', 'example', ['query' => 'test']); |
||
93 | } |
||
94 | |||
95 | function it_should_ask_for_api_token_once( |
||
96 | HttpClient $httpClient, |
||
97 | MessageFactory $messageFactory, |
||
98 | RequestInterface $firstRequest, |
||
99 | ResponseInterface $firstResponse, |
||
100 | StreamInterface $firstStream, |
||
101 | RequestInterface $secondRequest, |
||
102 | ResponseInterface $secondResponse, |
||
103 | StreamInterface $secondStream, |
||
104 | RequestInterface $tokenRequest |
||
105 | ) { |
||
106 | // first request |
||
107 | $firstHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
108 | $firstRequestUri = sprintf('%s/first', self::BASE_URL); |
||
109 | $firstRawResponse = json_encode(['foo' => 'bar']); |
||
110 | |||
111 | $messageFactory->createRequest('GET', $firstRequestUri, $firstHeaders, null)->willReturn($firstRequest); |
||
112 | $httpClient->sendRequest($firstRequest)->willReturn($firstResponse); |
||
113 | $firstResponse->getStatusCode()->willReturn(200); |
||
114 | $firstResponse->getBody()->willReturn($firstStream); |
||
115 | $firstStream->__toString()->willReturn($firstRawResponse); |
||
116 | |||
117 | $httpClient->sendRequest($firstRequest)->shouldBeCalled(); |
||
118 | |||
119 | $this->sendRequest('GET', 'first'); |
||
120 | |||
121 | // second request |
||
122 | $secondHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
123 | $secondRequestUri = sprintf('%s/second', self::BASE_URL); |
||
124 | $secondRawResponse = json_encode(['foo' => 'bar']); |
||
125 | |||
126 | $messageFactory->createRequest('GET', $secondRequestUri, $secondHeaders, null)->willReturn($secondRequest); |
||
127 | $httpClient->sendRequest($secondRequest)->willReturn($secondResponse); |
||
128 | $secondResponse->getStatusCode()->willReturn(200); |
||
129 | $secondResponse->getBody()->willReturn($secondStream); |
||
130 | $secondStream->__toString()->willReturn($secondRawResponse); |
||
131 | |||
132 | $httpClient->sendRequest($secondRequest)->shouldBeCalled(); |
||
133 | |||
134 | $this->sendRequest('GET', 'second'); |
||
135 | |||
136 | $httpClient->sendRequest($tokenRequest)->shouldHaveBeenCalledTimes(1); |
||
137 | } |
||
138 | |||
139 | function it_should_renew_the_token_and_resend_the_request( |
||
140 | HttpClient $httpClient, |
||
141 | MessageFactory $messageFactory, |
||
142 | RequestInterface $firstRequest, |
||
143 | ResponseInterface $firstResponse, |
||
144 | StreamInterface $firstStream, |
||
145 | RequestInterface $secondRequest, |
||
146 | ResponseInterface $secondResponse, |
||
147 | StreamInterface $secondStream, |
||
148 | RequestInterface $tokenRequest |
||
149 | ) { |
||
150 | // first request |
||
151 | $firstHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
152 | $firstRequestUri = sprintf('%s/first', self::BASE_URL); |
||
153 | $firstRawResponse = json_encode(['foo' => 'bar']); |
||
154 | |||
155 | $messageFactory->createRequest('GET', $firstRequestUri, $firstHeaders, null)->willReturn($firstRequest); |
||
156 | $httpClient->sendRequest($firstRequest)->willReturn($firstResponse); |
||
157 | $firstResponse->getStatusCode()->willReturn(200); |
||
158 | $firstResponse->getBody()->willReturn($firstStream); |
||
159 | $firstStream->__toString()->willReturn($firstRawResponse); |
||
160 | |||
161 | $httpClient->sendRequest($firstRequest)->shouldBeCalled(); |
||
162 | |||
163 | $this->sendRequest('GET', 'first'); |
||
164 | |||
165 | // second request |
||
166 | $secondHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
167 | $secondRequestUri = sprintf('%s/second', self::BASE_URL); |
||
168 | $secondRawResponse = json_encode(['foo' => 'bar']); |
||
169 | |||
170 | $messageFactory->createRequest('GET', $secondRequestUri, $secondHeaders, null)->willReturn($secondRequest); |
||
171 | $secondResponse->getStatusCode()->willReturn(200); |
||
172 | $secondResponse->getBody()->willReturn($secondStream); |
||
173 | $secondStream->__toString()->willReturn($secondRawResponse); |
||
174 | |||
175 | $secondRequestCounter = 0; |
||
176 | |||
177 | $httpClient->sendRequest($secondRequest)->will(function () use ($secondRequestCounter, $secondResponse) { |
||
178 | $secondRequestCounter ++; |
||
179 | |||
180 | if ($secondRequestCounter === 1) { |
||
181 | return MessageFactoryDiscovery::find()->createResponse(401); |
||
182 | } |
||
183 | |||
184 | return $secondResponse; |
||
185 | }); |
||
186 | |||
187 | $httpClient->sendRequest($secondRequest)->shouldBeCalledTimes(2); |
||
188 | |||
189 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'second']); |
||
190 | |||
191 | $httpClient->sendRequest($tokenRequest)->shouldHaveBeenCalledTimes(2); |
||
192 | } |
||
193 | |||
194 | function it_should_not_renew_the_token( |
||
195 | HttpClient $httpClient, |
||
196 | MessageFactory $messageFactory, |
||
197 | RequestInterface $request, |
||
198 | ResponseInterface $response, |
||
199 | StreamInterface $stream, |
||
200 | RequestInterface $tokenRequest |
||
201 | ) { |
||
202 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
203 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
204 | $rawResponse = json_encode(['foo' => 'bar']); |
||
205 | |||
206 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
207 | $httpClient->sendRequest($request)->willReturn($response); |
||
208 | $response->getStatusCode()->willReturn(401); |
||
209 | $stream->__toString()->willReturn($rawResponse); |
||
210 | |||
211 | $httpClient->sendRequest($request)->shouldBeCalled(); |
||
212 | |||
213 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'example']); |
||
214 | |||
215 | $httpClient->sendRequest($tokenRequest)->shouldHaveBeenCalledTimes(1); |
||
216 | } |
||
217 | |||
218 | function it_should_throw_transfer_exception_on_http_exception( |
||
219 | HttpClient $httpClient, |
||
220 | MessageFactory $messageFactory, |
||
221 | RequestInterface $request |
||
222 | ) { |
||
223 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
224 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
225 | |||
226 | $errorResponse = MessageFactoryDiscovery::find()->createResponse(500); |
||
227 | $httpException = HttpException::create($request->getWrappedObject(), $errorResponse); |
||
228 | |||
229 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
230 | $httpClient->sendRequest($request)->willThrow($httpException); |
||
231 | |||
232 | $this->shouldThrow(TransferException::class)->during('sendRequest', ['GET', 'example']); |
||
233 | } |
||
234 | |||
235 | function it_should_throw_invalid_response_exception_on_bad_status_code( |
||
236 | HttpClient $httpClient, |
||
237 | MessageFactory $messageFactory, |
||
238 | RequestInterface $request, |
||
239 | ResponseInterface $response |
||
240 | ) { |
||
241 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
242 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
243 | |||
244 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
245 | $httpClient->sendRequest($request)->willReturn($response); |
||
246 | $response->getStatusCode()->willReturn(400); |
||
247 | |||
248 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'example']); |
||
249 | } |
||
250 | |||
251 | function it_should_throw_invalid_response_exception_on_broken_response_body( |
||
252 | HttpClient $httpClient, |
||
253 | MessageFactory $messageFactory, |
||
254 | RequestInterface $request, |
||
255 | ResponseInterface $response, |
||
256 | StreamInterface $stream |
||
257 | ) { |
||
258 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
259 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
260 | |||
261 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
262 | $httpClient->sendRequest($request)->willReturn($response); |
||
263 | $response->getStatusCode()->willReturn(200); |
||
264 | $response->getBody()->willReturn($stream); |
||
265 | $stream->__toString()->willReturn('it is not a json'); |
||
266 | |||
267 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'example']); |
||
268 | } |
||
269 | |||
270 | function it_should_throw_authenfication_exception_on_http_exception( |
||
271 | HttpClient $httpClient, |
||
272 | RequestInterface $tokenRequest |
||
273 | ) { |
||
274 | $errorResponse = MessageFactoryDiscovery::find()->createResponse(500); |
||
275 | $httpException = HttpException::create($tokenRequest->getWrappedObject(), $errorResponse); |
||
276 | |||
277 | $httpClient->sendRequest($tokenRequest)->willThrow($httpException); |
||
278 | |||
279 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
280 | } |
||
281 | |||
282 | function it_should_throw_authenfication_exception_on_bad_status_code(ResponseInterface $tokenResponse) |
||
283 | { |
||
284 | $tokenResponse->getStatusCode()->willReturn(400); |
||
285 | |||
286 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
287 | } |
||
288 | |||
289 | function it_should_throw_authenfication_exception_on_broken_response_body(StreamInterface $tokenStream) |
||
290 | { |
||
291 | $tokenStream->__toString()->willReturn('it is not a json'); |
||
292 | |||
293 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
294 | } |
||
295 | |||
296 | function it_should_throw_authenfication_exception_on_bad_response_body(StreamInterface $tokenStream) |
||
297 | { |
||
298 | $rawResponse = json_encode(['it' => 'is not a token']); |
||
299 | |||
300 | $tokenStream->__toString()->willReturn($rawResponse); |
||
301 | |||
302 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
303 | } |
||
304 | } |
||
305 |