From f826cd20c6c4d7b34ea6b30384398674e7d8d535 Mon Sep 17 00:00:00 2001 From: Tis5813 Date: Thu, 19 Mar 2026 22:11:04 +0100 Subject: [PATCH] Completed assignment --- package-lock.json | 3 - task-1/cocktail.js | 61 ++++- task-1/output/black_russian.md | 41 +++ task-1/output/margarita.md | 22 ++ task-1/output/martini.md | 317 ++++++++++++++++++++++++ task-1/output/white_russian.md | 21 ++ task-2/post-cli/package-lock.json | 5 +- task-2/post-cli/src/services.js | 100 +++++++- task-2/post-cli/tests/test-crud.test.js | 128 +++++----- 9 files changed, 615 insertions(+), 83 deletions(-) create mode 100644 task-1/output/black_russian.md create mode 100644 task-1/output/margarita.md create mode 100644 task-1/output/martini.md create mode 100644 task-1/output/white_russian.md diff --git a/package-lock.json b/package-lock.json index 65aa212..a26d152 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "core-assignment-week-10", "version": "1.0.0", - "hasInstallScript": true, "license": "CC BY-NC-SA 4.0", "dependencies": { "@inquirer/prompts": "^7.0.0", @@ -1585,7 +1584,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1822,7 +1820,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/task-1/cocktail.js b/task-1/cocktail.js index 6fd5a62..613ce6c 100644 --- a/task-1/cocktail.js +++ b/task-1/cocktail.js @@ -1,14 +1,51 @@ // API documentation: https://www.thecocktaildb.com/api.php -import path from 'path'; +import path from "path"; +import fs from "fs/promises"; -const BASE_URL = 'https://www.thecocktaildb.com/api/json/v1/1'; +const BASE_URL = "https://www.thecocktaildb.com/api/json/v1/1"; // Add helper functions as needed here +function generatedMarkdown(data) { + return ( + "# Cocktail Recipes\n" + + data + .map( + (drink) => ` +## ${drink.strDrink} + +![${drink.strDrink}](${drink.strDrinkThumb}/medium) + +**Category**: ${drink.strCategory} + +**Alcoholic**: ${drink.strAlcoholic === "Alcoholic" ? "Yes" : "No"} + +### Ingredients + +${Array.from({ length: 15 }, (_, i) => i + 1) + .filter((i) => drink[`strIngredient${i}`]) + .map((i) => + drink[`strMeasure${i}`] + ? `- ${drink[`strMeasure${i}`]}${drink[`strIngredient${i}`]}` + : `- ${drink[`strIngredient${i}`]}`, + ) + .join("\n")} + +### Instructions + +${drink.strInstructions} + +Serve in: ${drink.strGlass} +`, + ) + .join("\n") + ); +} + export async function main() { if (process.argv.length < 3) { - console.error('Please provide a cocktail name as a command line argument.'); + console.error("Please provide a cocktail name as a command line argument."); return; } @@ -19,11 +56,27 @@ export async function main() { const outPath = path.join(__dirname, `./output/${cocktailName}.md`); try { + const response = await fetch(url); + if (!response.ok) { + const error = new Error(data.error || response.statusText); + error.status = response.status; + throw error; + } + const data = await response.json(); + console.log(data); + if (!data?.drinks) { + throw new Error("No cocktails found with that name."); + } + + const normalisedData = generatedMarkdown(data.drinks); + + const res = await fs.writeFile(outPath, normalisedData); + return res; // 1. Fetch data from the API at the given URL // 2. Generate markdown content to match the examples // 3. Write the generated content to a markdown file as given by outPath } catch (error) { - // 4. Handle errors + console.error(error.message); } } diff --git a/task-1/output/black_russian.md b/task-1/output/black_russian.md new file mode 100644 index 0000000..89e6632 --- /dev/null +++ b/task-1/output/black_russian.md @@ -0,0 +1,41 @@ +# Cocktail Recipes + +## Black Russian + +![Black Russian](https://www.thecocktaildb.com/images/media/drink/8oxlqf1606772765.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 3/4 oz Coffee liqueur +- 1 1/2 oz Vodka + +### Instructions + +Pour the ingredients into an old fashioned glass filled with ice cubes. Stir gently. + +Serve in: Old-fashioned glass + + +## Chocolate Black Russian + +![Chocolate Black Russian](https://www.thecocktaildb.com/images/media/drink/yyvywx1472720879.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 1 oz Kahlua +- 1/2 oz Vodka +- 5 oz Chocolate ice-cream + +### Instructions + +Combine all ingredients in an electric blender and blend at a low speed for a short length of time. Pour into a chilled champagne flute and serve. + +Serve in: Champagne flute diff --git a/task-1/output/margarita.md b/task-1/output/margarita.md new file mode 100644 index 0000000..eb835ca --- /dev/null +++ b/task-1/output/margarita.md @@ -0,0 +1,22 @@ +# Cocktail Recipes + +## Margarita + +![Margarita](https://www.thecocktaildb.com/images/media/drink/5noda61589575158.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 1 1/2 oz Tequila +- 1/2 oz Triple sec +- 1 oz Lime juice +- Salt + +### Instructions + +Rub the rim of the glass. + +Serve in: Cocktail glass diff --git a/task-1/output/martini.md b/task-1/output/martini.md new file mode 100644 index 0000000..8094b71 --- /dev/null +++ b/task-1/output/martini.md @@ -0,0 +1,317 @@ +# Cocktail Recipes + +## Martini + +![Martini](https://www.thecocktaildb.com/images/media/drink/71t8581504353095.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 1 2/3 oz Gin +- 1/3 oz Dry Vermouth +- 1 Olive + +### Instructions + +Straight: Pour all ingredients into mixing glass with ice cubes. Stir well. Strain in chilled martini cocktail glass. Squeeze oil from lemon peel onto the drink, or garnish with olive. + +Serve in: Cocktail glass + + +## Dry Martini + +![Dry Martini](https://www.thecocktaildb.com/images/media/drink/6ck9yi1589574317.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 1 2/3 oz Gin +- 1/3 oz Dry Vermouth +- 1 Olive + +### Instructions + +Straight: Pour all ingredients into mixing glass with ice cubes. Stir well. Strain in chilled martini cocktail glass. Squeeze oil from lemon peel onto the drink, or garnish with olive. + +Serve in: Cocktail glass + + +## Kiwi Martini + +![Kiwi Martini](https://www.thecocktaildb.com/images/media/drink/bmxmyq1630407098.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 1/2 Kiwi +- 1 tsp Sugar Syrup +- 1 1/2 oz Vodka +- Garnish with Kiwi + +### Instructions + +The kiwi martini is a very fun vodka cocktail and it is one of the best drinks that makes use of fresh fruit. Though there are a few recipes floating around, this is one of the easiest and it is an absolutely delightful green martini to drink. + +For this recipe, you'll simply muddle slices of kiwi with simple syrup, then shake it with vodka. It's a drink that anyone can mix up in minutes and a perfect cocktail to show off your favorite vodka. + +Serve in: Cocktail glass + + +## Vodka Martini + +![Vodka Martini](https://www.thecocktaildb.com/images/media/drink/qyxrqw1439906528.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 1 1/2 oz Vodka +- 3/4 oz Dry Vermouth +- 1 Olive + +### Instructions + +Shake the vodka and vermouth together with a number of ice cubes, strain into a cocktail glass, add the olive and serve. + +Serve in: Cocktail glass + + +## Dirty Martini + +![Dirty Martini](https://www.thecocktaildb.com/images/media/drink/vcyvpq1485083300.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 70ml/2fl oz Vodka +- 1 tbsp Dry Vermouth +- 2 tbsp Olive Brine +- 1 wedge Lemon +- 1 Olive + +### Instructions + +Pour the vodka, dry vermouth and olive brine into a cocktail shaker with a handful of ice and shake well. +Rub the rim of a martini glass with the wedge of lemon. +Strain the contents of the cocktail shaker into the glass and add the olive. +A dirty Martini contains a splash of olive brine or olive juice and is typically garnished with an olive. + +Serve in: Cocktail glass + + +## Abbey Martini + +![Abbey Martini](https://www.thecocktaildb.com/images/media/drink/2mcozt1504817403.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 2 shots Gin +- 1 shot Sweet Vermouth +- 1 shot Orange Juice +- 3 dashes Angostura Bitters + +### Instructions + +Put all ingredients into a shaker and mix, then strain contents into a chilled cocktail glass. + +Serve in: Cocktail glass + + +## French Martini + +![French Martini](https://www.thecocktaildb.com/images/media/drink/clth721504373134.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 4.5 cl Vodka +- 1.5 cl Raspberry Liqueur +- 1.5 cl pineapple juice + +### Instructions + +Pour all ingredients into shaker with ice cubes. Shake well and strain into a chilled cocktail glass. Squeeze oil from lemon peel onto the drink. + +Serve in: Cocktail glass + + +## Bellini Martini + +![Bellini Martini](https://www.thecocktaildb.com/images/media/drink/3h9wv51504389379.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 8 cubes Ice +- 3 oz Vodka +- 1.5 oz Peach nectar +- 1.5 oz Peach schnapps +- 1 Lemon peel + +### Instructions + +Add ice cubes to shaker. +Add vodka. +Add peach schnapps. +Add peach nectar. +Shake. +Strain into glass. +Add lemon twist peel. + +Serve in: Cocktail glass + + +## Espresso Martini + +![Espresso Martini](https://www.thecocktaildb.com/images/media/drink/n0sx531504372951.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 5 cl Vodka +- 1 cl Kahlua +- 1 dash Sugar syrup + +### Instructions + +Pour ingredients into shaker filled with ice, shake vigorously, and strain into chilled martini glass + +Serve in: Cocktail glass + + +## Pornstar Martini + +![Pornstar Martini](https://www.thecocktaildb.com/images/media/drink/xjhjdf1630406071.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 3 cl Vodka +- 3 cl Passoa +- 1 cl Passion fruit juice +- 1 cl Lime +- 1 shot Prosecco + +### Instructions + +Straight: Pour all ingredients into mixing glass with ice cubes. Shake well. Strain in chilled martini cocktail glass. Cut passion fruit in half and use as garnish. Pour prosecco into a chilled shot glass and serve alongside the martini. + +Serve in: Cocktail glass + + +## Cosmopolitan Martini + +![Cosmopolitan Martini](https://www.thecocktaildb.com/images/media/drink/upxxpq1439907580.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 1/2 oz Cointreau +- 1 oz Vodka +- Juice of 1/2 Lime +- 1 splash Cranberry juice + +### Instructions + +Pour all ingredients in mixing glass half filled with ice, shake and strain into chilled Martini glass. + +Serve in: Cocktail Glass + + +## Salted Toffee Martini + +![Salted Toffee Martini](https://www.thecocktaildb.com/images/media/drink/3s6mlr1509551211.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 50 ml Gin +- 30 ml Chocolate liqueur +- 15 ml Amaretto +- Garnish Chocolate Sauce +- Grated Salted Chocolate + +### Instructions + +Add ice, toffee gin, chocolate liqueur and Amaretto to a cocktail shaker and shake vigorously. +Pour the chocolate syrup into a saucer and dip the top of a martini glass into the sauce. Grate some of the Willie\'s sea salt chocolate into another saucer and dip the coated glass, so the flakes stick to the sauce, creating a chocolate rim! +Pour the contents of the shaker into your chocolatey glass and sprinkle with more grated chocolate - enjoy! + +Serve in: Cocktail glass + + +## Passion Fruit Martini + +![Passion Fruit Martini](https://www.thecocktaildb.com/images/media/drink/6trfve1582473527.jpg/medium) + +**Category**: Cocktail + +**Alcoholic**: Yes + +### Ingredients + +- 1 shot Vodka +- 1/2 shot Sugar Syrup +- Full Glass Passion fruit juice + +### Instructions + +Pour all ingredients into a glass and stir. Garnish with half a passion fruit piece. + +Serve in: Cocktail glass + + +## Ziemes Martini Apfelsaft + +![Ziemes Martini Apfelsaft](https://www.thecocktaildb.com/images/media/drink/xnzr2p1485619687.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 4 cl Vermouth +- 16 cl Apple juice + +### Instructions + +Serve without ice. At least the juice shold have room temperature. + +Serve in: Collins Glass diff --git a/task-1/output/white_russian.md b/task-1/output/white_russian.md new file mode 100644 index 0000000..b86b50e --- /dev/null +++ b/task-1/output/white_russian.md @@ -0,0 +1,21 @@ +# Cocktail Recipes + +## White Russian + +![White Russian](https://www.thecocktaildb.com/images/media/drink/vsrupw1472405732.jpg/medium) + +**Category**: Ordinary Drink + +**Alcoholic**: Yes + +### Ingredients + +- 2 oz Vodka +- 1 oz Coffee liqueur +- Light cream + +### Instructions + +Pour vodka and coffee liqueur over ice cubes in an old-fashioned glass. Fill with light cream and serve. + +Serve in: Old-fashioned glass diff --git a/task-2/post-cli/package-lock.json b/task-2/post-cli/package-lock.json index 8c03ed4..8071efc 100644 --- a/task-2/post-cli/package-lock.json +++ b/task-2/post-cli/package-lock.json @@ -7,8 +7,7 @@ "": { "name": "post-central-cli", "version": "1.0.0", - "hasInstallScript": true, - "license": "ISC", + "license": "CC BY-NC-SA 4.0", "dependencies": { "@inquirer/prompts": "^7.0.0", "chalk": "^5.6.2", @@ -1585,7 +1584,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1822,7 +1820,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/task-2/post-cli/src/services.js b/task-2/post-cli/src/services.js index 2601f57..ea57474 100644 --- a/task-2/post-cli/src/services.js +++ b/task-2/post-cli/src/services.js @@ -1,5 +1,5 @@ // Change base URL for API requests to the local IP of the Post Central API server -const BASE_URL = 'http://localhost:3000'; +const BASE_URL = "http://localhost:3000"; // ============================================================================ // AUTH TOKEN - Stored after login/register, sent with every request @@ -37,7 +37,7 @@ const getHello = async () => { const response = await fetch(`${BASE_URL}/posts/hello`); if (!response.ok) { throw new Error( - `Failed to get hello: HTTP ${response.status} ${response.statusText}` + `Failed to get hello: HTTP ${response.status} ${response.statusText}`, ); } return await response.json(); @@ -53,7 +53,15 @@ const getHello = async () => { * Response: { user: string } */ const getMe = async () => { - // TODO + const response = await fetch(`${BASE_URL}/users/me`, { + headers: { Authorization: `Bearer ${getToken()}` }, + }); + if (!response.ok) { + throw new Error( + `Failed to get me: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; // ============================================================================ @@ -68,15 +76,15 @@ const getMe = async () => { */ const createUser = async (name, password) => { const response = await fetch(`${BASE_URL}/users/register`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify({ name, password }), }); if (!response.ok) { throw new Error( - `Failed to create user: HTTP ${response.status} ${response.statusText}` + `Failed to create user: HTTP ${response.status} ${response.statusText}`, ); } return await response.json(); @@ -89,7 +97,19 @@ const createUser = async (name, password) => { * Response: { user: string, token: string } */ const loginUser = async (name, password) => { - // TODO + const response = await fetch(`${BASE_URL}/users/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ name, password }), + }); + if (!response.ok) { + throw new Error( + `Failed to login: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; // ============================================================================ @@ -103,7 +123,20 @@ const loginUser = async (name, password) => { * Response: { id: number, text: string, user: string } */ const createPost = async (text) => { - // TODO + const response = await fetch(`${BASE_URL}/posts`, { + method: "POST", + headers: { + Authorization: `Bearer ${getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text }), + }); + if (!response.ok) { + throw new Error( + `Failed to create post: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; /** @@ -112,7 +145,15 @@ const createPost = async (text) => { * Response: Array of { id, text, user } */ const getPosts = async () => { - // TODO + const response = await fetch(`${BASE_URL}/posts/me`, { + headers: { Authorization: `Bearer ${getToken()}` }, + }); + if (!response.ok) { + throw new Error( + `Failed to get posts: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; /** @@ -122,7 +163,20 @@ const getPosts = async () => { * Response: { id: number, text: string } */ const updatePost = async (id, text) => { - // TODO + const response = await fetch(`${BASE_URL}/posts/${id}`, { + method: "PUT", + headers: { + Authorization: `Bearer ${getToken()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ text }), + }); + if (!response.ok) { + throw new Error( + `Failed to update post: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; /** @@ -131,7 +185,18 @@ const updatePost = async (id, text) => { * Response: { user: string, message: string } */ const deleteUser = async () => { - // TODO + const response = await fetch(`${BASE_URL}/users/me`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${getToken()}`, + }, + }); + if (!response.ok) { + throw new Error( + `Failed to delete user: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; /** @@ -140,7 +205,18 @@ const deleteUser = async () => { * Response: { id: number, text: string, message: string } */ const deletePost = async (id) => { - // TODO + const response = await fetch(`${BASE_URL}/posts/${id}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${getToken()}`, + }, + }); + if (!response.ok) { + throw new Error( + `Failed to delete post: HTTP ${response.status} ${response.statusText}`, + ); + } + return await response.json(); }; // ============================================================================ diff --git a/task-2/post-cli/tests/test-crud.test.js b/task-2/post-cli/tests/test-crud.test.js index fbf7ea4..ebb29f0 100644 --- a/task-2/post-cli/tests/test-crud.test.js +++ b/task-2/post-cli/tests/test-crud.test.js @@ -6,14 +6,14 @@ * without requiring a running API server. */ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; // Import fetcher functions let services; try { - services = await import('../src/services.solution.js'); + services = await import("../src/services.solution.js"); } catch { - services = await import('../src/services.js'); + services = await import("../src/services.js"); } const { createPost, @@ -26,12 +26,12 @@ const { updatePost, } = services; -describe('Complete CRUD Operations', () => { +describe("Complete CRUD Operations", () => { let fetchMock; beforeEach(() => { // Mock the global fetch function before each test - fetchMock = vi.spyOn(global, 'fetch'); + fetchMock = vi.spyOn(global, "fetch"); }); afterEach(() => { @@ -40,18 +40,18 @@ describe('Complete CRUD Operations', () => { setToken(null); }); - it('should complete all CRUD operations successfully', async () => { - const testUser = 'Alice'; - const testPassword = 'password123'; - const postText = 'My first test post!'; - const updatedText = 'Updated post text!'; + it("should complete all CRUD operations successfully", async () => { + const testUser = "Alice"; + const testPassword = "password123"; + const postText = "My first test post!"; + const updatedText = "Updated post text!"; const postId = 1; // Mock CREATE user response fetchMock.mockResolvedValueOnce({ ok: true, status: 201, - json: async () => ({ user: testUser, token: 'jwt-token-abc' }), + json: async () => ({ user: testUser, token: "jwt-token-abc" }), }); // CREATE: Register a user @@ -59,11 +59,11 @@ describe('Complete CRUD Operations', () => { expect(data.user).toBe(testUser); expect(data.token).toBeDefined(); expect(fetchMock).toHaveBeenLastCalledWith( - expect.stringContaining('/users/register'), + expect.stringContaining("/users/register"), expect.objectContaining({ - method: 'POST', + method: "POST", body: JSON.stringify({ name: testUser, password: testPassword }), - }) + }), ); // Set token for subsequent authenticated requests @@ -81,14 +81,14 @@ describe('Complete CRUD Operations', () => { expect(data.id).toBe(postId); expect(data.text).toBe(postText); expect(fetchMock).toHaveBeenLastCalledWith( - expect.stringContaining('/posts'), + expect.stringContaining("/posts"), expect.objectContaining({ - method: 'POST', + method: "POST", headers: expect.objectContaining({ - Authorization: 'Bearer jwt-token-abc', + Authorization: "Bearer jwt-token-abc", }), body: JSON.stringify({ text: postText }), - }) + }), ); // Mock READ posts response @@ -104,12 +104,12 @@ describe('Complete CRUD Operations', () => { expect(Array.isArray(data)).toBe(true); expect(data.length).toBeGreaterThan(0); expect(fetchMock).toHaveBeenLastCalledWith( - expect.stringContaining('/posts/me'), + expect.stringContaining("/posts/me"), expect.objectContaining({ headers: expect.objectContaining({ - Authorization: 'Bearer jwt-token-abc', + Authorization: "Bearer jwt-token-abc", }), - }) + }), ); // Mock UPDATE post response @@ -126,12 +126,12 @@ describe('Complete CRUD Operations', () => { expect(fetchMock).toHaveBeenLastCalledWith( expect.stringContaining(`/posts/${postId}`), expect.objectContaining({ - method: 'PUT', + method: "PUT", headers: expect.objectContaining({ - Authorization: 'Bearer jwt-token-abc', + Authorization: "Bearer jwt-token-abc", }), body: JSON.stringify({ text: updatedText }), - }) + }), ); // Mock READ updated posts response @@ -152,6 +152,11 @@ describe('Complete CRUD Operations', () => { fetchMock.mockResolvedValueOnce({ ok: true, status: 200, + json: async () => ({ + id: 1, + text: "Updated post text!", + message: "Post deleted", + }), }); // DELETE: Remove the post @@ -159,36 +164,37 @@ describe('Complete CRUD Operations', () => { expect(fetchMock).toHaveBeenLastCalledWith( expect.stringContaining(`/posts/${postId}`), expect.objectContaining({ - method: 'DELETE', + method: "DELETE", headers: expect.objectContaining({ - Authorization: 'Bearer jwt-token-abc', + Authorization: "Bearer jwt-token-abc", }), - }) + }), ); // Mock DELETE user response fetchMock.mockResolvedValueOnce({ ok: true, status: 200, + json: async () => ({ user: "Alice", message: "User deleted" }), }); // DELETE: Remove the user await deleteUser(); expect(fetchMock).toHaveBeenLastCalledWith( - 'http://localhost:3000/users/me', + "http://localhost:3000/users/me", expect.objectContaining({ - method: 'DELETE', + method: "DELETE", headers: expect.objectContaining({ - Authorization: 'Bearer jwt-token-abc', + Authorization: "Bearer jwt-token-abc", }), - }) + }), ); }); - it('should verify updatePost uses PUT method with Authorization', async () => { + it("should verify updatePost uses PUT method with Authorization", async () => { const postId = 42; - const newText = 'Updated content'; - setToken('mock-jwt-token'); + const newText = "Updated content"; + setToken("mock-jwt-token"); fetchMock.mockResolvedValueOnce({ ok: true, @@ -202,63 +208,65 @@ describe('Complete CRUD Operations', () => { expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining(`/posts/${postId}`), expect.objectContaining({ - method: 'PUT', + method: "PUT", headers: expect.objectContaining({ - 'Content-Type': 'application/json', - Authorization: 'Bearer mock-jwt-token', + "Content-Type": "application/json", + Authorization: "Bearer mock-jwt-token", }), body: JSON.stringify({ text: newText }), - }) + }), ); }); - it('should verify deleteUser uses DELETE method with Authorization', async () => { - setToken('mock-jwt-token'); + it("should verify deleteUser uses DELETE method with Authorization", async () => { + setToken("mock-jwt-token"); fetchMock.mockResolvedValueOnce({ ok: true, status: 200, + json: async () => ({ user: "mock-user", message: "User deleted" }), }); await deleteUser(); // Verify DELETE request to correct endpoint with auth header expect(fetchMock).toHaveBeenCalledWith( - expect.stringContaining('/users/me'), + expect.stringContaining("/users/me"), expect.objectContaining({ - method: 'DELETE', + method: "DELETE", headers: expect.objectContaining({ - Authorization: 'Bearer mock-jwt-token', + Authorization: "Bearer mock-jwt-token", }), - }) + }), ); }); - it('should verify deletePost uses DELETE method with Authorization', async () => { - setToken('mock-jwt-token'); + it("should verify deletePost uses DELETE method with Authorization", async () => { + setToken("mock-jwt-token"); fetchMock.mockResolvedValueOnce({ ok: true, status: 200, + json: async () => ({ id: 5, text: "some text", message: "Post deleted" }), }); await deletePost(5); // Verify DELETE request with correct URL and auth header expect(fetchMock).toHaveBeenCalledWith( - expect.stringContaining('/posts/5'), + expect.stringContaining("/posts/5"), expect.objectContaining({ - method: 'DELETE', + method: "DELETE", headers: expect.objectContaining({ - Authorization: 'Bearer mock-jwt-token', + Authorization: "Bearer mock-jwt-token", }), - }) + }), ); }); - it('should verify getMe fetches current user info with Authorization', async () => { - setToken('mock-jwt-token'); - const mockUser = { user: 'Alice' }; + it("should verify getMe fetches current user info with Authorization", async () => { + setToken("mock-jwt-token"); + const mockUser = { user: "Alice" }; fetchMock.mockResolvedValueOnce({ ok: true, @@ -270,26 +278,26 @@ describe('Complete CRUD Operations', () => { expect(data).toEqual(mockUser); expect(fetchMock).toHaveBeenCalledWith( - expect.stringContaining('/users/me'), + expect.stringContaining("/users/me"), expect.objectContaining({ headers: expect.objectContaining({ - Authorization: 'Bearer mock-jwt-token', + Authorization: "Bearer mock-jwt-token", }), - }) + }), ); expect(fetchMock).toHaveBeenCalledTimes(1); }); - it('should handle errors in any CRUD operation', async () => { + it("should handle errors in any CRUD operation", async () => { // Mock error response fetchMock.mockResolvedValueOnce({ ok: false, status: 500, - statusText: 'Internal Server Error', - json: async () => ({ error: 'Server error' }), + statusText: "Internal Server Error", + json: async () => ({ error: "Server error" }), }); // Verify any function throws error on API failure - await expect(createUser('Test', 'pass')).rejects.toThrow(); + await expect(createUser("Test", "pass")).rejects.toThrow(); }); });