Repository: iopipe/iopipe-js-core (js)

Issue:
IOpipe does not catch unhandled Promise rejections
**Description**
When a Promise throws an error and is not properly caught in user code, iopipe should catch and report this error/invocation.

**Steps to reproduce the issue:**
1. Test a function that throws any error inside of a Promise.

**Describe the results you received:**
Invocation does not appear in dashboard, is not collected in telemetry.

**Describe the results you expected:**
Invocation should be reported to the dashboard.

**Additional information you deem important (e.g. issue happens only occasionally):**


Pull request description:
feat(error handling): catch uncaught Promise errors
Fixes #266 

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..17719ec
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,79 @@
+defaults: &defaults
+  docker:
+    - image: iopipe/circleci
+  working_directory: ~/circleci-deployment
+
+version: 2
+
+jobs:
+  install:
+    <<: *defaults
+    steps:
+      - checkout
+      - restore_cache:
+          key: yarn-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
+      - run: yarn
+      - save_cache:
+          paths:
+            - node_modules
+          key: yarn-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
+
+  test:
+    <<: *defaults
+    steps:
+      - checkout
+      - restore_cache:
+          key: yarn-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
+      - run: yarn test
+      # test also runs the build, so save the dist for later
+      - save_cache:
+          key: v1-dist-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
+          paths:
+            - dist
+
+  acceptanceTests:
+    <<: *defaults
+    steps:
+      - checkout
+      - restore_cache:
+          key: yarn-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
+      - restore_cache:
+          key: v1-dist-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
+      - run: npm run acceptance
+
+  deploy:
+    <<: *defaults
+    steps:
+      - checkout
+      - restore_cache:
+          key: yarn-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
+      - restore_cache:
+          key: v1-dist-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
+      - run: cp .npmrc-ci .npmrc
+      - run: npm publish --access public
+
+workflows:
+  version: 2
+  test_build_deploy:
+    jobs:
+      - install
+      - test:
+          requires:
+            - install
+      - acceptanceTests:
+          requires:
+            - install
+            - test
+          filters:
+            branches:
+              only:
+                - master
+      - deploy:
+          requires:
+            - install
+            - test
+          filters:
+            branches:
+              ignore: /.*/
+            tags:
+              only: /^v.*/
diff --git a/.npmrc-sample b/.npmrc-ci
similarity index 100%
rename from .npmrc-sample
rename to .npmrc-ci
diff --git a/circle.yml b/circle.yml
deleted file mode 100644
index 2d94a47..0000000
--- a/circle.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-dependencies:
-  override:
-    - yarn
-  cache_directories:
-    - ~/.cache/yarn
-machine:
-  environment:
-    PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
-  node:
-    version: 8.5.0
-test:
-  override:
-    - yarn test
-deployment:
-  release:
-    # release on tagged versions
-    tag: /v[0-9]+(\.[0-9]+)*/
-    owner: iopipe
-    commands:
-      - npm run build
-      - cp .npmrc-sample .npmrc
-      - npm publish --access public
-
-  acceptanceTests:
-    branch: master
-    owner: iopipe
-    commands:
-      - npm run build
-      - npm run acceptance
diff --git a/src/index.js b/src/index.js
index f02c109..6dd51ef 100644
--- a/src/index.js
+++ b/src/index.js
@@ -36,6 +36,16 @@ function setupTimeoutCapture(wrapperInstance) {
   }, endTime);
 }
 
+process.on('unhandledRejection', error => {
+  const ctx = getInvocationContext();
+  if (ctx && ctx.iopipe && ctx.iopipe.label) {
+    // default node behavior is to log these types of errors
+    console.error(error);
+    ctx.iopipe.label('@iopipe/unhandled-promise-rejection');
+    ctx.iopipe.label('@iopipe/error');
+  }
+});
+
 //TODO: refactor to abide by max-params rule*/
 /*eslint-disable max-params*/
 


Test patch:
diff --git a/testProjects/catchTypeError/yarn.lock b/testProjects/catchTypeError/yarn.lock
new file mode 100644
index 0000000..fb57ccd
--- /dev/null
+++ b/testProjects/catchTypeError/yarn.lock
@@ -0,0 +1,4 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
diff --git a/testProjects/packageJsonConfig/yarn.lock b/testProjects/packageJsonConfig/yarn.lock
new file mode 100644
index 0000000..fb57ccd
--- /dev/null
+++ b/testProjects/packageJsonConfig/yarn.lock
@@ -0,0 +1,4 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
diff --git a/testProjects/promiseRejection/package.json b/testProjects/promiseRejection/package.json
new file mode 100644
index 0000000..152764d
--- /dev/null
+++ b/testProjects/promiseRejection/package.json
@@ -0,0 +1,12 @@
+{
+  "name": "promiseRejectionTest",
+  "version": "0.0.0",
+  "description": "",
+  "main": "handler.test.js",
+  "scripts": {
+    "test": "node test.js"
+  },
+  "dependencies": {
+    "aws-lambda-mock-context": "^3.1.1"
+  }
+}
diff --git a/testProjects/promiseRejection/test.js b/testProjects/promiseRejection/test.js
new file mode 100644
index 0000000..14ea2b6
--- /dev/null
+++ b/testProjects/promiseRejection/test.js
@@ -0,0 +1,37 @@
+const mockContext = require('aws-lambda-mock-context');
+const iopipe = require('./iopipe');
+/*eslint-disable no-process-exit*/
+/*eslint-disable no-console*/
+
+const invocations = [];
+
+const handler = iopipe({
+  token: 'test',
+  plugins: [
+    inv => {
+      invocations.push(inv);
+    }
+  ]
+})((event, context) => {
+  const prom = new Promise(() => {
+    throw new Error('An error from a promise');
+  });
+  prom.then(context.succeed);
+});
+
+const ctx = mockContext({ timeout: 1 });
+handler({}, ctx);
+
+process.on('unhandledRejection', () => {
+  setTimeout(() => {
+    if (
+      invocations[0].report.report.labels.has(
+        '@iopipe/unhandled-promise-rejection'
+      )
+    ) {
+      process.exit(0);
+    }
+    console.error('Promise Error was NOT caught by iopipe.');
+    process.exit(1);
+  }, 10);
+});
diff --git a/testProjects/promiseRejection/yarn.lock b/testProjects/promiseRejection/yarn.lock
new file mode 100644
index 0000000..b82ce80
--- /dev/null
+++ b/testProjects/promiseRejection/yarn.lock
@@ -0,0 +1,23 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+aws-lambda-mock-context@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/aws-lambda-mock-context/-/aws-lambda-mock-context-3.1.1.tgz#29b2d00bf9dc8bc461f749f8585691ee205573d5"
+  dependencies:
+    moment "^2.10.5"
+    pinkie-defer "^1.0.0"
+    uuid "^3.0.1"
+
+moment@^2.10.5:
+  version "2.22.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad"
+
+pinkie-defer@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/pinkie-defer/-/pinkie-defer-1.0.0.tgz#78fcc22116c0da890ac92a808ad370888ded9421"
+
+uuid@^3.0.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
diff --git a/testProjects/rcFileConfig/yarn.lock b/testProjects/rcFileConfig/yarn.lock
new file mode 100644
index 0000000..fb57ccd
--- /dev/null
+++ b/testProjects/rcFileConfig/yarn.lock
@@ -0,0 +1,4 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+