Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@alexbeletsky
Last active August 20, 2020 22:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexbeletsky/248b2072f8d6bd33ea2705a9aac5900c to your computer and use it in GitHub Desktop.
Save alexbeletsky/248b2072f8d6bd33ea2705a9aac5900c to your computer and use it in GitHub Desktop.
/* eslint-disable no-console */
/* eslint-disable prefer-arrow-callback */
import expect from 'expect';
import { createTestClient } from 'apollo-server-testing';
import ggl from 'graphql-tag';
import config from '../../../config';
import { createServer } from '../../../src/server';
import { createTestApiServer } from '../../utils';
import { fixtures } from './fixtures';
describe('[queries] channel search queries', function () {
before('create graphql server', async function () {
const { apollo, server } = await createServer();
this.apollo = apollo;
this.server = server;
this.client = createTestClient(this.apollo);
});
before('create api server', async function () {
const { server } = await createTestApiServer(config.aurum);
this.apiServer = server;
});
after('stop server', async function () {
await this.server.close();
await this.apiServer.close();
});
describe('with channel insights field', function () {
before('reset api calls', function () {
this.apiServer.reset();
});
before('setup aurum calls', function () {
this.apiServer.mockResponse({
method: 'GET',
url: '/v2/channels/search',
response: {
statusCode: 200,
data: fixtures.aurum.instagram,
},
});
});
before('execute query', async function () {
const {
client: { query },
} = this;
const QUERY = ggl`
query channelSearch(
$page: Int = 1,
$query: ChannelSearchQuery,
$sort: ChannelSearchSort
) {
channelSearch(page: $page, query: $query, sort: $sort) {
data {
id
insights {
... on PlatformInsights {
followedBy
follows
media
}
}
}
}
}
`;
this.response = await query({
query: QUERY,
variables: {
query: { platforms: ['platform'] },
sort: { createdAt: -1 },
},
});
});
it('should not have errors', function () {
expect(this.response.errors).toNotExist();
});
it('should have correct api requests', function () {
expect(this.aurumServer.requests()).toMatchSnapshot(this);
});
it('should have correct response', function () {
expect(this.response).toMatchSnapshot(this);
});
});
});
// inspired by James Shore Spy-Server
// https://www.jamesshore.com/v2/blog/2018/testing-without-mocks#spy-server
import _ from 'lodash';
import Http from 'http';
import { promisify } from 'util';
export class ApiTestServer {
constructor(options = {}) {
const { url } = options;
if (!url) {
throw new Error('missing url parameter');
}
this.url = new URL('/', url);
this.router = new Router();
this.reset();
}
reset() {
this.router.reset();
}
hostname() {
return this.url.hostname;
}
port() {
return this.url.port;
}
host() {
return this.url.host;
}
async start() {
this.server = Http.createServer();
this.server.on('request', ApiTestServer.handleRequest.bind(null, this));
await promisify(this.server.listen.bind(this.server))(
this.port(),
this.hostname(),
);
}
async close() {
await promisify(this.server.close.bind(this.server))();
}
mockResponse(response) {
this.router.setResponse(response);
}
requests() {
return this.router.requests;
}
static handleRequest(self, request, response) {
self.router.route(request, response);
}
}
class Router {
constructor() {
this.defaultResponse = { statusCode: 200, data: {} };
this.reset();
}
reset() {
this.routes = {};
this.requests = [];
}
setResponse({ method, url, response }) {
if (!method) {
throw new Error('missing method parameter');
}
if (!url) {
throw new Error('missing url parameter');
}
if (!response) {
throw new Error('missing response parameter');
}
if (!response.statusCode || !response.data) {
throw new Error('response is missing statusCode or data');
}
const routeKey = Router.routeKey(method, url);
const route = this.routes[routeKey];
this.routes[routeKey] = route || [];
this.routes[routeKey].push(response);
}
route(request, response) {
const { method, url } = request;
const { pathname, searchParams } = new URL(url, 'http://test.server');
const { statusCode, data, skipped } = this.responseFromRoute(
method,
pathname,
);
const requestLog = {
request: {
method,
url: pathname,
params: searchParams,
headers: _.omit(request.headers, 'connection'),
body: '',
},
response: {
statusCode,
data,
skipped,
},
};
request.on('data', (data) => {
requestLog.request.body += data;
});
this.requests.push(requestLog);
return Router.endRequest(request, response, {
statusCode,
data,
});
}
responseFromRoute(method, url) {
const responses = this.routes[Router.routeKey(method, url)];
if (!responses || !responses.length) {
return {
...this.defaultResponse,
skipped:
'This request is NOT mocked and responded with default response',
};
}
return { ...responses.shift(), skipped: false };
}
static routeKey(method, url) {
return `${method}.${url}`;
}
static endRequest(request, response, { statusCode, data }) {
request.on('end', () => {
response.statusCode = statusCode;
response.end(JSON.stringify(data));
});
}
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`[queries] channel search queries with channel insights field for instagram should have correct api requests 1`] = `
Array [
Object {
"request": Object {
"body": "{\\"token\\":\\"authToken\\"}",
"headers": Object {
"accept": "application/json",
"content-length": "21",
"content-type": "application/json",
"host": "localhost:8084",
},
"method": "POST",
"params": URLSearchParams {
Symbol(query): Array [],
Symbol(context): "http://test.server/v1/auth/token",
},
"url": "/v1/auth/token",
},
"response": Object {
"data": Object {},
"skipped": "This request is NOT mocked and responded with default response",
"statusCode": 200,
},
},
Object {
"request": Object {
"body": "",
"headers": Object {
"accept": "application/json",
"host": "localhost:8084",
},
"method": "GET",
"params": URLSearchParams {
Symbol(query): Array [
"page",
"1",
"query",
"{\\"platforms\\":[\\"instagram\\"],\\"hideForExternals\\":false}",
"sort",
"{\\"createdAt\\":-1}",
"fields",
"[\\"id\\",\\"user\\",\\"name\\",\\"platform\\",\\"reach\\",\\"categories\\",\\"data\\",\\"kpi\\",\\"external\\"]",
],
Symbol(context): "http://test.server/v2/channels/search?page=1&query=%7B%22platforms%22%3A%5B%22instagram%22%5D%2C%22hideForExternals%22%3Afalse%7D&sort=%7B%22createdAt%22%3A-1%7D&fields=%5B%22id%22%2C%22user%22%2C%22name%22%2C%22platform%22%2C%22reach%22%2C%22categories%22%2C%22data%22%2C%22kpi%22%2C%22external%22%5D",
},
"url": "/v2/channels/search",
},
"response": Object {
"data": Object {
"data": Array [
Object {
"categories": Array [],
"data": Object {
"url": "https://instagram.com/oneofthebest",
},
"external": false,
"id": "000000000000000000000001",
"insights": Object {
"date": 2020-08-20T20:06:24.522Z,
"followedBy": 10,
"follows": 1000,
"media": 111,
},
"kpi": Object {},
"name": "oneofthebest",
"platform": "instagram",
"reach": 10000,
"user": Object {
"firstname": "dude",
"id": 1,
},
},
Object {
"categories": Array [],
"data": Object {
"url": "https://instagram.com/not",
},
"external": false,
"id": "000000000000000000000002",
"insights": Object {
"date": 2020-08-20T20:06:24.522Z,
"followedBy": 1000,
"follows": 1,
"media": 500,
},
"kpi": Object {},
"name": "not",
"platform": "instagram",
"reach": 8999,
"user": Object {
"firstname": "ahh",
"id": 1,
},
},
Object {
"categories": Array [],
"data": Object {
"url": "https://instagram.com/billie",
},
"external": false,
"id": "000000000000000000000003",
"insights": Object {},
"kpi": Object {},
"name": "billie",
"platform": "instagram",
"reach": 0,
"user": Object {
"firstname": "billie",
"id": 1,
},
},
],
"meta": Object {},
},
"skipped": false,
"statusCode": 200,
},
},
]
`;
exports[`[queries] channel search queries with channel insights field for instagram should have correct response 1`] = `
Object {
"data": Object {
"channelSearch": Object {
"data": Array [
Object {
"id": "000000000000000000000001",
"insights": Object {
"followedBy": 10,
"follows": 1000,
"media": 111,
},
},
Object {
"id": "000000000000000000000002",
"insights": Object {
"followedBy": 1000,
"follows": 1,
"media": 500,
},
},
Object {
"id": "000000000000000000000003",
"insights": Object {},
},
],
},
},
"errors": undefined,
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment