diff --git a/requirements.txt b/requirements.txt index 97dc7cd..2522ad0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ fastapi uvicorn +pytest +httpx diff --git a/src/app.py b/src/app.py index 4ebb1d9..25b0cee 100644 --- a/src/app.py +++ b/src/app.py @@ -38,6 +38,48 @@ "schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM", "max_participants": 30, "participants": ["john@mergington.edu", "olivia@mergington.edu"] + }, + + # Sports-related (2 more) + "Soccer Team": { + "description": "Competitive soccer team for students of all skill levels", + "schedule": "Mondays and Thursdays, 4:00 PM - 6:00 PM", + "max_participants": 25, + "participants": ["liam@mergington.edu", "noah@mergington.edu"] + }, + "Basketball Club": { + "description": "Pickup games and skills training for basketball enthusiasts", + "schedule": "Wednesdays, 5:00 PM - 7:00 PM", + "max_participants": 18, + "participants": ["lucas@mergington.edu", "mason@mergington.edu"] + }, + + # Artistic (2 more) + "Art Club": { + "description": "Explore drawing, painting, and mixed media projects", + "schedule": "Tuesdays, 3:30 PM - 5:00 PM", + "max_participants": 20, + "participants": ["ava@mergington.edu"] + }, + "Drama Club": { + "description": "Acting, play production, and stagecraft", + "schedule": "Thursdays, 4:00 PM - 6:00 PM", + "max_participants": 25, + "participants": ["isabella@mergington.edu"] + }, + + # Intellectual (2 more) + "Math Club": { + "description": "Problem solving, competitions, and math enrichment", + "schedule": "Wednesdays, 3:30 PM - 4:30 PM", + "max_participants": 20, + "participants": ["ethan@mergington.edu"] + }, + "Science Olympiad": { + "description": "Prepare for science competitions and run experiments", + "schedule": "Fridays, 3:30 PM - 5:00 PM", + "max_participants": 22, + "participants": ["mia@mergington.edu"] } } @@ -63,5 +105,27 @@ def signup_for_activity(activity_name: str, email: str): activity = activities[activity_name] # Add student + # Check if already signed up + if email in activity["participants"]: + raise HTTPException(status_code=400, detail="Student already signed up for this activity") + activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} + + +@app.delete("/activities/{activity_name}/participants/{email}") +def unregister_from_activity(activity_name: str, email: str): + """Unregister a student from an activity""" + # Validate activity exists + if activity_name not in activities: + raise HTTPException(status_code=404, detail="Activity not found") + + # Get the specific activity + activity = activities[activity_name] + + # Check if student is signed up + if email not in activity["participants"]: + raise HTTPException(status_code=400, detail="Student not signed up for this activity") + + activity["participants"].remove(email) + return {"message": f"Unregistered {email} from {activity_name}"} diff --git a/src/static/app.js b/src/static/app.js index dcc1e38..d97c49d 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -25,6 +25,10 @@ document.addEventListener("DOMContentLoaded", () => {

${details.description}

Schedule: ${details.schedule}

Availability: ${spotsLeft} spots left

+

Participants:

+ `; activitiesList.appendChild(activityCard); @@ -41,6 +45,36 @@ document.addEventListener("DOMContentLoaded", () => { } } + // Event listener for delete buttons + activitiesList.addEventListener('click', async (event) => { + if (event.target.classList.contains('delete-btn')) { + const activity = event.target.dataset.activity; + const email = event.target.dataset.email; + + try { + const response = await fetch(`/activities/${encodeURIComponent(activity)}/participants/${encodeURIComponent(email)}`, { + method: 'DELETE' + }); + + if (response.ok) { + // Refresh the activities + fetchActivities(); + } else { + const result = await response.json(); + messageDiv.textContent = result.detail || "Failed to unregister"; + messageDiv.className = "error"; + messageDiv.classList.remove("hidden"); + setTimeout(() => messageDiv.classList.add("hidden"), 5000); + } + } catch (error) { + messageDiv.textContent = "Failed to unregister. Please try again."; + messageDiv.className = "error"; + messageDiv.classList.remove("hidden"); + console.error("Error unregistering:", error); + } + } + }); + // Handle form submission signupForm.addEventListener("submit", async (event) => { event.preventDefault(); @@ -62,6 +96,8 @@ document.addEventListener("DOMContentLoaded", () => { messageDiv.textContent = result.message; messageDiv.className = "success"; signupForm.reset(); + // Refresh the activities list + fetchActivities(); } else { messageDiv.textContent = result.detail || "An error occurred"; messageDiv.className = "error"; diff --git a/src/static/styles.css b/src/static/styles.css index a533b32..523bc97 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -74,6 +74,17 @@ section h3 { margin-bottom: 8px; } +.activity-card ul { + list-style-type: disc; + padding-left: 20px; + margin-top: 10px; + color: #555; +} + +.activity-card li { + margin-bottom: 5px; +} + .form-group { margin-bottom: 15px; } diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..2748eb3 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,73 @@ +import pytest +from fastapi.testclient import TestClient +from src.app import app + +client = TestClient(app) + +def test_get_activities(): + """アクティビティの一覧を取得できることを確認するテスト""" + response = client.get("/activities") + assert response.status_code == 200 + data = response.json() + assert isinstance(data, dict) + assert "Chess Club" in data + assert "participants" in data["Chess Club"] + +def test_signup_success(): + """新しい参加者がアクティビティに正常にサインアップできることを確認するテスト""" + # Test signing up for an activity + response = client.post("/activities/Chess%20Club/signup?email=test@example.com") + assert response.status_code == 200 + data = response.json() + assert "Signed up" in data["message"] + + # Check if added + response = client.get("/activities") + data = response.json() + assert "test@example.com" in data["Chess Club"]["participants"] + +def test_signup_already_signed_up(): + """既にサインアップ済みの参加者が再度サインアップしようとするとエラーになることを確認するテスト""" + # First signup + client.post("/activities/Chess%20Club/signup?email=duplicate@example.com") + # Second signup should fail + response = client.post("/activities/Chess%20Club/signup?email=duplicate@example.com") + assert response.status_code == 400 + data = response.json() + assert "already signed up" in data["detail"] + +def test_signup_activity_not_found(): + """存在しないアクティビティにサインアップしようとするとエラーになることを確認するテスト""" + response = client.post("/activities/Nonexistent/signup?email=test@example.com") + assert response.status_code == 404 + data = response.json() + assert "Activity not found" in data["detail"] + +def test_unregister_success(): + """サインアップ済みの参加者がアクティビティから正常にアンサインアップできることを確認するテスト""" + # First signup + client.post("/activities/Programming%20Class/signup?email=unregister@example.com") + # Then unregister + response = client.delete("/activities/Programming%20Class/participants/unregister@example.com") + assert response.status_code == 200 + data = response.json() + assert "Unregistered" in data["message"] + + # Check if removed + response = client.get("/activities") + data = response.json() + assert "unregister@example.com" not in data["Programming Class"]["participants"] + +def test_unregister_not_signed_up(): + """サインアップしていない参加者をアンサインアップしようとするとエラーになることを確認するテスト""" + response = client.delete("/activities/Chess%20Club/participants/notsigned@example.com") + assert response.status_code == 400 + data = response.json() + assert "not signed up" in data["detail"] + +def test_unregister_activity_not_found(): + """存在しないアクティビティからアンサインアップしようとするとエラーになることを確認するテスト""" + response = client.delete("/activities/Nonexistent/participants/test@example.com") + assert response.status_code == 404 + data = response.json() + assert "Activity not found" in data["detail"] \ No newline at end of file