diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2eb42a0..f44376c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,8 +4,13 @@
-
-
+
+
+
+
@@ -53,18 +58,20 @@
-
-
+
+
-
+
-
+
+
diff --git a/src/Presentation/Agents/Ard/Krautwatch.Agents.Ard.csproj b/src/Presentation/Agents/Ard/Krautwatch.Agents.Ard.csproj
index a9f263a..6bc44b4 100644
--- a/src/Presentation/Agents/Ard/Krautwatch.Agents.Ard.csproj
+++ b/src/Presentation/Agents/Ard/Krautwatch.Agents.Ard.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Presentation/Agents/Ard/Program.cs b/src/Presentation/Agents/Ard/Program.cs
index 1124ffe..82c7653 100644
--- a/src/Presentation/Agents/Ard/Program.cs
+++ b/src/Presentation/Agents/Ard/Program.cs
@@ -1,6 +1,7 @@
using Krautwatch.Application;
using Krautwatch.Application.Crawling;
using Krautwatch.Infrastructure;
+using JasperFx.CodeGeneration.Model;
using Wolverine;
using Wolverine.Postgresql;
@@ -42,6 +43,12 @@
{
opts.PersistMessagesWithPostgresql(connectionString);
opts.Policies.UseDurableLocalQueues();
+ // Wolverine 6 changed the default ServiceLocationPolicy to NotAllowed (5.x was AllowedButWarn),
+ // which refuses to generate a handler needing container resolution. CrawlShowHandler needs it:
+ // IEnumerable is an opaque lambda registration, and IEpisodeRepository's
+ // graph reaches EF's own DbContextOptions factory — not something we control. Restore the 5.x
+ // behaviour: allowed, but keep Wolverine's warning so the nudge to inline stays visible.
+ opts.ServiceLocationPolicy = ServiceLocationPolicy.AllowedButWarn;
// Discover the Crawling Action (CrawlShowHandler) in the Application assembly.
opts.Discovery.IncludeAssembly(typeof(CrawlShowCommand).Assembly);
});
diff --git a/src/Presentation/Agents/Zdf/Krautwatch.Agents.Zdf.csproj b/src/Presentation/Agents/Zdf/Krautwatch.Agents.Zdf.csproj
index a9f263a..6bc44b4 100644
--- a/src/Presentation/Agents/Zdf/Krautwatch.Agents.Zdf.csproj
+++ b/src/Presentation/Agents/Zdf/Krautwatch.Agents.Zdf.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Presentation/Agents/Zdf/Program.cs b/src/Presentation/Agents/Zdf/Program.cs
index bda7683..d932c99 100644
--- a/src/Presentation/Agents/Zdf/Program.cs
+++ b/src/Presentation/Agents/Zdf/Program.cs
@@ -1,6 +1,7 @@
using Krautwatch.Application;
using Krautwatch.Application.Crawling;
using Krautwatch.Infrastructure;
+using JasperFx.CodeGeneration.Model;
using Wolverine;
using Wolverine.Postgresql;
@@ -38,6 +39,12 @@
{
opts.PersistMessagesWithPostgresql(connectionString);
opts.Policies.UseDurableLocalQueues();
+ // Wolverine 6 changed the default ServiceLocationPolicy to NotAllowed (5.x was AllowedButWarn),
+ // which refuses to generate a handler needing container resolution. CrawlShowHandler needs it:
+ // IEnumerable is an opaque lambda registration, and IEpisodeRepository's
+ // graph reaches EF's own DbContextOptions factory — not something we control. Restore the 5.x
+ // behaviour: allowed, but keep Wolverine's warning so the nudge to inline stays visible.
+ opts.ServiceLocationPolicy = ServiceLocationPolicy.AllowedButWarn;
// Discover the Crawling Action (CrawlShowHandler) in the Application assembly.
opts.Discovery.IncludeAssembly(typeof(CrawlShowCommand).Assembly);
});
diff --git a/tests/Application.Tests/CrawlShowHandlerTests.cs b/tests/Application.Tests/CrawlShowHandlerTests.cs
index 003789d..010d9be 100644
--- a/tests/Application.Tests/CrawlShowHandlerTests.cs
+++ b/tests/Application.Tests/CrawlShowHandlerTests.cs
@@ -41,7 +41,7 @@ public async Task Handle_selects_crawler_by_provider_and_upserts_the_crawled_epi
var repo = Substitute.For();
var handler = new CrawlShowHandler([otherProvider, zdf], repo, NullLogger.Instance);
- await handler.HandleAsync(new CrawlShowCommand("zdf", "heute-show"));
+ await handler.HandleAsync(new CrawlShowCommand("zdf", "heute-show"), TestContext.Current.CancellationToken);
zdf.LastQuery.ShouldBe("heute-show");
otherProvider.LastQuery.ShouldBeNull(); // the ARD crawler must not be invoked
@@ -56,7 +56,7 @@ public async Task Handle_is_case_insensitive_on_provider_key()
var repo = Substitute.For();
var handler = new CrawlShowHandler([zdf], repo, NullLogger.Instance);
- await handler.HandleAsync(new CrawlShowCommand("ZDF", "heute-show"));
+ await handler.HandleAsync(new CrawlShowCommand("ZDF", "heute-show"), TestContext.Current.CancellationToken);
zdf.LastQuery.ShouldBe("heute-show");
await repo.Received(1).UpsertManyAsync(Arg.Any>(), Arg.Any());
@@ -68,7 +68,7 @@ public async Task Handle_unknown_provider_is_a_no_op()
var repo = Substitute.For();
var handler = new CrawlShowHandler([], repo, NullLogger.Instance);
- await handler.HandleAsync(new CrawlShowCommand("kika", "Biene Maja"));
+ await handler.HandleAsync(new CrawlShowCommand("kika", "Biene Maja"), TestContext.Current.CancellationToken);
await repo.DidNotReceive().UpsertManyAsync(Arg.Any>(), Arg.Any());
}
@@ -80,7 +80,7 @@ public async Task Handle_empty_crawl_result_does_not_upsert()
var repo = Substitute.For();
var handler = new CrawlShowHandler([ard], repo, NullLogger.Instance);
- await handler.HandleAsync(new CrawlShowCommand("ard", "Nonexistent Show"));
+ await handler.HandleAsync(new CrawlShowCommand("ard", "Nonexistent Show"), TestContext.Current.CancellationToken);
await repo.DidNotReceive().UpsertManyAsync(Arg.Any>(), Arg.Any());
}
diff --git a/tests/Application.Tests/DownloadHandlerTests.cs b/tests/Application.Tests/DownloadHandlerTests.cs
index dabedaa..5090cd5 100644
--- a/tests/Application.Tests/DownloadHandlerTests.cs
+++ b/tests/Application.Tests/DownloadHandlerTests.cs
@@ -67,43 +67,42 @@ public class StartDownloadHandlerTests
public async Task ValidRequest_CreatesJobAndEnqueues()
{
var episode = Fixtures.MakeEpisode();
- _episodes.GetByIdAsync("ep-1", default).Returns(episode);
+ _episodes.GetByIdAsync("ep-1", Arg.Any()).Returns(episode);
- var result = await Handler().HandleAsync(new StartDownloadRequest("ep-1", "stream-1"));
+ var result = await Handler().HandleAsync(new StartDownloadRequest("ep-1", "stream-1"), TestContext.Current.CancellationToken);
result.ShouldNotBeNull();
result!.EpisodeId.ShouldBe("ep-1");
result.Status.ShouldBe(nameof(DownloadStatus.Queued));
- await _jobs.Received(1).AddAsync(Arg.Any(), default);
+ await _jobs.Received(1).AddAsync(Arg.Any(), TestContext.Current.CancellationToken);
await _queue.Received(1).EnqueueAsync(
Arg.Any(),
- "https://example.com/ep.mp4",
- default);
+ "https://example.com/ep.mp4", TestContext.Current.CancellationToken);
}
[Fact]
public async Task EpisodeNotFound_ReturnsNull_NoJobCreated()
{
- _episodes.GetByIdAsync("bad-id", default).Returns((Episode?)null);
+ _episodes.GetByIdAsync("bad-id", Arg.Any()).Returns((Episode?)null);
- var result = await Handler().HandleAsync(new StartDownloadRequest("bad-id", "stream-1"));
+ var result = await Handler().HandleAsync(new StartDownloadRequest("bad-id", "stream-1"), TestContext.Current.CancellationToken);
result.ShouldBeNull();
- await _jobs.DidNotReceive().AddAsync(Arg.Any(), default);
- await _queue.DidNotReceive().EnqueueAsync(Arg.Any(), Arg.Any(), default);
+ await _jobs.DidNotReceive().AddAsync(Arg.Any(), TestContext.Current.CancellationToken);
+ await _queue.DidNotReceive().EnqueueAsync(Arg.Any(), Arg.Any(), TestContext.Current.CancellationToken);
}
[Fact]
public async Task StreamNotFound_ReturnsNull_NoJobCreated()
{
var episode = Fixtures.MakeEpisode("real-stream");
- _episodes.GetByIdAsync("ep-1", default).Returns(episode);
+ _episodes.GetByIdAsync("ep-1", Arg.Any()).Returns(episode);
- var result = await Handler().HandleAsync(new StartDownloadRequest("ep-1", "wrong-stream"));
+ var result = await Handler().HandleAsync(new StartDownloadRequest("ep-1", "wrong-stream"), TestContext.Current.CancellationToken);
result.ShouldBeNull();
- await _jobs.DidNotReceive().AddAsync(Arg.Any(), default);
+ await _jobs.DidNotReceive().AddAsync(Arg.Any(), TestContext.Current.CancellationToken);
}
}
@@ -125,13 +124,13 @@ public async Task ActiveStatus_CancelsAndReturnsTrue(DownloadStatus status)
{
var job = Fixtures.MakeJob();
ApplyStatus(job, status);
- _jobs.GetByIdAsync(job.Id, default).Returns(job);
+ _jobs.GetByIdAsync(job.Id, Arg.Any()).Returns(job);
- var result = await Handler().HandleAsync(job.Id);
+ var result = await Handler().HandleAsync(job.Id, TestContext.Current.CancellationToken);
result.ShouldBeTrue();
job.Status.ShouldBe(DownloadStatus.Cancelled);
- await _jobs.Received(1).UpdateAsync(job, default);
+ await _jobs.Received(1).UpdateAsync(job, TestContext.Current.CancellationToken);
}
[Theory]
@@ -144,20 +143,20 @@ public async Task TerminalStatus_ReturnsFalse_NoUpdate(DownloadStatus status)
{
var job = Fixtures.MakeJob();
ApplyStatus(job, status);
- _jobs.GetByIdAsync(job.Id, default).Returns(job);
+ _jobs.GetByIdAsync(job.Id, Arg.Any()).Returns(job);
- var result = await Handler().HandleAsync(job.Id);
+ var result = await Handler().HandleAsync(job.Id, TestContext.Current.CancellationToken);
result.ShouldBeFalse();
- await _jobs.DidNotReceive().UpdateAsync(Arg.Any(), default);
+ await _jobs.DidNotReceive().UpdateAsync(Arg.Any(), TestContext.Current.CancellationToken);
}
[Fact]
public async Task JobNotFound_ReturnsFalse()
{
- _jobs.GetByIdAsync(Arg.Any(), default).Returns((DownloadJob?)null);
+ _jobs.GetByIdAsync(Arg.Any(), Arg.Any()).Returns((DownloadJob?)null);
- var result = await Handler().HandleAsync(Guid.NewGuid());
+ var result = await Handler().HandleAsync(Guid.NewGuid(), TestContext.Current.CancellationToken);
result.ShouldBeFalse();
}
@@ -197,17 +196,17 @@ public async Task FailedOrCancelledJob_CreatesNewJobAndEnqueues(DownloadStatus s
{
var original = Fixtures.MakeJob();
ApplyTerminal(original, status);
- _jobs.GetByIdAsync(original.Id, default).Returns(original);
+ _jobs.GetByIdAsync(original.Id, Arg.Any()).Returns(original);
- var result = await Handler().HandleAsync(original.Id);
+ var result = await Handler().HandleAsync(original.Id, TestContext.Current.CancellationToken);
result.ShouldNotBeNull();
result!.Status.ShouldBe(nameof(DownloadStatus.Queued));
// New job created — not the original id
result.JobId.ShouldNotBe(original.Id);
- await _jobs.Received(1).AddAsync(Arg.Any(), default);
- await _queue.Received(1).RequeueAsync(Arg.Any(), original.StreamUrl, default);
+ await _jobs.Received(1).AddAsync(Arg.Any(), TestContext.Current.CancellationToken);
+ await _queue.Received(1).RequeueAsync(Arg.Any(), original.StreamUrl, TestContext.Current.CancellationToken);
}
[Fact]
@@ -215,32 +214,32 @@ public async Task CompletedJob_ReturnsNull_NothingEnqueued()
{
var job = Fixtures.MakeJob();
job.MarkCompleted("/out/file.mp4", 1024);
- _jobs.GetByIdAsync(job.Id, default).Returns(job);
+ _jobs.GetByIdAsync(job.Id, Arg.Any()).Returns(job);
- var result = await Handler().HandleAsync(job.Id);
+ var result = await Handler().HandleAsync(job.Id, TestContext.Current.CancellationToken);
result.ShouldBeNull();
- await _queue.DidNotReceive().RequeueAsync(Arg.Any(), Arg.Any(), default);
+ await _queue.DidNotReceive().RequeueAsync(Arg.Any(), Arg.Any(), TestContext.Current.CancellationToken);
}
[Fact]
public async Task ActiveJob_ReturnsNull_NothingEnqueued()
{
var job = Fixtures.MakeJob(); // Queued — IsTerminal = false
- _jobs.GetByIdAsync(job.Id, default).Returns(job);
+ _jobs.GetByIdAsync(job.Id, Arg.Any()).Returns(job);
- var result = await Handler().HandleAsync(job.Id);
+ var result = await Handler().HandleAsync(job.Id, TestContext.Current.CancellationToken);
result.ShouldBeNull();
- await _queue.DidNotReceive().RequeueAsync(Arg.Any(), Arg.Any(), default);
+ await _queue.DidNotReceive().RequeueAsync(Arg.Any(), Arg.Any(), TestContext.Current.CancellationToken);
}
[Fact]
public async Task JobNotFound_ReturnsNull()
{
- _jobs.GetByIdAsync(Arg.Any(), default).Returns((DownloadJob?)null);
+ _jobs.GetByIdAsync(Arg.Any(), Arg.Any()).Returns((DownloadJob?)null);
- var result = await Handler().HandleAsync(Guid.NewGuid());
+ var result = await Handler().HandleAsync(Guid.NewGuid(), TestContext.Current.CancellationToken);
result.ShouldBeNull();
}
diff --git a/tests/Application.Tests/IndexingTests.cs b/tests/Application.Tests/IndexingTests.cs
index 6bd7edb..ff64b92 100644
--- a/tests/Application.Tests/IndexingTests.cs
+++ b/tests/Application.Tests/IndexingTests.cs
@@ -60,7 +60,7 @@ public async Task Query_maps_episodes_to_releases_with_stable_guid_and_token()
repo.SearchAsync("heute-show", Arg.Any())
.Returns(new[] { Ep("zdf:doc-1", SeriesType.Daily, null, null) });
- var releases = await new SearchReleasesHandler(repo).HandleAsync(new SearchReleasesQuery("heute-show"));
+ var releases = await new SearchReleasesHandler(repo).HandleAsync(new SearchReleasesQuery("heute-show"), TestContext.Current.CancellationToken);
var release = releases.ShouldHaveSingleItem();
release.Guid.ShouldBe("zdf:doc-1");
@@ -76,7 +76,7 @@ public async Task Empty_query_reads_the_recent_feed()
repo.GetRecentAsync(Arg.Any(), Arg.Any())
.Returns(new[] { Ep("zdf:doc-9", SeriesType.Daily, null, null) });
- var releases = await new SearchReleasesHandler(repo).HandleAsync(new SearchReleasesQuery(Q: null));
+ var releases = await new SearchReleasesHandler(repo).HandleAsync(new SearchReleasesQuery(Q: null), TestContext.Current.CancellationToken);
releases.ShouldHaveSingleItem().Guid.ShouldBe("zdf:doc-9");
await repo.DidNotReceive().SearchAsync(Arg.Any(), Arg.Any());
@@ -93,7 +93,7 @@ public async Task Season_and_episode_filter_a_standard_series()
});
var releases = await new SearchReleasesHandler(repo)
- .HandleAsync(new SearchReleasesQuery("Die Biene Maja", Season: 2, Episode: 52));
+ .HandleAsync(new SearchReleasesQuery("Die Biene Maja", Season: 2, Episode: 52), TestContext.Current.CancellationToken);
var release = releases.ShouldHaveSingleItem();
release.Guid.ShouldBe("kika:2");
diff --git a/tests/Application.Tests/Krautwatch.Application.Tests.csproj b/tests/Application.Tests/Krautwatch.Application.Tests.csproj
index 4e33c92..615c20e 100644
--- a/tests/Application.Tests/Krautwatch.Application.Tests.csproj
+++ b/tests/Application.Tests/Krautwatch.Application.Tests.csproj
@@ -1,5 +1,10 @@
+
+
+ Exe
+
+
@@ -7,7 +12,7 @@
-
+
diff --git a/tests/Application.Tests/RefreshProxyListHandlerTests.cs b/tests/Application.Tests/RefreshProxyListHandlerTests.cs
index 5c2c3d7..e578ca7 100644
--- a/tests/Application.Tests/RefreshProxyListHandlerTests.cs
+++ b/tests/Application.Tests/RefreshProxyListHandlerTests.cs
@@ -22,7 +22,7 @@ public async Task Upserts_the_fetched_candidates()
source.FetchAsync(Arg.Any()).Returns([P("1.1.1.1"), P("2.2.2.2")]);
var repo = Substitute.For();
- await new RefreshProxyListHandler(source, repo, NullLogger.Instance).HandleAsync();
+ await new RefreshProxyListHandler(source, repo, NullLogger.Instance).HandleAsync(TestContext.Current.CancellationToken);
await repo.Received(1).UpsertBatchAsync(
Arg.Is>(ps => ps != null && ps.Count() == 2), Arg.Any());
@@ -35,7 +35,7 @@ public async Task An_empty_fetch_keeps_the_cached_rows_untouched()
source.FetchAsync(Arg.Any()).Returns([]);
var repo = Substitute.For();
- await new RefreshProxyListHandler(source, repo, NullLogger.Instance).HandleAsync();
+ await new RefreshProxyListHandler(source, repo, NullLogger.Instance).HandleAsync(TestContext.Current.CancellationToken);
await repo.DidNotReceive().UpsertBatchAsync(Arg.Any>(), Arg.Any());
}
diff --git a/tests/Application.Tests/RunDownloadHandlerTests.cs b/tests/Application.Tests/RunDownloadHandlerTests.cs
index 4c37394..3343129 100644
--- a/tests/Application.Tests/RunDownloadHandlerTests.cs
+++ b/tests/Application.Tests/RunDownloadHandlerTests.cs
@@ -52,7 +52,7 @@ public async Task Downloads_and_marks_the_job_completed()
provider.DownloadAsync(job, "/downloads", Arg.Any>(), Arg.Any())
.Returns(new DownloadResult("/downloads/ZDF/heute-show/x.mp4", 123_456));
- await Sut(jobs, provider, settings).HandleAsync(job);
+ await Sut(jobs, provider, settings).HandleAsync(job, TestContext.Current.CancellationToken);
job.Status.ShouldBe(DownloadStatus.Completed);
job.OutputPath.ShouldBe("/downloads/ZDF/heute-show/x.mp4");
@@ -68,7 +68,7 @@ public async Task Provider_failure_marks_the_job_download_failed()
provider.DownloadAsync(Arg.Any(), Arg.Any(), Arg.Any>(), Arg.Any())
.ThrowsAsync(new HttpRequestException("stream 403"));
- await Sut(jobs, provider, settings).HandleAsync(job);
+ await Sut(jobs, provider, settings).HandleAsync(job, TestContext.Current.CancellationToken);
job.Status.ShouldBe(DownloadStatus.DownloadFailed);
job.ErrorMessage.ShouldBe("stream 403");
@@ -80,7 +80,7 @@ public async Task A_job_missing_episode_metadata_fails_without_downloading()
var job = new DownloadJob { Id = Guid.NewGuid(), EpisodeId = "x", StreamUrl = "https://cdn/x.mp4", Quality = VideoQuality.High };
var (jobs, provider, settings) = Deps();
- await Sut(jobs, provider, settings).HandleAsync(job);
+ await Sut(jobs, provider, settings).HandleAsync(job, TestContext.Current.CancellationToken);
job.Status.ShouldBe(DownloadStatus.DownloadFailed);
await provider.DidNotReceive().DownloadAsync(Arg.Any(), Arg.Any(), Arg.Any>(), Arg.Any());
diff --git a/tests/Application.Tests/SabnzbdDownloadTests.cs b/tests/Application.Tests/SabnzbdDownloadTests.cs
index a33611e..e97a42b 100644
--- a/tests/Application.Tests/SabnzbdDownloadTests.cs
+++ b/tests/Application.Tests/SabnzbdDownloadTests.cs
@@ -64,7 +64,7 @@ public async Task Creates_and_enqueues_a_job_for_a_known_token()
var jobs = Substitute.For();
var queue = Substitute.For();
- var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("zdf:1");
+ var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("zdf:1", TestContext.Current.CancellationToken);
jobId.ShouldNotBeNull();
await jobs.Received(1).AddAsync(Arg.Is(j => j != null && j.EpisodeId == "zdf:1" && j.StreamUrl == "https://cdn/x.mp4"), Arg.Any());
@@ -80,7 +80,7 @@ public async Task Snapshots_the_episodes_geo_restriction_onto_the_job()
var jobs = Substitute.For();
var queue = Substitute.For();
- await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("kika:1");
+ await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("kika:1", TestContext.Current.CancellationToken);
await jobs.Received(1).AddAsync(Arg.Is(j => j != null && j.GeoRestricted), Arg.Any());
}
@@ -93,7 +93,7 @@ public async Task Unknown_token_returns_null_and_enqueues_nothing()
var jobs = Substitute.For();
var queue = Substitute.For();
- var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("nope");
+ var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("nope", TestContext.Current.CancellationToken);
jobId.ShouldBeNull();
await queue.DidNotReceive().EnqueueAsync(Arg.Any(), Arg.Any(), Arg.Any());
@@ -110,7 +110,7 @@ public async Task Episode_without_a_stream_returns_null()
var jobs = Substitute.For();
var queue = Substitute.For();
- var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("zdf:2");
+ var jobId = await new AddDownloadByTokenHandler(episodes, jobs, queue).HandleAsync("zdf:2", TestContext.Current.CancellationToken);
jobId.ShouldBeNull();
await jobs.DidNotReceive().AddAsync(Arg.Any(), Arg.Any());
diff --git a/tests/Application.Tests/SearchCatalogQueryHandlerTests.cs b/tests/Application.Tests/SearchCatalogQueryHandlerTests.cs
index a1e95f4..ef1c714 100644
--- a/tests/Application.Tests/SearchCatalogQueryHandlerTests.cs
+++ b/tests/Application.Tests/SearchCatalogQueryHandlerTests.cs
@@ -56,7 +56,7 @@ public async Task HandleAsync_WithResults_ReturnsMappedResponses()
.Returns(episodes);
// Act
- var results = await _sut.HandleAsync(new SearchCatalogQuery("tagesschau"));
+ var results = await _sut.HandleAsync(new SearchCatalogQuery("tagesschau"), TestContext.Current.CancellationToken);
// Assert
results.Count.ShouldBe(1);
@@ -80,7 +80,7 @@ public async Task HandleAsync_NoResults_ReturnsEmptyList()
.SearchAsync(Arg.Any(), Arg.Any())
.Returns(new List());
- var results = await _sut.HandleAsync(new SearchCatalogQuery("noresults"));
+ var results = await _sut.HandleAsync(new SearchCatalogQuery("noresults"), TestContext.Current.CancellationToken);
results.ShouldBeEmpty();
}
@@ -92,7 +92,7 @@ public async Task HandleAsync_CallsRepositoryWithCorrectQuery()
.SearchAsync(Arg.Any(), Arg.Any())
.Returns(new List());
- await _sut.HandleAsync(new SearchCatalogQuery("tagesthemen"));
+ await _sut.HandleAsync(new SearchCatalogQuery("tagesthemen"), TestContext.Current.CancellationToken);
await _repository
.Received(1)
diff --git a/tests/Application.Tests/SearchCatalogValidatorTests.cs b/tests/Application.Tests/SearchCatalogValidatorTests.cs
index 6681905..2215eae 100644
--- a/tests/Application.Tests/SearchCatalogValidatorTests.cs
+++ b/tests/Application.Tests/SearchCatalogValidatorTests.cs
@@ -11,7 +11,7 @@ public class SearchCatalogQueryValidatorTests
[Fact]
public async Task Validate_ValidQuery_PassesValidation()
{
- var result = await _sut.ValidateAsync(new SearchCatalogQuery("tagesschau"));
+ var result = await _sut.ValidateAsync(new SearchCatalogQuery("tagesschau"), TestContext.Current.CancellationToken);
result.IsValid.ShouldBeTrue();
}
@@ -20,7 +20,7 @@ public async Task Validate_ValidQuery_PassesValidation()
[InlineData(" ")]
public async Task Validate_EmptyQuery_FailsValidation(string query)
{
- var result = await _sut.ValidateAsync(new SearchCatalogQuery(query));
+ var result = await _sut.ValidateAsync(new SearchCatalogQuery(query), TestContext.Current.CancellationToken);
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(e => e.PropertyName == "Query");
}
@@ -28,7 +28,7 @@ public async Task Validate_EmptyQuery_FailsValidation(string query)
[Fact]
public async Task Validate_SingleCharQuery_FailsValidation()
{
- var result = await _sut.ValidateAsync(new SearchCatalogQuery("a"));
+ var result = await _sut.ValidateAsync(new SearchCatalogQuery("a"), TestContext.Current.CancellationToken);
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(e => e.ErrorMessage.Contains("2 characters"));
}
@@ -37,7 +37,7 @@ public async Task Validate_SingleCharQuery_FailsValidation()
public async Task Validate_QueryExceeding200Chars_FailsValidation()
{
var longQuery = new string('a', 201);
- var result = await _sut.ValidateAsync(new SearchCatalogQuery(longQuery));
+ var result = await _sut.ValidateAsync(new SearchCatalogQuery(longQuery), TestContext.Current.CancellationToken);
result.IsValid.ShouldBeFalse();
result.Errors.ShouldContain(e => e.ErrorMessage.Contains("200 characters"));
}
diff --git a/tests/Architecture.Tests/ApplicationSliceSpecs.cs b/tests/Architecture.Tests/ApplicationSliceSpecs.cs
index b11a5a2..4b85f2b 100644
--- a/tests/Architecture.Tests/ApplicationSliceSpecs.cs
+++ b/tests/Architecture.Tests/ApplicationSliceSpecs.cs
@@ -1,5 +1,5 @@
using ArchUnitNET.Loader;
-using ArchUnitNET.xUnit;
+using ArchUnitNET.Fluent;
using Xunit;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
diff --git a/tests/Architecture.Tests/ArchRuleAssert.cs b/tests/Architecture.Tests/ArchRuleAssert.cs
new file mode 100644
index 0000000..cb5e0e5
--- /dev/null
+++ b/tests/Architecture.Tests/ArchRuleAssert.cs
@@ -0,0 +1,27 @@
+using ArchUnitNET.Domain;
+using ArchUnitNET.Fluent;
+using Xunit;
+
+namespace Krautwatch.Architecture.Tests;
+
+///
+/// Replaces TngTech.ArchUnitNET.xUnit's Check() extension. That package still depends on
+/// xunit.assert 2.x, which cannot coexist with xunit v3, so we evaluate the rule against the
+/// core ArchUnitNET API and fail through xunit ourselves.
+///
+internal static class ArchRuleAssert
+{
+ /// Evaluates the rule and fails the test with every violation listed if it does not hold.
+ public static void Check(this IArchRule rule, ArchUnitNET.Domain.Architecture architecture)
+ {
+ if (rule.HasNoViolations(architecture)) return;
+
+ var violations = rule.Evaluate(architecture)
+ .Where(result => !result.Passed)
+ .Select(result => $" - {result.Description}")
+ .ToList();
+
+ Assert.Fail($"Architecture rule violated: {rule.Description}{Environment.NewLine}"
+ + string.Join(Environment.NewLine, violations));
+ }
+}
diff --git a/tests/Architecture.Tests/HexagonalArchitectureSpecs.cs b/tests/Architecture.Tests/HexagonalArchitectureSpecs.cs
index e765647..bd2ccfb 100644
--- a/tests/Architecture.Tests/HexagonalArchitectureSpecs.cs
+++ b/tests/Architecture.Tests/HexagonalArchitectureSpecs.cs
@@ -1,6 +1,6 @@
using ArchUnitNET.Domain;
using ArchUnitNET.Loader;
-using ArchUnitNET.xUnit;
+using ArchUnitNET.Fluent;
using Xunit;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
diff --git a/tests/Architecture.Tests/Krautwatch.Architecture.Tests.csproj b/tests/Architecture.Tests/Krautwatch.Architecture.Tests.csproj
index 8eef37d..11e0fa8 100644
--- a/tests/Architecture.Tests/Krautwatch.Architecture.Tests.csproj
+++ b/tests/Architecture.Tests/Krautwatch.Architecture.Tests.csproj
@@ -1,5 +1,10 @@
+
+
+ Exe
+
+
@@ -8,9 +13,9 @@
-
+
-
+
diff --git a/tests/Domain.Tests/Krautwatch.Domain.Tests.csproj b/tests/Domain.Tests/Krautwatch.Domain.Tests.csproj
index 8598ffd..c3eaf43 100644
--- a/tests/Domain.Tests/Krautwatch.Domain.Tests.csproj
+++ b/tests/Domain.Tests/Krautwatch.Domain.Tests.csproj
@@ -1,12 +1,17 @@
+
+
+ Exe
+
+
-
+
diff --git a/tests/Infrastructure.Tests/DownloadJobRepositoryTests.cs b/tests/Infrastructure.Tests/DownloadJobRepositoryTests.cs
index e0d67ec..b7cfa63 100644
--- a/tests/Infrastructure.Tests/DownloadJobRepositoryTests.cs
+++ b/tests/Infrastructure.Tests/DownloadJobRepositoryTests.cs
@@ -15,7 +15,7 @@ public class DownloadJobRepositoryTests(PostgresFixture postgres) : IAsyncLifeti
// ExecuteUpdate + a re-read on the same context returns a stale tracked entity.
private DbContextOptions _options = null!;
- public async Task InitializeAsync()
+ public async ValueTask InitializeAsync()
{
_options = await postgres.CreateDatabaseAsync();
@@ -30,7 +30,7 @@ public async Task InitializeAsync()
await db.SaveChangesAsync();
}
- public Task DisposeAsync() => Task.CompletedTask;
+ public ValueTask DisposeAsync() => ValueTask.CompletedTask;
private DownloadJobRepository Repo() => new(new AppDbContext(_options));
@@ -56,7 +56,7 @@ public async Task TryClaimNext_claims_the_oldest_queued_job_with_its_episode()
var older = await AddQueuedAsync(DateTimeOffset.UtcNow.AddMinutes(-10));
await AddQueuedAsync(DateTimeOffset.UtcNow.AddMinutes(-1));
- var claimed = await Repo().TryClaimNextAsync("worker-1");
+ var claimed = await Repo().TryClaimNextAsync("worker-1", TestContext.Current.CancellationToken);
claimed.ShouldNotBeNull();
claimed!.Id.ShouldBe(older);
@@ -68,7 +68,7 @@ public async Task TryClaimNext_claims_the_oldest_queued_job_with_its_episode()
[Fact]
public async Task TryClaimNext_returns_null_when_nothing_is_queued()
{
- (await Repo().TryClaimNextAsync("worker-1")).ShouldBeNull();
+ (await Repo().TryClaimNextAsync("worker-1", TestContext.Current.CancellationToken)).ShouldBeNull();
}
[Fact]
@@ -76,20 +76,20 @@ public async Task TryClaimNext_will_not_hand_the_same_job_to_two_workers()
{
await AddQueuedAsync(DateTimeOffset.UtcNow.AddMinutes(-5));
- (await Repo().TryClaimNextAsync("worker-1")).ShouldNotBeNull();
- (await Repo().TryClaimNextAsync("worker-2")).ShouldBeNull(); // the only job is already claimed
+ (await Repo().TryClaimNextAsync("worker-1", TestContext.Current.CancellationToken)).ShouldNotBeNull();
+ (await Repo().TryClaimNextAsync("worker-2", TestContext.Current.CancellationToken)).ShouldBeNull(); // the only job is already claimed
}
[Fact]
public async Task ReclaimStale_requeues_this_workers_downloading_jobs()
{
var id = await AddQueuedAsync(DateTimeOffset.UtcNow.AddMinutes(-5));
- await Repo().TryClaimNextAsync("worker-1"); // → Downloading, worker-1
+ await Repo().TryClaimNextAsync("worker-1", TestContext.Current.CancellationToken); // → Downloading, worker-1
- var reclaimed = await Repo().ReclaimStaleAsync("worker-1");
+ var reclaimed = await Repo().ReclaimStaleAsync("worker-1", TestContext.Current.CancellationToken);
reclaimed.ShouldBe(1);
- var job = await Repo().GetByIdAsync(id);
+ var job = await Repo().GetByIdAsync(id, TestContext.Current.CancellationToken);
job!.Status.ShouldBe(DownloadStatus.Queued);
job.WorkerId.ShouldBeNull();
}
@@ -98,9 +98,9 @@ public async Task ReclaimStale_requeues_this_workers_downloading_jobs()
public async Task ReclaimStale_leaves_other_workers_jobs_alone()
{
await AddQueuedAsync(DateTimeOffset.UtcNow.AddMinutes(-5));
- await Repo().TryClaimNextAsync("worker-1");
+ await Repo().TryClaimNextAsync("worker-1", TestContext.Current.CancellationToken);
- (await Repo().ReclaimStaleAsync("worker-2")).ShouldBe(0);
+ (await Repo().ReclaimStaleAsync("worker-2", TestContext.Current.CancellationToken)).ShouldBe(0);
}
[Fact]
@@ -108,9 +108,9 @@ public async Task Delete_removes_the_job()
{
var id = await AddQueuedAsync(DateTimeOffset.UtcNow);
- await Repo().DeleteAsync(id);
+ await Repo().DeleteAsync(id, TestContext.Current.CancellationToken);
- (await Repo().GetByIdAsync(id)).ShouldBeNull();
+ (await Repo().GetByIdAsync(id, TestContext.Current.CancellationToken)).ShouldBeNull();
}
}
diff --git a/tests/Infrastructure.Tests/EgressProxyTests.cs b/tests/Infrastructure.Tests/EgressProxyTests.cs
index 9e10d44..427400a 100644
--- a/tests/Infrastructure.Tests/EgressProxyTests.cs
+++ b/tests/Infrastructure.Tests/EgressProxyTests.cs
@@ -20,9 +20,9 @@ public class ProxyRepositoryTests(PostgresFixture postgres) : IAsyncLifetime
{
private DbContextOptions _options = null!;
- public async Task InitializeAsync() => _options = await postgres.CreateDatabaseAsync();
+ public async ValueTask InitializeAsync() => _options = await postgres.CreateDatabaseAsync();
- public Task DisposeAsync() => Task.CompletedTask;
+ public ValueTask DisposeAsync() => ValueTask.CompletedTask;
private ProxyRepository Repo() => new(new AppDbContext(_options));
@@ -35,13 +35,13 @@ public class ProxyRepositoryTests(PostgresFixture postgres) : IAsyncLifetime
[Fact]
public async Task Upsert_adds_new_rows_then_refreshes_metrics_but_keeps_our_feedback()
{
- await Repo().UpsertBatchAsync([P("1.1.1.1", 3128, upTime: 40)]);
- await Repo().RecordProbeResultAsync("http://1.1.1.1:3128", ok: true);
+ await Repo().UpsertBatchAsync([P("1.1.1.1", 3128, upTime: 40)], TestContext.Current.CancellationToken);
+ await Repo().RecordProbeResultAsync("http://1.1.1.1:3128", ok: true, TestContext.Current.CancellationToken);
// A refresh brings new source metrics for the same host:port.
- await Repo().UpsertBatchAsync([P("1.1.1.1", 3128, upTime: 90)]);
+ await Repo().UpsertBatchAsync([P("1.1.1.1", 3128, upTime: 90)], TestContext.Current.CancellationToken);
- var row = (await Repo().GetRankedAsync("DE", 10)).ShouldHaveSingleItem();
+ var row = (await Repo().GetRankedAsync("DE", 10, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
row.UpTime.ShouldBe(90); // source metric refreshed
row.LastProbeOk.ShouldBe(true); // our feedback preserved across the refresh
}
@@ -54,9 +54,9 @@ await Repo().UpsertBatchAsync(
P("bad", 1, upTime: 99, probeOk: false), // known-bad sinks despite high uptime
P("unknown", 2, upTime: 60), // untested
P("good", 3, upTime: 10, probeOk: true), // known-good floats up despite low uptime
- ]);
+ ], TestContext.Current.CancellationToken);
- var ranked = await Repo().GetRankedAsync("DE", 10);
+ var ranked = await Repo().GetRankedAsync("DE", 10, TestContext.Current.CancellationToken);
ranked.Select(p => p.Host).ShouldBe(["good", "unknown", "bad"]);
}
@@ -66,9 +66,9 @@ public async Task GetRanked_only_returns_the_requested_country()
{
var de = P("de", 1);
var other = new Proxy { Id = "ch:2", Host = "ch", Port = 2, Protocol = "http", Source = "geonode", Country = "CH" };
- await Repo().UpsertBatchAsync([de, other]);
+ await Repo().UpsertBatchAsync([de, other], TestContext.Current.CancellationToken);
- (await Repo().GetRankedAsync("DE", 10)).ShouldHaveSingleItem().Host.ShouldBe("de");
+ (await Repo().GetRankedAsync("DE", 10, TestContext.Current.CancellationToken)).ShouldHaveSingleItem().Host.ShouldBe("de");
}
}
@@ -96,7 +96,7 @@ public async Task Parses_the_geonode_shape_and_skips_malformed_rows()
var http = new HttpClient(new StubHandler(Json));
var source = new GeoNodeProxyListSource(http, new ProxyListOptions(), NullLogger.Instance);
- var proxies = await source.FetchAsync();
+ var proxies = await source.FetchAsync(TestContext.Current.CancellationToken);
var p = proxies.ShouldHaveSingleItem(); // the port-less row is dropped
p.Id.ShouldBe("1.2.3.4:3128");
@@ -116,7 +116,7 @@ public async Task Byo_proxy_is_offered_when_configured()
var opts = new EgressProxyOptions { ProxyUrl = "http://10.0.0.9:3128" };
var sut = new EgressProxyProvider(opts, Substitute.For());
- (await sut.GetCandidatesAsync()).ShouldBe(["http://10.0.0.9:3128"]);
+ (await sut.GetCandidatesAsync(TestContext.Current.CancellationToken)).ShouldBe(["http://10.0.0.9:3128"]);
}
[Fact]
@@ -124,7 +124,7 @@ public async Task No_candidates_when_nothing_is_configured()
{
var sut = new EgressProxyProvider(new EgressProxyOptions(), Substitute.For());
- (await sut.GetCandidatesAsync()).ShouldBeEmpty();
+ (await sut.GetCandidatesAsync(TestContext.Current.CancellationToken)).ShouldBeEmpty();
}
}
diff --git a/tests/Infrastructure.Tests/EpisodeRepositoryTests.cs b/tests/Infrastructure.Tests/EpisodeRepositoryTests.cs
index b335319..832f620 100644
--- a/tests/Infrastructure.Tests/EpisodeRepositoryTests.cs
+++ b/tests/Infrastructure.Tests/EpisodeRepositoryTests.cs
@@ -15,7 +15,7 @@ public class EpisodeRepositoryTests(PostgresFixture postgres) : IAsyncLifetime
private AppDbContext _db = null!;
private EpisodeRepository _sut = null!;
- public async Task InitializeAsync()
+ public async ValueTask InitializeAsync()
{
_db = new AppDbContext(await postgres.CreateDatabaseAsync());
_sut = new EpisodeRepository(_db);
@@ -23,10 +23,10 @@ public async Task InitializeAsync()
SeedTestData();
}
- public Task DisposeAsync()
+ public ValueTask DisposeAsync()
{
_db.Dispose();
- return Task.CompletedTask;
+ return ValueTask.CompletedTask;
}
private void SeedTestData()
@@ -77,7 +77,7 @@ private void SeedTestData()
[Fact]
public async Task GetByIdAsync_ExistingEpisode_ReturnsEpisodeWithStreams()
{
- var result = await _sut.GetByIdAsync("ep-1");
+ var result = await _sut.GetByIdAsync("ep-1", TestContext.Current.CancellationToken);
result.ShouldNotBeNull();
result.Title.ShouldBe("Tagesschau 20 Uhr");
@@ -88,14 +88,14 @@ public async Task GetByIdAsync_ExistingEpisode_ReturnsEpisodeWithStreams()
[Fact]
public async Task GetByIdAsync_NonExistentId_ReturnsNull()
{
- var result = await _sut.GetByIdAsync("does-not-exist");
+ var result = await _sut.GetByIdAsync("does-not-exist", TestContext.Current.CancellationToken);
result.ShouldBeNull();
}
[Fact]
public async Task SearchAsync_MatchingTitle_ReturnsResults()
{
- var results = await _sut.SearchAsync("tagesschau");
+ var results = await _sut.SearchAsync("tagesschau", TestContext.Current.CancellationToken);
results.ShouldNotBeEmpty();
results.ShouldContain(e => e.Title == "Tagesschau 20 Uhr");
@@ -104,7 +104,7 @@ public async Task SearchAsync_MatchingTitle_ReturnsResults()
[Fact]
public async Task SearchAsync_MatchingDescription_ReturnsResults()
{
- var results = await _sut.SearchAsync("nachrichten");
+ var results = await _sut.SearchAsync("nachrichten", TestContext.Current.CancellationToken);
results.ShouldNotBeEmpty();
results.ShouldContain(e => e.Id == "ep-1");
@@ -113,14 +113,14 @@ public async Task SearchAsync_MatchingDescription_ReturnsResults()
[Fact]
public async Task SearchAsync_NoMatch_ReturnsEmpty()
{
- var results = await _sut.SearchAsync("zdfmediathek-xyz-nomatch");
+ var results = await _sut.SearchAsync("zdfmediathek-xyz-nomatch", TestContext.Current.CancellationToken);
results.ShouldBeEmpty();
}
[Fact]
public async Task GetByChannelAsync_KnownChannel_ReturnsEpisodes()
{
- var results = await _sut.GetByChannelAsync("ard");
+ var results = await _sut.GetByChannelAsync("ard", ct: TestContext.Current.CancellationToken);
results.Count.ShouldBe(2);
results.ShouldAllBe(e => e.Show.Channel.Id == "ard");
@@ -129,8 +129,8 @@ public async Task GetByChannelAsync_KnownChannel_ReturnsEpisodes()
[Fact]
public async Task UpsertManyAsync_NewEpisodes_AreInserted()
{
- var channel = await _db.Channels.FindAsync("ard");
- var show = await _db.Shows.FindAsync("show-1");
+ var channel = await _db.Channels.FindAsync(new object[] { "ard" }, TestContext.Current.CancellationToken);
+ var show = await _db.Shows.FindAsync(new object[] { "show-1" }, TestContext.Current.CancellationToken);
var newEpisodes = new[]
{
@@ -146,9 +146,9 @@ public async Task UpsertManyAsync_NewEpisodes_AreInserted()
}
};
- await _sut.UpsertManyAsync(newEpisodes);
+ await _sut.UpsertManyAsync(newEpisodes, TestContext.Current.CancellationToken);
- var result = await _sut.GetByIdAsync("ep-new-1");
+ var result = await _sut.GetByIdAsync("ep-new-1", TestContext.Current.CancellationToken);
result.ShouldNotBeNull();
result.Title.ShouldBe("New Episode");
}
@@ -182,12 +182,12 @@ public async Task UpsertManyAsync_FreshCrawlGraph_InsertsChannelShowEpisodeAndSt
]
}).ToList();
- await _sut.UpsertManyAsync(episodes);
+ await _sut.UpsertManyAsync(episodes, TestContext.Current.CancellationToken);
- (await _db.Channels.FindAsync("zdf")).ShouldNotBeNull();
- (await _db.Shows.FindAsync("zdf:heute-show")).ShouldNotBeNull();
+ (await _db.Channels.FindAsync(new object[] { "zdf" }, TestContext.Current.CancellationToken)).ShouldNotBeNull();
+ (await _db.Shows.FindAsync(new object[] { "zdf:heute-show" }, TestContext.Current.CancellationToken)).ShouldNotBeNull();
- var persisted = await _sut.GetByIdAsync("zdf:doc-1");
+ var persisted = await _sut.GetByIdAsync("zdf:doc-1", TestContext.Current.CancellationToken);
persisted.ShouldNotBeNull();
persisted.Show.Channel.Name.ShouldBe("ZDF");
persisted.Streams.ShouldHaveSingleItem().Url.ShouldBe("https://cdn.zdf.de/doc-1.mp4");
@@ -212,10 +212,10 @@ public async Task UpsertManyAsync_RecrawlSameEpisode_UpdatesInPlaceWithoutDuplic
]
};
- await _sut.UpsertManyAsync([Build("first title")]);
- await _sut.UpsertManyAsync([Build("updated title")]);
+ await _sut.UpsertManyAsync([Build("first title")], TestContext.Current.CancellationToken);
+ await _sut.UpsertManyAsync([Build("updated title")], TestContext.Current.CancellationToken);
- var all = await _sut.GetByShowAsync("ardx:extra-3");
+ var all = await _sut.GetByShowAsync("ardx:extra-3", TestContext.Current.CancellationToken);
all.ShouldHaveSingleItem().Title.ShouldBe("updated title");
}
diff --git a/tests/Infrastructure.Tests/Krautwatch.Infrastructure.Tests.csproj b/tests/Infrastructure.Tests/Krautwatch.Infrastructure.Tests.csproj
index d58b725..0ea2a9a 100644
--- a/tests/Infrastructure.Tests/Krautwatch.Infrastructure.Tests.csproj
+++ b/tests/Infrastructure.Tests/Krautwatch.Infrastructure.Tests.csproj
@@ -1,5 +1,10 @@
+
+
+ Exe
+
+
@@ -7,7 +12,7 @@
-
+
diff --git a/tests/Infrastructure.Tests/PostgresFixture.cs b/tests/Infrastructure.Tests/PostgresFixture.cs
index 5916bff..4186fdc 100644
--- a/tests/Infrastructure.Tests/PostgresFixture.cs
+++ b/tests/Infrastructure.Tests/PostgresFixture.cs
@@ -20,9 +20,9 @@ public sealed class PostgresFixture : IAsyncLifetime
private readonly PostgreSqlContainer _container =
new PostgreSqlBuilder("postgres:17-alpine").Build();
- public Task InitializeAsync() => _container.StartAsync();
+ public async ValueTask InitializeAsync() => await _container.StartAsync();
- public Task DisposeAsync() => _container.DisposeAsync().AsTask();
+ public ValueTask DisposeAsync() => _container.DisposeAsync();
///
/// Creates an isolated database with the schema applied, and returns options bound to it.
diff --git a/tests/Live.Tests/ArdLiveTests.cs b/tests/Live.Tests/ArdLiveTests.cs
index a24e31a..f515a7c 100644
--- a/tests/Live.Tests/ArdLiveTests.cs
+++ b/tests/Live.Tests/ArdLiveTests.cs
@@ -25,7 +25,7 @@ private static HttpClient CreateHttp()
[Fact]
public async Task Search_finds_Extra3_on_ARD()
{
- var show = await Client.FindShowAsync("Extra 3");
+ var show = await Client.FindShowAsync("Extra 3", ct: TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
show!.Title.ShouldContain("extra 3", Case.Insensitive);
@@ -35,10 +35,10 @@ public async Task Search_finds_Extra3_on_ARD()
[Fact]
public async Task Fetches_Extra3_full_episodes_with_sane_metadata()
{
- var show = await Client.FindShowAsync("Extra 3");
+ var show = await Client.FindShowAsync("Extra 3", ct: TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
- var episodes = await Client.GetFullEpisodesAsync(show!);
+ var episodes = await Client.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken);
episodes.ShouldNotBeEmpty();
// Full "extra 3" episodes follow the "extra 3 vom " pattern and run ~30-50 min.
@@ -52,11 +52,11 @@ public async Task Fetches_Extra3_full_episodes_with_sane_metadata()
[Fact]
public async Task Downloads_a_full_Extra3_episode()
{
- var show = await Client.FindShowAsync("Extra 3");
+ var show = await Client.FindShowAsync("Extra 3", ct: TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
- var episodes = await Client.GetFullEpisodesAsync(show!);
+ var episodes = await Client.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken);
var full = episodes.First(e => e.Title.Contains("extra 3 vom", StringComparison.OrdinalIgnoreCase));
- var detail = await Client.FetchEpisodeDetailAsync(full);
+ var detail = await Client.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken);
detail.ShouldNotBeNull();
detail!.StreamUrl.ShouldNotBeNull();
@@ -77,7 +77,7 @@ static ArdKikaLiveTests() =>
[Fact]
public async Task Search_finds_Biene_Maja_on_KiKA()
{
- var show = await Client.FindShowAsync("Biene Maja", client: "kika");
+ var show = await Client.FindShowAsync("Biene Maja", client: "kika", TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
show!.Title.ShouldContain("Biene Maja", Case.Insensitive);
@@ -88,9 +88,9 @@ public async Task Search_finds_Biene_Maja_on_KiKA()
[Fact]
public async Task Fetches_Biene_Maja_episodes()
{
- var show = await Client.FindShowAsync("Biene Maja", client: "kika");
+ var show = await Client.FindShowAsync("Biene Maja", client: "kika", TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
- var episodes = await Client.GetFullEpisodesAsync(show!);
+ var episodes = await Client.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken);
episodes.ShouldNotBeEmpty();
}
}
diff --git a/tests/Live.Tests/BroadcasterCrawlerLiveTests.cs b/tests/Live.Tests/BroadcasterCrawlerLiveTests.cs
index f540c2d..29e931d 100644
--- a/tests/Live.Tests/BroadcasterCrawlerLiveTests.cs
+++ b/tests/Live.Tests/BroadcasterCrawlerLiveTests.cs
@@ -29,7 +29,7 @@ public async Task Ard_crawler_maps_Extra3_to_domain_episodes_with_streams()
{
var crawler = new ArdBroadcasterCrawler(new ArdCatalogClient(Http), "ard", "ard", "ARD");
- var episodes = await crawler.CrawlShowAsync("Extra 3");
+ var episodes = await crawler.CrawlShowAsync("Extra 3", TestContext.Current.CancellationToken);
episodes.ShouldNotBeEmpty();
var episode = episodes[0];
@@ -48,7 +48,7 @@ public async Task Zdf_crawler_maps_HeuteShow_to_domain_episodes_with_streams()
{
var crawler = new ZdfBroadcasterCrawler(new ZdfCatalogClient(Http));
- var episodes = await crawler.CrawlShowAsync("heute-show");
+ var episodes = await crawler.CrawlShowAsync("heute-show", TestContext.Current.CancellationToken);
episodes.ShouldNotBeEmpty();
episodes.ShouldAllBe(e => e.Id.StartsWith("zdf:"));
diff --git a/tests/Live.Tests/FullDownloadTests.cs b/tests/Live.Tests/FullDownloadTests.cs
index f9eeb52..affb2e5 100644
--- a/tests/Live.Tests/FullDownloadTests.cs
+++ b/tests/Live.Tests/FullDownloadTests.cs
@@ -33,10 +33,10 @@ static FullDownloadTests() =>
public async Task Downloads_a_full_Extra3_episode_from_ARD()
{
var ard = new ArdCatalogClient(Http);
- var show = await ard.FindShowAsync("Extra 3");
- var full = (await ard.GetFullEpisodesAsync(show!))
+ var show = await ard.FindShowAsync("Extra 3", ct: TestContext.Current.CancellationToken);
+ var full = (await ard.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken))
.First(e => e.Title.Contains("extra 3 vom", StringComparison.OrdinalIgnoreCase));
- await DownloadAndVerifyAsync(await ard.FetchEpisodeDetailAsync(full));
+ await DownloadAndVerifyAsync(await ard.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken));
}
// A DE egress proxy for local runs (#45). Set it to do the REAL geo-restricted download; leave it
@@ -48,11 +48,11 @@ public async Task Downloads_a_full_Extra3_episode_from_ARD()
public async Task Downloads_a_full_BieneMaja_episode_from_KiKA()
{
var ard = new ArdCatalogClient(Http);
- var show = await ard.FindShowAsync("Biene Maja", client: "kika");
- var full = (await ard.GetFullEpisodesAsync(show!)).First();
+ var show = await ard.FindShowAsync("Biene Maja", client: "kika", TestContext.Current.CancellationToken);
+ var full = (await ard.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken)).First();
// Biene Maja is DACH geo-fenced. With a DE egress (KRAUTWATCH_TEST_PROXY) it downloads for real;
// without one, the provider fails fast (geo-restricted + no egress) — tolerated here.
- await DownloadAndVerifyAsync(await ard.FetchEpisodeDetailAsync(full),
+ await DownloadAndVerifyAsync(await ard.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken),
tolerateGeoBlock: string.IsNullOrWhiteSpace(TestProxy));
}
@@ -60,9 +60,9 @@ await DownloadAndVerifyAsync(await ard.FetchEpisodeDetailAsync(full),
public async Task Downloads_a_full_HeuteShow_episode_from_ZDF()
{
var zdf = new ZdfCatalogClient(Http);
- var full = (await zdf.SearchEpisodesAsync("Heute Show"))
+ var full = (await zdf.SearchEpisodesAsync("Heute Show", TestContext.Current.CancellationToken))
.First(e => e.Title.Contains("heute-show vom", StringComparison.OrdinalIgnoreCase));
- await DownloadAndVerifyAsync(await zdf.FetchEpisodeDetailAsync(full));
+ await DownloadAndVerifyAsync(await zdf.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken));
}
// ── shared: run the real provider, assert a genuine MP4 landed, then clean up ──
diff --git a/tests/Live.Tests/FullEpisodeFetchTests.cs b/tests/Live.Tests/FullEpisodeFetchTests.cs
index 6ce73e5..bce3adb 100644
--- a/tests/Live.Tests/FullEpisodeFetchTests.cs
+++ b/tests/Live.Tests/FullEpisodeFetchTests.cs
@@ -21,12 +21,12 @@ static FullEpisodeFetchTests() =>
public async Task Fetches_a_full_Extra3_episode_from_ARD()
{
var ard = new ArdCatalogClient(Http);
- var show = await ard.FindShowAsync("Extra 3");
+ var show = await ard.FindShowAsync("Extra 3", ct: TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
- var episodes = await ard.GetFullEpisodesAsync(show!);
+ var episodes = await ard.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken);
var full = episodes.First(e => e.Title.Contains("extra 3 vom", StringComparison.OrdinalIgnoreCase));
- var detail = await ard.FetchEpisodeDetailAsync(full);
+ var detail = await ard.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken);
detail.ShouldNotBeNull();
detail!.Title.ShouldContain("extra 3 vom", Case.Insensitive);
@@ -45,12 +45,12 @@ public async Task Fetches_a_full_Extra3_episode_from_ARD()
public async Task Fetches_a_full_BieneMaja_episode_from_KiKA()
{
var ard = new ArdCatalogClient(Http);
- var show = await ard.FindShowAsync("Biene Maja", client: "kika");
+ var show = await ard.FindShowAsync("Biene Maja", client: "kika", TestContext.Current.CancellationToken);
show.ShouldNotBeNull();
- var episodes = await ard.GetFullEpisodesAsync(show!);
+ var episodes = await ard.GetFullEpisodesAsync(show!, TestContext.Current.CancellationToken);
var full = episodes.First();
- var detail = await ard.FetchEpisodeDetailAsync(full);
+ var detail = await ard.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken);
detail.ShouldNotBeNull();
detail!.Title.ShouldNotBeNullOrWhiteSpace();
@@ -66,10 +66,10 @@ public async Task Fetches_a_full_BieneMaja_episode_from_KiKA()
public async Task Fetches_a_full_HeuteShow_episode_from_ZDF()
{
var zdf = new ZdfCatalogClient(Http);
- var episodes = await zdf.SearchEpisodesAsync("Heute Show");
+ var episodes = await zdf.SearchEpisodesAsync("Heute Show", TestContext.Current.CancellationToken);
var full = episodes.First(e => e.Title.Contains("heute-show vom", StringComparison.OrdinalIgnoreCase));
- var detail = await zdf.FetchEpisodeDetailAsync(full);
+ var detail = await zdf.FetchEpisodeDetailAsync(full, TestContext.Current.CancellationToken);
detail.ShouldNotBeNull();
detail!.Title.ShouldContain("heute-show vom", Case.Insensitive);
diff --git a/tests/Live.Tests/Krautwatch.Live.Tests.csproj b/tests/Live.Tests/Krautwatch.Live.Tests.csproj
index 191fa07..b6a0498 100644
--- a/tests/Live.Tests/Krautwatch.Live.Tests.csproj
+++ b/tests/Live.Tests/Krautwatch.Live.Tests.csproj
@@ -1,5 +1,10 @@
+
+
+ Exe
+
+
@@ -7,7 +12,7 @@
-
+
diff --git a/tests/Live.Tests/ZdfLiveTests.cs b/tests/Live.Tests/ZdfLiveTests.cs
index c37b224..462d485 100644
--- a/tests/Live.Tests/ZdfLiveTests.cs
+++ b/tests/Live.Tests/ZdfLiveTests.cs
@@ -21,7 +21,7 @@ static ZdfLiveTests() =>
[Fact]
public async Task Search_finds_HeuteShow_episodes()
{
- var episodes = await Client.SearchEpisodesAsync("Heute Show");
+ var episodes = await Client.SearchEpisodesAsync("Heute Show", TestContext.Current.CancellationToken);
episodes.ShouldNotBeEmpty();
episodes.ShouldContain(e => e.Title.Contains("heute-show vom", StringComparison.OrdinalIgnoreCase));
@@ -31,10 +31,10 @@ public async Task Search_finds_HeuteShow_episodes()
[Fact]
public async Task Resolves_a_HeuteShow_progressive_MP4()
{
- var episodes = await Client.SearchEpisodesAsync("Heute Show");
+ var episodes = await Client.SearchEpisodesAsync("Heute Show", TestContext.Current.CancellationToken);
var episode = episodes.First(e => e.Title.Contains("heute-show vom", StringComparison.OrdinalIgnoreCase));
- var stream = await Client.ResolveBestMp4Async(episode.Canonical);
+ var stream = await Client.ResolveBestMp4Async(episode.Canonical, TestContext.Current.CancellationToken);
stream.ShouldNotBeNull();
stream!.MimeType.ShouldContain("mp4");
@@ -47,26 +47,26 @@ public async Task Downloads_a_HeuteShow_episode()
{
// Real download of a real ZDF stream, bounded to ~5 MB so it's fast + small.
// The production Downloader agent streams the whole file; this proves the pipeline.
- var episodes = await Client.SearchEpisodesAsync("Heute Show");
+ var episodes = await Client.SearchEpisodesAsync("Heute Show", TestContext.Current.CancellationToken);
var episode = episodes.First(e => e.Title.Contains("heute-show vom", StringComparison.OrdinalIgnoreCase));
- var stream = await Client.ResolveBestMp4Async(episode.Canonical);
+ var stream = await Client.ResolveBestMp4Async(episode.Canonical, TestContext.Current.CancellationToken);
stream.ShouldNotBeNull();
var path = Path.Combine(Path.GetTempPath(), $"krautwatch-heuteshow-{Guid.NewGuid():N}.mp4");
try
{
const int cap = 5 * 1024 * 1024;
- using var resp = await Http.GetAsync(stream!.Url, HttpCompletionOption.ResponseHeadersRead);
+ using var resp = await Http.GetAsync(stream!.Url, HttpCompletionOption.ResponseHeadersRead, TestContext.Current.CancellationToken);
resp.EnsureSuccessStatusCode();
- await using (var source = await resp.Content.ReadAsStreamAsync())
+ await using (var source = await resp.Content.ReadAsStreamAsync(TestContext.Current.CancellationToken))
await using (var file = File.Create(path))
{
var buffer = new byte[81920];
long total = 0; int read;
- while (total < cap && (read = await source.ReadAsync(buffer)) > 0)
+ while (total < cap && (read = await source.ReadAsync(buffer, TestContext.Current.CancellationToken)) > 0)
{
- await file.WriteAsync(buffer.AsMemory(0, read));
+ await file.WriteAsync(buffer.AsMemory(0, read), TestContext.Current.CancellationToken);
total += read;
}
}