From 4915c1fe58a313402786f5b735bafb199a84a55d Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 8 Aug 2025 14:07:16 -0400 Subject: [PATCH 1/2] feat: implement booking creation and availability endpoints with tests --- backend/routes/api.py | 58 +++++++++++++++++++++++++++++++++++ backend/tests/test_booking.py | 53 ++++++++++++++++++++++++++++++++ frontend/package.json | 12 ++++++++ start_all.sh | 20 ++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 backend/tests/test_booking.py create mode 100755 start_all.sh diff --git a/backend/routes/api.py b/backend/routes/api.py index 587db65..fef2c82 100644 --- a/backend/routes/api.py +++ b/backend/routes/api.py @@ -1,8 +1,66 @@ + from flask import Blueprint, jsonify, request from flask_login import login_required, current_user api_bp = Blueprint('api', __name__) +from datetime import datetime, timezone +bookings_db = [] # In-memory mock for bookings + +# --- Booking Endpoints --- +@api_bp.route('/bookings/', methods=['POST']) +def create_booking(): + data = request.get_json() or {} + required = ["station_id", "user_id", "start_time", "end_time"] + if not all(k in data for k in required): + return jsonify({"error": "Missing booking data"}), 400 + try: + start = datetime.fromisoformat(data["start_time"].replace("Z", "+00:00")) + end = datetime.fromisoformat(data["end_time"].replace("Z", "+00:00")) + except Exception: + return jsonify({"error": "Invalid date format"}), 400 + # Check for overlap + for b in bookings_db: + if b["station_id"] == data["station_id"] and not (end <= b["start_time"] or start >= b["end_time"]): + return jsonify({"error": "Booking time overlaps with existing booking"}), 409 + booking_id = len(bookings_db) + 1 + booking = { + "booking_id": booking_id, + "station_id": data["station_id"], + "user_id": data["user_id"], + "start_time": start, + "end_time": end, + "status": "confirmed" + } + bookings_db.append(booking) + return jsonify({"booking_id": booking_id, "status": "confirmed"}), 201 + +@api_bp.route('/stations//availability') +def station_availability(station_id): + date_str = request.args.get("date") + if not date_str: + return jsonify({"error": "Missing date parameter"}), 400 + try: + date = datetime.fromisoformat(date_str) + except Exception: + return jsonify({"error": "Invalid date format"}), 400 + # Mock: 8am-8pm, 1hr slots, remove slots with bookings + slots = [ + (date.replace(hour=h, minute=0, second=0, microsecond=0, tzinfo=timezone.utc), + date.replace(hour=h+1, minute=0, second=0, microsecond=0, tzinfo=timezone.utc)) + for h in range(8, 20) + ] + available = [] + for start, end in slots: + overlap = False + for b in bookings_db: + if b["station_id"] == station_id and not (end <= b["start_time"] or start >= b["end_time"]): + overlap = True + break + if not overlap: + available.append({"start": start.isoformat(), "end": end.isoformat()}) + return jsonify({"available_slots": available}) + @api_bp.route('/health') def health_check(): """Health check endpoint""" diff --git a/backend/tests/test_booking.py b/backend/tests/test_booking.py new file mode 100644 index 0000000..b7baa2c --- /dev/null +++ b/backend/tests/test_booking.py @@ -0,0 +1,53 @@ +import pytest + +def test_create_booking(client): + """Should create a booking for a station with valid data""" + booking_data = { + "station_id": 1, + "user_id": 1, + "start_time": "2025-08-10T10:00:00Z", + "end_time": "2025-08-10T12:00:00Z" + } + response = client.post('/api/bookings/', json=booking_data) + assert response.status_code == 201 + data = response.get_json() + assert "booking_id" in data + assert data["status"] == "confirmed" + +def test_booking_conflict(client): + """Should return 409 if booking time overlaps with existing booking""" + # First booking + booking_data = { + "station_id": 1, + "user_id": 1, + "start_time": "2025-08-10T10:00:00Z", + "end_time": "2025-08-10T12:00:00Z" + } + client.post('/api/bookings/', json=booking_data) + # Overlapping booking + conflict_data = { + "station_id": 1, + "user_id": 2, + "start_time": "2025-08-10T11:00:00Z", + "end_time": "2025-08-10T13:00:00Z" + } + response = client.post('/api/bookings/', json=conflict_data) + assert response.status_code == 409 + data = response.get_json() + assert "error" in data + assert "overlap" in data["error"].lower() + +def test_booking_availability(client): + """Should return available time slots for a station""" + response = client.get('/api/stations/1/availability?date=2025-08-10') + assert response.status_code == 200 + data = response.get_json() + assert "available_slots" in data + assert isinstance(data["available_slots"], list) + +def test_invalid_booking_data(client): + """Should return 400 for invalid booking data""" + response = client.post('/api/bookings/', json={}) + assert response.status_code == 400 + data = response.get_json() + assert "error" in data diff --git a/frontend/package.json b/frontend/package.json index e03e7b3..5cff759 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,5 +17,17 @@ "@testing-library/jest-dom": "^6.6.4", "@testing-library/react": "^16.3.0", "react-scripts": "^5.0.1" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] } } diff --git a/start_all.sh b/start_all.sh new file mode 100755 index 0000000..479b3e3 --- /dev/null +++ b/start_all.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Launch both backend (Flask) and frontend (React) servers for ChargeBnB + +# Start backend +cd backend +source venv/bin/activate +export FLASK_APP=run.py +export FLASK_ENV=development +flask run & +BACKEND_PID=$! +cd .. + +# Start frontend +cd frontend +npm start & +FRONTEND_PID=$! +cd .. + +# Wait for both to exit +wait $BACKEND_PID $FRONTEND_PID From 71a4ed05a33b322ab77e0bb13d5a403b73c14314 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 8 Aug 2025 14:16:10 -0400 Subject: [PATCH 2/2] chore: remove obsolete OAuth authentication test workflow --- .github/workflows/test-oauth.yml | 154 ------------------------------- 1 file changed, 154 deletions(-) delete mode 100644 .github/workflows/test-oauth.yml diff --git a/.github/workflows/test-oauth.yml b/.github/workflows/test-oauth.yml deleted file mode 100644 index f7592a5..0000000 --- a/.github/workflows/test-oauth.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: ChargeBnB OAuth Authentication Tests - -on: - push: - branches: [ main, develop, '36-generate-project-structure' ] - paths: - - 'backend/**' - pull_request: - branches: [ main, develop ] - paths: - - 'backend/**' - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.9, 3.10, 3.11] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Cache pip dependencies - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Install dependencies - working-directory: ./backend - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Set up test environment - working-directory: ./backend - run: | - export FLASK_APP=run.py - export FLASK_ENV=testing - # Create test configuration - echo "TESTING=True" > .env.test - - - name: Run unit tests - working-directory: ./backend - run: | - pytest tests/test_models.py tests/test_oauth_services.py -v --tb=short - - - name: Run authentication route tests - working-directory: ./backend - run: | - pytest tests/test_auth_routes.py -v --tb=short - - - name: Run integration tests - working-directory: ./backend - run: | - pytest tests/test_integration.py -v --tb=short - - - name: Run security and edge case tests - working-directory: ./backend - run: | - pytest tests/test_security_edge_cases.py -v --tb=short - - - name: Run all tests with coverage - working-directory: ./backend - run: | - pytest tests/ --cov=. --cov-report=xml --cov-report=term-missing --cov-fail-under=80 - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - with: - file: ./backend/coverage.xml - flags: backend - name: codecov-umbrella - fail_ci_if_error: true - - security-scan: - runs-on: ubuntu-latest - needs: test - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.11 - - - name: Install security scanning tools - run: | - pip install bandit safety - - - name: Run Bandit security scan - working-directory: ./backend - run: | - bandit -r . -f json -o bandit-report.json || true - bandit -r . -f txt - - - name: Check for known security vulnerabilities - working-directory: ./backend - run: | - safety check --json --output safety-report.json || true - safety check - - - name: Upload security reports - uses: actions/upload-artifact@v3 - if: always() - with: - name: security-reports - path: | - backend/bandit-report.json - backend/safety-report.json - - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: 3.11 - - - name: Install linting tools - run: | - pip install flake8 black isort mypy - - - name: Run Black code formatter check - working-directory: ./backend - run: | - black --check --diff . - - - name: Run isort import sorting check - working-directory: ./backend - run: | - isort --check-only --diff . - - - name: Run flake8 linting - working-directory: ./backend - run: | - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - - name: Run mypy type checking - working-directory: ./backend - run: | - mypy . --ignore-missing-imports || true