Imported Upstream version 17.25.4
[platform/upstream/libzypp.git] / tests / zyppng / media / EvDownloader_test.cc
1 #include <zypp/zyppng/base/EventDispatcher>
2 #include <zypp/zyppng/media/network/downloader.h>
3 #include <zypp/zyppng/media/network/networkrequesterror.h>
4 #include <zypp/zyppng/media/network/networkrequestdispatcher.h>
5 #include <zypp/zyppng/media/network/request.h>
6 #include <zypp/media/CredentialManager.h>
7 #include <zypp/TmpPath.h>
8 #include <zypp/PathInfo.h>
9 #include <zypp/ZConfig.h>
10 #include <iostream>
11 #include <fstream>
12 #include <random>
13 #include "WebServer.h"
14
15 #include <boost/test/unit_test.hpp>
16 #include <boost/test/data/test_case.hpp>
17
18 #define BOOST_TEST_REQ_SUCCESS(REQ) \
19   do { \
20       BOOST_REQUIRE_MESSAGE( REQ->state() == zyppng::Download::Success, zypp::str::Format(" %1% != zyppng::Download::Success (%2%)") % REQ->state() % REQ->errorString() ); \
21       BOOST_REQUIRE_EQUAL( REQ->lastRequestError().type(), zyppng::NetworkRequestError::NoError ); \
22       BOOST_REQUIRE( REQ->errorString().empty() ); \
23   } while(false)
24
25 #define BOOST_TEST_REQ_FAILED(REQ) \
26   do { \
27       BOOST_REQUIRE_EQUAL( REQ->state(), zyppng::Download::Failed ); \
28       BOOST_REQUIRE_NE( REQ->lastRequestError().type(), zyppng::NetworkRequestError::NoError ); \
29       BOOST_REQUIRE( !REQ->errorString().empty() ); \
30   } while(false)
31
32 namespace bdata = boost::unit_test::data;
33
34 bool withSSL[] = {true, false};
35
36 //read all contents of a file into a string)
37 std::string readFile ( const zypp::Pathname &file )
38 {
39   if ( ! zypp::PathInfo( file ).isFile() ) {
40     return std::string();
41   }
42   std::ifstream istr( file.asString().c_str() );
43   if ( ! istr ) {
44     return std::string();
45   }
46
47   std::string str((std::istreambuf_iterator<char>(istr)),
48     std::istreambuf_iterator<char>());
49   return str;
50 }
51
52
53 BOOST_DATA_TEST_CASE( dltest_basic, bdata::make( withSSL ), withSSL)
54 {
55   auto ev = zyppng::EventDispatcher::createMain();
56
57   zyppng::Downloader downloader;
58
59   //make sure the data here is big enough to cross the threshold of 256 bytes so we get a progress signal emitted and not only the alive signal.
60   std::string dummyContent = "This is just some dummy content,\nto test downloading and signals.\n"
61                              "This is just some dummy content,\nto test downloading and signals.\n"
62                              "This is just some dummy content,\nto test downloading and signals.\n"
63                              "This is just some dummy content,\nto test downloading and signals.\n";
64
65   WebServer web((zypp::Pathname(TESTS_SRC_DIR)/"data"/"dummywebroot").c_str(), 10001, withSSL );
66   web.addRequestHandler("getData", WebServer::makeResponse("200", dummyContent ) );
67   BOOST_REQUIRE( web.start() );
68
69   zypp::filesystem::TmpFile targetFile;
70   zyppng::Url weburl (web.url());
71   weburl.setPathName("/handler/getData");
72   zyppng::TransferSettings set = web.transferSettings();
73
74   bool gotStarted = false;
75   bool gotFinished = false;
76   bool gotProgress = false;
77   bool gotAlive = false;
78   off_t lastProgress = 0;
79   off_t totalDL = 0;
80
81   std::vector<zyppng::Download::State> allStates;
82
83
84   zyppng::Download::Ptr dl = downloader.downloadFile(  weburl, targetFile.path(), dummyContent.length() );
85   dl->settings() = set;
86
87   dl->sigFinished().connect([&]( zyppng::Download & ){
88     gotFinished = true;
89     ev->quit();
90   });
91
92   dl->sigStarted().connect([&]( zyppng::Download & ){
93     gotStarted = true;
94   });
95
96   dl->sigAlive().connect([&]( zyppng::Download &, off_t ){
97     gotAlive = true;
98   });
99
100   dl->sigStateChanged().connect([&]( zyppng::Download &, zyppng::Download::State state ){
101     allStates.push_back( state );
102   });
103
104   dl->sigProgress().connect([&]( zyppng::Download &, off_t dltotal, off_t dlnow ){
105     gotProgress = true;
106     lastProgress = dlnow;
107     totalDL = dltotal;
108   });
109   dl->start();
110   ev->run();
111
112   BOOST_TEST_REQ_SUCCESS( dl );
113   BOOST_REQUIRE( gotStarted );
114   BOOST_REQUIRE( gotFinished );
115   BOOST_REQUIRE( gotProgress );
116   BOOST_REQUIRE( gotAlive );
117   BOOST_REQUIRE_EQUAL( totalDL, dummyContent.length() );
118   BOOST_REQUIRE_EQUAL( lastProgress, dummyContent.length() );
119   BOOST_REQUIRE ( allStates == std::vector<zyppng::Download::State>({zyppng::Download::Initializing,zyppng::Download::Running, zyppng::Download::Success}) );
120 }
121
122
123 struct MirrorSet
124 {
125   std::string name; //<dataset name, used only in debug output if the test fails
126   std::string handlerPath; //< the webhandler path used to query the resource
127   std::vector< std::pair<int, std::string> > mirrors; //all mirrors injected into the metalink file
128   int expectedFileDownloads; //< how many downloads are direct file downloads
129   int expectedHandlerDownloads; //< how many started downloads are handler requests
130   std::vector<zyppng::Download::State> expectedStates;
131   bool expectSuccess; //< should the download work out?
132
133   std::ostream & operator<<( std::ostream & str ) const {
134     str << "MirrorSet{ " << name << " }";
135     return str;
136   }
137 };
138
139 namespace boost{ namespace test_tools{ namespace tt_detail{
140 template<>
141 struct print_log_value< MirrorSet > {
142     void    operator()( std::ostream& str, MirrorSet const& set) {
143       set.operator<<( str );
144     }
145 };
146 }}}
147
148 std::vector< MirrorSet > generateMirr ()
149 {
150   std::vector< MirrorSet > res;
151
152   //all mirrors good:
153   res.push_back( MirrorSet() );
154   res.back().name = "All good mirrors";
155   res.back().expectSuccess = true;
156   res.back().expectedHandlerDownloads  = 1;
157   res.back().expectedFileDownloads  = 9;
158   res.back().expectedStates = {zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Success};
159   for ( int i = 100 ; i >= 10; i -= 10 )
160     res.back().mirrors.push_back( std::make_pair( i, "/test.txt") );
161
162   //no mirrors:
163   res.push_back( MirrorSet() );
164   res.back().name = "Empty mirrors";
165   res.back().expectSuccess = true;
166   res.back().expectedHandlerDownloads  = 10;
167   res.back().expectedFileDownloads  = 0;
168   res.back().expectedStates = {zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Success};
169
170   //only broken mirrors:
171   res.push_back( MirrorSet() );
172   res.back().name = "All broken mirrors";
173   res.back().expectSuccess = true;
174   res.back().expectedHandlerDownloads  = 2; //has to fall back to url handler download
175   res.back().expectedFileDownloads  = 10; //should try all mirrors and fail
176   res.back().expectedStates = {zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Running, zyppng::Download::Success};
177   for ( int i = 100 ; i >= 10; i -= 10 )
178     res.back().mirrors.push_back( std::make_pair( i, "/doesnotexist.txt") );
179
180   //some broken mirrors:
181   res.push_back( MirrorSet() );
182   res.back().name = "Some broken mirrors less URLs than blocks";
183   res.back().expectSuccess = true;
184   res.back().expectedHandlerDownloads = 1;
185   res.back().expectedFileDownloads = 9 + 3; // 3 should fail due to broken mirrors
186   res.back().expectedStates = {zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Success};
187   for ( int i = 10 ; i >= 5; i-- ) {
188     if ( i % 2 ) {
189       res.back().mirrors.push_back( std::make_pair( i*10, "/doesnotexist.txt") );
190     } else {
191       res.back().mirrors.push_back( std::make_pair( i*10, "/test.txt") );
192     }
193   }
194
195   //some broken mirrors with more URLs than blocks:
196   res.push_back( MirrorSet() );
197   res.back().name = "Some broken mirrors more URLs than blocks";
198   res.back().expectSuccess = true;
199   res.back().expectedHandlerDownloads = 1;
200   //its not really possible to know how many times the downloads will fail, there are
201   //5 broken mirrors in the set, but if a working mirror is done before the last broken
202   //URL is picked from the dataset not all broken URLs will be used
203   res.back().expectedFileDownloads  = -1;
204   res.back().expectedStates = {zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Success};
205   for ( int i = 10 ; i >= 1; i-- ) {
206     if ( i % 2 ) {
207       res.back().mirrors.push_back( std::make_pair( i*10, "/doesnotexist.txt") );
208     } else {
209       res.back().mirrors.push_back( std::make_pair( i*10, "/test.txt") );
210     }
211   }
212
213   //mirrors where some return a invalid block
214   res.push_back( MirrorSet() );
215   res.back().name = "Some mirrors return broken blocks";
216   res.back().expectSuccess = true;
217   res.back().expectedHandlerDownloads  = 1;
218   //its not really possible to know how many times the downloads will fail, there are
219   //5 broken mirrors in the set, but if a working mirror is done before the last broken
220   //URL is picked from the dataset not all broken URLs will be used
221   res.back().expectedFileDownloads  = -1;
222   res.back().expectedStates = {zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Success};
223   for ( int i = 10 ; i >= 1; i-- ) {
224     if ( i % 2 ) {
225       res.back().mirrors.push_back( std::make_pair( i*10, "/handler/random") );
226     } else {
227       res.back().mirrors.push_back( std::make_pair( i*10, "/test.txt") );
228     }
229   }
230   return res;
231 }
232
233
234 //create one URL line for a metalink template file
235 std::string makeUrl ( int pref, const zyppng::Url &url )
236 {
237   return ( zypp::str::Format( "<url preference=\"%1%\" location=\"de\" type=\"%2%\">%3%</url>" ) % pref % url.getScheme() % url );
238 };
239
240
241 static bool requestWantsMetaLink ( const WebServer::Request &req )
242 {
243   auto it = req.params.find( "HTTP_ACCEPT" );
244   if ( it != req.params.end() ) {
245     if ( (*it).second.find("application/metalink+xml")  != std::string::npos ||
246          (*it).second.find("application/metalink4+xml") != std::string::npos ) {
247       return true;
248     }
249   }
250   return false;
251 }
252
253 //creates a request handler for the Mock WebServer that returns the metalink data
254 //specified in \a data if the request has the metalink accept handler
255 WebServer::RequestHandler makeMetaFileHandler ( const std::string *data )
256 {
257   return [ data ]( WebServer::Request &req ){
258     if ( requestWantsMetaLink( req ) ) {
259       req.rout << WebServer::makeResponseString( "200", { "Content-Type: application/metalink+xml; charset=utf-8\r\n" }, *data );
260       return;
261     }
262     req.rout << "Location: /test.txt\r\n\r\n";
263     return;
264   };
265 };
266
267 //creates a request handler for the Mock WebServer that returns a junk block of
268 //data for a range request, otherwise relocates the request
269 WebServer::RequestHandler makeJunkBlockHandler ( )
270 {
271   return [ ]( WebServer::Request &req ){
272     auto it = req.params.find( "HTTP_RANGE" );
273     if ( it != req.params.end() && zypp::str::startsWith( it->second, "bytes=" ) ) {
274         //bytes=786432-1048575
275         std::string range = it->second.substr( 6 ); //remove bytes=
276         size_t dash = range.find_first_of( "-" );
277         off_t start = -1;
278         off_t end = -1;
279         if ( dash != std::string::npos ) {
280           start = std::stoll( range.substr( 0, dash ) );
281           end   = std::stoll( range.substr( dash+1 ) );
282         }
283
284         if ( start != -1 && end != -1 ) {
285           std::string block;
286           for ( off_t curr = 0; curr < ( end - start); curr++ ) {
287             block += 'a';
288           }
289           req.rout << "Status: 206 Partial Content\r\n"
290                    << "Accept-Ranges: bytes\r\n"
291                    << "Content-Length: "<<( end - start)<<"\r\n\r\n"
292                    << block;
293           return;
294         }
295     }
296     req.rout << "Location: /test.txt\r\n\r\n";
297     return;
298   };
299 };
300
301 int maxConcurrentDLs[] = { 1, 2, 4, 8, 10, 15 };
302
303 BOOST_DATA_TEST_CASE( test1, bdata::make( generateMirr() ) * bdata::make( withSSL ) * bdata::make( maxConcurrentDLs )  , elem, withSSL, maxDLs )
304 {
305   //each URL in the metalink file has a preference , a schema and of course the URL, we need to adapt those to our test setup
306   //so we generate the file on the fly from a template in the test data
307   std::string metaTempl = readFile ( zypp::Pathname(TESTS_SRC_DIR)/"/zyppng/data/downloader/test.txt.meta" );
308   BOOST_REQUIRE( !metaTempl.empty() );
309
310   auto ev = zyppng::EventDispatcher::createMain();
311
312   WebServer web((zypp::Pathname(TESTS_SRC_DIR)/"/zyppng/data/downloader").c_str(), 10001, withSSL );
313   BOOST_REQUIRE( web.start() );
314
315   zypp::filesystem::TmpFile targetFile;
316   zyppng::Downloader downloader;
317   downloader.requestDispatcher()->setMaximumConcurrentConnections( maxDLs );
318
319   //first metalink download, generate a fully valid one
320   zyppng::Url weburl (web.url());
321   weburl.setPathName("/handler/test.txt");
322
323   std::string urls;
324   if ( elem.mirrors.size() ) {
325     for ( const auto &mirr : elem.mirrors ) {
326       zyppng::Url mirrUrl (web.url());
327       mirrUrl.setPathName( mirr.second );
328       urls += makeUrl( mirr.first, mirrUrl ) + "\n";
329     }
330   }
331
332   std::string metaFile = zypp::str::Format( metaTempl ) % urls;
333   web.addRequestHandler("test.txt", makeMetaFileHandler( &metaFile ) );
334   web.addRequestHandler("random", makeJunkBlockHandler( ) );
335
336   int expectedDownloads = elem.expectedHandlerDownloads + elem.expectedFileDownloads;
337   int startedDownloads = 0;
338   int finishedDownloads = 0;
339   bool downloadHadError = false;
340   bool gotProgress = false;
341   bool gotAlive = false;
342   bool gotMultiDLState = false;
343   std::vector<zyppng::Download::State> allStates;
344   off_t gotTotal = 0;
345   off_t lastProgress = 0;
346
347   int countHandlerReq = 0; //the requests made to the handler slot
348   int countFileReq = 0;    //the requests made to the file directly, e.g. a mirror read from the metalink file
349
350   auto dl = downloader.downloadFile( weburl, targetFile );
351   dl->settings() = web.transferSettings();
352
353   dl->dispatcher().sigDownloadStarted().connect( [&]( zyppng::NetworkRequestDispatcher &, zyppng::NetworkRequest &req){
354     startedDownloads++;
355     if ( req.url() == weburl )
356       countHandlerReq++;
357     else
358       countFileReq++;
359   });
360
361   dl->dispatcher().sigDownloadFinished().connect( [&]( zyppng::NetworkRequestDispatcher &, zyppng::NetworkRequest &req ){
362     finishedDownloads++;
363
364     if ( !downloadHadError )
365       downloadHadError = req.hasError();
366   });
367
368   dl->sigFinished().connect([&]( zyppng::Download & ){
369     ev->quit();
370   });
371
372   dl->sigAlive().connect([&]( zyppng::Download &, off_t dlnow ){
373     gotAlive = true;
374     lastProgress = dlnow;
375   });
376
377   dl->sigProgress().connect([&]( zyppng::Download &, off_t dltotal, off_t dlnow ){
378     gotProgress = true;
379     gotTotal = dltotal;
380     lastProgress = dlnow;
381   });
382
383   dl->sigStateChanged().connect([&]( zyppng::Download &, zyppng::Download::State state ){
384     if ( state == zyppng::Download::RunningMulti )
385       gotMultiDLState = true;
386   });
387
388   dl->sigStateChanged().connect([&]( zyppng::Download &, zyppng::Download::State state ){
389     allStates.push_back( state );
390   });
391
392   dl->start();
393   ev->run();
394
395   //std::cout << dl->errorString() << std::endl;
396
397   if ( elem.expectSuccess )
398     BOOST_TEST_REQ_SUCCESS( dl );
399   else
400     BOOST_TEST_REQ_FAILED ( dl );
401
402   if ( elem.expectedHandlerDownloads > -1 && elem.expectedFileDownloads > -1 )
403     BOOST_REQUIRE_EQUAL( startedDownloads, expectedDownloads );
404
405   BOOST_REQUIRE_EQUAL( startedDownloads, finishedDownloads );
406   BOOST_REQUIRE( gotAlive );
407   BOOST_REQUIRE( gotProgress );
408   BOOST_REQUIRE( gotMultiDLState );
409   BOOST_REQUIRE_EQUAL( lastProgress, 2148018 );
410   BOOST_REQUIRE_EQUAL( lastProgress, gotTotal );
411
412   if ( elem.expectedHandlerDownloads > -1 )
413     BOOST_REQUIRE_EQUAL( countHandlerReq, elem.expectedHandlerDownloads );
414
415   if ( elem.expectedFileDownloads > -1 )
416     BOOST_REQUIRE_EQUAL( countFileReq, elem.expectedFileDownloads );
417
418   if ( !elem.expectedStates.empty() )
419     BOOST_REQUIRE( elem.expectedStates == allStates );
420 }
421
422 //tests:
423 // - broken cert
424 // - correct expected filesize
425 // - invalid filesize
426 // - password handling and propagation
427
428
429 //creates a request handler that requires a authentication to work
430 WebServer::RequestHandler createAuthHandler ( )
431 {
432   return [ ]( WebServer::Request &req ){
433     //Basic dGVzdDp0ZXN0
434     auto it = req.params.find( "HTTP_AUTHORIZATION" );
435     bool authorized = false;
436     if ( it != req.params.end() )
437       authorized = ( it->second == "Basic dGVzdDp0ZXN0" );
438
439     if ( !authorized ) {
440       req.rout << "Status: 401 Unauthorized\r\n"
441                   "Content-Type: text/html; charset=utf-8\r\n"
442                   "WWW-Authenticate: Basic realm=\"User Visible Realm\", charset=\"UTF-8\" \r\n"
443                   "\r\n"
444                   "Sorry you are not authorized.";
445       return;
446     }
447
448     if ( requestWantsMetaLink( req ) ) {
449       it = req.params.find( "HTTPS" );
450       if ( it != req.params.end() && it->second == "on" ) {
451         req.rout << "Status: 307 Temporary Redirect\r\n"
452                  << "Cache-Control: no-cache\r\n"
453                  << "Location: /auth-https.meta\r\n\r\n";
454       } else {
455         req.rout << "Status: 307 Temporary Redirect\r\n"
456                  << "Cache-Control: no-cache\r\n"
457                  << "Location: /auth-http.meta\r\n\r\n";
458       }
459       return;
460     }
461     req.rout << "Status: 307 Temporary Redirect\r\n"
462                 "Location: /test.txt\r\n\r\n";
463     return;
464   };
465 };
466
467 BOOST_DATA_TEST_CASE( dltest_auth, bdata::make( withSSL ), withSSL )
468 {
469   //don't write or read creds from real settings dir
470   zypp::filesystem::TmpDir repoManagerRoot;
471   zypp::ZConfig::instance().setRepoManagerRoot( repoManagerRoot.path() );
472
473   auto ev = zyppng::EventDispatcher::createMain();
474
475   zyppng::Downloader downloader;
476
477   WebServer web((zypp::Pathname(TESTS_SRC_DIR)/"/zyppng/data/downloader").c_str(), 10001, withSSL );
478   BOOST_REQUIRE( web.start() );
479
480   zypp::filesystem::TmpFile targetFile;
481   zyppng::Url weburl (web.url());
482   weburl.setPathName("/handler/test.txt");
483   zyppng::TransferSettings set = web.transferSettings();
484
485   web.addRequestHandler( "test.txt", createAuthHandler() );
486   web.addRequestHandler( "quit", [ &ev ]( WebServer::Request & ){ ev->quit();} );
487
488   {
489     auto dl = downloader.downloadFile( weburl, targetFile.path() );
490     dl->settings() = set;
491
492     dl->sigFinished( ).connect([ &ev ]( zyppng::Download & ){
493       ev->quit();
494     });
495
496     dl->start();
497     ev->run();
498     BOOST_TEST_REQ_FAILED( dl );
499     BOOST_REQUIRE_EQUAL( dl->lastRequestError().type(), zyppng::NetworkRequestError::Unauthorized );
500   }
501
502   {
503     auto dl = downloader.downloadFile( weburl, targetFile.path() );
504     dl->settings() = set;
505
506     int gotAuthRequest = 0;
507
508     dl->sigFinished( ).connect([ &ev ]( zyppng::Download & ){
509       ev->quit();
510     });
511
512     dl->sigAuthRequired().connect( [&]( zyppng::Download &, zyppng::NetworkAuthData &auth, const std::string &availAuth ){
513       gotAuthRequest++;
514       if ( gotAuthRequest >= 2 )
515         return;
516       auth.setUsername("wrong");
517       auth.setPassword("credentials");
518     });
519
520     dl->start();
521     ev->run();
522     BOOST_TEST_REQ_FAILED( dl );
523     BOOST_REQUIRE_EQUAL( gotAuthRequest, 2 );
524     BOOST_REQUIRE_EQUAL( dl->lastRequestError().type(), zyppng::NetworkRequestError::AuthFailed );
525   }
526
527   {
528     int gotAuthRequest = 0;
529     std::vector<zyppng::Download::State> allStates;
530     auto dl = downloader.downloadFile( weburl, targetFile.path() );
531     dl->settings() = set;
532
533     dl->sigFinished( ).connect([ &ev ]( zyppng::Download & ){
534       ev->quit();
535     });
536
537     dl->sigAuthRequired().connect( [&]( zyppng::Download &, zyppng::NetworkAuthData &auth, const std::string &availAuth ){
538       gotAuthRequest++;
539       auth.setUsername("test");
540       auth.setPassword("test");
541     });
542
543     dl->sigStateChanged().connect([&]( zyppng::Download &, zyppng::Download::State state ){
544       allStates.push_back( state );
545     });
546
547
548     dl->start();
549     ev->run();
550     BOOST_TEST_REQ_SUCCESS( dl );
551     BOOST_REQUIRE_EQUAL( gotAuthRequest, 1 );
552     BOOST_REQUIRE ( allStates == std::vector<zyppng::Download::State>({zyppng::Download::Initializing, zyppng::Download::RunningMulti, zyppng::Download::Success}) );
553   }
554
555   {
556     //the creds should be in the credential manager now, we should not need to specify them again in the slot
557     bool gotAuthRequest = false;
558     auto dl = downloader.downloadFile( weburl, targetFile.path() );
559     dl->settings() = set;
560
561     dl->sigFinished( ).connect([ &ev ]( zyppng::Download & ){
562       ev->quit();
563     });
564
565     dl->sigAuthRequired().connect( [&]( zyppng::Download &, zyppng::NetworkAuthData &auth, const std::string &availAuth ){
566       gotAuthRequest = true;
567     });
568
569     dl->start();
570     ev->run();
571     BOOST_TEST_REQ_SUCCESS( dl );
572     BOOST_REQUIRE( !gotAuthRequest );
573   }
574 }
575
576 /**
577  * Test for bsc#1174011 auth=basic ignored in some cases
578  *
579  * If the URL specifes ?auth=basic libzypp should proactively send credentials we have available in the cred store
580  */
581 BOOST_DATA_TEST_CASE( dltest_auth_basic, bdata::make( withSSL ), withSSL )
582 {
583   //don't write or read creds from real settings dir
584   zypp::filesystem::TmpDir repoManagerRoot;
585   zypp::ZConfig::instance().setRepoManagerRoot( repoManagerRoot.path() );
586
587   auto ev = zyppng::EventDispatcher::createMain();
588
589   zyppng::Downloader downloader;
590
591   WebServer web((zypp::Pathname(TESTS_SRC_DIR)/"/zyppng/data/downloader").c_str(), 10001, withSSL );
592   BOOST_REQUIRE( web.start() );
593
594   zypp::filesystem::TmpFile targetFile;
595   zyppng::Url weburl (web.url());
596   weburl.setPathName("/handler/test.txt");
597   weburl.setQueryParam("auth", "basic");
598   weburl.setUsername("test");
599
600   // make sure the creds are already available
601   zypp::media::CredentialManager cm( repoManagerRoot.path() );
602   zypp::media::AuthData data ("test", "test");
603   data.setUrl( weburl );
604   cm.addCred( data );
605   cm.save();
606
607   zyppng::TransferSettings set = web.transferSettings();
608
609   web.addRequestHandler( "test.txt", createAuthHandler() );
610   web.addRequestHandler( "quit", [ &ev ]( WebServer::Request & ){ ev->quit();} );
611
612   {
613     // simply check by request count if the test was successfull:
614     // if the proactive code adding the credentials to the first request is not executed we will
615     // have more than 1 request.
616     int reqCount = 0;
617     auto dispatcher = downloader.requestDispatcher();
618     dispatcher->sigDownloadStarted().connect([&]( zyppng::NetworkRequestDispatcher &, zyppng::NetworkRequest & ){
619       reqCount++;
620     });
621
622
623     auto dl = downloader.downloadFile( weburl, targetFile.path() );
624     dl->setMultiPartHandlingEnabled( false );
625
626     dl->settings() = set;
627
628     dl->sigFinished( ).connect([ &ev ]( zyppng::Download & ){
629       ev->quit();
630     });
631
632     dl->start();
633     ev->run();
634     BOOST_TEST_REQ_SUCCESS( dl );
635     BOOST_REQUIRE_EQUAL( reqCount, 1 );
636   }
637 }