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 }