winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

SFSClientImplTests.cpp (16100B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 #include "../../util/SFSExceptionMatcher.h"
      5 #include "../../util/TestHelper.h"
      6 #include "SFSClientImpl.h"
      7 #include "TestOverride.h"
      8 #include "connection/Connection.h"
      9 #include "connection/ConnectionManager.h"
     10 #include "connection/CurlConnection.h"
     11 #include "connection/CurlConnectionManager.h"
     12 #include "connection/mock/MockConnectionManager.h"
     13 
     14 #include <catch2/catch_test_macros.hpp>
     15 #include <nlohmann/json.hpp>
     16 
     17 #define TEST(...) TEST_CASE("[SFSClientImplTests] " __VA_ARGS__)
     18 
     19 using namespace SFS;
     20 using namespace SFS::details;
     21 using namespace SFS::test;
     22 using json = nlohmann::json;
     23 
     24 namespace
     25 {
     26 class MockCurlConnection : public CurlConnection
     27 {
     28   public:
     29     MockCurlConnection(const ReportingHandler& handler,
     30                        Result::Code& responseCode,
     31                        std::string& getResponse,
     32                        std::string& postResponse,
     33                        bool& expectEmptyPostBody)
     34         : CurlConnection({}, handler)
     35         , m_responseCode(responseCode)
     36         , m_getResponse(getResponse)
     37         , m_postResponse(postResponse)
     38         , m_expectEmptyPostBody(expectEmptyPostBody)
     39     {
     40     }
     41 
     42     std::string Get(const std::string&) override
     43     {
     44         if (m_responseCode == Result::Success)
     45         {
     46             INFO("MockCurlConnection::Get() called, response: " << m_getResponse);
     47             return m_getResponse;
     48         }
     49         throw SFSException(m_responseCode);
     50     }
     51 
     52     std::string Post(const std::string&, const std::string& data) override
     53     {
     54         if (m_responseCode == Result::Success)
     55         {
     56             INFO("MockCurlConnection::Post() called, response: " << m_postResponse);
     57             if (m_expectEmptyPostBody)
     58             {
     59                 REQUIRE(data.empty());
     60             }
     61             else
     62             {
     63                 REQUIRE(!data.empty());
     64             }
     65             return m_postResponse;
     66         }
     67         throw SFSException(m_responseCode);
     68     }
     69 
     70   private:
     71     Result::Code& m_responseCode;
     72     std::string& m_getResponse;
     73     std::string& m_postResponse;
     74     bool& m_expectEmptyPostBody;
     75 };
     76 
     77 void CheckProduct(const VersionEntity& entity, std::string_view ns, std::string_view name, std::string_view version)
     78 {
     79     REQUIRE(entity.GetContentType() == ContentType::Generic);
     80     REQUIRE(entity.contentId.nameSpace == ns);
     81     REQUIRE(entity.contentId.name == name);
     82     REQUIRE(entity.contentId.version == version);
     83 }
     84 
     85 void CheckDownloadInfo(const FileEntities& files, const std::string& name)
     86 {
     87     REQUIRE(files.size() == 2);
     88     REQUIRE(files[0]->fileId == (name + ".json"));
     89     REQUIRE(files[0]->url == ("http://localhost/1.json"));
     90     REQUIRE(files[1]->fileId == (name + ".bin"));
     91     REQUIRE(files[1]->url == ("http://localhost/2.bin"));
     92 }
     93 } // namespace
     94 
     95 TEST("Testing class SFSClientImpl()")
     96 {
     97     const std::string ns = "testNameSpace";
     98     SFSClientImpl<CurlConnectionManager> sfsClient({"testAccountId", "testInstanceId", ns, LogCallbackToTest});
     99 
    100     Result::Code responseCode = Result::Success;
    101     std::string getResponse;
    102     std::string postResponse;
    103     bool expectEmptyPostBody = true;
    104     std::unique_ptr<Connection> connection = std::make_unique<MockCurlConnection>(sfsClient.GetReportingHandler(),
    105                                                                                   responseCode,
    106                                                                                   getResponse,
    107                                                                                   postResponse,
    108                                                                                   expectEmptyPostBody);
    109 
    110     const std::string productName = "productName";
    111     const std::string expectedVersion = "0.0.0.2";
    112 
    113     SECTION("Testing SFSClientImpl::GetLatestVersion()")
    114     {
    115         expectEmptyPostBody = false;
    116         std::unique_ptr<VersionEntity> entity;
    117 
    118         SECTION("Expected response")
    119         {
    120             const json latestVersionResponse = {
    121                 {"ContentId", {{"Namespace", ns}, {"Name", productName}, {"Version", expectedVersion}}}};
    122             postResponse = latestVersionResponse.dump();
    123             SECTION("No attributes")
    124             {
    125                 REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({productName, {}}, *connection));
    126                 REQUIRE(entity);
    127                 CheckProduct(*entity, ns, productName, expectedVersion);
    128             }
    129 
    130             SECTION("With attributes")
    131             {
    132                 const TargetingAttributes attributes{{"attr1", "value"}};
    133                 REQUIRE_NOTHROW(entity = sfsClient.GetLatestVersion({productName, attributes}, *connection));
    134                 REQUIRE(entity);
    135                 CheckProduct(*entity, ns, productName, expectedVersion);
    136             }
    137 
    138             SECTION("Failing")
    139             {
    140                 responseCode = Result::HttpNotFound;
    141                 REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", {}}, *connection), HttpNotFound);
    142                 REQUIRE(!entity);
    143 
    144                 const TargetingAttributes attributes{{"attr1", "value"}};
    145                 REQUIRE_THROWS_CODE(entity = sfsClient.GetLatestVersion({"badName", attributes}, *connection),
    146                                     HttpNotFound);
    147                 REQUIRE(!entity);
    148             }
    149         }
    150 
    151         SECTION("Unexpected response")
    152         {
    153             SECTION("Wrong ns")
    154             {
    155                 const json latestVersionResponse = {
    156                     {"ContentId", {{"Namespace", "wrong"}, {"Name", productName}, {"Version", expectedVersion}}}};
    157                 postResponse = latestVersionResponse.dump();
    158             }
    159 
    160             SECTION("Wrong name")
    161             {
    162                 const json latestVersionResponse = {
    163                     {"ContentId", {{"Namespace", ns}, {"Name", "wrong"}, {"Version", expectedVersion}}}};
    164                 postResponse = latestVersionResponse.dump();
    165             }
    166 
    167             REQUIRE_THROWS_CODE_MSG(entity = sfsClient.GetLatestVersion({productName, {}}, *connection),
    168                                     ServiceInvalidResponse,
    169                                     "Response does not match the requested product");
    170             REQUIRE(!entity);
    171         }
    172     }
    173 
    174     SECTION("Testing SFSClientImpl::GetLatestVersionBatch()")
    175     {
    176         expectEmptyPostBody = false;
    177         json latestVersionResponse = json::array();
    178         latestVersionResponse.push_back(
    179             {{"ContentId", {{"Namespace", ns}, {"Name", productName}, {"Version", expectedVersion}}}});
    180         postResponse = latestVersionResponse.dump();
    181         VersionEntities entities;
    182         SECTION("No attributes")
    183         {
    184             REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{productName, {}}}, *connection));
    185             REQUIRE(!entities.empty());
    186             CheckProduct(*entities[0], ns, productName, expectedVersion);
    187         }
    188 
    189         SECTION("With attributes")
    190         {
    191             const TargetingAttributes attributes{{"attr1", "value"}};
    192             REQUIRE_NOTHROW(entities = sfsClient.GetLatestVersionBatch({{productName, attributes}}, *connection));
    193             REQUIRE(!entities.empty());
    194             CheckProduct(*entities[0], ns, productName, expectedVersion);
    195         }
    196 
    197         SECTION("Failing")
    198         {
    199             responseCode = Result::HttpNotFound;
    200             REQUIRE_THROWS_CODE(entities = sfsClient.GetLatestVersionBatch({{"badName", {}}}, *connection),
    201                                 HttpNotFound);
    202 
    203             const TargetingAttributes attributes{{"attr1", "value"}};
    204             REQUIRE_THROWS_CODE(entities = sfsClient.GetLatestVersionBatch({{"badName", attributes}}, *connection),
    205                                 HttpNotFound);
    206         }
    207     }
    208 
    209     SECTION("Testing SFSClientImpl::GetSpecificVersion()")
    210     {
    211         json specificVersionResponse;
    212         specificVersionResponse["ContentId"] = {{"Namespace", ns}, {"Name", productName}, {"Version", expectedVersion}};
    213         specificVersionResponse["Files"] = json::array({productName + ".json", productName + ".bin"});
    214         getResponse = specificVersionResponse.dump();
    215         std::unique_ptr<VersionEntity> entity;
    216         SECTION("Getting version")
    217         {
    218             REQUIRE_NOTHROW(entity = sfsClient.GetSpecificVersion(productName, expectedVersion, *connection));
    219             REQUIRE(entity);
    220             CheckProduct(*entity, ns, productName, expectedVersion);
    221         }
    222 
    223         SECTION("Failing")
    224         {
    225             responseCode = Result::HttpNotFound;
    226             REQUIRE_THROWS_CODE(entity = sfsClient.GetSpecificVersion(productName, expectedVersion, *connection),
    227                                 HttpNotFound);
    228         }
    229     }
    230 
    231     SECTION("Testing SFSClientImpl::GetDownloadInfo()")
    232     {
    233         expectEmptyPostBody = true;
    234         json downloadInfoResponse;
    235         downloadInfoResponse = json::array();
    236         downloadInfoResponse.push_back({{"Url", "http://localhost/1.json"},
    237                                         {"FileId", productName + ".json"},
    238                                         {"SizeInBytes", 100},
    239                                         {"Hashes", {{"Sha1", "123"}, {"Sha256", "456"}}}});
    240         downloadInfoResponse[0]["DeliveryOptimization"] = {{"CatalogId", "789"}};
    241         downloadInfoResponse[0]["DeliveryOptimization"]["Properties"] = {
    242             {"IntegrityCheckInfo", {{"PiecesHashFileUrl", "http://localhost/1.json"}, {"HashOfHashes", "abc"}}}};
    243 
    244         downloadInfoResponse.push_back({{"Url", "http://localhost/2.bin"},
    245                                         {"FileId", productName + ".bin"},
    246                                         {"SizeInBytes", 200},
    247                                         {"Hashes", {{"Sha1", "421"}, {"Sha256", "132"}}}});
    248         downloadInfoResponse[1]["DeliveryOptimization"] = downloadInfoResponse[0]["DeliveryOptimization"];
    249         postResponse = downloadInfoResponse.dump();
    250 
    251         FileEntities files;
    252         SECTION("Getting version")
    253         {
    254             REQUIRE_NOTHROW(files = sfsClient.GetDownloadInfo(productName, expectedVersion, *connection));
    255             REQUIRE(!files.empty());
    256             CheckDownloadInfo(files, productName);
    257         }
    258 
    259         SECTION("Failing")
    260         {
    261             responseCode = Result::HttpNotFound;
    262             REQUIRE_THROWS_CODE(files = sfsClient.GetDownloadInfo(productName, expectedVersion, *connection),
    263                                 HttpNotFound);
    264             REQUIRE(files.empty());
    265         }
    266     }
    267 }
    268 
    269 TEST("Testing ClientConfig validation")
    270 {
    271     SECTION("accountId must not be empty")
    272     {
    273         const std::string expectedErrorMsg = "ClientConfig::accountId must not be empty";
    274         for (size_t i = 0; i <= 10; ++i)
    275         {
    276             ClientConfig config;
    277             config.accountId = std::string(i, 'a');
    278             if (i >= 1)
    279             {
    280                 REQUIRE_NOTHROW(SFSClientImpl<CurlConnectionManager>(std::move(config)));
    281             }
    282             else
    283             {
    284                 REQUIRE_THROWS_CODE_MSG(SFSClientImpl<CurlConnectionManager>(std::move(config)),
    285                                         InvalidArg,
    286                                         expectedErrorMsg);
    287             }
    288         }
    289     }
    290 
    291     SECTION("instanceId must not be empty")
    292     {
    293         const std::string expectedErrorMsg = "ClientConfig::instanceId must not be empty";
    294         for (size_t i = 0; i <= 10; ++i)
    295         {
    296             ClientConfig config;
    297             config.accountId = "testAccountId";
    298             config.instanceId = std::string(i, 'a');
    299             if (i >= 1)
    300             {
    301                 REQUIRE_NOTHROW(SFSClientImpl<CurlConnectionManager>(std::move(config)));
    302             }
    303             else
    304             {
    305                 REQUIRE_THROWS_CODE_MSG(SFSClientImpl<CurlConnectionManager>(std::move(config)),
    306                                         InvalidArg,
    307                                         expectedErrorMsg);
    308             }
    309         }
    310     }
    311 
    312     SECTION("NameSpace must not be empty")
    313     {
    314         const std::string expectedErrorMsg = "ClientConfig::nameSpace must not be empty";
    315         for (size_t i = 0; i <= 10; ++i)
    316         {
    317             ClientConfig config;
    318             config.accountId = "testAccountId";
    319             config.nameSpace = std::string(i, 'a');
    320             if (i >= 1)
    321             {
    322                 REQUIRE_NOTHROW(SFSClientImpl<CurlConnectionManager>(std::move(config)));
    323             }
    324             else
    325             {
    326                 REQUIRE_THROWS_CODE_MSG(SFSClientImpl<CurlConnectionManager>(std::move(config)),
    327                                         InvalidArg,
    328                                         expectedErrorMsg);
    329             }
    330         }
    331     }
    332 }
    333 
    334 TEST("Testing SFSClientImpl::SetCustomBaseUrl()")
    335 {
    336     ClientConfig config;
    337     config.accountId = "testAccountId";
    338     SFSClientImpl<MockConnectionManager> sfsClient(std::move(config));
    339 
    340     REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/");
    341 
    342     sfsClient.SetCustomBaseUrl("customUrl");
    343     REQUIRE_THROWS_CODE_MSG(sfsClient.MakeUrlBuilder().GetUrl(),
    344                             ConnectionUrlSetupFailed,
    345                             "Curl URL error: Bad scheme");
    346 
    347     sfsClient.SetCustomBaseUrl("http://customUrl.com/");
    348     REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/");
    349 }
    350 
    351 TEST("Testing test override SFS_TEST_OVERRIDE_BASE_URL")
    352 {
    353     ClientConfig config;
    354     config.accountId = "testAccountId";
    355     SFSClientImpl<MockConnectionManager> sfsClient(std::move(config));
    356 
    357     REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/");
    358 
    359     {
    360         INFO("Can override the base url with the test key");
    361         ScopedTestOverride override(TestOverride::BaseUrl, "http://override.com");
    362         if (AreTestOverridesAllowed())
    363         {
    364             REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://override.com/");
    365         }
    366         else
    367         {
    368             REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/");
    369         }
    370     }
    371 
    372     if (AreTestOverridesAllowed())
    373     {
    374         INFO("Fails if the override is not a valid URL");
    375         ScopedTestOverride override(TestOverride::BaseUrl, "override");
    376         {
    377             REQUIRE_THROWS_CODE_MSG(sfsClient.MakeUrlBuilder().GetUrl(),
    378                                     ConnectionUrlSetupFailed,
    379                                     "Curl URL error: Bad scheme");
    380         }
    381     }
    382 
    383     INFO("Override is unset after ScopedEnv goes out of scope");
    384     REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "https://testAccountId.api.cdp.microsoft.com/");
    385 
    386     sfsClient.SetCustomBaseUrl("http://customUrl.com");
    387     REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/");
    388 
    389     {
    390         INFO("Can also override a custom base base url with the test key");
    391         ScopedTestOverride override(TestOverride::BaseUrl, "http://override.com");
    392         if (AreTestOverridesAllowed())
    393         {
    394             REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://override.com/");
    395         }
    396         else
    397         {
    398             REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/");
    399         }
    400     }
    401 
    402     REQUIRE(sfsClient.MakeUrlBuilder().GetUrl() == "http://customUrl.com/");
    403 }
    404 
    405 TEST("Testing passing a logging callback to constructor of SFSClientImpl")
    406 {
    407     SFSClientImpl<MockConnectionManager> sfsClient(
    408         {"testAccountId", "testInstanceId", "testNameSpace", [](const LogData&) {}});
    409     SFSClientImpl<MockConnectionManager> sfsClient2({"testAccountId", "testInstanceId", "testNameSpace", nullptr});
    410 }