From a4bb642fad8505a52c26169ea73ca999e801d711 Mon Sep 17 00:00:00 2001 From: Evan Botello Date: Thu, 10 Sep 2020 15:15:34 -0400 Subject: [PATCH] tests --- jest.compiled.config.js | 7 + jest.config.js | 189 ++ package-lock.json | 2304 ++++++++++++++- package.json | 12 +- tests/compiled.spec.js | 11 + tests/compound.test.ts | 864 ++++++ tests/report.test.ts | 5771 +++++++++++++++++++++++++++++++++++++ tests/transaction.test.ts | 1110 +++++++ tests/utils/factories.js | 96 + tests/utils/price.js | 9 + 10 files changed, 10358 insertions(+), 15 deletions(-) create mode 100644 jest.compiled.config.js create mode 100644 jest.config.js create mode 100644 tests/compiled.spec.js create mode 100644 tests/compound.test.ts create mode 100644 tests/report.test.ts create mode 100644 tests/transaction.test.ts create mode 100644 tests/utils/factories.js create mode 100644 tests/utils/price.js diff --git a/jest.compiled.config.js b/jest.compiled.config.js new file mode 100644 index 0000000..8f3c1df --- /dev/null +++ b/jest.compiled.config.js @@ -0,0 +1,7 @@ +const base = require('./jest.config.js'); + +base.moduleNameMapper = { + '/src/index$': '/lib/index', +}; + +module.exports = base; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..bf12eda --- /dev/null +++ b/jest.config.js @@ -0,0 +1,189 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // Respect "browser" field in package.json when resolving modules + // browser: false, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/64/0m79fk4566n19z0t6stcjgxm0000gn/T/jest_dx", + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: null, + + // The directory where Jest should output its coverage files + // coverageDirectory: null, + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: null, + + // A path to a custom dependency extractor + // dependencyExtractor: null, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: null, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: null, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "json", + // "jsx", + // "ts", + // "tsx", + // "node" + // ], + + // A map from regular expressions to module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: null, + + // Run tests from one or more projects + // projects: null, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: null, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: null, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: null, + + // This option allows use of a custom test runner + // testRunner: "jasmine2", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + // transform: null, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + transformIgnorePatterns: [ + "/node_modules/", + "/lib/" + ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: null, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a36b544..abedffa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -862,18 +862,316 @@ "to-fast-properties": "^2.0.0" } }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "dev": true, + "requires": { + "@jest/source-map": "^24.9.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", + "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.9.0", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-resolve-dependencies": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "jest-watcher": "^24.9.0", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "slash": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "@jest/environment": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", + "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/fake-timers": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/reporters": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", + "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.1", + "istanbul-reports": "^2.2.6", + "jest-haste-map": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "node-notifier": "^5.4.2", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", + "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "dev": true, + "requires": { + "@jest/test-result": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0" + } + }, + "@jest/transform": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.9.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", + "micromatch": "^3.1.10", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, + "@types/babel__core": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.14.tgz", + "integrity": "sha512-8w9szzKs14ZtBVuP6Wn7nMLRJ0D6dfB0VEBEyRgxrZ/Ln49aNMykrghM2FaNn4FJRzNppCSa0Rv9pBRM5Xc3wg==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.9.1.tgz", + "integrity": "sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q==", + "dev": true, + "requires": { + "jest-diff": "^24.3.0" + } + }, "@types/node": { "version": "11.15.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.23.tgz", "integrity": "sha512-g8xLXIZGAujPv8inZ5y3boiroM8OeFLjYpf8lai3TYFj/A5LBgNfpba91RDE3pdC7MmyeU1oWSV5oQ75zwGTfg==", "dev": true }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, "@types/uuid": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.9.tgz", "integrity": "sha512-XDwyIlt/47l2kWLTzw/mtrpLdB+GPSskR2n/PIcPn+VYhVO77rGhRncIR5GPU0KRzXuqkDO+J5qqrG0Y8P6jzQ==", "dev": true }, + "@types/yargs": { + "version": "13.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.10.tgz", + "integrity": "sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -1062,12 +1360,34 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "abab": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz", + "integrity": "sha512-Eu9ELJWCz/c1e9gTiCY+FceWxcqzjYEbqMgtndnuSqZSUCOL73TWNK2mHfIj4Cw2E/ongOp+JISVNCmovt2KYQ==", + "dev": true + }, "acorn": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true + }, "ajv": { "version": "6.12.4", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", @@ -1092,6 +1412,12 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", @@ -1142,12 +1468,27 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -1195,12 +1536,24 @@ } } }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -1208,12 +1561,51 @@ "dev": true, "optional": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", + "dev": true + }, + "babel-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", + "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "dev": true, + "requires": { + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.9.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", @@ -1235,6 +1627,37 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", + "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", + "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.9.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1302,6 +1725,15 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -1388,6 +1820,29 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -1500,6 +1955,15 @@ "node-releases": "^1.1.61" } }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -1569,6 +2033,12 @@ "unset-value": "^1.0.0" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -1581,6 +2051,21 @@ "integrity": "sha512-9f+r7BW8Qli917mU3j0fUaTweT3f3vnX/Lcs+1C73V+RADmFme+Ih0Br8vONQi3X0lseOe6ZHfsZLCA8MSjxUA==", "dev": true }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1663,6 +2148,12 @@ "tslib": "^1.9.0" } }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -1707,6 +2198,12 @@ "wrap-ansi": "^5.1.0" } }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -1732,6 +2229,15 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -1915,12 +2421,60 @@ "randomfill": "^1.0.3" } }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1942,6 +2496,12 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -1992,6 +2552,12 @@ } } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -2008,6 +2574,18 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -2033,6 +2611,15 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -2045,6 +2632,16 @@ "stream-shift": "^1.0.0" } }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "electron-to-chromium": { "version": "1.3.564", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.564.tgz", @@ -2127,18 +2724,79 @@ "prr": "~1.0.1" } }, - "escalade": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", - "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", - "dev": true - }, - "escape-string-regexp": { + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "dev": true + }, + "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -2149,6 +2807,12 @@ "estraverse": "^4.1.1" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -2172,6 +2836,12 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "events": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", @@ -2188,6 +2858,12 @@ "safe-buffer": "^5.1.1" } }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "dev": true + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -2203,6 +2879,12 @@ "strip-eof": "^1.0.0" } }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -2262,6 +2944,26 @@ "homedir-polyfill": "^1.0.1" } }, + "expect": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -2348,6 +3050,12 @@ } } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2360,6 +3068,21 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -2444,6 +3167,23 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -2515,6 +3255,15 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -2586,6 +3335,37 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2690,12 +3470,53 @@ "parse-passwd": "^1.0.0" } }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -2798,6 +3619,12 @@ } } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2814,6 +3641,21 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -2834,6 +3676,12 @@ } } }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -2871,6 +3719,12 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -2909,12 +3763,36 @@ "isobject": "^3.0.1" } }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -2945,6 +3823,597 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0" + } + }, + "jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", + "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.9.0" + }, + "dependencies": { + "jest-cli": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", + "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", + "dev": true, + "requires": { + "@jest/core": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^13.3.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + } + } + }, + "jest-changed-files": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", + "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", + "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.9.0", + "@jest/types": "^24.9.0", + "babel-jest": "^24.9.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.9.0", + "jest-environment-node": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.9.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-docblock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", + "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", + "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-environment-jsdom": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", + "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", + "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0" + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "jest-jasmine2": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", + "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.9.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", + "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "dev": true, + "requires": { + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-matcher-utils": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", + "dev": true + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "dev": true + }, + "jest-resolve": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", + "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", + "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.9.0" + } + }, + "jest-runner": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", + "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-leak-detector": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", + "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^13.3.0" + }, + "dependencies": { + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + } + } + }, + "jest-serializer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "dev": true + }, + "jest-snapshot": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", + "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "expect": "^24.9.0", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.9.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "jest-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "jest-validate": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", + "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "camelcase": "^5.3.1", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "leven": "^3.1.0", + "pretty-format": "^24.9.0" + } + }, + "jest-watcher": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", + "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.9.0", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -2957,6 +4426,54 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + } + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -2969,12 +4486,24 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", @@ -2984,12 +4513,30 @@ "minimist": "^1.2.5" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -2999,6 +4546,48 @@ "invert-kv": "^2.0.0" } }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -3043,6 +4632,12 @@ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3071,6 +4666,15 @@ "semver": "^5.6.0" } }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", @@ -3133,6 +4737,12 @@ "readable-stream": "^2.0.1" } }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -3172,6 +4782,21 @@ } } }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -3304,6 +4929,12 @@ "to-regex": "^3.0.1" } }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -3316,6 +4947,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -3355,12 +4992,43 @@ } } }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", + "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, "node-releases": { "version": "1.1.61", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", "dev": true }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3377,6 +5045,18 @@ "path-key": "^2.0.0" } }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3414,6 +5094,12 @@ } } }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -3441,6 +5127,16 @@ "object-keys": "^1.0.11" } }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -3459,6 +5155,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -3482,6 +5192,15 @@ "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -3512,6 +5231,12 @@ "p-limit": "^2.0.0" } }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -3548,12 +5273,28 @@ "safe-buffer": "^5.1.1" } }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -3597,6 +5338,23 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "pbkdf2": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", @@ -3610,6 +5368,12 @@ "sha.js": "^2.4.8" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -3623,6 +5387,15 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -3632,12 +5405,36 @@ "find-up": "^3.0.0" } }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -3656,12 +5453,28 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "prompts": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.4" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -3723,6 +5536,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -3754,6 +5573,33 @@ "safe-buffer": "^5.1.0" } }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -3779,6 +5625,15 @@ "picomatch": "^2.2.1" } }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, "regenerate": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", @@ -3859,8 +5714,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true, - "optional": true + "dev": true }, "repeat-element": { "version": "1.1.3", @@ -3874,6 +5728,62 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3964,6 +5874,12 @@ "inherits": "^2.0.1" } }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -3994,6 +5910,50 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -4080,12 +6040,30 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -4257,6 +6235,38 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -4266,6 +6276,23 @@ "extend-shallow": "^3.0.0" } }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -4275,6 +6302,12 @@ "figgy-pudding": "^3.5.1" } }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -4296,6 +6329,12 @@ } } }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -4335,6 +6374,33 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -4346,6 +6412,26 @@ "strip-ansi": "^5.1.0" } }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4364,6 +6450,12 @@ "ansi-regex": "^4.1.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -4379,6 +6471,12 @@ "has-flag": "^3.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -4429,6 +6527,24 @@ } } }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -4448,6 +6564,12 @@ "setimmediate": "^1.0.4" } }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -4502,6 +6624,25 @@ "repeat-string": "^1.6.1" } }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", @@ -4514,6 +6655,30 @@ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -4687,10 +6852,22 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, "uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { "version": "2.0.3", @@ -4698,12 +6875,51 @@ "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, "watchpack": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", @@ -4835,6 +7051,12 @@ } } }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, "webpack": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.40.2.tgz", @@ -4957,6 +7179,32 @@ } } }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -4972,6 +7220,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -4998,6 +7252,32 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index ab9ee82..dac280b 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,11 @@ "description": "A tax report generator for cryptoassets.", "main": "lib/index.js", "scripts": { + "clean": "rm -rf lib", + "prebuild": "npm run clean", "build": "webpack", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest --config ./jest.config.js && jest --config ./jest.compiled.config.js", + "test:build": "npm run build && npm run test" }, "repository": { "type": "git", @@ -23,8 +26,8 @@ "dependencies": { "@babel/polyfill": "7.11.5", "immutable": "4.0.0-rc.12", - "moment": "2.27.0", - "uuid": "8.3.0" + "moment": "^2.27.0", + "uuid": "^3.4.0" }, "devDependencies": { "@babel/core": "7.6.2", @@ -32,10 +35,13 @@ "@babel/plugin-proposal-object-rest-spread": "7.11.0", "@babel/preset-env": "7.6.2", "@babel/preset-typescript": "7.10.4", + "@types/jest": "24.9.1", "@types/node": "11.15.23", "@types/uuid": "3.4.9", + "babel-jest": "24.9.0", "babel-loader": "8.0.6", "bignumber.js": "8.1.1", + "jest": "24.9.0", "webpack": "4.40.2", "webpack-cli": "3.3.9", "webpack-node-externals": "1.7.2" diff --git a/tests/compiled.spec.js b/tests/compiled.spec.js new file mode 100644 index 0000000..13b95e3 --- /dev/null +++ b/tests/compiled.spec.js @@ -0,0 +1,11 @@ +/* + * Test that the compiled bundle exports a function + * instead of an object with a "default" property. + */ + +// eslint-disable-next-line import/no-unresolved +const bundle = require('../lib/index.js'); + +test('commonjs bundle exports function', () => { + expect(typeof bundle).toEqual('function'); +}); diff --git a/tests/compound.test.ts b/tests/compound.test.ts new file mode 100644 index 0000000..1515e9f --- /dev/null +++ b/tests/compound.test.ts @@ -0,0 +1,864 @@ +import createReport from '../src/index'; +import { + repayBorrowFactory, + borrowFactory, + redeemFactory, + mintFactory, + depositFactory, + taxReportFactory, + liqBorrow_BorrowerFactory, + liqBorrow_LiquidatorFactory +} from './utils/factories'; +import { impliedPrice } from './utils/price'; + +describe('Compound Lending', () => { + test('Lend DAI', () => { + const tx = mintFactory({ + timestamp: '2019-01-01T01:00:00Z', + c_token_amount: '4270.51788924', + c_token_code: 'CDAI', + supplied_amount: '89.90136056219178411', + supplied_code: 'DAI' + }); + // In this example, DAI has broken it's peg. It's 2 USD per DAI. + const daiPriceRecord = { + tx_id: tx.tx_id, + timestamp: tx.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '2' + }; + const cDAIprice = impliedPrice({ + amountPriced: tx.supplied_amount, + amountUnpriced: tx.c_token_amount, + price: daiPriceRecord.price + }); + const prices = [ + daiPriceRecord, + { + tx_id: tx.tx_id, + timestamp: tx.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: cDAIprice + } + ]; + const transactions = [tx]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + report: taxReportFactory({ + 2019: { + assets: { + CDAI: { + holdings: '4270.51788924', + bought: '4270.51788924', + sold: '0' + }, + DAI: { + holdings: '-89.90136056219178411', + bought: '0', + sold: '89.90136056219178411' + } + }, + short: [ + { + asset: 'DAI', + asset_amount: '89.90136056219178411', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '179.8' + } + ], + unmatched: [ + { + asset: 'DAI', + asset_amount: '89.90136056219178411', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '179.8', + transaction_id: tx.tx_id + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); + test('Lend and Redeem DAI', () => { + const mintTx = mintFactory({ + timestamp: '2019-01-01T01:00:00Z', + c_token_amount: '4270.51788924', + c_token_code: 'CDAI', + supplied_amount: '89.90136056219178411', + supplied_code: 'DAI' + }); + const redeemTx = redeemFactory({ + tx_id: 'dec961d7-0765-42af-bf57-a88d5356faf7', + timestamp: '2019-01-02T01:00:00Z', + redeem_amount: '89.902620769167106564', + redeem_code: 'DAI', + c_token_amount: '4270.51788924', + c_token_code: 'CDAI' + }); + // In this example, DAI has broken it's peg. It's 2 USD per DAI. + const daiPriceRecordMint = { + tx_id: mintTx.tx_id, + timestamp: '2019-01-01T01:00:00Z', + base_code: 'DAI', + quote_code: 'USD', + price: '2' + }; + const daiPriceRecordRedeem = { + tx_id: 'dec961d7-0765-42af-bf57-a88d5356faf7', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'DAI', + quote_code: 'USD', + price: '2' + }; + const cDAIpriceMint = impliedPrice({ + amountPriced: mintTx.supplied_amount, + amountUnpriced: mintTx.c_token_amount, + price: daiPriceRecordMint.price + }); + const cDAIpriceRedeem = impliedPrice({ + amountPriced: redeemTx.c_token_amount, + amountUnpriced: redeemTx.redeem_amount, + price: daiPriceRecordRedeem.price + }); + const prices = [ + daiPriceRecordMint, + { + tx_id: mintTx.tx_id, + timestamp: '2019-01-01T01:00:00Z', + base_code: 'CDAI', + quote_code: 'USD', + price: cDAIpriceMint + }, + daiPriceRecordRedeem, + { + tx_id: 'dec961d7-0765-42af-bf57-a88d5356faf7', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'CDAI', + quote_code: 'USD', + price: cDAIpriceRedeem + } + ]; + const transactions = [mintTx, redeemTx]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + report: taxReportFactory({ + 2019: { + assets: { + CDAI: { + holdings: '0', + bought: '4270.51788924', + sold: '4270.51788924' + }, + DAI: { + holdings: '0.001260206975322454', + bought: '89.902620769167106564', + sold: '89.90136056219178411' + } + }, + short: [ + { + asset: 'DAI', + asset_amount: '89.90136056219178411', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '179.8' + } + ], + unmatched: [ + { + asset: 'DAI', + asset_amount: '89.90136056219178411', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '179.8', + transaction_id: mintTx.tx_id + } + ], + // In this example, there is 1 USD cent of interest income. + interest_income: [ + { + asset: 'CDAI', + asset_amount: '4270.51788924', + cost_basis: '179.8', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '179.81' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); + test('Lend with partial redemption, followed by lending with full redemption', () => { + // Initial DAI deposit + const deposit = depositFactory({ + timestamp: '2019-01-01T00:10:00Z', + deposit_amount: '1', + deposit_code: 'DAI' + }); + + // Lend DAI + const mintTx1 = mintFactory({ + timestamp: '2019-01-01T01:00:00Z', + c_token_amount: '10', + c_token_code: 'CDAI', + supplied_amount: '1', + supplied_code: 'DAI' + }); + const mintPriceDai1 = { + tx_id: mintTx1.tx_id, + timestamp: mintTx1.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }; + const mintPriceCdai1 = impliedPrice({ + amountPriced: mintTx1.supplied_amount, + amountUnpriced: mintTx1.c_token_amount, + price: mintPriceDai1.price + }); + + // Redeem DAI + const redeemTx1 = redeemFactory({ + tx_id: 'dec961d7-0765-42af-bf57-a88d5356faf7', + timestamp: '2019-01-02T01:00:00Z', + redeem_amount: '1', + redeem_code: 'DAI', + c_token_amount: '5', + c_token_code: 'CDAI' + }); + const redeemPriceDai1 = { + tx_id: redeemTx1.tx_id, + timestamp: redeemTx1.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }; + const redeemPriceCdai1 = impliedPrice({ + amountPriced: redeemTx1.c_token_amount, + amountUnpriced: redeemTx1.redeem_amount, + price: redeemPriceDai1.price + }); + + // Lend DAI again + const mintTx2 = mintFactory({ + timestamp: '2019-01-03T01:00:00Z', + // time has passed, exchange rate increase by 100 factor + c_token_amount: '1000', + c_token_code: 'CDAI', + supplied_amount: '1', + supplied_code: 'DAI' + }); + const mintPriceDai2 = { + tx_id: mintTx2.tx_id, + timestamp: mintTx2.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }; + const mintPriceCdai2 = impliedPrice({ + amountPriced: mintTx2.supplied_amount, + amountUnpriced: mintTx2.c_token_amount, + price: mintPriceDai2.price + }); + + // Redeem DAI in FULL + const redeemTx2 = redeemFactory({ + tx_id: 'yyc961d7-0765-42af-bf57-a88d5356faf7', + timestamp: '2019-01-04T01:00:00Z', + redeem_amount: '3', + redeem_code: 'DAI', + c_token_amount: '1005', + c_token_code: 'CDAI' + }); + const redeemPriceDai2 = { + tx_id: redeemTx2.tx_id, + timestamp: redeemTx2.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }; + const redeemPriceCdai2 = impliedPrice({ + amountPriced: redeemTx2.c_token_amount, + amountUnpriced: redeemTx2.redeem_amount, + price: redeemPriceDai2.price + }); + + const prices = [ + // deposit price + { + tx_id: deposit.tx_id, + timestamp: deposit.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }, + // first mint prices + mintPriceDai1, + { + tx_id: mintTx1.tx_id, + timestamp: mintTx1.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: mintPriceCdai1 + }, + // first redeem prices + redeemPriceDai1, + { + tx_id: redeemTx1.tx_id, + timestamp: redeemTx1.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: redeemPriceCdai1 + }, + // second mint prices + mintPriceDai2, + { + tx_id: mintTx2.tx_id, + timestamp: mintTx2.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: mintPriceCdai2 + }, + // second redeem prices + redeemPriceDai2, + { + tx_id: redeemTx2.tx_id, + timestamp: redeemTx2.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: redeemPriceCdai2 + } + ]; + const transactions = [deposit, mintTx1, redeemTx1, mintTx2, redeemTx2]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + report: taxReportFactory({ + 2019: { + assets: { + CDAI: { + holdings: '0', + bought: '1010', + sold: '1010' + }, + DAI: { + holdings: '3', + bought: '5', + sold: '2' + } + }, + short: [ + { + asset: 'DAI', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2019-01-01T00:10:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '1' + }, + { + asset: 'DAI', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2019-01-02T01:00:00Z', + date_sold: '2019-01-03T01:00:00Z', + proceeds: '1' + } + ], + interest_income: [ + { + asset: 'CDAI', + asset_amount: '5', + cost_basis: '0.5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '1' + }, + { + asset: 'CDAI', + asset_amount: '5', + cost_basis: '0.5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-04T01:00:00Z', + proceeds: '0.01' + }, + { + asset: 'CDAI', + asset_amount: '1000', + cost_basis: '1', + date_acquired: '2019-01-03T01:00:00Z', + date_sold: '2019-01-04T01:00:00Z', + proceeds: '2.99' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); + + // TODO: More borrow and repay scenarios. + test('Lend DAI, borrow ETH, repay ETH', () => { + // Initial DAI deposit + const deposit = depositFactory({ + timestamp: '2019-01-01T00:10:00Z', + deposit_amount: '1', + deposit_code: 'DAI' + }); + + // Lend DAI + const mintTx1 = mintFactory({ + timestamp: '2019-01-01T01:00:00Z', + c_token_amount: '10', + c_token_code: 'CDAI', + supplied_amount: '1', + supplied_code: 'DAI' + }); + const mintPriceDai1 = { + tx_id: mintTx1.tx_id, + timestamp: mintTx1.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }; + const mintPriceCdai1 = impliedPrice({ + amountPriced: mintTx1.supplied_amount, + amountUnpriced: mintTx1.c_token_amount, + price: mintPriceDai1.price + }); + + // Borrow ETH + const borrowTx1 = borrowFactory({ + timestamp: '2019-01-02T01:00:00Z', + borrow_amount: '1', + borrow_code: 'ETH' + }); + const borrowPriceEth = { + tx_id: borrowTx1.tx_id, + timestamp: borrowTx1.timestamp, + base_code: 'ETH', + quote_code: 'USD', + price: '1' + }; + + // Repay ETH + const repayTx1 = repayBorrowFactory({ + timestamp: '2019-01-03T01:00:00Z', + repay_amount: '1', + repay_code: 'ETH' + }); + const repayPriceEth = { + tx_id: repayTx1.tx_id, + timestamp: repayTx1.timestamp, + base_code: 'ETH', + quote_code: 'USD', + price: '1' + }; + + const prices = [ + // deposit price + { + tx_id: deposit.tx_id, + timestamp: deposit.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }, + // first mint prices + mintPriceDai1, + { + tx_id: mintTx1.tx_id, + timestamp: mintTx1.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: mintPriceCdai1 + }, + borrowPriceEth, + repayPriceEth + ]; + const transactions = [deposit, mintTx1, borrowTx1, repayTx1]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + report: taxReportFactory({ + 2019: { + assets: { + CDAI: { + bought: '10', + sold: '0', + holdings: '10' + }, + DAI: { + bought: '1', + sold: '1', + holdings: '0' + }, + ETH: { + bought: '1', + holdings: '0', + sold: '1' + } + }, + short: [ + { + asset: 'DAI', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2019-01-01T00:10:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '1' + }, + { + asset: 'ETH', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2019-01-02T01:00:00Z', + date_sold: '2019-01-03T01:00:00Z', + proceeds: '1' + } + ], + borrow_repayments: [ + { + asset: 'ETH', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2019-01-02T01:00:00Z', + date_sold: '2019-01-03T01:00:00Z', + proceeds: '1' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); +}); + +describe('Compound Liquidations', () => { + test('Single liquidation; no prior borrow or lend', () => { + const liquidation = liqBorrow_BorrowerFactory({ + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-01T00:10:00Z', + liquidate_amount: '1', + liquidate_code: 'CDAI' + }); + const transactions = [liquidation]; + // price value does not matter because liquidation + // is not a taxable event. during liquidation, the borrower: + // - gets to keep the borrowed amount + // - loses collateral (the amount lent to compound which means he/she loses cTokens). + + // the price record is required by the model but essentially does not matter + // The disposal reduces any lots, but it does not result in a taxable sale. + const cDaiPriceRecord = { + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-01T00:10:00Z', + base_code: 'CDAI', + quote_code: 'USD', + price: '1' + }; + const prices = [cDaiPriceRecord]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + }, + report: taxReportFactory({ + '2019': { + assets: { + CDAI: { bought: '0', holdings: '-1', sold: '1' } + }, + compound_liquidations_borrower: [ + { + asset: 'CDAI', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2019-01-01T00:10:00Z', + date_sold: '2019-01-01T00:10:00Z', + proceeds: '1' + } + ], + income: [], + interest_income: [], + long: [], + lost: [], + short: [], + unmatched: [ + { + asset: 'CDAI', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2019-01-01T00:10:00Z', + date_sold: '2019-01-01T00:10:00Z', + proceeds: '1', + transaction_id: 'c5ebb42f-8f56-495a-a349-858283844808' + } + ] + } + }) + }; + expect(received).toEqual(expected); + }); + test('Single liquidation; borrow and collateral setup', () => { + const mint = mintFactory({ + timestamp: '2019-01-01T01:00:00Z', + c_token_amount: '4270.51788924', + c_token_code: 'CDAI', + supplied_amount: '89.90136056219178411', + supplied_code: 'DAI' + }); + const liquidation = liqBorrow_BorrowerFactory({ + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-03T00:10:00Z', + liquidate_amount: '1', + liquidate_code: 'CDAI' + }); + // Borrow ETH + const borrowTx1 = borrowFactory({ + timestamp: '2019-01-02T01:00:00Z', + borrow_amount: '1', + borrow_code: 'ETH' + }); + const transactions = [mint, borrowTx1, liquidation]; + const borrowPriceEth = { + tx_id: borrowTx1.tx_id, + timestamp: borrowTx1.timestamp, + base_code: 'ETH', + quote_code: 'USD', + price: '100' + }; + const daiPriceRecord = { + tx_id: mint.tx_id, + timestamp: mint.timestamp, + base_code: 'DAI', + quote_code: 'USD', + price: '1' + }; + const cDaiPriceRecordMint = { + tx_id: mint.tx_id, + timestamp: mint.timestamp, + base_code: 'CDAI', + quote_code: 'USD', + price: '1' + }; + // price value does not matter because liquidation + // is not a taxable event. during liquidation, the borrower: + // - gets to keep the borrowed amount + // - loses collateral (the amount lent to compound which means he/she loses cTokens). + + // the price record is required by the model but essentially does not matter + // The disposal reduces any lots, but it does not result in a taxable sale. + const cDaiPriceRecord = { + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-03T00:10:00Z', + base_code: 'CDAI', + quote_code: 'USD', + price: '1' + }; + const prices = [daiPriceRecord, cDaiPriceRecordMint, cDaiPriceRecord, borrowPriceEth]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + }, + report: taxReportFactory({ + '2019': { + assets: { + ETH: { bought: '1', holdings: '1', sold: '0' }, + CDAI: { bought: '4270.51788924', holdings: '4269.51788924', sold: '1' }, + DAI: { bought: '0', holdings: '-89.90136056219178411', sold: '89.90136056219178411' } + }, + compound_liquidations_borrower: [ + { + asset: 'CDAI', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-03T00:10:00Z', + proceeds: '1' + } + ], + income: [], + interest_income: [], + long: [], + lost: [], + short: [ + { + asset: 'DAI', + asset_amount: '89.90136056219178411', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '4270.52' + } + ], + unmatched: [ + { + asset: 'DAI', + asset_amount: '89.90136056219178411', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '4270.52', + transaction_id: mint.tx_id + } + ] + } + }) + }; + expect(received).toEqual(expected); + }); + test('Single liquidation; liquidator side', () => { + const liquidation = liqBorrow_LiquidatorFactory({ + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-01T00:10:00Z', + repay_amount: '1', + repay_code: 'ZRX', + seize_amount: '50', + seize_code: 'CETH' + }); + const transactions = [liquidation]; + const zrxPrice = { + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-01T00:10:00Z', + base_code: 'ZRX', + quote_code: 'USD', + price: '1' + }; + const cTokenPrice = { + tx_id: 'c5ebb42f-8f56-495a-a349-858283844808', + timestamp: '2019-01-01T00:10:00Z', + base_code: 'CETH', + quote_code: 'USD', + price: '1' + }; + const prices = [zrxPrice, cTokenPrice]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + }, + report: taxReportFactory({ + '2019': { + assets: { + ZRX: { bought: '0', holdings: '-1', sold: '1' }, + CETH: { bought: '50', holdings: '50', sold: '0' } + }, + short: [ + { + asset: 'ZRX', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2019-01-01T00:10:00Z', + date_sold: '2019-01-01T00:10:00Z', + proceeds: '1' + } + ], + unmatched: [ + { + asset: 'ZRX', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2019-01-01T00:10:00Z', + date_sold: '2019-01-01T00:10:00Z', + proceeds: '1', + transaction_id: 'c5ebb42f-8f56-495a-a349-858283844808' + } + ] + } + }) + }; + expect(expected).toEqual(received); + }); +}); diff --git a/tests/report.test.ts b/tests/report.test.ts new file mode 100644 index 0000000..c987e88 --- /dev/null +++ b/tests/report.test.ts @@ -0,0 +1,5771 @@ +import createReport from '../src/index'; +import { PriceMethod, CostBasisMethod } from '../src/types'; +import { + taxReportFactory, + depositFactory, + withdrawalFactory, + lostFactory, + incomeFactory, + tradeFactory +} from './utils/factories'; + +test('Empty transactions', () => { + try { + createReport({ + transactions: [], + prices: [], + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + } catch (error) { + expect(error.name).toEqual('EmptyParamError'); + expect(error.message).toEqual( + 'The "transactions" config parameter must include at least one object.' + ); + } +}); + +describe('crypto/fiat long term gains', () => { + const trade_1 = tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + side: 'BUY', + base_amount: '1', + base_code: 'ETH', + quote_amount: '200', + quote_code: 'USD', + fee_amount: '5', + fee_code: 'USD' + }); + const trade_2 = tradeFactory({ + timestamp: '2019-01-04T12:00:00Z', + side: 'BUY', + base_amount: '1', + base_code: 'ETH', + quote_amount: '153.19', + quote_code: 'USD', + fee_amount: '2', + fee_code: 'USD' + }); + const trade_3 = tradeFactory({ + timestamp: '2020-01-31T13:00:00Z', + side: 'SELL', + base_amount: '1', + base_code: 'ETH', + quote_amount: '250', + quote_code: 'USD', + fee_amount: '5', + fee_code: 'USD' + }); + const transactions = [trade_1, trade_2, trade_3]; + const prices = [ + { + tx_id: trade_1.tx_id, + timestamp: trade_1.timestamp, + base_code: 'ETH', + quote_code: 'USD', + price: '200' + }, + { + tx_id: trade_1.tx_id, + timestamp: trade_1.timestamp, + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: trade_2.tx_id, + timestamp: trade_2.timestamp, + base_code: 'ETH', + quote_code: 'USD', + price: '153.19' + }, + { + tx_id: trade_2.tx_id, + timestamp: trade_2.timestamp, + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: trade_3.tx_id, + timestamp: trade_3.timestamp, + base_code: 'ETH', + quote_code: 'USD', + price: '250' + }, + { + tx_id: trade_3.tx_id, + timestamp: trade_3.timestamp, + base_code: 'USD', + quote_code: 'USD', + price: '1' + } + ]; + + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + proceeds: '200', + date_sold: '2018-01-01T09:30:00Z', + cost_basis: '0', + asset_amount: '205', + date_acquired: '2018-01-01T09:30:00Z', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + ETH: { + holdings: '2', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-360.19', + bought: '0', + sold: '155.19' + } + }, + unmatched: [ + { + asset: 'USD', + proceeds: '153.19', + date_sold: '2019-01-04T12:00:00Z', + cost_basis: '0', + asset_amount: '155.19', + date_acquired: '2019-01-04T12:00:00Z', + transaction_id: trade_2.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '1', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-115.19', + bought: '245', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2020-01-31T13:00:00Z', + proceeds: '245', + cost_basis: '205' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + ETH: { + holdings: '2', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-360.19', + bought: '0', + sold: '155.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '1', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-115.19', + bought: '245', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2020-01-31T13:00:00Z', + proceeds: '245', + cost_basis: '205' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'HIFO' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + ETH: { + holdings: '2', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-360.19', + bought: '0', + sold: '155.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '1', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-115.19', + bought: '245', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2020-01-31T13:00:00Z', + proceeds: '245', + cost_basis: '155.19' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'LIFO' + } + }; + expect(received).toEqual(expected); + }); +}); + +describe('crypto/fiat short term gains', () => { + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '300', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-03T01:00:00Z', + tx_id: '3', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '200', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-04T01:00:00Z', + tx_id: '4', + side: 'SELL', + base_amount: '3', + base_code: 'BTC', + quote_amount: '1000', + quote_code: 'USD', + fee_amount: '3', + fee_code: 'USD' + }) + ]; + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }, + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '300' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '200' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '333.3333333' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + } + ]; + + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '394', + bought: '997', + sold: '603' + } + }, + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '101' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '201' + } + ], + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '394', + bought: '997', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '101' + } + ] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '394', + bought: '997', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '101' + } + ] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); +}); + +describe('crypto/fiat short term unmatched gains', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '333.3333333' + } + ]; + const transactions = [ + tradeFactory({ + timestamp: '2018-01-04T01:00:00Z', + tx_id: '1', + side: 'SELL', + base_amount: '3', + base_code: 'BTC', + quote_amount: '1000', + quote_code: 'USD', + fee_amount: '3', + fee_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '-3', + bought: '0', + sold: '3' + }, + USD: { + holdings: '997', + bought: '997', + sold: '0' + } + }, + short: [ + { + asset: 'BTC', + asset_amount: '3', + cost_basis: '0', + date_acquired: '2018-01-04T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '997' + } + ], + unmatched: [ + { + asset: 'BTC', + asset_amount: '3', + cost_basis: '0', + date_acquired: '2018-01-04T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '997', + transaction_id: '1' + } + ] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); +}); + +describe('crypto/fiat short term gains with null fees', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '101' + }, + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '301' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '201' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '332.33' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + } + ]; + + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '101', + quote_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '301', + quote_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-03T01:00:00Z', + tx_id: '3', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '201', + quote_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-04T01:00:00Z', + tx_id: '4', + side: 'SELL', + base_amount: '3', + base_code: 'BTC', + quote_amount: '997', + quote_code: 'USD' + }) + ]; + + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '394', + bought: '997', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '101', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '301', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '201', + transaction_id: '3' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '101' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '201' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '394', + bought: '997', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '101', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '301', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '201', + transaction_id: '3' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '101' + } + ] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '394', + bought: '997', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '101', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '301', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '201', + transaction_id: '3' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '101' + } + ] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); +}); + +describe('crypto/crypto long term gains', () => { + const trade_1 = tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + side: 'BUY', + base_amount: '1', + base_code: 'ETH', + quote_amount: '200', + quote_code: 'USD', + fee_amount: '5', + fee_code: 'USD' + }); + const trade_2 = tradeFactory({ + timestamp: '2019-01-04T12:00:00Z', + side: 'BUY', + base_amount: '1', + base_code: 'ETH', + quote_amount: '153.19', + quote_code: 'USD', + fee_amount: '2', + fee_code: 'USD' + }); + const trade_3 = tradeFactory({ + timestamp: '2019-01-31T13:00:00Z', + side: 'BUY', + base_amount: '2', + base_code: 'ETH', + quote_amount: '250', + quote_code: 'USD', + fee_amount: '5', + fee_code: 'USD' + }); + const trade_4 = tradeFactory({ + timestamp: '2020-03-03T15:00:00Z', + side: 'SELL', + base_amount: '1', + base_code: 'ETH', + quote_amount: '0.07', + quote_code: 'BTC', + fee_amount: '0.001', + fee_code: 'BTC' + }); + const trade_5 = tradeFactory({ + timestamp: '2021-05-01T20:00:00Z', + side: 'SELL', + base_amount: '0.069', + base_code: 'BTC', + quote_amount: '345', + quote_code: 'USD', + fee_amount: '4', + fee_code: 'USD' + }); + const transactions = [trade_1, trade_2, trade_3, trade_4, trade_5]; + const prices = [ + { + tx_id: trade_1.tx_id, + timestamp: '2018-01-01T09:30:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '200' + }, + { + tx_id: trade_1.tx_id, + timestamp: '2018-01-01T09:30:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: trade_2.tx_id, + timestamp: '2019-01-04T12:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '153.19' + }, + { + tx_id: trade_2.tx_id, + timestamp: '2019-01-04T12:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: trade_3.tx_id, + timestamp: '2019-01-31T13:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '125' + }, + { + tx_id: trade_3.tx_id, + timestamp: '2019-01-31T13:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: trade_4.tx_id, + timestamp: '2020-03-03T15:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '135' + }, + { + tx_id: trade_4.tx_id, + timestamp: '2020-03-03T15:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '1925' + }, + { + tx_id: trade_5.tx_id, + timestamp: '2021-05-01T20:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '5000' + }, + { + tx_id: trade_5.tx_id, + timestamp: '2021-05-01T20:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + } + ]; + describe('Use Base', () => { + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + bought: '0', + sold: '0', + holdings: '0' + }, + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '4', + bought: '3', + sold: '0' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '410.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + }, + { + asset: 'USD', + asset_amount: '255', + cost_basis: '0', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2019-01-31T13:00:00Z', + proceeds: '250', + transaction_id: trade_3.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '0' + }, + BTC: { + holdings: '0.069', + bought: '0.069', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2020-03-03T15:00:00Z', + proceeds: '133.08', + cost_basis: '205' + } + ] + }, + 2021: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '0' + }, + USD: { + holdings: '-274.19', + bought: '341', + sold: '0' + }, + BTC: { + holdings: '0', + bought: '0', + sold: '0.069' + } + }, + long: [ + { + asset: 'BTC', + asset_amount: '0.069', + date_acquired: '2020-03-03T15:00:00Z', + date_sold: '2021-05-01T20:00:00Z', + proceeds: '341', + cost_basis: '135' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '4', + bought: '3', + sold: '0' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '410.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + }, + { + asset: 'USD', + asset_amount: '255', + cost_basis: '0', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2019-01-31T13:00:00Z', + proceeds: '250', + transaction_id: trade_3.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '0' + }, + BTC: { + holdings: '0.069', + bought: '0.069', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2020-03-03T15:00:00Z', + proceeds: '133.08', + cost_basis: '127.5' + } + ] + }, + 2021: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '0' + }, + USD: { + holdings: '-274.19', + bought: '341', + sold: '0' + }, + BTC: { + holdings: '0', + bought: '0', + sold: '0.069' + } + }, + long: [ + { + asset: 'BTC', + asset_amount: '0.069', + date_acquired: '2020-03-03T15:00:00Z', + date_sold: '2021-05-01T20:00:00Z', + proceeds: '341', + cost_basis: '135' + } + ] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '4', + bought: '3', + sold: '0' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '410.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + }, + { + asset: 'USD', + asset_amount: '255', + cost_basis: '0', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2019-01-31T13:00:00Z', + proceeds: '250', + transaction_id: trade_3.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '0' + }, + BTC: { + holdings: '0.069', + bought: '0.069', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2020-03-03T15:00:00Z', + proceeds: '133.08', + cost_basis: '205' + } + ] + }, + 2021: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '0' + }, + USD: { + holdings: '-274.19', + bought: '341', + sold: '0' + }, + BTC: { + holdings: '0', + bought: '0', + sold: '0.069' + } + }, + long: [ + { + asset: 'BTC', + asset_amount: '0.069', + date_acquired: '2020-03-03T15:00:00Z', + date_sold: '2021-05-01T20:00:00Z', + proceeds: '341', + cost_basis: '135' + } + ] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('Use Quote', () => { + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'FIFO', + decimalPlaces: 3 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '4', + bought: '3', + sold: '0' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '410.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + }, + { + asset: 'USD', + asset_amount: '255', + cost_basis: '0', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2019-01-31T13:00:00Z', + proceeds: '250', + transaction_id: trade_3.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '0' + }, + BTC: { + holdings: '0.069', + bought: '0.069', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2020-03-03T15:00:00Z', + proceeds: '132.83', + cost_basis: '205' + } + ] + }, + 2021: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '0' + }, + USD: { + holdings: '-274.19', + bought: '341', + sold: '0' + }, + BTC: { + holdings: '0', + bought: '0', + sold: '0.069' + } + }, + long: [ + { + asset: 'BTC', + asset_amount: '0.069', + date_acquired: '2020-03-03T15:00:00Z', + date_sold: '2021-05-01T20:00:00Z', + proceeds: '341', + cost_basis: '134.75' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'LIFO', + decimalPlaces: 3 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ] + }, + 2019: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '4', + bought: '3', + sold: '0' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '410.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + }, + { + asset: 'USD', + asset_amount: '255', + cost_basis: '0', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2019-01-31T13:00:00Z', + proceeds: '250', + transaction_id: trade_3.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '0' + }, + BTC: { + holdings: '0.069', + bought: '0.069', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2020-03-03T15:00:00Z', + proceeds: '132.83', + cost_basis: '127.5' + } + ] + }, + 2021: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '0' + }, + USD: { + holdings: '-274.19', + bought: '341', + sold: '0' + }, + BTC: { + holdings: '0', + bought: '0', + sold: '0.069' + } + }, + long: [ + { + asset: 'BTC', + asset_amount: '0.069', + date_acquired: '2020-03-03T15:00:00Z', + date_sold: '2021-05-01T20:00:00Z', + proceeds: '341', + cost_basis: '134.75' + } + ] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'HIFO', + decimalPlaces: 3 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '1', + bought: '1', + sold: '0' + }, + USD: { + holdings: '-205', + bought: '0', + sold: '205' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '205', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '200', + transaction_id: trade_1.tx_id + } + ], + lost: [], + interest_income: [] + }, + 2019: { + assets: { + BTC: { + holdings: '0', + bought: '0', + sold: '0' + }, + ETH: { + holdings: '4', + bought: '3', + sold: '0' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '410.19' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '155.19', + cost_basis: '0', + date_acquired: '2019-01-04T12:00:00Z', + date_sold: '2019-01-04T12:00:00Z', + proceeds: '153.19', + transaction_id: trade_2.tx_id + }, + { + asset: 'USD', + asset_amount: '255', + cost_basis: '0', + date_acquired: '2019-01-31T13:00:00Z', + date_sold: '2019-01-31T13:00:00Z', + proceeds: '250', + transaction_id: trade_3.tx_id + } + ] + }, + 2020: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '1' + }, + USD: { + holdings: '-615.19', + bought: '0', + sold: '0' + }, + BTC: { + holdings: '0.069', + bought: '0.069', + sold: '0' + } + }, + long: [ + { + asset: 'ETH', + asset_amount: '1', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2020-03-03T15:00:00Z', + proceeds: '132.83', + cost_basis: '205' + } + ] + }, + 2021: { + assets: { + ETH: { + holdings: '3', + bought: '0', + sold: '0' + }, + USD: { + holdings: '-274.19', + bought: '341', + sold: '0' + }, + BTC: { + holdings: '0', + bought: '0', + sold: '0.069' + } + }, + long: [ + { + asset: 'BTC', + asset_amount: '0.069', + date_acquired: '2020-03-03T15:00:00Z', + date_sold: '2021-05-01T20:00:00Z', + proceeds: '341', + cost_basis: '134.75' + } + ] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + }); +}); + +describe('crypto/fiat short term gains with free BTC and null fees', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '3000' + }, + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '2', + timestamp: '2019-02-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '0' + }, + { + tx_id: '2', + timestamp: '2019-02-01T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '3', + timestamp: '2019-03-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '3500' + }, + { + tx_id: '3', + timestamp: '2019-03-01T01:00:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + } + ]; + + const transactions = [ + tradeFactory({ + timestamp: '2019-01-01T01:00:00Z', + tx_id: '1', + side: 'NONE', + base_amount: '1', + base_code: 'BTC', + quote_amount: '3000', + quote_code: 'USD' + }), + tradeFactory({ + timestamp: '2019-02-01T01:00:00Z', + tx_id: '2', + side: 'NONE', + base_amount: '1', + base_code: 'BTC', + quote_amount: '0', + quote_code: 'USD' + }), + tradeFactory({ + timestamp: '2019-03-01T01:00:00Z', + tx_id: '3', + side: 'NONE', + base_amount: '3500', + base_code: 'USD', + quote_amount: '1', + quote_code: 'BTC' + }) + ]; + + describe('Use quote price', () => { + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '500', + bought: '3500', + sold: '3000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '3000', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '3000', + transaction_id: '1' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '500', + bought: '3500', + sold: '3000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '3000', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '3000', + transaction_id: '1' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-02-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '0' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '500', + bought: '3500', + sold: '3000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '3000', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '3000', + transaction_id: '1' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('Use base price', () => { + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '500', + bought: '3500', + sold: '3000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '3000', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '3000', + transaction_id: '1' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '500', + bought: '3500', + sold: '3000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '3000', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '3000', + transaction_id: '1' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-02-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '0' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '500', + bought: '3500', + sold: '3000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '3000', + cost_basis: '0', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '3000', + transaction_id: '1' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); +}); + +describe('crypto/crypto short term gains', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '300' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '200' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '4000' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '60' + } + ]; + + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '300', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-03T01:00:00Z', + tx_id: '3', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '200', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-04T01:00:00Z', + tx_id: '4', + side: 'SELL', + base_amount: '3', + base_code: 'BTC', + quote_amount: '200', + quote_code: 'ETH', + fee_amount: '2', + fee_code: 'ETH' + }) + ]; + + describe('Use Base', () => { + let decimalPlaces = 2; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + }, + USD: { + holdings: '-603', + bought: '0', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '101' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '201' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces + } + }); + const expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + }, + USD: { + holdings: '-603', + bought: '0', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '101' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces + } + }); + const expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + }, + USD: { + holdings: '-603', + bought: '0', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '101' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('Use Quote', () => { + const decimalPlaces = 2; + test('FIFO', () => { + const expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + }, + USD: { + holdings: '-603', + bought: '0', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '101' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '201' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'QUOTE' + } + }; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'FIFO', + decimalPlaces + } + }); + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'LIFO', + decimalPlaces + } + }); + const expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + }, + USD: { + holdings: '-603', + bought: '0', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '101' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'QUOTE', + costBasisMethod: 'HIFO', + decimalPlaces + } + }); + const expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + }, + USD: { + holdings: '-603', + bought: '0', + sold: '603' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '101', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '100', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '301', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '300', + transaction_id: '2' + }, + { + asset: 'USD', + asset_amount: '201', + cost_basis: '0', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '200', + transaction_id: '3' + } + ], + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '301' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '201' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '3960', + cost_basis: '101' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + }); +}); + +describe('fiat -> crypto -> fiat -> crypto scenario', () => { + test('No fiat sales in short/long array', () => { + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-02T09:30:00Z', + tx_id: '2', + side: 'NONE', + base_amount: '1000', + base_code: 'USD', + quote_amount: '1', + quote_code: 'BTC' + }), + tradeFactory({ + timestamp: '2018-01-03T09:30:00Z', + tx_id: '3', + side: 'NONE', + base_amount: '20', + base_code: 'ETH', + quote_amount: '500', + quote_code: 'USD' + }) + ]; + + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T09:30:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }, + { + tx_id: '1', + timestamp: '2018-01-01T09:30:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '2', + timestamp: '2018-01-02T09:30:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '2', + timestamp: '2018-01-02T09:30:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '1000' + }, + { + tx_id: '3', + timestamp: '2018-01-03T09:30:00Z', + base_code: 'USD', + quote_code: 'USD', + price: '1' + }, + { + tx_id: '3', + timestamp: '2018-01-03T09:30:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '25' + } + ]; + + const result = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + const expected = { + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + }, + report: taxReportFactory({ + '2018': { + assets: { + BTC: { + bought: '1', + holdings: '0', + sold: '1' + }, + ETH: { + bought: '20', + holdings: '20', + sold: '0' + }, + USD: { + bought: '1000', + holdings: '400', + sold: '600' + } + }, + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '100', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-02T09:30:00Z', + proceeds: '1000' + } + ], + unmatched: [ + { + asset: 'USD', + asset_amount: '100', + cost_basis: '0', + date_acquired: '2018-01-01T09:30:00Z', + date_sold: '2018-01-01T09:30:00Z', + proceeds: '100', + transaction_id: '1' + } + ], + lost: [], + interest_income: [] + } + }) + }; + expect(result).toEqual(expected); + }); +}); + +describe('deposit assets', () => { + describe('crypto/fiat short term gains - deposit BTC - null fees on deposit', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '300' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '200' + }, + { + tx_id: '4', + timestamp: '2018-01-04T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '333.3333333' + } + ]; + + const transactions = [ + depositFactory({ + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + deposit_amount: '1', + deposit_code: 'BTC' + }), + depositFactory({ + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + deposit_amount: '1', + deposit_code: 'BTC' + }), + depositFactory({ + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + deposit_amount: '1', + deposit_code: 'BTC' + }), + tradeFactory({ + timestamp: '2018-01-04T01:00:00Z', + tx_id: '4', + side: 'SELL', + base_amount: '3', + base_code: 'BTC', + quote_amount: '1000', + quote_code: 'USD', + fee_amount: '3', + fee_code: 'USD' + }) + ]; + + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '997', + bought: '997', + sold: '0' + } + }, + income: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '100' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '300' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '200' + } + ], + unmatched: [], + lost: [], + interest_income: [] + } + }), + config: { + price_method: 'BASE', + cost_basis_method: 'FIFO' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '997', + bought: '997', + sold: '0' + } + }, + income: [], + unmatched: [], + long: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '300' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '200' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '100' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '3', + sold: '3' + }, + USD: { + holdings: '997', + bought: '997', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-03T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '200' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '300' + }, + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-04T01:00:00Z', + proceeds: '332.33', + cost_basis: '100' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + + describe('crypto/fiat short term gains with - deposit BTC - null fees', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '3000' + }, + { + tx_id: '2', + timestamp: '2019-02-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '0' + }, + { + tx_id: '3', + timestamp: '2019-03-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '3500' + } + ]; + + const transactions = [ + depositFactory({ + timestamp: '2019-01-01T01:00:00Z', + tx_id: '1', + deposit_amount: '1', + deposit_code: 'BTC' + }), + depositFactory({ + timestamp: '2019-02-01T01:00:00Z', + tx_id: '2', + deposit_amount: '1', + deposit_code: 'BTC' + }), + tradeFactory({ + timestamp: '2019-03-01T01:00:00Z', + tx_id: '3', + side: 'NONE', + base_amount: '3500', + base_code: 'USD', + quote_amount: '1', + quote_code: 'BTC' + }) + ]; + + describe('use_quote price', () => { + const priceOption = 'QUOTE'; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '3500', + bought: '3500', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '3500', + bought: '3500', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-02-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '0' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '3500', + bought: '3500', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('use_base price', () => { + const priceOption = 'BASE'; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '3500', + bought: '3500', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '3500', + bought: '3500', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-02-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '0' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '1', + bought: '2', + sold: '1' + }, + USD: { + holdings: '3500', + bought: '3500', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-03-01T01:00:00Z', + proceeds: '3500', + cost_basis: '3000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + }); + + describe('crypto/crypto short term gains - deposit BTC - fees with fee code === quote code', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '1000' + }, + { + tx_id: '2', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '1000' + }, + { + tx_id: '2', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '100' + } + ]; + const transactions = [ + depositFactory({ + timestamp: '2019-01-01T01:00:00Z', + tx_id: '1', + deposit_amount: '10', + deposit_code: 'BTC' + }), + tradeFactory({ + timestamp: '2019-01-02T01:00:00Z', + tx_id: '2', + side: 'SELL', + base_amount: '5', + base_code: 'BTC', + quote_amount: '200', + quote_code: 'ETH', + fee_amount: '2', + fee_code: 'ETH' + }) + ]; + + describe('use quote price', () => { + const priceOption = 'QUOTE'; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '19800', + cost_basis: '5000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '19800', + cost_basis: '5000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '19800', + cost_basis: '5000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('use base price', () => { + const priceOption = 'BASE'; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '4800', + cost_basis: '5000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('LIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'LIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '4800', + cost_basis: '5000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'LIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('HIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'HIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '198', + bought: '198', + sold: '0' + } + }, + unmatched: [], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '4800', + cost_basis: '5000' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'HIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + }); + + test('crypto/crypto short term gains - deposit BTC, deposit BNB - fees with fee code !== quote code', () => { + const prices = [ + { + tx_id: '0', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'BNB', + quote_code: 'USD', + price: '50' + }, + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '1000' + }, + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '50' + }, + { + tx_id: '1', + timestamp: '2019-01-01T01:00:00Z', + base_code: 'BNB', + quote_code: 'USD', + price: '50' + }, + { + tx_id: '2', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '1000' + }, + { + tx_id: '2', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'ETH', + quote_code: 'USD', + price: '100' + }, + { + tx_id: '2', + timestamp: '2019-01-02T01:00:00Z', + base_code: 'BNB', + quote_code: 'USD', + price: '100' + } + ]; + + const transactions = [ + depositFactory({ + timestamp: '2019-01-01T01:00:00Z', + tx_id: '0', + deposit_amount: '10', + deposit_code: 'BNB' + }), + depositFactory({ + timestamp: '2019-01-01T01:00:00Z', + tx_id: '1', + deposit_amount: '10', + deposit_code: 'BTC', + fee_code: 'BNB', + fee_amount: '2' + }), + tradeFactory({ + timestamp: '2019-01-02T01:00:00Z', + tx_id: '2', + side: 'SELL', + base_amount: '5', + base_code: 'BTC', + quote_amount: '200', + quote_code: 'ETH', + fee_amount: '2', + fee_code: 'BNB' + }) + ]; + + ['BASE', 'QUOTE'].forEach((priceMethod: PriceMethod) => { + ['FIFO', 'LIFO', 'HIFO'].forEach((costBasisMethod: CostBasisMethod) => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod, + costBasisMethod, + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2019: { + assets: { + BTC: { + holdings: '5', + bought: '10', + sold: '5' + }, + ETH: { + holdings: '200', + bought: '200', + sold: '0' + }, + BNB: { + holdings: '6', + bought: '10', + sold: '4' + } + }, + short: [ + { + asset: 'BNB', + asset_amount: '2', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-01T01:00:00Z', + proceeds: '100', + cost_basis: '100' + }, + { + asset: 'BTC', + asset_amount: '5', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: priceMethod === 'QUOTE' ? '19800' : '4800', + cost_basis: '5050' + }, + { + asset: 'BNB', + asset_amount: '2', + date_acquired: '2019-01-01T01:00:00Z', + date_sold: '2019-01-02T01:00:00Z', + proceeds: '200', + cost_basis: '100' + } + ] + } + }), + config: { + cost_basis_method: costBasisMethod, + price_method: priceMethod + } + }; + expect(received).toEqual(expected); + }); + }); + }); +}); + +describe('withdraw assets', () => { + describe('crypto/fiat short term gains - withdraw BTC - fees with fee code === quote code', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '11000' + } + ]; + + const transactions = [ + depositFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + deposit_amount: '10', + deposit_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }), + withdrawalFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + withdrawal_amount: '1', + withdrawal_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }) + ]; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '9', + bought: '10', + sold: '1' + }, + USD: { + bought: '0', + holdings: '-2', + sold: '2' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '1', + transaction_id: '2' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '10999', + cost_basis: '10000.1' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('crypto/crypto short term gains - withdraw - null fees ', () => { + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'None', + base_amount: '10', + base_code: 'BTC', + quote_amount: '1000', + quote_code: 'USD' + }), + withdrawalFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + withdrawal_amount: '1', + withdrawal_code: 'BTC' + }) + ]; + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '4000' + } + ]; + describe('use quote price', () => { + const priceOption = 'QUOTE'; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '9', + bought: '10', + sold: '1' + }, + USD: { + holdings: '-1000', + bought: '0', + sold: '1000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1000', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1000', + transaction_id: '1' + } + ], + long: [], + income: [], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '4000', + cost_basis: '100' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'QUOTE' + } + }; + expect(received).toEqual(expected); + }); + }); + describe('use base price', () => { + const priceOption = 'BASE'; + test('FIFO', () => { + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: priceOption, + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '9', + bought: '10', + sold: '1' + }, + USD: { + holdings: '-1000', + bought: '0', + sold: '1000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1000', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1000', + transaction_id: '1' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '4000', + cost_basis: '100' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + }); + }); +}); + +describe('receive income', () => { + test('single income txn is reported', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + } + ]; + + const transactions = [ + incomeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + income_amount: '10', + income_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '10', + bought: '10', + sold: '0' + }, + USD: { + bought: '0', + holdings: '-1', + sold: '1' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1', + transaction_id: '1' + } + ], + long: [], + income: [ + { + asset: 'BTC', + asset_amount: '10', + date_acquired: '2018-01-01T01:00:00Z', + basis_amount: '100001', + basis: 'USD', + tx_id: '1' + } + ], + short: [], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('income and deposit txns reported', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + } + ]; + + const transactions = [ + incomeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + income_amount: '10', + income_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }), + depositFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + deposit_amount: '10', + deposit_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '20', + bought: '20', + sold: '0' + }, + USD: { + bought: '0', + holdings: '-2', + sold: '2' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '1', + transaction_id: '2' + } + ], + long: [], + income: [ + { + asset: 'BTC', + asset_amount: '10', + date_acquired: '2018-01-01T01:00:00Z', + basis_amount: '100001', + basis: 'USD', + tx_id: '1' + } + ], + short: [], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('single income, short term gains on trade', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '2', + timestamp: '2018-01-02T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '3', + timestamp: '2018-01-03T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '11000' + } + ]; + + const transactions = [ + incomeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + income_amount: '10', + income_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }), + depositFactory({ + timestamp: '2018-01-02T01:00:00Z', + tx_id: '2', + deposit_amount: '10', + deposit_code: 'BTC', + fee_amount: '1', + fee_code: 'USD' + }), + tradeFactory({ + timestamp: '2018-01-03T01:00:00Z', + tx_id: '3', + side: 'SELL', + base_amount: '1', + base_code: 'BTC', + quote_amount: '11000', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '19', + bought: '20', + sold: '1' + }, + USD: { + holdings: '10997', + bought: '10999', + sold: '2' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1', + transaction_id: '1' + }, + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-02T01:00:00Z', + date_sold: '2018-01-02T01:00:00Z', + proceeds: '1', + transaction_id: '2' + } + ], + long: [], + income: [ + { + asset: 'BTC', + asset_amount: '10', + date_acquired: '2018-01-01T01:00:00Z', + basis_amount: '100001', + basis: 'USD', + tx_id: '1' + } + ], + short: [ + { + asset: 'BTC', + asset_amount: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-03T01:00:00Z', + proceeds: '10999', + cost_basis: '10000.1' + } + ], + lost: [], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); +}); + +describe('lost crypto or fiat', () => { + test('buy bitcoin, lose equal bitcoin amount', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '2', + timestamp: '2018-01-01T02:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + } + ]; + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '10000', + quote_code: 'USD' + }), + lostFactory({ + timestamp: '2018-01-01T02:00:00Z', + tx_id: '2', + lost_amount: '1', + lost_code: 'BTC' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0', + bought: '1', + sold: '1' + }, + USD: { + bought: '0', + holdings: '-10000', + sold: '10000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '10000', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '10000', + transaction_id: '1' + } + ], + lost: [ + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '10000', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '10000' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('buy bitcoin, lose more bitcoin than bought', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '2', + timestamp: '2018-01-01T02:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + } + ]; + + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '10000', + quote_code: 'USD' + }), + lostFactory({ + timestamp: '2018-01-01T02:00:00Z', + tx_id: '2', + lost_amount: '2', + lost_code: 'BTC' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '-1', + bought: '1', + sold: '2' + }, + USD: { + bought: '0', + holdings: '-10000', + sold: '10000' + } + }, + // First the program reduces the existing TaxLot, + // then it records an unmatched disposal. + unmatched: [ + { + asset: 'USD', + asset_amount: '10000', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '10000', + transaction_id: '1' + }, + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T02:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '10000', + transaction_id: '2' + } + ], + long: [], + income: [], + short: [], + lost: [ + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '10000', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '10000' + }, + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T02:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '10000' + } + ], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('buy bitcoin, lose fraction of that bitcoin', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T01:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + }, + { + tx_id: '2', + timestamp: '2018-01-01T02:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + } + ]; + + const transactions = [ + tradeFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '10000', + quote_code: 'USD' + }), + lostFactory({ + timestamp: '2018-01-01T02:00:00Z', + tx_id: '2', + lost_amount: '0.5', + lost_code: 'BTC' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '0.5', + bought: '1', + sold: '0.5' + }, + USD: { + bought: '0', + holdings: '-10000', + sold: '10000' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '10000', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '10000', + transaction_id: '1' + } + ], + long: [], + income: [], + short: [], + lost: [ + { + asset: 'BTC', + asset_amount: '0.5', + cost_basis: '5000', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '5000' + } + ], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('lose bitcoin unmatched', () => { + const prices = [ + { + tx_id: '1', + timestamp: '2018-01-01T02:00:00Z', + base_code: 'BTC', + quote_code: 'USD', + price: '10000' + } + ]; + + const transactions = [ + lostFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + lost_amount: '1', + lost_code: 'BTC' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + BTC: { + holdings: '-1', + bought: '0', + sold: '1' + } + }, + unmatched: [ + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '10000', + transaction_id: '1' + } + ], + lost: [ + { + asset: 'BTC', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '10000' + } + ] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('deposit USD, lose equal USD amount', () => { + const prices = []; + + const transactions = [ + depositFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + deposit_amount: '1', + deposit_code: 'USD' + }), + lostFactory({ + timestamp: '2018-01-01T02:00:00Z', + tx_id: '2', + lost_amount: '1', + lost_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + USD: { + bought: '1', + holdings: '0', + sold: '1' + } + }, + unmatched: [], + long: [], + income: [], + short: [], + lost: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '1', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '1' + } + ], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('deposit USD, lose fraction of that USD amount', () => { + const prices = []; + const transactions = [ + depositFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + deposit_amount: '1', + deposit_code: 'USD' + }), + lostFactory({ + timestamp: '2018-01-01T02:00:00Z', + tx_id: '2', + lost_amount: '0.5', + lost_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + USD: { + bought: '1', + holdings: '0.5', + sold: '0.5' + } + }, + unmatched: [], + long: [], + income: [], + short: [], + lost: [ + { + asset: 'USD', + asset_amount: '0.5', + cost_basis: '0.5', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T02:00:00Z', + proceeds: '0.5' + } + ], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); + test('lose USD unmatched', () => { + const prices = []; + + const transactions = [ + lostFactory({ + timestamp: '2018-01-01T01:00:00Z', + tx_id: '1', + lost_amount: '1', + lost_code: 'USD' + }) + ]; + const received = createReport({ + transactions, + prices, + config: { + localCurrency: 'USD', + priceMethod: 'BASE', + costBasisMethod: 'FIFO', + decimalPlaces: 2 + } + }); + let expected = { + report: taxReportFactory({ + 2018: { + assets: { + USD: { + holdings: '-1', + bought: '0', + sold: '1' + } + }, + unmatched: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1', + transaction_id: '1' + } + ], + long: [], + income: [], + short: [], + lost: [ + { + asset: 'USD', + asset_amount: '1', + cost_basis: '0', + date_acquired: '2018-01-01T01:00:00Z', + date_sold: '2018-01-01T01:00:00Z', + proceeds: '1' + } + ], + interest_income: [] + } + }), + config: { + cost_basis_method: 'FIFO', + price_method: 'BASE' + } + }; + expect(received).toEqual(expected); + }); +}); diff --git a/tests/transaction.test.ts b/tests/transaction.test.ts new file mode 100644 index 0000000..e962aa4 --- /dev/null +++ b/tests/transaction.test.ts @@ -0,0 +1,1110 @@ +import { Map as IMap, List } from 'immutable'; +import { BigNumber } from 'bignumber.js'; + +import TaxLot from '../src/taxLot'; +import Disposal from '../src/disposal'; +import { makeLotsAndDisposals } from '../src/accounting'; +import { + depositFactory, + withdrawalFactory, + lostFactory, + incomeFactory, + tradeFactory +} from './utils/factories'; + +/* + Every TRADE transaction scenario to base future tests on. + + TRADE. + fiat/crypto + Taxed currency and fiat trade portion are equal (USD) + Note: + Test BOTH crypto/fiat and fiat/crypto trades. + Test both BASE & QUOTE price method in every test, since they should be equal. + BTC/USD (crypto/fiat) + side buy + no fee (test base & quote price method) + with fee fiat (test base & quote price method) + with fee crypto (test base & quote price method) + with foreign fee crypto (test base & quote price method) + side sell + no fee (test base & quote price method) + with fee fiat (test base & quote price method) + with fee crypto (test base & quote price method) + with foreign fee crypto (test base & quote price method) + USD/BTC (fiat/crypto) + side buy + no fee (test base & quote price method) + with fee fiat (test base & quote price method) + with fee crypto (test base & quote price method) + with foreign fee crypto (test base & quote price method) + side sell + no fee (test base & quote price method) + with fee fiat (test base & quote price method) + with fee crypto (test base & quote price method) + with foreign fee crypto (test base & quote price method) + FUTURE: Taxed currency and fiat trade portion are unequal (Example: GBP & USD) + crypto/crypto + use BASE prices + side buy + no fee + with fee fiat + with fee crypto + with foreign fee crypto + side sell + no fee + with fee fiat + with fee crypto + with foreign fee crypto + use QUOTE prices + side buy + no fee + with fee fiat + with fee crypto + with foreign fee crypto + side sell + no fee + with fee fiat + with fee crypto + with foreign fee crypto + */ + +/* + Currently tested scenarios: + TRADE type + side buy + no fee (test base & quote price method) + with fee fiat (test base & quote price method) + with fee crypto (test base & quote price method) + with foreign fee crypto (test base & quote price method) + side sell + no fee (test base & quote price method) + with fee fiat (test base & quote price method) + with fee crypto (test base & quote price method) + with foreign fee crypto (test base & quote price method) + */ + +// TODO: remove references to NONE once we refactor NONE out of the TRADE data model. +// NONE seems kind of unnecssary since we only use it to identify trades that were +// We can use another less important property to identify these trades. +// TODO: Need to test DEPOSIT when fee matches deposit asset and when it does not. + +describe('TRADE transaction', () => { + /* + * The function we are testing does not differentiate + * between crypto/crypto trades and crypto/fiat trades. + * We test crypto/fiat but it does not matter. + */ + describe('crypto base / fiat quote', () => { + describe('BUY trade', () => { + test('type: TRADE, assets: crypto/fiat, side: BUY, fee: none, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('100'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1' + }) + ]) + }); + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('100') + ); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('100') + ); + }); + test('type: TRADE, assets: crypto/fiat, side: BUY | NONE, fee: fiat, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'NONE', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '5', + fee_code: 'USD' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + basisCode: 'USD', + basisAmount: new BigNumber('105'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('105'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('105') + ); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('105') + ); + }); + test('type: TRADE, assets: crypto/fiat, side: BUY, fee: crypto, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'BUY', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '0.1', + fee_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('0.9'), + basisCode: 'USD', + basisAmount: new BigNumber('110'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('100'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('122.22222222222222222222') + ); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('122.22222222222222222222') + ); + }); + test('type: TRADE, assets: crypto/fiat, side: BUY | NONE, fee: foreign crypto, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'NONE', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'ETH' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }), + IMap({ + base_code: 'ETH', + quote_code: 'USD', + price: '5' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + basisCode: 'USD', + basisAmount: new BigNumber('105'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('100'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1' + }), + new Disposal({ + unix: 1514799000, + assetCode: 'ETH', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('5'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('105') + ); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('105') + ); + }); + }); + describe('SELL trade', () => { + test('type: TRADE, assets: crypto/fiat, side: SELL, fee: none, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'SELL', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('100'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1' + }) + ]) + }); + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('1')); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('1')); + }); + test('type: TRADE, assets: crypto/fiat, side: SELL, fee: fiat, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'SELL', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '10', + fee_code: 'USD' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('90'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('90'), + transactionId: '1' + }) + ]) + }); + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('1.11111111111111111111') + ); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual( + new BigNumber('1.11111111111111111111') + ); + }); + test('type: TRADE, assets: crypto/fiat, side: SELL, fee: crypto, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'SELL', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '0.1', + fee_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('100'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1.1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('90'), + transactionId: '1' + }) + ]) + }); + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('1')); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('1')); + }); + test('type: TRADE, assets: crypto/fiat, side: BUY, fee: foreign crypto, price method: BASE & QUOTE', () => { + const transactions = List([ + IMap( + tradeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + side: 'SELL', + base_amount: '1', + base_code: 'BTC', + quote_amount: '100', + quote_code: 'USD', + fee_amount: '1', + fee_code: 'ETH' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'USD', + quote_code: 'USD', + price: '1' + }), + IMap({ + base_code: 'ETH', + quote_code: 'USD', + price: '5' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const actualQuoteMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'QUOTE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'USD', + assetAmount: new BigNumber('100'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('95'), + transactionId: '1' + }), + new Disposal({ + unix: 1514799000, + assetCode: 'ETH', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('5'), + transactionId: '1' + }) + ]) + }); + expect(actualBaseMethod.equals(expected)).toEqual(true); + expect(actualQuoteMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('1')); + expect(actualQuoteMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('1')); + }); + }); + }); +}); + +describe('DEPOSIT transaction', () => { + test('TaxLot from DEPOSIT', () => { + const transactions = List([ + IMap( + depositFactory({ + tx_id: '1', + deposit_amount: '1', + deposit_code: 'BTC', + timestamp: '2018-01-01T09:30:00Z' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: false + }) + ]), + disposalList: List([]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('100')); + }); +}); + +describe('INCOME transaction', () => { + test('TaxLot from INCOME', () => { + const transactions = List([ + IMap( + incomeFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + income_amount: '1', + income_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([ + new TaxLot({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + basisCode: 'USD', + basisAmount: new BigNumber('100'), + transactionId: '1', + isIncome: true + }) + ]), + disposalList: List([]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + + expect(actualBaseMethod.getIn(['taxLotList', 0]).pricePerUnit).toEqual(new BigNumber('100')); + }); +}); + +describe('WITHDRAWAL transaction', () => { + test('with no fee', () => { + const transactions = List([ + IMap( + withdrawalFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + withdrawal_amount: '1', + withdrawal_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + }); + test('with fee matching withdrawn asset', () => { + const transactions = List([ + IMap( + withdrawalFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + withdrawal_amount: '1', + withdrawal_code: 'BTC', + fee_amount: '0.01', + fee_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1.01'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('99'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + }); + test('with fee not matching', () => { + const transactions = List([ + IMap( + withdrawalFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + withdrawal_amount: '1', + withdrawal_code: 'BTC', + fee_amount: '1', + fee_code: 'ETH' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'ETH', + quote_code: 'USD', + price: '10' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('90'), + transactionId: '1' + }), + new Disposal({ + unix: 1514799000, + assetCode: 'ETH', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('10'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + }); +}); + +describe('LOST transaction', () => { + test('with no fee', () => { + const transactions = List([ + IMap( + lostFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + lost_amount: '1', + lost_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('100'), + transactionId: '1', + isLost: true + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + }); + test('with fee matching lost asset', () => { + const transactions = List([ + IMap( + lostFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + lost_amount: '1', + lost_code: 'BTC', + fee_amount: '0.01', + fee_code: 'BTC' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1.01'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('99'), + transactionId: '1', + isLost: true + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + }); + test('with fee not matching', () => { + const transactions = List([ + IMap( + lostFactory({ + timestamp: '2018-01-01T09:30:00Z', + tx_id: '1', + lost_amount: '1', + lost_code: 'BTC', + fee_amount: '1', + fee_code: 'ETH' + }) + ) + ]); + const priceTable = IMap({ + '1': List([ + IMap({ + base_code: 'BTC', + quote_code: 'USD', + price: '100' + }), + IMap({ + base_code: 'ETH', + quote_code: 'USD', + price: '10' + }) + ]) + }); + const actualBaseMethod = makeLotsAndDisposals({ + transactions, + priceTable, + priceMethod: 'BASE', + localCurrency: 'USD' + }); + const expected = IMap({ + taxLotList: List([]), + disposalList: List([ + new Disposal({ + unix: 1514799000, + assetCode: 'BTC', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('90'), + transactionId: '1', + isLost: true + }), + new Disposal({ + unix: 1514799000, + assetCode: 'ETH', + assetAmount: new BigNumber('1'), + proceedsCode: 'USD', + proceedsAmount: new BigNumber('10'), + transactionId: '1' + }) + ]) + }); + + expect(actualBaseMethod.equals(expected)).toEqual(true); + }); +}); diff --git a/tests/utils/factories.js b/tests/utils/factories.js new file mode 100644 index 0000000..3c03a67 --- /dev/null +++ b/tests/utils/factories.js @@ -0,0 +1,96 @@ +import uuidv4 from 'uuid/v4'; + +export const taxReportFactory = (report) => { + return Object.entries(report).reduce((reduction, [reportYear, reportEntries]) => { + reduction[reportYear] = { + income: [], + long: [], + short: [], + unmatched: [], + lost: [], + interest_income: [], + ...reportEntries + }; + return reduction; + }, {}); +}; + +export const incomeFactory = (income) => ({ + tx_id: uuidv4(), + tx_type: 'INCOME', + ...income +}); + +export const lostFactory = (lost) => ({ + tx_id: uuidv4(), + tx_type: 'LOST', + ...lost +}); + +export const withdrawalFactory = (withdrawal) => ({ + tx_id: uuidv4(), + tx_type: 'WITHDRAWAL', + ...withdrawal +}); + +export const depositFactory = (mint) => { + return { + tx_id: uuidv4(), + tx_type: 'DEPOSIT', + ...mint + }; +}; + +export const mintFactory = (mint) => { + return { + tx_id: uuidv4(), + tx_type: 'COMPOUND_MINT', + ...mint + }; +}; + +export const borrowFactory = (mint) => { + return { + tx_id: uuidv4(), + tx_type: 'COMPOUND_BORROW', + ...mint + }; +}; + +export const redeemFactory = (redeem) => { + return { + tx_id: uuidv4(), + tx_type: 'COMPOUND_REDEEM', + ...redeem + }; +}; + +export const repayBorrowFactory = (repay) => { + return { + tx_id: uuidv4(), + tx_type: 'COMPOUND_REPAYBORROW', + ...repay + }; +}; + +export const liqBorrow_BorrowerFactory = (tx) => { + return { + tx_id: uuidv4(), + tx_type: 'COMPOUND_LIQUIDATEBORROW_BORROWER', + ...tx + }; +}; + +export const liqBorrow_LiquidatorFactory = (tx) => { + return { + tx_id: uuidv4(), + tx_type: 'COMPOUND_LIQUIDATEBORROW_LIQUIDATOR', + ...tx + }; +}; + +export const tradeFactory = (trade) => ({ + tx_id: uuidv4(), + tx_type: 'TRADE', + ...trade +}); diff --git a/tests/utils/price.js b/tests/utils/price.js new file mode 100644 index 0000000..7949bf5 --- /dev/null +++ b/tests/utils/price.js @@ -0,0 +1,9 @@ +import { BigNumber } from 'bignumber.js'; + +export const impliedPrice = ({ amountPriced, amountUnpriced, price }) => { + return new BigNumber(amountPriced) + .dividedBy(new BigNumber(amountUnpriced)) + .multipliedBy(new BigNumber(price)) + .dp(18) + .toString(10); +};