From 9be8755241db2741a5b04b5ffb6c4d0125b1a0b3 Mon Sep 17 00:00:00 2001 From: Siris <40269790+siriscmv@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:23:58 -0400 Subject: [PATCH] feat: ci (#21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: dockerfiles * feat: revamped ci * feat: revamped ci * 🚿 * 🚿 * 🚿 * 🚿 * 🚿 * 🚿 * 🚿 --- .github/workflows/build.yml | 26 ++++++++++ .github/workflows/frontend_CI_CD.yml | 33 ------------- .github/workflows/prettier.yml | 29 ++++++++++++ .github/workflows/pylint.yml | 44 ++++++++++------- .github/workflows/pytest.yml | 47 +++++++++++++++++++ .github/workflows/pytest_ci.yml | 39 --------------- .github/workflows/super-linter.yml | 32 ------------- backend/Dockerfile | 9 +++- backend/test_app.py | 39 ++++++++++----- docker-compose.yml | 6 +-- frontend/Dockerfile | 4 ++ frontend/src/App.js | 3 +- frontend/src/application/MyApplicationPage.js | 5 +- 13 files changed, 175 insertions(+), 141 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/frontend_CI_CD.yml create mode 100644 .github/workflows/prettier.yml create mode 100644 .github/workflows/pytest.yml delete mode 100644 .github/workflows/pytest_ci.yml delete mode 100644 .github/workflows/super-linter.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..a47234b4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Check if frontend can be built + +on: + push: + branches: [project2] + pull_request: + branches: [project2] + +jobs: + frontend: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + working-directory: frontend/ + + - name: Build + run: npm run build + working-directory: frontend/ diff --git a/.github/workflows/frontend_CI_CD.yml b/.github/workflows/frontend_CI_CD.yml deleted file mode 100644 index 8cb33ba4..00000000 --- a/.github/workflows/frontend_CI_CD.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Build and Deploy Frontend - -on: - push: - branches: - - "main" - paths: - - 'frontend/**' - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [14.x] - - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install dependencies - run: npm install - working-directory: frontend/ - - name: Build - run: npm run build - working-directory: frontend/ - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@4.1.1 - with: - branch: gh-pages # The branch the action should deploy to. - folder: frontend/build # The folder the action should deploy. diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 00000000..04ba6dc5 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,29 @@ +name: Check Prettier formatting + +on: + push: + branches: [project2] + pull_request: + branches: [project2] + +jobs: + format: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 # Updated to the latest version + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '18' # Specify the version of Node.js you need + + - name: Install Prettier + run: npm install --global prettier # Install Prettier globally + + - name: Run Prettier + run: | + prettier --check src/**/*.{js,ts,tsx,css} + prettier --check ../backend + working-directory: frontend/ diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 383e65cd..0964a3d0 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,23 +1,33 @@ name: Pylint -on: [push] +on: + push: + branches: [project2] + pull_request: + branches: [project2] jobs: - build: + lint: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10"] + steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint - - name: Analysing the code with pylint - run: | - pylint $(git ls-files '*.py') + - uses: actions/checkout@v3 + - name: Set up Python 3.13 + uses: actions/setup-python@v3 + with: + python-version: 3.13 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + + - name: Analyzing the code with pylint + run: | + pylint $(git ls-files '*.py') > pylint_output.txt || true + score=$(tail -n 1 pylint_output.txt | awk '{print $2}') # Get the Pylint score + echo "::set-output name=score::$score" # Set the score as output + + - name: Save Pylint score to file + run: | + echo "Pylint Score: ${{ steps.pylint.outputs.score }}" > pylint_score.txt diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 00000000..a7146773 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,47 @@ +name: Pytest CI + +on: + push: + branches: [project2] + pull_request: + branches: [project2] + +jobs: + test: + runs-on: ubuntu-latest + services: + mongodb: + image: mongo:5.0 + ports: + - 27017:27017 + env: + MONGO_INITDB_DATABASE: mydatabase + options: >- + --health-cmd "mongo --eval 'db.runCommand({ ping: 1 })'" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python 3.13 + uses: actions/setup-python@v2 + with: + python-version: 3.13 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov pytest-mock coverage + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -r ./backend/requirements.txt + + - name: Set environment variable for MongoDB + run: echo "MONGODB_HOST_STRING=mongodb://localhost:27017/mydatabase" >> $GITHUB_ENV + + - name: Test with pytest + run: | + cd ./backend + pwd + pytest --cov-report xml:cov.xml --cov=./ ./ diff --git a/.github/workflows/pytest_ci.yml b/.github/workflows/pytest_ci.yml deleted file mode 100644 index e4c78726..00000000 --- a/.github/workflows/pytest_ci.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Pytest CI - -on: - push: - branches: - - "main" - -jobs: - build: - - runs-on: ubuntu-latest - env: - MONGO_USER: ${{ secrets.MONGO_USER }} - MONGO_PASS: ${{ secrets.MONGO_PASS }} - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest pytest-cov coverage - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - pip install -r ./backend/requirements.txt - - name: Test with pytest, upload to codecov - run: | - cd ./backend - echo 'Created application.yml' - pwd - pytest --cov-report xml:cov.xml --cov=./ ./ - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml deleted file mode 100644 index f60fb901..00000000 --- a/.github/workflows/super-linter.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Super Linter - -on: - push: - branches: - - "main" - pull_request: - branches: [main] - -jobs: - Build: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Lint Code Base - uses: github/super-linter@v4 - env: - LINTER_RULES_PATH: / - VALIDATE_JSCPD: false - VALIDATE_ALL_CODEBASE: false - VALIDATE_MARKDOWN: false - VALIDATE_JAVASCRIPT_STANDARD: false - VALIDATE_PYTHON_FLAKE8: false - VALIDATE_PYTHON_ISORT: false - VALIDATE_PYTHON_MYPY: false - DEFAULT_BRANCH: main - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/backend/Dockerfile b/backend/Dockerfile index ca389181..958b7eeb 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,7 +10,14 @@ COPY requirements.txt /app/ # Install dependencies specified in requirements.txt RUN pip install --no-cache-dir -r requirements.txt -RUN apt update && apt install -y texlive +RUN apt-get update && \ + apt-get install -y \ + texlive-full \ + texlive-latex-extra \ + texlive-fonts-recommended \ + texlive-fonts-extra \ + texlive-xetex \ + && apt-get clean && rm -rf /var/lib/apt/lists/* # Copy the rest of the application code COPY . /app diff --git a/backend/test_app.py b/backend/test_app.py index c4707811..fea5166e 100644 --- a/backend/test_app.py +++ b/backend/test_app.py @@ -1,11 +1,13 @@ """ Test module for the backend """ + import hashlib from io import BytesIO import pytest import json +import os import datetime from flask_mongoengine import MongoEngine import yaml @@ -26,14 +28,12 @@ def client(): :return: client fixture """ app = create_app() - with open("application.yml") as f: - info = yaml.load(f, Loader=yaml.FullLoader) - username = info["username"] - password = info["password"] - app.config["MONGODB_SETTINGS"] = { - "db": "appTracker", - "host": f"mongodb+srv://{username}:{password}@applicationtracker.287am.mongodb.net/myFirstDatabase?retryWrites=true&w=majority", - } + + app.config["MONGODB_SETTINGS"] = { + "db": "appTracker", + "host": os.getenv("MONGODB_HOST_STRING"), + } + db = MongoEngine() db.disconnect() db.init_app(app) @@ -42,23 +42,29 @@ def client(): db.disconnect() +init = False + + @pytest.fixture def user(client): + global init """ Creates a user with test data :param client: the mongodb client :return: the user object and auth token """ - # print(request.data) data = {"username": "testUser", "password": "test", "fullName": "fullName"} - user = Users.objects(username=data["username"]) - user.first()["applications"] = [] - user.first().save() + if not init: + resp = client.post("/users/signup", json=data) + print(json.loads(resp.data.decode("utf-8"))) + init = True + rv = client.post("/users/login", json=data) jdata = json.loads(rv.data.decode("utf-8")) header = {"Authorization": "Bearer " + jdata["token"]} + user = Users.objects(username=data["username"]) yield user.first(), header user.first()["applications"] = [] user.first().save() @@ -86,6 +92,7 @@ def test_search(client): jdata = json.loads(rv.data.decode("utf-8"))["label"] assert jdata == "successful test search" + def test_search_with_keywords(client): """Test the search endpoint with keywords only.""" rv = client.get("/search?keywords=developer") @@ -93,6 +100,7 @@ def test_search_with_keywords(client): assert isinstance(jdata, list) # Expecting a list of job postings assert len(jdata) >= 0 # Assuming there could be 0 or more job postings + def test_search_with_location(client): """Test the search endpoint with keywords and location.""" rv = client.get("/search?keywords=developer&location=New York") @@ -100,6 +108,7 @@ def test_search_with_location(client): assert isinstance(jdata, list) assert len(jdata) >= 0 # Check if you receive results + def test_search_with_job_type(client): """Test the search endpoint with keywords and job type.""" rv = client.get("/search?keywords=developer&jobType=full-time") @@ -107,6 +116,7 @@ def test_search_with_job_type(client): assert isinstance(jdata, list) assert len(jdata) >= 0 + def test_search_with_location_and_job_type(client): """Test the search endpoint with keywords, location, and job type.""" rv = client.get("/search?keywords=developer&location=New York&jobType=part-time") @@ -114,18 +124,21 @@ def test_search_with_location_and_job_type(client): assert isinstance(jdata, list) assert len(jdata) >= 0 + def test_search_with_invalid_parameters(client): """Test the search endpoint with invalid parameters.""" rv = client.get("/search?keywords=&location=&jobType=") jdata = json.loads(rv.data.decode("utf-8")) assert jdata["label"] == "successful test search" # Default case check + def test_search_with_special_characters(client): """Test the search endpoint with special characters in keywords.""" rv = client.get("/search?keywords=developer@#$%^&*()") jdata = json.loads(rv.data.decode("utf-8")) assert isinstance(jdata, list) + def test_search_no_results(client): """Test the search endpoint with unlikely keywords.""" rv = client.get("/search?keywords=nonexistentjob&location=Nowhere") @@ -133,6 +146,7 @@ def test_search_no_results(client): assert isinstance(jdata, list) # Should still return a list assert len(jdata) == 0 # Assuming no results were found + def test_search_long_strings(client): """Test the search endpoint with excessively long strings.""" long_keyword = "a" * 500 # 500 characters long @@ -140,6 +154,7 @@ def test_search_long_strings(client): jdata = json.loads(rv.data.decode("utf-8")) assert isinstance(jdata, list) + # 3. testing if the application is getting data from database properly def test_get_data(client, user): """ diff --git a/docker-compose.yml b/docker-compose.yml index d6f18da3..66896454 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,10 @@ -version: '3.8' - services: frontend: build: context: ./frontend dockerfile: Dockerfile - environment: - - REACT_APP_BACKEND_BASE_URL=${REACT_APP_BACKEND_BASE_URL} + args: + REACT_APP_BACKEND_BASE_URL: ${REACT_APP_BACKEND_BASE_URL} networks: - app-network volumes: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f26a285c..167804f3 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,6 +1,10 @@ # Step 1: Build the React app FROM node:18 AS build +ARG REACT_APP_BACKEND_BASE_URL + +ENV REACT_APP_BACKEND_BASE_URL=${REACT_APP_BACKEND_BASE_URL} + # Set the working directory inside the container WORKDIR /app diff --git a/frontend/src/App.js b/frontend/src/App.js index 0e3fa145..66c167c2 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -8,7 +8,6 @@ import LoginPage from './login/LoginPage'; import ManageResumePage from './resume/ManageResumePage'; import ProfilePage from './profile/ProfilePage'; import axios from 'axios'; -import fetch from './api/handler'; import MatchesPage from './matches/MatchesPage'; import MyApplicationPage from './application/MyApplicationPage'; import CreateResumePage from './resume/CreateResumePage.tsx'; @@ -81,7 +80,7 @@ export default class App extends React.Component { switchPage(pageName) { const currentPage = - pageName == 'ProfilePage' ? ( + pageName === 'ProfilePage' ? ( ) : ( this.state.mapRouter[pageName] diff --git a/frontend/src/application/MyApplicationPage.js b/frontend/src/application/MyApplicationPage.js index 026bae0e..0d103a95 100644 --- a/frontend/src/application/MyApplicationPage.js +++ b/frontend/src/application/MyApplicationPage.js @@ -114,6 +114,8 @@ const KanbanBoard = ({ applicationLists, handleCardClick, handleUpdateDetails, h } }); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [applicationLists]); const toggleCardExpansion = (id) => { @@ -186,7 +188,8 @@ const KanbanBoard = ({ applicationLists, handleCardClick, handleUpdateDetails, h const ApplicationPage = () => { const [applicationList, setApplicationList] = useState([]); - const [selectedApplication, setSelectedApplication] = useState(null); + // eslint-disable-next-line no-unused-vars + const [_selectedApplication, setSelectedApplication] = useState(null); const [isChanged, setISChanged] = useState(true); useEffect(() => {