|  | #ifndef FUMA_INTEGRATION_TEST_HELPER_HPP
 | 
  
    |  | #define FUMA_INTEGRATION_TEST_HELPER_HPP
 | 
  
    |  | 
 | 
  
    |  | #include <Wt/WResource>
 | 
  
    |  | #include <Wt/WServer>
 | 
  
    |  | #include <Wt/Http/Client>
 | 
  
    |  | #include <thread>
 | 
  
    |  | #include <mutex>
 | 
  
    |  | #include <condition_variable>
 | 
  
    |  | 
 | 
  
    |  | #include <vector>
 | 
  
    |  | 
 | 
  
    |  | namespace Wt
 | 
  
    |  | {
 | 
  
    |  |     namespace Http
 | 
  
    |  |     {
 | 
  
    |  |         class Response;
 | 
  
    |  |         class ResponseContinuation;
 | 
  
    |  |         class Request;
 | 
  
    |  |     }
 | 
  
    |  | }
 | 
  
    |  | 
 | 
  
    |  | namespace Fuma
 | 
  
    |  | {
 | 
  
    |  |     namespace Test
 | 
  
    |  |     {
 | 
  
    |  |         struct StringResource
 | 
  
    |  |         {
 | 
  
    |  |             std::string m_data;
 | 
  
    |  |             explicit StringResource(const std::string & val)
 | 
  
    |  |                 : m_data{val}
 | 
  
    |  |             {}
 | 
  
    |  |             void write(const Wt::Http::Request & request, Wt::Http::Response & response) const;
 | 
  
    |  |         };
 | 
  
    |  | 
 | 
  
    |  |         struct StringVecResource
 | 
  
    |  |         {
 | 
  
    |  |             std::vector<std::string> m_data;
 | 
  
    |  |             explicit StringVecResource(const std::string & val)
 | 
  
    |  |                 : m_data{10,val}
 | 
  
    |  |             {}
 | 
  
    |  |             void write(const Wt::Http::Request & request, Wt::Http::Response & response) const;
 | 
  
    |  |         };
 | 
  
    |  | 
 | 
  
    |  |         class Resource
 | 
  
    |  |             : public Wt::WResource
 | 
  
    |  |         {
 | 
  
    |  |                 int aborted_;
 | 
  
    |  |                 StringResource simple_fixture_;
 | 
  
    |  |                 StringVecResource stream_fixture_;
 | 
  
    |  | 
 | 
  
    |  |             public:
 | 
  
    |  |                 Resource();
 | 
  
    |  |                 virtual ~Resource();
 | 
  
    |  |                 // accessors
 | 
  
    |  |                 int abortedCount() const;
 | 
  
    |  |                 // interface requirements
 | 
  
    |  |             protected:
 | 
  
    |  |                 virtual void handleAbort(const Wt::Http::Request & request);
 | 
  
    |  |                 virtual void handleRequest(const Wt::Http::Request & request, Wt::Http::Response & response);
 | 
  
    |  |         };
 | 
  
    |  | 
 | 
  
    |  |         class Server
 | 
  
    |  |             : public Wt::WServer
 | 
  
    |  |         {
 | 
  
    |  |                 Resource resource_;
 | 
  
    |  |             public:
 | 
  
    |  |                 Server();
 | 
  
    |  |                 std::string address() const;
 | 
  
    |  |                 Resource & resource();
 | 
  
    |  |                 const Resource & resource() const;
 | 
  
    |  |         };
 | 
  
    |  | 
 | 
  
    |  |         class Client
 | 
  
    |  |             : public Wt::Http::Client
 | 
  
    |  |         {
 | 
  
    |  |                 bool done_;
 | 
  
    |  |                 bool abortAfterHeaders_;
 | 
  
    |  |                 std::condition_variable doneCondition_;
 | 
  
    |  |                 std::mutex doneMutex_;
 | 
  
    |  |                 std::mutex dataMutex_;
 | 
  
    |  | 
 | 
  
    |  |                 boost::system::error_code err_;
 | 
  
    |  |                 Wt::Http::Message message_;
 | 
  
    |  |             public:
 | 
  
    |  |                 Client();
 | 
  
    |  |                 bool isDone() const;
 | 
  
    |  |                 const boost::system::error_code & err() const;
 | 
  
    |  |                 const Wt::Http::Message & message() const;
 | 
  
    |  |                 void abortAfterHeaders();
 | 
  
    |  |                 void waitDone();
 | 
  
    |  |                 void onDone(boost::system::error_code err, const Wt::Http::Message & m);
 | 
  
    |  |                 void onHeadersReceived(const Wt::Http::Message & m);
 | 
  
    |  |                 void onDataReceived(const std::string & d);
 | 
  
    |  |         };
 | 
  
    |  |     } // Fuma::Test namespace
 | 
  
    |  | } // Fuma namespace
 | 
  
    |  | 
 | 
  
    |  | #if defined(HAVE_CONFIG_H)
 | 
  
    |  |     #include "config.h"
 | 
  
    |  | #endif
 | 
  
    |  | 
 | 
  
    |  | #include <Wt/WConfig.h>
 | 
  
    |  | #include <Wt/Http/Response>
 | 
  
    |  | #include <Wt/Http/ResponseContinuation>
 | 
  
    |  | #include <Wt/Http/Request>
 | 
  
    |  | #include <Wt/Utils>
 | 
  
    |  | #include <boost/asio/error.hpp>
 | 
  
    |  | namespace Fuma
 | 
  
    |  | {
 | 
  
    |  |     namespace Test
 | 
  
    |  |     {
 | 
  
    |  |         Client::Client()
 | 
  
    |  |             : done_(false)
 | 
  
    |  |             , abortAfterHeaders_(false)
 | 
  
    |  |             , err_{boost::asio::error::bad_descriptor}
 | 
  
    |  |         {
 | 
  
    |  |             setTimeout(15);
 | 
  
    |  |             setMaximumResponseSize(0);
 | 
  
    |  |             setFollowRedirect(true);
 | 
  
    |  |             setMaxRedirects(10);
 | 
  
    |  |             done().connect(this, &Client::onDone);
 | 
  
    |  |             headersReceived().connect(this, &Client::onHeadersReceived);
 | 
  
    |  |             bodyDataReceived().connect(this, &Client::onDataReceived);
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void Client::abortAfterHeaders()
 | 
  
    |  |         {
 | 
  
    |  |             abortAfterHeaders_ = true;
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void Client::waitDone()
 | 
  
    |  |         {
 | 
  
    |  |             std::unique_lock<decltype(doneMutex_)> guard(doneMutex_);
 | 
  
    |  |             doneCondition_.wait(guard,[this] { return done_;});
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         bool Client::isDone() const
 | 
  
    |  |         {
 | 
  
    |  |             return done_;
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void Client::onDone(boost::system::error_code err, const Wt::Http::Message & m)
 | 
  
    |  |         {
 | 
  
    |  |             std::unique_lock<decltype(doneMutex_)> guard(doneMutex_);
 | 
  
    |  |             err_ = err;
 | 
  
    |  |             done_ = true;
 | 
  
    |  |             doneCondition_.notify_one();
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void Client::onHeadersReceived(const Wt::Http::Message & m)
 | 
  
    |  |         {
 | 
  
    |  |             std::unique_lock<decltype(dataMutex_)> guard(dataMutex_);
 | 
  
    |  |             message_ = m;
 | 
  
    |  |             if(abortAfterHeaders_)
 | 
  
    |  |             {
 | 
  
    |  |                 abort();
 | 
  
    |  |             }
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void Client::onDataReceived(const std::string & d)
 | 
  
    |  |         {
 | 
  
    |  |             std::unique_lock<decltype(dataMutex_)> guard(dataMutex_);
 | 
  
    |  |             message_.addBodyText(d);
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         const boost::system::error_code & Client::err() const
 | 
  
    |  |         {
 | 
  
    |  |             return err_;
 | 
  
    |  |         }
 | 
  
    |  |         const Wt::Http::Message & Client::message() const
 | 
  
    |  |         {
 | 
  
    |  |             return message_;
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         Resource::Resource()
 | 
  
    |  |             : aborted_(0)
 | 
  
    |  |             ,simple_fixture_{"Hello"}
 | 
  
    |  |             ,stream_fixture_{"Hello"}
 | 
  
    |  |         { }
 | 
  
    |  | 
 | 
  
    |  |         Resource::~Resource()
 | 
  
    |  |         {
 | 
  
    |  |             beingDeleted();
 | 
  
    |  |         }
 | 
  
    |  |         int Resource::abortedCount() const
 | 
  
    |  |         {
 | 
  
    |  |             return aborted_;
 | 
  
    |  |         }
 | 
  
    |  |         void Resource::handleRequest(const Wt::Http::Request & request, Wt::Http::Response & response)
 | 
  
    |  |         {
 | 
  
    |  |             if(request.getParameterMap().count("stream"))
 | 
  
    |  |             {
 | 
  
    |  |                 stream_fixture_.write(request,response);
 | 
  
    |  |                 return;
 | 
  
    |  |             }
 | 
  
    |  |             simple_fixture_.write(request,response);
 | 
  
    |  |         }
 | 
  
    |  |         void Resource::handleAbort(const Wt::Http::Request & request)
 | 
  
    |  |         {
 | 
  
    |  |             ++aborted_;
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         Server::Server()
 | 
  
    |  |         {
 | 
  
    |  |             int argc = 7;
 | 
  
    |  |             const char * const argv[]
 | 
  
    |  |                 = { "test",
 | 
  
    |  |                     "--http-address", "127.0.0.1",
 | 
  
    |  |                     "--http-port", "0",
 | 
  
    |  |                     "--docroot", "."
 | 
  
    |  |                   };
 | 
  
    |  |             setServerConfiguration(argc, const_cast<char **>(argv));
 | 
  
    |  |             addResource(&resource_, "/test");
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         std::string Server::address() const
 | 
  
    |  |         {
 | 
  
    |  |             return "127.0.0.1:" + std::to_string(httpPort());
 | 
  
    |  |         }
 | 
  
    |  |         Resource & Server::resource()
 | 
  
    |  |         {
 | 
  
    |  |             return resource_;
 | 
  
    |  |         }
 | 
  
    |  |         const Resource & Server::resource() const
 | 
  
    |  |         {
 | 
  
    |  |             return resource_;
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void StringResource::write(const Wt::Http::Request & request, Wt::Http::Response & response) const
 | 
  
    |  |         {
 | 
  
    |  |             response.out() << m_data;
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |         void StringVecResource::write(const Wt::Http::Request & request, Wt::Http::Response & response) const
 | 
  
    |  |         {
 | 
  
    |  |             try
 | 
  
    |  |             {
 | 
  
    |  |                 auto * c  = request.continuation();
 | 
  
    |  |                 // mr kipling's exceedingly smug c++
 | 
  
    |  |                 size_t idx = (c) ? boost::any_cast<decltype(idx)>(c->data()) : decltype(idx) {} ;
 | 
  
    |  |                 size_t sz = m_data.size();
 | 
  
    |  |                 if(idx < sz)
 | 
  
    |  |                 {
 | 
  
    |  |                     response.out() << m_data.at(idx);
 | 
  
    |  |                     ++idx;
 | 
  
    |  |                     if(idx < sz)
 | 
  
    |  |                     {
 | 
  
    |  |                         c = response.createContinuation();
 | 
  
    |  |                         c->setData(idx);
 | 
  
    |  |                         c->waitForMoreData();
 | 
  
    |  |                         c->haveMoreData();
 | 
  
    |  |                     }
 | 
  
    |  |                 }
 | 
  
    |  |             }
 | 
  
    |  |             catch(boost::bad_any_cast & err)
 | 
  
    |  |             {
 | 
  
    |  |             }
 | 
  
    |  |         }
 | 
  
    |  | 
 | 
  
    |  |     } // Fuma::Test namespace
 | 
  
    |  | } // Fuma namespace
 | 
  
    |  | 
 | 
  
    |  | #if defined(HAVE_CONFIG_H)
 | 
  
    |  |     #include "config.h"
 | 
  
    |  | #endif
 | 
  
    |  | 
 | 
  
    |  | #include <vector>
 | 
  
    |  | #include <memory>
 | 
  
    |  | #include <boost/test/unit_test.hpp>
 | 
  
    |  | //#include "TestHelper.hpp"
 | 
  
    |  | 
 | 
  
    |  | struct IntegrationFixture
 | 
  
    |  | {
 | 
  
    |  |     Fuma::Test::Server m_server;
 | 
  
    |  |     std::vector<std::unique_ptr<Fuma::Test::Client>> m_clients;
 | 
  
    |  |     bool   m_is_started;
 | 
  
    |  | 
 | 
  
    |  |     enum state_t
 | 
  
    |  |     {
 | 
  
    |  |         eDontWait = 0,
 | 
  
    |  |         eWaitDone = 1<<1,
 | 
  
    |  |         eAbortAfterHeader = 1<<2,
 | 
  
    |  |         eCancelledDownload = (eWaitDone|eAbortAfterHeader)
 | 
  
    |  |     };
 | 
  
    |  | 
 | 
  
    |  |     IntegrationFixture()
 | 
  
    |  |         : m_server{}
 | 
  
    |  |         , m_clients{}
 | 
  
    |  |         , m_is_started{false}
 | 
  
    |  |     {
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     auto & nth_client(size_t n)
 | 
  
    |  |     {
 | 
  
    |  |         return m_clients.at(n);
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     auto & nth_msg(size_t n)
 | 
  
    |  |     {
 | 
  
    |  |         return nth_client(n)->message();
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     auto & nth_err(size_t n)
 | 
  
    |  |     {
 | 
  
    |  |         return nth_client(n)->err();
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     int aborted_count() const
 | 
  
    |  |     {
 | 
  
    |  |         return m_server.resource().abortedCount();
 | 
  
    |  |     }
 | 
  
    |  | 
 | 
  
    |  |     size_t require_http_get_url_scheduled(const std::string & endpoint,state_t setup)
 | 
  
    |  |     {
 | 
  
    |  |         size_t sz = m_clients.size();
 | 
  
    |  |         m_clients.emplace_back(std::make_unique<Fuma::Test::Client>());
 | 
  
    |  |         if(setup & eAbortAfterHeader)
 | 
  
    |  |         {
 | 
  
    |  |             nth_client(sz)->abortAfterHeaders();
 | 
  
    |  |         }
 | 
  
    |  |         if(!m_is_started)
 | 
  
    |  |         {
 | 
  
    |  |             BOOST_REQUIRE(m_server.start());
 | 
  
    |  |             m_is_started = true;
 | 
  
    |  |         }
 | 
  
    |  |         BOOST_REQUIRE(nth_client(sz)->get("http://" + m_server.address() + endpoint));
 | 
  
    |  |         if(setup & eWaitDone)
 | 
  
    |  |         {
 | 
  
    |  |             nth_client(sz)->waitDone();
 | 
  
    |  |         }
 | 
  
    |  |         return sz;
 | 
  
    |  |     }
 | 
  
    |  | };
 | 
  
    |  | #endif /* ndef FUMA_INTEGRATION_TEST_HELPER_HPP */
 |