From 281da320fd20425817e1d38b40273d5ea241f2b8 Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:24:44 -0400 Subject: [PATCH 1/7] Update README.md --- README.md | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d058da1a8..688dd8774 100644 --- a/README.md +++ b/README.md @@ -66,26 +66,11 @@ where `` is your name of choice for the conda environment. ```bash python app.py ``` - -## Usage - -1. Enter the `frontend` directory and run - -```bash -npm run dev -``` - -2. Enter the `backend` directory and run - -```bash -python app.py -``` - -3. Ask away! +4. Ask away! ![demo](./demo.png) -4. You can also use your favorite API client (e.g., Postman) to send a POST request to `http://localhost:8000/api/chat` with the following JSON payload: +4.5. You can also use your favorite API client (e.g., Postman) to send a POST request to `http://localhost:8000/api/chat` with the following JSON payload: ```json { @@ -130,3 +115,13 @@ eb init eb deploy ``` The CloudFront Distribution should have a routing link to the backend server through a behavior pointing to the EB instance. + +## Running and Adding Tests + +This repository contains tests for the backend. To run these tests, pip install pytest (if you haven't already), cd into backend and run +``` +pytest +``` +This runs the tests located in ####**`backend/test_app.py`** + +The tests are also configured run automatically through GitHub Actions every time someone pushes to any branch or opens a pull request. You can find the workflow under #### **`.github/workflows/python-app.yml`** From d42d8e2c4bad04a6271328d8c5f442283a45b46b Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:47:19 -0400 Subject: [PATCH 2/7] Update README.md --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 688dd8774..d6ac6533e 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,172 @@ The CloudFront Distribution should have a routing link to the backend server thr ## Running and Adding Tests -This repository contains tests for the backend. To run these tests, pip install pytest (if you haven't already), cd into backend and run +This repository contains tests for the backend. To run these tests, `pip install pytest` (if you haven't already), cd into backend and run ``` pytest ``` -This runs the tests located in ####**`backend/test_app.py`** +This runs the tests located in **`backend/test_app.py`** -The tests are also configured run automatically through GitHub Actions every time someone pushes to any branch or opens a pull request. You can find the workflow under #### **`.github/workflows/python-app.yml`** +The tests are also configured run automatically through GitHub Actions every time someone pushes to any branch or opens a pull request. You can find the workflow under **`.github/workflows/python-app.yml`** + +To generate a coverage report, first `pip install coverage`, then cd into backend and run +``` +coverage run -m pytest +coverage html +``` +then cd into htmlcov and run `open index.html` + +### Adding Tests + +Always check the import statements at the top of **`test_app.py`** and `pip install` anything that you didn't already. + +The GitHub Actions workflow runs a single `pytest` command in the backend folder, so make sure your pytests are in the root directory and are named appropriately (filename begins with "test_...") + +Some stuff to keep in mind while writing new tests + +1. Locate the Correct Test File: Find the test file that corresponds to the module you are modifying or create a new one if your code is in a new module. +2. Test Function Naming: Begin your test function names with test_. This naming convention is necessary for pytest to recognize the function as a test. +3. Arrange, Act, Assert (AAA) Pattern: Structure your tests with the setup (Arrange), the action (Act), and the verification (Assert). This makes tests easy to read and understand. +4. Mock External Dependencies: Use mock or MagicMock to simulate external API calls, database interactions, and any other external processes. +5. Minimize Test Dependencies: Each test should be independent of others. Avoid shared state between tests. +6. Use Fixtures for Common Setup Code: If multiple tests share setup code, consider using pytest fixtures to centralize this setup logic. + +Here's an example of a test complete with mocks and fixtures: + +####**`mock_client`** +```python +@pytest.fixture +def client(): + mock_courses_collection = MagicMock() + mock_courses_collection.aggregate.return_value = iter( + [ + { + "areas": ["Hu"], + "course_code": "CPSC 150", + "description": "Introduction to the basic ideas of computer science (computability, algorithm, virtual machine, symbol processing system), and of several ongoing relationships between computer science and other fields, particularly philosophy of mind.", + "season_code": "202303", + "sentiment_info": { + "final_label": "NEGATIVE", + "final_proportion": 0.9444444444444444, + }, + "title": "Computer Science and the Modern Intellectual Agenda", + }, + ] + ) + + mock_profiles_collection = MagicMock() + mock_profiles_collection.aggregate.return_value = iter( + [ + { + "_id": {"$oid": "661c6bf1de004d9ab0e15604"}, + "uid": "bob", + "chat_history": [ + {"chat_id": "79f0c4a7-548b-4fce-9a90-aaa709936907", "messages": []}, + {"chat_id": "c5b133b1-2f19-4c3c-8efc-0214c1540a75", "messages": []}, + ], + "courses": [], + "name": "hello", + "email": "bluebookai@harvard.com", + } + ] + ) + + app = create_app( + { + "TESTING": True, + "courses": mock_courses_collection, + "profiles": mock_profiles_collection, + "MONGO_URL": "TEST_URL", + "COURSE_QUERY_LIMIT": 5, + "SAFETY_CHECK_ENABLED": True, + "DATABASE_RELEVANCY_CHECK_ENABLED": True, + } + ) + with app.test_client() as client: + yield client +``` +####**`mock_chat_completion_request`** +``` +@pytest.fixture +def mock_chat_completion_complete(): + with patch("app.chat_completion_request") as mock: + # Common setup for tool call within the chat message + function_mock = MagicMock() + function_mock.arguments = '{"season_code": "202303"}' + + tool_call_mock = MagicMock() + tool_call_mock.function = function_mock + + message_mock_with_tool_calls = MagicMock() + message_mock_with_tool_calls.content = "yes" + message_mock_with_tool_calls.tool_calls = [tool_call_mock] + + message_mock_mock_response = MagicMock() + message_mock_mock_response.content = "Mock response based on user message" + message_mock_mock_response.tool_calls = [tool_call_mock] + + # Special setup for the 7th call + special_function = MagicMock( + arguments='{\n "subject": "ENGL",\n "season_code": "202403",\n "areas": "Hu",\n "skills": "WR"\n}', + name="CourseFilter", + ) + special_tool_call = MagicMock( + id="call_XwZ68clbWJGtafNAlM2uq9f0", + function=special_function, + type="function", + ) + + special_message = MagicMock( + content=None, + role="assistant", + function_call=None, + tool_calls=[special_tool_call], + ) + special_choice = MagicMock( + finish_reason="tool_calls", index=0, logprobs=None, message=special_message + ) + + special_chat_completion = MagicMock( + id="chatcmpl-9EU2H7bduxQysddvkIYnJBFuq1gmR", + choices=[special_choice], + created=1713239077, + model="gpt-4-0613", + object="chat.completion", + system_fingerprint=None, + usage=MagicMock( + completion_tokens=39, prompt_tokens=1475, total_tokens=1514 + ), + ) + + # Wrap these into the respective choice structures + responses = [ + MagicMock(choices=[MagicMock(message=message_mock_with_tool_calls)]), + MagicMock(choices=[MagicMock(message=message_mock_with_tool_calls)]), + MagicMock(choices=[MagicMock(message=message_mock_with_tool_calls)]), + special_chat_completion, + MagicMock(choices=[MagicMock(message=message_mock_with_tool_calls)]), + special_chat_completion, + ] + + mock.side_effect = responses + yield mock +``` +####**`test`** +``` +def test_with_frontend_filters(client, mock_chat_completion_complete): + request_data = { + "season_codes": ["bruh"], + "subject": ["bruh"], + "areas": ["WR", "Hu"], + "message": [ + {"id": 123, "role": "user", "content": "msg"}, + {"id": 123, "role": "ai", "content": "msg2"}, + {"id": 123, "role": "user", "content": "Tell me about cs courses"}, + ], + } + response = client.post("/api/chat", json=request_data) + assert response.status_code == 200 + data = response.get_json() + assert "yes" in data["response"] + assert mock_chat_completion_complete.call_count == 5 +``` From 9da4659d67a2c7425d1f9f2f0d29efc159e7befd Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:47:54 -0400 Subject: [PATCH 3/7] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d6ac6533e..f1043e964 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Some stuff to keep in mind while writing new tests Here's an example of a test complete with mocks and fixtures: -####**`mock_client`** +#### **`mock_client`** ```python @pytest.fixture def client(): @@ -202,8 +202,8 @@ def client(): with app.test_client() as client: yield client ``` -####**`mock_chat_completion_request`** -``` +#### **`mock_chat_completion_request`** +```python @pytest.fixture def mock_chat_completion_complete(): with patch("app.chat_completion_request") as mock: @@ -268,8 +268,8 @@ def mock_chat_completion_complete(): mock.side_effect = responses yield mock ``` -####**`test`** -``` +#### **`test`** +```python def test_with_frontend_filters(client, mock_chat_completion_complete): request_data = { "season_codes": ["bruh"], From b9896bff357735e1c6789a529e11303bee42af83 Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:48:45 -0400 Subject: [PATCH 4/7] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index f1043e964..6265bc250 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,6 @@ Some stuff to keep in mind while writing new tests Here's an example of a test complete with mocks and fixtures: -#### **`mock_client`** ```python @pytest.fixture def client(): @@ -202,7 +201,6 @@ def client(): with app.test_client() as client: yield client ``` -#### **`mock_chat_completion_request`** ```python @pytest.fixture def mock_chat_completion_complete(): @@ -268,7 +266,6 @@ def mock_chat_completion_complete(): mock.side_effect = responses yield mock ``` -#### **`test`** ```python def test_with_frontend_filters(client, mock_chat_completion_complete): request_data = { From afb995b90d09088beec931ad5e0d06c5976461ae Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:52:26 -0400 Subject: [PATCH 5/7] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 6265bc250..d2c108fd7 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,18 @@ Some stuff to keep in mind while writing new tests 4. Mock External Dependencies: Use mock or MagicMock to simulate external API calls, database interactions, and any other external processes. 5. Minimize Test Dependencies: Each test should be independent of others. Avoid shared state between tests. 6. Use Fixtures for Common Setup Code: If multiple tests share setup code, consider using pytest fixtures to centralize this setup logic. +7. Add any new dependencies to the GitHub workflow file. Like this: +```yml +... + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest flask_testing requests_mock uuid +... +``` +And finally, make sure to comment your tests clearly, especially for complex test logic. +Update the project README or docs if your changes include new functionality or change existing behaviors that require documentation. Here's an example of a test complete with mocks and fixtures: From e16f8e8604089db68a5e126c1d3df577f291dbaa Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:15:04 -0400 Subject: [PATCH 6/7] Update README.md --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/README.md b/README.md index d2c108fd7..73439f7be 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,60 @@ With CourseTable, students retrieve information using keyword search, filtering, In this project, we aim to enhance students’ course selection experience by augmenting CourseTable with a natural language interface that can provide customized course recommendations in response to student queries. By supplying more relevant and dynamic results and expanding students’ means of interaction with course data, this will enable students to more easily and effectively determine the best course schedule for themselves. +## Code Structure + +. +├── ./.DS_Store +├── ./.github +│   └── ./.github/workflows +│   └── ./.github/workflows/python-app.yml +├── ./.gitignore +├── ./README.md +├── ./backend +│   ├── ./backend/.elasticbeanstalk +│   │   └── ./backend/.elasticbeanstalk/config.yml +│   ├── ./backend/.env +│   ├── ./backend/add_rating_info.py +│   ├── ./backend/app.py +│   ├── ./backend/course_subjects.json +│   ├── ./backend/lib.py +│   ├── ./backend/port_sentiment_info_to_parsed_courses.py +│   ├── ./backend/process_data.ipynb +│   ├── ./backend/sentiment_classif_requirements.txt +│   ├── ./backend/sentiment_classification.py +│   ├── ./backend/sentiment_classification_for_summer_courses.py +│   └── ./backend/test_app.py +├── ./data +├── ./database_scripts +│   ├── ./database_scripts/.env +│   └── ./database_scripts/load_season_courses.py +├── ./demo.png +├── ./deploy_frontend.sh +├── ./frontend +│   ├── ./frontend/.eslintrc.json +│   ├── ./frontend/README.md +│   ├── ./frontend/next-env.d.ts +│   ├── ./frontend/next.config.mjs +│   ├── ./frontend/package-lock.json +│   ├── ./frontend/package.json +│   ├── ./frontend/src +│   │   └── ./frontend/src/app +│   │   ├── ./frontend/src/app/bg.png +│   │   ├── ./frontend/src/app/chaticon.png +│   │   ├── ./frontend/src/app/course_subjects.json +│   │   ├── ./frontend/src/app/favicon.ico +│   │   ├── ./frontend/src/app/globals.css +│   │   ├── ./frontend/src/app/layout.tsx +│   │   ├── ./frontend/src/app/page.module.css +│   │   ├── ./frontend/src/app/page.tsx +│   │   ├── ./frontend/src/app/profile-icon.png +│   │   ├── ./frontend/src/app/profile.module.css +│   │   └── ./frontend/src/app/profiles.tsx +│   └── ./frontend/tsconfig.json +├── ./load_courses.js +├── ./output.txt +└── ./requirements.txt + ## Get Started ### Frontend From 1f6698a1c5c7b6a3ff91cf758f53d22b99f88a12 Mon Sep 17 00:00:00 2001 From: Buwei Chen <59496117+BuweiChen@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:15:32 -0400 Subject: [PATCH 7/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73439f7be..6a45f8d22 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ With CourseTable, students retrieve information using keyword search, filtering, In this project, we aim to enhance students’ course selection experience by augmenting CourseTable with a natural language interface that can provide customized course recommendations in response to student queries. By supplying more relevant and dynamic results and expanding students’ means of interaction with course data, this will enable students to more easily and effectively determine the best course schedule for themselves. ## Code Structure - +``` . ├── ./.DS_Store ├── ./.github @@ -59,7 +59,7 @@ In this project, we aim to enhance students’ course selection experience by au ├── ./load_courses.js ├── ./output.txt └── ./requirements.txt - +``` ## Get Started ### Frontend