Skip to content

Commit 14a464c

Browse files
committed
feat(examples): WIP e2e example
1 parent ac0c786 commit 14a464c

9 files changed

Lines changed: 305 additions & 0 deletions

File tree

examples/e2e/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Pact JS End-to-End Example
2+
3+
Using a simple animal dating API, we demonstrate the following Pact features:
4+
5+
* Consumer testing and pact file generation, including advanced features like:
6+
* [Flexible matching](https://docs.pact.io/documentation/javascript/flexible_matching.html)
7+
* [Provider states](https://docs.pact.io/documentation/provider_states.html)
8+
* Sharing Pacts by publishing to and retrieving from a [Pact Broker](https://github.com/bethesque/pact_broker)
9+
* Provider side verification
10+
11+
This comprises a complete E2E example that can be used as a basis for projects.
12+
13+
## The Example Project
14+
15+
[Matching API] -> [Profile API]+\(DB\)
16+
17+
### Provider (Profile API)
18+
19+
Provides Animal profile information, including interests, zoo location and other personal details.
20+
21+
### Consumer (Matching API)
22+
23+
Given an animal profile, recommends a suitable partner based on similar interests.
24+
25+
## Running
26+
27+
1. `npm install`
28+
1. `npm test:consumer`
29+
1. `npm test:publish`
30+
1. `npm test:provider`
31+
32+
33+
## Viewing contracts with the Pact Broker
34+
35+
A test [Pact Boker](https://github.com/bethesque/pact_broker) is running at https://test.pact.dius.com.au:
36+
37+
* Username: `dXfltyFMgNOFZAxr8io9wJ37iUpY42M`
38+
* Password: `O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1`
39+
40+
Or use the API: `curl -v -u 'dXfltyFMgNOFZAxr8io9wJ37iUpY42M:O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1' https://test.pact.dius.com.au`

examples/e2e/consumer.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const express = require('express');
2+
const request = require('superagent-bluebird-promise');
3+
const server = express();
4+
const API_HOST = process.env.API_HOST;
5+
6+
const availableAnimals = () => {
7+
return request
8+
.get(`${API_HOST}/animals/available`)
9+
.then(res => res.body,
10+
err => []);
11+
}
12+
13+
// Suggestions function:
14+
// Given availability and sex etc. find available suitors.
15+
const suggestion = (animal) => {
16+
const predicates = [
17+
((candidate, animal) => candidate.id != animal.id),
18+
((candidate, animal) => candidate.gender != animal.gender),
19+
((candidate, animal) => candidate.animal == animal.animal)
20+
]
21+
22+
let weights = [
23+
((candidate, animal) => Math.abs(candidate.age - animal.age))
24+
]
25+
26+
return availableAnimals().then(available => {
27+
const eligible = available.filter(a => !predicates.map(p => p(a, animal)).includes(false));
28+
29+
return eligible.map(candidate => {
30+
let score = weights.reduce((acc, weight) => {
31+
return acc - weight(candidate, animal);
32+
}, 100);
33+
34+
return {
35+
"score": score,
36+
"animal": candidate
37+
}
38+
});
39+
});
40+
}
41+
42+
// API
43+
server.get('/suggestions/:animalId', (req, res) => {
44+
if (!req.params.animalId) {
45+
res.writeHead(400);
46+
res.end();
47+
}
48+
49+
request(`${API_HOST}/animals/${req.params.animalId}`, (err, r, body) => {
50+
if (!err && r.statusCode == 200) {
51+
suggestion(r.body).then(suggestions => {
52+
res.json(suggestions);
53+
})
54+
} else if (r.statusCode == 404){
55+
res.writeHead(404);
56+
res.end();
57+
} else {
58+
res.writeHead(500);
59+
res.end();
60+
}
61+
});
62+
});
63+
64+
module.exports = server;

examples/e2e/consumerService.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var server = require('./consumer.js');
2+
3+
var server = server.listen(8080, function () {
4+
var host = server.address().address;
5+
var port = server.address().port;
6+
7+
console.log("Animal Matching Service listening on http://%s:%s", host, port)
8+
})

examples/e2e/data/animalData.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
[{
2+
"first_name": "Billy",
3+
"last_name": "Goat",
4+
"animal": "goat",
5+
"age": 21,
6+
"gender": "M",
7+
"location": {
8+
"description": "Melbourne Zoo",
9+
"country": "Australia",
10+
"post_code": 3000
11+
},
12+
"eligibility": {
13+
"available": true,
14+
"previously_married": false
15+
},
16+
"interests": [
17+
"walks in the garden/meadow",
18+
"munching on a paddock bomb",
19+
"parkour"
20+
]
21+
},
22+
{
23+
"first_name": "Nanny",
24+
"animal": "goat",
25+
"last_name": "Doe",
26+
"age": 27,
27+
"gender": "F",
28+
"location": {
29+
"description": "Werribee Zoo",
30+
"country": "Australia",
31+
"post_code": 3000
32+
},
33+
"eligibility": {
34+
"available": true,
35+
"previously_married": true
36+
},
37+
"interests": [
38+
"walks in the garden/meadow",
39+
"parkour"
40+
]
41+
},
42+
{
43+
"first_name": "Simba",
44+
"last_name": "Cantwaittobeking",
45+
"animal": "lion",
46+
"age": 4,
47+
"gender": "M",
48+
"location": {
49+
"description": "Werribee Zoo",
50+
"country": "Australia",
51+
"post_code": 3000
52+
},
53+
"eligibility": {
54+
"available": true,
55+
"previously_married": true
56+
},
57+
"interests": [
58+
"walks in the garden/meadow",
59+
"parkour"
60+
]
61+
}]

examples/e2e/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "e2e",
3+
"version": "1.0.0",
4+
"description": "Pact JS E2E Example",
5+
"scripts": {
6+
"test": "echo \"Error: no test specified\" && exit 1",
7+
"consumer": "node ./consumerService.js",
8+
"provider": "node ./providerService.js"
9+
},
10+
"author": "matt.fellows@onegeek.com.au",
11+
"license": "MIT",
12+
"devDependencies": {
13+
"@pact-foundation/pact-node": "^4.6.0",
14+
"@pact-foundation/pact-provider-verifier": "^0.1.1"
15+
},
16+
"dependencies": {
17+
"express": "^4.14.0",
18+
"pact": "^1.0.0-rc.5",
19+
"superagent-bluebird-promise": "^4.1.0"
20+
}
21+
}

examples/e2e/provider.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const express = require('express');
2+
const cors = require('cors');
3+
const bodyParser = require('body-parser');
4+
const Repository = require('./repository');
5+
6+
const server = express();
7+
server.use(cors());
8+
server.use(bodyParser.json());
9+
server.use(bodyParser.urlencoded({extended: true}));
10+
11+
// Load data into a repository
12+
const animalRepository = new Repository();
13+
const data = require('./data/animalData.json');
14+
data.reduce( (a, v) => {
15+
v.id = a + 1;
16+
animalRepository.insert(v);
17+
return a + 1;
18+
}, 0);
19+
20+
// Suggestions function:
21+
// Given availability and sex, find available suitors...
22+
const availableAnimals = () => {
23+
return animalRepository.fetchAll().filter(a => {
24+
return a.eligibility.available
25+
});
26+
}
27+
28+
// Get all animals
29+
server.get('/animals', (req, res) => {
30+
res.json(animalRepository.fetchAll());
31+
});
32+
33+
// Get all animals
34+
server.get('/animals/available', (req, res) => {
35+
res.json(availableAnimals());
36+
});
37+
38+
// Find an animal by ID
39+
server.get('/animals/:id', (req, res) => {
40+
const response = animalRepository.getById(req.params.id);
41+
if (response) {
42+
res.writeHead(200, {'Content-Type': 'application/json'});
43+
res.end(JSON.stringify(response));
44+
} else {
45+
res.writeHead(404);
46+
res.end();
47+
}
48+
});
49+
50+
// Register a new Animal for the service
51+
server.post('/animals', (req, res) => {
52+
const animal = req.body;
53+
54+
// Really basic validation
55+
if (!animal || !animal.first_name) {
56+
res.writeHead(400);
57+
res.end();
58+
59+
return;
60+
}
61+
62+
animal.id = animalRepository.fetchAll().length;;
63+
animalRepository.insert(animal);
64+
65+
res.writeHead(200);
66+
res.end();
67+
});
68+
69+
module.exports = server;

examples/e2e/provider.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
var stateData = "";
2+
3+
server.get('/provider-states', function (req, res) {
4+
res.json({me: ["There is a greeting"], anotherclient: ["There is a greeting"]});
5+
});
6+
7+
server.post('/provider-state', function (req, res) {
8+
stateData = "State data!";
9+
res.json({greeting: stateData});
10+
});

examples/e2e/providerService.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var server = require('./provider.js');
2+
3+
var server = server.listen(8081, function () {
4+
var host = server.address().address;
5+
var port = server.address().port;
6+
7+
console.log("Animal Profile Service listening on http://%s:%s", host, port)
8+
})

examples/e2e/repository.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Simple object repository
2+
class Repository {
3+
constructor() {
4+
this.entities = [];
5+
}
6+
7+
fetchAll() {
8+
return this.entities;
9+
}
10+
11+
getById(id) {
12+
return this.entities.find((entity) => id == entity.id);
13+
}
14+
15+
insert(entity) {
16+
this.entities.push(entity);
17+
}
18+
19+
clear() {
20+
this.entities = [];
21+
}
22+
}
23+
24+
module.exports = Repository;

0 commit comments

Comments
 (0)