├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── index.d.ts ├── package.json ├── src ├── factory.js ├── index.js ├── scalars.js └── types.js └── tests ├── index.js └── schema.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [], 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true, 8 | }, 9 | "globals": { 10 | "__DEV__": true, 11 | "__SERVER__": true 12 | }, 13 | "rules": { 14 | // Strict mode 15 | "strict": [2, "never"], 16 | 17 | // Code style 18 | //"indent": [2, 2], 19 | "quotes": [2, "single"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | lib 4 | npm-debug.log 5 | package-lock.json 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tests 3 | .babelrc 4 | .eslintrc 5 | .travis.yml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | os: linux 3 | language: node_js 4 | 5 | node_js: 6 | - "14" 7 | - "12" 8 | - "10" 9 | - "9" 10 | - "8" 11 | - "7.1" 12 | - "6.9" 13 | 14 | install: 15 | - npm install graphql@^${GRAPHQL_VERSION} 16 | - npm install 17 | 18 | env: 19 | - GRAPHQL_VERSION=v0.6 20 | - GRAPHQL_VERSION=v0.7 21 | - GRAPHQL_VERSION=v0.8 22 | - GRAPHQL_VERSION=v0.9 23 | - GRAPHQL_VERSION=v0.10 24 | - GRAPHQL_VERSION=v0.11 25 | - GRAPHQL_VERSION=v0.12 26 | - GRAPHQL_VERSION=v0.13 27 | - GRAPHQL_VERSION=v14.0 28 | - GRAPHQL_VERSION=v15.0 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## [1.7.0] - 2022-09-14 3 | - Updated graphql peer dependency to ^16 4 | 5 | ## [1.6.0] - 2020-07-03 6 | - Updated graphql peer dependency 7 | - Dropped support for NodeJs < 6.9 8 | - Improved .gitignore 9 | 10 | ## [1.5.1] - 2019-07-10 11 | ### Fixed 12 | - TS Password definition 13 | 14 | ## [1.5.0] - 2018-11-22 15 | - Updated graphql peer dependency 16 | 17 | ## [1.4.0] - 2018-05-20 18 | - Updated graphql peer dependency 19 | 20 | ## [1.3.0] - 2018-01-31 21 | - Updated graphql peer dependency 22 | - Updated dev dependencies 23 | - Adjusted test cases - graphql ^0.12.0 is not throwing errors when validation fails, instead it is populating an error property in the result object. Testing will fail with older graphql versions. 24 | 25 | ## [1.2.0] - 2017-10-02 26 | - Updated peer dependency 27 | 28 | ## [1.1.0] - 2017-06-28 29 | - Added typescript definitions 30 | - Added editorconfig 31 | 32 | ## [1.0.0] - 2017-06-19 33 | - Updated peer dependency 34 | 35 | ## [0.7.3] - 2017-05-05 36 | - Updated peer dependency 37 | 38 | ## [0.7.2] - 2016-11-15 39 | - Updated dev dependencies 40 | 41 | ## [0.7.1] - 2016-11-14 42 | - Updated graphql dependencies 43 | 44 | ## [0.7.0] - 2016-09-20 45 | ### Improved 46 | - Added badges for dev and peer dependencies 47 | 48 | ### Updated dependencies 49 | - graphql 50 | - eslint 51 | 52 | ### Fixed 53 | - Properly import Factory 54 | 55 | ## [0.6.0] - 2016-06-09 56 | ### Improved 57 | - graphql is now a peerDependecy 58 | 59 | ### Fixed 60 | - Date validation 61 | 62 | ## [0.5.1] - 2016-06-08 63 | ### Updated dependencies 64 | - graphql to ^0.6.0 65 | 66 | ## [0.5.0] - 2016-06-08 67 | ### Imporoved 68 | - Properly parse values 69 | 70 | ## [0.4.0] - 2016-05-10 71 | ### Added 72 | - Export factory and GraphQLCustomScalarType 73 | - Test against nodeJS >= 4.4 && <= 6.1 74 | 75 | ### Removed 76 | - Support for iojs 77 | - Support for nodeJS < 4.4 78 | 79 | ## [0.2.0] - 2015-12-12 80 | ### Added 81 | - GraphQLDateTime 82 | 83 | ## [0.1.1] - 2015-12-11 84 | ### Improved 85 | - Packaging for NPM 86 | 87 | ## [0.1.0] - 2015-12-11 88 | ### Added 89 | - Custom scalar factory 90 | - RegExp type factory 91 | - GraphQLURL 92 | - GraphQLLimitedString 93 | - GraphQLPassword 94 | - CHANGELOG 95 | 96 | ### Improved 97 | - Test cases 98 | - README 99 | - Improved NPM config, exlude files that are not needed to use the library 100 | 101 | ### Deprecated 102 | - Dropped support for NodeJs < 0.11.0 103 | 104 | ## [0.0.0] - 2015-12-9 105 | ### Added 106 | - GraphQLEmail 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Custom Types 2 | [![npm version](https://badge.fury.io/js/graphql-custom-types.svg)](https://badge.fury.io/js/graphql-custom-types) [![Build Status](https://secure.travis-ci.org/stylesuxx/graphql-custom-types.png?branch=master)](https://travis-ci.org/stylesuxx/graphql-custom-types) [![Dependency Status](https://david-dm.org/stylesuxx/graphql-custom-types.svg)](https://david-dm.org/stylesuxx/graphql-custom-types) [![devDependencies Status](https://david-dm.org/stylesuxx/graphql-custom-types/dev-status.svg)](https://david-dm.org/stylesuxx/graphql-custom-types?type=dev) [![peerDependencies Status](https://david-dm.org/stylesuxx/graphql-custom-types/peer-status.svg)](https://david-dm.org/stylesuxx/graphql-custom-types?type=peer) 3 | 4 | > This is a collection of custom GraphQL types that I tend to reuse quite often so I packed them into a module. 5 | 6 | ## Available Types 7 | Let me give you an overview of the available types. If you need more detail about how to use them, check *schema.js* in the tests folder. 8 | 9 | ### Scalar 10 | The primitive types, aka everything that may be represented as a string. The ones with parameters you need to instantiate with *new* and pass according parameters, the others may be used as are. 11 | 12 | * GraphQLEmail 13 | * GraphQLURL 14 | * GraphQLDateTime 15 | * GraphQLLimitedString(min, max, alphabet) 16 | * GraphQLPassword(min, max, alphabet, complexity) 17 | * GraphQLUUID 18 | 19 | *complexity* default options: 20 | ```JavaScript 21 | { 22 | alphaNumeric: false, 23 | mixedCase: false, 24 | specialChars: false 25 | } 26 | ``` 27 | 28 | ## Installation 29 | Most likely you already will have it, but do not forget to also install *graphql*, since it is required as peer dependency: 30 | ```Bash 31 | npm install graphql graphql-custom-types --save 32 | ``` 33 | 34 | ## Usage 35 | ```JavaScript 36 | import { 37 | GraphQLEmail, 38 | GraphQLURL, 39 | GraphQLDateTime, 40 | GraphQLLimitedString, 41 | GraphQLPassword, 42 | GraphQLUUID 43 | } from 'graphql-custom-types'; 44 | ``` 45 | 46 | And use it in your Schema as you would use any other type. 47 | 48 | ## Development 49 | Contributions are very welcome, please feel free to submit a type. If you do so make sure there are test cases in place. 50 | 51 | ### Testing 52 | The test suite may be invoked by running: 53 | ```Bash 54 | npm run test 55 | ``` 56 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable max-classes-per-file 2 | import {GraphQLScalarType} from "graphql"; 3 | 4 | declare namespace graphqlCustomTypes { 5 | 6 | type IASTParser = (ast: {kind: string, value: string}) => TResult; 7 | 8 | interface IFactoryOptions { 9 | name: string; 10 | regex: RegExp; 11 | description: string; 12 | error?: string; 13 | } 14 | 15 | class Factory { 16 | public getRegexScalar(options: IFactoryOptions): GraphQLCustomScalarType; 17 | 18 | public getCustomScalar(name: string, description: string, parser: IASTParser): GraphQLCustomScalarType; 19 | } 20 | 21 | class GraphQLCustomScalarType extends GraphQLScalarType { 22 | constructor(name: string, description: string, parser: IASTParser); 23 | } 24 | 25 | const GraphQLEmail: GraphQLCustomScalarType; 26 | 27 | const GraphQLURL: GraphQLCustomScalarType; 28 | 29 | const GraphQLDateTime: GraphQLCustomScalarType; 30 | 31 | const GraphQLUUID: GraphQLCustomScalarType; 32 | 33 | class GraphQLPassword extends GraphQLCustomScalarType { 34 | constructor(min?: number, max?: number, alphabet?: string, complexity?: { 35 | alphaNumeric?: boolean, 36 | mixedCase?: boolean, 37 | specialChars?: boolean, 38 | }); 39 | } 40 | 41 | class GraphQLLimitedString extends GraphQLCustomScalarType { 42 | constructor(min?: number, max?: number, alphabet?: boolean) 43 | } 44 | } 45 | 46 | export = graphqlCustomTypes; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-custom-types", 3 | "repository": "stylesuxx/graphql-custom-types", 4 | "version": "1.7.0", 5 | "description": "Collection of custom GraphQL types like Email, URL, password and many more", 6 | "author": "Chris Landa ", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "scripts": { 10 | "clean": "rm -rf lib", 11 | "build": "npm run clean && ./node_modules/.bin/babel src --out-dir lib", 12 | "test": "npm run build && ./node_modules/.bin/babel-node ./tests/index.js", 13 | "lint": "./node_modules/.bin/eslint src tests", 14 | "prepublish": "npm run build", 15 | "preversion": "npm test" 16 | }, 17 | "devDependencies": { 18 | "@babel/cli": "^7.10.4", 19 | "@babel/core": "^7.10.4", 20 | "@babel/node": "^7.10.4", 21 | "@babel/preset-env": "^7.10.4", 22 | "babel-eslint": "^10.1.0", 23 | "eslint": "^7.0.0", 24 | "tape": "^4.11.0" 25 | }, 26 | "peerDependencies": { 27 | "graphql": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" 28 | }, 29 | "keywords": [ 30 | "GraphQL", 31 | "express-graphql", 32 | "GraphQL types" 33 | ], 34 | "engines": { 35 | "node": ">= 6.9" 36 | }, 37 | "types": "./index.d.ts" 38 | } 39 | -------------------------------------------------------------------------------- /src/factory.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLString, 3 | GraphQLScalarType 4 | } from 'graphql'; 5 | import { GraphQLError } from 'graphql/error'; 6 | import { Kind } from 'graphql/language'; 7 | import { GraphQLCustomScalarType } from './types'; 8 | 9 | export class Factory { 10 | getRegexScalar(options) { 11 | const error = options.error || 'Query error: ' + options.name; 12 | 13 | const parser = function(ast) { 14 | if (ast.kind !== Kind.STRING) { 15 | throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]); 16 | } 17 | 18 | var re = options.regex; 19 | if(!re.test(ast.value)) { 20 | throw new GraphQLError(error, [ast]); 21 | } 22 | 23 | return ast.value; 24 | }; 25 | 26 | return this.getCustomScalar(options.name, options.description, parser); 27 | } 28 | 29 | getCustomScalar(name, description, parser) { 30 | return new GraphQLCustomScalarType(name, description, parser); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLEmail, 3 | GraphQLURL, 4 | GraphQLLimitedString, 5 | GraphQLPassword, 6 | GraphQLDateTime, 7 | GraphQLUUID 8 | } from './scalars'; 9 | 10 | import { 11 | GraphQLCustomScalarType 12 | } from './types'; 13 | 14 | import { Factory } from './factory'; 15 | 16 | module.exports = { 17 | Factory, 18 | GraphQLCustomScalarType, 19 | GraphQLEmail, 20 | GraphQLURL, 21 | GraphQLLimitedString, 22 | GraphQLPassword, 23 | GraphQLDateTime, 24 | GraphQLUUID 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/scalars.js: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from 'graphql/error'; 2 | import { Kind } from 'graphql/language'; 3 | import { Factory } from './factory'; 4 | import { GraphQLCustomScalarType } from './types'; 5 | 6 | const factory = new Factory(); 7 | 8 | export const GraphQLEmail = factory.getRegexScalar({ 9 | name: 'Email', 10 | regex: /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i, 11 | description: 'The Email scalar type represents E-Mail addresses compliant to RFC 822.', 12 | error: 'Query error: Not a valid Email address' 13 | }); 14 | 15 | export const GraphQLURL = factory.getRegexScalar({ 16 | name: 'URL', 17 | // RegExp taken from https://gist.github.com/dperini/729294 18 | regex: new RegExp('^(?:(?:https?|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))\\.?)(?::\\d{2,5})?(?:[/?#]\\S*)?$', 'i'), 19 | description: 'The URL scalar type represents URL addresses.', 20 | error: 'Query error: Not a valid URL' 21 | }); 22 | 23 | export const GraphQLUUID = factory.getRegexScalar({ 24 | name: 'UUID', 25 | // https://github.com/chriso/validator.js/blob/master/src/lib/isUUID.js#L7 26 | regex: new RegExp('^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$', 'i'), 27 | description: 'The UUID scalar type represents a UUID.', 28 | error: 'Query error: Not a valid UUID' 29 | }); 30 | 31 | const stringValidator = function(ast) { 32 | if (ast.kind !== Kind.STRING) { 33 | throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]); 34 | } 35 | }; 36 | 37 | const lengthValidator = function(ast, min, max) { 38 | if(ast.value.length < min) { 39 | throw new GraphQLError('Query error: String not long enough', [ast]); 40 | } 41 | 42 | if(max && ast.value.length > max) { 43 | throw new GraphQLError('Query error: String too long', [ast]); 44 | } 45 | }; 46 | 47 | const alphabetValidator = function(ast, alphabet) { 48 | for(var char of ast.value) { 49 | if(alphabet.indexOf(char) < 0) { 50 | throw new GraphQLError('Query error: Invalid character found', [ast]); 51 | } 52 | } 53 | }; 54 | 55 | const complexityValidator = function(ast, options) { 56 | const complexity = options || {}; 57 | const alhpaNumericRe = /^(?=.*[0-9])(?=.*[a-zA-Z])(.+)$/; 58 | const mixedCaseRe = /^(?=.*[a-z])(?=.*[A-Z])(.+)$/; 59 | const specialCharsRe = /^(?=.*[^a-zA-Z0-9])(.+)$/; 60 | 61 | if(complexity.alphaNumeric && !alhpaNumericRe.test(ast.value)) { 62 | throw new GraphQLError('Query error: String must contain at least one number and one letter', [ast]); 63 | } 64 | 65 | if(complexity.mixedCase && !mixedCaseRe.test(ast.value)) { 66 | throw new GraphQLError('Query error: String must contain at least one uper and one lower case letter', [ast]); 67 | } 68 | 69 | if(complexity.specialChars && !specialCharsRe.test(ast.value)) { 70 | throw new GraphQLError('Query error: String must contain at least one special character', [ast]); 71 | } 72 | }; 73 | 74 | var limitedStringCounter = 0; 75 | export class GraphQLLimitedString extends GraphQLCustomScalarType { 76 | constructor(min = 1, max, alphabet) { 77 | const suffix = (limitedStringCounter++ > 0) ? limitedStringCounter : ''; 78 | const name = 'LimitedString' + suffix; 79 | var description = 'A limited string.'; 80 | if(max) description += ' Has to be between ' + min + ' and ' + max + ' characters long.'; 81 | else description += ' Has to be at least ' + min + ' characters long.'; 82 | if(alphabet) description += ' May only contain the following characters: ' + alphabet; 83 | 84 | const validator = function(ast) { 85 | stringValidator(ast); 86 | lengthValidator(ast, min, max); 87 | 88 | if(alphabet) alphabetValidator(ast, alphabet); 89 | 90 | return ast.value; 91 | } 92 | 93 | super(name, description, validator); 94 | } 95 | }; 96 | 97 | var passwordCounter = 0; 98 | export class GraphQLPassword extends GraphQLCustomScalarType { 99 | constructor(min = 1, max, alphabet, complexity) { 100 | const suffix = (passwordCounter++ > 0) ? passwordCounter : ''; 101 | const name = 'Password' + suffix; 102 | var description = 'A password string.'; 103 | if(max) description += ' Has to be between ' + min + ' and ' + max + ' characters long.'; 104 | else description += ' Has to be at least ' + min + ' characters long.'; 105 | if(alphabet) description += ' May only contain the following characters: ' + alphabet; 106 | if(complexity) { 107 | if(complexity.alphaNumeric) description += ' Has to be alpha numeric.'; 108 | if(complexity.mixedCase) description += ' Has to be mixed case.'; 109 | if(complexity.specialChars) description += ' Has to contain special characters.'; 110 | } 111 | 112 | const validator = function(ast) { 113 | stringValidator(ast); 114 | lengthValidator(ast, min, max); 115 | 116 | if(alphabet) alphabetValidator(ast, alphabet); 117 | if(complexity) complexityValidator(ast, complexity); 118 | 119 | return ast.value; 120 | } 121 | 122 | super(name, description, validator); 123 | } 124 | }; 125 | 126 | export const GraphQLDateTime = factory.getCustomScalar( 127 | 'DateTime', 128 | 'The DateTime scalar type represents date time strings complying to ISO-8601.', 129 | function(ast) { 130 | stringValidator(ast); 131 | if(isNaN(Date.parse(ast.value))) { 132 | throw new GraphQLError('Query error: String is not a valid date time string', [ast]); 133 | } 134 | 135 | return ast.value; 136 | } 137 | ); 138 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | import { GraphQLScalarType } from 'graphql'; 2 | import { Kind } from 'graphql/language'; 3 | 4 | export class GraphQLCustomScalarType extends GraphQLScalarType { 5 | constructor(name, description, parser) { 6 | super({ 7 | name: name, 8 | description: description, 9 | serialize: value => { 10 | return value; 11 | }, 12 | parseValue: value => { 13 | const ast = { 14 | kind: Kind.STRING, 15 | value: value 16 | }; 17 | return parser(ast); 18 | }, 19 | parseLiteral: ast => { 20 | return parser(ast); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import {graphql, formatError, GraphQLError} from 'graphql'; 2 | import test from 'tape'; 3 | import {schema} from './schema'; 4 | 5 | function executeQuery(schema, query, type, item) { 6 | return graphql(schema, query) 7 | .catch(error => { 8 | // In some GraphQL versions before 0.12, the query was rejected with and error and after 0.12 the errors 9 | // were inside the response `errors` attribute. This code will convert old GraphQL behavior 10 | // into the new GraphQL 0.12 behavior so that the tests can assert error behavior across all 11 | // versions 12 | if (error instanceof GraphQLError) { 13 | return { 14 | errors: [error] 15 | }; 16 | } else { 17 | throw error; 18 | } 19 | }) 20 | .then(response => { 21 | if (response.errors && response.errors.length) { 22 | // In GraphQL versions before 0.12, the error message does not include the type details 23 | // but in 0.12 and after the type details are included. This code will convert old GraphQL behavior 24 | // into the new GraphQL 0.12 behavior so that the tests can assert error behavior across all 25 | // versions 26 | const message = response.errors[0].message; 27 | if (!/Expected type/.test(message)) { 28 | response.errors[0].message = 'Expected type ' + type + ', found "' + item + '"; ' + message 29 | } 30 | } 31 | return response; 32 | }) 33 | } 34 | 35 | test('GraphQLEmail', t => { 36 | const invalid = [ 37 | 'plainaddress', 38 | '#@%^%#$@#$@#.com', 39 | '@example.com', 40 | 'Joe Smith ', 41 | 'email.example.com', 42 | 'email@example@example.com', 43 | '.email@example.com', 44 | 'email.@example.com', 45 | 'email..email@example.com', 46 | 'email@example.com (Joe Smith)', 47 | 'email@example', 48 | 'email@example..com', 49 | 'Abc..123@example.com' 50 | ]; 51 | 52 | const valid = [ 53 | 'email@example.com', 54 | 'firstname.lastname@example.com', 55 | 'email@subdomain.example.com', 56 | 'firstname+lastname@example.com', 57 | 'email@123.123.123.123', 58 | '“email”@example.com', 59 | '1234567890@example.com', 60 | 'email@example-one.com', 61 | '_______@example.com', 62 | 'email@example.name', 63 | 'email@example.museum', 64 | 'email@example.co.jp', 65 | 'firstname-lastname@example.com' 66 | ]; 67 | 68 | t.plan(valid.length + invalid.length); 69 | 70 | for (const item of invalid) { 71 | const query = '{email(item: "' + item + '")}'; 72 | executeQuery(schema, query, 'Email', item) 73 | .then(result => { 74 | if(result.errors) { 75 | result.errors = result.errors.map(formatError); 76 | t.equal(result.errors[0].message, 'Expected type Email, found "' + item + '"; Query error: Not a valid Email address', 'invalid address recognized'); 77 | } 78 | else { 79 | t.fail('invalid address recognized as valid: ' + item); 80 | } 81 | }) 82 | .catch(error => { 83 | t.fail(error); 84 | }); 85 | } 86 | 87 | for (const item of valid) { 88 | const query = '{email(item: "' + item + '")}'; 89 | executeQuery(schema, query, 'Email', item) 90 | .then(result => { 91 | if (result.data && result.data.email && !result.errors) { 92 | t.equal(result.data.email, item, 'valid address recognized'); 93 | } 94 | else { 95 | t.fail('valid address recognized as invalid: ' + item); 96 | } 97 | }) 98 | .catch(error => { 99 | t.fail(error); 100 | }); 101 | } 102 | }); 103 | 104 | test('GraphQLURL', t => { 105 | const valid = [ 106 | 'http://foo.com/blah_blah', 107 | 'http://foo.com/blah_blah/', 108 | 'http://foo.com/blah_blah_(wikipedia)', 109 | 'http://foo.com/blah_blah_(wikipedia)_(again)', 110 | 'http://www.example.com/wpstyle/?p=364', 111 | 'https://www.example.com/foo/?bar=baz&inga=42&quux', 112 | 'http://✪df.ws/123', 113 | 'http://userid:password@example.com:8080', 114 | 'http://userid:password@example.com:8080/', 115 | 'http://userid@example.com', 116 | 'http://userid@example.com/', 117 | 'http://userid@example.com:8080', 118 | 'http://userid@example.com:8080/', 119 | 'http://userid:password@example.com', 120 | 'http://userid:password@example.com/', 121 | 'http://142.42.1.1/', 122 | 'http://142.42.1.1:8080/', 123 | 'http://➡.ws/䨹', 124 | 'http://⌘.ws', 125 | 'http://⌘.ws/', 126 | 'http://foo.com/blah_(wikipedia)#cite-1', 127 | 'http://foo.com/blah_(wikipedia)_blah#cite-1', 128 | 'http://foo.com/unicode_(✪)_in_parens', 129 | 'http://foo.com/(something)?after=parens', 130 | 'http://☺.damowmow.com/', 131 | 'http://code.google.com/events/#&product=browser', 132 | 'http://j.mp', 133 | 'ftp://foo.bar/baz', 134 | 'http://foo.bar/?q=Test%20URL-encoded%20stuff', 135 | 'http://مثال.إختبار', 136 | 'http://例子.测试', 137 | 'http://उदाहरण.परीक्षा', 138 | 'http://-.~_!$&\'()*+,;=:%40:80%2f::::::@example.com', 139 | 'http://1337.net', 140 | 'http://a.b-c.de', 141 | 'http://223.255.255.254' 142 | ]; 143 | 144 | const invalid = [ 145 | 'http://', 146 | 'http://.', 147 | 'http://..', 148 | 'http://../', 149 | 'http://?', 150 | 'http://??', 151 | 'http://??/', 152 | 'http://#', 153 | 'http://##', 154 | 'http://##/', 155 | 'http://foo.bar?q=Spaces should be encoded', 156 | '//', 157 | '//a', 158 | '///a', 159 | '///', 160 | 'http:///a', 161 | 'foo.com', 162 | 'rdar://1234', 163 | 'h://test', 164 | 'http:// shouldfail.com', 165 | ':// should fail', 166 | 'http://foo.bar/foo(bar)baz quux', 167 | 'ftps://foo.bar/', 168 | 'http://-error-.invalid/', 169 | 'http://-a.b.co', 170 | 'http://a.b-.co', 171 | 'http://0.0.0.0', 172 | 'http://10.1.1.0', 173 | 'http://10.1.1.255', 174 | 'http://224.1.1.1', 175 | 'http://1.1.1.1.1', 176 | 'http://123.123.123', 177 | 'http://3628126748', 178 | 'http://.www.foo.bar/', 179 | 'http://.www.foo.bar./', 180 | 'http://10.1.1.1' 181 | ]; 182 | 183 | t.plan(valid.length + invalid.length); 184 | 185 | for (const item of valid) { 186 | const query = '{url(item: "' + item + '")}'; 187 | executeQuery(schema, query, 'URL', item) 188 | .then(result => { 189 | if (result.data && result.data.url && !result.errors) { 190 | t.equal(result.data.url, item, 'valid URL recognized'); 191 | } 192 | else { 193 | t.fail('valid URL recognized as invalid: ' + item); 194 | } 195 | }) 196 | .catch(error => { 197 | t.fail(error); 198 | }); 199 | } 200 | 201 | for (const item of invalid) { 202 | const query = '{url(item: "' + item + '")}'; 203 | executeQuery(schema, query, 'URL', item) 204 | .then(result => { 205 | if(result.errors) { 206 | result.errors = result.errors.map(formatError); 207 | t.equal(result.errors[0].message, 'Expected type URL, found "' + item + '"; Query error: Not a valid URL', 'invalid address recognized'); 208 | } 209 | else { 210 | t.fail('invalid URL recognized as valid: ' + item); 211 | } 212 | }) 213 | .catch(error => { 214 | t.fail(error); 215 | }); 216 | } 217 | }); 218 | 219 | test('GraphQLLimitedString (default)', t => { 220 | const valid = [ 221 | 'a', 222 | 'aa', 223 | 'aaa1', 224 | '1aaa' 225 | ]; 226 | 227 | const invalid = ['']; 228 | t.plan(valid.length + invalid.length); 229 | 230 | for (const item of valid) { 231 | const query = '{limitedStringDefault(item: "' + item + '")}'; 232 | executeQuery(schema, query, 'LimitedString', item) 233 | .then(result => { 234 | if (result.data && result.data.limitedStringDefault && !result.errors) { 235 | t.equal(result.data.limitedStringDefault, item, 'valid LimitedString recognized'); 236 | } 237 | else { 238 | t.fail('valid LimitedString recognized as invalid: ' + item); 239 | } 240 | }) 241 | .catch(error => { 242 | t.fail(error); 243 | }); 244 | } 245 | 246 | for (const item of invalid) { 247 | const query = '{limitedStringDefault(item: "' + item + '")}'; 248 | executeQuery(schema, query, 'LimitedString', item) 249 | .then(result => { 250 | if(result.errors) { 251 | result.errors = result.errors.map(formatError); 252 | t.equal(result.errors[0].message, 'Expected type LimitedString, found "' + item + '"; Query error: String not long enough', 'invalid LimitedString recognized'); 253 | } 254 | else { 255 | t.fail('invalid LimitedString recognized as valid: ' + item); 256 | } 257 | }) 258 | .catch(error => { 259 | t.fail(error); 260 | }); 261 | } 262 | }); 263 | 264 | test('GraphQLLimitedString too short (min = 3, max = 10)', t => { 265 | const valid = [ 266 | 'foo', 267 | 'foobar', 268 | 'foo-bar', 269 | 'foobar23', 270 | '123456789' 271 | ]; 272 | 273 | const invalid = [ 274 | '', 275 | 'a', 276 | 'aa', 277 | ]; 278 | 279 | t.plan(valid.length + invalid.length); 280 | 281 | for (const item of valid) { 282 | const query = '{limitedStringMinMax(item: "' + item + '")}'; 283 | executeQuery(schema, query, 'LimitedString2', item) 284 | .then(result => { 285 | if (result.data && result.data.limitedStringMinMax && !result.errors) { 286 | t.equal(result.data.limitedStringMinMax, item, 'valid LimitedString recognized'); 287 | } 288 | else { 289 | t.fail('valid LimitedString recognized as invalid: ' + item); 290 | } 291 | }) 292 | .catch(error => { 293 | t.fail(error); 294 | }); 295 | } 296 | 297 | for (const item of invalid) { 298 | const query = '{limitedStringMinMax(item: "' + item + '")}'; 299 | executeQuery(schema, query, 'LimitedString2', item) 300 | .then(result => { 301 | if(result.errors) { 302 | result.errors = result.errors.map(formatError); 303 | t.equal(result.errors[0].message, 'Expected type LimitedString2, found "' + item + '"; Query error: String not long enough', 'invalid LimitedString recognized'); 304 | } 305 | else { 306 | t.fail('invalid LimitedString recognized as valid: ' + item); 307 | } 308 | }) 309 | .catch(error => { 310 | t.fail(error); 311 | }); 312 | } 313 | }); 314 | 315 | test('GraphQLLimitedString too long (min = 3, max = 10)', t => { 316 | const valid = [ 317 | 'foo', 318 | 'foobar', 319 | 'foo-bar', 320 | 'foobar23', 321 | '123456789' 322 | ]; 323 | 324 | const invalid = [ 325 | '01234567890', 326 | 'foobar23456' 327 | ]; 328 | 329 | t.plan(valid.length + invalid.length); 330 | 331 | for (const item of valid) { 332 | const query = '{limitedStringMinMax(item: "' + item + '")}'; 333 | executeQuery(schema, query, 'LimitedString2', item) 334 | .then(result => { 335 | if (result.data && result.data.limitedStringMinMax && !result.errors) { 336 | t.equal(result.data.limitedStringMinMax, item, 'valid LimitedString recognized'); 337 | } 338 | else { 339 | t.fail('valid LimitedString recognized as invalid: ' + item); 340 | } 341 | }) 342 | .catch(error => { 343 | t.fail(error); 344 | }); 345 | } 346 | 347 | for (const item of invalid) { 348 | const query = '{limitedStringMinMax(item: "' + item + '")}'; 349 | executeQuery(schema, query, 'LimitedString2', item) 350 | .then(result => { 351 | if(result.errors) { 352 | result.errors = result.errors.map(formatError); 353 | t.equal(result.errors[0].message, 'Expected type LimitedString2, found "' + item + '"; Query error: String too long', 'invalid LimitedString recognized'); 354 | } 355 | else { 356 | t.fail('invalid LimitedString recognized as valid: ' + item); 357 | } 358 | }) 359 | .catch(error => { 360 | t.fail(error); 361 | }); 362 | } 363 | }); 364 | 365 | test('GraphQLLimitedString too short (min = 3, max = 10, alphabet = "abc123")', t => { 366 | const valid = [ 367 | 'aaa', 368 | 'abc', 369 | 'abc123', 370 | '1231231231', 371 | 'aaaaabbbbb', 372 | '33333ccc22', 373 | ]; 374 | 375 | const invalid = [ 376 | '', 377 | 'a', 378 | 'aa', 379 | ]; 380 | 381 | t.plan(valid.length + invalid.length); 382 | 383 | for (const item of valid) { 384 | const query = '{limitedStringAlphabet(item: "' + item + '")}'; 385 | executeQuery(schema, query, 'LimitedString3', item) 386 | .then(result => { 387 | if (result.data && result.data.limitedStringAlphabet) { 388 | t.equal(result.data.limitedStringAlphabet, item, 'valid LimitedString recognized'); 389 | } 390 | else { 391 | t.fail('valid LimitedString recognized as invalid: ' + item); 392 | } 393 | }) 394 | .catch(error => { 395 | t.fail(error); 396 | }); 397 | } 398 | 399 | for (const item of invalid) { 400 | const query = '{limitedStringAlphabet(item: "' + item + '")}'; 401 | executeQuery(schema, query, 'LimitedString3', item) 402 | .then(result => { 403 | if(result.errors) { 404 | result.errors = result.errors.map(formatError); 405 | t.equal(result.errors[0].message, 'Expected type LimitedString3, found "' + item + '"; Query error: String not long enough', 'invalid LimitedString recognized'); 406 | } 407 | else { 408 | t.fail('invalid LimitedString recognized as valid: ' + item); 409 | } 410 | }) 411 | .catch(error => { 412 | t.fail(error); 413 | }); 414 | } 415 | }); 416 | 417 | test('GraphQLLimitedString too long (min = 3, max = 10, alphabet = "abc123")', t => { 418 | const invalid = [ 419 | '01234567890', 420 | 'foobar23456' 421 | ]; 422 | 423 | t.plan(invalid.length); 424 | 425 | for (const item of invalid) { 426 | const query = '{limitedStringAlphabet(item: "' + item + '")}'; 427 | executeQuery(schema, query, 'LimitedString3', item) 428 | .then(result => { 429 | if(result.errors) { 430 | result.errors = result.errors.map(formatError); 431 | t.equal(result.errors[0].message, 'Expected type LimitedString3, found "' + item + '"; Query error: String too long', 'invalid LimitedString recognized'); 432 | } 433 | else { 434 | t.fail('invalid LimitedString recognized as valid: ' + item); 435 | } 436 | }) 437 | .catch(error => { 438 | t.fail(error); 439 | }); 440 | } 441 | }); 442 | 443 | test('GraphQLLimitedString invalid chars (min = 3, max = 10, alphabet = "abc123")', t => { 444 | const invalid = [ 445 | 'dddd', 446 | 'abd1234', 447 | ]; 448 | 449 | t.plan(invalid.length); 450 | 451 | for (const item of invalid) { 452 | const query = '{limitedStringAlphabet(item: "' + item + '")}'; 453 | executeQuery(schema, query, 'LimitedString3', item) 454 | .then(result => { 455 | if(result.errors) { 456 | result.errors = result.errors.map(formatError); 457 | t.equal(result.errors[0].message, 'Expected type LimitedString3, found "' + item + '"; Query error: Invalid character found', 'invalid LimitedString recognized'); 458 | } 459 | else { 460 | t.fail('invalid LimitedString recognized as valid: ' + item); 461 | } 462 | }) 463 | .catch(error => { 464 | t.fail(error); 465 | }); 466 | } 467 | }); 468 | 469 | test('GraphQLPassword (alphaNumeric)', t => { 470 | const valid = [ 471 | 'a1', 472 | 'a1c', 473 | 'abc123', 474 | '123abc1231', 475 | '33333ccc22', 476 | '33333ccc22C', 477 | '333§ccc22' 478 | ]; 479 | 480 | const invalid = [ 481 | '', 482 | 'a', 483 | 'aa', 484 | 'dddd', 485 | 'aaaaabbbbb', 486 | '1', 487 | '1234', 488 | '1234567890' 489 | ]; 490 | 491 | t.plan(valid.length + invalid.length); 492 | 493 | for (const item of valid) { 494 | const query = '{password(item: "' + item + '")}'; 495 | executeQuery(schema, query, 'Password', item) 496 | .then(result => { 497 | if (result.data && result.data.password && !result.errors) { 498 | t.equal(result.data.password, item, 'valid Password recognized'); 499 | } 500 | else { 501 | t.fail('valid Password recognized as invalid: ' + item); 502 | } 503 | }) 504 | .catch(error => { 505 | t.fail(error); 506 | }); 507 | } 508 | 509 | for (const item of invalid) { 510 | const query = '{password(item: "' + item + '")}'; 511 | executeQuery(schema, query, 'Password', item) 512 | .then(result => { 513 | if(result.errors) { 514 | result.errors = result.errors.map(formatError); 515 | t.equal(result.errors[0].message, 'Expected type Password, found "' + item + '"; Query error: String must contain at least one number and one letter', 'invalid LimitedString recognized'); 516 | } 517 | else { 518 | t.fail('invalid Password recognized as valid: ' + item); 519 | } 520 | }) 521 | .catch(error => { 522 | t.fail(error); 523 | }); 524 | } 525 | }); 526 | 527 | test('GraphQLPassword (mixedCase)', t => { 528 | const valid = [ 529 | 'aA', 530 | 'a1C', 531 | 'aBc123', 532 | '123Abc1231', 533 | '33333cCc22', 534 | '33333ccc22C', 535 | '333§ccC22' 536 | ]; 537 | 538 | const invalid = [ 539 | '', 540 | 'a', 541 | 'aa', 542 | 'dddd', 543 | 'aaaaabbbbb', 544 | '1', 545 | '1234', 546 | '1234567890', 547 | '1a', 548 | '123aaaa', 549 | 'foo23bar' 550 | ]; 551 | 552 | t.plan(valid.length + invalid.length); 553 | 554 | for (const item of valid) { 555 | const query = '{passwordMixedCase(item: "' + item + '")}'; 556 | executeQuery(schema, query, 'Password2', item) 557 | .then(result => { 558 | if (result.data && result.data.passwordMixedCase && !result.errors) { 559 | t.equal(result.data.passwordMixedCase, item, 'valid Password recognized'); 560 | } 561 | else { 562 | t.fail('valid Password recognized as invalid: ' + item); 563 | } 564 | }) 565 | .catch(error => { 566 | t.fail(error); 567 | }); 568 | } 569 | 570 | for (const item of invalid) { 571 | const query = '{passwordMixedCase(item: "' + item + '")}'; 572 | executeQuery(schema, query, 'Password2', item) 573 | .then(result => { 574 | if(result.errors) { 575 | result.errors = result.errors.map(formatError); 576 | t.equal(result.errors[0].message, 'Expected type Password2, found "' + item + '"; Query error: String must contain at least one uper and one lower case letter', 'invalid LimitedString recognized'); 577 | } 578 | else { 579 | t.fail('invalid Password recognized as valid: ' + item); 580 | } 581 | }) 582 | .catch(error => { 583 | t.fail(error); 584 | }); 585 | } 586 | }); 587 | 588 | test('GraphQLPassword (specialChars)', t => { 589 | const valid = [ 590 | 'aÄ', 591 | 'a1*', 592 | 'a(c123', 593 | '1%3Abc1231', 594 | '33333#Cc22', 595 | '!1', 596 | '!#!§$%&/()' 597 | ]; 598 | 599 | const invalid = [ 600 | '', 601 | 'a', 602 | 'aa', 603 | 'dddd', 604 | 'aaaaabbbbb', 605 | '1', 606 | '1234', 607 | '1234567890', 608 | '1a', 609 | '123aaaa', 610 | 'foo23bar' 611 | ]; 612 | 613 | t.plan(valid.length + invalid.length); 614 | 615 | for (const item of valid) { 616 | const query = '{passwordSpecialChars(item: "' + item + '")}'; 617 | executeQuery(schema, query, 'Password3', item) 618 | .then(result => { 619 | if (result.data && result.data.passwordSpecialChars && !result.errors) { 620 | t.equal(result.data.passwordSpecialChars, item, 'valid Password recognized'); 621 | } 622 | else { 623 | t.fail('valid Password recognized as invalid: ' + item); 624 | } 625 | }) 626 | .catch(error => { 627 | t.fail(error); 628 | }); 629 | } 630 | 631 | for (const item of invalid) { 632 | const query = '{passwordSpecialChars(item: "' + item + '")}'; 633 | executeQuery(schema, query, 'Password3', item) 634 | .then(result => { 635 | if(result.errors) { 636 | result.errors = result.errors.map(formatError); 637 | t.equal(result.errors[0].message, 'Expected type Password3, found "' + item + '"; Query error: String must contain at least one special character', 'invalid LimitedString recognized'); 638 | } 639 | else { 640 | t.fail('invalid Password recognized as valid: ' + item); 641 | } 642 | }) 643 | .catch(error => { 644 | t.fail(error); 645 | }); 646 | } 647 | }); 648 | 649 | test('GraphQLPassword too short (all)', t => { 650 | const valid = [ 651 | 'a1!B', 652 | '!!A1b', 653 | 'b2§A3!', 654 | ]; 655 | 656 | const invalid = [ 657 | '', 658 | 'a', 659 | '1', 660 | 'aa', 661 | '1a', 662 | 'aÄ', 663 | '!1', 664 | ]; 665 | 666 | t.plan(valid.length + invalid.length); 667 | 668 | for (const item of valid) { 669 | const query = '{passwordAll(item: "' + item + '")}'; 670 | executeQuery(schema, query, 'Password4', item) 671 | .then(result => { 672 | if (result.data && result.data.passwordAll && !result.errors) { 673 | t.equal(result.data.passwordAll, item, 'valid Password recognized'); 674 | } 675 | else { 676 | t.fail('valid Password recognized as invalid: ' + item); 677 | } 678 | }) 679 | .catch(error => { 680 | t.fail(error); 681 | }); 682 | } 683 | 684 | for (const item of invalid) { 685 | const query = '{passwordAll(item: "' + item + '")}'; 686 | executeQuery(schema, query, 'Password4', item) 687 | .then(result => { 688 | if(result.errors) { 689 | result.errors = result.errors.map(formatError); 690 | t.equal(result.errors[0].message, 'Expected type Password4, found "' + item + '"; Query error: String not long enough', 'invalid LimitedString recognized'); 691 | } 692 | else { 693 | t.fail('invalid Password recognized as valid: ' + item); 694 | } 695 | }) 696 | .catch(error => { 697 | t.fail(error); 698 | }); 699 | } 700 | }); 701 | 702 | test('GraphQLPassword illegal char (all)', t => { 703 | const invalid = [ 704 | 'a1*', 705 | 'a(c123', 706 | '1234', 707 | 'dddd', 708 | ]; 709 | 710 | t.plan(invalid.length); 711 | 712 | for (const item of invalid) { 713 | const query = '{passwordAll(item: "' + item + '")}'; 714 | executeQuery(schema, query, 'Password4', item) 715 | .then(result => { 716 | if(result.errors) { 717 | result.errors = result.errors.map(formatError); 718 | t.equal(result.errors[0].message, 'Expected type Password4, found "' + item + '"; Query error: Invalid character found', 'invalid LimitedString recognized'); 719 | } 720 | else { 721 | t.fail('invalid Password recognized as valid: ' + item); 722 | } 723 | }) 724 | .catch(error => { 725 | t.fail(error); 726 | }); 727 | } 728 | }); 729 | 730 | test('GraphQLPassword too long (all)', t => { 731 | const invalid = [ 732 | '123aaaa', 733 | 'foo23bar', 734 | '1234567890', 735 | '1%3Abc1231', 736 | '33333#Cc22', 737 | 'aaaaabbbbb', 738 | ]; 739 | 740 | t.plan(invalid.length); 741 | 742 | for (const item of invalid) { 743 | const query = '{passwordAll(item: "' + item + '")}'; 744 | executeQuery(schema, query, 'Password4', item) 745 | .then(result => { 746 | if(result.errors) { 747 | result.errors = result.errors.map(formatError); 748 | t.equal(result.errors[0].message, 'Expected type Password4, found "' + item + '"; Query error: String too long', 'invalid LimitedString recognized'); 749 | } 750 | else { 751 | t.fail('invalid Password recognized as valid: ' + item); 752 | } 753 | }) 754 | .catch(error => { 755 | t.fail(error); 756 | }); 757 | } 758 | }); 759 | 760 | test('GraphQLDateTime', t => { 761 | const valid = [ 762 | '2015', 763 | '9999', 764 | '123456', 765 | '2015-1', 766 | '2015-1-1', 767 | '2015-5-31', 768 | '2015-01-01', 769 | '2015-05-31', 770 | '2015-05-31T14:23', 771 | '2015-05-31T14:23:30', 772 | '2015-05-31T14:23:30.1234', 773 | '2015-05-31T14:23Z', 774 | '2015-05-31T14:23:30.1234Z', 775 | '2015-05-31T14:23:30.1234+05:00', 776 | '1970-01-01T00:00:00.000Z', 777 | '1969-12-31T23:59:59.999Z' 778 | ]; 779 | 780 | const invalid = [ 781 | '2015-13-1', 782 | '2015-01-01T23:61:59', 783 | '2015-05-31T14:63:30' 784 | ]; 785 | 786 | t.plan(valid.length + invalid.length); 787 | 788 | for (const item of valid) { 789 | const query = '{date(item: "' + item + '")}'; 790 | executeQuery(schema, query, 'DateTime', item) 791 | .then(result => { 792 | if (result.data && result.data.date && !result.errors) { 793 | t.equal(result.data.date, item, 'valid DateTime recognized'); 794 | } 795 | else { 796 | t.fail('valid Password recognized as invalid: ' + item); 797 | } 798 | }) 799 | .catch(error => { 800 | t.fail(error); 801 | }); 802 | } 803 | 804 | for (const item of invalid) { 805 | const query = '{date(item: "' + item + '")}'; 806 | executeQuery(schema, query, 'DateTime', item) 807 | .then(result => { 808 | if(result.errors) { 809 | result.errors = result.errors.map(formatError); 810 | t.equal(result.errors[0].message, 'Expected type DateTime, found "' + item + '"; Query error: String is not a valid date time string', 'invalid LimitedString recognized'); 811 | } 812 | else { 813 | t.fail('invalid DateTime recognized as valid: ' + item); 814 | } 815 | }) 816 | .catch(error => { 817 | t.fail(error); 818 | }); 819 | } 820 | }); 821 | 822 | test('GraphQLUUID', t => { 823 | const valid = [ 824 | 'bfaa2768-ba8c-11e5-9912-ba0be0483c18', 825 | 'E8D6F4C2-BA8C-11E5-9912-BA0BE0483C18', 826 | 'bd2e3ee3-8908-4665-9b59-682587236654', 827 | 'df7c8034-41e3-409a-a441-2e08ba65b827', 828 | '5a028adb-c082-4980-aab3-f3c16642281a', 829 | '6715da1d-212b-4aab-9b9e-117e3a10de19', 830 | '209a03b9-2d18-4ea1-ab11-c3d46e7f1725', 831 | 'f1b2eddc-4d38-42b1-8232-137934b6821d', 832 | '874ed1b5-51e6-470f-8b29-b21ade28cb81', 833 | '13627f16-6b28-4a91-bdc0-15bd9387b9ed', 834 | '738442a4-00e6-43b6-b6d5-f9a8e8aa3528', 835 | 'b27fbc79-1314-472c-b509-2feb9d0050f7', 836 | '6ea45d93-9d50-4668-9ccb-07ab78b14458', 837 | 'f97f6df2-f94b-47a1-a2db-ea2802ef79d9', 838 | '019fad9a-fbae-4dd6-aba2-3d76bdcaed59' 839 | ]; 840 | 841 | const invalid = [ 842 | '', 843 | 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', 844 | 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', 845 | 'A987FBC94BED3078CF079141BA07C9F3', 846 | '934859', 847 | '987FBC9-4BED-3078-CF07A-9141BA07C9F3', 848 | 'AAAAAAAA-1111-1111-AAAG-111111111111' 849 | ]; 850 | 851 | t.plan(valid.length + invalid.length); 852 | 853 | for (const item of valid) { 854 | const query = '{uuid(item: "' + item + '")}'; 855 | executeQuery(schema, query, 'UUID', item) 856 | .then(result => { 857 | if (result.data && result.data.uuid && !result.errors) { 858 | t.equal(result.data.uuid, item, 'valid UUID recognized'); 859 | } 860 | else { 861 | t.fail('valid UUID recognized as invalid: ' + item); 862 | } 863 | }) 864 | .catch(error => { 865 | t.fail(error); 866 | }); 867 | } 868 | 869 | for (const item of invalid) { 870 | const query = '{uuid(item: "' + item + '")}'; 871 | executeQuery(schema, query, 'UUID', item) 872 | .then(result => { 873 | if(result.errors) { 874 | result.errors = result.errors.map(formatError); 875 | t.equal(result.errors[0].message, 'Expected type UUID, found "' + item + '"; Query error: Not a valid UUID', 'invalid LimitedString recognized'); 876 | } 877 | else { 878 | t.fail('invalid UUID recognized as valid: ' + item); 879 | } 880 | }) 881 | .catch(error => { 882 | t.equal(error.message, 'Query error: Not a valid UUID', 'invalid UUID recognized'); 883 | }); 884 | } 885 | }); 886 | -------------------------------------------------------------------------------- /tests/schema.js: -------------------------------------------------------------------------------- 1 | import { 2 | graphql, 3 | GraphQLSchema, 4 | GraphQLObjectType, 5 | GraphQLString 6 | } from 'graphql'; 7 | import { 8 | GraphQLEmail, 9 | GraphQLURL, 10 | GraphQLLimitedString, 11 | GraphQLPassword, 12 | GraphQLDateTime, 13 | GraphQLUUID 14 | } from '../lib'; 15 | 16 | export const schema = new GraphQLSchema({ 17 | query: new GraphQLObjectType({ 18 | name: 'RootQueryType', 19 | fields: { 20 | email: { 21 | type: GraphQLString, 22 | args: { 23 | item: { type: GraphQLEmail } 24 | }, 25 | resolve: (root, {item}) => { 26 | return item; 27 | } 28 | }, 29 | url: { 30 | type: GraphQLString, 31 | args: { 32 | item: { type: GraphQLURL } 33 | }, 34 | resolve: (root, {item}) => { 35 | return item; 36 | } 37 | }, 38 | limitedStringDefault: { 39 | type: GraphQLString, 40 | args: { 41 | item: { type: new GraphQLLimitedString() } 42 | }, 43 | resolve: (root, {item}) => { 44 | return item; 45 | } 46 | }, 47 | limitedStringMinMax: { 48 | type: GraphQLString, 49 | args: { 50 | item: { type: new GraphQLLimitedString(3, 10) } 51 | }, 52 | resolve: (root, {item}) => { 53 | return item; 54 | } 55 | }, 56 | limitedStringAlphabet: { 57 | type: GraphQLString, 58 | args: { 59 | item: { type: new GraphQLLimitedString(3, 10, 'abc123') } 60 | }, 61 | resolve: (root, {item}) => { 62 | return item; 63 | } 64 | }, 65 | password: { 66 | type: GraphQLString, 67 | args: { 68 | item: { type: new GraphQLPassword(null, null, null, { alphaNumeric: true }) } 69 | }, 70 | resolve: (root, {item}) => { 71 | return item; 72 | } 73 | }, 74 | passwordMixedCase: { 75 | type: GraphQLString, 76 | args: { 77 | item: { type: new GraphQLPassword(null, null, null, { mixedCase: true }) } 78 | }, 79 | resolve: (root, {item}) => { 80 | return item; 81 | } 82 | }, 83 | passwordSpecialChars: { 84 | type: GraphQLString, 85 | args: { 86 | item: { type: new GraphQLPassword(null, null, null, { specialChars: true }) } 87 | }, 88 | resolve: (root, {item}) => { 89 | return item; 90 | } 91 | }, 92 | passwordAll: { 93 | type: GraphQLString, 94 | args: { 95 | item: { type: new GraphQLPassword(3, 6, 'abcABC123!"§', { specialChars: true, mixedCase: true, alphaNumeric: true }) } 96 | }, 97 | resolve: (root, {item}) => { 98 | return item; 99 | } 100 | }, 101 | date: { 102 | type: GraphQLString, 103 | args: { 104 | item: { type: GraphQLDateTime } 105 | }, 106 | resolve: (root, {item}) => { 107 | return item; 108 | } 109 | }, 110 | uuid: { 111 | type: GraphQLString, 112 | args: { 113 | item: { type: GraphQLUUID } 114 | }, 115 | resolve: (root, {item}) => { 116 | return item; 117 | } 118 | }, 119 | } 120 | }) 121 | }); 122 | --------------------------------------------------------------------------------