Mercurial > hg > orthanc
comparison OrthancFramework/UnitTestsSources/RestApiTests.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | UnitTestsSources/RestApiTests.cpp@27628b0f6ada |
children | 05b8fd21089c |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1 | |
35 # include <OrthancFramework.h> | |
36 #endif | |
37 | |
38 #include "PrecompiledHeadersUnitTests.h" | |
39 #include "gtest/gtest.h" | |
40 | |
41 #include <ctype.h> | |
42 #include <boost/lexical_cast.hpp> | |
43 #include <algorithm> | |
44 | |
45 #include "../Core/ChunkedBuffer.h" | |
46 #include "../Core/HttpClient.h" | |
47 #include "../Core/Logging.h" | |
48 #include "../Core/SystemToolbox.h" | |
49 #include "../Core/RestApi/RestApi.h" | |
50 #include "../Core/OrthancException.h" | |
51 #include "../Core/Compression/ZlibCompressor.h" | |
52 #include "../Core/RestApi/RestApiHierarchy.h" | |
53 #include "../Core/HttpServer/HttpContentNegociation.h" | |
54 #include "../Core/HttpServer/MultipartStreamReader.h" | |
55 | |
56 | |
57 using namespace Orthanc; | |
58 | |
59 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) | |
60 #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" | |
61 #endif | |
62 | |
63 | |
64 | |
65 TEST(HttpClient, Basic) | |
66 { | |
67 HttpClient c; | |
68 ASSERT_FALSE(c.IsVerbose()); | |
69 c.SetVerbose(true); | |
70 ASSERT_TRUE(c.IsVerbose()); | |
71 c.SetVerbose(false); | |
72 ASSERT_FALSE(c.IsVerbose()); | |
73 | |
74 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 | |
75 // The "http://www.orthanc-server.com/downloads/third-party/" does | |
76 // not automatically redirect to HTTPS, so we cas use it even if the | |
77 // OpenSSL/HTTPS support is disabled in curl | |
78 const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/"; | |
79 | |
80 Json::Value v; | |
81 c.SetUrl(BASE + "Product.json"); | |
82 | |
83 c.Apply(v); | |
84 ASSERT_TRUE(v.type() == Json::objectValue); | |
85 ASSERT_TRUE(v.isMember("Description")); | |
86 #endif | |
87 } | |
88 | |
89 | |
90 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 && ORTHANC_ENABLE_SSL == 1 | |
91 | |
92 /** | |
93 The HTTPS CA certificates for BitBucket were extracted as follows: | |
94 | |
95 (1) We retrieve the certification chain of BitBucket: | |
96 | |
97 # echo | openssl s_client -showcerts -connect www.bitbucket.org:443 | |
98 | |
99 (2) We see that the certification authority (CA) is | |
100 "www.digicert.com", and the root certificate is "DigiCert High | |
101 Assurance EV Root CA". As a consequence, we navigate to DigiCert to | |
102 find the URL to this CA certificate: | |
103 | |
104 firefox https://www.digicert.com/digicert-root-certificates.htm | |
105 | |
106 (3) Once we get the URL to the CA certificate, we convert it to a C | |
107 macro that can be used by libcurl: | |
108 | |
109 # cd UnitTestsSources | |
110 # ../Resources/RetrieveCACertificates.py BITBUCKET_CERTIFICATES https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt > BitbucketCACertificates.h | |
111 **/ | |
112 | |
113 #include "BitbucketCACertificates.h" | |
114 | |
115 TEST(HttpClient, Ssl) | |
116 { | |
117 SystemToolbox::WriteFile(BITBUCKET_CERTIFICATES, "UnitTestsResults/bitbucket.cert"); | |
118 | |
119 /*{ | |
120 std::string s; | |
121 SystemToolbox::ReadFile(s, "/usr/share/ca-certificates/mozilla/WoSign.crt"); | |
122 SystemToolbox::WriteFile(s, "UnitTestsResults/bitbucket.cert"); | |
123 }*/ | |
124 | |
125 HttpClient c; | |
126 c.SetHttpsVerifyPeers(true); | |
127 c.SetHttpsCACertificates("UnitTestsResults/bitbucket.cert"); | |
128 | |
129 // Test file modified on 2020-04-20, in order to use a git | |
130 // repository on BitBucket instead of a Mercurial repository | |
131 // (because Mercurial support disappears on 2020-05-31) | |
132 c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json"); | |
133 | |
134 Json::Value v; | |
135 c.Apply(v); | |
136 ASSERT_TRUE(v.isMember("ServeFolders")); | |
137 } | |
138 | |
139 TEST(HttpClient, SslNoVerification) | |
140 { | |
141 HttpClient c; | |
142 c.SetHttpsVerifyPeers(false); | |
143 c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json"); | |
144 | |
145 Json::Value v; | |
146 c.Apply(v); | |
147 ASSERT_TRUE(v.isMember("ServeFolders")); | |
148 } | |
149 | |
150 #endif | |
151 | |
152 | |
153 TEST(RestApi, ChunkedBuffer) | |
154 { | |
155 ChunkedBuffer b; | |
156 ASSERT_EQ(0u, b.GetNumBytes()); | |
157 | |
158 b.AddChunk("hello", 5); | |
159 ASSERT_EQ(5u, b.GetNumBytes()); | |
160 | |
161 b.AddChunk("world", 5); | |
162 ASSERT_EQ(10u, b.GetNumBytes()); | |
163 | |
164 std::string s; | |
165 b.Flatten(s); | |
166 ASSERT_EQ("helloworld", s); | |
167 } | |
168 | |
169 TEST(RestApi, ParseCookies) | |
170 { | |
171 IHttpHandler::Arguments headers; | |
172 IHttpHandler::Arguments cookies; | |
173 | |
174 headers["cookie"] = "a=b;c=d;;;e=f;;g=h;"; | |
175 HttpToolbox::ParseCookies(cookies, headers); | |
176 ASSERT_EQ(4u, cookies.size()); | |
177 ASSERT_EQ("b", cookies["a"]); | |
178 ASSERT_EQ("d", cookies["c"]); | |
179 ASSERT_EQ("f", cookies["e"]); | |
180 ASSERT_EQ("h", cookies["g"]); | |
181 | |
182 headers["cookie"] = " name = value ; name2=value2"; | |
183 HttpToolbox::ParseCookies(cookies, headers); | |
184 ASSERT_EQ(2u, cookies.size()); | |
185 ASSERT_EQ("value", cookies["name"]); | |
186 ASSERT_EQ("value2", cookies["name2"]); | |
187 | |
188 headers["cookie"] = " ;;; "; | |
189 HttpToolbox::ParseCookies(cookies, headers); | |
190 ASSERT_EQ(0u, cookies.size()); | |
191 | |
192 headers["cookie"] = " ; n=v ;; "; | |
193 HttpToolbox::ParseCookies(cookies, headers); | |
194 ASSERT_EQ(1u, cookies.size()); | |
195 ASSERT_EQ("v", cookies["n"]); | |
196 } | |
197 | |
198 TEST(RestApi, RestApiPath) | |
199 { | |
200 IHttpHandler::Arguments args; | |
201 UriComponents trail; | |
202 | |
203 { | |
204 RestApiPath uri("/coucou/{abc}/d/*"); | |
205 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); | |
206 ASSERT_EQ(1u, args.size()); | |
207 ASSERT_EQ(3u, trail.size()); | |
208 ASSERT_EQ("moi", args["abc"]); | |
209 ASSERT_EQ("e", trail[0]); | |
210 ASSERT_EQ("f", trail[1]); | |
211 ASSERT_EQ("g", trail[2]); | |
212 | |
213 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f")); | |
214 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/")); | |
215 ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d")); | |
216 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi")); | |
217 | |
218 ASSERT_EQ(3u, uri.GetLevelCount()); | |
219 ASSERT_TRUE(uri.IsUniversalTrailing()); | |
220 | |
221 ASSERT_EQ("coucou", uri.GetLevelName(0)); | |
222 ASSERT_THROW(uri.GetWildcardName(0), OrthancException); | |
223 | |
224 ASSERT_EQ("abc", uri.GetWildcardName(1)); | |
225 ASSERT_THROW(uri.GetLevelName(1), OrthancException); | |
226 | |
227 ASSERT_EQ("d", uri.GetLevelName(2)); | |
228 ASSERT_THROW(uri.GetWildcardName(2), OrthancException); | |
229 } | |
230 | |
231 { | |
232 RestApiPath uri("/coucou/{abc}/d"); | |
233 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g")); | |
234 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d")); | |
235 ASSERT_EQ(1u, args.size()); | |
236 ASSERT_EQ(0u, trail.size()); | |
237 ASSERT_EQ("moi", args["abc"]); | |
238 | |
239 ASSERT_EQ(3u, uri.GetLevelCount()); | |
240 ASSERT_FALSE(uri.IsUniversalTrailing()); | |
241 | |
242 ASSERT_EQ("coucou", uri.GetLevelName(0)); | |
243 ASSERT_THROW(uri.GetWildcardName(0), OrthancException); | |
244 | |
245 ASSERT_EQ("abc", uri.GetWildcardName(1)); | |
246 ASSERT_THROW(uri.GetLevelName(1), OrthancException); | |
247 | |
248 ASSERT_EQ("d", uri.GetLevelName(2)); | |
249 ASSERT_THROW(uri.GetWildcardName(2), OrthancException); | |
250 } | |
251 | |
252 { | |
253 RestApiPath uri("/*"); | |
254 ASSERT_TRUE(uri.Match(args, trail, "/a/b/c")); | |
255 ASSERT_EQ(0u, args.size()); | |
256 ASSERT_EQ(3u, trail.size()); | |
257 ASSERT_EQ("a", trail[0]); | |
258 ASSERT_EQ("b", trail[1]); | |
259 ASSERT_EQ("c", trail[2]); | |
260 | |
261 ASSERT_EQ(0u, uri.GetLevelCount()); | |
262 ASSERT_TRUE(uri.IsUniversalTrailing()); | |
263 } | |
264 } | |
265 | |
266 | |
267 | |
268 | |
269 | |
270 | |
271 static int testValue; | |
272 | |
273 template <int value> | |
274 static void SetValue(RestApiGetCall& get) | |
275 { | |
276 testValue = value; | |
277 } | |
278 | |
279 | |
280 static bool GetDirectory(Json::Value& target, | |
281 RestApiHierarchy& hierarchy, | |
282 const std::string& uri) | |
283 { | |
284 UriComponents p; | |
285 Toolbox::SplitUriComponents(p, uri); | |
286 return hierarchy.GetDirectory(target, p); | |
287 } | |
288 | |
289 | |
290 | |
291 namespace | |
292 { | |
293 class MyVisitor : public RestApiHierarchy::IVisitor | |
294 { | |
295 public: | |
296 virtual bool Visit(const RestApiHierarchy::Resource& resource, | |
297 const UriComponents& uri, | |
298 const IHttpHandler::Arguments& components, | |
299 const UriComponents& trailing) ORTHANC_OVERRIDE | |
300 { | |
301 return resource.Handle(*(RestApiGetCall*) NULL); | |
302 } | |
303 }; | |
304 } | |
305 | |
306 | |
307 static bool HandleGet(RestApiHierarchy& hierarchy, | |
308 const std::string& uri) | |
309 { | |
310 UriComponents p; | |
311 Toolbox::SplitUriComponents(p, uri); | |
312 MyVisitor visitor; | |
313 return hierarchy.LookupResource(p, visitor); | |
314 } | |
315 | |
316 | |
317 TEST(RestApi, RestApiHierarchy) | |
318 { | |
319 RestApiHierarchy root; | |
320 root.Register("/hello/world/test", SetValue<1>); | |
321 root.Register("/hello/world/test2", SetValue<2>); | |
322 root.Register("/hello/{world}/test3/test4", SetValue<3>); | |
323 root.Register("/hello2/*", SetValue<4>); | |
324 | |
325 Json::Value m; | |
326 root.CreateSiteMap(m); | |
327 std::cout << m; | |
328 | |
329 Json::Value d; | |
330 ASSERT_FALSE(GetDirectory(d, root, "/hello")); | |
331 | |
332 ASSERT_TRUE(GetDirectory(d, root, "/hello/a")); | |
333 ASSERT_EQ(1u, d.size()); | |
334 ASSERT_EQ("test3", d[0].asString()); | |
335 | |
336 ASSERT_TRUE(GetDirectory(d, root, "/hello/world")); | |
337 ASSERT_EQ(2u, d.size()); | |
338 | |
339 ASSERT_TRUE(GetDirectory(d, root, "/hello/a/test3")); | |
340 ASSERT_EQ(1u, d.size()); | |
341 ASSERT_EQ("test4", d[0].asString()); | |
342 | |
343 ASSERT_TRUE(GetDirectory(d, root, "/hello/world/test")); | |
344 ASSERT_TRUE(GetDirectory(d, root, "/hello/world/test2")); | |
345 ASSERT_FALSE(GetDirectory(d, root, "/hello2")); | |
346 | |
347 testValue = 0; | |
348 ASSERT_TRUE(HandleGet(root, "/hello/world/test")); | |
349 ASSERT_EQ(testValue, 1); | |
350 ASSERT_TRUE(HandleGet(root, "/hello/world/test2")); | |
351 ASSERT_EQ(testValue, 2); | |
352 ASSERT_TRUE(HandleGet(root, "/hello/b/test3/test4")); | |
353 ASSERT_EQ(testValue, 3); | |
354 ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test")); | |
355 ASSERT_EQ(testValue, 3); | |
356 ASSERT_TRUE(HandleGet(root, "/hello2/a/b")); | |
357 ASSERT_EQ(testValue, 4); | |
358 } | |
359 | |
360 | |
361 | |
362 | |
363 | |
364 namespace | |
365 { | |
366 class AcceptHandler : public HttpContentNegociation::IHandler | |
367 { | |
368 private: | |
369 std::string type_; | |
370 std::string subtype_; | |
371 | |
372 public: | |
373 AcceptHandler() | |
374 { | |
375 Reset(); | |
376 } | |
377 | |
378 void Reset() | |
379 { | |
380 Handle("nope", "nope"); | |
381 } | |
382 | |
383 const std::string& GetType() const | |
384 { | |
385 return type_; | |
386 } | |
387 | |
388 const std::string& GetSubType() const | |
389 { | |
390 return subtype_; | |
391 } | |
392 | |
393 virtual void Handle(const std::string& type, | |
394 const std::string& subtype) ORTHANC_OVERRIDE | |
395 { | |
396 type_ = type; | |
397 subtype_ = subtype; | |
398 } | |
399 }; | |
400 } | |
401 | |
402 | |
403 TEST(RestApi, HttpContentNegociation) | |
404 { | |
405 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | |
406 | |
407 AcceptHandler h; | |
408 | |
409 { | |
410 HttpContentNegociation d; | |
411 d.Register("audio/mp3", h); | |
412 d.Register("audio/basic", h); | |
413 | |
414 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); | |
415 ASSERT_EQ("audio", h.GetType()); | |
416 ASSERT_EQ("basic", h.GetSubType()); | |
417 | |
418 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope")); | |
419 ASSERT_EQ("audio", h.GetType()); | |
420 ASSERT_EQ("mp3", h.GetSubType()); | |
421 | |
422 ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf")); | |
423 | |
424 ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf")); | |
425 ASSERT_EQ("audio", h.GetType()); | |
426 } | |
427 | |
428 // "This would be interpreted as "text/html and text/x-c are the | |
429 // preferred media types, but if they do not exist, then send the | |
430 // text/x-dvi entity, and if that does not exist, send the | |
431 // text/plain entity."" | |
432 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; | |
433 | |
434 { | |
435 HttpContentNegociation d; | |
436 d.Register("text/plain", h); | |
437 d.Register("text/html", h); | |
438 d.Register("text/x-dvi", h); | |
439 ASSERT_TRUE(d.Apply(T1)); | |
440 ASSERT_EQ("text", h.GetType()); | |
441 ASSERT_EQ("html", h.GetSubType()); | |
442 } | |
443 | |
444 { | |
445 HttpContentNegociation d; | |
446 d.Register("text/plain", h); | |
447 d.Register("text/x-dvi", h); | |
448 d.Register("text/x-c", h); | |
449 ASSERT_TRUE(d.Apply(T1)); | |
450 ASSERT_EQ("text", h.GetType()); | |
451 ASSERT_EQ("x-c", h.GetSubType()); | |
452 } | |
453 | |
454 { | |
455 HttpContentNegociation d; | |
456 d.Register("text/plain", h); | |
457 d.Register("text/x-dvi", h); | |
458 d.Register("text/x-c", h); | |
459 d.Register("text/html", h); | |
460 ASSERT_TRUE(d.Apply(T1)); | |
461 ASSERT_EQ("text", h.GetType()); | |
462 ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html"); | |
463 } | |
464 | |
465 { | |
466 HttpContentNegociation d; | |
467 d.Register("text/plain", h); | |
468 d.Register("text/x-dvi", h); | |
469 ASSERT_TRUE(d.Apply(T1)); | |
470 ASSERT_EQ("text", h.GetType()); | |
471 ASSERT_EQ("x-dvi", h.GetSubType()); | |
472 } | |
473 | |
474 { | |
475 HttpContentNegociation d; | |
476 d.Register("text/plain", h); | |
477 ASSERT_TRUE(d.Apply(T1)); | |
478 ASSERT_EQ("text", h.GetType()); | |
479 ASSERT_EQ("plain", h.GetSubType()); | |
480 } | |
481 } | |
482 | |
483 | |
484 TEST(WebServiceParameters, Serialization) | |
485 { | |
486 { | |
487 Json::Value v = Json::arrayValue; | |
488 v.append("http://localhost:8042/"); | |
489 | |
490 WebServiceParameters p(v); | |
491 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
492 | |
493 Json::Value v2; | |
494 p.Serialize(v2, false, true); | |
495 ASSERT_EQ(v, v2); | |
496 | |
497 WebServiceParameters p2(v2); | |
498 ASSERT_EQ("http://localhost:8042/", p2.GetUrl()); | |
499 ASSERT_TRUE(p2.GetUsername().empty()); | |
500 ASSERT_TRUE(p2.GetPassword().empty()); | |
501 ASSERT_TRUE(p2.GetCertificateFile().empty()); | |
502 ASSERT_TRUE(p2.GetCertificateKeyFile().empty()); | |
503 ASSERT_TRUE(p2.GetCertificateKeyPassword().empty()); | |
504 ASSERT_FALSE(p2.IsPkcs11Enabled()); | |
505 } | |
506 | |
507 { | |
508 Json::Value v = Json::arrayValue; | |
509 v.append("http://localhost:8042/"); | |
510 v.append("user"); | |
511 v.append("pass"); | |
512 | |
513 WebServiceParameters p(v); | |
514 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
515 ASSERT_EQ("http://localhost:8042/", p.GetUrl()); | |
516 ASSERT_EQ("user", p.GetUsername()); | |
517 ASSERT_EQ("pass", p.GetPassword()); | |
518 ASSERT_TRUE(p.GetCertificateFile().empty()); | |
519 ASSERT_TRUE(p.GetCertificateKeyFile().empty()); | |
520 ASSERT_TRUE(p.GetCertificateKeyPassword().empty()); | |
521 ASSERT_FALSE(p.IsPkcs11Enabled()); | |
522 | |
523 Json::Value v2; | |
524 p.Serialize(v2, false, true); | |
525 ASSERT_EQ(v, v2); | |
526 | |
527 p.Serialize(v2, false, false /* no password */); | |
528 WebServiceParameters p2(v2); | |
529 ASSERT_EQ(Json::arrayValue, v2.type()); | |
530 ASSERT_EQ(3u, v2.size()); | |
531 ASSERT_EQ("http://localhost:8042/", v2[0u].asString()); | |
532 ASSERT_EQ("user", v2[1u].asString()); | |
533 ASSERT_TRUE(v2[2u].asString().empty()); | |
534 } | |
535 | |
536 { | |
537 Json::Value v = Json::arrayValue; | |
538 v.append("http://localhost:8042/"); | |
539 | |
540 WebServiceParameters p(v); | |
541 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
542 p.SetPkcs11Enabled(true); | |
543 ASSERT_TRUE(p.IsAdvancedFormatNeeded()); | |
544 | |
545 Json::Value v2; | |
546 p.Serialize(v2, false, true); | |
547 WebServiceParameters p2(v2); | |
548 | |
549 ASSERT_EQ(Json::objectValue, v2.type()); | |
550 ASSERT_EQ(3u, v2.size()); | |
551 ASSERT_EQ("http://localhost:8042/", v2["Url"].asString()); | |
552 ASSERT_TRUE(v2["Pkcs11"].asBool()); | |
553 ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type()); | |
554 ASSERT_EQ(0u, v2["HttpHeaders"].size()); | |
555 } | |
556 | |
557 { | |
558 Json::Value v = Json::arrayValue; | |
559 v.append("http://localhost:8042/"); | |
560 | |
561 WebServiceParameters p(v); | |
562 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
563 p.SetClientCertificate("a", "b", "c"); | |
564 ASSERT_TRUE(p.IsAdvancedFormatNeeded()); | |
565 | |
566 Json::Value v2; | |
567 p.Serialize(v2, false, true); | |
568 WebServiceParameters p2(v2); | |
569 | |
570 ASSERT_EQ(Json::objectValue, v2.type()); | |
571 ASSERT_EQ(6u, v2.size()); | |
572 ASSERT_EQ("http://localhost:8042/", v2["Url"].asString()); | |
573 ASSERT_EQ("a", v2["CertificateFile"].asString()); | |
574 ASSERT_EQ("b", v2["CertificateKeyFile"].asString()); | |
575 ASSERT_EQ("c", v2["CertificateKeyPassword"].asString()); | |
576 ASSERT_FALSE(v2["Pkcs11"].asBool()); | |
577 ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type()); | |
578 ASSERT_EQ(0u, v2["HttpHeaders"].size()); | |
579 } | |
580 | |
581 { | |
582 Json::Value v = Json::arrayValue; | |
583 v.append("http://localhost:8042/"); | |
584 | |
585 WebServiceParameters p(v); | |
586 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
587 p.AddHttpHeader("a", "b"); | |
588 p.AddHttpHeader("c", "d"); | |
589 ASSERT_TRUE(p.IsAdvancedFormatNeeded()); | |
590 | |
591 Json::Value v2; | |
592 p.Serialize(v2, false, true); | |
593 WebServiceParameters p2(v2); | |
594 | |
595 ASSERT_EQ(Json::objectValue, v2.type()); | |
596 ASSERT_EQ(3u, v2.size()); | |
597 ASSERT_EQ("http://localhost:8042/", v2["Url"].asString()); | |
598 ASSERT_FALSE(v2["Pkcs11"].asBool()); | |
599 ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type()); | |
600 ASSERT_EQ(2u, v2["HttpHeaders"].size()); | |
601 ASSERT_EQ("b", v2["HttpHeaders"]["a"].asString()); | |
602 ASSERT_EQ("d", v2["HttpHeaders"]["c"].asString()); | |
603 | |
604 std::set<std::string> a; | |
605 p2.ListHttpHeaders(a); | |
606 ASSERT_EQ(2u, a.size()); | |
607 ASSERT_TRUE(a.find("a") != a.end()); | |
608 ASSERT_TRUE(a.find("c") != a.end()); | |
609 | |
610 std::string s; | |
611 ASSERT_TRUE(p2.LookupHttpHeader(s, "a")); ASSERT_EQ("b", s); | |
612 ASSERT_TRUE(p2.LookupHttpHeader(s, "c")); ASSERT_EQ("d", s); | |
613 ASSERT_FALSE(p2.LookupHttpHeader(s, "nope")); | |
614 } | |
615 } | |
616 | |
617 | |
618 TEST(WebServiceParameters, UserProperties) | |
619 { | |
620 Json::Value v = Json::nullValue; | |
621 | |
622 { | |
623 WebServiceParameters p; | |
624 p.SetUrl("http://localhost:8042/"); | |
625 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
626 | |
627 ASSERT_THROW(p.AddUserProperty("Url", "nope"), OrthancException); | |
628 p.AddUserProperty("Hello", "world"); | |
629 p.AddUserProperty("a", "b"); | |
630 ASSERT_TRUE(p.IsAdvancedFormatNeeded()); | |
631 | |
632 p.Serialize(v, false, true); | |
633 | |
634 p.ClearUserProperties(); | |
635 ASSERT_FALSE(p.IsAdvancedFormatNeeded()); | |
636 } | |
637 | |
638 { | |
639 WebServiceParameters p(v); | |
640 ASSERT_TRUE(p.IsAdvancedFormatNeeded()); | |
641 ASSERT_TRUE(p.GetHttpHeaders().empty()); | |
642 | |
643 std::set<std::string> tmp; | |
644 p.ListUserProperties(tmp); | |
645 ASSERT_EQ(2u, tmp.size()); | |
646 ASSERT_TRUE(tmp.find("a") != tmp.end()); | |
647 ASSERT_TRUE(tmp.find("Hello") != tmp.end()); | |
648 ASSERT_TRUE(tmp.find("hello") == tmp.end()); | |
649 | |
650 std::string s; | |
651 ASSERT_TRUE(p.LookupUserProperty(s, "a")); ASSERT_TRUE(s == "b"); | |
652 ASSERT_TRUE(p.LookupUserProperty(s, "Hello")); ASSERT_TRUE(s == "world"); | |
653 ASSERT_FALSE(p.LookupUserProperty(s, "hello")); | |
654 } | |
655 } | |
656 | |
657 | |
658 TEST(StringMatcher, Basic) | |
659 { | |
660 StringMatcher matcher("---"); | |
661 | |
662 ASSERT_THROW(matcher.GetMatchBegin(), OrthancException); | |
663 | |
664 { | |
665 const std::string s = ""; | |
666 ASSERT_FALSE(matcher.Apply(s)); | |
667 } | |
668 | |
669 { | |
670 const std::string s = "abc----def"; | |
671 ASSERT_TRUE(matcher.Apply(s)); | |
672 ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin())); | |
673 ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd())); | |
674 } | |
675 | |
676 { | |
677 const std::string s = "abc---"; | |
678 ASSERT_TRUE(matcher.Apply(s)); | |
679 ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin())); | |
680 ASSERT_EQ(s.end(), matcher.GetMatchEnd()); | |
681 ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd())); | |
682 ASSERT_EQ("", std::string(matcher.GetMatchEnd(), s.end())); | |
683 } | |
684 | |
685 { | |
686 const std::string s = "abc--def"; | |
687 ASSERT_FALSE(matcher.Apply(s)); | |
688 ASSERT_THROW(matcher.GetMatchBegin(), OrthancException); | |
689 ASSERT_THROW(matcher.GetMatchEnd(), OrthancException); | |
690 } | |
691 | |
692 { | |
693 std::string s(10u, '\0'); // String with null values | |
694 ASSERT_EQ(10u, s.size()); | |
695 ASSERT_EQ(10u, s.size()); | |
696 ASSERT_FALSE(matcher.Apply(s)); | |
697 | |
698 s[9] = '-'; | |
699 ASSERT_FALSE(matcher.Apply(s)); | |
700 | |
701 s[8] = '-'; | |
702 ASSERT_FALSE(matcher.Apply(s)); | |
703 | |
704 s[7] = '-'; | |
705 ASSERT_TRUE(matcher.Apply(s)); | |
706 ASSERT_EQ(s.c_str() + 7, matcher.GetPointerBegin()); | |
707 ASSERT_EQ(s.c_str() + 10, matcher.GetPointerEnd()); | |
708 ASSERT_EQ(s.end() - 3, matcher.GetMatchBegin()); | |
709 ASSERT_EQ(s.end(), matcher.GetMatchEnd()); | |
710 } | |
711 } | |
712 | |
713 | |
714 | |
715 class MultipartTester : public MultipartStreamReader::IHandler | |
716 { | |
717 private: | |
718 struct Part | |
719 { | |
720 MultipartStreamReader::HttpHeaders headers_; | |
721 std::string data_; | |
722 | |
723 Part(const MultipartStreamReader::HttpHeaders& headers, | |
724 const void* part, | |
725 size_t size) : | |
726 headers_(headers), | |
727 data_(reinterpret_cast<const char*>(part), size) | |
728 { | |
729 } | |
730 }; | |
731 | |
732 std::vector<Part> parts_; | |
733 | |
734 public: | |
735 virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers, | |
736 const void* part, | |
737 size_t size) | |
738 { | |
739 parts_.push_back(Part(headers, part, size)); | |
740 } | |
741 | |
742 unsigned int GetCount() const | |
743 { | |
744 return parts_.size(); | |
745 } | |
746 | |
747 MultipartStreamReader::HttpHeaders& GetHeaders(size_t i) | |
748 { | |
749 return parts_[i].headers_; | |
750 } | |
751 | |
752 const std::string& GetData(size_t i) const | |
753 { | |
754 return parts_[i].data_; | |
755 } | |
756 }; | |
757 | |
758 | |
759 TEST(MultipartStreamReader, ParseHeaders) | |
760 { | |
761 std::string ct, b, st, header; | |
762 | |
763 { | |
764 MultipartStreamReader::HttpHeaders h; | |
765 h["hello"] = "world"; | |
766 h["Content-Type"] = "world"; // Should be in lower-case | |
767 h["CONTENT-type"] = "world"; // Should be in lower-case | |
768 ASSERT_FALSE(MultipartStreamReader::GetMainContentType(header, h)); | |
769 } | |
770 | |
771 { | |
772 MultipartStreamReader::HttpHeaders h; | |
773 h["content-type"] = "world"; | |
774 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h)); | |
775 ASSERT_EQ(header, "world"); | |
776 ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header)); | |
777 } | |
778 | |
779 { | |
780 MultipartStreamReader::HttpHeaders h; | |
781 h["content-type"] = "multipart/related; dummy=value; boundary=1234; hello=world"; | |
782 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h)); | |
783 ASSERT_EQ(header, h["content-type"]); | |
784 ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header)); | |
785 ASSERT_EQ(ct, "multipart/related"); | |
786 ASSERT_EQ(b, "1234"); | |
787 ASSERT_TRUE(st.empty()); | |
788 } | |
789 | |
790 { | |
791 ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType | |
792 (ct, st, b, "multipart/related; boundary=")); // Empty boundary | |
793 } | |
794 | |
795 { | |
796 ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType | |
797 (ct, st, b, "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO")); | |
798 ASSERT_EQ(ct, "multipart/related"); | |
799 ASSERT_EQ(b, "heLLO"); | |
800 ASSERT_EQ(st, "application/dicom"); | |
801 } | |
802 | |
803 { | |
804 ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType | |
805 (ct, st, b, "Multipart/Related; type=\"application/DICOM\"; Boundary=a")); | |
806 ASSERT_EQ(ct, "multipart/related"); | |
807 ASSERT_EQ(b, "a"); | |
808 ASSERT_EQ(st, "application/dicom"); | |
809 } | |
810 } | |
811 | |
812 | |
813 TEST(MultipartStreamReader, BytePerByte) | |
814 { | |
815 std::string stream = "GARBAGE"; | |
816 | |
817 std::string boundary = "123456789123456789"; | |
818 | |
819 { | |
820 for (size_t i = 0; i < 10; i++) | |
821 { | |
822 std::string f = "hello " + boost::lexical_cast<std::string>(i); | |
823 | |
824 stream += "\r\n--" + boundary + "\r\n"; | |
825 if (i % 2 == 0) | |
826 stream += "Content-Length: " + boost::lexical_cast<std::string>(f.size()) + "\r\n"; | |
827 stream += "Content-Type: toto " + boost::lexical_cast<std::string>(i) + "\r\n\r\n"; | |
828 stream += f; | |
829 } | |
830 | |
831 stream += "\r\n--" + boundary + "--"; | |
832 stream += "GARBAGE"; | |
833 } | |
834 | |
835 for (unsigned int k = 0; k < 2; k++) | |
836 { | |
837 MultipartTester decoded; | |
838 | |
839 MultipartStreamReader reader(boundary); | |
840 reader.SetBlockSize(1); | |
841 reader.SetHandler(decoded); | |
842 | |
843 if (k == 0) | |
844 { | |
845 for (size_t i = 0; i < stream.size(); i++) | |
846 { | |
847 reader.AddChunk(&stream[i], 1); | |
848 } | |
849 } | |
850 else | |
851 { | |
852 reader.AddChunk(stream); | |
853 } | |
854 | |
855 reader.CloseStream(); | |
856 | |
857 ASSERT_EQ(10u, decoded.GetCount()); | |
858 | |
859 for (size_t i = 0; i < 10; i++) | |
860 { | |
861 ASSERT_EQ("hello " + boost::lexical_cast<std::string>(i), decoded.GetData(i)); | |
862 ASSERT_EQ("toto " + boost::lexical_cast<std::string>(i), decoded.GetHeaders(i)["content-type"]); | |
863 | |
864 if (i % 2 == 0) | |
865 { | |
866 ASSERT_EQ(2u, decoded.GetHeaders(i).size()); | |
867 ASSERT_TRUE(decoded.GetHeaders(i).find("content-length") != decoded.GetHeaders(i).end()); | |
868 } | |
869 } | |
870 } | |
871 } |