Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Graphql Transformer #125

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,32 @@ yarn run example:test:cy:run
```sh
yarn run example:test:unit
```


## Graphql


Start the graphql API in one terminal:

```bash
yarn start:gql
```

Then run the graphql client in another terminal:
```bash
yarn run:gqlClient
```


Generate a consumer pact, by running a jest test, with msw-pact, testing the gql consumer

```
test:gql:consumer
```

Run the provider verification for the gql consumer


```
test:gql:verifier
```
59 changes: 59 additions & 0 deletions examples/react/src/graphqlApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use strict";
// Using the example graphql api here: https://www.apollographql.com/blog/graphql/examples/building-a-graphql-api/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const server_1 = require("@apollo/server");
const graphql_tag_1 = __importDefault(require("graphql-tag"));
const { startStandaloneServer } = require('@apollo/server/standalone');
// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = (0, graphql_tag_1.default) `
# Comments in GraphQL strings (such as this one) start with the hash (#) symbol.



# This "Book" type defines the queryable fields: 'title' and 'author'.
type Book {
title: String
author: String
}

# The "GetBooksQuery" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each. In this
# case, the "GetBooksQuery" query returns an array of zero or more Books (defined above).
type Query {
books: [Book]
}
`;
const books = [
{
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
},
{
title: 'Wuthering Heights',
author: 'Emily Brontë',
},
];
// Resolvers define the technique for fetching the types defined in the
// schema. This resolver retrieves books from the "books" array above.
const resolvers = {
Query: {
books: () => books,
},
};
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new server_1.ApolloServer({ typeDefs, resolvers });
startStandaloneServer(server, {
listen: {
port: 4000,
path: '/graphql',
},
//@ts-ignore
}).then((url) => {
console.log(`🚀 Server ready at ${url.url}`);
});
36 changes: 36 additions & 0 deletions examples/react/src/graphqlClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GetBooksQuery = void 0;
const client_1 = require("@apollo/client");
const graphql_tag_1 = __importDefault(require("graphql-tag"));
const cross_fetch_1 = __importDefault(require("cross-fetch"));
const client = new client_1.ApolloClient({
cache: new client_1.InMemoryCache(),
link: new client_1.HttpLink({ uri: 'http://127.0.0.1:4000/graphql', fetch: cross_fetch_1.default }),
headers: {
foo: 'bar',
},
});
function GetBooksQuery() {
return client
.query({
query: (0, graphql_tag_1.default) `
query GetBooksQuery {
books {
title
author
}
}
`,
})
//@ts-ignore
.then((result) => result.data);
}
exports.GetBooksQuery = GetBooksQuery;
// For testing
// GetBooksQuery().then(results => {
// console.log(results);
// })
32 changes: 22 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"dist:ci": "npm run build:clean && npm test",
"release": "standard-version",
"release:trigger": "./scripts/trigger-release.sh",
"test:gql:consumer": "jest src/pactFromMswServerGql.msw.spec.ts",
"test:gql:provider": "jest src/fixtures/provider.msw.spec.ts",
"start:gql": "ts-node examples/react/src/graphqlApi.js run",
"run:gqlClient": "ts-node examples/react/src/graphqlClient.ts",
"example:test:cy:run": "cd examples/react && npm run start:test:cy:run",
"example:test:cy:open": "cd examples/react && npm run start:test:cy:open",
"example:test:unit": "cd examples/react && npm test",
Expand All @@ -43,22 +47,30 @@
"example:install:link": "yarn link && npm run example:install && npm run example:link"
},
"devDependencies": {
"@babel/preset-env": "7.16.11",
"@pact-foundation/pact": "9.17.2",
"@apollo/client": "^3.8.6",
"@apollo/server": "^4.9.4",
"apollo-link-http": "^1.5.17",
"@babel/preset-env": "7.23.2",
"@pact-foundation/pact": "12.1.0",
"@types/axios": "0.14.0",
"@types/jest": "27.4.1",
"@typescript-eslint/eslint-plugin": "5.56.0",
"@typescript-eslint/parser": "5.59.5",
"@types/jest": "27.5.2",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"axios": "0.26.1",
"babel-jest": "27.5.1",
"eslint": "8.10.0",
"cross-fetch": "^4.0.0",
"graphql": "^16.8.1",
"graphql-tag": "^2.12.6",
"eslint": "8.51.0",
"jest": "27.5.1",
"msw": "0.36.8",
"nock": "13.2.4",
"regenerator-runtime": "0.13.11",
"rimraf": "5.0.1",
"nock": "13.3.4",
"regenerator-runtime": "0.14.0",
"rimraf": "5.0.5",
"react": "^16.14.0",
"standard-version": "9.5.0",
"ts-jest": "27.1.3",
"ts-jest": "27.1.5",
"ts-node": "^10.9.1",
"typescript": "4.9.5"
},
"dependencies": {
Expand Down
74 changes: 74 additions & 0 deletions src/convertMswGqlMatchToPact.msw.spec.wip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

/**
* TODO:
* Create a "sampleGqlMatch" like we are doing for the regular rest test (const sampleMatch: MswMatch[] = [ {...data: "myData"}])
*
* Should probably keep it to the same generated pactfile as listed in the original test, except with queries instead of requests
*/

// Example graphql pact file (from running examples/graphql/ in the pact-js repo):
const pactFileExample = {
"consumer": {
"name": "GraphQLConsumer"
},
"interactions": [
{
"description": "a hello request",
"request": {
"body": {
"operationName": "HelloQuery",
"query": "\n query HelloQuery {\n hello\n }\n ",
"variables": {
"foo": "bar"
}
},
"headers": {
"Content-Type": "application/json"
},
"matchingRules": {
"$.body.query": {
"match": "regex",
"regex": "\\s*query\\s*HelloQuery\\s*\\{\\s*hello\\s*\\}\\s*"
}
},
"method": "POST",
"path": "/graphql"
},
"response": {
"body": {
"data": {
"hello": "Hello world!"
}
},
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"matchingRules": {
"$.body.data.hello": {
"match": "type"
}
},
"status": 200
}
}
],
"metadata": {
"pact-js": {
"version": "11.0.2"
},
"pactRust": {
"ffi": "0.4.0",
"models": "1.0.4"
},
"pactSpecification": {
"version": "2.0.0"
}
},
"provider": {
"name": "GraphQLProvider"
}
}




64 changes: 64 additions & 0 deletions src/fixtures/graphqlApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Using the example graphql api here: https://www.apollographql.com/blog/graphql/examples/building-a-graphql-api/

import { ApolloServer } from "@apollo/server";
import gql from "graphql-tag";
const { startStandaloneServer } = require("@apollo/server/standalone");

// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = gql`
# Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

# This "Book" type defines the queryable fields: 'title' and 'author'.
type Book {
title: String
author: String
}

# The "GetBooksQuery" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each. In this
# case, the "GetBooksQuery" query returns an array of zero or more Books (defined above).
type Query {
books: [Book]
}
`;

const books = [
{
title: "The Great Gatsby",
author: "F. Scott Fitzgerald",
},
{
title: "Wuthering Heights",
author: "Emily Brontë",
},
];

// Resolvers define the technique for fetching the types defined in the
// schema. This resolver retrieves books from the "books" array above.
const resolvers = {
Query: {
books: () => books,
},
};

// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
export const server = new ApolloServer({ typeDefs, resolvers });

export const runStandaloneServer = async (server: any) => {
await startStandaloneServer(server, {
listen: {
port: 4000,
path: "/graphql",
},
//@ts-ignore
}).then((url) => {
console.log(`🚀 Server ready at ${url.url}`);
});
};

if (process.argv[2] == "run"){
runStandaloneServer(server)
}
32 changes: 32 additions & 0 deletions src/fixtures/graphqlClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import gql from 'graphql-tag';
import fetch from 'cross-fetch';

const client = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({ uri: 'http://127.0.0.1:4000/graphql', fetch }),
headers: {
foo: 'bar',
},
});

export function GetBooksQuery() {
return client
.query({
query: gql`
query GetBooksQuery {
books {
title
author
}
}
`,
})
//@ts-ignore
.then((result) => result.data);
}

// For testing
// GetBooksQuery().then(results => {
// console.log(results);
// })
28 changes: 28 additions & 0 deletions src/fixtures/provider.msw.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { LogLevel, Verifier } from '@pact-foundation/pact';
import { runStandaloneServer, server } from './graphqlApi';
import path from 'path'

// Verify that the provider meets all consumer expectations
describe('Pact Verification', () => {
beforeAll(async () => {
await runStandaloneServer(server)
});
afterAll(async () => {
await server.stop()
});
console.log(path.resolve(process.cwd(), "./msw_generated_pacts/testConsumer-graphql.json"))

it('validates the expectations of Matching Service', async () => {
const opts = {
pactUrls: [path.resolve(process.cwd(), "./msw_generated_pacts/testConsumer-graphql.json")],
providerVersion: "1.0.0",
providerVersionBranch: 'master',
providerName: "graphql",
logLevel: "info" as LogLevel,
providerBaseUrl: "http://localhost:4000",
timeout: 5000
};

await new Verifier(opts).verifyProvider()
});
});
Loading