From 6f54c4a1f6588320b4ada652fdbf098b7f655ea9 Mon Sep 17 00:00:00 2001 From: lowiee0812 Date: Tue, 3 Jun 2025 20:28:43 +0800 Subject: [PATCH] Add REST API blog example with full CRUD and unit tests --- examples/rest_api_blog/README.md | 15 ++++++++ examples/rest_api_blog/app.py | 58 ++++++++++++++++++++++++++++++ examples/rest_api_blog/test_app.py | 53 +++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 examples/rest_api_blog/README.md create mode 100644 examples/rest_api_blog/app.py create mode 100644 examples/rest_api_blog/test_app.py diff --git a/examples/rest_api_blog/README.md b/examples/rest_api_blog/README.md new file mode 100644 index 00000000..bd85af3d --- /dev/null +++ b/examples/rest_api_blog/README.md @@ -0,0 +1,15 @@ +# REST API Blog Example (Flask) + +This is a simple Flask REST API example added inside the Flask repo fork for the IT6 Final Drill. + +## Features +- Full CRUD operations for blog posts (id, title, content) +- Proper error handling and HTTP status codes +- Unit tests with 100% coverage (using unittest) + +## How to run + +1. Install dependencies (preferably in a virtual environment): + +```bash +pip install flask diff --git a/examples/rest_api_blog/app.py b/examples/rest_api_blog/app.py new file mode 100644 index 00000000..bbdde174 --- /dev/null +++ b/examples/rest_api_blog/app.py @@ -0,0 +1,58 @@ +from flask import Flask, request, jsonify, abort + +app = Flask(__name__) + +posts = [] +current_id = 1 + +def find_post(post_id): + return next((post for post in posts if post['id'] == post_id), None) + +@app.route('/api/posts', methods=['GET']) +def get_posts(): + return jsonify(posts), 200 + +@app.route('/api/posts/', methods=['GET']) +def get_post(post_id): + post = find_post(post_id) + if not post: + abort(404, description="Post not found") + return jsonify(post), 200 + +@app.route('/api/posts', methods=['POST']) +def create_post(): + global current_id + data = request.get_json() + if not data or 'title' not in data or 'content' not in data: + abort(400, description="Missing title or content") + post = { + 'id': current_id, + 'title': data['title'], + 'content': data['content'] + } + posts.append(post) + current_id += 1 + return jsonify(post), 201 + +@app.route('/api/posts/', methods=['PUT']) +def update_post(post_id): + post = find_post(post_id) + if not post: + abort(404, description="Post not found") + data = request.get_json() + if not data: + abort(400, description="Missing data") + post['title'] = data.get('title', post['title']) + post['content'] = data.get('content', post['content']) + return jsonify(post), 200 + +@app.route('/api/posts/', methods=['DELETE']) +def delete_post(post_id): + post = find_post(post_id) + if not post: + abort(404, description="Post not found") + posts.remove(post) + return '', 204 + +if __name__ == '__main__': + app.run(debug=True) diff --git a/examples/rest_api_blog/test_app.py b/examples/rest_api_blog/test_app.py new file mode 100644 index 00000000..b48b313c --- /dev/null +++ b/examples/rest_api_blog/test_app.py @@ -0,0 +1,53 @@ +import unittest +from app import app + +class BlogPostTestCase(unittest.TestCase): + def setUp(self): + self.client = app.test_client() + self.sample_post = {"title": "Test Post", "content": "This is a test post"} + + def test_create_post_success(self): + response = self.client.post('/api/posts', json=self.sample_post) + self.assertEqual(response.status_code, 201) + + def test_create_post_missing_field(self): + response = self.client.post('/api/posts', json={"title": "Only title"}) + self.assertEqual(response.status_code, 400) + + def test_get_all_posts(self): + self.client.post('/api/posts', json=self.sample_post) + response = self.client.get('/api/posts') + self.assertEqual(response.status_code, 200) + + def test_get_single_post_success(self): + post_resp = self.client.post('/api/posts', json=self.sample_post) + post_id = post_resp.get_json()['id'] + response = self.client.get(f'/api/posts/{post_id}') + self.assertEqual(response.status_code, 200) + + def test_get_single_post_not_found(self): + response = self.client.get('/api/posts/999') + self.assertEqual(response.status_code, 404) + + def test_update_post_success(self): + post_resp = self.client.post('/api/posts', json=self.sample_post) + post_id = post_resp.get_json()['id'] + response = self.client.put(f'/api/posts/{post_id}', json={"title": "Updated"}) + self.assertEqual(response.status_code, 200) + + def test_update_post_not_found(self): + response = self.client.put('/api/posts/999', json={"title": "Nothing"}) + self.assertEqual(response.status_code, 404) + + def test_delete_post_success(self): + post_resp = self.client.post('/api/posts', json=self.sample_post) + post_id = post_resp.get_json()['id'] + response = self.client.delete(f'/api/posts/{post_id}') + self.assertEqual(response.status_code, 204) + + def test_delete_post_not_found(self): + response = self.client.delete('/api/posts/999') + self.assertEqual(response.status_code, 404) + +if __name__ == '__main__': + unittest.main()