From 682f78b9a8958d0d361cf1f0f3afeec5f6686fc0 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 20 Jul 2020 09:51:33 -0700 Subject: [PATCH] test: add more auto updater tests for squirrel.mac (#24611) --- docs/api/auto-updater.md | 2 +- spec-main/api-auto-updater-spec.ts | 2 +- spec-main/api-autoupdater-darwin-spec.ts | 245 ++++++++++++++---- .../auto-update/check-with-headers/index.js | 26 ++ .../check-with-headers/package.json | 5 + .../fixtures/auto-update/update-json/index.js | 43 +++ .../auto-update/update-json/package.json | 5 + 7 files changed, 273 insertions(+), 55 deletions(-) create mode 100644 spec-main/fixtures/auto-update/check-with-headers/index.js create mode 100644 spec-main/fixtures/auto-update/check-with-headers/package.json create mode 100644 spec-main/fixtures/auto-update/update-json/index.js create mode 100644 spec-main/fixtures/auto-update/update-json/package.json diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 3a05dcecbf61..77d3e4c4d514 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -104,7 +104,7 @@ The `autoUpdater` object has the following methods: * `options` Object * `url` String * `headers` Record (optional) _macOS_ - HTTP request headers. - * `serverType` String (optional) _macOS_ - Either `json` or `default`, see the [Squirrel.Mac][squirrel-mac] + * `serverType` String (optional) _macOS_ - Can be `json` or `default`, see the [Squirrel.Mac][squirrel-mac] README for more information. Sets the `url` and initialize the auto updater. diff --git a/spec-main/api-auto-updater-spec.ts b/spec-main/api-auto-updater-spec.ts index 1839303b6fcf..4086d62cce7e 100644 --- a/spec-main/api-auto-updater-spec.ts +++ b/spec-main/api-auto-updater-spec.ts @@ -73,7 +73,7 @@ ifdescribe(!process.mas)('autoUpdater module', function () { }); it('does throw if an unknown string is the serverType', () => { - expect(() => autoUpdater.setFeedURL({ url: '', serverType: 'weow' })).to.throw('Expected serverType to be \'default\' or \'json\''); + expect(() => autoUpdater.setFeedURL({ url: '', serverType: 'weow' as any })).to.throw('Expected serverType to be \'default\' or \'json\''); }); }); }); diff --git a/spec-main/api-autoupdater-darwin-spec.ts b/spec-main/api-autoupdater-darwin-spec.ts index 7300d7867c33..eef9c4a7855a 100644 --- a/spec-main/api-autoupdater-darwin-spec.ts +++ b/spec-main/api-autoupdater-darwin-spec.ts @@ -94,12 +94,14 @@ describeFn('autoUpdater behavior', function () { return spawn(path.resolve(appPath, 'Contents/MacOS/Electron'), args); }; - const withTempDirectory = async (fn: (dir: string) => Promise) => { + const withTempDirectory = async (fn: (dir: string) => Promise, autoCleanUp = true) => { const dir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-update-spec-')); try { await fn(dir); } finally { - cp.spawnSync('rm', ['-r', dir]); + if (autoCleanUp) { + cp.spawnSync('rm', ['-r', dir]); + } } }; @@ -112,6 +114,36 @@ describeFn('autoUpdater behavior', function () { } }; + const cachedZips: Record = {}; + + const getOrCreateUpdateZipPath = async (version: string, fixture: string) => { + const key = `${version}-${fixture}`; + if (!cachedZips[key]) { + let updateZipPath: string; + await withTempDirectory(async (dir) => { + const secondAppPath = await copyApp(dir, fixture); + const appPJPath = path.resolve(secondAppPath, 'Contents', 'Resources', 'app', 'package.json'); + await fs.writeFile( + appPJPath, + (await fs.readFile(appPJPath, 'utf8')).replace('1.0.0', version) + ); + await signApp(secondAppPath); + updateZipPath = path.resolve(dir, 'update.zip'); + await spawn('zip', ['-r', '--symlinks', updateZipPath, './'], { + cwd: dir + }); + }, false); + cachedZips[key] = updateZipPath!; + } + return cachedZips[key]; + }; + + after(() => { + for (const version of Object.keys(cachedZips)) { + cp.spawnSync('rm', ['-r', path.dirname(cachedZips[version])]); + } + }); + it('should fail to set the feed URL when the app is not signed', async () => { await withTempDirectory(async (dir) => { const appPath = await copyApp(dir); @@ -150,12 +182,14 @@ describeFn('autoUpdater behavior', function () { }); }); - afterEach((done) => { + afterEach(async () => { if (httpServer) { - httpServer.close(() => { - httpServer = null as any; - server = null as any; - done(); + await new Promise(resolve => { + httpServer.close(() => { + httpServer = null as any; + server = null as any; + resolve(); + }); }); } }); @@ -177,6 +211,23 @@ describeFn('autoUpdater behavior', function () { }); }); + it('should hit the update endpoint with customer headers when checkForUpdates is called', async () => { + await withTempDirectory(async (dir) => { + const appPath = await copyApp(dir, 'check-with-headers'); + await signApp(appPath); + server.get('/update-check', (req, res) => { + res.status(204).send(); + }); + const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]); + logOnError(launchResult, () => { + expect(launchResult.code).to.equal(0); + expect(requests).to.have.lengthOf(1); + expect(requests[0]).to.have.property('url', '/update-check'); + expect(requests[0].header('x-test')).to.equal('this-is-a-test'); + }); + }); + }); + it('should hit the download endpoint when an update is available and error if the file is bad', async () => { await withTempDirectory(async (dir) => { const appPath = await copyApp(dir, 'update'); @@ -205,57 +256,145 @@ describeFn('autoUpdater behavior', function () { }); }); - it('should hit the download endpoint when an update is available and update successfully when the zip is provided', async () => { + const withUpdatableApp = async (opts: { + nextVersion: string; + startFixture: string; + endFixture: string; + }, fn: (appPath: string, zipPath: string) => Promise) => { await withTempDirectory(async (dir) => { - const appPath = await copyApp(dir, 'update'); + const appPath = await copyApp(dir, opts.startFixture); await signApp(appPath); - // Prepare update - await withTempDirectory(async (dir2) => { - const secondAppPath = await copyApp(dir2, 'update'); - const appPJPath = path.resolve(secondAppPath, 'Contents', 'Resources', 'app', 'package.json'); - await fs.writeFile( - appPJPath, - (await fs.readFile(appPJPath, 'utf8')).replace('1.0.0', '2.0.0') - ); - await signApp(secondAppPath); - const updateZipPath = path.resolve(dir2, 'update.zip'); - await spawn('zip', ['-r', '--symlinks', updateZipPath, './'], { - cwd: dir2 - }); + const updateZipPath = await getOrCreateUpdateZipPath(opts.nextVersion, opts.endFixture); - server.get('/update-file', (req, res) => { - res.download(updateZipPath); - }); - server.get('/update-check', (req, res) => { - res.json({ - url: `http://localhost:${port}/update-file`, - name: 'My Release Name', - notes: 'Theses are some release notes innit', - pub_date: (new Date()).toString() - }); - }); - const relaunchPromise = new Promise((resolve) => { - server.get('/update-check/updated/:version', (req, res) => { - res.status(204).send(); - resolve(); - }); - }); - const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]); - logOnError(launchResult, () => { - expect(launchResult).to.have.property('code', 0); - expect(launchResult.out).to.include('Update Downloaded'); - expect(requests).to.have.lengthOf(2); - expect(requests[0]).to.have.property('url', '/update-check'); - expect(requests[1]).to.have.property('url', '/update-file'); - expect(requests[0].header('user-agent')).to.include('Electron/'); - expect(requests[1].header('user-agent')).to.include('Electron/'); - }); + await fn(appPath, updateZipPath); + }); + }; - await relaunchPromise; - expect(requests).to.have.lengthOf(3); - expect(requests[2]).to.have.property('url', '/update-check/updated/2.0.0'); - expect(requests[2].header('user-agent')).to.include('Electron/'); + it('should hit the download endpoint when an update is available and update successfully when the zip is provided', async () => { + await withUpdatableApp({ + nextVersion: '2.0.0', + startFixture: 'update', + endFixture: 'update' + }, async (appPath, updateZipPath) => { + server.get('/update-file', (req, res) => { + res.download(updateZipPath); + }); + server.get('/update-check', (req, res) => { + res.json({ + url: `http://localhost:${port}/update-file`, + name: 'My Release Name', + notes: 'Theses are some release notes innit', + pub_date: (new Date()).toString() + }); + }); + const relaunchPromise = new Promise((resolve) => { + server.get('/update-check/updated/:version', (req, res) => { + res.status(204).send(); + resolve(); + }); + }); + const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]); + logOnError(launchResult, () => { + expect(launchResult).to.have.property('code', 0); + expect(launchResult.out).to.include('Update Downloaded'); + expect(requests).to.have.lengthOf(2); + expect(requests[0]).to.have.property('url', '/update-check'); + expect(requests[1]).to.have.property('url', '/update-file'); + expect(requests[0].header('user-agent')).to.include('Electron/'); + expect(requests[1].header('user-agent')).to.include('Electron/'); + }); + + await relaunchPromise; + expect(requests).to.have.lengthOf(3); + expect(requests[2]).to.have.property('url', '/update-check/updated/2.0.0'); + expect(requests[2].header('user-agent')).to.include('Electron/'); + }); + }); + + it('should hit the download endpoint when an update is available and update successfully when the zip is provided with JSON update mode', async () => { + await withUpdatableApp({ + nextVersion: '2.0.0', + startFixture: 'update-json', + endFixture: 'update-json' + }, async (appPath, updateZipPath) => { + server.get('/update-file', (req, res) => { + res.download(updateZipPath); + }); + server.get('/update-check', (req, res) => { + res.json({ + currentRelease: '2.0.0', + releases: [ + { + version: '2.0.0', + updateTo: { + version: '2.0.0', + url: `http://localhost:${port}/update-file`, + name: 'My Release Name', + notes: 'Theses are some release notes innit', + pub_date: (new Date()).toString() + } + } + ] + }); + }); + const relaunchPromise = new Promise((resolve) => { + server.get('/update-check/updated/:version', (req, res) => { + res.status(204).send(); + resolve(); + }); + }); + const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]); + logOnError(launchResult, () => { + expect(launchResult).to.have.property('code', 0); + expect(launchResult.out).to.include('Update Downloaded'); + expect(requests).to.have.lengthOf(2); + expect(requests[0]).to.have.property('url', '/update-check'); + expect(requests[1]).to.have.property('url', '/update-file'); + expect(requests[0].header('user-agent')).to.include('Electron/'); + expect(requests[1].header('user-agent')).to.include('Electron/'); + }); + + await relaunchPromise; + expect(requests).to.have.lengthOf(3); + expect(requests[2]).to.have.property('url', '/update-check/updated/2.0.0'); + expect(requests[2].header('user-agent')).to.include('Electron/'); + }); + }); + + it('should hit the download endpoint when an update is available and not update in JSON update mode when the currentRelease is older than the current version', async () => { + await withUpdatableApp({ + nextVersion: '0.1.0', + startFixture: 'update-json', + endFixture: 'update-json' + }, async (appPath, updateZipPath) => { + server.get('/update-file', (req, res) => { + res.download(updateZipPath); + }); + server.get('/update-check', (req, res) => { + res.json({ + currentRelease: '0.1.0', + releases: [ + { + version: '0.1.0', + updateTo: { + version: '0.1.0', + url: `http://localhost:${port}/update-file`, + name: 'My Release Name', + notes: 'Theses are some release notes innit', + pub_date: (new Date()).toString() + } + } + ] + }); + }); + const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]); + logOnError(launchResult, () => { + expect(launchResult).to.have.property('code', 1); + expect(launchResult.out).to.include('No update available'); + expect(requests).to.have.lengthOf(1); + expect(requests[0]).to.have.property('url', '/update-check'); + expect(requests[0].header('user-agent')).to.include('Electron/'); }); }); }); diff --git a/spec-main/fixtures/auto-update/check-with-headers/index.js b/spec-main/fixtures/auto-update/check-with-headers/index.js new file mode 100644 index 000000000000..987a586f9cc4 --- /dev/null +++ b/spec-main/fixtures/auto-update/check-with-headers/index.js @@ -0,0 +1,26 @@ +process.on('uncaughtException', (err) => { + console.error(err); + process.exit(1); +}); + +const { autoUpdater } = require('electron'); + +autoUpdater.on('error', (err) => { + console.error(err); + process.exit(1); +}); + +const feedUrl = process.argv[1]; + +autoUpdater.setFeedURL({ + url: feedUrl, + headers: { + 'X-test': 'this-is-a-test' + } +}); + +autoUpdater.checkForUpdates(); + +autoUpdater.on('update-not-available', () => { + process.exit(0); +}); diff --git a/spec-main/fixtures/auto-update/check-with-headers/package.json b/spec-main/fixtures/auto-update/check-with-headers/package.json new file mode 100644 index 000000000000..5edc5dc51ce7 --- /dev/null +++ b/spec-main/fixtures/auto-update/check-with-headers/package.json @@ -0,0 +1,5 @@ +{ + "name": "initial-app", + "version": "1.0.0", + "main": "./index.js" +} \ No newline at end of file diff --git a/spec-main/fixtures/auto-update/update-json/index.js b/spec-main/fixtures/auto-update/update-json/index.js new file mode 100644 index 000000000000..1595696131f9 --- /dev/null +++ b/spec-main/fixtures/auto-update/update-json/index.js @@ -0,0 +1,43 @@ +const fs = require('fs'); +const path = require('path'); + +process.on('uncaughtException', (err) => { + console.error(err); + process.exit(1); +}); + +const { app, autoUpdater } = require('electron'); + +autoUpdater.on('error', (err) => { + console.error(err); + process.exit(1); +}); + +const urlPath = path.resolve(__dirname, '../../../../url.txt'); +let feedUrl = process.argv[1]; +if (!feedUrl || !feedUrl.startsWith('http')) { + feedUrl = `${fs.readFileSync(urlPath, 'utf8')}/${app.getVersion()}`; +} else { + fs.writeFileSync(urlPath, `${feedUrl}/updated`); +} + +autoUpdater.setFeedURL({ + url: feedUrl, + serverType: 'json' +}); + +autoUpdater.checkForUpdates(); + +autoUpdater.on('update-available', () => { + console.log('Update Available'); +}); + +autoUpdater.on('update-downloaded', () => { + console.log('Update Downloaded'); + autoUpdater.quitAndInstall(); +}); + +autoUpdater.on('update-not-available', () => { + console.error('No update available'); + process.exit(1); +}); diff --git a/spec-main/fixtures/auto-update/update-json/package.json b/spec-main/fixtures/auto-update/update-json/package.json new file mode 100644 index 000000000000..5edc5dc51ce7 --- /dev/null +++ b/spec-main/fixtures/auto-update/update-json/package.json @@ -0,0 +1,5 @@ +{ + "name": "initial-app", + "version": "1.0.0", + "main": "./index.js" +} \ No newline at end of file