winget-cli

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

SFSClientTests.cpp (17349B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 #include "../mock/MockWebServer.h"
      5 #include "../mock/ProxyServer.h"
      6 #include "../util/TestHelper.h"
      7 #include "TestOverride.h"
      8 #include "sfsclient/SFSClient.h"
      9 
     10 #include <catch2/catch_test_macros.hpp>
     11 
     12 #include <chrono>
     13 
     14 #define TEST(...) TEST_CASE("[Functional][SFSClientTests] " __VA_ARGS__)
     15 
     16 using namespace SFS;
     17 using namespace SFS::test;
     18 using namespace std::chrono;
     19 
     20 const std::string c_instanceId = "testInstanceId";
     21 const std::string c_namespace = "testNamespace";
     22 const std::string c_productName = "testProduct";
     23 const std::string c_version = "0.0.1";
     24 const std::string c_nextVersion = "0.0.2";
     25 
     26 namespace
     27 {
     28 void CheckContentId(const ContentId& contentId, const std::string& name, const std::string& version)
     29 {
     30     REQUIRE(contentId.GetNameSpace() == c_namespace);
     31     REQUIRE(contentId.GetName() == name);
     32     REQUIRE(contentId.GetVersion() == version);
     33 }
     34 
     35 void CheckFiles(const std::vector<File>& files)
     36 {
     37     REQUIRE(files.size() == 2);
     38     REQUIRE(files[0].GetFileId() == (c_productName + ".json"));
     39     REQUIRE(files[0].GetUrl() == ("http://localhost/1.json"));
     40     REQUIRE(files[1].GetFileId() == (c_productName + ".bin"));
     41     REQUIRE(files[1].GetUrl() == ("http://localhost/2.bin"));
     42 }
     43 
     44 void CheckAppFiles(const std::vector<AppFile>& files, const std::string& name)
     45 {
     46     REQUIRE(files.size() == 2);
     47     REQUIRE(files[0].GetFileId() == (name + ".json"));
     48     REQUIRE(files[0].GetUrl() == ("http://localhost/1.json"));
     49     REQUIRE_FALSE(files[0].GetFileMoniker().empty());
     50     REQUIRE_FALSE(files[0].GetApplicabilityDetails().GetArchitectures().empty());
     51     REQUIRE_FALSE(files[0].GetApplicabilityDetails().GetPlatformApplicabilityForPackage().empty());
     52     REQUIRE(files[1].GetFileId() == (name + ".bin"));
     53     REQUIRE(files[1].GetUrl() == ("http://localhost/2.bin"));
     54     REQUIRE_FALSE(files[1].GetFileMoniker().empty());
     55     REQUIRE_FALSE(files[1].GetApplicabilityDetails().GetArchitectures().empty());
     56     REQUIRE_FALSE(files[1].GetApplicabilityDetails().GetPlatformApplicabilityForPackage().empty());
     57 }
     58 
     59 void CheckMockContent(const Content& content, const std::string& version)
     60 {
     61     CheckContentId(content.GetContentId(), c_productName, version);
     62     CheckFiles(content.GetFiles());
     63 }
     64 
     65 void CheckMockAppContent(const AppContent& content,
     66                          const std::string& version,
     67                          const std::vector<MockPrerequisite> mockPrereqs)
     68 {
     69     CheckContentId(content.GetContentId(), c_productName, version);
     70     REQUIRE_FALSE(content.GetUpdateId().empty());
     71     CheckAppFiles(content.GetFiles(), c_productName);
     72 
     73     REQUIRE(content.GetPrerequisites().size() == mockPrereqs.size());
     74     for (size_t i = 0; i < mockPrereqs.size(); ++i)
     75     {
     76         const auto& prereq = content.GetPrerequisites()[i];
     77         const auto& mockPrereq = mockPrereqs[i];
     78         CheckContentId(prereq.GetContentId(), mockPrereq.name, mockPrereq.version);
     79 
     80         CheckAppFiles(prereq.GetFiles(), mockPrereq.name);
     81     }
     82 }
     83 } // namespace
     84 
     85 TEST("Testing SFSClient::GetLatestDownloadInfo()")
     86 {
     87     if (!AreTestOverridesAllowed())
     88     {
     89         return;
     90     }
     91 
     92     test::MockWebServer server;
     93     ScopedTestOverride override(TestOverride::BaseUrl, server.GetBaseUrl());
     94 
     95     std::unique_ptr<SFSClient> sfsClient;
     96     REQUIRE(SFSClient::Make({"testAccountId", c_instanceId, c_namespace, LogCallbackToTest}, sfsClient) ==
     97             Result::Success);
     98     REQUIRE(sfsClient != nullptr);
     99 
    100     server.RegisterProduct(c_productName, c_version);
    101 
    102     std::vector<Content> contents;
    103 
    104     SECTION("Single product request")
    105     {
    106         RequestParams params;
    107         params.baseCV = "aaaaaaaaaaaaaaaa.1";
    108         SECTION("No attributes")
    109         {
    110             params.productRequests = {{c_productName, {}}};
    111             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success);
    112             REQUIRE(contents.size() == 1);
    113             CheckMockContent(contents[0], c_version);
    114         }
    115 
    116         SECTION("No attributes + proxy")
    117         {
    118             test::ProxyServer proxy;
    119 
    120             params.productRequests = {{c_productName, {}}};
    121             params.proxy = proxy.GetBaseUrl();
    122             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success);
    123             REQUIRE(contents.size() == 1);
    124             CheckMockContent(contents[0], c_version);
    125 
    126             REQUIRE(proxy.Stop() == Result::Success);
    127         }
    128 
    129         SECTION("With attributes")
    130         {
    131             const TargetingAttributes attributes{{"attr1", "value"}};
    132             params.productRequests = {{c_productName, attributes}};
    133             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success);
    134             REQUIRE(contents.size() == 1);
    135             CheckMockContent(contents[0], c_version);
    136         }
    137 
    138         SECTION("Wrong product name")
    139         {
    140             params.productRequests = {{"badName", {}}};
    141             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpNotFound);
    142             REQUIRE(contents.empty());
    143 
    144             const TargetingAttributes attributes{{"attr1", "value"}};
    145             params.productRequests = {{"badName", attributes}};
    146             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpNotFound);
    147             REQUIRE(contents.empty());
    148         }
    149 
    150         SECTION("Adding new version")
    151         {
    152             server.RegisterProduct(c_productName, c_nextVersion);
    153 
    154             params.productRequests = {{c_productName, {}}};
    155             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success);
    156             REQUIRE(contents.size() == 1);
    157             CheckMockContent(contents[0], c_nextVersion);
    158         }
    159     }
    160 
    161     REQUIRE(server.Stop() == Result::Success);
    162 }
    163 
    164 TEST("Testing SFSClient::GetLatestAppDownloadInfo()")
    165 {
    166     if (!AreTestOverridesAllowed())
    167     {
    168         return;
    169     }
    170 
    171     test::MockWebServer server;
    172     ScopedTestOverride override(TestOverride::BaseUrl, server.GetBaseUrl());
    173 
    174     const std::string prereq1 = "prereq1";
    175     const std::string prereq1Version = "1.0";
    176     const std::string prereq2 = "prereq2";
    177     const std::string prereq2Version = "2.0";
    178 
    179     std::unique_ptr<SFSClient> sfsClient;
    180     REQUIRE(SFSClient::Make({"testAccountId", "storeapps", c_namespace, LogCallbackToTest}, sfsClient) ==
    181             Result::Success);
    182     REQUIRE(sfsClient != nullptr);
    183 
    184     SECTION("App products")
    185     {
    186         auto CheckApp = [&](const std::vector<MockPrerequisite>& mockPrereqs) {
    187             server.RegisterAppProduct(c_productName, c_version, mockPrereqs);
    188 
    189             std::vector<AppContent> contents;
    190 
    191             RequestParams params;
    192             params.baseCV = "aaaaaaaaaaaaaaaa.1";
    193             SECTION("No attributes")
    194             {
    195                 params.productRequests = {{c_productName, {}}};
    196                 REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::Success);
    197                 REQUIRE(contents.size() == 1);
    198                 CheckMockAppContent(contents[0], c_version, mockPrereqs);
    199             }
    200 
    201             SECTION("With attributes")
    202             {
    203                 const TargetingAttributes attributes{{"attr1", "value"}};
    204                 params.productRequests = {{c_productName, attributes}};
    205                 REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::Success);
    206                 REQUIRE(contents.size() == 1);
    207                 CheckMockAppContent(contents[0], c_version, mockPrereqs);
    208             }
    209 
    210             SECTION("Wrong product name")
    211             {
    212                 params.productRequests = {{"badName", {}}};
    213                 REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::HttpNotFound);
    214                 REQUIRE(contents.empty());
    215 
    216                 const TargetingAttributes attributes{{"attr1", "value"}};
    217                 params.productRequests = {{"badName", attributes}};
    218                 REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::HttpNotFound);
    219                 REQUIRE(contents.empty());
    220             }
    221 
    222             SECTION("Adding new version")
    223             {
    224                 server.RegisterAppProduct(c_productName, c_nextVersion, mockPrereqs);
    225 
    226                 params.productRequests = {{c_productName, {}}};
    227                 REQUIRE(sfsClient->GetLatestAppDownloadInfo(params, contents) == Result::Success);
    228                 REQUIRE(contents.size() == 1);
    229                 CheckMockAppContent(contents[0], c_nextVersion, mockPrereqs);
    230             }
    231         };
    232 
    233         SECTION("No prerequisites")
    234         {
    235             CheckApp({});
    236         }
    237 
    238         SECTION("1 prereq")
    239         {
    240             CheckApp({{prereq1, prereq1Version}});
    241         }
    242 
    243         SECTION("2 prereqs")
    244         {
    245             CheckApp({{prereq1, prereq1Version}, {prereq2, prereq2Version}});
    246         }
    247     }
    248 
    249     SECTION("Non-app products")
    250     {
    251         server.RegisterProduct(c_productName, c_version);
    252 
    253         std::vector<AppContent> contents;
    254 
    255         RequestParams params;
    256         params.productRequests = {{c_productName, {}}};
    257         auto result = sfsClient->GetLatestAppDownloadInfo(params, contents);
    258         REQUIRE(result.GetCode() == Result::ServiceUnexpectedContentType);
    259         REQUIRE(
    260             result.GetMsg() ==
    261             R"(The service returned entity "testProduct" with content type [Generic] while the expected type was [App])");
    262         REQUIRE(contents.empty());
    263     }
    264 
    265     REQUIRE(server.Stop() == Result::Success);
    266 }
    267 
    268 TEST("Testing SFSClient retry behavior")
    269 {
    270     if (!AreTestOverridesAllowed())
    271     {
    272         INFO("Skipping. Test overrides not enabled");
    273         return;
    274     }
    275 
    276     MockWebServer server;
    277     ScopedTestOverride urlOverride(TestOverride::BaseUrl, server.GetBaseUrl());
    278 
    279     server.RegisterProduct(c_productName, c_version);
    280     RequestParams params;
    281     params.productRequests = {{c_productName, {}}};
    282     std::vector<Content> contents;
    283 
    284     std::unique_ptr<SFSClient> sfsClient;
    285     ClientConfig clientConfig{"testAccountId", c_instanceId, c_namespace, LogCallbackToTest};
    286 
    287     SECTION("Test exponential backoff")
    288     {
    289         INFO("Sets the retry delay to 50ms to speed up the test");
    290         ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 50);
    291 
    292         REQUIRE(SFSClient::Make(clientConfig, sfsClient));
    293         REQUIRE(sfsClient != nullptr);
    294 
    295         const int retriableError = 503; // ServerBusy
    296 
    297         auto RunTimedGet = [&](bool success = true) -> long long {
    298             auto begin = steady_clock::now();
    299             if (success)
    300             {
    301                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents));
    302                 REQUIRE(contents.size() == 1);
    303             }
    304             else
    305             {
    306                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable);
    307                 REQUIRE(contents.empty());
    308             }
    309             auto end = steady_clock::now();
    310             return duration_cast<milliseconds>(end - begin).count();
    311         };
    312 
    313         long long allowedTimeDeviation = 200LL;
    314         std::queue<HttpCode> forcedHttpErrors({retriableError});
    315         SECTION("Should take at least 50ms with a single retriable error")
    316         {
    317             server.SetForcedHttpErrors(forcedHttpErrors);
    318             const auto time = RunTimedGet();
    319             REQUIRE(time >= 50LL);
    320             REQUIRE(time < 50LL + allowedTimeDeviation);
    321         }
    322 
    323         forcedHttpErrors.push(retriableError);
    324         SECTION("Should take at least 150ms (50ms + 2*50ms) with two retriable errors")
    325         {
    326             server.SetForcedHttpErrors(forcedHttpErrors);
    327             const auto time = RunTimedGet();
    328             REQUIRE(time >= 150LL);
    329             REQUIRE(time < 150LL + allowedTimeDeviation);
    330         }
    331 
    332         forcedHttpErrors.push(retriableError);
    333         SECTION("Should take at least 300ms (50ms + 2*50ms + 3*50ms) with three retriable errors")
    334         {
    335             server.SetForcedHttpErrors(forcedHttpErrors);
    336             const auto time = RunTimedGet();
    337             REQUIRE(time >= 300LL);
    338             REQUIRE(time < 300LL + allowedTimeDeviation);
    339         }
    340 
    341         forcedHttpErrors.push(retriableError);
    342         SECTION("Should take at least 300ms (50ms + 2*50ms + 3*50ms) with four retriable errors, but fail")
    343         {
    344             server.SetForcedHttpErrors(forcedHttpErrors);
    345             const auto time = RunTimedGet(false /*success*/);
    346             REQUIRE(time >= 300LL);
    347             REQUIRE(time < 300LL + allowedTimeDeviation);
    348         }
    349     }
    350 
    351     SECTION("Test retriable errors with Retry-After headers")
    352     {
    353         INFO("Sets the retry delay to 200ms to speed up the test");
    354         ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 200);
    355 
    356         REQUIRE(SFSClient::Make(clientConfig, sfsClient));
    357         REQUIRE(sfsClient != nullptr);
    358 
    359         const int retriableError = 503;  // ServerBusy
    360         const int retriableError2 = 502; // BadGateway
    361 
    362         auto RunTimedGet = [&]() -> long long {
    363             auto begin = steady_clock::now();
    364             REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents));
    365             REQUIRE(contents.size() == 1);
    366             auto end = steady_clock::now();
    367             return duration_cast<milliseconds>(end - begin).count();
    368         };
    369 
    370         std::unordered_map<HttpCode, HeaderMap> headersByCode;
    371         headersByCode[retriableError] = {{"Retry-After", "1"}}; // 1s delay
    372         server.SetResponseHeaders(headersByCode);
    373 
    374         long long allowedTimeDeviation = 200LL;
    375         std::queue<HttpCode> forcedHttpErrors({retriableError});
    376         SECTION("Should take at least 1000ms with a single retriable error with 1s in Retry-After")
    377         {
    378             server.SetForcedHttpErrors(forcedHttpErrors);
    379             const auto time = RunTimedGet();
    380             REQUIRE(time >= 1000LL);
    381             REQUIRE(time < 1000LL + allowedTimeDeviation);
    382         }
    383 
    384         SECTION(
    385             "Should take at least 1000ms + 200ms with a retriable error with 1s in Retry-After and one with 200ms*2 as default value")
    386         {
    387             forcedHttpErrors.push(retriableError2);
    388             server.SetForcedHttpErrors(forcedHttpErrors);
    389             const auto time = RunTimedGet();
    390             REQUIRE(time >= 1400LL);
    391             REQUIRE(time < 1400LL + allowedTimeDeviation);
    392         }
    393     }
    394 
    395     SECTION("Test maxRetries")
    396     {
    397         INFO("Sets the retry delay to 1ms to speed up the test");
    398         ScopedTestOverride override(TestOverride::BaseRetryDelayMs, 1);
    399 
    400         const int retriableError = 503; // ServerBusy
    401 
    402         SECTION("With default retries")
    403         {
    404             REQUIRE(SFSClient::Make(clientConfig, sfsClient));
    405             REQUIRE(sfsClient != nullptr);
    406 
    407             SECTION("Should pass with 3 errors")
    408             {
    409                 server.SetForcedHttpErrors(std::queue<HttpCode>({retriableError, retriableError, retriableError}));
    410                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents));
    411             }
    412 
    413             SECTION("Should fail with 4 errors")
    414             {
    415                 server.SetForcedHttpErrors(
    416                     std::queue<HttpCode>({retriableError, retriableError, retriableError, retriableError}));
    417                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable);
    418             }
    419         }
    420 
    421         SECTION("Forcing retries to 3")
    422         {
    423             params.retryOnError = true;
    424             REQUIRE(SFSClient::Make(clientConfig, sfsClient));
    425             REQUIRE(sfsClient != nullptr);
    426 
    427             SECTION("Should pass with 3 errors")
    428             {
    429                 server.SetForcedHttpErrors(std::queue<HttpCode>({retriableError, retriableError, retriableError}));
    430                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents));
    431             }
    432 
    433             SECTION("Should fail with 4 errors")
    434             {
    435                 server.SetForcedHttpErrors(
    436                     std::queue<HttpCode>({retriableError, retriableError, retriableError, retriableError}));
    437                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable);
    438             }
    439         }
    440 
    441         SECTION("Reducing retries to 0")
    442         {
    443             params.retryOnError = false;
    444             REQUIRE(SFSClient::Make(clientConfig, sfsClient));
    445             REQUIRE(sfsClient != nullptr);
    446 
    447             SECTION("Should pass with no errors")
    448             {
    449                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents));
    450             }
    451 
    452             SECTION("Should fail with 1 error")
    453             {
    454                 server.SetForcedHttpErrors(std::queue<HttpCode>({retriableError}));
    455                 REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::HttpServiceNotAvailable);
    456             }
    457         }
    458     }
    459 
    460     REQUIRE(server.Stop() == Result::Success);
    461 }