diff --git a/.gitignore b/.gitignore index b6b402ed9..02fe5a078 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store bin +sources assignments-solution node_modules npm-debug.log @@ -8,3 +9,5 @@ yarn-error.log *.bkp week3/prep-exercise/server-demo/ +assignments/hackyourtemperature/sources/keys.js + diff --git a/assignments/config-files/jest.config.js b/assignments/config-files/jest.config.js index 19ba9649e..463c210da 100644 --- a/assignments/config-files/jest.config.js +++ b/assignments/config-files/jest.config.js @@ -1,3 +1,4 @@ +console.log("Jest config loaded"); export default { // Tells jest that any file that has 2 .'s in it and ends with either js or jsx should be run through the babel-jest transformer transform: { diff --git a/assignments/hackyourtemperature/__tests__/app.test.js b/assignments/hackyourtemperature/__tests__/app.test.js new file mode 100644 index 000000000..e1c294ffe --- /dev/null +++ b/assignments/hackyourtemperature/__tests__/app.test.js @@ -0,0 +1,65 @@ +import app from "../app.js"; +import supertest from "supertest"; +import fetch from "node-fetch"; + +jest.mock("node-fetch"); +const request = supertest(app); + +describe("POST /", () => { + it("Quick test", () => { + expect(1).toBe(1); + }); +}); + +describe("Test GET /", () => { + test("Send hello to backend", async () => { + const res = await request.get("/"); + + expect(res.statusCode).toBe(200); + expect(res.text).toBe("hello from backend to frontend!"); + }); +}); + +describe("Test POST /weather", () => { + test("Return city name is required.", async () => { + const city = " "; + + const res = await request.post("/weather").send({ cityName: city }); + + expect(res.statusCode).toBe(400); + expect(res.body).toEqual({ msg: "City name is required." }); + }); +}); + +//test the happy path: when the user sends a valid city name, the API returns weather data. +describe("Test POST /weather", () => { + test("Return temperature data", async () => { + const city = "amsterdam"; + fetch.mockResolvedValue({ + ok: true, + json: async () => ({ + name: "Amsterdam", + main: { temp: 286.78 }, + }), + }); + const res = await request.post("/weather").send({ cityName: city }); + + expect(res.statusCode).toBe(200); + expect(res.body.main.temp).toBe(286.78); + }); +}); + +describe("Test POST /weather", () => { + test("Return city not found", async () => { + const city = "amsterddam"; + + fetch.mockResolvedValue({ + ok: false, + json: async () => ({}), + }); + const res = await request.post("/weather").send({ cityName: city }); + + expect(res.statusCode).toBe(400); + expect(res.body).toEqual({ weatherText: "City is not found!" }); + }); +}); diff --git a/assignments/hackyourtemperature/app.js b/assignments/hackyourtemperature/app.js new file mode 100644 index 000000000..312e70e45 --- /dev/null +++ b/assignments/hackyourtemperature/app.js @@ -0,0 +1,36 @@ +import express from "express"; +import fetch from "node-fetch"; +import { keys } from "./sources/keys.js"; + +const app = express(); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +app.get("/", (req, res) => { + res.status(200).send(`hello from backend to frontend!`); +}); + +app.post("/weather", async (req, res) => { + const { cityName } = req.body; + if (!cityName || cityName.trim() === "") { + return res.status(400).json({ msg: "City name is required." }); + } + + const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent( + cityName.trim() + )}&appid=${keys.API_KEY}`; + + try { + const response = await fetch(url); + if (!response.ok) { + return res.status(400).json({ weatherText: "City is not found!" }); + } + const data = await response.json(); + + return res.json({ main: data.main }); + } catch (error) { + res.send(error); + } +}); + +export default app; diff --git a/assignments/hackyourtemperature/babel.config.cjs b/assignments/hackyourtemperature/babel.config.cjs new file mode 100644 index 000000000..fbb629af6 --- /dev/null +++ b/assignments/hackyourtemperature/babel.config.cjs @@ -0,0 +1,13 @@ +module.exports = { + presets: [ + [ + // This is a configuration, here we are telling babel what configuration to use + "@babel/preset-env", + { + targets: { + node: "current", + }, + }, + ], + ], +}; diff --git a/assignments/hackyourtemperature/jest.config.js b/assignments/hackyourtemperature/jest.config.js new file mode 100644 index 000000000..19ba9649e --- /dev/null +++ b/assignments/hackyourtemperature/jest.config.js @@ -0,0 +1,8 @@ +export default { + // Tells jest that any file that has 2 .'s in it and ends with either js or jsx should be run through the babel-jest transformer + transform: { + "^.+\\.jsx?$": "babel-jest", + }, + // By default our `node_modules` folder is ignored by jest, this tells jest to transform those as well + transformIgnorePatterns: [], +}; diff --git a/assignments/hackyourtemperature/package.json b/assignments/hackyourtemperature/package.json new file mode 100644 index 000000000..5ad612975 --- /dev/null +++ b/assignments/hackyourtemperature/package.json @@ -0,0 +1,28 @@ +{ + "name": "hackyourtemperature", + "version": "1.0.0", + "main": "server.js", + "type": "module", + "scripts": { + "test": "jest", + "start": "node server.js", + "dev": "nodemon server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "express": "^5.1.0", + "express-handlebars": "^8.0.1", + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "babel-jest": "^30.2.0", + "jest": "^30.2.0", + "nodemon": "^3.1.10", + "supertest": "^7.1.4" + } +} diff --git a/assignments/hackyourtemperature/server.js b/assignments/hackyourtemperature/server.js new file mode 100644 index 000000000..7297c6dd8 --- /dev/null +++ b/assignments/hackyourtemperature/server.js @@ -0,0 +1,7 @@ +import app from "./app.js"; + +const port = 3000; + +app.listen(port, () => { + console.log(`server is running on port ${port}`); +});