Skip to content

Commit

Permalink
feat(message): tidy up Message interface and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Apr 22, 2018
1 parent 65582b8 commit 58c334c
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 44 deletions.
7 changes: 2 additions & 5 deletions examples/graphql/consumer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// POC that graphQL endpoints can be tested!
/* tslint:disable:no-unused-expression object-literal-sort-keys max-classes-per-file no-empty */

import * as chai from "chai";
import * as chaiAsPromised from "chai-as-promised";
import * as sinon from "sinon";
import { like, term } from "../../src/dsl/matchers";
import { query } from "./consumer";
import { Pact, GraphQLInteraction } from "../../src/pact";
Expand Down Expand Up @@ -51,8 +48,8 @@ describe("GraphQL example", () => {
return provider.addInteraction(graphqlQuery);
});

it("returns the correct response", (done) => {
expect(query()).to.eventually.deep.eq({ hello: "Hello world!" }).notify(done);
it("returns the correct response", () => {
return expect(query()).to.eventually.deep.eq({ hello: "Hello world!" });
});

// verify with Pact, and reset expectations
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"consumer driven testing"
],
"author": "Matt Fellows <m@onegeek.com.au> (http://twitter.com/matthewfellows)",
"contributors": [{
"contributors": [
{
"name": "Tarcio Saraiva",
"email": "tarcio@gmail.com",
"url": "http://twitter.com/tarciosaraiva"
Expand Down
Empty file added src/dsl/Untitled-1
Empty file.
96 changes: 96 additions & 0 deletions src/dsl/graphql.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import * as chai from "chai";
import * as chaiAsPromised from "chai-as-promised";
import { GraphQLInteraction } from "./graphql";
import { Interaction } from "../pact";
import { isMatcher, regex } from "./matchers";

chai.use(chaiAsPromised);
const expect = chai.expect;

describe("GraphQLInteraction", () => {
let interaction: GraphQLInteraction;

beforeEach(() => {
interaction = new GraphQLInteraction();
});

describe("#withOperation", () => {
describe("when given a valid operation", () => {
it("should not fail", () => {
interaction.uponReceiving("a request");
interaction.withOperation("query");
interaction.withQuery("{ hello }");

const json: any = interaction.json();
expect(json.request.body.operationName).to.eq("query");
});
});
describe("when no operation is provided", () => {
it("should marshal to null", () => {
interaction.uponReceiving("a request");
interaction.withQuery("{ hello }");

const json: any = interaction.json();
expect(json.request.body.operationName).to.eq(null);
});
});
describe("when given an invalid operation", () => {
it("should fail with an error", () => {
expect(interaction.withOperation.bind("aoeu")).to.throw(Error);
});
});
});

describe("#withVariables", () => {
describe("when given a set of variables", () => {
it("should add the variables to the payload", () => {
interaction.uponReceiving("a request");
interaction.withOperation("query");
interaction.withQuery("{ hello }");
interaction.withVariables({
foo: "bar",
});

const json: any = interaction.json();
expect(json.request.body.variables).to.deep.eq({ foo: "bar" });
});
});
});

describe("#withQuery", () => {
beforeEach(() => {
interaction.uponReceiving("a request");
interaction.withOperation("query");
interaction.withQuery("{ hello }");
interaction.withVariables({
foo: "bar",
});
});

describe("when given an invalid query", () => {
it("should fail with an error", () => {
expect(() => interaction.withQuery("{ not properly terminated")).to.throw(Error);
});
});

describe("when given a valid valid query", () => {
it("should properly marshal the query", () => {
const json: any = interaction.json();
expect(isMatcher(json.request.body.query)).to.eq(true);
expect(json.request.body.query.getValue()).to.eq("{ hello }");
});

it("should add regular expressions for the whitespace in the query", () => {
const json: any = interaction.json();

expect(isMatcher(json.request.body.query)).to.eq(true);
const r = new RegExp(json.request.body.query.data.matcher.s, "g");
const lotsOfWhitespace = `{ hello
}`;
expect(r.test(lotsOfWhitespace)).to.eq(true);
});
});
});

});
18 changes: 5 additions & 13 deletions src/dsl/utils.ts → src/dsl/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/**
* Pact Utilities module.
* @module PactUtils
* Pact GraphQL module.
*
* @module GraphQL
*/
import { Interaction, ResponseOptions, RequestOptions, InteractionState, Query } from "../dsl/interaction";
import { HTTPMethod, methods } from "../common/request";
import { MatcherResult, regex } from "./matchers";
import { extend } from "underscore";
import { keys, isNil, omitBy } from "lodash";
import { keys, isNil } from "lodash";
import gql from "graphql-tag";
import { ERROR } from "bunyan";

export type GraphQLOperation = "query" | "mutation" | null;

Expand All @@ -29,9 +29,6 @@ export class GraphQLInteraction extends Interaction {

/**
* The type of GraphQL operation. Generally not required.
*
* @param {string} operation The operation, one of "query"|"mutation"
* @returns {Interaction} interaction
*/
public withOperation(operation: GraphQLOperation) {
if (!operation || operation && keys(GraphQLOperations).indexOf(operation.toString()) < 0) {
Expand All @@ -45,8 +42,6 @@ export class GraphQLInteraction extends Interaction {

/**
* Any variables used in the Query
* @param {Object} variables a k/v set of variables for the query
* @returns {Interaction} interaction
*/
public withVariables(variables: GraphQLVariables) {
this.variables = variables;
Expand All @@ -71,8 +66,6 @@ export class GraphQLInteraction extends Interaction {
* }
* }"
* }'
* @param {Object} query the actual GraphQL query, as per example above.
* @returns {Interaction} interaction
*/
public withQuery(query: string) {
if (isNil(query)) {
Expand All @@ -92,7 +85,6 @@ export class GraphQLInteraction extends Interaction {

/**
* Returns the interaction object created.
* @returns {Object}
*/
public json(): InteractionState {
if (isNil(this.query)) {
Expand All @@ -105,7 +97,7 @@ export class GraphQLInteraction extends Interaction {
this.state.request = extend({
body: {
operationName: this.operation,
query: regex({ generate: this.query, matcher: this.query.replace(/\s+/g, "\\s+") }),
query: regex({ generate: this.query, matcher: this.query.replace(/\s+/g, "\\s*") }),
variables: this.variables,
},
headers: { "content-type": "application/json" },
Expand Down
24 changes: 12 additions & 12 deletions src/dsl/interaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ const expect = chai.expect;
describe("Interaction", () => {
describe("#given", () => {
it("creates Interaction with provider state", () => {
const actual = new Interaction().given("provider state").json();
expect(actual).to.eql({ providerState: "provider state" });
const actual = new Interaction().uponReceiving("r").given("provider state").json();
expect(actual).to.eql({ description: "r", providerState: "provider state" });
});

describe("without provider state", () => {
it("creates Interaction when blank", () => {
const actual = new Interaction().given("").json();
expect(actual).to.eql({});
const actual = new Interaction().uponReceiving("r").given("").json();
expect(actual).to.eql({ description: "r" });
});
it("creates Interaction when nothing is passed", () => {
const actual = new Interaction().json();
expect(actual).to.eql({});
const actual = new Interaction().uponReceiving("r").json();
expect(actual).to.eql({ description: "r" });
});
});
});
Expand Down Expand Up @@ -67,7 +67,7 @@ describe("Interaction", () => {
.json();

it("has a state containing only the given keys", () => {
expect(actual).to.have.keys("request");
expect(actual).to.have.property("request");
expect(actual.request).to.have.keys("method", "path");
});

Expand All @@ -88,7 +88,7 @@ describe("Interaction", () => {
}).json();

it("has a full state all available keys", () => {
expect(actual).to.have.keys("request");
expect(actual).to.have.property("request");
expect(actual.request).to.have.keys("method", "path", "query", "headers", "body");
});
});
Expand All @@ -108,12 +108,12 @@ describe("Interaction", () => {

describe("with only mandatory params", () => {
interaction = new Interaction();
interaction.uponReceiving("request")
interaction.uponReceiving("request");
interaction.willRespondWith({ status: 200 });
const actual = interaction.json();

it("has a state compacted with only present keys", () => {
expect(actual).to.have.keys("response");
expect(actual).to.have.property("response");
expect(actual.response).to.have.keys("status");
});

Expand All @@ -124,7 +124,7 @@ describe("Interaction", () => {

describe("with all other parameters", () => {
interaction = new Interaction();
interaction.uponReceiving("request")
interaction.uponReceiving("request");
interaction.willRespondWith({
body: { id: 1, name: "Test", due: "tomorrow" },
headers: { "Content-Type": "application/json" },
Expand All @@ -134,7 +134,7 @@ describe("Interaction", () => {
const actual = interaction.json();

it("has a full state all available keys", () => {
expect(actual).to.have.keys("response");
expect(actual).to.have.property("response");
expect(actual.response).to.have.keys("status", "headers", "body");
});
});
Expand Down
6 changes: 2 additions & 4 deletions src/dsl/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { MatcherResult } from "./matchers";
*
* @module Message
*/

export interface Metadata { [name: string]: string | MatcherResult; }

/**
Expand All @@ -23,7 +22,6 @@ export interface Message {
}

// Consumer message handler
export type Handler = (m: Message) => Promise<any>;

export interface MessageHandlers { [name: string]: Handler; }
export type MessageHandler = (m: Message) => Promise<any>;
export interface MessageHandlers { [name: string]: MessageHandler; }
export interface StateHandlers { [name: string]: (state: string) => Promise<any>; }
8 changes: 4 additions & 4 deletions src/messageConsumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { isEmpty, cloneDeep } from "lodash";
import { MatcherResult, extractPayload } from "./dsl/matchers";
import { qToPromise } from "./common/utils";
import { Metadata, Message, Handler } from "./dsl/message";
import { Metadata, Message, MessageHandler } from "./dsl/message";
import logger from "./common/logger";
import serviceFactory from "@pact-foundation/pact-node";
import { MessageConsumerOptions } from "./dsl/options";
Expand Down Expand Up @@ -109,7 +109,7 @@ export class MessageConsumer {
* @param handler A message handler, that must be able to consume the given Message
* @returns {Promise}
*/
public verify(handler: Handler): Promise<any> {
public verify(handler: MessageHandler): Promise<any> {
logger.info("Verifying message");

return this.validate()
Expand Down Expand Up @@ -150,7 +150,7 @@ const isMessage = (x: Message | any): x is Message => {

// bodyHandler takes a synchronous function and returns
// a wrapped function that accepts a Message and returns a Promise
export function synchronousBodyHandler(handler: (body: any) => any): Handler {
export function synchronousBodyHandler(handler: (body: any) => any): MessageHandler {
return (m: Message): Promise<any> => {
const body = m.content;

Expand All @@ -168,6 +168,6 @@ export function synchronousBodyHandler(handler: (body: any) => any): Handler {
// bodyHandler takes an asynchronous (promisified) function and returns
// a wrapped function that accepts a Message and returns a Promise
// TODO: move this into its own package and re-export?
export function asynchronousBodyHandler(handler: (body: any) => Promise<any>): Handler {
export function asynchronousBodyHandler(handler: (body: any) => Promise<any>): MessageHandler {
return (m: Message) => handler(m.content);
}
Loading

0 comments on commit 58c334c

Please sign in to comment.