#pragma once #include #include #include #include #include #include "core_response_generator.hpp" #include "rublon/sign.hpp" namespace { rublon::Configuration conf{rublon::Configuration{// {"320BAB778C4D4262B54CD243CDEFFAFD"}, {"39e8d771d83a2ed3cc728811911c25"}, {"https://staging-core.rublon.net"}, 1, true, true, false, rublon::FailMode::safe}}; rublon::Configuration confBypass{rublon::Configuration{// {"320BAB778C4D4262B54CD243CDEFFAFD"}, {"39e8d771d83a2ed3cc728811911c25"}, {"https://staging-core.rublon.net"}, 1, true, true, false, rublon::FailMode::safe}}; inline std::string randomTID() { static const char alphanum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::string tmp_s; const auto len = 32; tmp_s.resize(len); std::for_each(tmp_s.begin(), tmp_s.end(), [](auto & chr) { chr = alphanum[rand() % (sizeof(alphanum) - 1)]; }); return tmp_s; } std::string join_methods(std::set< std::string > _methods) { std::string ret; for(const auto & m : _methods) ret += "\"" + m + "\","; ret.pop_back(); return ret; } } // namespace struct ResponseMockOptions { bool generateBrokenSigneture{false}; bool generateBrokenBody{false}; bool generateBrokenCode{false}; std::string coreException{}; std::set< std::string > methods{}; std::optional< rublon::Error > error; }; class InitCoreResponse { static constexpr const char * result_ok_template = R"json({"status":"OK","result":{"tid":"%s","status":"%s","companyName":"%s","applicationName":"%s","methods":[%s]}})json"; static constexpr const char * result_broken_template = // R"json({"some":"random","json":"notrublon"})json"; public: static std::string generate(const ResponseMockOptions & options) { if(options.generateBrokenBody) { return result_broken_template; } std::array< char, 2048 > _buf; auto tid = randomTID(); auto status = "pending"; std::string companyName{"rublon"}; std::string applicationName{"test_app"}; std::set< std::string > methods{"email", "totp", "qrcode", "push"}; if(options.methods.size()) methods = options.methods; io::sprintf(_buf.data(), result_ok_template, tid, status, companyName, applicationName, join_methods(methods)); return _buf.data(); } }; class MethodSelectCoreResponse { static constexpr const char * result_ok_template = R"json({"status":"OK","result":{"tid":"%s","status":"%s","companyName":"%s","applicationName":"%s","methods":[%s]}})json"; static constexpr const char * result_broken_template = // R"json({"some":"random","json":"notrublon"})json"; public: static std::string generate(const ResponseMockOptions & options) { if(options.generateBrokenBody) { return result_broken_template; } std::array< char, 2048 > _buf; auto tid = randomTID(); auto status = "pending"; std::string companyName{"rublon"}; std::string applicationName{"test_app"}; std::set< std::string > methods{"email", "totp", "qrcode", "push"}; io::sprintf(_buf.data(), result_ok_template, tid, status, companyName, applicationName, join_methods(methods)); return _buf.data(); } }; class CodeVerificationResponse { static constexpr const char * wrongPasscode = R"json({"status":"OK","result":{"error":"Hmm, that's not the right code. Try again."}})json"; static constexpr const char * goodPasscode = R"json({"status":"OK","result":true})json"; public: static std::string generate(const ResponseMockOptions & options) { return options.generateBrokenCode ? wrongPasscode : goodPasscode; } }; class AuthenticationOkResponse { static constexpr const char * authOk = R"json({"status":"OK","result":true})json"; public: static std::string generate(const ResponseMockOptions &) { return authOk; } }; template < typename Generator > class ResponseBase { public: Generator & initMessage() { _coreGenerator = InitCoreResponse{}; return static_cast< Generator & >(*this); } Generator & selectMethodMessage() { _coreGenerator = MethodSelectCoreResponse{}; return static_cast< Generator & >(*this); } Generator & codeConfirmationMessage() { _coreGenerator = CodeVerificationResponse{}; return static_cast< Generator & >(*this); } Generator & authenticationSuccessfullMessage() { _coreGenerator = AuthenticationOkResponse{}; return static_cast< Generator & >(*this); } Generator & withBrokenBody() { options.generateBrokenBody = true; return static_cast< Generator & >(*this); } Generator & withCoreException() { options.coreException = "some exception"; return static_cast< Generator & >(*this); } Generator & withBrokenSignature() { options.generateBrokenSigneture = true; return static_cast< Generator & >(*this); } Generator & withWrongCodeResponse() { options.generateBrokenCode = true; return static_cast< Generator & >(*this); } Generator & withTimeoutError() { options.error = rublon::Error{rublon::ConnectionError{}}; return static_cast< Generator & >(*this); } Generator & withServiceUnavailableError() { options.error = rublon::Error{rublon::ConnectionError{rublon::ConnectionError::HttpError, 405}}; return static_cast< Generator & >(*this); } template < typename... T > Generator & withMethods(T... methods) { (options.methods.insert(methods), ...); return static_cast< Generator & >(*this); } Generator & withConnectionError() { // options.error = rublon::Error{rublon::CoreHandlerError{rublon::CoreHandlerError::ConnectionError}}; return static_cast< Generator & >(*this); } Generator & withBasSignature() { options.error = rublon::Error{rublon::CoreHandlerError{rublon::CoreHandlerError::BadSigature}}; return static_cast< Generator & >(*this); } operator auto() { return not options.error.has_value() ? static_cast< Generator * >(this)->generate() : tl::unexpected{error()}; } rublon::Error error() { return rublon::Error{options.error.value()}; } std::variant< InitCoreResponse, MethodSelectCoreResponse, CodeVerificationResponse, AuthenticationOkResponse > _coreGenerator; ResponseMockOptions options; }; class RawCoreResponse : public ResponseBase< RawCoreResponse > { public: tl::expected< rublon::Document, rublon::Error > generate() { static rublon::RapidJSONPMRAlloc alloc; auto jsonString = std::visit([&](const auto generator) { return generator.generate(options); }, _coreGenerator); rublon::Document doc{&alloc}; doc.Parse(jsonString.c_str()); return doc; } }; class RawHttpResponse : public ResponseBase< RawHttpResponse > { rublon::Response & response; public: RawHttpResponse(rublon::Response & r) : ResponseBase< RawHttpResponse >(), response{r} { }; static auto signResponse(rublon::Response & res, ResponseMockOptions opts) { const auto & sign = opts.generateBrokenSigneture ? std::array< char, 65 >{} : rublon::signData(res.body, conf.secretKey.data()); res.headers["x-rublon-signature"] = sign.data(); } tl::expected< std::reference_wrapper< rublon::Response >, rublon::Error > generate() { response.body = std::visit([&](const auto generator) { return generator.generate(options); }, _coreGenerator); signResponse(response, options); return response; } }; class HttpHandlerMock { public: template < typename... Args > HttpHandlerMock(const Args &...) {} MOCK_METHOD(( tl::expected< std::reference_wrapper< rublon::Response >, rublon::Error > ), request, ( std::string_view, const rublon::Request & ), (const)); tl::expected< std::reference_wrapper< rublon::Response >, rublon::Error > request(std::string_view uri, const rublon::Request & req, rublon::Response &) const { return request(uri, req); } };