├── .eslintrc.json
├── .github
└── pull_request_template.md
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .travis.yml
├── .vscode
└── settings.json
├── @types
└── log.d.ts
├── LICENSE
├── README.md
├── jest.unit.json
├── package-lock.json
├── package.json
├── src
├── index.ts
└── middleware
│ ├── post.ts
│ └── verify.ts
├── template.env
├── test
├── index.test.ts
├── middleware
│ ├── post.test.ts
│ └── verify.test.ts
├── mockConstants.ts
└── server.ts
├── tsconfig.eslint.json
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es2021": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "airbnb-base",
10 | "airbnb-typescript/base",
11 | "plugin:@typescript-eslint/recommended",
12 | "plugin:import/typescript",
13 | "prettier"
14 | ],
15 | "parser": "@typescript-eslint/parser",
16 | "parserOptions": {
17 | "ecmaVersion": "latest",
18 | "project": "tsconfig.json"
19 | },
20 | "plugins": ["@typescript-eslint", "prettier"],
21 | "rules": {
22 | "indent": [
23 | "warn",
24 | 4,
25 | {
26 | "SwitchCase": 2
27 | }
28 | ],
29 | "prettier/prettier": [
30 | "error"
31 | ]
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Summary
2 | _Provide a short summary of the changes in this PR_
3 |
4 | ### Type of Change
5 | Please delete options that are not relevant.
6 |
7 | - [ ] Bug fix (non-breaking change which fixes an issue)
8 | - [ ] New feature (non-breaking change which adds functionality)
9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
10 | - [ ] This change requires a documentation update
11 |
12 | ### Issues
13 | - Link any issues this PR resolves using keywords (resolve, closes, fixed)
14 |
15 | ### Evidence
16 | - Provide evidence of the the changes functioning as expected or describe your tests. If tests are included in the CI pipeline this may be omitted.
17 |
18 |
19 | _(delete this line)_ Prior to submitting the PR assign a reviewer from each team to review this PR.
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # Runs all code quality tools prior to each commit.
5 | npx lint-staged
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .vscode/*
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 100,
7 | "bracketSpacing": true,
8 | "arrowParens": "always",
9 | "proseWrap": "never"
10 | }
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 16
4 | branches:
5 | only:
6 | - dev
7 | script:
8 | - 'npm run lint'
9 | # - "npm run test"
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [],
3 | "settings": {},
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll.eslint": true
6 | },
7 | "editor.formatOnSave": true,
8 | }
--------------------------------------------------------------------------------
/@types/log.d.ts:
--------------------------------------------------------------------------------
1 | // represents query data stored by limiter into res.locals.graphqlGate
2 | export type LimQueryData = {
3 | // removed depth until functionality is added into limiter
4 | // depth?: number;
5 | complexity?: number;
6 | tokens: number;
7 | success: boolean;
8 | };
9 |
10 | // represents query data once passed through the logger
11 | export type LogQueryData = {
12 | // removed depth until functionality is added into limiter
13 | // depth?: number;
14 | complexity?: number;
15 | tokens: number;
16 | success: boolean;
17 | timestamp: number;
18 | loggedOn: number;
19 | latency?: number;
20 | };
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
Gate Logger
4 |

5 |
6 |
The data pipeline package for communication between the GraphQLGate Rate-Limiter and your Gateway developer portal.
7 |
8 |
9 |
10 |
11 | ## Prerequisites
12 |
13 | This package interfaces with the GraphQLGate rate-limiting package to log query data for visualization in the Gateway developer portal
14 |
15 | 1. Signup/login to the [Gateway developer portal](graphqlgate.io).
16 |
17 | 2. Create a new project to recieve a project ID and API key.
18 |
19 | 3. Import and configure the [GraphQLGate rate-limiting package](https://www.npmjs.com/package/graphqlgate)
20 |
21 | ## Getting Started
22 |
23 | Install the package
24 |
25 | ```
26 | npm i gate-logger
27 | ```
28 |
29 | Import the package and add the logging middleware to the Express middleware chain BEFORE the GraphQLGate middleware.
30 |
31 | ** ERRORS WILL BE THROWN if the logger is added after the limiter **
32 |
33 | Copy the project ID and the API key from your project on the Gateway developer portal and include them as middleware arguments.
34 |
35 | ```javascript
36 | // import package
37 | import gateLogger from 'gate-logger';
38 | import expressGraphQLRateLimiter from 'graphqlgate';
39 |
40 | /**
41 | * Import other dependencies
42 | * */
43 |
44 | // Add the logger middleware into your GraphQL middleware chain
45 | app.use('gql', gateLogger(/* PROJECT ID */, /* API KEY */ );
46 |
47 | //Add the rate limiteing middleware
48 | app.use(
49 | 'gql',
50 | expressGraphQLRateLimiter(schemaObject, {
51 | rateLimiter: {
52 | type: 'TOKEN_BUCKET',
53 | refillRate: 10,
54 | capacity: 100,
55 | },
56 | }) /** add GraphQL server here */
57 | );
58 | ```
59 |
60 | And that's it! The logger will now send all query rate-limiting data, blocked or allowed, for you to view in the Gateway developer portal!
61 |
--------------------------------------------------------------------------------
/jest.unit.json:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "ts-jest",
3 | "coverageDirectory": "./coverage",
4 | "testMatch": ["**/?(*.)+(test).ts"],
5 | "resetMocks": true,
6 | "clearMocks": true
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gate-logger",
3 | "version": "1.0.0",
4 | "description": "A backend library to log GraphQL requests based on their depth and complexity.",
5 | "main": "src/index.ts",
6 | "scripts": {
7 | "test": "jest -c ./jest.unit.json",
8 | "test:coverage": "jest --coverage -c ./jest.unit.json",
9 | "lint": "eslint src test",
10 | "lint:fix": "eslint --fix src test",
11 | "prettier": "prettier --write .",
12 | "prepare": "husky install",
13 | "test:server": "nodemon --exec ts-node --files ./test/server.ts"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/oslabs-beta/beaver-logger.git"
18 | },
19 | "author": "",
20 | "license": "ISC",
21 | "bugs": {
22 | "url": "https://github.com/oslabs-beta/beaver-logger/issues"
23 | },
24 | "homepage": "https://github.com/oslabs-beta/beaver-logger#readme",
25 | "dependencies": {
26 | "axios": "^0.27.2",
27 | "body-parser": "^1.20.0",
28 | "express": "^4.18.1",
29 | "graphql": "^16.5.0",
30 | "mongoose": "^6.3.5"
31 | },
32 | "devDependencies": {
33 | "@types/express": "^4.17.13",
34 | "@types/jest": "^27.5.2",
35 | "@types/node": "^17.0.34",
36 | "@types/supertest": "^2.0.12",
37 | "@typescript-eslint/eslint-plugin": "^5.23.0",
38 | "@typescript-eslint/parser": "^5.23.0",
39 | "eslint": "^8.15.0",
40 | "eslint-config-airbnb-base": "^15.0.0",
41 | "eslint-config-airbnb-typescript": "^17.0.0",
42 | "eslint-config-prettier": "^8.5.0",
43 | "eslint-plugin-import": "^2.26.0",
44 | "eslint-plugin-prettier": "^4.0.0",
45 | "husky": "^8.0.1",
46 | "jest": "^28.1.0",
47 | "lint-staged": "^12.4.1",
48 | "msw": "^0.42.3",
49 | "prettier": "2.6.2",
50 | "supertest": "^6.2.3",
51 | "ts-jest": "^28.0.3",
52 | "ts-node": "^10.8.0",
53 | "typescript": "^4.6.4"
54 | },
55 | "lint-staged": {
56 | "*.{js, ts}": "eslint --cache --fix",
57 | "*.{js,ts,css,md}": "prettier --write --ignore-unknown"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response, NextFunction } from 'express';
2 |
3 | import verification from './middleware/verify';
4 | import postQuery from './middleware/post';
5 |
6 | // URI pointing to the visual webapp
7 | const gateURI = 'http://localhost:3000';
8 |
9 | /**
10 | * @function gatelog runs along with the helper functions
11 | * first, authorizes the user's request to post to the webapp
12 | * using params. This middleware should be called BEFORE the
13 | * rate-limiting middleware in order to log blocked requests &
14 | * allow the limiter to kill requests that must be blocked,
15 | * but it also needs to throw an error if the limiter is not setup.
16 | *
17 | *
18 | * Therefore, this middlware should be placed before the limiter
19 | * e.g. app.use(gatelog(params...))
20 | * app.use(gatelimiter(params...))
21 | *
22 | * The gate log adds functionality into a res.on('finish', ()=>{})
23 | * event listener designed to fire once the response gets sent back to
24 | * the client. This will make sure that the logger has now received data
25 | * from the limiter regarding the current query, and now can send this data
26 | * to the web app's backend.
27 | *
28 | *
29 | * @param projectID points to the user's project in the webapp backend
30 | *
31 | * @param apiKey the authorization code the user provides to post
32 | * to the webapp's backend
33 | *
34 | */
35 |
36 | // instantation, everything before the return callback runs only once
37 | export default function gateLogger(projectID: string, apiKey: string) {
38 | verification(gateURI, projectID, apiKey)
39 | .then((res) => {
40 | if (res instanceof Error)
41 | throw new SyntaxError(
42 | `[gatelog] Error with server, project ID, or API key:\n${res}`
43 | );
44 | })
45 | .catch((err) => {
46 | throw new Error(`[gatelog] Error verifying:\n${err}`);
47 | });
48 |
49 | // every time a request is processed in the user's backend,
50 | // this express middleware callback will run
51 | return (req: Request, res: Response, next: NextFunction) => {
52 | // calls initial timestamp of request's beginning,
53 | // this will depend on where logger is placed in middleware chain
54 | const timestamp: number = new Date().valueOf();
55 |
56 | // runs logger's functionality upon response being sent back to client
57 | res.on('finish', async (): Promise => {
58 | // checks if data from limiter middleware contains required properties,
59 | // logs error otherwise to fail without crashing the server
60 | if (
61 | !(
62 | // prettier-ignore
63 | // eslint-disable-next-line no-prototype-builtins
64 | res.locals.graphqlGate?.hasOwnProperty('success') &&
65 | // eslint-disable-next-line no-prototype-builtins
66 | res.locals.graphqlGate?.hasOwnProperty('tokens')
67 | )
68 | ) {
69 | console.log(
70 | new SyntaxError(
71 | `[gatelog] Error: Limiter is not including required query properties: success & tokens\n`
72 | )
73 | );
74 | return;
75 | }
76 | // calls timestamp of request's end
77 | const loggedOn: number = new Date().valueOf();
78 |
79 | // stores time between request's beginning and end
80 | const latency: number = loggedOn - timestamp;
81 |
82 | const result = await postQuery(gateURI, projectID, {
83 | ...res.locals.graphqlGate,
84 | timestamp,
85 | loggedOn,
86 | latency,
87 | }).catch((err) => {
88 | console.log(new Error(`postQuery.post threw an error: ${err}`));
89 | });
90 |
91 | // returns Bad Request code if postQuery fails
92 | if (result instanceof Error) {
93 | console.log(new Error(`postQuery.post threw an error: ${result}`));
94 | }
95 | });
96 |
97 | return next();
98 | };
99 | }
100 |
--------------------------------------------------------------------------------
/src/middleware/post.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { LogQueryData } from '../../@types/log';
3 |
4 | // returns the result
5 | export default async function postQuery(
6 | gateURI: string,
7 | projectID: string,
8 | queryData: LogQueryData
9 | ): Promise {
10 | // needs depth property once added into limiter functionality
11 | const { complexity, tokens, success, timestamp, loggedOn, latency } = queryData;
12 |
13 | // most likely redundant, in place for strange edge cases
14 | if (
15 | (complexity && complexity < 0) ||
16 | timestamp < 0 ||
17 | tokens < 0 ||
18 | loggedOn < 0 ||
19 | (latency && latency < 0)
20 | )
21 | throw new SyntaxError(`[gatelog] Query data cannot be negative\n`);
22 |
23 | // returns false to signal index to send back 400 status code
24 | if (!success && complexity)
25 | throw new SyntaxError(
26 | `[gatelog] Complexity should be undefined when query is blocked by limiter\n`
27 | );
28 |
29 | // TEMP: depth is random until added onto limiter middleware
30 | const depth = Math.round(Math.random() * 10);
31 |
32 | const variables = {
33 | projectQuery: {
34 | projectID,
35 | complexity,
36 | depth,
37 | tokens,
38 | success,
39 | timestamp,
40 | loggedOn,
41 | latency,
42 | },
43 | };
44 |
45 | // graphQL query
46 | const query = `
47 | mutation CreateProjectQuery($projectQuery: CreateProjectQueryInput!) {
48 | createProjectQuery(projectQuery: $projectQuery) {
49 | projectID
50 | complexity
51 | depth
52 | tokens
53 | success
54 | timestamp
55 | loggedOn
56 | latency
57 | }
58 | }
59 | `;
60 |
61 | const data = {
62 | query,
63 | variables,
64 | };
65 |
66 | // axios post request to the webapp's backend
67 | const result = await axios
68 | .post(`${gateURI}/gql`, data)
69 | .then((json) => json.data.data.createProjectQuery)
70 | .catch((err: Error): Error => {
71 | throw new Error(
72 | `[gatelog] Error posting project query\n${JSON.stringify(err, null, 2)}`
73 | );
74 | });
75 |
76 | // check in place to make sure query is posted to the correct project,
77 | // fails without crashing the server
78 | if (result?.projectID !== projectID) {
79 | throw new Error(
80 | `[gatelog] GraphQL error, resulting query's projectID does not match the ID entered\n`
81 | );
82 | }
83 |
84 | // returns project query object
85 | return result;
86 | }
87 |
--------------------------------------------------------------------------------
/src/middleware/verify.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | // the current expected length of project IDs
4 | // these are automatically generated by mongoDB
5 | const PROJECT_ID_LENGTH = 24;
6 |
7 | // the current expected length of API keys
8 | // these are random char strings generated in the
9 | // backend of the Gateway webapp
10 | const API_KEY_LENGTH = 10;
11 |
12 | /**
13 | * @function verification validates format of API requests & verifies key provided in header
14 | * matches key in associated project's entry in DB. If either fail, returns error.
15 | *
16 | * @param gateURI the web app's URI, during development it is localhost:3000
17 | * @param projectID the 20 char mongoDB-assigned ID for the project
18 | * @param logKey the 10 char random string given to user from webapp
19 | *
20 | * @returns Error if any of these parameters are syntactically wrong, or if the
21 | * web app's server is having issues, otherwise returns nothing
22 | */
23 | export default async function verification(
24 | gateURI: string,
25 | projectID: string,
26 | logKey: string
27 | ): Promise {
28 | let dbKey: string | Error = '';
29 |
30 | // if the gate URI responds with a bad status code, throw an error
31 | // (in place to make sure webapp is running)
32 | await axios(gateURI)
33 | .then((response) => {
34 | if (response.status >= 400)
35 | throw new SyntaxError('[Log API] Invalid Gateway URL provided');
36 | })
37 | // throws error if server is not running
38 | .catch((err) => {
39 | throw new Error(`[Log API] Server not running ${err}`);
40 | });
41 |
42 | // if project query is the wrong length: ?project=[projectID] is required
43 | if (projectID?.length !== PROJECT_ID_LENGTH) {
44 | throw new SyntaxError('[Log API] Project ID passed into middleware is invalid');
45 | }
46 |
47 | // if provided log_key length is wrong
48 | if (logKey?.length !== API_KEY_LENGTH) {
49 | throw new SyntaxError(
50 | '[Log API] Log_key header is an incorrect length, must be 10 characters.'
51 | );
52 | }
53 |
54 | // this endpoint returns the associated project's API key
55 | await axios(`${gateURI}/key/${projectID}`)
56 | .then((key: any): void => {
57 | dbKey = key.data;
58 | })
59 | .catch((err: Error): Error => {
60 | throw new Error(`[Log API] Communication error with Gateway backend ${err}`);
61 | });
62 |
63 | // if received DB key's length is wrong
64 | if (dbKey.length !== API_KEY_LENGTH)
65 | throw new SyntaxError('[Log API] API key from DB is incorrect length.');
66 |
67 | if (logKey !== dbKey)
68 | throw new Error(
69 | `[Log API] The log_key provided in header does not match the key of the project specified`
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/template.env:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Gate-Logger/52ac5380153ca294861a533dd29fc910844bd599/template.env
--------------------------------------------------------------------------------
/test/index.test.ts:
--------------------------------------------------------------------------------
1 | import 'jest';
2 | import request from 'supertest';
3 |
4 | import { LimQueryData } from '../@types/log';
5 |
6 | import app from './server';
7 |
8 | const MOCK_QUERY_DATA: LimQueryData = {
9 | complexity: Math.round(Math.random() * 10), // query cost
10 | tokens: Math.round(Math.random() * 10), // tokens remaining
11 | success: Math.random() < 0.5,
12 | };
13 |
14 | /**
15 | *
16 | * TESTING SUITE
17 | *
18 | */
19 | describe('Logger End to End Test', () => {
20 | describe('successful query', () => {
21 | test('Test API with middleware runs without errors', (done) => {
22 | // merely checks if test API can run the middleware without errors
23 | request(app)
24 | .post('/')
25 | .set('Content-type', 'application/json')
26 | .send({
27 | mockQueryData: MOCK_QUERY_DATA,
28 | })
29 | .expect(200)
30 | .expect(MOCK_QUERY_DATA)
31 | .end((err, res) => {
32 | if (err) {
33 | return err;
34 | }
35 | return done();
36 | });
37 | });
38 | });
39 |
40 | describe('unsuccessful query', () => {
41 | test('negative complexity passed into index', (done) => {
42 | const badMockQueryData: LimQueryData = {
43 | complexity: -1,
44 | tokens: 2,
45 | success: true,
46 | };
47 | const badData = {
48 | mockQueryData: badMockQueryData,
49 | };
50 |
51 | request(app)
52 | .post('/')
53 | .set('Content-type', 'application/json')
54 | .send(badData)
55 | .end((err, res) => {
56 | if (err) {
57 | console.log(`Supertest error: ${err}`);
58 | return err;
59 | }
60 | return done();
61 | });
62 | });
63 |
64 | test('query failure and complexity value passed into index', (done) => {
65 | const badMockQueryData: LimQueryData = {
66 | complexity: 2,
67 | tokens: 2,
68 | success: false,
69 | };
70 | const badData = {
71 | mockQueryData: badMockQueryData,
72 | };
73 |
74 | request(app)
75 | .post('/')
76 | .set('Content-type', 'application/json')
77 | .send(badData)
78 | .end((err, res) => {
79 | if (err) {
80 | console.log(`Supertest error: ${err}`);
81 | return err;
82 | }
83 | return done();
84 | });
85 | });
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/test/middleware/post.test.ts:
--------------------------------------------------------------------------------
1 | import 'jest';
2 | import postQuery from '../../src/middleware/post';
3 | import { LogQueryData } from '../../@types/log';
4 |
5 | import { MOCK_PROJECT_ID, MOCK_URI } from '../mockConstants';
6 |
7 | describe('Test API key header verification', () => {
8 | let mockURI: string;
9 | let mockProjectID: string;
10 | let mockQueryData: LogQueryData;
11 |
12 | let newPost: () => Promise;
13 |
14 | beforeEach(() => {
15 | /* The mock data below is pulled from a personal development dB.
16 |
17 | To perform some of these tests yourself, you must connect your dB
18 | to the webapp project and create a mock project with the webapp,
19 | then change these values to match the new data.
20 | */
21 | mockURI = MOCK_URI;
22 | mockProjectID = MOCK_PROJECT_ID;
23 | mockQueryData = {
24 | complexity: 4,
25 | timestamp: Date.now(),
26 | tokens: 8,
27 | success: true,
28 | loggedOn: 0,
29 | };
30 | });
31 |
32 | test('error throws when query data is incorrect', () => {
33 | mockQueryData = {
34 | complexity: 3,
35 | timestamp: 100,
36 | tokens: -1,
37 | success: true,
38 | loggedOn: 0,
39 | };
40 |
41 | newPost = async () => {
42 | await postQuery(mockURI, mockProjectID, mockQueryData);
43 | };
44 |
45 | expect(newPost).rejects.toThrowError();
46 | });
47 |
48 | test('No error thrown when query data is correct syntax', () => {
49 | newPost = async () => {
50 | await postQuery(mockURI, mockProjectID, mockQueryData);
51 | };
52 |
53 | expect(newPost).not.toThrow();
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/test/middleware/verify.test.ts:
--------------------------------------------------------------------------------
1 | import 'jest';
2 |
3 | import verification from '../../src/middleware/verify';
4 |
5 | import { MOCK_API_KEY, MOCK_PROJECT_ID, MOCK_URI } from '../mockConstants';
6 |
7 | let newVerification: () => Promise;
8 |
9 | describe('API Verification', () => {
10 | beforeEach(async () => {
11 | newVerification = async () => {
12 | await verification(MOCK_URI, MOCK_PROJECT_ID, MOCK_API_KEY);
13 | };
14 | });
15 | /*
16 | SKIP this test when the webapp server is running (it will fail)
17 | */
18 | xtest('throws when webapp server not running', () => {
19 | expect(newVerification).rejects.toThrow('[Log API] Server not running');
20 | });
21 | /*
22 | */
23 |
24 | test('throws when invalid project ID is provided', () => {
25 | newVerification = async () => {
26 | await verification(MOCK_URI, 'error', MOCK_API_KEY);
27 | };
28 |
29 | expect(newVerification).rejects.toThrow();
30 | });
31 |
32 | test('throws when invalid API key header is provided', () => {
33 | newVerification = async () => {
34 | await verification(MOCK_URI, MOCK_PROJECT_ID, 'error');
35 | };
36 |
37 | expect(newVerification).rejects.toThrowError();
38 | });
39 |
40 | test('throws when key provided does not match the key in dB', () => {
41 | newVerification = async () => {
42 | await verification(MOCK_URI, MOCK_PROJECT_ID, '10kcharact');
43 | };
44 |
45 | expect(newVerification).rejects.toThrow();
46 | });
47 |
48 | test('no errors when keys match', () => {
49 | expect(newVerification).not.toThrow();
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/test/mockConstants.ts:
--------------------------------------------------------------------------------
1 | /* The mock data below is pulled from a personal development dB.
2 |
3 | To perform many tests in this suite, you must connect your dB
4 | to the webapp project and create a mock project with the webapp,
5 | then change these values to match the new data.
6 | */
7 | export const MOCK_PROJECT_ID = '62cce5cf4bb3340a30ad3428';
8 |
9 | export const MOCK_API_KEY = 'CxVANsQL3o';
10 |
11 | export const MOCK_URI = 'http://localhost:3000';
12 |
--------------------------------------------------------------------------------
/test/server.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import bodyParser from 'body-parser';
3 |
4 | import gatelog from '../src/index';
5 |
6 | import { MOCK_API_KEY, MOCK_PROJECT_ID } from './mockConstants';
7 |
8 | const app = express();
9 |
10 | // logger middleware instantiation, changes res.end definition
11 | app.use(gatelog(MOCK_PROJECT_ID, MOCK_API_KEY));
12 |
13 | app.use(bodyParser.json());
14 |
15 | // mocking limiter middleware (to set res.locals.graphQLGate)
16 | // this is in the case that a query goes through successfully
17 | app.use((req, res, next) => {
18 | res.locals.graphqlGate = req.body.mockQueryData;
19 | return next();
20 | });
21 |
22 | /**
23 | * this res.end() is in place for ending the middleware chain
24 | * and firing the event listener in gatelog's middleware callback.
25 | * one a response is sent back to the client, the functionality
26 | * within the event listener will execute
27 | */
28 | app.use('/', (req, res) => {
29 | res.send(req.body.mockQueryData);
30 | });
31 |
32 | // // for manual middleware tests
33 | // app.listen(3001, () => {
34 | // console.log('test server running');
35 | // });
36 |
37 | export default app;
38 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": [
4 | // repeated from base config's "include" setting
5 | "src",
6 | "test",
7 |
8 | // these are the eslint-only inclusions
9 | ".eslintrc.json"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "strict": true,
6 | "removeComments": false,
7 | "preserveConstEnums": true,
8 | "sourceMap": true,
9 | "target": "es5",
10 | "lib": ["dom", "dom.iterable", "esnext"],
11 | "allowJs": true,
12 | "skipLibCheck": true,
13 | "esModuleInterop": true,
14 | "allowSyntheticDefaultImports": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": false,
20 | "typeRoots": ["./@types", "node_modules/@types"],
21 | "types": ["node", "jest"],
22 | "noImplicitAny": true
23 | },
24 | "include": ["src/**/*.ts", "src/**/*.js", "test/**/*.ts", "test/**/*.js", "./@types/*"],
25 | "exclude": ["node_modules", "**/*.spec.ts"]
26 | }
27 |
--------------------------------------------------------------------------------