Browse Source

spec: add tests for the autoUpdater on macOS that actually test if it works (#17442)

* spec: add tests for the autoUpdater on macOS that actually test if it works

* spec: add express as dep

* spec: add logic to auto-trust self-signed certificate and not run autoupdate specs on MAS

* build: fix the step name for importing the codesign cert

* chore: update updater spec PR as per feedback

* fix: s/atomBinding/electronBinding

* build: use spawn instead of exec
Samuel Attard 6 years ago
parent
commit
b8dbe4bc15

+ 10 - 0
.circleci/config.yml

@@ -204,6 +204,15 @@ step-fix-sync-on-mac: &step-fix-sync-on-mac
         python src/electron/script/update-external-binaries.py
       fi
 
+step-install-signing-cert-on-mac: &step-install-signing-cert-on-mac
+  run:
+    name: Import and trust self-signed codesigning cert on MacOS
+    command: |
+      if [ "`uname`" == "Darwin" ]; then
+        cd src/electron
+        ./script/codesign/import-testing-cert-ci.sh
+      fi
+
 step-install-gnutar-on-mac: &step-install-gnutar-on-mac
   run:
     name: Install gnu-tar on macos
@@ -753,6 +762,7 @@ steps-tests: &steps-tests
     - *step-setup-linux-for-headless-testing
     - *step-restore-brew-cache
     - *step-fix-known-hosts-linux
+    - *step-install-signing-cert-on-mac
 
     - run:
         name: Run Electron tests

+ 9 - 0
atom/common/api/features.cc

@@ -41,6 +41,14 @@ bool IsPrintingEnabled() {
   return BUILDFLAG(ENABLE_PRINTING);
 }
 
+bool IsComponentBuild() {
+#if defined(COMPONENT_BUILD)
+  return true;
+#else
+  return false;
+#endif
+}
+
 void Initialize(v8::Local<v8::Object> exports,
                 v8::Local<v8::Value> unused,
                 v8::Local<v8::Context> context,
@@ -55,6 +63,7 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod("isViewApiEnabled", &IsViewApiEnabled);
   dict.SetMethod("isTtsEnabled", &IsTtsEnabled);
   dict.SetMethod("isPrintingEnabled", &IsPrintingEnabled);
+  dict.SetMethod("isComponentBuild", &IsComponentBuild);
 }
 
 }  // namespace

+ 339 - 40
package-lock.json

@@ -155,6 +155,16 @@
         "any-observable": "^0.3.0"
       }
     },
+    "@types/body-parser": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz",
+      "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==",
+      "dev": true,
+      "requires": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
     "@types/chai": {
       "version": "4.1.7",
       "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
@@ -170,6 +180,51 @@
         "@types/chai": "*"
       }
     },
+    "@types/connect": {
+      "version": "3.4.32",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
+      "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/express": {
+      "version": "4.16.1",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.1.tgz",
+      "integrity": "sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg==",
+      "dev": true,
+      "requires": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "@types/express-serve-static-core": {
+      "version": "4.16.2",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz",
+      "integrity": "sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*",
+        "@types/range-parser": "*"
+      }
+    },
+    "@types/fs-extra": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.5.tgz",
+      "integrity": "sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/mime": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
+      "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
+      "dev": true
+    },
     "@types/mocha": {
       "version": "5.2.6",
       "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz",
@@ -182,6 +237,22 @@
       "integrity": "sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ==",
       "dev": true
     },
+    "@types/range-parser": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
+      "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
+      "dev": true
+    },
+    "@types/serve-static": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
+      "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==",
+      "dev": true,
+      "requires": {
+        "@types/express-serve-static-core": "*",
+        "@types/mime": "*"
+      }
+    },
     "@types/split": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@types/split/-/split-1.0.0.tgz",
@@ -396,7 +467,6 @@
       "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
       "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
       "dev": true,
-      "optional": true,
       "requires": {
         "kind-of": "^3.0.2",
         "longest": "^1.0.1",
@@ -600,6 +670,12 @@
       "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
       "dev": true
     },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+      "dev": true
+    },
     "array-ify": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
@@ -980,6 +1056,71 @@
       "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=",
       "dev": true
     },
+    "body-parser": {
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+      "dev": true,
+      "requires": {
+        "bytes": "3.0.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "~1.6.3",
+        "iconv-lite": "0.4.23",
+        "on-finished": "~2.3.0",
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "~1.6.16"
+      },
+      "dependencies": {
+        "depd": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+          "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+          "dev": true
+        },
+        "http-errors": {
+          "version": "1.6.3",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+          "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+          "dev": true,
+          "requires": {
+            "depd": "~1.1.2",
+            "inherits": "2.0.3",
+            "setprototypeof": "1.1.0",
+            "statuses": ">= 1.4.0 < 2"
+          }
+        },
+        "iconv-lite": {
+          "version": "0.4.23",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+          "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+          "dev": true,
+          "requires": {
+            "safer-buffer": ">= 2.1.2 < 3"
+          }
+        },
+        "raw-body": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+          "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+          "dev": true,
+          "requires": {
+            "bytes": "3.0.0",
+            "http-errors": "1.6.3",
+            "iconv-lite": "0.4.23",
+            "unpipe": "1.0.0"
+          }
+        },
+        "setprototypeof": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+          "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+          "dev": true
+        }
+      }
+    },
     "boolbase": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -2018,7 +2159,7 @@
         "dot-prop": {
           "version": "4.2.0",
           "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
-          "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+          "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=",
           "dev": true,
           "requires": {
             "is-obj": "^1.0.0"
@@ -2047,6 +2188,12 @@
       "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
       "dev": true
     },
+    "content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+      "dev": true
+    },
     "content-type": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
@@ -2244,6 +2391,18 @@
       "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
       "dev": true
     },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+      "dev": true
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+      "dev": true
+    },
     "copy-descriptor": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@@ -2383,8 +2542,7 @@
       "version": "0.3.6",
       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz",
       "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "cssstyle": {
       "version": "0.2.37",
@@ -3852,6 +4010,64 @@
         "fill-range": "^2.1.0"
       }
     },
+    "express": {
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+      "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.5",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.18.3",
+        "content-disposition": "0.5.2",
+        "content-type": "~1.0.4",
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.1.1",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.4",
+        "qs": "6.5.2",
+        "range-parser": "~1.2.0",
+        "safe-buffer": "5.1.2",
+        "send": "0.16.2",
+        "serve-static": "1.13.2",
+        "setprototypeof": "1.1.0",
+        "statuses": "~1.4.0",
+        "type-is": "~1.6.16",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "depd": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+          "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+          "dev": true
+        },
+        "setprototypeof": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+          "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+          "dev": true
+        },
+        "statuses": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+          "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+          "dev": true
+        }
+      }
+    },
     "extend": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
@@ -4339,6 +4555,29 @@
         "repeat-string": "^1.5.2"
       }
     },
+    "finalhandler": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+      "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "statuses": "~1.4.0",
+        "unpipe": "~1.0.0"
+      },
+      "dependencies": {
+        "statuses": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+          "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+          "dev": true
+        }
+      }
+    },
     "find-parent-dir": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz",
@@ -4475,6 +4714,12 @@
       "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=",
       "dev": true
     },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+      "dev": true
+    },
     "fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -4544,8 +4789,7 @@
           "version": "2.1.1",
           "resolved": false,
           "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -4569,15 +4813,13 @@
           "version": "1.0.0",
           "resolved": false,
           "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "resolved": false,
           "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -4594,22 +4836,19 @@
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "resolved": false,
           "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "resolved": false,
           "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -4740,8 +4979,7 @@
           "version": "2.0.3",
           "resolved": false,
           "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -4755,7 +4993,6 @@
           "resolved": false,
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -4772,7 +5009,6 @@
           "resolved": false,
           "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -4781,15 +5017,13 @@
           "version": "0.0.8",
           "resolved": false,
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.2.4",
           "resolved": false,
           "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -4810,7 +5044,6 @@
           "resolved": false,
           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -4899,8 +5132,7 @@
           "version": "1.0.1",
           "resolved": false,
           "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -4914,7 +5146,6 @@
           "resolved": false,
           "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5010,8 +5241,7 @@
           "version": "5.1.1",
           "resolved": false,
           "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5053,7 +5283,6 @@
           "resolved": false,
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -5075,7 +5304,6 @@
           "resolved": false,
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -5124,15 +5352,13 @@
           "version": "1.0.2",
           "resolved": false,
           "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.2",
           "resolved": false,
           "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
@@ -5441,7 +5667,7 @@
     "gunzip-maybe": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.1.tgz",
-      "integrity": "sha512-qtutIKMthNJJgeHQS7kZ9FqDq59/Wn0G2HYCRNjpup7yKfVI6/eqwpmroyZGFoCYaG+sW6psNVb4zoLADHpp2g==",
+      "integrity": "sha1-Occu2J0bSbpwjhh3ZQBIiQKlICc=",
       "dev": true,
       "requires": {
         "browserify-zlib": "^0.1.4",
@@ -6002,6 +6228,12 @@
       "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
       "dev": true
     },
+    "ipaddr.js": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
+      "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=",
+      "dev": true
+    },
     "irregular-plurals": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz",
@@ -7571,8 +7803,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
       "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
-      "dev": true,
-      "optional": true
+      "dev": true
     },
     "longest-streak": {
       "version": "2.0.2",
@@ -7841,6 +8072,12 @@
       "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
       "dev": true
     },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "dev": true
+    },
     "meow": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz",
@@ -7933,12 +8170,24 @@
         }
       }
     },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+      "dev": true
+    },
     "merge2": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz",
       "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==",
       "dev": true
     },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "dev": true
+    },
     "micro": {
       "version": "9.3.1",
       "resolved": "https://registry.npmjs.org/micro/-/micro-9.3.1.tgz",
@@ -8886,6 +9135,12 @@
         "@types/node": "*"
       }
     },
+    "parseurl": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+      "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
+      "dev": true
+    },
     "pascalcase": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
@@ -8943,6 +9198,12 @@
       "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
       "dev": true
     },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+      "dev": true
+    },
     "path-type": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
@@ -9322,6 +9583,16 @@
         "object-assign": "^4.1.1"
       }
     },
+    "proxy-addr": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
+      "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
+      "dev": true,
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.8.0"
+      }
+    },
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -10575,7 +10846,7 @@
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
-      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+      "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=",
       "dev": true,
       "optional": true
     },
@@ -10749,6 +11020,18 @@
         }
       }
     },
+    "serve-static": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+      "dev": true,
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.2",
+        "send": "0.16.2"
+      }
+    },
     "set-immediate-shim": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
@@ -11127,7 +11410,7 @@
     "split": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
-      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+      "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=",
       "dev": true,
       "requires": {
         "through": "2"
@@ -11145,7 +11428,7 @@
     "split2": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
-      "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
+      "integrity": "sha1-GGsldbz4PoW30YRldWI47k7kJJM=",
       "dev": true,
       "requires": {
         "through2": "^2.0.2"
@@ -12595,6 +12878,16 @@
         "prelude-ls": "~1.1.2"
       }
     },
+    "type-is": {
+      "version": "1.6.16",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+      "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+      "dev": true,
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.18"
+      }
+    },
     "typedarray": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -13125,6 +13418,12 @@
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
       "dev": true
     },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "dev": true
+    },
     "uuid": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",

+ 3 - 0
package.json

@@ -7,6 +7,8 @@
     "@octokit/rest": "^16.3.2",
     "@types/chai": "^4.1.7",
     "@types/chai-as-promised": "^7.1.0",
+    "@types/express": "^4.16.1",
+    "@types/fs-extra": "^5.0.5",
     "@types/mocha": "^5.2.6",
     "@types/node": "^10.12.21",
     "@types/split": "^1.0.0",
@@ -26,6 +28,7 @@
     "eslint-config-standard": "^12.0.0",
     "eslint-plugin-mocha": "^5.2.0",
     "eslint-plugin-typescript": "^0.14.0",
+    "express": "^4.16.4",
     "folder-hash": "^2.1.1",
     "fs-extra": "^7.0.1",
     "husky": "^0.14.3",

+ 13 - 0
script/codesign/get-trusted-identity.sh

@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -e
+
+valid_certs=$(security find-identity -p codesigning -v)
+if [[ $valid_certs == *"1)"* ]]; then
+  first_valid_cert=$(echo $valid_certs | sed 's/ \".*//' | sed 's/.* //')
+  echo $first_valid_cert
+  exit 0
+else
+  # No Certificate
+  exit 0
+fi

+ 25 - 0
script/codesign/import-testing-cert-ci.sh

@@ -0,0 +1,25 @@
+#!/bin/sh
+
+KEY_CHAIN=mac-build.keychain
+KEYCHAIN_PASSWORD=unsafe_keychain_pass
+security create-keychain -p $KEYCHAIN_PASSWORD $KEY_CHAIN
+# Make the keychain the default so identities are found
+security default-keychain -s $KEY_CHAIN
+# Unlock the keychain
+security unlock-keychain -p $KEYCHAIN_PASSWORD $KEY_CHAIN
+# Set keychain locking timeout to 3600 seconds
+security set-keychain-settings -t 3600 -u $KEY_CHAIN
+
+# Add certificates to keychain and allow codesign to access them
+security import "$(dirname $0)"/signing.cer -k $KEY_CHAIN -A /usr/bin/codesign
+security import "$(dirname $0)"/signing.pem -k $KEY_CHAIN -A /usr/bin/codesign
+security import "$(dirname $0)"/signing.p12 -k $KEY_CHAIN -P $SPEC_KEY_PASSWORD -A /usr/bin/codesign
+
+echo "Add keychain to keychain-list"
+security list-keychains -s mac-build.keychain
+
+echo "Setting key partition list"
+security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEY_CHAIN
+
+echo "Trusting self-signed certificate"
+sudo security trust-settings-import -d "$(dirname $0)"/trust-settings.plist

BIN
script/codesign/signing.cer


BIN
script/codesign/signing.p12


+ 9 - 0
script/codesign/signing.pem

@@ -0,0 +1,9 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw91mumcVpai94X7PASje
+R9+meqEHsavRsKQmtVV5JkJk9ZZbWTdpXgOjy1hhGQURrbp3li9lmi3MFHVqZjuQ
+H8omufj0iFiUD0bBY9EZeQjmcXd/ZgP8SoFfMS3BSAeRzXI5UQ5zFq86CWyzBh4k
+lgRN+iuhmxxZ/8PUcuEQ49fzNWVtRskkX+ZDwj8mn9YYRQMm3nl+bB+lYbpgVnkX
+WztXvSdRxCMjvjzLtoSJQhG36DEz6Sv7XeEAfYi70diQIwr/yCtgCpYUTadjOdzO
+h0W/rpC2DTVE/yC3xZxg2uVjEa9siC8+DX9F6luAytkx2TgUGF6KdVblPVVCYkxW
+QQIDAQAB
+-----END RSA PUBLIC KEY-----

+ 138 - 0
script/codesign/trust-settings.plist

@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>trustList</key>
+	<dict>
+		<key>80079C1EC6AED92C03B4C67E9A94B0B05E854AC8</key>
+		<dict>
+			<key>issuerName</key>
+			<data>
+			MIGIMSYwJAYDVQQDDB1FbGVjdHJvblNlbGZTaWduZWRTcGVjU2ln
+			bmluZzEUMBIGA1UECgwLRWxlY3Ryb24gSlMxEDAOBgNVBAsMB1Rl
+			c3RpbmcxCzAJBgNVBAYTAlVTMSkwJwYJKoZIhvcNAQkBFhpjb2Rl
+			c2lnbmluZ0BlbGVjdHJvbmpzLm9yZw==
+			</data>
+			<key>modDate</key>
+			<date>2019-03-19T02:33:38Z</date>
+			<key>serialNumber</key>
+			<data>
+			AQ==
+			</data>
+			<key>trustSettings</key>
+			<array>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAED
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>sslServer</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147408896</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAED
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>sslServer</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEI
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>SMIME</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147408872</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEI
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>SMIME</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEJ
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>eapServer</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEL
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>ipsecServer</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEQ
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>CodeSigning</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEU
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>AppleTimeStamping</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+				<dict>
+					<key>kSecTrustSettingsAllowedError</key>
+					<integer>-2147409654</integer>
+					<key>kSecTrustSettingsPolicy</key>
+					<data>
+					KoZIhvdjZAEC
+					</data>
+					<key>kSecTrustSettingsPolicyName</key>
+					<string>basicX509</string>
+					<key>kSecTrustSettingsResult</key>
+					<integer>1</integer>
+				</dict>
+			</array>
+		</dict>
+	</dict>
+	<key>trustVersion</key>
+	<integer>1</integer>
+</dict>
+</plist>

+ 2 - 2
script/spec-runner.js

@@ -78,8 +78,8 @@ function saveSpecHash ([newSpecHash, newSpecInstallHash]) {
 async function runElectronTests () {
   const errors = []
   const runners = [
-    ['Remote based specs', 'remote', runRemoteBasedElectronTests],
-    ['Main process specs', 'main', runMainProcessElectronTests]
+    ['Main process specs', 'main', runMainProcessElectronTests],
+    ['Remote based specs', 'remote', runRemoteBasedElectronTests]
   ]
 
   const mochaFile = process.env.MOCHA_FILE

+ 1 - 0
spec-main/.gitignore

@@ -0,0 +1 @@
+node_modules

+ 261 - 0
spec-main/api-autoupdater-darwin-spec.ts

@@ -0,0 +1,261 @@
+import { expect } from 'chai'
+import * as cp from 'child_process'
+import * as http from 'http'
+import * as express from 'express'
+import * as fs from 'fs-extra'
+import * as os from 'os'
+import * as path from 'path'
+import { AddressInfo } from 'net';
+
+const features = process.electronBinding('features')
+
+const fixturesPath = path.resolve(__dirname, '../spec/fixtures')
+
+// We can only test the auto updater on darwin non-component builds
+const describeFn = (process.platform === 'darwin' && !process.mas && !features.isComponentBuild() ? describe : describe.skip)
+
+describeFn('autoUpdater behavior', function () {
+  this.timeout(120000)
+
+  let identity = ''
+
+  beforeEach(function () {
+    const result = cp.spawnSync(path.resolve(__dirname, '../script/codesign/get-trusted-identity.sh'))
+    if (result.status !== 0 || result.stdout.toString().trim().length === 0)  {
+      if (isCI) {
+        throw new Error('No valid signing identity available to run autoUpdater specs')
+      }
+      this.skip()
+    } else {
+      identity = result.stdout.toString().trim()
+    }
+  })
+
+  it('should have a valid code signing identity', () => {
+    expect(identity).to.be.a('string').with.lengthOf.at.least(1)
+  })
+
+  const copyApp = async (newDir: string, fixture = 'initial') => {
+    const appBundlePath = path.resolve(process.execPath, '../../..')
+    const newPath = path.resolve(newDir, 'Electron.app')
+    cp.spawnSync('cp', ['-R', appBundlePath, path.dirname(newPath)])
+    const appDir = path.resolve(newPath, 'Contents/Resources/app')
+    await fs.mkdirp(appDir)
+    await fs.copy(path.resolve(fixturesPath, 'auto-update', fixture), appDir)
+    const plistPath = path.resolve(newPath, 'Contents', 'Info.plist')
+    await fs.writeFile(
+      plistPath,
+      (await fs.readFile(plistPath, 'utf8')).replace('<key>BuildMachineOSBuild</key>', `<key>NSAppTransportSecurity</key>
+      <dict>
+          <key>NSAllowsArbitraryLoads</key>
+          <true/>
+          <key>NSExceptionDomains</key>
+          <dict>
+              <key>localhost</key>
+              <dict>
+                  <key>NSExceptionAllowsInsecureHTTPLoads</key>
+                  <true/>
+                  <key>NSIncludesSubdomains</key>
+                  <true/>
+              </dict>
+          </dict>
+      </dict><key>BuildMachineOSBuild</key>`)
+    )
+    return newPath
+  }
+
+  const spawn = (cmd: string, args: string[], opts: any = {}) => {
+    let out = ''
+    const child = cp.spawn(cmd, args, opts)
+    child.stdout.on('data', (chunk: Buffer) => {
+      out += chunk.toString()
+    })
+    child.stderr.on('data', (chunk: Buffer) => {
+      out += chunk.toString()
+    })
+    return new Promise<{ code: number, out: string }>((resolve) => {
+      child.on('exit', (code, signal) => {
+        expect(signal).to.equal(null)
+        resolve({
+          code: code!,
+          out
+        })
+      })
+    })
+  }
+
+  const signApp = (appPath: string) => {
+    return spawn('codesign', ['-s', identity, '--deep', '--force', appPath])
+  }
+
+  const launchApp = (appPath: string, args: string[] = []) => {
+    return spawn(path.resolve(appPath, 'Contents/MacOS/Electron'), args)
+  }
+
+  const withTempDirectory = async (fn: (dir: string) => Promise<void>) => {
+    const dir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-update-spec-'))
+    try {
+      await fn(dir)
+    } finally {
+      cp.spawnSync('rm', ['-r' , dir])
+    }
+  }
+
+  const logOnError = (what: any, fn: () => void) => {
+    try {
+      fn()
+    } catch (err) {
+      console.error(what)
+      throw err
+    }
+  }
+
+  it('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'])
+      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)
+      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((done) => {
+      if (httpServer) {
+        httpServer.close(() => {
+          httpServer = null as any
+          server = null as any
+          done()
+        })
+      }
+    })
+
+    it('should hit the update endpoint when checkForUpdates is called', async () => {
+      await withTempDirectory(async (dir) => {
+        const appPath = await copyApp(dir, 'check')
+        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('user-agent')).to.include('Electron/')
+        })
+      })
+    })
+
+    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)
+        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/')
+        })
+      })
+    })
+
+    it('should hit the download endpoint when an update is available and update successfully when the zip is provided', async () => {
+      await withTempDirectory(async (dir) => {
+        const appPath = await copyApp(dir, 'update')
+        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
+          })
+
+          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, reject) => {
+            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/')
+        })
+      })
+    })
+  })
+})

+ 23 - 0
spec/fixtures/auto-update/check/index.js

@@ -0,0 +1,23 @@
+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
+})
+
+autoUpdater.checkForUpdates()
+
+autoUpdater.on('update-not-available', () => {
+  process.exit(0)
+})

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

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

+ 18 - 0
spec/fixtures/auto-update/initial/index.js

@@ -0,0 +1,18 @@
+process.on('uncaughtException', (err) => {
+  console.error(err)
+  process.exit(1)
+})
+
+const { autoUpdater } = require('electron')
+
+const feedUrl = process.argv[1]
+
+console.log('Setting Feed URL')
+
+autoUpdater.setFeedURL({
+  url: feedUrl
+})
+
+console.log('Feed URL Set:', feedUrl)
+
+process.exit(0)

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

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

+ 42 - 0
spec/fixtures/auto-update/update/index.js

@@ -0,0 +1,42 @@
+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
+})
+
+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/fixtures/auto-update/update/package.json

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

+ 14 - 6
spec/package-lock.json

@@ -241,12 +241,14 @@
     "console-control-strings": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+      "optional": true
     },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "optional": true
     },
     "cross-spawn": {
       "version": "6.0.5",
@@ -680,7 +682,8 @@
     "isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "optional": true
     },
     "isexe": {
       "version": "2.0.0",
@@ -786,7 +789,8 @@
     "minimist": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+      "optional": true
     },
     "mkdirp": {
       "version": "0.5.1",
@@ -1106,7 +1110,8 @@
     "process-nextick-args": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
-      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+      "optional": true
     },
     "pump": {
       "version": "2.0.1",
@@ -1158,6 +1163,7 @@
       "version": "2.3.6",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
       "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "optional": true,
       "requires": {
         "core-util-is": "~1.0.0",
         "inherits": "~2.0.3",
@@ -1367,6 +1373,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "optional": true,
       "requires": {
         "safe-buffer": "~5.1.0"
       }
@@ -1493,7 +1500,8 @@
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "optional": true
     },
     "walkdir": {
       "version": "0.3.2",

+ 1 - 0
typings/internal-ambient.d.ts

@@ -8,6 +8,7 @@ declare namespace NodeJS {
     isViewApiEnabled(): boolean;
     isTtsEnabled(): boolean;
     isPrintingEnabled(): boolean;
+    isComponentBuild(): boolean;
   }
 
   interface V8UtilBinding {