fix: do not throw UpdateException on SHOREBIRD_NO_UPDATE (#334)

`ShorebirdUpdater.update()` previously threw `UpdateException: No update
(noUpdate)` whenever the patch check returned no available update, even
though that is a successful outcome of calling `update()` — the app is
already running the latest patch.

The wrapper only returned normally on `SHOREBIRD_UPDATE_INSTALLED`;
every other status (including `SHOREBIRD_NO_UPDATE`, value 0) fell
through to the generic `throw UpdateException(...)` path, which is
why callers saw `UpdateException: No update (noUpdate)` in their
exception telemetry.

Treat `SHOREBIRD_NO_UPDATE` as a successful return alongside
`SHOREBIRD_UPDATE_INSTALLED`. No FFI change, safe against any engine
version (status code 0 is stable).

Fixes shorebirdtech/shorebird#3681
This commit is contained in:
Eric Seidel
2026-04-07 18:05:53 -07:00
committed by GitHub
parent adacb4190c
commit b8987364e3
2 changed files with 12 additions and 18 deletions
@@ -117,7 +117,14 @@ class ShorebirdUpdaterImpl implements ShorebirdUpdater {
final status = result.ref.status;
if (status == SHOREBIRD_UPDATE_INSTALLED) return;
// SHOREBIRD_UPDATE_INSTALLED is the success case. SHOREBIRD_NO_UPDATE
// (the app is already up to date) is also a successful outcome of a call
// to update() and must not throw — previously it surfaced as a confusing
// `UpdateException: No update (noUpdate)` in customer telemetry.
if (status == SHOREBIRD_UPDATE_INSTALLED ||
status == SHOREBIRD_NO_UPDATE) {
return;
}
final reason = status.toFailureReason();
final message = result.ref.message != nullptr
@@ -402,23 +402,10 @@ void main() {
shorebirdUpdater = ShorebirdUpdaterImpl(updater: updater, run: run);
});
test('throws $UpdateException', () async {
await expectLater(
shorebirdUpdater.update,
throwsA(
isA<UpdateException>()
.having(
(e) => e.message,
'message',
'oops',
)
.having(
(e) => e.reason,
'reason',
UpdateFailureReason.noUpdate,
),
),
);
test('returns normally and does not throw', () async {
// SHOREBIRD_NO_UPDATE is a successful outcome of update() — the app
// is already running the latest patch. It must not throw.
await expectLater(shorebirdUpdater.update(), completes);
verify(updater.update).called(1);
verify(() => updater.freeUpdateResult(any())).called(1);
});