winget-cli

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

SFSClientTests.cpp (12168B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 #include "sfsclient/ApplicabilityDetails.h"
      5 #include "sfsclient/SFSClient.h"
      6 
      7 #include <catch2/catch_test_macros.hpp>
      8 
      9 #include <optional>
     10 
     11 #define TEST(...) TEST_CASE("[SFSClientTests] " __VA_ARGS__)
     12 #define TEST_SCENARIO(...) TEST_CASE("[SFSClientTests] Scenario: " __VA_ARGS__)
     13 
     14 using namespace SFS;
     15 
     16 namespace
     17 {
     18 std::unique_ptr<SFSClient> GetSFSClient(std::optional<std::string> instanceId = std::nullopt)
     19 {
     20     std::unique_ptr<SFSClient> sfsClient;
     21     ClientConfig options;
     22     options.accountId = "testAccountId";
     23     if (instanceId)
     24     {
     25         options.instanceId = *instanceId;
     26     }
     27     REQUIRE(SFSClient::Make(options, sfsClient) == Result::Success);
     28     REQUIRE(sfsClient != nullptr);
     29     return sfsClient;
     30 }
     31 
     32 void TestLoggingCallback(const LogData&)
     33 {
     34 }
     35 
     36 struct TestLoggingCallbackStruct
     37 {
     38     static void TestLoggingCallback(const LogData&)
     39     {
     40     }
     41 };
     42 } // namespace
     43 
     44 static void StaticTestLoggingCallback(const LogData&)
     45 {
     46 }
     47 
     48 TEST("Testing SFSClient::Make()")
     49 {
     50 #ifdef __GNUG__
     51 // For GCC, explicitly turning off "missing-field-initializers" warning as this block is testing the scenario
     52 // in which a user calls explicitly onto the API with field initializers for ClientConfig
     53 #pragma GCC diagnostic push
     54 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
     55 #endif
     56 
     57     const std::string accountId{"testAccountId"};
     58     const std::string instanceId{"testInstanceId"};
     59     const std::string nameSpace{"testNameSpace"};
     60 
     61     std::unique_ptr<SFSClient> sfsClient;
     62 
     63     SECTION("Make({accountId}, out)")
     64     {
     65         REQUIRE(SFSClient::Make({accountId}, sfsClient) == Result::Success);
     66         REQUIRE(sfsClient != nullptr);
     67 
     68         ClientConfig config{accountId};
     69         REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
     70         REQUIRE(sfsClient != nullptr);
     71     }
     72 
     73     SECTION("Make(accountId, instanceId, out)")
     74     {
     75         REQUIRE(SFSClient::Make({accountId, instanceId}, sfsClient) == Result::Success);
     76         REQUIRE(sfsClient != nullptr);
     77 
     78         ClientConfig config{accountId, instanceId};
     79         REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
     80         REQUIRE(sfsClient != nullptr);
     81     }
     82 
     83     SECTION("Make(accountId, instanceId, namespace, out)")
     84     {
     85         REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace}, sfsClient) == Result::Success);
     86         REQUIRE(sfsClient != nullptr);
     87 
     88         SECTION("Call make when the pointer is reset")
     89         {
     90             sfsClient.reset();
     91             REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace}, sfsClient) == Result::Success);
     92             REQUIRE(sfsClient != nullptr);
     93         }
     94 
     95         SECTION("Call make if the pointer is not reset, as Make() resets it")
     96         {
     97             REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace}, sfsClient) == Result::Success);
     98             REQUIRE(sfsClient != nullptr);
     99         }
    100 
    101         SECTION("We can also use a separate ClientConfig object")
    102         {
    103             ClientConfig config{accountId, instanceId, nameSpace};
    104             REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    105             REQUIRE(sfsClient != nullptr);
    106         }
    107 
    108         SECTION("We can also move a separate ClientConfig object")
    109         {
    110             ClientConfig config{accountId, instanceId, nameSpace};
    111             REQUIRE(SFSClient::Make(std::move(config), sfsClient) == Result::Success);
    112             REQUIRE(sfsClient != nullptr);
    113         }
    114     }
    115 
    116     SECTION("Make(accountId, std::nullopt, nameSpace, out)")
    117     {
    118         REQUIRE(SFSClient::Make({accountId, std::nullopt, nameSpace}, sfsClient) == Result::Success);
    119         REQUIRE(sfsClient != nullptr);
    120 
    121         ClientConfig config;
    122         config.accountId = accountId;
    123         config.nameSpace = nameSpace;
    124         REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    125         REQUIRE(sfsClient != nullptr);
    126     }
    127 
    128     SECTION("Make(accountId, instanceId, namespace, logCallbackFn, out) works")
    129     {
    130         SECTION("Using a lambda with {} initialization")
    131         {
    132             REQUIRE(SFSClient::Make({accountId, instanceId, nameSpace, [](const LogData&) {}}, sfsClient) ==
    133                     Result::Success);
    134             REQUIRE(sfsClient != nullptr);
    135         }
    136 
    137         SECTION("Using a lambda with a ClientConfig object")
    138         {
    139             ClientConfig config{accountId, instanceId, nameSpace, [](const LogData&) {}};
    140             REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    141             REQUIRE(sfsClient != nullptr);
    142         }
    143 
    144         SECTION("Using a nullptr with a ClientConfig object")
    145         {
    146             ClientConfig config{accountId, instanceId, nameSpace, nullptr};
    147             REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    148             REQUIRE(sfsClient != nullptr);
    149         }
    150 
    151         SECTION("Using a valid empty-namespace function within a ClientConfig object")
    152         {
    153             ClientConfig config{accountId, instanceId, nameSpace, TestLoggingCallback};
    154             REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    155             REQUIRE(sfsClient != nullptr);
    156         }
    157 
    158         SECTION("Using a valid static function within a ClientConfig object")
    159         {
    160             ClientConfig config{accountId, instanceId, nameSpace, StaticTestLoggingCallback};
    161             REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    162             REQUIRE(sfsClient != nullptr);
    163         }
    164 
    165         SECTION("Using a valid static member method within a ClientConfig object")
    166         {
    167             ClientConfig config{accountId, instanceId, nameSpace, &TestLoggingCallbackStruct::TestLoggingCallback};
    168             REQUIRE(SFSClient::Make(config, sfsClient) == Result::Success);
    169             REQUIRE(sfsClient != nullptr);
    170         }
    171 
    172         SECTION("Can also move a lambda")
    173         {
    174             ClientConfig config{accountId, instanceId, nameSpace, [](const LogData&) {}};
    175             REQUIRE(SFSClient::Make(std::move(config), sfsClient) == Result::Success);
    176             REQUIRE(sfsClient != nullptr);
    177         }
    178     }
    179 
    180     SECTION("AccountId cannot be empty")
    181     {
    182         REQUIRE(SFSClient::Make({}, sfsClient) == Result::InvalidArg);
    183         REQUIRE(sfsClient == nullptr);
    184 
    185         ClientConfig config;
    186         REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg);
    187         REQUIRE(sfsClient == nullptr);
    188 
    189         config = {};
    190         REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg);
    191         REQUIRE(sfsClient == nullptr);
    192 
    193         config.accountId = std::string();
    194         REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg);
    195         REQUIRE(sfsClient == nullptr);
    196 
    197         config.instanceId = instanceId;
    198         REQUIRE(SFSClient::Make(config, sfsClient) == Result::InvalidArg);
    199         REQUIRE(sfsClient == nullptr);
    200     }
    201 
    202 #ifdef __GNUG__
    203 // For "-Wmissing-field-initializers"
    204 #pragma GCC diagnostic pop
    205 #endif
    206 }
    207 
    208 namespace
    209 {
    210 void TestProductInRequestParams(const std::function<Result(const RequestParams&)>& apiCall,
    211                                 const std::function<void()>& checkContents)
    212 {
    213     RequestParams params;
    214     SECTION("Product must not be empty")
    215     {
    216         const std::string expectedErrorMsg = "product must not be empty";
    217 
    218         params.productRequests = {{"", {}}};
    219         auto result = apiCall(params);
    220         REQUIRE(result.GetCode() == Result::InvalidArg);
    221         REQUIRE(result.GetMsg() == expectedErrorMsg);
    222         checkContents();
    223 
    224         const TargetingAttributes attributes{{"attr1", "value"}};
    225         params.productRequests = {{"", attributes}};
    226         result = apiCall(params);
    227         REQUIRE(result.GetCode() == Result::InvalidArg);
    228         REQUIRE(result.GetMsg() == expectedErrorMsg);
    229         checkContents();
    230     }
    231 
    232     SECTION("Does not allow an empty request")
    233     {
    234         auto result = apiCall(params);
    235         REQUIRE(result.GetCode() == Result::InvalidArg);
    236         REQUIRE(result.GetMsg() == "productRequests cannot be empty");
    237         checkContents();
    238     }
    239 
    240     SECTION("Accepting multiple products is not implemented yet")
    241     {
    242         params.productRequests = {{"p1", {}}, {"p2", {}}};
    243         auto result = apiCall(params);
    244         REQUIRE(result.GetCode() == Result::NotImpl);
    245         REQUIRE(result.GetMsg() == "There cannot be more than 1 productRequest at the moment");
    246         checkContents();
    247     }
    248 
    249     SECTION("Fails if base cv is not correct")
    250     {
    251         params.productRequests = {{"p1", {}}};
    252         params.baseCV = "";
    253         auto result = apiCall(params);
    254         REQUIRE(result.GetCode() == Result::InvalidArg);
    255         REQUIRE(result.GetMsg() == "cv must not be empty");
    256         checkContents();
    257 
    258         params.baseCV = "cv";
    259         result = apiCall(params);
    260         REQUIRE(result.GetCode() == Result::InvalidArg);
    261         REQUIRE(result.GetMsg().find("baseCV is not a valid correlation vector:") == 0);
    262         checkContents();
    263     }
    264 
    265     SECTION("Fails if proxy is setup incorrectly")
    266     {
    267         params.productRequests = {{"p1", {}}};
    268         params.proxy = "bad://";
    269         auto result = apiCall(params);
    270         REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError);
    271         REQUIRE(result.GetMsg() == "Unsupported proxy syntax in 'bad://': No host part in the URL");
    272         checkContents();
    273 
    274         params.proxy = "bad://bad.com";
    275         result = apiCall(params);
    276         REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError);
    277         REQUIRE(result.GetMsg() == "Unsupported proxy scheme for 'bad://bad.com'");
    278         checkContents();
    279 
    280         params.proxy = ":";
    281         result = apiCall(params);
    282         REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError);
    283         REQUIRE(result.GetMsg() ==
    284                 "Unsupported proxy syntax in ':': Port number was not a decimal number between 0 and 65535");
    285         checkContents();
    286 
    287         params.proxy = "http://bad:bad";
    288         result = apiCall(params);
    289         REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError);
    290         REQUIRE(
    291             result.GetMsg() ==
    292             "Unsupported proxy syntax in 'http://bad:bad': Port number was not a decimal number between 0 and 65535");
    293         checkContents();
    294 
    295         params.proxy = "bad:bad";
    296         result = apiCall(params);
    297         REQUIRE(result.GetCode() == Result::ConnectionUnexpectedError);
    298         REQUIRE(result.GetMsg() ==
    299                 "Unsupported proxy syntax in 'bad:bad': Port number was not a decimal number between 0 and 65535");
    300         checkContents();
    301     }
    302 }
    303 } // namespace
    304 
    305 TEST("Testing SFSClient::GetLatestDownloadInfo()")
    306 {
    307     auto sfsClient = GetSFSClient();
    308     std::vector<Content> contents;
    309 
    310     TestProductInRequestParams(
    311         [&](const RequestParams& params) { return sfsClient->GetLatestDownloadInfo(params, contents); },
    312         [&contents] { REQUIRE(contents.empty()); });
    313 }
    314 
    315 TEST("Testing SFSClient::GetAppLatestDownloadInfo()")
    316 {
    317     SECTION("With storeapps instance")
    318     {
    319         auto sfsClient = GetSFSClient("storeapps");
    320         std::vector<AppContent> contents;
    321 
    322         TestProductInRequestParams(
    323             [&](const RequestParams& params) { return sfsClient->GetLatestAppDownloadInfo(params, contents); },
    324             [&contents] { REQUIRE(contents.empty()); });
    325     }
    326 
    327     SECTION("Fails if not storeapps instanceId")
    328     {
    329         auto sfsClient = GetSFSClient("testInstanceId");
    330         std::vector<AppContent> contents;
    331         RequestParams params;
    332         params.productRequests = {{"a", {}}};
    333         auto result = sfsClient->GetLatestAppDownloadInfo(params, contents);
    334         REQUIRE(result.GetCode() == Result::Unexpected);
    335         REQUIRE(result.GetMsg() == "At this moment only the \"storeapps\" instanceId can send app requests");
    336         REQUIRE(contents.empty());
    337     }
    338 }