Browse Source

test: add more auto updater tests for squirrel.mac (#24611)

Samuel Attard 4 years ago
parent
commit
682f78b9a8

+ 1 - 1
docs/api/auto-updater.md

@@ -104,7 +104,7 @@ The `autoUpdater` object has the following methods:
 * `options` Object
   * `url` String
   * `headers` Record<String, String> (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.

+ 1 - 1
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\'');
       });
     });
   });

+ 188 - 49
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<void>) => {
+  const withTempDirectory = async (fn: (dir: string) => Promise<void>, 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<string, string> = {};
+
+  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<void>) => {
       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);
+        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()
           });
-          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 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 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/');
+        });
+        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/');
+        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/');
         });
       });
     });

+ 26 - 0
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);
+});

+ 5 - 0
spec-main/fixtures/auto-update/check-with-headers/package.json

@@ -0,0 +1,5 @@
+{
+  "name": "initial-app",
+  "version": "1.0.0",
+  "main": "./index.js"
+}

+ 43 - 0
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);
+});

+ 5 - 0
spec-main/fixtures/auto-update/update-json/package.json

@@ -0,0 +1,5 @@
+{
+  "name": "initial-app",
+  "version": "1.0.0",
+  "main": "./index.js"
+}