Skip to content

Commit

Permalink
[pact] feat: Adding github action for contract test (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
helloitsdave authored May 27, 2024
1 parent 3455ae4 commit b377ef0
Show file tree
Hide file tree
Showing 20 changed files with 1,717 additions and 1,552 deletions.
3,050 changes: 1,568 additions & 1,482 deletions backend/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"test:service": "npx vitest **/service/*.spec.ts",
"test:unit": "npx vitest -c ./vitest.config.unit.mts",
"test:coverage": "npx vitest **/unit/*.spec.ts --coverage",
"test:contract": "npx vitest **/contract/*.spec.ts",
"test:contract": "npx vitest --testTimeout 30000 **/contract/*.spec.ts",
"start:local": "npx nodemon",
"start": "node dist/index.js",
"build": "tsc",
Expand Down
12 changes: 12 additions & 0 deletions backend/prisma/migrations/20240526112710_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Warnings:
- The primary key for the `Note` table will be changed. If it partially fails, the table could be left without primary key constraint.
*/
-- AlterTable
ALTER TABLE "Note" DROP CONSTRAINT "Note_pkey",
ALTER COLUMN "id" DROP DEFAULT,
ALTER COLUMN "id" SET DATA TYPE TEXT,
ADD CONSTRAINT "Note_pkey" PRIMARY KEY ("id");
DROP SEQUENCE "Note_id_seq";
2 changes: 1 addition & 1 deletion backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ datasource db {
}

model Note {
id Int @id @default(autoincrement())
id String @id @default(uuid())
title String
content String
createdAt DateTime @default(now()) @map("created_at")
Expand Down
9 changes: 9 additions & 0 deletions backend/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,71 @@ export const seed = [
createdAt: '2024-02-05T23:33:42.252Z',
updatedAt: '2024-02-05T23:33:42.252Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: '328baf25-3cb5-46a8-a66e-d86ac9bd46d5',
},
{
title: 'Different User - scoping check',
content: 'Should not see this note with Test User',
createdAt: '2024-02-05T23:33:42.253Z',
updatedAt: '2024-02-05T23:33:42.253Z',
userID: 'dcf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: '7b981062-92dc-40a5-b7e2-bda7fc9d5cd2',
},
{
title: 'Recipe',
content: 'Ingredients: Chicken, tomatoes, onions, garlic.',
createdAt: '2024-02-05T23:33:42.254Z',
updatedAt: '2024-02-05T23:33:42.254Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: 'a37f39bc-9e4f-45f2-b1d6-fe668bba2b55',
},
{
title: 'Ideas',
content: 'Brainstorming ideas for the next feature release. 🚀',
createdAt: '2024-02-05T23:33:42.255Z',
updatedAt: '2024-02-05T23:33:42.255Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: '1e69b2a9-15dd-4d59-872a-b6830d0a2f92',
},
{
title: 'Personal Goals',
content: 'Exercise for 30 minutes daily. Read a book every week.',
createdAt: '2024-02-05T23:33:42.256Z',
updatedAt: '2024-02-05T23:33:42.256Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: 'dd05d471-3f71-4616-8372-a399f496cb39',
},
{
title: "Fête d'anniversaire",
content: "Préparer une surprise pour la fête d'anniversaire.",
createdAt: '2024-02-05T23:33:42.257Z',
updatedAt: '2024-02-05T23:33:42.257Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: '13ee0bfc-5510-43d7-b58b-d714a1f26aea',
},
{
title: '日本旅行',
content: '計画: 東京、京都、大阪を訪れる。',
createdAt: '2024-02-05T23:33:42.258Z',
updatedAt: '2024-02-05T23:33:42.258Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: '75e5e48d-4ea8-4609-8b57-732eea9b628e',
},
{
title: 'Семейный ужин',
content: 'Приготовить вкусный ужин для всей семьи.',
createdAt: '2024-02-05T23:33:42.259Z',
updatedAt: '2024-02-05T23:33:42.259Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: 'a9392144-54c0-44a4-8f51-1dd06ebfe5d5',
},
{
title: 'Coding Project',
content: 'Implement new features using React and Express.',
createdAt: '2024-02-05T23:33:42.260Z',
updatedAt: '2024-02-05T23:33:42.260Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
id: 'dcbdc92f-f04c-4a84-8631-57e1943acfc7',
},
];

Expand Down
2 changes: 1 addition & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});

/* istanbul ignore next */
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'test') {
app.listen(PORT, () => {
console.log('server running on localhost', PORT);
Expand Down
12 changes: 2 additions & 10 deletions backend/src/routes/noteRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,12 @@ router.post('/api/notes', authenticateToken, async (req, res) => {

router.put('/api/notes/:id', authenticateToken, async (req, res) => {
const { title, content } = req.body;
const id = parseInt(req.params.id);
const id = req.params.id;

if (!title || !content) {
return res.status(400).send({ error: 'title and content fields required' });
}

if (!id || isNaN(id)) {
return res.status(400).send({ error: 'ID must be a valid number' });
}

try {
const updatedNote = await prisma.note.update({
where: { id },
Expand All @@ -59,11 +55,7 @@ router.put('/api/notes/:id', authenticateToken, async (req, res) => {
});

router.delete('/api/notes/:id', authenticateToken, async (req, res) => {
const id = parseInt(req.params.id);

if (!id || isNaN(id)) {
return res.status(400).send({ error: 'ID field required' });
}
const id = req.params.id;

try {
await prisma.note.delete({
Expand Down
1 change: 0 additions & 1 deletion backend/tests/contract/notes.pact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe('Pact Verification', () => {
],
log: 'INFO', // Set to "DEBUG" to see output
};

return new Verifier(opts).verifyProvider().then((output) => {
console.log(output);
});
Expand Down
6 changes: 3 additions & 3 deletions backend/tests/service/notes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Authenticated Flows', () => {
{
content: 'Discussed project timelines and goals.',
createdAt: '2024-02-05T23:33:42.252Z',
id: 1,
id: '328baf25-3cb5-46a8-a66e-d86ac9bd46d5',
title: 'Meeting Notes',
updatedAt: '2024-02-05T23:33:42.252Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('Authenticated Flows', () => {
const deleteRes = await request(NOTES_URL)
.delete(`/invalid-id`)
.set('Authorization', `Bearer ${token}`);
expect(deleteRes.status).toBe(400);
expect(deleteRes.body.error).toBe('ID field required');
expect(deleteRes.status).toBe(500);
expect(deleteRes.body.error).toBe('Oops, something went wrong');
});
});
18 changes: 9 additions & 9 deletions backend/tests/unit/mocks/notes.mock.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,70 @@
export const noteSeed = [
{
id: 1,
id: 'a1b2c3d4-1234-5678-9abc-abcdef123456',
title: 'Meeting Notes',
content: 'Discussed project timelines and goals.',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 2,
id: 'e5f6g7h8-5678-1234-abcd-efghijklmnop',
title: 'Shopping List',
content: 'Milk, eggs, bread, and fruits.',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 3,
id: 'i9j0k1l2-9012-3456-cdef-ghijklmnopqr',
title: 'Recipe',
content: 'Ingredients: Chicken, tomatoes, onions, garlic.',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 4,
id: 's3t4u5v6-3456-7890-ijkl-mnopqrstuvwx',
title: 'Ideas',
content: 'Brainstorming ideas for the next feature release. 🚀',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 5,
id: 'y7z8a9b0-7890-1234-qrst-uvwxyz123456',
title: 'Personal Goals',
content: 'Exercise for 30 minutes daily. Read a book every week.',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 6,
id: 'c1d2e3f4-2345-6789-uvwx-yz0123456789',
title: "Fête d'anniversaire",
content: "Préparer une surprise pour la fête d'anniversaire.",
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 7,
id: 'f5g6h7i8-4567-8901-2345-6789abcdefghij',
title: '日本旅行',
content: '計画: 東京、京都、大阪を訪れる。',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 8,
id: 'j9k0l1m2-5678-9012-3456-7890abcdefghij',
title: 'Семейный ужин',
content: 'Приготовить вкусный ужин для всей семьи.',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:43:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
},
{
id: 9,
id: 'n3o4p5q6-7890-1234-5678-90abcdefghij',
title: 'Coding Project',
content: 'Implement new features using React and Express.',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
Expand Down
34 changes: 9 additions & 25 deletions backend/tests/unit/notes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('View notes', () => {
expect(response.status).toBe(200);
expect(response.body).toEqual([
{
id: 1,
id: 'a1b2c3d4-1234-5678-9abc-abcdef123456',
title: 'Meeting Notes',
content: 'Discussed project timelines and goals.',
createdAt: '2024-02-05T23:43:42.252Z',
Expand Down Expand Up @@ -70,7 +70,7 @@ describe('Create a note', () => {
prisma.note.create.mockResolvedValue({
content: 'Test',
title: 'Test',
id: 1,
id: 'a1b2c3d4-1234-5678-9abc-abcdef123456',
updatedAt: new Date('2024-02-05T23:33:42.252Z'),
createdAt: new Date('2024-02-05T23:33:42.252Z'),
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
Expand Down Expand Up @@ -120,55 +120,48 @@ describe('Update a note', () => {
prisma.note.update.mockResolvedValue({
title: 'Test update',
content: 'Test',
id: 1,
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',
});
const response = await request(app)
.put('/api/notes/1')
.put('/api/notes/a1b2c3d4-1234-5678-9abc-abcdef123457')
.send({ title: 'Test update', content: 'Test', id: 1 });
expect(response.status).toBe(200);
expect(response.body).toStrictEqual({
title: 'Test update',
content: 'Test',
createdAt: '2024-02-05T23:33:42.252Z',
id: 1,
id: 'a1b2c3d4-1234-5678-9abc-abcdef123457',
updatedAt: '2024-02-05T23:33:42.252Z',
userID: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
});
});
test('PUT without title - failure', async ({}) => {
const response = await request(app)
.put('/api/notes/1')
.put('/api/notes/a1b2c3d4-1234-5678-9abc-abcdef123457')
.send({ content: 'Test', id: 1 });
expect(response.status).toBe(400);
});
test('PUT without content - failure', async ({}) => {
const response = await request(app)
.put('/api/notes/1')
.put('/api/notes/a1b2c3d4-1234-5678-9abc-abcdef123457')
.send({ title: 'Test' });
expect(response.status).toBe(400);
});
test('PUT without id in url - failure', async ({}) => {
const response = await request(app)
.put('/api/notes/')
.send({ title: 'Test' });
expect(response.status).toBe(404);
});
test('PUT with not string in url - failure', async ({}) => {
const response = await request(app)
.put('/api/notes/blah')
.send({ title: 'Test', content: 'Test', id: 1 });
expect(response.status).toBe(400);
expect(response.body).toStrictEqual({ error: 'ID must be a valid number' });
expect(response.status).toBe(404);
});
test('PUT with a 500 error - failure', async ({}) => {
prisma.note.update.mockImplementation(() => {
throw new Error('Test error');
});
const response = await request(app)
.put('/api/notes/1')
.put('/api/notes/a1b2c3d4-1234-5678-9abc-abcdef123457')
.send({ title: 'Test update', content: 'Test', id: 1 });
expect(response.status).toBe(500);
expect(response.body).toStrictEqual({
Expand Down Expand Up @@ -199,15 +192,6 @@ describe('Delete a note', () => {
error: 'Oops, something went wrong',
});
});
test('DELETE with id error', async ({}) => {
prisma.note.delete.mockImplementation(() => {
throw new Error('Test error');
});
const response = await request(app).delete('/api/notes/string');

expect(response.status).toBe(400);
expect(response.body).toStrictEqual({ error: 'ID field required' });
});
});

describe('Health check', () => {
Expand Down
Loading

2 comments on commit b377ef0

@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.04%

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

97.43%

Coverage Report
FileBranchesFuncsLinesUncovered Lines
src
   App.tsx87.50%75%96%30–31, 40–41
   NoteApp.tsx94.44%100%96.99%70–74
src/api
   apiService.ts100%85.71%92.55%88–94
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.