From 0330729232f43ee53441c4b960462395d7b197e9 Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 16 Mar 2025 09:52:26 -0500 Subject: [PATCH 01/15] added changes to test --- app/.env => .env | 0 Dockerfile | 4 ++-- app/app.py => app.py | 51 +++++++++++++++++++++++++----------------- docker-compose.yml | 24 +++++++++++++++----- requirements.txt | Bin 636 -> 672 bytes src/models/student.py | 24 ++++++++++++++++++++ 6 files changed, 76 insertions(+), 27 deletions(-) rename app/.env => .env (100%) rename app/app.py => app.py (61%) create mode 100644 src/models/student.py diff --git a/app/.env b/.env similarity index 100% rename from app/.env rename to .env diff --git a/Dockerfile b/Dockerfile index 8b950f7..33d001a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.11 # Set the working directory -WORKDIR /app +WORKDIR / # Copy the requirements and the application code COPY requirements.txt requirements.txt @@ -15,4 +15,4 @@ COPY . . EXPOSE 8888 # Set the entrypoint to run the application -CMD ["python", "app/app.py"] \ No newline at end of file +CMD ["python", "app.py"] \ No newline at end of file diff --git a/app/app.py b/app.py similarity index 61% rename from app/app.py rename to app.py index e1e1f7d..fc2fe06 100644 --- a/app/app.py +++ b/app.py @@ -2,6 +2,7 @@ from flask_sqlalchemy import SQLAlchemy import os from dotenv import load_dotenv +from src.models.student import db, Student app = Flask(__name__) @@ -9,28 +10,15 @@ load_dotenv() DATABASE_HOST=os.getenv('DATABASE_HOST') DATABASE_PORT=os.getenv('DATABASE_PORT') -DATABASE_URL = f'{DATABASE_HOST}:{DATABASE_PORT}' -app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres@localhost:5432/postgres' -app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +DATABASE_PASS=os.getenv('DATABASE_PASS') +DATABASE_USER=os.getenv('DATABASE_USER') +DATABASE_NAME=os.getenv('DATABASE_NAME') -# Initialize the SQLAlchemy ORM -db = SQLAlchemy(app) +DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}' +app.config['SQLALCHEMY_DATABASE_URI'] = DATABASE_URL +# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -# Define the Student model -class Student(db.Model): - __tablename__ = 'students' - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) - age = db.Column(db.Integer, nullable=False) - major = db.Column(db.String(100), nullable=False) - - def to_dict(self): - return { - 'id': self.id, - 'name': self.name, - 'age': self.age, - 'major': self.major - } +db.init_app(app) # 在此綁定 app # Endpoint to get all students @app.route('/api/student', methods=['GET']) @@ -64,6 +52,28 @@ def add_student(): except Exception as e: return str(e), 500 +@app.route('/api/student/', methods=['DELETE']) +def delete_student(id): + student = Student.query.get(id) + if student: + db.session.delete(student) + db.session.commit() + return "Student deleted successfully", 200 + return "Student not found", 404 + +@app.route('/api/student/', methods=['PUT']) +def update_student(id): + student = Student.query.get(id) + if not student: + return "Student not found", 404 + + student_data = request.get_json() + student.name = student_data.get('name', student.name) + student.age = student_data.get('age', student.age) + student.major = student_data.get('major', student.major) + db.session.commit() + return "Student updated successfully", 200 + if __name__ == '__main__': with app.app_context(): db.create_all() @@ -80,3 +90,4 @@ def add_student(): db.session.commit() app.run(host='0.0.0.0', port=8888) + diff --git a/docker-compose.yml b/docker-compose.yml index 5defc31..a88d6a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,28 +6,42 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres + ports: - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data + # volumes: + # - postgres_data:/var/lib/postgresql/data restart: always healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s retries: 5 + networks: + - app-network app: build: context: . container_name: flask_app environment: - DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/mydatabase" + DATABASE_HOST: postgres + DATABASE_PORT: 5432 + DATABASE_PASS: postgres + DATABASE_USER: postgres + DATABASE_NAME: postgres ports: - "8888:8888" depends_on: postgres: condition: service_healthy restart: always + networks: + - app-network + +networks: + app-network: + driver: bridge + -volumes: - postgres_data: +# volumes: +# postgres_data: \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 233048e067fbc2ebe108e9d24f6388d897b70b4f..d00d3488d3156bca7bf0ce7309d2d5826d1a0710 100644 GIT binary patch delta 52 zcmeyvvVe8MlgS#4e49lWGZ-gxF{vpOFjO*>Fr+dR14&*6E`}l?yO5z2DpJe<0CvR; AHUIzs delta 41 tcmZ3$`iEu06X{}xN`_>He1-ysbOs{^TOc%I&||O!;feo6H-5@s1OV?%3nu^o diff --git a/src/models/student.py b/src/models/student.py new file mode 100644 index 0000000..a115d76 --- /dev/null +++ b/src/models/student.py @@ -0,0 +1,24 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +class Student(db.Model): # 改為繼承 db.Model + __tablename__ = 'students' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + age = db.Column(db.Integer, nullable=False) + major = db.Column(db.String(100), nullable=False) + + def __init__(self, name, age, major): + self.name = name + self.age = age + self.major = major + + def to_dict(self): + return { + 'id': self.id, + 'name': self.name, + 'age': self.age, + 'major': self.major + } \ No newline at end of file From 289e0424d5ec0a14d6ca0bbe0bfaeb5c27cbcc3b Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 16 Mar 2025 10:13:28 -0500 Subject: [PATCH 02/15] added unit test --- tests/unit/test_student.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/unit/test_student.py diff --git a/tests/unit/test_student.py b/tests/unit/test_student.py new file mode 100644 index 0000000..76559dd --- /dev/null +++ b/tests/unit/test_student.py @@ -0,0 +1,13 @@ +import pytest +from src.models.student import Student + + +def test_student_to_dict(): + """Test converting a Student instance to a dictionary.""" + student = Student(name="Bob", age=23, major="Computer Science") + student_dict = student.to_dict() + + assert student_dict.get("name") == "Bob" + assert student_dict.get("age") == 23 + assert student_dict.get("major") == "Computer Science" + assert "id" in student_dict # ID should be present, even if None before commit \ No newline at end of file From ccc2ae1c048faf13df98f145208a4ef0685e5afd Mon Sep 17 00:00:00 2001 From: HaKkaz Date: Sun, 16 Mar 2025 23:16:19 +0800 Subject: [PATCH 03/15] chore: remove duplicated packages from requirements.txt --- requirements.txt | Bin 672 -> 642 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index d00d3488d3156bca7bf0ce7309d2d5826d1a0710..e6055735580b0f06a2d117e246f2882921e5e6ae 100644 GIT binary patch delta 16 XcmZ3$+QhmcfoXCRlN<{#0~Z4TD1`%$ delta 15 WcmZo-UBJ2_fobwPMuo|7ObP%h`vmj= From 83d0eba89491ada5d32fd5ec0513c3964f7c5604 Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 16 Mar 2025 10:30:57 -0500 Subject: [PATCH 04/15] added pytest.ini --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..03f586d --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . \ No newline at end of file From b6ea6ef023a4fe5b9426566de4c768b64a1c02f4 Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 16 Mar 2025 10:31:59 -0500 Subject: [PATCH 05/15] Add GitHub Actions workflow for pytest --- .github/workflows/python-app.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..3798d5c --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,24 @@ +name: Python CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' # Specify the Python version you are using + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests + run: pytest tests/unit/test_student.py From 4a12bc9707331c0d74881c91394aedae655e51c6 Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 6 Apr 2025 09:36:40 -0500 Subject: [PATCH 06/15] added routes --- src/models/routes/student_routes.py | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/models/routes/student_routes.py diff --git a/src/models/routes/student_routes.py b/src/models/routes/student_routes.py new file mode 100644 index 0000000..c527303 --- /dev/null +++ b/src/models/routes/student_routes.py @@ -0,0 +1,50 @@ +from flask import Blueprint, request, jsonify +from src.models.student import db, Student + +student_bp = Blueprint('student', __name__, url_prefix='/api/student') + +@student_bp.route('', methods=['GET']) +def get_students(): + try: + students = Student.query.all() + if not students: + return "No students found", 404 + return jsonify([s.to_dict() for s in students]), 200 + except Exception as e: + return str(e), 500 + +@student_bp.route('', methods=['POST']) +def add_student(): + try: + data = request.get_json() + name, age, major = data.get('name'), data.get('age'), data.get('major') + if not all([name, age, major]): + return "Missing required fields", 400 + + student = Student(name=name, age=age, major=major) + db.session.add(student) + db.session.commit() + return "Student added successfully", 201 + except Exception as e: + return str(e), 500 + +@student_bp.route('/', methods=['DELETE']) +def delete_student(id): + student = Student.query.get(id) + if not student: + return "Student not found", 404 + db.session.delete(student) + db.session.commit() + return "Student deleted successfully", 200 + +@student_bp.route('/', methods=['PUT']) +def update_student(id): + student = Student.query.get(id) + if not student: + return "Student not found", 404 + data = request.get_json() + student.name = data.get('name', student.name) + student.age = data.get('age', student.age) + student.major = data.get('major', student.major) + db.session.commit() + return "Student updated successfully", 200 From f4fc9509cc2304110b9a03672e28dc9d001b1a39 Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 6 Apr 2025 09:52:52 -0500 Subject: [PATCH 07/15] added database and models.py --- app.py | 135 ++++++++++++++++++++++-------------------- orm/database.py | 21 +++++++ orm/models.py | 18 ++++++ src/models/student.py | 36 +++++------ 4 files changed, 126 insertions(+), 84 deletions(-) create mode 100644 orm/database.py create mode 100644 orm/models.py diff --git a/app.py b/app.py index fc2fe06..ca27649 100644 --- a/app.py +++ b/app.py @@ -1,93 +1,102 @@ from flask import Flask, jsonify, request -from flask_sqlalchemy import SQLAlchemy -import os -from dotenv import load_dotenv -from src.models.student import db, Student +from sqlalchemy.orm import scoped_session -app = Flask(__name__) - -# Database configuration from environment variable -load_dotenv() -DATABASE_HOST=os.getenv('DATABASE_HOST') -DATABASE_PORT=os.getenv('DATABASE_PORT') -DATABASE_PASS=os.getenv('DATABASE_PASS') -DATABASE_USER=os.getenv('DATABASE_USER') -DATABASE_NAME=os.getenv('DATABASE_NAME') - -DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}' -app.config['SQLALCHEMY_DATABASE_URI'] = DATABASE_URL -# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +from orm.models import Student +from orm.database import Session, init_db -db.init_app(app) # 在此綁定 app +app = Flask(__name__) -# Endpoint to get all students @app.route('/api/student', methods=['GET']) def get_student(): + session = scoped_session(Session) try: - students = Student.query.all() - if students: - student_list = [student.to_dict() for student in students] - return jsonify(student_list), 200 + student_id = request.args.get("id", type=int) + + if student_id: + student = session.get(Student, student_id) + if not student: + return jsonify({"error": f"Student with ID {student_id} was not found"}), 404 + return jsonify(student.to_dict()), 200 else: - return "No students found", 404 + students = session.query(Student).all() + if not students: + return jsonify({"error": "No students found"}), 404 + return jsonify([student.to_dict() for student in students]), 200 except Exception as e: - return str(e), 500 + session.rollback() + return jsonify({"error": str(e)}), 500 + finally: + session.remove() + # Endpoint to add a student @app.route('/api/student', methods=['POST']) def add_student(): + session = scoped_session(Session) try: student_data = request.get_json() name = student_data.get('name') age = student_data.get('age') major = student_data.get('major') + if not all([name, age, major]): - return "Missing required fields", 400 + return jsonify({"error": "Missing required fields"}), 400 new_student = Student(name=name, age=age, major=major) - db.session.add(new_student) - db.session.commit() - return "Student added successfully", 201 - + session.add(new_student) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + try: + session.commit() except Exception as e: - return str(e), 500 + session.rollback() + return jsonify({"error": str(e)}), 500 + finally: + session.remove() + + return jsonify({"message": "Student added successfully"}), 201 + @app.route('/api/student/', methods=['DELETE']) def delete_student(id): - student = Student.query.get(id) - if student: - db.session.delete(student) - db.session.commit() - return "Student deleted successfully", 200 - return "Student not found", 404 + session = scoped_session(Session) + try: + student = session.get(Student, id) + if student: + session.delete(student) + session.commit() + return jsonify({"message": "Student deleted successfully"}), 200 + else: + return jsonify({"error": "Student not found"}), 404 + except Exception as e: + session.rollback() + return jsonify({"error": str(e)}), 500 + finally: + session.remove() @app.route('/api/student/', methods=['PUT']) def update_student(id): - student = Student.query.get(id) - if not student: - return "Student not found", 404 - - student_data = request.get_json() - student.name = student_data.get('name', student.name) - student.age = student_data.get('age', student.age) - student.major = student_data.get('major', student.major) - db.session.commit() - return "Student updated successfully", 200 + session = scoped_session(Session) + try: + student = session.get(Student, id) + if not student: + return jsonify({"error": "Student not found"}), 404 + + student_data = request.get_json() + student.name = student_data.get('name', student.name) + student.age = student_data.get('age', student.age) + student.major = student_data.get('major', student.major) + + session.commit() + return jsonify({"message": "Student updated successfully"}), 200 + except Exception as e: + session.rollback() + return jsonify({"error": str(e)}), 500 + finally: + session.remove() -if __name__ == '__main__': - with app.app_context(): - db.create_all() - - # Add initial data if the table is empty - if Student.query.count() == 0: - initial_students = [ - Student(name="Alice", age=20, major="Physics"), - Student(name="Bob", age=21, major="Mathematics"), - Student(name="Charlie", age=22, major="Computer Science"), - Student(name="Diana", age=23, major="Biology"), - ] - db.session.bulk_save_objects(initial_students) - db.session.commit() - - app.run(host='0.0.0.0', port=8888) +if __name__ == '__main__': + init_db() + app.run(host='0.0.0.0', port=8787) \ No newline at end of file diff --git a/orm/database.py b/orm/database.py new file mode 100644 index 0000000..87a90c2 --- /dev/null +++ b/orm/database.py @@ -0,0 +1,21 @@ +import os + +from dotenv import load_dotenv +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from orm.models import Base + +load_dotenv() +DATABASE_HOST=os.getenv('DATABASE_HOST') +DATABASE_PORT=os.getenv('DATABASE_PORT') +DATABASE_PASS=os.getenv('DATABASE_PASS') +DATABASE_USER=os.getenv('DATABASE_USER') +DATABASE_NAME=os.getenv('DATABASE_NAME') + +DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}' +engine = create_engine(DATABASE_URL) # Connect to database +Session = sessionmaker(bind=engine) + +def init_db(): + Base.metadata.create_all(engine) \ No newline at end of file diff --git a/orm/models.py b/orm/models.py new file mode 100644 index 0000000..6ced222 --- /dev/null +++ b/orm/models.py @@ -0,0 +1,18 @@ +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy import ( + Column, + Integer, + String, +) + +class Base(DeclarativeBase): + def to_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + +class Student(Base): + __tablename__ = 'student' + + id = Column(Integer, primary_key=True, unique=True, nullable=False) + name = Column(String(100), nullable=False) + age = Column(Integer, nullable=False) + major = Column(String(100), nullable=False) \ No newline at end of file diff --git a/src/models/student.py b/src/models/student.py index a115d76..a8f4d7e 100644 --- a/src/models/student.py +++ b/src/models/student.py @@ -1,24 +1,18 @@ -from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy import ( + Column, + Integer, + String, +) -db = SQLAlchemy() - -class Student(db.Model): # 改為繼承 db.Model - __tablename__ = 'students' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) - age = db.Column(db.Integer, nullable=False) - major = db.Column(db.String(100), nullable=False) +class Base(DeclarativeBase): + def to_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} - def __init__(self, name, age, major): - self.name = name - self.age = age - self.major = major +class Student(Base): + __tablename__ = 'student' - def to_dict(self): - return { - 'id': self.id, - 'name': self.name, - 'age': self.age, - 'major': self.major - } \ No newline at end of file + id = Column(Integer, primary_key=True, unique=True, nullable=False) + name = Column(String(100), nullable=False) + age = Column(Integer, nullable=False) + major = Column(String(100), nullable=False) From 731d7b31f25d18f5170c67685bad51a1c1f353ae Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 6 Apr 2025 10:09:01 -0500 Subject: [PATCH 08/15] added orm and testing --- _script/add_init_student.py | 67 +++++++++++++++++++++++++++++++++++++ orm/__init__.py | 0 orm/database.py | 10 +++--- 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 _script/add_init_student.py create mode 100644 orm/__init__.py diff --git a/_script/add_init_student.py b/_script/add_init_student.py new file mode 100644 index 0000000..2d169ce --- /dev/null +++ b/_script/add_init_student.py @@ -0,0 +1,67 @@ +import os +from dotenv import load_dotenv +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy import ( + Column, + Integer, + String, +) + +class Base(DeclarativeBase): + def to_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} + +class Student(Base): + __tablename__ = 'student' + + id = Column(Integer, primary_key=True, unique=True, nullable=False) + name = Column(String(100), nullable=False) + age = Column(Integer, nullable=False) + major = Column(String(100), nullable=False) + +# 載入環境變數 +load_dotenv() +DATABASE_HOST=os.getenv('DATABASE_HOST', 'postgres') +DATABASE_PORT=os.getenv('DATABASE_PORT', '5432') +DATABASE_PASS=os.getenv('DATABASE_PASS', 'postgres') +DATABASE_USER=os.getenv('DATABASE_USER', 'postgres') +DATABASE_NAME=os.getenv('DATABASE_NAME', 'postgres') + +# 建立連線 URL +DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}' + +# 創建資料庫引擎 +engine = create_engine(DATABASE_URL) + +# 創建 session +Session = sessionmaker(bind=engine) +session = Session() + +# 插入四個學生資料 +def insert_students(): + try: + # 創建四個學生實例 + student1 = Student(name="Alice", age=20, major="Computer Science") + student2 = Student(name="Bob", age=22, major="Mathematics") + student3 = Student(name="Charlie", age=21, major="Physics") + student4 = Student(name="David", age=23, major="Engineering") + + # 將學生物件加入 session + session.add_all([student1, student2, student3, student4]) + + # 提交事務到資料庫 + session.commit() + + print("Students inserted successfully!") + + except Exception as e: + session.rollback() + print(f"An error occurred: {e}") + finally: + session.close() + +if __name__ == "__main__": + Base.metadata.create_all(engine) + insert_students() \ No newline at end of file diff --git a/orm/__init__.py b/orm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/orm/database.py b/orm/database.py index 87a90c2..021611e 100644 --- a/orm/database.py +++ b/orm/database.py @@ -7,11 +7,11 @@ from orm.models import Base load_dotenv() -DATABASE_HOST=os.getenv('DATABASE_HOST') -DATABASE_PORT=os.getenv('DATABASE_PORT') -DATABASE_PASS=os.getenv('DATABASE_PASS') -DATABASE_USER=os.getenv('DATABASE_USER') -DATABASE_NAME=os.getenv('DATABASE_NAME') +DATABASE_HOST=os.getenv('DATABASE_HOST', 'postgres') +DATABASE_PORT=os.getenv('DATABASE_PORT', '5432') +DATABASE_PASS=os.getenv('DATABASE_PASS', 'postgres') +DATABASE_USER=os.getenv('DATABASE_USER', 'postgres') +DATABASE_NAME=os.getenv('DATABASE_NAME', 'postgres') DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}' engine = create_engine(DATABASE_URL) # Connect to database From 7ec0704896c05698e822deed280939d704759d6e Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 6 Apr 2025 10:20:36 -0500 Subject: [PATCH 09/15] fixed e2e test --- tests/test_e2e.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_e2e.py b/tests/test_e2e.py index d229102..b16f60e 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -1,7 +1,7 @@ import requests import pytest -BASE_URL = "http://127.0.0.1:8888/api/student" +BASE_URL = "http://127.0.0.1:8787/api/student" @pytest.fixture(scope="function", autouse=True) def reset_db(): @@ -16,14 +16,14 @@ def test_get_empty_students(): """Test GET when the database is empty.""" response = requests.get(BASE_URL) assert response.status_code == 404 - assert response.text == "No students found" + assert response.json() == {'error': 'No students found'} def test_add_student(): """Test adding a student using POST.""" data = {"name": "John Doe", "age": 25, "major": "Physics"} response = requests.post(BASE_URL, json=data) assert response.status_code == 201 - assert response.text == "Student added successfully" + assert response.json() == {"message": "Student added successfully"} def test_get_students(): """Test GET after adding students.""" @@ -42,7 +42,7 @@ def test_delete_student(): delete_response = requests.delete(f"{BASE_URL}/{student_id}") assert delete_response.status_code == 200 - assert delete_response.text == "Student deleted successfully" + assert delete_response.json() == {"message": "Student deleted successfully"} # Verify deletion get_response = requests.get(BASE_URL) @@ -57,8 +57,7 @@ def test_update_student(): updated_data = {"name": "Jane Doe", "age": 30, "major": "Math"} put_response = requests.put(f"{BASE_URL}/{student_id}", json=updated_data) assert put_response.status_code == 200 - assert put_response.text == "Student updated successfully" - + assert put_response.json() == {"message": "Student updated successfully"} # Verify update get_response = requests.get(BASE_URL) student = get_response.json()[0] From ad852c64ac4033bfdd4c1dea297e3392babddced Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 6 Apr 2025 10:38:48 -0500 Subject: [PATCH 10/15] rewrote reset_db function in e2e test --- tests/test_e2e.py | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/tests/test_e2e.py b/tests/test_e2e.py index b16f60e..0095c47 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -1,16 +1,47 @@ import requests import pytest +from dotenv import load_dotenv +import os +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import DeclarativeBase +from orm.models import Student + BASE_URL = "http://127.0.0.1:8787/api/student" +load_dotenv() +DATABASE_HOST=os.getenv('DATABASE_HOST', 'postgres') +DATABASE_PORT=os.getenv('DATABASE_PORT', '5432') +DATABASE_PASS=os.getenv('DATABASE_PASS', 'postgres') +DATABASE_USER=os.getenv('DATABASE_USER', 'postgres') +DATABASE_NAME=os.getenv('DATABASE_NAME', 'postgres') + +# 建立連線 URL +DATABASE_URL = f'postgresql://{DATABASE_USER}:{DATABASE_PASS}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}' + +# 創建資料庫引擎 +engine = create_engine(DATABASE_URL) + +# 創建 session +Session = sessionmaker(bind=engine) +session = Session() + + @pytest.fixture(scope="function", autouse=True) def reset_db(): """Reset the database before each test by deleting all students.""" - response = requests.get(BASE_URL) - if response.status_code == 200: - students = response.json() - for student in students: - requests.delete(f"{BASE_URL}/{student['id']}") + try: + # 直接刪除所有學生資料 + deleted = session.query(Student).delete() + session.commit() + print(f"{deleted} student(s) deleted successfully!") + + except Exception as e: + session.rollback() + print(f"An error occurred while deleting students: {e}") + finally: + session.close() def test_get_empty_students(): """Test GET when the database is empty.""" From ce89bff0725a034aa16383c317c92dc5f75bf1e7 Mon Sep 17 00:00:00 2001 From: ILikeToCode Date: Sun, 4 May 2025 10:20:54 -0500 Subject: [PATCH 11/15] add Docker CI workflow --- .github/workflows/docker-ci.yml | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/docker-ci.yml diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml new file mode 100644 index 0000000..0785f5a --- /dev/null +++ b/.github/workflows/docker-ci.yml @@ -0,0 +1,34 @@ +name: Build and Push Docker Image + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub (only on push to main) + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build Docker image + run: | + docker build -t ${{ secrets.DOCKER_USERNAME }}/restful-api:latest . + + - name: Push Docker image (only on push to main) + if: github.event_name == 'push' + run: | + docker push ${{ secrets.DOCKER_USERNAME }}/restful-api:latest From ae4490fd6468f83fb288d1bada9712b66cc479dd Mon Sep 17 00:00:00 2001 From: MasterJYL <115123868+CodingIsCool666@users.noreply.github.com> Date: Sun, 4 May 2025 10:28:49 -0500 Subject: [PATCH 12/15] Update docker-ci.yml --- .github/workflows/docker-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 0785f5a..30abdf5 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -2,6 +2,7 @@ name: Build and Push Docker Image on: pull_request: + types: [opened, synchronize, reopened] branches: [ main ] push: branches: [ main ] @@ -26,9 +27,9 @@ jobs: - name: Build Docker image run: | - docker build -t ${{ secrets.DOCKER_USERNAME }}/restful-api:latest . + docker build -t ${{ secrets.DOCKER_USERNAME }}/my-api-server:latest . - name: Push Docker image (only on push to main) if: github.event_name == 'push' run: | - docker push ${{ secrets.DOCKER_USERNAME }}/restful-api:latest + docker push ${{ secrets.DOCKER_USERNAME }}/my-api-server:latest From c67c32cbedc5cf909d1e1a560616181a3c645f0d Mon Sep 17 00:00:00 2001 From: Anthony Lin <33683583+HaKkaz@users.noreply.github.com> Date: Sun, 4 May 2025 23:33:11 +0800 Subject: [PATCH 13/15] Update docker-ci.yml Remove trigger condition when test on pull_request. --- .github/workflows/docker-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 30abdf5..1ca721a 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -3,7 +3,6 @@ name: Build and Push Docker Image on: pull_request: types: [opened, synchronize, reopened] - branches: [ main ] push: branches: [ main ] From 58a538e42261f76e42a532b0eb9c80866c0fbd2a Mon Sep 17 00:00:00 2001 From: Anthony Lin <33683583+HaKkaz@users.noreply.github.com> Date: Sun, 4 May 2025 23:34:03 +0800 Subject: [PATCH 14/15] Update docker-ci.yml --- .github/workflows/docker-ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 1ca721a..544d098 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -1,10 +1,6 @@ name: Build and Push Docker Image -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: [ main ] +on: [push, pull_request] jobs: build: From 46d23585823f20aaa5d05aeb4277e3d9ea80cc9f Mon Sep 17 00:00:00 2001 From: Anthony Lin <33683583+HaKkaz@users.noreply.github.com> Date: Sun, 4 May 2025 23:40:25 +0800 Subject: [PATCH 15/15] Update docker-ci.yml --- .github/workflows/docker-ci.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker-ci.yml b/.github/workflows/docker-ci.yml index 544d098..58cb317 100644 --- a/.github/workflows/docker-ci.yml +++ b/.github/workflows/docker-ci.yml @@ -13,18 +13,15 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub (only on push to main) - if: github.event_name == 'push' + - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build Docker image - run: | - docker build -t ${{ secrets.DOCKER_USERNAME }}/my-api-server:latest . - - - name: Push Docker image (only on push to main) - if: github.event_name == 'push' - run: | - docker push ${{ secrets.DOCKER_USERNAME }}/my-api-server:latest + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ secrets.DOCKER_USERNAME }}/restful-api:${{ github.sha }}