Skip to content

Commit 57d4d05

Browse files
committed
feat: demo using quilt
1 parent 35b133c commit 57d4d05

File tree

5 files changed

+316
-6
lines changed

5 files changed

+316
-6
lines changed

package-lock.json

Lines changed: 91 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
"version": "0.2.0",
44
"private": true,
55
"dependencies": {
6-
"axios": "^0.27.2"
6+
"axios": "^0.27.2",
7+
"wait-port": "^1.1.0"
78
},
89
"scripts": {
910
"test": "cross-env CI=true npx jest --colors --testTimeout 30000 --testMatch \"**/*.pact.spec.ts\"",
10-
"test:pact": "cross-env CI=true npx jest --colors --testTimeout 30000 --testMatch \"**/*.pact.spec.ts\""
11+
"test:pact": "cross-env CI=true npx jest --colors --testTimeout 30000 --testMatch \"**/*.pact.spec.ts\"",
12+
"test:quilt": "cross-env CI=true npx jest --colors --testTimeout 30000 --testMatch \"**/*.quilt.spec.ts\"",
13+
"test:quilt:start-mock-server": "bin/quilt mock --test-file /Users/matthew.fellows/development/public/api-testing-tool/quilt-cli/example/product.testcases.yaml --port 8002 --log-level error"
1114
},
1215
"devDependencies": {
1316
"@babel/core": "^7.25.2",

src/api.pact.spec.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { SpecificationVersion, PactV4, MatchersV3 } from "@pact-foundation/pact";
2+
import { API } from './api';
3+
4+
const { like } = MatchersV3;
5+
6+
describe("Product API", () => {
7+
const pact = new PactV4({
8+
consumer: "ProductConsumer",
9+
provider: "ProductProvider",
10+
spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
11+
logLevel: "error",
12+
});
13+
14+
describe("GET /product/:id", () => {
15+
test("given a valid product, returns 200", async () => {
16+
await pact
17+
.addInteraction()
18+
.given("a product with id 1234 exists")
19+
.uponReceiving("a request for a valid product")
20+
.withRequest("GET", "/product/1234", (builder) => {
21+
builder.headers({ Accept: "application/json; charset=utf-8" });
22+
})
23+
.willRespondWith(200, (builder) => {
24+
builder.jsonBody(
25+
like({
26+
id: "1234",
27+
name: "Product Name",
28+
type: "food",
29+
})
30+
);
31+
})
32+
.executeTest(async (mockserver) => {
33+
const api = new API(mockserver.url);
34+
const product = await api.getProduct("1234");
35+
expect(product).toEqual({
36+
id: "1234",
37+
name: "Product Name",
38+
type: "food",
39+
});
40+
});
41+
});
42+
43+
test("given a non-existent product, returns 404", async () => {
44+
await pact
45+
.addInteraction()
46+
.given("no product with id 9999 exists")
47+
.uponReceiving("a request for a non-existent product")
48+
.withRequest("GET", "/product/9999", (builder) => {
49+
builder.headers({ Accept: "application/json; charset=utf-8" });
50+
})
51+
.willRespondWith(404)
52+
.executeTest(async (mockserver) => {
53+
const api = new API(mockserver.url);
54+
await expect(api.getProduct("9999")).rejects.toThrow();
55+
});
56+
});
57+
58+
test("given an invalid product id, returns 400", async () => {
59+
await pact
60+
.addInteraction()
61+
.uponReceiving("a request with an invalid product id")
62+
.withRequest("GET", "/product/invalid-id", (builder) => {
63+
builder.headers({ Accept: "application/json; charset=utf-8" });
64+
})
65+
.willRespondWith(400)
66+
.executeTest(async (mockserver) => {
67+
const api = new API(mockserver.url);
68+
await expect(api.getProduct("invalid-id")).rejects.toThrow();
69+
});
70+
});
71+
72+
test("given no authorization, returns 401", async () => {
73+
await pact
74+
.addInteraction()
75+
.uponReceiving("a request without authorization")
76+
.withRequest("GET", "/product/1234", (builder) => {
77+
builder.headers({ Accept: "application/json; charset=utf-8" });
78+
})
79+
.willRespondWith(401)
80+
.executeTest(async (mockserver) => {
81+
const api = new API(mockserver.url);
82+
await expect(api.getProduct("1234")).rejects.toThrow();
83+
});
84+
});
85+
});
86+
87+
describe("GET /products", () => {
88+
test("returns all products with 200", async () => {
89+
await pact
90+
.addInteraction()
91+
.given("products exist")
92+
.uponReceiving("a request for all products")
93+
.withRequest("GET", "/products", (builder) => {
94+
builder.headers({ Accept: "application/json; charset=utf-8" });
95+
})
96+
.willRespondWith(200, (builder) => {
97+
builder.jsonBody(
98+
like([
99+
{
100+
id: "1234",
101+
name: "Product Name",
102+
type: "food",
103+
},
104+
])
105+
);
106+
})
107+
.executeTest(async (mockserver) => {
108+
const api = new API(mockserver.url);
109+
const products = await api.getAllProducts();
110+
expect(products).toEqual([
111+
{
112+
id: "1234",
113+
name: "Product Name",
114+
type: "food",
115+
},
116+
]);
117+
});
118+
});
119+
});
120+
});

src/api.quilt.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { API } from "./api";
2+
import { startMockServer, stopMockServer } from "./mockserver";
3+
4+
const mockserver = "http://localhost:8002";
5+
6+
describe("Product API", () => {
7+
beforeAll(startMockServer);
8+
afterAll(() => stopMockServer())
9+
10+
describe("GET /product/:id", () => {
11+
test("given a valid product, returns 200", async () => {
12+
const api = new API(mockserver);
13+
const product = await api.getProduct("10");
14+
expect(product).toEqual({
15+
id: 10,
16+
name: "cola",
17+
type: "beverage",
18+
});
19+
});
20+
21+
test("given a non-existent product, returns 404", async () => {
22+
const api = new API(mockserver);
23+
await expect(api.getProduct("9999")).rejects.toThrow();
24+
});
25+
26+
test("given an invalid product id, returns 400", async () => {
27+
const api = new API(mockserver);
28+
await expect(api.getProduct("invalid-id")).rejects.toThrow();
29+
});
30+
31+
test("given no authorization, returns 401", async () => {
32+
const api = new API(mockserver);
33+
api.generateAuthToken = jest.fn(() => undefined as any as string);
34+
await expect(api.getProduct("10")).rejects.toThrow();
35+
});
36+
});
37+
38+
describe.skip("GET /products", () => {
39+
test("returns all products with 200", async () => {
40+
const api = new API(mockserver);
41+
try {
42+
const products = await api.getAllProducts();
43+
console.log(products);
44+
expect(products).toContain([
45+
{
46+
id: 10,
47+
name: "cola",
48+
type: "beverage",
49+
},
50+
]);
51+
} catch (error) {
52+
console.error("Error fetching products:", error);
53+
throw error;
54+
}
55+
});
56+
});
57+
});

src/mockserver.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { spawn, ChildProcess } from "child_process";
2+
const waitPort = require('wait-port');
3+
4+
let mockserverProcess: ChildProcess;
5+
export const startMockServer = (done: () => void): void => {
6+
console.log("starting mock server...");
7+
8+
mockserverProcess = spawn(
9+
"npm",
10+
[
11+
"run",
12+
"test:quilt:start-mock-server",
13+
],
14+
{
15+
stdio: "inherit",
16+
shell: true,
17+
detached: true,
18+
}
19+
);
20+
mockserverProcess.on("error", (error: any) => {
21+
console.error(`error starting mock server: ${error.message}`);
22+
throw error;
23+
});
24+
25+
waitPort({port: 8002, host: "localhost", output: 'silent'})
26+
.then(() => {
27+
console.log("mock server is ready on port 8002");
28+
done();
29+
})
30+
.catch((error) => {
31+
console.error(`Error waiting for port 8002: ${error.message}`);
32+
throw error;
33+
});
34+
};
35+
36+
export const stopMockServer = (): void => {
37+
if (mockserverProcess) {
38+
console.log("stopping mock server...");
39+
mockserverProcess.kill();
40+
} else {
41+
console.warn("No mock server process to stop.");
42+
}
43+
}

0 commit comments

Comments
 (0)