|
| 1 | +using System; |
1 | 2 | using System.Net; |
2 | 3 | using System.Text.RegularExpressions; |
3 | 4 | using NetPace.Core.Clients.Ookla; |
| 5 | +using NetPace.Core.Clients.Testing; |
4 | 6 | using RichardSzalay.MockHttp; |
5 | 7 | using Shouldly; |
6 | 8 |
|
@@ -364,7 +366,8 @@ public async Task GetServerLatencyAsync_WithProgress_ShouldCancel_WhenTokenIsCan |
364 | 366 | // Then |
365 | 367 | exception.ShouldNotBeNull(); |
366 | 368 | exception.ShouldBeAssignableTo<OperationCanceledException>(); |
367 | | - cts.IsCancellationRequested.ShouldBeTrue(); |
| 369 | + cts.IsCancellationRequested.ShouldBeTrue(); |
| 370 | + progressReports.ShouldBeEmpty(); |
368 | 371 | } |
369 | 372 |
|
370 | 373 | [Fact] |
@@ -399,6 +402,67 @@ public async Task GetServerLatencyAsync_WithProgress_ShouldPropagateException_Wh |
399 | 402 | exception.Message.ShouldBe("Progress callback failed"); |
400 | 403 | } |
401 | 404 |
|
| 405 | + [Fact] |
| 406 | + public async Task GetServerLatencyAsync_WithInterval_ShouldRequestDelayBetweenIterations() |
| 407 | + { |
| 408 | + // Given |
| 409 | + using var mockHttp = new MockHttpMessageHandler(); |
| 410 | + mockHttp.When("http://testserver.com/latency.txt") |
| 411 | + .Respond("text/plain", "test=test"); |
| 412 | + |
| 413 | + var httpClient = mockHttp.ToHttpClient(); |
| 414 | + var delayStub = new DelayProviderStub(); |
| 415 | + var settings = new OoklaSpeedtestSettings |
| 416 | + { |
| 417 | + LatencyTest = new() |
| 418 | + { |
| 419 | + LatencyTestIterations = 3, |
| 420 | + LatencyTestIntervalMilliseconds = 50 // 50ms between iterations |
| 421 | + } |
| 422 | + }; |
| 423 | + |
| 424 | + var speedtest = new OoklaSpeedtest(settings, httpClient, delayStub); |
| 425 | + var server = new Server { Url = "http://testserver.com/", Sponsor = "Sponsor", Location = "Location" }; |
| 426 | + |
| 427 | + // When |
| 428 | + await speedtest.GetServerLatencyAsync(server); |
| 429 | + |
| 430 | + // Then |
| 431 | + // With 3 iterations, we expect 2 delays (between iterations, not before first) |
| 432 | + delayStub.DelayCallCount.ShouldBe(2); |
| 433 | + delayStub.RequestedDelays.ShouldAllBe(d => d == 50); |
| 434 | + } |
| 435 | + |
| 436 | + [Fact] |
| 437 | + public async Task GetServerLatencyAsync_WithZeroInterval_ShouldNotRequestDelay() |
| 438 | + { |
| 439 | + // Given |
| 440 | + using var mockHttp = new MockHttpMessageHandler(); |
| 441 | + mockHttp.When("http://testserver.com/latency.txt") |
| 442 | + .Respond("text/plain", "test=test"); |
| 443 | + |
| 444 | + var httpClient = mockHttp.ToHttpClient(); |
| 445 | + var delayStub = new DelayProviderStub(); |
| 446 | + var settings = new OoklaSpeedtestSettings |
| 447 | + { |
| 448 | + LatencyTest = new() |
| 449 | + { |
| 450 | + LatencyTestIterations = 3, |
| 451 | + LatencyTestIntervalMilliseconds = 0 // No delay |
| 452 | + } |
| 453 | + }; |
| 454 | + |
| 455 | + var speedtest = new OoklaSpeedtest(settings, httpClient, delayStub); |
| 456 | + var server = new Server { Url = "http://testserver.com/", Sponsor = "Sponsor", Location = "Location" }; |
| 457 | + |
| 458 | + // When |
| 459 | + await speedtest.GetServerLatencyAsync(server); |
| 460 | + |
| 461 | + // Then |
| 462 | + // With 0ms interval, no delays should be requested |
| 463 | + delayStub.DelayCallCount.ShouldBe(0); |
| 464 | + } |
| 465 | + |
402 | 466 | // --- GetFastestServerByLatencyAsync --- |
403 | 467 |
|
404 | 468 | [Fact] |
@@ -515,6 +579,187 @@ public async Task GetFastestServerByLatencyAsync_ShouldCancel_WhenTokenIsCancell |
515 | 579 | cts.IsCancellationRequested.ShouldBeTrue(); |
516 | 580 | } |
517 | 581 |
|
| 582 | + // --- GetFastestServerByLatencyAsync with Progress --- |
| 583 | + |
| 584 | + [Fact] |
| 585 | + public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldReportProgress_ForThreeServers() |
| 586 | + { |
| 587 | + // Given |
| 588 | + using var mockHttp = new MockHttpMessageHandler(); |
| 589 | + mockHttp.When("*/latency.txt") |
| 590 | + .Respond("text/plain", "test=test"); |
| 591 | + |
| 592 | + var httpClient = mockHttp.ToHttpClient(); |
| 593 | + var settings = new OoklaSpeedtestSettings |
| 594 | + { |
| 595 | + LatencyTest = new() |
| 596 | + { |
| 597 | + LatencyTestIterations = 1, |
| 598 | + LatencyTestIntervalMilliseconds = 0, |
| 599 | + DefaultHttpTimeoutMilliseconds = 500 |
| 600 | + } |
| 601 | + }; |
| 602 | + |
| 603 | + var speedtest = new OoklaSpeedtest(settings, httpClient); |
| 604 | + var servers = new[] { |
| 605 | + new Server { Url = "http://server1.com/", Sponsor = "Sponsor1", Location = "Location1" }, |
| 606 | + new Server { Url = "http://server2.com/", Sponsor = "Sponsor2", Location = "Location2" }, |
| 607 | + new Server { Url = "http://server3.com/", Sponsor = "Sponsor3", Location = "Location3" } |
| 608 | + }; |
| 609 | + var progressReports = new List<int>(); |
| 610 | + |
| 611 | + // When |
| 612 | + var result = await speedtest.GetFastestServerByLatencyAsync(servers, progress => progressReports.Add(progress.PercentageComplete)); |
| 613 | + |
| 614 | + // Then |
| 615 | + result.ShouldNotBeNull(); |
| 616 | + result.Server.ShouldBeOneOf(servers); |
| 617 | + progressReports.ShouldBe(new[] { 33, 66, 100 }); |
| 618 | + } |
| 619 | + |
| 620 | + [Fact] |
| 621 | + public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldReport100Percent_WithSingleServer() |
| 622 | + { |
| 623 | + // Given |
| 624 | + using var mockHttp = new MockHttpMessageHandler(); |
| 625 | + mockHttp.When("*/latency.txt") |
| 626 | + .Respond("text/plain", "test=test"); |
| 627 | + |
| 628 | + var httpClient = mockHttp.ToHttpClient(); |
| 629 | + var settings = new OoklaSpeedtestSettings |
| 630 | + { |
| 631 | + LatencyTest = new() |
| 632 | + { |
| 633 | + LatencyTestIterations = 1, |
| 634 | + LatencyTestIntervalMilliseconds = 0, |
| 635 | + DefaultHttpTimeoutMilliseconds = 500 |
| 636 | + } |
| 637 | + }; |
| 638 | + |
| 639 | + var speedtest = new OoklaSpeedtest(settings, httpClient); |
| 640 | + var server = new Server { Url = "http://server.com/", Sponsor = "Sponsor", Location = "Location" }; |
| 641 | + var servers = new[] { server }; |
| 642 | + var progressReports = new List<int>(); |
| 643 | + |
| 644 | + // When |
| 645 | + var result = await speedtest.GetFastestServerByLatencyAsync(servers, progress => progressReports.Add(progress.PercentageComplete)); |
| 646 | + |
| 647 | + // Then |
| 648 | + result.ShouldNotBeNull(); |
| 649 | + result.Server.ShouldBe(server); |
| 650 | + progressReports.ShouldBe(new[] { 100 }); |
| 651 | + } |
| 652 | + |
| 653 | + [Fact] |
| 654 | + public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldReportProgress_EvenWhenServersFail() |
| 655 | + { |
| 656 | + // Given |
| 657 | + using var mockHttp = new MockHttpMessageHandler(); |
| 658 | + |
| 659 | + // First two servers fail, third succeeds |
| 660 | + mockHttp.When("http://fail1.com/latency.txt") |
| 661 | + .Throw(new HttpRequestException("Server unreachable")); |
| 662 | + mockHttp.When("http://fail2.com/latency.txt") |
| 663 | + .Throw(new HttpRequestException("Server unreachable")); |
| 664 | + mockHttp.When("http://success.com/latency.txt") |
| 665 | + .Respond("text/plain", "test=test"); |
| 666 | + |
| 667 | + var httpClient = mockHttp.ToHttpClient(); |
| 668 | + var settings = new OoklaSpeedtestSettings |
| 669 | + { |
| 670 | + LatencyTest = new() |
| 671 | + { |
| 672 | + LatencyTestIterations = 1, |
| 673 | + LatencyTestIntervalMilliseconds = 0, |
| 674 | + DefaultHttpTimeoutMilliseconds = 100 |
| 675 | + } |
| 676 | + }; |
| 677 | + |
| 678 | + var speedtest = new OoklaSpeedtest(settings, httpClient); |
| 679 | + var failServer1 = new Server { Url = "http://fail1.com/", Sponsor = "Fail1", Location = "Location1" }; |
| 680 | + var failServer2 = new Server { Url = "http://fail2.com/", Sponsor = "Fail2", Location = "Location2" }; |
| 681 | + var successServer = new Server { Url = "http://success.com/", Sponsor = "Success", Location = "Location3" }; |
| 682 | + var servers = new[] { failServer1, failServer2, successServer }; |
| 683 | + var progressReports = new List<int>(); |
| 684 | + |
| 685 | + // When |
| 686 | + var result = await speedtest.GetFastestServerByLatencyAsync(servers, progress => progressReports.Add(progress.PercentageComplete)); |
| 687 | + |
| 688 | + // Then |
| 689 | + result.ShouldNotBeNull(); |
| 690 | + result.Server.ShouldBe(successServer); |
| 691 | + progressReports.ShouldBe(new[] { 33, 66, 100 }); |
| 692 | + } |
| 693 | + |
| 694 | + [Fact] |
| 695 | + public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldPropagateException_WhenProgressCallbackThrows() |
| 696 | + { |
| 697 | + // Given |
| 698 | + using var mockHttp = new MockHttpMessageHandler(); |
| 699 | + mockHttp.When("*/latency.txt") |
| 700 | + .Respond("text/plain", "test=test"); |
| 701 | + |
| 702 | + var httpClient = mockHttp.ToHttpClient(); |
| 703 | + var settings = new OoklaSpeedtestSettings |
| 704 | + { |
| 705 | + LatencyTest = new() |
| 706 | + { |
| 707 | + LatencyTestIterations = 1, |
| 708 | + LatencyTestIntervalMilliseconds = 0, |
| 709 | + DefaultHttpTimeoutMilliseconds = 500 |
| 710 | + } |
| 711 | + }; |
| 712 | + |
| 713 | + var speedtest = new OoklaSpeedtest(settings, httpClient); |
| 714 | + var server = new Server { Url = "http://server.com/", Sponsor = "Sponsor", Location = "Location" }; |
| 715 | + var servers = new[] { server }; |
| 716 | + |
| 717 | + // When |
| 718 | + var exception = await Record.ExceptionAsync(() => speedtest.GetFastestServerByLatencyAsync(servers, progress => |
| 719 | + { |
| 720 | + throw new InvalidOperationException("Progress callback failed"); |
| 721 | + })); |
| 722 | + |
| 723 | + // Then |
| 724 | + exception.ShouldNotBeNull(); |
| 725 | + exception.ShouldBeOfType<InvalidOperationException>(); |
| 726 | + exception.Message.ShouldBe("Progress callback failed"); |
| 727 | + } |
| 728 | + |
| 729 | + [Fact] |
| 730 | + public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldCancel_WhenTokenIsCancelled() |
| 731 | + { |
| 732 | + // Given |
| 733 | + using var mockHttp = new MockHttpMessageHandler(); |
| 734 | + mockHttp.When("*").Respond(async _ => |
| 735 | + { |
| 736 | + // Simulate slow response. |
| 737 | + await Task.Delay(1000); |
| 738 | + return new HttpResponseMessage(HttpStatusCode.OK); |
| 739 | + }); |
| 740 | + |
| 741 | + var httpClient = mockHttp.ToHttpClient(); |
| 742 | + var speedtest = new OoklaSpeedtest(httpClientOverride: httpClient); |
| 743 | + var servers = new[] { |
| 744 | + new Server { Url = "http://server1.com/", Sponsor = "Sponsor1", Location = "Location1" }, |
| 745 | + new Server { Url = "http://server2.com/", Sponsor = "Sponsor2", Location = "Location2" }, |
| 746 | + new Server { Url = "http://server3.com/", Sponsor = "Sponsor3", Location = "Location3" } |
| 747 | + }; |
| 748 | + var progressReports = new List<int>(); |
| 749 | + |
| 750 | + using var cts = new CancellationTokenSource(); |
| 751 | + cts.CancelAfter(200); |
| 752 | + |
| 753 | + // When |
| 754 | + var exception = await Record.ExceptionAsync(() => speedtest.GetFastestServerByLatencyAsync(servers, progress => progressReports.Add(progress.PercentageComplete), cts.Token)); |
| 755 | + |
| 756 | + // Then |
| 757 | + exception.ShouldNotBeNull(); |
| 758 | + exception.ShouldBeAssignableTo<OperationCanceledException>(); |
| 759 | + cts.IsCancellationRequested.ShouldBeTrue(); |
| 760 | + progressReports.ShouldBeEmpty(); |
| 761 | + } |
| 762 | + |
518 | 763 | // --- GetDownloadSpeedAsync --- |
519 | 764 |
|
520 | 765 | [Fact] |
|
0 commit comments