123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826 |
- import { expect } from 'chai';
- import * as cp from 'node:child_process';
- import * as http from 'node:http';
- import * as express from 'express';
- import * as fs from 'fs-extra';
- import * as path from 'node:path';
- import * as psList from 'ps-list';
- import { AddressInfo } from 'node:net';
- import { ifdescribe, ifit } from './lib/spec-helpers';
- import { copyApp, getCodesignIdentity, shouldRunCodesignTests, signApp, spawn, withTempDirectory } from './lib/codesign-helpers';
- import * as uuid from 'uuid';
- import { autoUpdater, systemPreferences } from 'electron';
- // We can only test the auto updater on darwin non-component builds
- ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
- this.timeout(120000);
- let identity = '';
- beforeEach(function () {
- const result = getCodesignIdentity();
- if (result === null) {
- this.skip();
- } else {
- identity = result;
- }
- });
- it('should have a valid code signing identity', () => {
- expect(identity).to.be.a('string').with.lengthOf.at.least(1);
- });
- const launchApp = (appPath: string, args: string[] = []) => {
- return spawn(path.resolve(appPath, 'Contents/MacOS/Electron'), args);
- };
- const spawnAppWithHandle = (appPath: string, args: string[] = []) => {
- return cp.spawn(path.resolve(appPath, 'Contents/MacOS/Electron'), args);
- };
- const getRunningShipIts = async (appPath: string) => {
- const processes = await psList();
- const activeShipIts = processes.filter(p => p.cmd?.includes('Squirrel.framework/Resources/ShipIt com.github.Electron.ShipIt') && p.cmd!.startsWith(appPath));
- return activeShipIts;
- };
- const logOnError = (what: any, fn: () => void) => {
- try {
- fn();
- } catch (err) {
- console.error(what);
- throw err;
- }
- };
- const cachedZips: Record<string, string> = {};
- type Mutation = {
- mutate: (appPath: string) => Promise<void>,
- mutationKey: string,
- };
- const getOrCreateUpdateZipPath = async (version: string, fixture: string, mutateAppPreSign?: Mutation, mutateAppPostSign?: Mutation) => {
- const key = `${version}-${fixture}-${mutateAppPreSign?.mutationKey || 'no-pre-mutation'}-${mutateAppPostSign?.mutationKey || 'no-post-mutation'}`;
- 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)
- );
- const infoPath = path.resolve(secondAppPath, 'Contents', 'Info.plist');
- await fs.writeFile(
- infoPath,
- (await fs.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, `$1${version}`)
- );
- await mutateAppPreSign?.mutate(secondAppPath);
- await signApp(secondAppPath, identity);
- await mutateAppPostSign?.mutate(secondAppPath);
- updateZipPath = path.resolve(dir, 'update.zip');
- await spawn('zip', ['-0', '-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])]);
- }
- });
- // On arm64 builds the built app is self-signed by default so the setFeedURL call always works
- ifit(process.arch !== 'arm64')('should fail to set the feed URL when the app is not signed', async () => {
- await withTempDirectory(async (dir) => {
- const appPath = await copyApp(dir);
- const launchResult = await launchApp(appPath, ['http://myupdate']);
- console.log(launchResult);
- expect(launchResult.code).to.equal(1);
- expect(launchResult.out).to.include('Could not get code signature for running application');
- });
- });
- it('should cleanly set the feed URL when the app is signed', async () => {
- await withTempDirectory(async (dir) => {
- const appPath = await copyApp(dir);
- await signApp(appPath, identity);
- const launchResult = await launchApp(appPath, ['http://myupdate']);
- expect(launchResult.code).to.equal(0);
- expect(launchResult.out).to.include('Feed URL Set: http://myupdate');
- });
- });
- describe('with update server', () => {
- let port = 0;
- let server: express.Application = null as any;
- let httpServer: http.Server = null as any;
- let requests: express.Request[] = [];
- beforeEach((done) => {
- requests = [];
- server = express();
- server.use((req, res, next) => {
- requests.push(req);
- next();
- });
- httpServer = server.listen(0, '127.0.0.1', () => {
- port = (httpServer.address() as AddressInfo).port;
- done();
- });
- });
- afterEach(async () => {
- if (httpServer) {
- await new Promise<void>(resolve => {
- httpServer.close(() => {
- httpServer = null as any;
- server = null as any;
- resolve();
- });
- });
- }
- });
- it('should hit the update endpoint when checkForUpdates is called', async () => {
- await withTempDirectory(async (dir) => {
- const appPath = await copyApp(dir, 'check');
- await signApp(appPath, identity);
- 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('user-agent')).to.include('Electron/');
- });
- });
- });
- 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, identity);
- 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');
- await signApp(appPath, identity);
- server.get('/update-file', (req, res) => {
- res.status(500).send('This is not a file');
- });
- 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 launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- logOnError(launchResult, () => {
- expect(launchResult).to.have.property('code', 1);
- expect(launchResult.out).to.include('Update download failed. The server sent an invalid response.');
- 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/');
- });
- });
- });
- const withUpdatableApp = async (opts: {
- nextVersion: string;
- startFixture: string;
- endFixture: string;
- mutateAppPreSign?: Mutation;
- mutateAppPostSign?: Mutation;
- }, fn: (appPath: string, zipPath: string) => Promise<void>) => {
- await withTempDirectory(async (dir) => {
- const appPath = await copyApp(dir, opts.startFixture);
- await opts.mutateAppPreSign?.mutate(appPath);
- const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
- await fs.writeFile(
- infoPath,
- (await fs.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, '$11.0.0')
- );
- await signApp(appPath, identity);
- const updateZipPath = await getOrCreateUpdateZipPath(opts.nextVersion, opts.endFixture, opts.mutateAppPreSign, opts.mutateAppPostSign);
- await fn(appPath, updateZipPath);
- });
- };
- 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<void>((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].url).to.equal('/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 even after a different update was staged', async () => {
- await withUpdatableApp({
- nextVersion: '2.0.0',
- startFixture: 'update-stack',
- endFixture: 'update-stack'
- }, async (appPath, updateZipPath2) => {
- await withUpdatableApp({
- nextVersion: '3.0.0',
- startFixture: 'update-stack',
- endFixture: 'update-stack'
- }, async (_, updateZipPath3) => {
- let updateCount = 0;
- server.get('/update-file', (req, res) => {
- res.download(updateCount > 1 ? updateZipPath3 : updateZipPath2);
- });
- server.get('/update-check', (req, res) => {
- updateCount++;
- 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<void>((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(4);
- 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/');
- expect(requests[2]).to.have.property('url', '/update-check');
- expect(requests[3]).to.have.property('url', '/update-file');
- expect(requests[2].header('user-agent')).to.include('Electron/');
- expect(requests[3].header('user-agent')).to.include('Electron/');
- });
- await relaunchPromise;
- expect(requests).to.have.lengthOf(5);
- expect(requests[4].url).to.equal('/update-check/updated/3.0.0');
- expect(requests[4].header('user-agent')).to.include('Electron/');
- });
- });
- });
- it('should update to lower version numbers', async () => {
- await withUpdatableApp({
- nextVersion: '0.0.1',
- 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<void>((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].url).to.equal('/update-check/updated/0.0.1');
- expect(requests[2].header('user-agent')).to.include('Electron/');
- });
- });
- describe('with ElectronSquirrelPreventDowngrades enabled', () => {
- it('should not update to lower version numbers', async () => {
- await withUpdatableApp({
- nextVersion: '0.0.1',
- startFixture: 'update',
- endFixture: 'update',
- mutateAppPreSign: {
- mutationKey: 'prevent-downgrades',
- mutate: async (appPath) => {
- const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
- await fs.writeFile(
- infoPath,
- (await fs.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
- );
- }
- }
- }, 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 launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- logOnError(launchResult, () => {
- expect(launchResult).to.have.property('code', 1);
- expect(launchResult.out).to.include('Cannot update to a bundle with a lower version number');
- 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/');
- });
- });
- });
- it('should not update to version strings that are not simple Major.Minor.Patch', async () => {
- await withUpdatableApp({
- nextVersion: '2.0.0-bad',
- startFixture: 'update',
- endFixture: 'update',
- mutateAppPreSign: {
- mutationKey: 'prevent-downgrades',
- mutate: async (appPath) => {
- const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
- await fs.writeFile(
- infoPath,
- (await fs.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
- );
- }
- }
- }, 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 launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- logOnError(launchResult, () => {
- expect(launchResult).to.have.property('code', 1);
- expect(launchResult.out).to.include('Cannot update to a bundle with a lower version number');
- 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/');
- });
- });
- });
- it('should still update to higher version numbers', async () => {
- await withUpdatableApp({
- nextVersion: '1.0.1',
- 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<void>((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].url).to.equal('/update-check/updated/1.0.1');
- expect(requests[2].header('user-agent')).to.include('Electron/');
- });
- });
- it('should compare version numbers correctly', () => {
- expect(autoUpdater.isVersionAllowedForUpdate!('1.0.0', '2.0.0')).to.equal(true);
- expect(autoUpdater.isVersionAllowedForUpdate!('1.0.1', '1.0.10')).to.equal(true);
- expect(autoUpdater.isVersionAllowedForUpdate!('1.0.10', '1.0.1')).to.equal(false);
- expect(autoUpdater.isVersionAllowedForUpdate!('1.31.1', '1.32.0')).to.equal(true);
- expect(autoUpdater.isVersionAllowedForUpdate!('1.31.1', '0.32.0')).to.equal(false);
- });
- });
- it('should abort the update if the application is still running when ShipIt kicks off', 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()
- });
- });
- enum FlipFlop {
- INITIAL,
- FLIPPED,
- FLOPPED,
- }
- const shipItFlipFlopPromise = new Promise<void>((resolve) => {
- let state = FlipFlop.INITIAL;
- const checker = setInterval(async () => {
- const running = await getRunningShipIts(appPath);
- switch (state) {
- case FlipFlop.INITIAL: {
- if (running.length) state = FlipFlop.FLIPPED;
- break;
- }
- case FlipFlop.FLIPPED: {
- if (!running.length) state = FlipFlop.FLOPPED;
- break;
- }
- }
- if (state === FlipFlop.FLOPPED) {
- clearInterval(checker);
- resolve();
- }
- }, 500);
- });
- const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- const retainerHandle = spawnAppWithHandle(appPath, ['remain-open']);
- 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 shipItFlipFlopPromise;
- expect(requests).to.have.lengthOf(2, 'should not have relaunched the updated app');
- expect(JSON.parse(await fs.readFile(path.resolve(appPath, 'Contents/Resources/app/package.json'), 'utf8')).version).to.equal('1.0.0', 'should still be the old version on disk');
- retainerHandle.kill('SIGINT');
- });
- });
- describe('with SquirrelMacEnableDirectContentsWrite enabled', () => {
- let previousValue: any;
- beforeEach(() => {
- previousValue = systemPreferences.getUserDefault('SquirrelMacEnableDirectContentsWrite', 'boolean');
- systemPreferences.setUserDefault('SquirrelMacEnableDirectContentsWrite', 'boolean', true as any);
- });
- afterEach(() => {
- systemPreferences.setUserDefault('SquirrelMacEnableDirectContentsWrite', 'boolean', previousValue as any);
- });
- it('should hit the download endpoint when an update is available and update successfully when the zip is provided leaving the parent directory untouched', async () => {
- await withUpdatableApp({
- nextVersion: '2.0.0',
- startFixture: 'update',
- endFixture: 'update'
- }, async (appPath, updateZipPath) => {
- const randomID = uuid.v4();
- cp.spawnSync('xattr', ['-w', 'spec-id', randomID, appPath]);
- 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<void>((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].url).to.equal('/update-check/updated/2.0.0');
- expect(requests[2].header('user-agent')).to.include('Electron/');
- const result = cp.spawnSync('xattr', ['-l', appPath]);
- expect(result.stdout.toString()).to.include(`spec-id: ${randomID}`);
- });
- });
- });
- it('should hit the download endpoint when an update is available and fail when the zip signature is invalid', async () => {
- await withUpdatableApp({
- nextVersion: '2.0.0',
- startFixture: 'update',
- endFixture: 'update',
- mutateAppPostSign: {
- mutationKey: 'add-resource',
- mutate: async (appPath) => {
- const resourcesPath = path.resolve(appPath, 'Contents', 'Resources', 'app', 'injected.txt');
- await fs.writeFile(resourcesPath, 'demo');
- }
- }
- }, 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 launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- logOnError(launchResult, () => {
- expect(launchResult).to.have.property('code', 1);
- expect(launchResult.out).to.include('Code signature at URL');
- expect(launchResult.out).to.include('a sealed resource is missing or invalid');
- 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/');
- });
- });
- });
- it('should hit the download endpoint when an update is available and fail when the ShipIt binary is a symlink', async () => {
- await withUpdatableApp({
- nextVersion: '2.0.0',
- startFixture: 'update',
- endFixture: 'update',
- mutateAppPostSign: {
- mutationKey: 'modify-shipit',
- mutate: async (appPath) => {
- const shipItPath = path.resolve(appPath, 'Contents', 'Frameworks', 'Squirrel.framework', 'Resources', 'ShipIt');
- await fs.remove(shipItPath);
- await fs.symlink('/tmp/ShipIt', shipItPath, 'file');
- }
- }
- }, 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 launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- logOnError(launchResult, () => {
- expect(launchResult).to.have.property('code', 1);
- expect(launchResult.out).to.include('Code signature at URL');
- expect(launchResult.out).to.include('a sealed resource is missing or invalid');
- 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/');
- });
- });
- });
- it('should hit the download endpoint when an update is available and fail when the Electron Framework is modified', async () => {
- await withUpdatableApp({
- nextVersion: '2.0.0',
- startFixture: 'update',
- endFixture: 'update',
- mutateAppPostSign: {
- mutationKey: 'modify-eframework',
- mutate: async (appPath) => {
- const shipItPath = path.resolve(appPath, 'Contents', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
- await fs.appendFile(shipItPath, Buffer.from('123'));
- }
- }
- }, 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 launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
- logOnError(launchResult, () => {
- expect(launchResult).to.have.property('code', 1);
- expect(launchResult.out).to.include('Code signature at URL');
- expect(launchResult.out).to.include(' main executable failed strict validation');
- 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/');
- });
- });
- });
- 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<void>((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/');
- });
- });
- });
- });
- });
|