You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Seamless-Updates - added tests for autoUpdaterService (#10935)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
		| @@ -491,6 +491,7 @@ packages/app-desktop/integration-tests/util/test.js | |||||||
| packages/app-desktop/integration-tests/util/waitForNextOpenPath.js | packages/app-desktop/integration-tests/util/waitForNextOpenPath.js | ||||||
| packages/app-desktop/playwright.config.js | packages/app-desktop/playwright.config.js | ||||||
| packages/app-desktop/plugins/GotoAnything.js | packages/app-desktop/plugins/GotoAnything.js | ||||||
|  | packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js | ||||||
| packages/app-desktop/services/autoUpdater/AutoUpdaterService.js | packages/app-desktop/services/autoUpdater/AutoUpdaterService.js | ||||||
| packages/app-desktop/services/bridge.js | packages/app-desktop/services/bridge.js | ||||||
| packages/app-desktop/services/commands/stateToWhenClauseContext.js | packages/app-desktop/services/commands/stateToWhenClauseContext.js | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -468,6 +468,7 @@ packages/app-desktop/integration-tests/util/test.js | |||||||
| packages/app-desktop/integration-tests/util/waitForNextOpenPath.js | packages/app-desktop/integration-tests/util/waitForNextOpenPath.js | ||||||
| packages/app-desktop/playwright.config.js | packages/app-desktop/playwright.config.js | ||||||
| packages/app-desktop/plugins/GotoAnything.js | packages/app-desktop/plugins/GotoAnything.js | ||||||
|  | packages/app-desktop/services/autoUpdater/AutoUpdaterService.test.js | ||||||
| packages/app-desktop/services/autoUpdater/AutoUpdaterService.js | packages/app-desktop/services/autoUpdater/AutoUpdaterService.js | ||||||
| packages/app-desktop/services/bridge.js | packages/app-desktop/services/bridge.js | ||||||
| packages/app-desktop/services/commands/stateToWhenClauseContext.js | packages/app-desktop/services/commands/stateToWhenClauseContext.js | ||||||
|   | |||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | import { LoggerWrapper } from '@joplin/utils/Logger'; | ||||||
|  | import { releases3 } from '../../utils/checkForUpdatesUtilsTestData'; | ||||||
|  | import AutoUpdaterService from './AutoUpdaterService'; | ||||||
|  | import { BrowserWindow } from 'electron'; | ||||||
|  |  | ||||||
|  | jest.mock('electron', () => ({ | ||||||
|  | 	BrowserWindow: jest.fn(), | ||||||
|  | })); | ||||||
|  |  | ||||||
|  | jest.mock('electron-updater', () => { | ||||||
|  | 	const mockAutoUpdater = { | ||||||
|  | 		setFeedURL: jest.fn(), | ||||||
|  | 		checkForUpdates: jest.fn(), | ||||||
|  | 		on: jest.fn(), | ||||||
|  | 		quitAndInstall: jest.fn(), | ||||||
|  | 	}; | ||||||
|  | 	return { autoUpdater: mockAutoUpdater }; | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | describe('AutoUpdaterService', () => { | ||||||
|  | 	let service: AutoUpdaterService; | ||||||
|  | 	let mockLogger: LoggerWrapper; | ||||||
|  |  | ||||||
|  | 	beforeEach(() => { | ||||||
|  | 		const mockWindow = new BrowserWindow(); | ||||||
|  | 		mockLogger = { info: jest.fn(), error: jest.fn(), debug: jest.fn(), warn: jest.fn() }; | ||||||
|  | 		service = new AutoUpdaterService(mockWindow, mockLogger, false, true); | ||||||
|  | 		global.fetch = jest.fn(() => | ||||||
|  | 			Promise.resolve({ | ||||||
|  | 				ok: true, | ||||||
|  | 				json: () => Promise.resolve(releases3), | ||||||
|  | 			}), | ||||||
|  | 		) as jest.Mock; | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should correctly fetch and process the latest prerelease', async () => { | ||||||
|  | 		const release = await service.fetchLatestRelease(true); | ||||||
|  | 		expect(release).toBeDefined(); | ||||||
|  | 		expect(release.tag_name).toBe('v3.1.3'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should correctly fetch and process the latest release', async () => { | ||||||
|  | 		const release = await service.fetchLatestRelease(false); | ||||||
|  | 		expect(release).toBeDefined(); | ||||||
|  | 		expect(release.tag_name).toBe('v3.1.2'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should return the correct download URL for Windows', async () => { | ||||||
|  | 		const release = await service.fetchLatestRelease(true); | ||||||
|  | 		expect(release).toBeDefined(); | ||||||
|  | 		const url = service.getDownloadUrlForPlatform(release, 'win32'); | ||||||
|  | 		expect(url).toBe('https://github.com/laurent22/joplin/releases/download/v3.1.3/latest.yml'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should return the correct download URL for Mac', async () => { | ||||||
|  | 		const release = await service.fetchLatestRelease(true); | ||||||
|  | 		expect(release).toBeDefined(); | ||||||
|  | 		const url = service.getDownloadUrlForPlatform(release, 'darwin'); | ||||||
|  | 		expect(url).toBe('https://github.com/laurent22/joplin/releases/download/v3.1.3/latest-mac.yml'); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	it('should throw an error for Linux', async () => { | ||||||
|  | 		const release = await service.fetchLatestRelease(true); | ||||||
|  | 		expect(release).toBeDefined(); | ||||||
|  | 		expect(() => service.getDownloadUrlForPlatform(release, 'linux')).toThrow('The AutoUpdaterService does not support the following platform: linux'); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
| @@ -19,10 +19,16 @@ export enum AutoUpdaterEvents { | |||||||
| export const defaultUpdateInterval = 12 * 60 * 60 * 1000; | export const defaultUpdateInterval = 12 * 60 * 60 * 1000; | ||||||
| export const initialUpdateStartup = 5 * 1000; | export const initialUpdateStartup = 5 * 1000; | ||||||
| const releasesLink = 'https://objects.joplinusercontent.com/r/releases'; | const releasesLink = 'https://objects.joplinusercontent.com/r/releases'; | ||||||
|  | const supportedPlatformAssets: { [key in string]: string } = { | ||||||
|  | 	'darwin': 'latest-mac.yml', | ||||||
|  | 	'win32': 'latest.yml', | ||||||
|  | }; | ||||||
|  |  | ||||||
| export interface AutoUpdaterServiceInterface { | export interface AutoUpdaterServiceInterface { | ||||||
| 	checkForUpdates(): void; | 	checkForUpdates(): void; | ||||||
| 	updateApp(): void; | 	updateApp(): void; | ||||||
|  | 	fetchLatestRelease(includePreReleases: boolean): Promise<GitHubRelease>; | ||||||
|  | 	getDownloadUrlForPlatform(release: GitHubRelease, platform: string): string; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default class AutoUpdaterService implements AutoUpdaterServiceInterface { | export default class AutoUpdaterService implements AutoUpdaterServiceInterface { | ||||||
| @@ -45,7 +51,7 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface { | |||||||
|  |  | ||||||
| 	public checkForUpdates = async (): Promise<void> => { | 	public checkForUpdates = async (): Promise<void> => { | ||||||
| 		try { | 		try { | ||||||
| 			await this.fetchLatestRelease(); | 			await this.checkForLatestRelease(); | ||||||
| 		} catch (error) { | 		} catch (error) { | ||||||
| 			this.logger_.error('Failed to check for updates:', error); | 			this.logger_.error('Failed to check for updates:', error); | ||||||
| 			if (error.message.includes('ERR_CONNECTION_REFUSED')) { | 			if (error.message.includes('ERR_CONNECTION_REFUSED')) { | ||||||
| @@ -58,7 +64,33 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface { | |||||||
| 		autoUpdater.quitAndInstall(false, true); | 		autoUpdater.quitAndInstall(false, true); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	private fetchLatestReleases = async (): Promise<GitHubRelease[]> => { | 	public fetchLatestRelease = async (includePreReleases: boolean): Promise<GitHubRelease> => { | ||||||
|  | 		const releases = await this.fetchReleases(includePreReleases); | ||||||
|  | 		const release = releases[0]; | ||||||
|  |  | ||||||
|  | 		if (!release) { | ||||||
|  | 			throw new Error('No suitable release found'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return release; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	public getDownloadUrlForPlatform(release: GitHubRelease, platform: string): string { | ||||||
|  | 		const assetName: string = supportedPlatformAssets[platform]; | ||||||
|  | 		if (!assetName) { | ||||||
|  | 			throw new Error(`The AutoUpdaterService does not support the following platform: ${platform}`); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const asset: GitHubReleaseAsset = release.assets.find(a => a.name === assetName); | ||||||
|  | 		if (!asset) { | ||||||
|  | 			throw new Error('No suitable update asset found for this platform.'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return asset.browser_download_url; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private fetchReleases = async (includePreReleases: boolean): Promise<GitHubRelease[]> => { | ||||||
| 		const response = await fetch(releasesLink); | 		const response = await fetch(releasesLink); | ||||||
|  |  | ||||||
| 		if (!response.ok) { | 		if (!response.ok) { | ||||||
| @@ -66,48 +98,29 @@ export default class AutoUpdaterService implements AutoUpdaterServiceInterface { | |||||||
| 			throw new Error(`Cannot get latest release info: ${responseText.substr(0, 500)}`); | 			throw new Error(`Cannot get latest release info: ${responseText.substr(0, 500)}`); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return (await response.json()) as GitHubRelease[]; | 		const releases: GitHubRelease[] = await response.json(); | ||||||
|  | 		const sortedReleasesByVersion = releases.sort((a, b) => semver.rcompare(a.tag_name, b.tag_name)); | ||||||
|  | 		const filteredReleases = sortedReleasesByVersion.filter(release => includePreReleases || !release.prerelease); | ||||||
|  |  | ||||||
|  | 		return filteredReleases; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	private fetchLatestRelease = async (): Promise<void> => { | 	private checkForLatestRelease = async (): Promise<void> => { | ||||||
| 		try { | 		try { | ||||||
| 			const releases = await this.fetchLatestReleases(); | 			const release: GitHubRelease = await this.fetchLatestRelease(this.includePreReleases_); | ||||||
|  |  | ||||||
| 			const sortedReleasesByVersion = releases.sort((a, b) => { | 			try { | ||||||
| 				return semver.rcompare(a.tag_name, b.tag_name); | 				let assetUrl = this.getDownloadUrlForPlatform(release, shim.platformName()); | ||||||
| 			}); | 				// electron's autoUpdater appends automatically the platform's yml file to the link so we should remove it | ||||||
| 			const filteredReleases = sortedReleasesByVersion.filter(release => { | 				assetUrl = assetUrl.substring(0, assetUrl.lastIndexOf('/')); | ||||||
| 				return this.includePreReleases_ || !release.prerelease; | 				autoUpdater.setFeedURL({ provider: 'generic', url: assetUrl }); | ||||||
| 			}); | 				await autoUpdater.checkForUpdates(); | ||||||
| 			const release = filteredReleases[0]; | 			} catch (error) { | ||||||
|  | 				this.logger_.error(`Update download url failed: ${error.message}`); | ||||||
| 			if (release) { |  | ||||||
| 				let assetUrl = null; |  | ||||||
|  |  | ||||||
| 				if (shim.isWindows()) { |  | ||||||
| 					const asset = release.assets.find((asset: GitHubReleaseAsset) => asset.name === 'latest.yml'); |  | ||||||
| 					if (asset) { |  | ||||||
| 						assetUrl = asset.browser_download_url.replace('/latest.yml', ''); |  | ||||||
| 					} |  | ||||||
| 				} else if (shim.isMac()) { |  | ||||||
| 					const asset = release.assets.find((asset: GitHubReleaseAsset) => asset.name === 'latest-mac.yml'); |  | ||||||
| 					if (asset) { |  | ||||||
| 						assetUrl = asset.browser_download_url.replace('/latest-mac.yml', ''); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if (assetUrl) { |  | ||||||
| 					autoUpdater.setFeedURL({ |  | ||||||
| 						provider: 'generic', |  | ||||||
| 						url: assetUrl, |  | ||||||
| 					}); |  | ||||||
| 					await autoUpdater.checkForUpdates(); |  | ||||||
| 				} else { |  | ||||||
| 					this.logger_.error('No suitable update asset found for this platform.'); |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 		} catch (error) { | 		} catch (error) { | ||||||
| 			this.logger_.error(error); | 			this.logger_.error(`Fetching releases failed:  ${error.message}`); | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user