Skip to content

Commit

Permalink
[pact] feat: Adding additional pact test (#49)
Browse files Browse the repository at this point in the history
* [pact] feat: Adding additional pact test
  • Loading branch information
helloitsdave authored May 27, 2024
1 parent 5504171 commit 1baaf36
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 65 deletions.
28 changes: 6 additions & 22 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
"prisma": "npx prisma migrate dev --name init",
"seed": "npx ts-node prisma/seed.ts",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prepare": "husky"
"lint:fix": "eslint . --fix"
},
"author": "Dave Gordon<[email protected]>",
"license": "ISC",
Expand All @@ -41,7 +40,6 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"globals": "^15.1.0",
"husky": "^9.0.11",
"nodemon": "3.0.2",
"nyc": "15.1.0",
"prettier": "^3.2.5",
Expand Down
5 changes: 5 additions & 0 deletions backend/src/routes/noteRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ router.put('/api/notes/:id', authenticateToken, async (req, res) => {
return res.status(400).send({ error: 'title and content fields required' });
}

const note = await prisma.note.findUnique({ where: { id } });
if (!note) {
return res.status(404).send({ error: 'Note not found' });
}

try {
const updatedNote = await prisma.note.update({
where: { id },
Expand Down
28 changes: 28 additions & 0 deletions backend/tests/unit/notes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ describe('Create a note', () => {

describe('Update a note', () => {
test('PUT update note - success', async ({}) => {
prisma.note.findUnique.mockResolvedValue({
title: 'Test',
content: 'Test',
id: 'a1b2c3d4-1234-5678-9abc-abcdef123457',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:33:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
});
prisma.note.update.mockResolvedValue({
title: 'Test update',
content: 'Test',
Expand Down Expand Up @@ -156,7 +164,27 @@ describe('Update a note', () => {
.send({ title: 'Test', content: 'Test', id: 1 });
expect(response.status).toBe(404);
});
test('PUT with a 404 error - failure', async ({}) => {
prisma.note.update.mockImplementation(() => {
throw new Error('Test error');
});
const response = await request(app)
.put('/api/notes/a1b2c3d4-1234-5678-9abc-abcdef123457')
.send({ title: 'Test update', content: 'Test', id: 1 });
expect(response.status).toBe(404);
expect(response.body).toStrictEqual({
error: 'Note not found',
});
});
test('PUT with a 500 error - failure', async ({}) => {
prisma.note.findUnique.mockResolvedValue({
title: 'Test',
content: 'Test',
id: 'a1b2c3d4-1234-5678-9abc-abcdef123457',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:33:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
});
prisma.note.update.mockImplementation(() => {
throw new Error('Test error');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
MatchersV3,
SpecificationVersion,
} from "@pact-foundation/pact";
import * as API from "../api/apiService";
import * as API from "../src/api/apiService";
import { AxiosResponse } from "axios";
const { eachLike, like } = MatchersV3;

const provider = new PactV3({
Expand Down Expand Up @@ -140,15 +141,53 @@ describe("Pact with NotesBEService", () => {
content: "Updated Note Content",
});

expect(response.data).toStrictEqual(
expect((response as AxiosResponse<any>).data).toStrictEqual(
{
"id": "a37f39bc-9e4f-45f2-b1d6-fe668bba2b55",
"title": "Updated Note Title",
"content": "Updated Note Content",
"createdAt": "2024-05-21T22:58:55.743Z",
"updatedAt": "2024-05-26T19:43:58.742Z",
"userID": "ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272"
},
);
});
});
it("update non-existant note", async () => {
provider.addInteraction({
states: [{ description: "note does not exist" }],
uponReceiving: "a request to update a non-existant note",
withRequest: {
method: "PUT",
path: "/api/notes/b37f39bc-9e4f-45f2-b1d6-fe668bba2b55",
body: {
title: "Updated Note Title",
content: "Updated Note Content",
},
},
willRespondWith: {
status: 404,
headers: {
"Content-Type": "application/json"
},
body: like ( { error: "Note not found" }),
},
});

await provider.executeTest(async (mockService) => {
const response = await API.patchNote({
id: "b37f39bc-9e4f-45f2-b1d6-fe668bba2b55",
title: "Updated Note Title",
content: "Updated Note Content",
});

expect(response.status).toBe(404);

expect(response).toStrictEqual(
{
error: "Note not found",
status: 404,
},
);
});
});
Expand Down
29 changes: 29 additions & 0 deletions frontend/pacts/NotesFEService-NotesBEService.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,35 @@
"name": "NotesFEService"
},
"interactions": [
{
"description": "a request to update a non-existant note",
"providerState": "note does not exist",
"request": {
"body": {
"content": "Updated Note Content",
"title": "Updated Note Title"
},
"headers": {
"Content-Type": "application/json"
},
"method": "PUT",
"path": "/api/notes/b37f39bc-9e4f-45f2-b1d6-fe668bba2b55"
},
"response": {
"body": {
"error": "Note not found"
},
"headers": {
"Content-Type": "application/json"
},
"matchingRules": {
"$.body": {
"match": "type"
}
},
"status": 404
}
},
{
"description": "a request to create a note",
"providerState": "note is added",
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/NoteApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ const NoteApp: React.FC<LogoutProps> = ({ onLogout }) => {
const updateNote = async (updatedNote: NoteType) => {
try {
const response = await patchNote(updatedNote);
await response.data;
if ('data' in response) {
await response.data;
}
fetchNotes();
} catch (error) {
console.error(error);
Expand Down
74 changes: 39 additions & 35 deletions frontend/src/api/apiService.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import axios from "axios";
import axios, { AxiosError } from "axios";

import type NoteType from "../types/note";

const URL =
process.env.REACT_APP_API_BASE_URL || "http://localhost:5000/api/";
const URL = process.env.REACT_APP_API_BASE_URL || "http://localhost:5000/api/";

const api = axios.create({
baseURL: URL,
Expand All @@ -15,30 +14,39 @@ const api = axios.create({

api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
config.headers.Authorization = token ? `Bearer ${token}` : '';
config.headers.Authorization = token ? `Bearer ${token}` : "";
return config;
});

export const postNote = async (newNote: NoteType) => {
const response = await api.post(
'notes',
{
title: newNote.title,
content: newNote.content,
}
);
const response = await api.post("notes", {
title: newNote.title,
content: newNote.content,
});
return response;
};

export const patchNote = async (updatedNote: NoteType) => {
const response = await api.put(
`notes/${updatedNote.id}`,
{
try {
const response = await api.put(`notes/${updatedNote.id}`, {
title: updatedNote.title,
content: updatedNote.content,
});
return response;
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response && axiosError.response.status === 404) {
// Return custom response for 404 error
return {
status: 404,
error: 'Note not found',
};
} else {
// Log and rethrow the error for non-404 errors
console.log(axiosError);
throw axiosError;
}
);
return response;
}
};

export const removeNote = async (id: string) => {
Expand All @@ -47,13 +55,13 @@ export const removeNote = async (id: string) => {
};

export const getNotes = async () => {
const response = await api.get('notes');
const response = await api.get("notes");
return response;
};

export const login = async (username: string, password: string) => {
const response = await api.post(
'login',
"login",
{
username,
password,
Expand All @@ -67,28 +75,24 @@ export const login = async (username: string, password: string) => {
return response;
};

export const createUser = async ( user:
{ username: string,
email: string,
password: string }
) => {
const response = await api.post(
'users',
user,
{
headers: {
"Content-Type": "application/json",
},
}
);
export const createUser = async (user: {
username: string;
email: string;
password: string;
}) => {
const response = await api.post("users", user, {
headers: {
"Content-Type": "application/json",
},
});
return response;
}
};

export const deleteUser = async () => {
const response = await api.delete('users', {
const response = await api.delete("users", {
headers: {
"Content-Type": "application/json",
},
});
return response;
}
};
2 changes: 1 addition & 1 deletion frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
},
"include": [
"src"
]
, "contract-tests" ]
}
2 changes: 1 addition & 1 deletion frontend/vite.config.pact.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
include: ["**/src/contract-tests/**",],
include: ["**/contract-tests/**",],
globals: true,
environment: "jsdom",
},
Expand Down

2 comments on commit 1baaf36

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

98.10%

Coverage Report
FileBranchesFuncsLinesUncovered Lines
src
   authenticateToken.ts100%100%100%
   hash.ts100%100%100%
   index.ts50%50%83.33%31–33
   prisma.ts100%100%100%
src/__mocks__
   prisma.ts100%100%100%
src/routes
   loginRoutes.ts100%100%100%
   noteRoutes.ts100%100%100%
   userRoutes.ts100%100%100%

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage for this commit

96.78%

Coverage Report
FileBranchesFuncsLinesUncovered Lines
src
   App.tsx87.50%75%96%30–31, 40–41
   NoteApp.tsx94.44%100%97.04%72–76
src/api
   apiService.ts87.50%85.71%87.76%38, 38–43, 92–98
src/components
   Header.tsx100%100%100%
   Login.tsx81.82%100%98.59%32, 32–33
   Note.tsx100%100%100%
   NoteForm.tsx100%100%100%
   NoteFormModal.tsx100%100%100%
   NoteGrid.tsx100%100%100%
   RegistrationForm.tsx86.67%100%97.04%57, 57–61
   RegistrationLink.tsx100%100%100%
   Spinner.tsx100%100%100%

Please sign in to comment.