diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8018ad..b75da6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ env: jobs: core-tests: - name: Core build + tests (ubuntu) + name: Core + Execution build + tests (ubuntu) runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -39,13 +39,20 @@ jobs: - name: Restore Core projects run: | dotnet restore src/Snipdeck.Core/Snipdeck.Core.csproj + dotnet restore src/Snipdeck.Execution/Snipdeck.Execution.csproj dotnet restore tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj + dotnet restore tests/Snipdeck.Execution.Tests/Snipdeck.Execution.Tests.csproj - name: Build Core - run: dotnet build src/Snipdeck.Core/Snipdeck.Core.csproj --configuration Release --no-restore + run: | + dotnet build src/Snipdeck.Core/Snipdeck.Core.csproj --configuration Release --no-restore + dotnet build src/Snipdeck.Execution/Snipdeck.Execution.csproj --configuration Release --no-restore - name: Build + run Core tests - run: dotnet test tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-restore --logger "trx;LogFileName=core-tests.trx" --results-directory TestResults + run: dotnet test --project tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-restore --report-xunit-trx --report-xunit-trx-filename core-tests.trx --results-directory TestResults + + - name: Build + run Execution tests + run: dotnet test --project tests/Snipdeck.Execution.Tests/Snipdeck.Execution.Tests.csproj --configuration Release --no-restore --report-xunit-trx --report-xunit-trx-filename execution-tests.trx --results-directory TestResults - name: Upload test results if: always() @@ -80,7 +87,7 @@ jobs: run: dotnet build tools/Snipdeck.Importer/Snipdeck.Importer.csproj --configuration Release --no-restore - name: Build + run importer tests - run: dotnet test tools/Snipdeck.Importer.Tests/Snipdeck.Importer.Tests.csproj --configuration Release --no-restore --logger "trx;LogFileName=importer-tests.trx" --results-directory TestResults + run: dotnet test --project tools/Snipdeck.Importer.Tests/Snipdeck.Importer.Tests.csproj --configuration Release --no-restore --report-xunit-trx --report-xunit-trx-filename importer-tests.trx --results-directory TestResults - name: Upload test results if: always() @@ -113,7 +120,12 @@ jobs: run: dotnet build --configuration Release --no-restore - name: Run Core tests (sanity check on Windows) - run: dotnet test tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-build --logger "trx;LogFileName=core-tests-windows.trx" --results-directory TestResults + run: dotnet test --project tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-build --report-xunit-trx --report-xunit-trx-filename core-tests-windows.trx --results-directory TestResults + + # Execution wraps ConPTY and process launching, so Windows is the platform + # that actually matters for it — the ubuntu run only proves it is portable. + - name: Run Execution tests (ConPTY + process launching are platform-specific) + run: dotnet test --project tests/Snipdeck.Execution.Tests/Snipdeck.Execution.Tests.csproj --configuration Release --no-build --report-xunit-trx --report-xunit-trx-filename execution-tests-windows.trx --results-directory TestResults - name: Upload test results if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85f9fe5..3f4ac2d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,7 +87,7 @@ jobs: run: dotnet restore - name: Run Core tests - run: dotnet test tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-restore + run: dotnet test --project tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj --configuration Release --no-restore - name: Publish Snipdeck.App (win-x64) # No -p:Version here — Nerdbank.GitVersioning stamps the assemblies at diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ad917..36751ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Migrated the test suites to xUnit v3 on Microsoft Testing Platform, replacing + the VSTest stack. Development-only; the shipped app is unaffected. + +### Fixed +- CI now runs the `Snipdeck.Execution` test suite, on both Ubuntu and Windows. + It was built but never executed, so 61 tests were only ever run locally. + ### Changed - Refreshed dependencies to their latest stable releases, including the Windows App SDK (2.4.0), WebView2, the Windows SDK build tools, SQLite, the dependency diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cead4cf..04b12a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,10 +21,17 @@ dotnet build # Build Core only (works on Linux / macOS) dotnet build src/Snipdeck.Core -# Run Core tests -dotnet test tests/Snipdeck.Core.Tests +# Run the tests (Core, Execution, importer) +dotnet test --project tests/Snipdeck.Core.Tests +dotnet test --project tests/Snipdeck.Execution.Tests +dotnet test --project tools/Snipdeck.Importer.Tests ``` +The test projects are **xUnit v3**, which runs on Microsoft Testing Platform +rather than VSTest. The repo-root `global.json` opts `dotnet test` into that +runner, which is why the project is passed as `--project ` rather than as a +bare argument. TRX reports come from `--report-xunit-trx`, not `--logger trx`. + On a non-Windows machine, restoring the `Snipdeck.App` project requires `EnableWindowsTargeting=true`: diff --git a/Directory.Packages.props b/Directory.Packages.props index d8e6811..a959e19 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,12 +42,14 @@ - + - - - - + + diff --git a/README.md b/README.md index d7b13b3..ae1e47d 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,9 @@ Requirements: # Restore + build everything dotnet build -# Run Core unit tests -dotnet test tests/Snipdeck.Core.Tests +# Run the unit tests +dotnet test --project tests/Snipdeck.Core.Tests +dotnet test --project tests/Snipdeck.Execution.Tests ``` The `Snipdeck.Core` project targets `net10.0` and is fully portable, so @@ -137,10 +138,10 @@ Snipdeck is built on the work of these open-source projects, with thanks to their authors and maintainers: - [.NET Community Toolkit](https://github.com/CommunityToolkit/dotnet) — MVVM source generators and helpers (MIT) -- [Coverlet](https://github.com/coverlet-coverage/coverlet) — code-coverage collection (MIT) - [H.NotifyIcon](https://github.com/HavenDV/H.NotifyIcon) — system-tray icon and menu (MIT) - [Jdenticon](https://github.com/dmester/jdenticon-net) — identicons for CLIs without a custom icon (MIT) - [Markdig](https://github.com/xoofx/markdig) — Markdown rendering for Snip descriptions (BSD-2-Clause) +- [Microsoft Testing Platform](https://github.com/microsoft/testfx) — runs the test suites and collects code coverage (MIT) - [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime) — dependency injection (MIT) - [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) — git-derived versioning (MIT) - [Porta.Pty](https://github.com/tomlm/Porta.Pty) — cross-platform pseudo-terminal (ConPTY) for running commands (MIT) diff --git a/global.json b/global.json new file mode 100644 index 0000000..3140116 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} diff --git a/src/Snipdeck.Core/ViewModels/SettingsViewModel.cs b/src/Snipdeck.Core/ViewModels/SettingsViewModel.cs index c90a011..100ecd4 100644 --- a/src/Snipdeck.Core/ViewModels/SettingsViewModel.cs +++ b/src/Snipdeck.Core/ViewModels/SettingsViewModel.cs @@ -117,7 +117,7 @@ public SettingsViewModel( new("Spectre.Console", "Console UI for the SnipCommand import tool.", new Uri("https://github.com/spectreconsole/spectre.console"), "MIT"), new("Nerdbank.GitVersioning", "Derives the version from git history.", new Uri("https://github.com/dotnet/Nerdbank.GitVersioning"), "MIT"), new("xUnit", "Unit-testing framework.", new Uri("https://github.com/xunit/xunit"), "Apache-2.0"), - new("Coverlet", "Code-coverage collection for tests.", new Uri("https://github.com/coverlet-coverage/coverlet"), "MIT"), + new("Microsoft Testing Platform", "Runs the test suites and collects code coverage.", new Uri("https://github.com/microsoft/testfx"), "MIT"), ]; [ObservableProperty] diff --git a/tests/Snipdeck.Core.Tests/Engine/SubstitutionEngineTests.cs b/tests/Snipdeck.Core.Tests/Engine/SubstitutionEngineTests.cs index cb48a52..9d1c918 100644 --- a/tests/Snipdeck.Core.Tests/Engine/SubstitutionEngineTests.cs +++ b/tests/Snipdeck.Core.Tests/Engine/SubstitutionEngineTests.cs @@ -81,8 +81,8 @@ public void Repeated_missing_token_is_listed_once_only() new Dictionary()); Assert.Equal("{missing} and {missing} again", result.Text); - Assert.Single(result.UnresolvedTokens); - Assert.Equal("missing", result.UnresolvedTokens[0]); + var token = Assert.Single(result.UnresolvedTokens); + Assert.Equal("missing", token); } [Fact] diff --git a/tests/Snipdeck.Core.Tests/Services/BackupServiceTests.cs b/tests/Snipdeck.Core.Tests/Services/BackupServiceTests.cs index 0f6d1e7..5fc8160 100644 --- a/tests/Snipdeck.Core.Tests/Services/BackupServiceTests.cs +++ b/tests/Snipdeck.Core.Tests/Services/BackupServiceTests.cs @@ -54,7 +54,7 @@ public async Task CreateBackupAsync_returns_null_when_source_missing() var clock = new FakeClock(new DateTimeOffset(2026, 5, 29, 12, 0, 0, TimeSpan.Zero)); var service = BuildService(clock); - var info = await service.CreateBackupAsync(); + var info = await service.CreateBackupAsync(TestContext.Current.CancellationToken); Assert.Null(info); Assert.False(Directory.Exists(_backupDirectory) && Directory.EnumerateFiles(_backupDirectory).Any()); @@ -67,12 +67,12 @@ public async Task CreateBackupAsync_copies_source_to_timestamped_destination() var clock = new FakeClock(new DateTimeOffset(2026, 5, 29, 12, 34, 56, 789, TimeSpan.Zero)); var service = BuildService(clock); - var info = await service.CreateBackupAsync(); + var info = await service.CreateBackupAsync(TestContext.Current.CancellationToken); Assert.NotNull(info); Assert.True(File.Exists(info!.FilePath)); Assert.EndsWith("snipstore_20260529_123456789.json", info.FilePath); - Assert.Equal("hello", await File.ReadAllTextAsync(info.FilePath)); + Assert.Equal("hello", await File.ReadAllTextAsync(info.FilePath, TestContext.Current.CancellationToken)); Assert.Equal(5, info.SizeBytes); Assert.Equal(clock.UtcNow, info.CreatedAtUtc); } @@ -84,7 +84,7 @@ public async Task CreateBackupAsync_creates_missing_backup_directory() Assert.False(Directory.Exists(_backupDirectory)); var clock = new FakeClock(new DateTimeOffset(2026, 5, 29, 12, 0, 0, TimeSpan.Zero)); - await BuildService(clock).CreateBackupAsync(); + await BuildService(clock).CreateBackupAsync(TestContext.Current.CancellationToken); Assert.True(Directory.Exists(_backupDirectory)); } @@ -97,9 +97,9 @@ public async Task CreateBackupAsync_appends_collision_suffix_when_clock_repeats( var clock = new FakeClock(fixedTime); var service = BuildService(clock); - var first = await service.CreateBackupAsync(); - var second = await service.CreateBackupAsync(); - var third = await service.CreateBackupAsync(); + var first = await service.CreateBackupAsync(TestContext.Current.CancellationToken); + var second = await service.CreateBackupAsync(TestContext.Current.CancellationToken); + var third = await service.CreateBackupAsync(TestContext.Current.CancellationToken); Assert.NotEqual(first!.FilePath, second!.FilePath); Assert.NotEqual(second.FilePath, third!.FilePath); @@ -117,7 +117,7 @@ public async Task CreateBackupAsync_prunes_backups_beyond_retention() for (var i = 0; i < 5; i++) { - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(1)); } @@ -143,14 +143,14 @@ public async Task CreateBackupAsync_reads_retention_lazily_from_provider() // Fill up under the initial retention of 5. for (var i = 0; i < 5; i++) { - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(1)); } Assert.Equal(5, Directory.GetFiles(_backupDirectory, "snipstore_*.json").Length); // Tighten retention and back up again: the next prune honours the new value. retention = 2; - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); Assert.Equal(2, Directory.GetFiles(_backupDirectory, "snipstore_*.json").Length); } @@ -164,7 +164,7 @@ public async Task CreateBackupAsync_clamps_non_positive_provider_value_to_one() for (var i = 0; i < 4; i++) { - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(1)); } @@ -186,19 +186,19 @@ public async Task PruneStep_does_not_touch_unrelated_files_in_backup_directory() WriteSource(); Directory.CreateDirectory(_backupDirectory); var sibling = Path.Combine(_backupDirectory, "not-a-backup.txt"); - await File.WriteAllTextAsync(sibling, "untouched"); + await File.WriteAllTextAsync(sibling, "untouched", TestContext.Current.CancellationToken); var clock = new FakeClock(new DateTimeOffset(2026, 5, 29, 12, 0, 0, TimeSpan.Zero)); var service = BuildService(clock, retention: 1); for (var i = 0; i < 5; i++) { - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(1)); } Assert.True(File.Exists(sibling)); - Assert.Equal("untouched", await File.ReadAllTextAsync(sibling)); + Assert.Equal("untouched", await File.ReadAllTextAsync(sibling, TestContext.Current.CancellationToken)); } [Fact] @@ -208,13 +208,13 @@ public async Task ListBackupsAsync_returns_newest_first_with_parsed_timestamps() var clock = new FakeClock(new DateTimeOffset(2026, 5, 29, 12, 0, 0, TimeSpan.Zero)); var service = BuildService(clock); - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(5)); - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); clock.Advance(TimeSpan.FromSeconds(5)); - await service.CreateBackupAsync(); + await service.CreateBackupAsync(TestContext.Current.CancellationToken); - var list = await service.ListBackupsAsync(); + var list = await service.ListBackupsAsync(TestContext.Current.CancellationToken); Assert.Equal(3, list.Count); Assert.True(list[0].CreatedAtUtc > list[1].CreatedAtUtc); @@ -227,7 +227,7 @@ public async Task ListBackupsAsync_returns_empty_when_directory_missing() var clock = new FakeClock(DateTimeOffset.UtcNow); var service = BuildService(clock); - var list = await service.ListBackupsAsync(); + var list = await service.ListBackupsAsync(TestContext.Current.CancellationToken); Assert.Empty(list); } diff --git a/tests/Snipdeck.Core.Tests/Services/ExamplesSeedTests.cs b/tests/Snipdeck.Core.Tests/Services/ExamplesSeedTests.cs index 3a7fa58..eb72849 100644 --- a/tests/Snipdeck.Core.Tests/Services/ExamplesSeedTests.cs +++ b/tests/Snipdeck.Core.Tests/Services/ExamplesSeedTests.cs @@ -95,8 +95,8 @@ public async Task Seed_round_trips_through_the_json_store() var store = new JsonSnipStore(Path.Combine(tempDir, "store.json")); var original = ExamplesSeed.Build(); - await store.SaveAsync(original); - var loaded = await store.LoadAsync(); + await store.SaveAsync(original, TestContext.Current.CancellationToken); + var loaded = await store.LoadAsync(TestContext.Current.CancellationToken); Assert.Equal(original.Clis.Count, loaded.Clis.Count); Assert.Equal(original.Snips.Count, loaded.Snips.Count); diff --git a/tests/Snipdeck.Core.Tests/Services/IconAssetStorageTests.cs b/tests/Snipdeck.Core.Tests/Services/IconAssetStorageTests.cs index c26ee68..45247e1 100644 --- a/tests/Snipdeck.Core.Tests/Services/IconAssetStorageTests.cs +++ b/tests/Snipdeck.Core.Tests/Services/IconAssetStorageTests.cs @@ -51,7 +51,7 @@ public async Task DeleteIconAsync_ignores_parent_directory_traversal() { // A file outside the icons directory that a malformed IconRef tries to reach. var victim = Path.Combine(_baseDirectory, "important.txt"); - await File.WriteAllTextAsync(victim, "keep me"); + await File.WriteAllTextAsync(victim, "keep me", TestContext.Current.CancellationToken); await _storage.DeleteIconAsync("../important.txt"); @@ -62,7 +62,7 @@ public async Task DeleteIconAsync_ignores_parent_directory_traversal() public async Task DeleteIconAsync_ignores_absolute_paths() { var victim = Path.Combine(_baseDirectory, "outside.txt"); - await File.WriteAllTextAsync(victim, "keep me"); + await File.WriteAllTextAsync(victim, "keep me", TestContext.Current.CancellationToken); // Path.Combine(base, absolute) discards base — an unguarded delete would hit this. await _storage.DeleteIconAsync(victim); diff --git a/tests/Snipdeck.Core.Tests/Services/JsonSettingsStoreTests.cs b/tests/Snipdeck.Core.Tests/Services/JsonSettingsStoreTests.cs index 3486b78..68bdbf1 100644 --- a/tests/Snipdeck.Core.Tests/Services/JsonSettingsStoreTests.cs +++ b/tests/Snipdeck.Core.Tests/Services/JsonSettingsStoreTests.cs @@ -30,7 +30,7 @@ public async Task LoadAsync_returns_defaults_when_file_missing() { var store = new JsonSettingsStore(PathIn("settings.json")); - var config = await store.LoadAsync(); + var config = await store.LoadAsync(TestContext.Current.CancellationToken); Assert.Equal(AppConfig.CurrentSchemaVersion, config.SchemaVersion); Assert.Null(config.StoragePath); @@ -58,9 +58,9 @@ await store.SaveAsync(new AppConfig Modifiers = HotkeyModifiers.Control | HotkeyModifiers.Shift, Key = "Space", }, - }); + }, TestContext.Current.CancellationToken); - var loaded = await store.LoadAsync(); + var loaded = await store.LoadAsync(TestContext.Current.CancellationToken); Assert.Equal("/data/store", loaded.StoragePath); Assert.Equal("/data/backups", loaded.BackupDirectory); @@ -76,7 +76,7 @@ public async Task SaveAsync_creates_missing_parent_directory() var nested = PathIn("a/b/settings.json"); var store = new JsonSettingsStore(nested); - await store.SaveAsync(new AppConfig()); + await store.SaveAsync(new AppConfig(), TestContext.Current.CancellationToken); Assert.True(File.Exists(nested)); } @@ -87,7 +87,7 @@ public async Task SaveAsync_does_not_leave_tmp_file_behind_on_success() var path = PathIn("settings.json"); var store = new JsonSettingsStore(path); - await store.SaveAsync(new AppConfig()); + await store.SaveAsync(new AppConfig(), TestContext.Current.CancellationToken); Assert.False(File.Exists(path + ".tmp")); Assert.True(File.Exists(path)); @@ -100,21 +100,21 @@ public async Task LoadAsync_throws_when_schema_version_is_newer_than_supported() var futureJson = $$""" { "schemaVersion": {{AppConfig.CurrentSchemaVersion + 1}} } """; - await File.WriteAllTextAsync(path, futureJson); + await File.WriteAllTextAsync(path, futureJson, TestContext.Current.CancellationToken); var store = new JsonSettingsStore(path); - await Assert.ThrowsAsync(() => store.LoadAsync()); + await Assert.ThrowsAsync(() => store.LoadAsync(TestContext.Current.CancellationToken)); } [Fact] public async Task LoadAsync_repairs_a_missing_hotkey_with_the_default() { var path = PathIn("settings.json"); - await File.WriteAllTextAsync(path, "{}"); + await File.WriteAllTextAsync(path, "{}", TestContext.Current.CancellationToken); var store = new JsonSettingsStore(path); - var loaded = await store.LoadAsync(); + var loaded = await store.LoadAsync(TestContext.Current.CancellationToken); Assert.NotNull(loaded.Hotkey); Assert.Equal(HotkeyModifiers.Control | HotkeyModifiers.Alt, loaded.Hotkey.Modifiers); @@ -126,7 +126,7 @@ public async Task SaveAsync_throws_on_null_config() { var store = new JsonSettingsStore(PathIn("settings.json")); - await Assert.ThrowsAsync(() => store.SaveAsync(null!)); + await Assert.ThrowsAsync(() => store.SaveAsync(null!, TestContext.Current.CancellationToken)); } } } diff --git a/tests/Snipdeck.Core.Tests/Services/JsonSnipStoreTests.cs b/tests/Snipdeck.Core.Tests/Services/JsonSnipStoreTests.cs index 39aa2fa..b964596 100644 --- a/tests/Snipdeck.Core.Tests/Services/JsonSnipStoreTests.cs +++ b/tests/Snipdeck.Core.Tests/Services/JsonSnipStoreTests.cs @@ -38,7 +38,7 @@ public async Task LoadAsync_returns_empty_document_when_file_missing() { var store = new JsonSnipStore(PathIn("store.json")); - var document = await store.LoadAsync(); + var document = await store.LoadAsync(TestContext.Current.CancellationToken); Assert.Equal(SnipStoreDocument.CurrentSchemaVersion, document.SchemaVersion); Assert.Empty(document.Clis); @@ -88,8 +88,8 @@ public async Task SaveAsync_then_LoadAsync_round_trips_full_document() }, }; - await store.SaveAsync(original); - var loaded = await store.LoadAsync(); + await store.SaveAsync(original, TestContext.Current.CancellationToken); + var loaded = await store.LoadAsync(TestContext.Current.CancellationToken); Assert.Equal(SnipStoreDocument.CurrentSchemaVersion, loaded.SchemaVersion); @@ -123,7 +123,7 @@ public async Task SaveAsync_creates_missing_parent_directory() var nested = PathIn("a/b/c/store.json"); var store = new JsonSnipStore(nested); - await store.SaveAsync(new SnipStoreDocument()); + await store.SaveAsync(new SnipStoreDocument(), TestContext.Current.CancellationToken); Assert.True(File.Exists(nested)); } @@ -134,7 +134,7 @@ public async Task SaveAsync_does_not_leave_tmp_file_behind_on_success() var path = PathIn("store.json"); var store = new JsonSnipStore(path); - await store.SaveAsync(new SnipStoreDocument()); + await store.SaveAsync(new SnipStoreDocument(), TestContext.Current.CancellationToken); Assert.False(File.Exists(path + ".tmp")); Assert.True(File.Exists(path)); @@ -149,14 +149,14 @@ public async Task SaveAsync_overwrites_existing_file() await store.SaveAsync(new SnipStoreDocument { Clis = { new Cli { Name = "first" } }, - }); + }, TestContext.Current.CancellationToken); await store.SaveAsync(new SnipStoreDocument { Clis = { new Cli { Name = "second" } }, - }); + }, TestContext.Current.CancellationToken); - var loaded = await store.LoadAsync(); + var loaded = await store.LoadAsync(TestContext.Current.CancellationToken); var cli = Assert.Single(loaded.Clis); Assert.Equal("second", cli.Name); } @@ -172,11 +172,11 @@ public async Task LoadAsync_throws_when_schema_version_is_newer_than_supported() "snips": [] } """; - await File.WriteAllTextAsync(path, futureJson); + await File.WriteAllTextAsync(path, futureJson, TestContext.Current.CancellationToken); var store = new JsonSnipStore(path); - await Assert.ThrowsAsync(() => store.LoadAsync()); + await Assert.ThrowsAsync(() => store.LoadAsync(TestContext.Current.CancellationToken)); } [Fact] @@ -184,7 +184,7 @@ public async Task SaveAsync_throws_on_null_document() { var store = new JsonSnipStore(PathIn("store.json")); - await Assert.ThrowsAsync(() => store.SaveAsync(null!)); + await Assert.ThrowsAsync(() => store.SaveAsync(null!, TestContext.Current.CancellationToken)); } [Fact] @@ -200,7 +200,7 @@ public async Task Concurrent_saves_leave_a_consistent_well_formed_file() await Task.WhenAll(tasks); - var loaded = await store.LoadAsync(); + var loaded = await store.LoadAsync(TestContext.Current.CancellationToken); var cli = Assert.Single(loaded.Clis); Assert.StartsWith("cli-", cli.Name); Assert.False(File.Exists(path + ".tmp")); @@ -224,9 +224,9 @@ await store.SaveAsync(new SnipStoreDocument }, }, }, - }); + }, TestContext.Current.CancellationToken); - var json = await File.ReadAllTextAsync(path); + var json = await File.ReadAllTextAsync(path, TestContext.Current.CancellationToken); Assert.Contains("\"type\": \"choice\"", json); } } diff --git a/tests/Snipdeck.Core.Tests/Services/SnipFilterTests.cs b/tests/Snipdeck.Core.Tests/Services/SnipFilterTests.cs index bb0eb04..695718a 100644 --- a/tests/Snipdeck.Core.Tests/Services/SnipFilterTests.cs +++ b/tests/Snipdeck.Core.Tests/Services/SnipFilterTests.cs @@ -72,8 +72,8 @@ public void Search_matches_command_template() var result = SnipFilter.Apply(snips, "users", null).ToList(); - Assert.Single(result); - Assert.Equal("b", result[0].Title); + var match = Assert.Single(result); + Assert.Equal("b", match.Title); } [Fact] @@ -87,8 +87,8 @@ public void Search_matches_tag() var result = SnipFilter.Apply(snips, "deploy", null).ToList(); - Assert.Single(result); - Assert.Equal("a", result[0].Title); + var match = Assert.Single(result); + Assert.Equal("a", match.Title); } [Fact] @@ -120,8 +120,8 @@ public void Search_and_tag_apply_together_as_an_AND_filter() var result = SnipFilter.Apply(snips, "logs", "read").ToList(); - Assert.Single(result); - Assert.Equal("Read logs", result[0].Title); + var match = Assert.Single(result); + Assert.Equal("Read logs", match.Title); } [Fact] diff --git a/tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj b/tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj index 9276705..7cbb623 100644 --- a/tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj +++ b/tests/Snipdeck.Core.Tests/Snipdeck.Core.Tests.csproj @@ -2,6 +2,7 @@ net10.0 + Exe false