Skip to content

Fix error where update notice checked prerelease versions incorrectly and improperly references cached test output#541

Merged
nmetulev merged 6 commits into
mainfrom
zt/438-prerelease-update-bug
May 22, 2026
Merged

Fix error where update notice checked prerelease versions incorrectly and improperly references cached test output#541
nmetulev merged 6 commits into
mainfrom
zt/438-prerelease-update-bug

Conversation

@zateutsch

Copy link
Copy Markdown
Contributor

fixes #538

Changes:

  • Added function to check if a version is prerelease, and to only consider core version when deciding whether or not show update check
  • Added check to ensure cached versioning info hasn't been malformed by tests
  • Updated corresponding tests

@zateutsch zateutsch requested review from Copilot and nmetulev May 19, 2026 18:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes the update-notification logic when running prerelease builds and adds a safety check against malformed/test-artifact cache content.

Changes:

  • Compare cached “latest” vs an “effective” current version for prerelease builds (core version only).
  • Detect and purge “unreasonable” cached versions likely produced by tests.
  • Update and extend unit tests for the new version parsing/sanity logic.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs Adds prerelease/core-version helpers and purges unreasonable cached versions before notifying.
src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationServiceTests.cs Updates fixtures away from extreme versions and adds unit tests for new helper methods + cache purge behavior.
src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationGatingTests.cs Updates gating seed version to avoid being purged by new “unreasonable version” logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationServiceTests.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationServiceTests.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs Outdated
@github-actions

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 31.26 MB 31.26 MB 📈 +0.5 KB (+0.00%)
CLI (x64) 31.61 MB 31.61 MB 📈 +0.5 KB (+0.00%)
MSIX (ARM64) 13.13 MB 13.13 MB 📈 +0.2 KB (+0.00%)
MSIX (x64) 13.96 MB 13.96 MB 📈 +1.2 KB (+0.01%)
NPM Package 27.34 MB 27.34 MB 📈 +1.1 KB (+0.00%)
NuGet Package 27.43 MB 27.43 MB 📈 +2.5 KB (+0.01%)
VS Code Extension 20.15 MB 20.15 MB 📈 +1.3 KB (+0.01%)

Test Results

1010 passed, 1 skipped out of 1011 tests in 427.4s (+17 tests, -38.0s vs. baseline)

Test Coverage

16.7% line coverage, 34.8% branch coverage · ✅ no change vs. baseline

CLI Startup Time

42ms median (x64, winapp --version) · ✅ no change vs. baseline


Updated 2026-05-21 15:23:32 UTC · commit edbc197 · workflow run

@zateutsch

Copy link
Copy Markdown
Contributor Author

@copilot apply changes based on the comments in this thread

Copilot AI commented May 19, 2026

Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments in this thread

Implemented the requested fixes in 6f9b769: prerelease/stable comparison now uses full SemVer ordering (so stable of same core is treated as newer than prerelease), unreasonable cache-version detection is now relative to current major (with v/V prefix handling), brittle hardcoded newer-version tests were made dynamic, and new end-to-end CheckAndNotify prerelease scenarios were added.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/microsoft/winappcli/releases/latest
    • Triggering command: /home/REDACTED/work/winappCli/winappCli/src/winapp-CLI/WinApp.Cli.Tests/bin/Debug/net10.0-windows10.0.19041.0/WinApp.Cli.Tests /home/REDACTED/work/winappCli/winappCli/src/winapp-CLI/WinApp.Cli.Tests/bin/Debug/net10.0-windows10.0.19041.0/WinApp.Cli.Tests --filter FullyQualifiedName~UpdateNotification --server dotnettestcli --dotnet-test-pipe /tmp/d12bddbb220b47e0b8e3eb6de85a18ea (http block)
    • Triggering command: REDACTED, pid is -1 (http block)
    • Triggering command: /home/REDACTED/work/winappCli/winappCli/src/winapp-CLI/WinApp.Cli.Tests/bin/Debug/net10.0-windows10.0.19041.0/WinApp.Cli.Tests /home/REDACTED/work/winappCli/winappCli/src/winapp-CLI/WinApp.Cli.Tests/bin/Debug/net10.0-windows10.0.19041.0/WinApp.Cli.Tests --filter FullyQualifiedName~UpdateNotification --server dotnettestcli --dotnet-test-pipe /tmp/1f921bcbba084b06a5bb0db26e0ba305 (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@nmetulev nmetulev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot PR review — multi-dimensional

Summary: Critical 0 · High 1 · Medium 5 · Low 3

Dimension Status
security ✓ clean
correctness ⚠ 1 high
cli-ux ⚠ 1 low
alternative-solution ⚠ 2 medium
test-coverage ⚠ 4 findings
docs-and-samples ✓ clean
packaging ✓ clean
multi-model (GPT-5.4 cross-check) 1 confirmed, 1 downgraded

Findings at a glance

ID File:lines Domain Summary
H1 Services/UpdateNotificationService.cs:55-62 correctness Cache wipe preserves LastCheck → user gets no update notice for ~24h after a poisoned-cache encounter (regresses the fix)
M1 Tests/UpdateNotificationGatingTests.cs:47-60 test-coverage Setup nulls CI/GITHUB_ACTIONS/TF_BUILD without saving them; leaks across in-process tests
M2 Tests/UpdateNotificationServiceTests.cs:478-499 test-coverage IsUnreasonableVersion has no test for unparseable (corrupt-cache) input
M3 Tests/UpdateNotificationServiceTests.cs:586-602 test-coverage Wipe test doesn't assert LastCheck / LastShownDate outcome
M4 Services/UpdateNotificationService.cs:202-263 alternative-solution GetCoreVersion/TryParseMajorVersion duplicate splitter logic already in TryParseSemVer
M5 Services/UpdateNotificationService.cs:55-67 alternative-solution CheckAndNotify parses each version string 2-3× across consecutive helpers
L1 Services/UpdateNotificationService.cs:186-196 test-coverage / alternative-solution IsPreReleaseVersion is added with 6 unit tests but has zero production callers
L2 Services/UpdateNotificationService.cs:55-62 cli-ux Silent cache mutation has no LogDebug breadcrumb
L3 Tests/UpdateNotificationServiceTests.cs test-coverage No test pinning CurrentVersionProvider throw → swallow behavior

Details

H1 — src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs:55-62 · correctness · multi-model: confirmed

When IsUnreasonableVersion fires on a poisoned cache (e.g. leftover 999.0.0 from a prior test run), CheckAndNotify wipes LatestVersion and LastShownDate but preserves LastCheck. The very next gate at lines 77-80 (!cache.LastCheck.HasValue || (UtcNow - LastCheck).TotalHours >= 24) then short-circuits to false, so the background refresh is skipped.

Net effect: the false notification goes away (good), but the user receives no real update notification for up to 24 hours — silently regressing the fix this PR exists to deliver.

// Line 60 — LastCheck intentionally omitted:
cache = cache with { LatestVersion = "", LastShownDate = "" };

The new test CheckAndNotify_UnreasonableCachedVersion_DiscardsAndNoNotification only asserts no notification on the first call and can't catch the missed refresh because SkipBackgroundRefreshForTesting = true.

Recommendation: Also reset LastCheck:

cache = cache with { LastCheck = null, LatestVersion = "", LastShownDate = "" };

Then extend the test to assert LastCheck is null afterward, and ideally add a second test with SkipBackgroundRefreshForTesting = false + a stubbed GetLatestVersionAsync that verifies the refresh actually runs.

M1 — src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationGatingTests.cs:47-60 · test-coverage

Setup explicitly nulls CI / GITHUB_ACTIONS / TF_BUILD to defeat CIEnvironmentDetectorForTelemetry, but Cleanup only restores WINAPP_CLI_CACHE_DIRECTORY / WINAPP_CLI_CALLER / WINAPP_CLI_UPDATE_CHECK. Contrast UpdateNotificationServiceTests.cs:22-28,67-70 which snapshots and restores all 13 CiVarNames. On CI runners (CI=true), this test class permanently clears those vars for the rest of the test process, silently changing the behavior of subsequent CI-detection tests in the same run.

Recommendation: Mirror the _savedCiVars pattern (or extract it into BaseCommandTests / a shared fixture) so both classes snapshot and restore the same set.

M2 — src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationServiceTests.cs:478-499 · test-coverage

IsUnreasonableVersion returns false when either side fails to parse. The new tests cover normal / high / test-artifact / boundary / above-threshold / v-prefix, but never exercise the realistic "corrupt cache file" branch ("garbage", "", "1", "1.x"). That's exactly the failure mode the guard is supposed to backstop.

Recommendation: Add IsUnreasonableVersion_UnparseableCached_ReturnsFalse and IsUnreasonableVersion_UnparseableCurrent_ReturnsFalse, plus a CheckAndNotify_GarbageCachedVersion_DoesNotNotifyOrThrow integration test.

M3 — src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationServiceTests.cs:586-602 · test-coverage

The new wipe test asserts cache.LatestVersion == "" but does not assert LastShownDate is cleared, and (per H1) does not pin LastCheck behavior. Whichever way H1 is resolved, this test should pin the intended LastCheck outcome so a future refactor can't silently regress it.

Recommendation: Also assert cache.LastShownDate == "" and the intended LastCheck value (null after H1 fix, or preserved if the team prefers to throttle refreshes).

M4 — src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs:202-263 · alternative-solution

GetCoreVersion (202-212) and TryParseMajorVersion (229-240) reimplement the +/- stripping already inside the nested TryParseSemVer local in IsNewerVersion (244-263). Three copies of the same SemVer-decomposition rules in one file; future fixes have to be remembered in all three places.

Recommendation: Promote TryParseSemVer from a nested local to a private static method and implement GetCoreVersion / TryParseMajorVersion in terms of it:

return TryParseSemVer(v, out var core, out _) ? core.Major : 0;

M5 — src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs:55-67 · alternative-solution

CheckAndNotify calls IsUnreasonableVersion(latest, current) then IsNewerVersion(latest, current) back-to-back, parsing each string 2-3 times. Once TryParseSemVer is promoted (M4), parse each string once and pass Version objects to a tiny IsUnreasonable(Version, Version) predicate — or inline it, since it has exactly one caller.

L1 — src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs:186-196 · test-coverage / alternative-solution · multi-model: downgraded from high → low

internal static bool IsPreReleaseVersion(string) is added with six dedicated unit tests but has zero production callers. The PR title says "prerelease versions checked incorrectly", but the actual prerelease behavior is delivered entirely by the unchanged IsNewerVersion/ComparePreRelease path. A future reader will reasonably assume this helper IS the prerelease fix — it isn't.

Recommendation: Delete the method and its six tests. If a future caller actually needs the distinction (e.g. to suppress notifications when on a prerelease against an equal-core stable), add it back together with the production code path that uses it.

L2 — src/winapp-CLI/WinApp.Cli/Services/UpdateNotificationService.cs:55-62 · cli-ux

The cache-wipe branch silently mutates user state on disk with no log entry. A user troubleshooting under --verbose has no breadcrumb. The service already has ILogger<UpdateNotificationService> injected, so the cost is one line:

logger.LogDebug("Discarded unreasonable cached update version {Version}", cache.LatestVersion);

L3 — src/winapp-CLI/WinApp.Cli.Tests/UpdateNotificationServiceTests.cs · test-coverage

The new CurrentVersionProvider test seam is invoked inside CheckAndNotify's silent outer try/catch. No test pins "provider throws → no crash, no cache mutation, no notification".

Recommendation: Add CheckAndNotify_CurrentVersionProviderThrows_DoesNotCrashAndDoesNotNotify.


Coverage notes

  • security: Verified no new Process.Start / HTTP / cert / manifest / regex / deserialization / NoWarn surface. Cache path and atomic-write pattern unchanged. New helpers operate on local strings only.
  • docs-and-samples: No Commands/*.cs changed → cli-schema.json regen not needed. docs/usage.md "Update Checks" section documents the opt-out env var only; no described behavior contradicted by this PR.
  • packaging: Lands uniformly in all distribution channels (native / npm / NuGet / VSC) via the shared CLI binary. No version bump needed (Nerdbank.GitVersioning is height-based).
  • multi-model: GPT-5.4 cross-checked H1 (confirmed) and the original H2 (downgraded to L1). Independently scanned the static _refreshScheduled interlock — no additional critical/high findings.

Blocker: H1 — without also resetting LastCheck, the fix suppresses the false notification but then suppresses real ones too for ~24h.

@nmetulev nmetulev merged commit 64d5e21 into main May 22, 2026
23 checks passed
@nmetulev nmetulev deleted the zt/438-prerelease-update-bug branch May 22, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Update check shows wrong version in debug builds

4 participants