diff --git a/.gitignore b/.gitignore index 2c78a49..a3a494e 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ typings/ /yarn.lock package-lock.json .idea +coverage diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..c74fb53 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }]], +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..47c88f6 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,195 @@ +/* eslint-disable max-len */ +/* + * 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, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/tmp/jest_rs", + + // 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: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "babel", + + // 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: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // 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: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // 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 or to arrays of 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: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // 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: undefined, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // 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: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // 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: undefined, + + // 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: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // 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: undefined, + + // 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, +}; diff --git a/package.json b/package.json index f94b4dd..ab3bef6 100644 --- a/package.json +++ b/package.json @@ -29,15 +29,19 @@ "moment": "^2.22.2", "mongodb": "2.2.34", "query-string": "5.0.1", - "uuid": "3.1.0" + "uuid": "^8.3.0" }, "devDependencies": { + "@babel/core": "^7.12.3", + "@babel/preset-env": "^7.12.1", "@commitlint/cli": "^8.1.0", "@commitlint/config-conventional": "^8.1.0", + "babel-jest": "^26.6.1", "eslint": "^6.2.1", "eslint-config-airbnb-base": "^14.0.0", "eslint-plugin-import": "^2.18.2", - "husky": "^3.0.4" + "husky": "^3.0.4", + "jest": "^26.6.1" }, "keywords": [ "koa", @@ -49,7 +53,8 @@ ], "scripts": { "lint": "eslint *.js src/*.js src/**/*.js", - "lint-fix": "eslint --fix *.js src/*.js src/**/*.js" + "lint-fix": "eslint --fix *.js src/*.js src/**/*.js", + "test": "NODE_ENV=test jest --detectOpenHandles --forceExit --unhandled-rejections=strict --coverage" }, "commitlint": { "extends": [ diff --git a/src/infra/uuid.js b/src/infra/uuid.js index 87e0d19..03f2c62 100644 --- a/src/infra/uuid.js +++ b/src/infra/uuid.js @@ -1,8 +1,8 @@ /* eslint-disable max-len */ const crypto = require('crypto'); -const v1 = require('uuid/v1'); -const v4 = require('uuid/v4'); -const v5 = require('uuid/v5'); +const { + v1, v4, v5, validate: validateUUID, +} = require('uuid'); function create(version = 4) { function generateId() { @@ -19,6 +19,15 @@ function create(version = 4) { return generateId; } +function validate(string) { + if (validateUUID(string)) { + return true; + } + + // validates v4c + return typeof string === 'string' && /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.test(string); +} + module.exports = { - v1, v4, v5, v4c: create(4), v6: create(6), + v1, v4, v5, v4c: create(4), v6: create(6), validate, }; diff --git a/test/infra/uuid.test.js b/test/infra/uuid.test.js new file mode 100644 index 0000000..3f5a6e8 --- /dev/null +++ b/test/infra/uuid.test.js @@ -0,0 +1,24 @@ +const uuid = require('../../src/infra/uuid'); + +test('isValid', () => { + expect(uuid.validate('00000000-0000-0000-0000-000000000000')).toBe(true); + expect(uuid.validate('cfbeb8b6-dfce-4c33-a9de-acb715e82388')).toBe(true); + expect(uuid.validate('39f5f864-3439-4ec0-9420-f54a77cb51a1')).toBe(true); + expect(uuid.validate('1eb17906-64b4-4330-19a6-1ab37ccb9522')).toBe(true); + expect(uuid.validate('1eb156c0-4cfa-4360-bd6c-cd70e09707b5')).toBe(true); + + const uuidV1 = uuid.v1(); + expect(uuid.validate(uuidV1)).toBe(true); + + const uuidV4 = uuid.v4(); + expect(uuid.validate(uuidV4)).toBe(true); + + const uuidV4c = uuid.v4c(); + expect(uuid.validate(uuidV4c)).toBe(true); + + const uuidV5 = uuid.v5('Hello, World!', '1b671a64-40d5-491e-99b0-da01ff1f3341'); + expect(uuid.validate(uuidV5)).toBe(true); + + const uuidV6 = uuid.v6(); + expect(uuid.validate(uuidV6)).toBe(true); +});