Merge pull request #484 from willmiao/codex/fix-update-check-in-offline-mode

Fix offline update logging and respect update notification toggle
This commit is contained in:
pixelpaws
2025-09-25 14:54:28 +08:00
committed by GitHub
4 changed files with 130 additions and 7 deletions

View File

@@ -5,12 +5,16 @@ import git
import zipfile
import shutil
import tempfile
from aiohttp import web
import asyncio
from aiohttp import web, ClientError
from typing import Dict, List
from ..services.downloader import get_downloader
logger = logging.getLogger(__name__)
NETWORK_EXCEPTIONS = (ClientError, OSError, asyncio.TimeoutError)
class UpdateRoutes:
"""Routes for handling plugin update checks"""
@@ -63,6 +67,12 @@ class UpdateRoutes:
'nightly': nightly
})
except NETWORK_EXCEPTIONS as e:
logger.warning("Network unavailable during update check: %s", e)
return web.json_response({
'success': False,
'error': 'Network unavailable for update check'
})
except Exception as e:
logger.error(f"Failed to check for updates: {e}", exc_info=True)
return web.json_response({
@@ -283,6 +293,9 @@ class UpdateRoutes:
return version, changelog
except NETWORK_EXCEPTIONS as e:
logger.warning("Unable to reach GitHub for nightly version: %s", e)
return "main", []
except Exception as e:
logger.error(f"Error fetching nightly version: {e}", exc_info=True)
return "main", []
@@ -448,6 +461,9 @@ class UpdateRoutes:
return version, changelog
except NETWORK_EXCEPTIONS as e:
logger.warning("Unable to reach GitHub for release info: %s", e)
return "v0.0.0", []
except Exception as e:
logger.error(f"Error fetching remote version: {e}", exc_info=True)
return "v0.0.0", []

View File

@@ -82,11 +82,15 @@ export class UpdateService {
}
}
async checkForUpdates() {
async checkForUpdates({ force = false } = {}) {
if (!force && !this.updateNotificationsEnabled) {
return;
}
// Check if we should perform an update check
const now = Date.now();
const forceCheck = this.lastCheckTime === 0;
const forceCheck = force || this.lastCheckTime === 0;
if (!forceCheck && now - this.lastCheckTime < this.updateCheckInterval) {
// If we already have update info, just update the UI
if (this.updateAvailable) {
@@ -94,7 +98,7 @@ export class UpdateService {
}
return;
}
try {
// Call backend API to check for updates with nightly flag
const response = await fetch(`/api/lm/check-updates?nightly=${this.nightlyMode}`);
@@ -435,8 +439,7 @@ export class UpdateService {
}
async manualCheckForUpdates() {
this.lastCheckTime = 0; // Reset last check time to force check
await this.checkForUpdates();
await this.checkForUpdates({ force: true });
// Ensure badge visibility is updated after manual check
this.updateBadgeVisibility();
}

View File

@@ -0,0 +1,45 @@
import { describe, beforeEach, afterEach, expect, it, vi } from 'vitest';
import { UpdateService } from '../../../static/js/managers/UpdateService.js';
function createFetchResponse(payload) {
return {
json: vi.fn().mockResolvedValue(payload)
};
}
describe('UpdateService passive checks', () => {
let service;
let fetchMock;
beforeEach(() => {
fetchMock = vi.fn().mockResolvedValue(createFetchResponse({
success: true,
current_version: 'v1.0.0',
latest_version: 'v1.0.0',
git_info: { short_hash: 'abc123' }
}));
global.fetch = fetchMock;
service = new UpdateService();
service.updateNotificationsEnabled = false;
service.lastCheckTime = 0;
service.nightlyMode = false;
});
afterEach(() => {
delete global.fetch;
});
it('skips passive update checks when notifications are disabled', async () => {
await service.checkForUpdates();
expect(fetchMock).not.toHaveBeenCalled();
});
it('allows manual checks even when notifications are disabled', async () => {
await service.checkForUpdates({ force: true });
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock).toHaveBeenCalledWith('/api/lm/check-updates?nightly=false');
});
});

View File

@@ -0,0 +1,59 @@
import logging
from aiohttp import ClientError
import pytest
from py.routes import update_routes
class OfflineDownloader:
async def make_request(self, *_, **__):
return False, "Cannot connect to host"
class RaisingDownloader:
async def make_request(self, *_, **__):
raise ClientError("offline")
async def _stub_downloader(instance):
return instance
@pytest.mark.asyncio
async def test_get_remote_version_offline_logs_without_traceback(monkeypatch, caplog):
caplog.set_level(logging.WARNING)
monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(OfflineDownloader()))
version, changelog = await update_routes.UpdateRoutes._get_remote_version()
assert version == "v0.0.0"
assert changelog == []
assert "Failed to fetch GitHub release" in caplog.text
assert "Cannot connect to host" in caplog.text
assert "Traceback" not in caplog.text
@pytest.mark.asyncio
async def test_get_remote_version_network_error_logs_warning(monkeypatch, caplog):
caplog.set_level(logging.WARNING)
monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(RaisingDownloader()))
version, changelog = await update_routes.UpdateRoutes._get_remote_version()
assert version == "v0.0.0"
assert changelog == []
assert "Unable to reach GitHub for release info" in caplog.text
assert "Traceback" not in caplog.text
@pytest.mark.asyncio
async def test_get_nightly_version_network_error_logs_warning(monkeypatch, caplog):
caplog.set_level(logging.WARNING)
monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(RaisingDownloader()))
version, changelog = await update_routes.UpdateRoutes._get_nightly_version()
assert version == "main"
assert changelog == []
assert "Unable to reach GitHub for nightly version" in caplog.text
assert "Traceback" not in caplog.text