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 }