From c881cd94c2d6b4c2d8f779e619f48cbfd15cf665 Mon Sep 17 00:00:00 2001 From: Hamed Razizadeh Date: Wed, 18 Mar 2026 19:45:51 +0100 Subject: [PATCH] Week 10 Assinment --- package-lock.json | 3 - task-1/cocktail.js | 133 +++++++++++++++++++-- task-1/output/margarita.md | 23 ++++ task-2/post-cli/package-lock.json | 5 +- task-2/post-cli/src/services.js | 152 ++++++++++++++++++++---- task-2/post-cli/src/services.starter.js | 94 ++++++++++++--- task-2/post-cli/tests/test-crud.test.js | 128 ++++++++++---------- 7 files changed, 423 insertions(+), 115 deletions(-) create mode 100644 task-1/output/margarita.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..d7c4e0b 100644 --- a/task-1/cocktail.js +++ b/task-1/cocktail.js @@ -1,29 +1,142 @@ // API documentation: https://www.thecocktaildb.com/api.php -import path from 'path'; +import path from "path"; +import { writeFile } 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 +/** + * Generate markdown content for the cocktail recipes. + * The output format must match the provided example exactly. + * + * @param {Array} drinks - Array of drink objects from the API + * @returns {string} markdown string + */ +function generateMarkdown(drinks) { + /** + * Start the markdown with the main title. + * This is required to match the expected output format. + */ + let markdown = "# Cocktail Recipes\n\n"; + // Loop through each drink in the API response + for (const drink of drinks) { + /** + * Add drink name as a level-2 heading + */ + markdown += `## ${drink.strDrink}\n\n`; + + /** + * Add drink image + * The assignment requires appending '/medium' to the image URL + */ + markdown += `![${drink.strDrink}](${drink.strDrinkThumb}/medium)\n\n`; + + /** + * Add basic metadata about the drink + */ + markdown += `**Category**: ${drink.strCategory}\n\n`; + const alcoholic = drink.strAlcoholic === "Alcoholic" ? "Yes" : "No"; + markdown += `**Alcoholic**: ${alcoholic}\n\n`; + + /** + * Ingredients section + * Ingredients and measures are stored in numbered fields: + * strIngredient1 ... strIngredient15 + * strMeasure1 ... strMeasure15 + */ + markdown += `### Ingredients\n\n`; + + for (let i = 1; i <= 15; i++) { + const ingredient = drink[`strIngredient${i}`]; + const measure = drink[`strMeasure${i}`]; + + /** + * Stop looping when no more ingredients exist + */ + if (!ingredient) break; + + /** + * Add ingredient line + * If measure exists, include it; otherwise only show ingredient + */ + if (measure) { + markdown += `- ${measure.trim()} ${ingredient}\n`; + } else { + markdown += `- ${ingredient}\n`; + } + } + + /** + * Instructions section + */ + markdown += `\n### Instructions\n\n${drink.strInstructions}\n\n`; + + /** + * Add serving glass information + * Must match exact format: "Serve in: ..." + */ + markdown += `Serve in: ${drink.strGlass}\n\n`; + } + + return markdown; +} export async function main() { + // Ensure a cocktail name is provided via command line 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; } - const cocktailName = process.argv[2]; + // Construct API endpoint for searching cocktails const url = `${BASE_URL}/search.php?s=${cocktailName}`; - + // Resolve output file path const __dirname = import.meta.dirname; const outPath = path.join(__dirname, `./output/${cocktailName}.md`); try { - // 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 + /** + * STEP 1: Fetch data from the API + */ + const response = await fetch(url); + + // Handle HTTP errors (non-2xx status codes) + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + + /** + * STEP 2: Parse JSON response + */ + const data = await response.json(); + + // Handle case where no drinks are found + if (!data.drinks) { + console.error("No cocktails found with that name."); + return; + } + + /** + * STEP 3: Transform API data into markdown format + */ + const markdown = generateMarkdown(data.drinks); + + /** + * STEP 4: Write markdown content to file + */ + await writeFile(outPath, markdown, "utf-8"); + + console.log(`File created successfully: ${outPath}`); } catch (error) { - // 4. Handle errors + /** + * STEP 5: Handle errors gracefully + * This will catch: + * - Network errors + * - HTTP errors + * - JSON parsing errors + * - File system errors + */ + console.error(error.message); } } diff --git a/task-1/output/margarita.md b/task-1/output/margarita.md new file mode 100644 index 0000000..d33d005 --- /dev/null +++ b/task-1/output/margarita.md @@ -0,0 +1,23 @@ +# 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-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..43282e4 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,21 @@ const getHello = async () => { * Response: { user: string } */ const getMe = async () => { - // TODO + try { + const response = await fetch(`${BASE_URL}/users/me`, { + method: "GET", + headers: { + Authorization: `Bearer ${getToken()}`, + }, + }); + if (!response.ok) { + throw new Error(`Failed to get user info: HTTP ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Error in getMe():", error.message); + throw error; + } }; // ============================================================================ @@ -67,19 +81,24 @@ const getMe = async () => { * Response: { user: string, token: string } */ const createUser = async (name, password) => { - const response = await fetch(`${BASE_URL}/users/register`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ name, password }), - }); - if (!response.ok) { - throw new Error( - `Failed to create user: HTTP ${response.status} ${response.statusText}` - ); + try { + const response = await fetch(`${BASE_URL}/users/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ name, password }), + }); + if (!response.ok) { + throw new Error(`Failed to create user: HTTP ${response.status}`); + } + const data = await response.json(); + setToken(data.token); // automatically save token + return data; + } catch (error) { + console.error("Error in createUser():", error.message); + throw error; } - return await response.json(); }; /** @@ -89,7 +108,24 @@ const createUser = async (name, password) => { * Response: { user: string, token: string } */ const loginUser = async (name, password) => { - // TODO + try { + 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}`); + } + const data = await response.json(); + setToken(data.token); // save token after login + return data; + } catch (error) { + console.error("Error in loginUser():", error.message); + throw error; + } }; // ============================================================================ @@ -103,7 +139,23 @@ const loginUser = async (name, password) => { * Response: { id: number, text: string, user: string } */ const createPost = async (text) => { - // TODO + try { + const response = await fetch(`${BASE_URL}/posts`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${getToken()}`, + }, + body: JSON.stringify({ text }), + }); + if (!response.ok) { + throw new Error(`Failed to create post: HTTP ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Error in createPost():", error.message); + throw error; + } }; /** @@ -112,7 +164,21 @@ const createPost = async (text) => { * Response: Array of { id, text, user } */ const getPosts = async () => { - // TODO + try { + const response = await fetch(`${BASE_URL}/posts/me`, { + method: "GET", + headers: { + Authorization: `Bearer ${getToken()}`, + }, + }); + if (!response.ok) { + throw new Error(`Failed to get posts: HTTP ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Error in getPosts():", error.message); + throw error; + } }; /** @@ -122,7 +188,23 @@ const getPosts = async () => { * Response: { id: number, text: string } */ const updatePost = async (id, text) => { - // TODO + try { + const response = await fetch(`${BASE_URL}/posts/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${getToken()}`, + }, + body: JSON.stringify({ text }), + }); + if (!response.ok) { + throw new Error(`Failed to update post: HTTP ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Error in updatePost():", error.message); + throw error; + } }; /** @@ -131,7 +213,21 @@ const updatePost = async (id, text) => { * Response: { user: string, message: string } */ const deleteUser = async () => { - // TODO + try { + 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}`); + } + return await response.json(); + } catch (error) { + console.error("Error in deleteUser():", error.message); + throw error; + } }; /** @@ -140,7 +236,21 @@ const deleteUser = async () => { * Response: { id: number, text: string, message: string } */ const deletePost = async (id) => { - // TODO + try { + 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}`); + } + return await response.json(); + } catch (error) { + console.error("Error in deletePost():", error.message); + throw error; + } }; // ============================================================================ diff --git a/task-2/post-cli/src/services.starter.js b/task-2/post-cli/src/services.starter.js index 2601f57..ff24588 100644 --- a/task-2/post-cli/src/services.starter.js +++ b/task-2/post-cli/src/services.starter.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,16 @@ const getHello = async () => { * Response: { user: string } */ const getMe = async () => { - // TODO + const response = await fetch(`${BASE_URL}/users/me`, { + method: "GET", + headers: { + Authorization: `Bearer ${getToken()}`, + }, + }); + if (!response.ok) { + throw new Error(`Failed to get user info: HTTP ${response.status}`); + } + return await response.json(); }; // ============================================================================ @@ -68,18 +77,16 @@ const getMe = async () => { */ const createUser = async (name, password) => { const response = await fetch(`${BASE_URL}/users/register`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, password }), }); if (!response.ok) { - throw new Error( - `Failed to create user: HTTP ${response.status} ${response.statusText}` - ); + throw new Error(`Failed to create user: HTTP ${response.status}`); } - return await response.json(); + const data = await response.json(); + setToken(data.token); // Save JWT token after register + return data; }; /** @@ -89,7 +96,17 @@ 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}`); + } + const data = await response.json(); + setToken(data.token); // Save JWT token after login + return data; }; // ============================================================================ @@ -103,7 +120,18 @@ 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: { + "Content-Type": "application/json", + Authorization: `Bearer ${getToken()}`, + }, + body: JSON.stringify({ text }), + }); + if (!response.ok) { + throw new Error(`Failed to create post: HTTP ${response.status}`); + } + return await response.json(); }; /** @@ -112,7 +140,14 @@ const createPost = async (text) => { * Response: Array of { id, text, user } */ const getPosts = async () => { - // TODO + const response = await fetch(`${BASE_URL}/posts/me`, { + method: "GET", + headers: { Authorization: `Bearer ${getToken()}` }, + }); + if (!response.ok) { + throw new Error(`Failed to get posts: HTTP ${response.status}`); + } + return await response.json(); }; /** @@ -122,7 +157,18 @@ 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: { + "Content-Type": "application/json", + Authorization: `Bearer ${getToken()}`, + }, + body: JSON.stringify({ text }), + }); + if (!response.ok) { + throw new Error(`Failed to update post: HTTP ${response.status}`); + } + return await response.json(); }; /** @@ -131,7 +177,14 @@ 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}`); + } + return await response.json(); }; /** @@ -140,7 +193,14 @@ 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}`); + } + 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(); }); });