From ec67bf1a0c174c91cc16603a4a5f8704db6bf7d3 Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:37:58 -0500 Subject: [PATCH 1/8] Session Manager Test Coverage --- backend/features/blackboard_session.feature | 2 +- .../blackboard_session_manager.feature | 75 +++++ backend/features/steps/all_steps.py | 315 ++++++++++++++++++ .../steps/blackboard_session_steps.py | 134 -------- backend/requirements.txt | 3 +- 5 files changed, 393 insertions(+), 136 deletions(-) create mode 100644 backend/features/blackboard_session_manager.feature create mode 100644 backend/features/steps/all_steps.py delete mode 100644 backend/features/steps/blackboard_session_steps.py diff --git a/backend/features/blackboard_session.feature b/backend/features/blackboard_session.feature index ae17f8b..a4bfe4e 100644 --- a/backend/features/blackboard_session.feature +++ b/backend/features/blackboard_session.feature @@ -1,4 +1,4 @@ -Feature: Blackboard Session Management +Feature: Blackboard Session Testing Scenario: Valid credentials login Given I have valid credentials diff --git a/backend/features/blackboard_session_manager.feature b/backend/features/blackboard_session_manager.feature new file mode 100644 index 0000000..77a43d4 --- /dev/null +++ b/backend/features/blackboard_session_manager.feature @@ -0,0 +1,75 @@ +Feature: Blackboard Session Management + + Scenario: Creating a new blackboard session for a user + Given a blackboard session manager + When I request a session for user "Alice" + Then a new session should be created for "Alice" + + Scenario: Retrieving an existing blackboard session for a user + Given a blackboard session manager + And an existing session for user "Bob" + When I request a session for user "Bob" + Then the existing session for "Bob" should be returned + + Scenario: Storing a blackboard session for a user + Given a blackboard session manager + And a blackboard session for user "Charlie" + When I store the session for user "Charlie" + Then the session for "Charlie" should be stored in the manager + + Scenario: Retrieving a blackboard session by username + Given a blackboard session manager + And an existing session for user "David" + When I retrieve a session by username "David" + Then the session for "David" should be returned + + Scenario: Retrieving a blackboard session by session ID + Given a blackboard session manager + And an existing session with ID "session123" + When I retrieve a session by session ID "session123" + Then the session with ID "session123" should be returned + + Scenario: Deleting a blackboard session for a user + Given a blackboard session manager + And an existing session for user "Eve" + When I delete the session for user "Eve" + Then the session for "Eve" should be removed + + Scenario: Cleaning up inactive sessions + Given a blackboard session manager + And inactive sessions older than 3600 seconds + When I clean up inactive sessions + Then all sessions older than 3600 seconds should be removed + + Scenario: Attempting to retrieve a session for a non-existent user + Given a blackboard session manager + When I retrieve a session by username "NonExistentUser" + Then no session should be returned + + Scenario: Attempting to retrieve a session with a non-existent session ID + Given a blackboard session manager + When I retrieve a session by session ID "nonExistentSession123" + Then no session should be returned + + Scenario: Attempting to delete a session for a non-existent user + Given a blackboard session manager + When I delete the session for user "NonExistentUser" + Then no session should be removed + + Scenario: Cleaning up with no inactive sessions + Given a blackboard session manager + And no inactive sessions + When I clean up inactive sessions + Then no session should be removed + + Scenario: Updating the last activity time of a session + Given a blackboard session manager + And an existing session for user "George" + When I update the last activity time for "George"'s session + Then the last activity time for "George"'s session should be updated + + Scenario: Multiple users sharing the same session ID + Given a blackboard session manager + And an existing session with ID "sharedSession123" for users "Harry" and "Irene" + When I retrieve a session by session ID "sharedSession123" + Then the same session should be returned for both "Harry" and "Irene" \ No newline at end of file diff --git a/backend/features/steps/all_steps.py b/backend/features/steps/all_steps.py new file mode 100644 index 0000000..3e9a7d3 --- /dev/null +++ b/backend/features/steps/all_steps.py @@ -0,0 +1,315 @@ +import os +import time +import uuid +from behave import given, when, then +from blackboard_session import BlackboardSession +from blackboard_session_manager import BlackboardSessionManager +import assertpy +from behave import given, when, then +from blackboard_session import BlackboardSession +import os +from unittest.mock import patch +from dotenv import load_dotenv + +load_dotenv() + +#* Given steps +@given('a blackboard session manager') +def step_impl(context): + context.manager = BlackboardSessionManager() + + +@given('an existing session for user "{username}"') +def step_impl(context, username): + context.manager.get_bb_session(username) + + +@given('a blackboard session for user "{username}"') +def step_impl(context, username): + context.manager.get_bb_session(username) + context.temp_session = context.manager.retrieve_bb_session_by_username( + username) + + +@given('inactive sessions older than {seconds:d} seconds') +def step_impl(context, seconds): + context.blackboard_session_manager = BlackboardSessionManager() + context.blackboard_session_manager.put_bb_session( + 'user1', BlackboardSession(str(uuid.uuid4()), time.time() - seconds - 1)) + + +@given('no inactive sessions') +def step_impl(context): + context.manager = BlackboardSessionManager() + + current_time = time.time() + with context.manager.lock: + inactive_sessions = [session_id for session_id, session in context.manager.bb_sessions.items() + if current_time - session.last_activity_time > 3600] # 1 hour in seconds + + for session_id in inactive_sessions: + username = next((user for user, id in context.manager.user_session_map.items( + ) if id == session_id), None) + if username: + context.manager.delete_bb_session(username) + + +@given('an existing session with ID "{session_id}" for users "{user1}" and "{user2}"') +def step_impl(context, session_id, user1, user2): + new_session = BlackboardSession(session_id, time.time()) + context.manager.bb_sessions[session_id] = new_session + context.manager.user_session_map[user1] = session_id + context.manager.user_session_map[user2] = session_id + + +@given('I have valid credentials') +def step_impl(context): + context.username = os.getenv('TEST_USERNAME') + context.password = os.getenv('TEST_PASSWORD') + context.session = BlackboardSession( + username=context.username, password=context.password) + + +@given('I have invalid username and password') +def step_impl(context): + context.session = BlackboardSession( + username='InvalidUsername', password='InvalidPassword') + + +@given('I am logged in') +def step_impl(context): + context.session = BlackboardSession( + username=context.username, password=context.password) + context.session.login() + context.logged_in = context.session.is_logged_in + + +@given('I am not logged in') +def step_impl(context): + context.session = BlackboardSession( + username='InvalidUsername', password='InvalidPassword') + context.logged_in = context.session.is_logged_in + +#* When steps +@when('I request a session for user "{username}"') +def step_impl(context, username): + context.session = context.manager.get_bb_session(username) + + +@when('I store the session for user "{username}"') +def step_impl(context, username): + context.manager.put_bb_session(username, context.temp_session) + + +@when('I retrieve a session by session ID "{session_id}"') +def step_impl(context, session_id): + context.session = context.manager.retrieve_bb_session_by_id(session_id) + + +@when('I retrieve a session by username "{username}"') +def step_impl(context, username): + context.session = context.manager.retrieve_bb_session_by_username(username) + + +@when('I delete the session for user "{username}"') +def step_impl(context, username): + context.initial_session_count = len(context.manager.bb_sessions) + context.manager.delete_bb_session(username) + + +@when('I clean up inactive sessions') +def step_impl(context): + context.initial_session_count = len(context.manager.bb_sessions) + context.manager.clean_up_inactive_sessions() + + +@given('an existing session with ID "{session_id}"') +def step_impl(context, session_id): + new_session = BlackboardSession(session_id, time.time()) + test_username = "test_user_for_" + session_id + context.manager.bb_sessions[session_id] = new_session + context.manager.user_session_map[test_username] = session_id + + +@when('I update the last activity time for "{username}"\'s session') +def step_impl(context, username): + session = context.manager.retrieve_bb_session_by_username(username) + session.last_activity_time = time.time() # Update the last activity time + + +@when('I login') +def step_impl(context): + context.session.login() + context.response = context.session.get_response() + + +@when('I enable instructors') +def step_impl(context): + if context.logged_in: + with patch.object(context.session, '_get_request') as mock_get_request, \ + patch.object(context.session, '_send_post_request') as mock_post_request: + mock_get_request.return_value.status_code = 200 + mock_get_request.return_value.content = '
' + mock_post_request.return_value.status_code = 302 + mock_post_request.return_value.headers = { + 'Location': 'https://kettering.blackboard.com'} + context.session.enable_instructors() + context.enable_instructors_response = "Instructors enabled" + else: + context.session.enable_instructors() + context.enable_instructors_response = context.session.response + + +@when('I get courses') +def step_impl(context): + if context.logged_in: + with patch.object(context.session, '_send_post_request') as mock_post_request: + mock_post_request.return_value.status_code = 200 + mock_post_request.return_value.content = '
' + context.session.get_courses() + context.get_courses_response = "Courses retrieved" + else: + context.session.get_courses() + context.get_courses_response = context.session.response + + +@when('I get download tasks') +def step_impl(context): + if context.logged_in: + with patch.object(context.session, '_get_request') as mock_get_request: + mock_get_request.side_effect = [ + type('', (), {'status_code': 200, 'content': ''' + + +
+ +
+ + + '''}) + ] + context.session.get_download_tasks() + context.get_download_tasks_response = "Download tasks retrieved" + else: + context.session.get_download_tasks() + context.get_download_tasks_response = context.session.response + + +#* Then steps +@then('a new session should be created for "{username}"') +def step_impl(context, username): + assertpy.assert_that(context.session).is_not_none() + assertpy.assert_that( + context.manager.user_session_map).contains_key(username) + + +@then('the existing session for "{username}" should be returned') +def step_impl(context, username): + existing_session = context.manager.retrieve_bb_session_by_username( + username) + assertpy.assert_that(context.session).is_equal_to(existing_session) + + +@then('the session for "{username}" should be stored in the manager') +def step_impl(context, username): + stored_session = context.manager.retrieve_bb_session_by_username(username) + assertpy.assert_that(stored_session).is_equal_to(context.temp_session) + + +@then('the session with ID "{session_id}" should be returned') +def step_impl(context, session_id): + assertpy.assert_that(context.session.session_id).is_equal_to(session_id) + + +@then('the session for "{username}" should be returned') +def step_impl(context, username): + retrieved_session = context.manager.retrieve_bb_session_by_username( + username) + assertpy.assert_that(retrieved_session).is_not_none() + assertpy.assert_that(retrieved_session).is_equal_to(context.session) + + +@then('the session for "{username}" should be removed') +def step_impl(context, username): + assertpy.assert_that( + context.manager.user_session_map).does_not_contain_key(username) + + +@then('all sessions older than {seconds:d} seconds should be removed') +def step_impl(context, seconds): + current_time = time.time() + for session in context.manager.bb_sessions.values(): + assertpy.assert_that( + current_time - session.last_activity_time).is_less_than(seconds) + + +@then('no session should be returned') +def step_impl(context): + assertpy.assert_that(context.session).is_none() + + +@then('no session should be removed') +def step_impl(context): + final_session_count = len(context.manager.bb_sessions) + assertpy.assert_that(final_session_count).is_equal_to( + context.initial_session_count) + + +@then('the last activity time for "{username}"\'s session should be updated') +def step_impl(context, username): + session = context.manager.retrieve_bb_session_by_username(username) + current_time = time.time() + tolerance = 1 # Define a tolerance for the time comparison + + assertpy.assert_that(session.last_activity_time).is_close_to( + current_time, tolerance) + + +@then('the same session should be returned for both "{user1}" and "{user2}"') +def step_impl(context, user1, user2): + session1 = context.manager.retrieve_bb_session_by_username(user1) + session2 = context.manager.retrieve_bb_session_by_username(user2) + assertpy.assert_that(session1).is_equal_to(session2) + + +@then('the response should be "Login successful."') +def step_impl(context): + assert context.response == "Login successful." + + +@then('the response should be "The username you entered cannot be identified."') +def step_impl(context): + assert context.response == "The username you entered cannot be identified." + + +@then('the response should be "Already logged in."') +def step_impl(context): + assert context.response == "Already logged in." + + +@then('the enable instructors response should be "{message}"') +def step_impl(context, message): + assert context.enable_instructors_response == message + + +@then('the get courses response should be "{message}"') +def step_impl(context, message): + assert context.get_courses_response == message + + +@then('the get download tasks response should be "{message}"') +def step_impl(context, message): + assert context.get_download_tasks_response == message diff --git a/backend/features/steps/blackboard_session_steps.py b/backend/features/steps/blackboard_session_steps.py deleted file mode 100644 index 545f325..0000000 --- a/backend/features/steps/blackboard_session_steps.py +++ /dev/null @@ -1,134 +0,0 @@ -from behave import given, when, then -from blackboard_session import BlackboardSession -import os -from unittest.mock import patch -from dotenv import load_dotenv - -load_dotenv() - -@given('I have valid credentials') -def step_impl(context): - context.username = os.getenv('TEST_USERNAME') - context.password = os.getenv('TEST_PASSWORD') - context.session = BlackboardSession( - username=context.username, password=context.password) - - -@given('I have invalid username and password') -def step_impl(context): - context.session = BlackboardSession( - username='InvalidUsername', password='InvalidPassword') - - -@when('I login') -def step_impl(context): - context.session.login() - context.response = context.session.get_response() - - -@then('the response should be "Login successful."') -def step_impl(context): - assert context.response == "Login successful." - - -@then('the response should be "The username you entered cannot be identified."') -def step_impl(context): - assert context.response == "The username you entered cannot be identified." - -@then('the response should be "Already logged in."') -def step_impl(context): - assert context.response == "Already logged in." - -@given('I am logged in') -def step_impl(context): - context.session = BlackboardSession( - username=context.username, password=context.password) - context.session.login() - context.logged_in = context.session.is_logged_in - - -@given('I am not logged in') -def step_impl(context): - context.session = BlackboardSession( - username='InvalidUsername', password='InvalidPassword') - context.logged_in = context.session.is_logged_in - - -@when('I enable instructors') -def step_impl(context): - if context.logged_in: - with patch.object(context.session, '_get_request') as mock_get_request, \ - patch.object(context.session, '_send_post_request') as mock_post_request: - mock_get_request.return_value.status_code = 200 - mock_get_request.return_value.content = '
' - mock_post_request.return_value.status_code = 302 - mock_post_request.return_value.headers = { - 'Location': 'https://kettering.blackboard.com'} - context.session.enable_instructors() - context.enable_instructors_response = "Instructors enabled" - else: - context.session.enable_instructors() - context.enable_instructors_response = context.session.response - - -@then('the enable instructors response should be "{message}"') -def step_impl(context, message): - assert context.enable_instructors_response == message - - -@when('I get courses') -def step_impl(context): - if context.logged_in: - with patch.object(context.session, '_send_post_request') as mock_post_request: - mock_post_request.return_value.status_code = 200 - mock_post_request.return_value.content = '
' - context.session.get_courses() - context.get_courses_response = "Courses retrieved" - else: - context.session.get_courses() - context.get_courses_response = context.session.response - - -@then('the get courses response should be "{message}"') -def step_impl(context, message): - assert context.get_courses_response == message - - -@when('I get download tasks') -def step_impl(context): - if context.logged_in: - with patch.object(context.session, '_get_request') as mock_get_request: - mock_get_request.side_effect = [ - type('', (), {'status_code': 200, 'content': ''' - - -
- -
- - - '''}) - ] - context.session.get_download_tasks() - context.get_download_tasks_response = "Download tasks retrieved" - else: - context.session.get_download_tasks() - context.get_download_tasks_response = context.session.response - - -@then('the get download tasks response should be "{message}"') -def step_impl(context, message): - assert context.get_download_tasks_response == message diff --git a/backend/requirements.txt b/backend/requirements.txt index 47609ba..0359990 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -9,4 +9,5 @@ flask_apscheduler pydrive2 python-dotenv lxml -behave \ No newline at end of file +behave +assertpy \ No newline at end of file From a6fb6128aa8922e990469c519b79797c33de340c Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:16:57 -0500 Subject: [PATCH 2/8] Refactored Behave Split file base test into feature based tests :D --- ...ession_enable_instructors_and_time.feature | 16 ++++ .../features/bb_session_get_courses.feature | 16 ++++ .../bb_session_get_download_tasks.feature | 16 ++++ backend/features/bb_session_login.feature | 20 +++++ ..._session_manager_creating_sessions.feature | 15 ++++ ..._session_manager_deleting_sessions.feature | 27 +++++++ ...session_manager_modifying_sessions.feature | 10 +++ ...ession_manager_retrieving_sessions.feature | 38 ++++++++++ backend/features/blackboard_session.feature | 53 ------------- .../blackboard_session_manager.feature | 75 ------------------- 10 files changed, 158 insertions(+), 128 deletions(-) create mode 100644 backend/features/bb_session_enable_instructors_and_time.feature create mode 100644 backend/features/bb_session_get_courses.feature create mode 100644 backend/features/bb_session_get_download_tasks.feature create mode 100644 backend/features/bb_session_login.feature create mode 100644 backend/features/bb_session_manager_creating_sessions.feature create mode 100644 backend/features/bb_session_manager_deleting_sessions.feature create mode 100644 backend/features/bb_session_manager_modifying_sessions.feature create mode 100644 backend/features/bb_session_manager_retrieving_sessions.feature delete mode 100644 backend/features/blackboard_session.feature delete mode 100644 backend/features/blackboard_session_manager.feature diff --git a/backend/features/bb_session_enable_instructors_and_time.feature b/backend/features/bb_session_enable_instructors_and_time.feature new file mode 100644 index 0000000..8b7f76f --- /dev/null +++ b/backend/features/bb_session_enable_instructors_and_time.feature @@ -0,0 +1,16 @@ +Feature: Blackboard Session - Enable Insturctors and Time + In order to correctly name courses + As a system + I want to enable instructors and course season + + Scenario: Enable instructors when logged in + Given I have valid credentials + And I am logged in + When I enable instructors + Then the enable instructors response should be "Instructors enabled" + + Scenario: Enable instructors when not logged in + Given I have invalid username and password + And I am not logged in + When I enable instructors + Then the enable instructors response should be "Not logged in." \ No newline at end of file diff --git a/backend/features/bb_session_get_courses.feature b/backend/features/bb_session_get_courses.feature new file mode 100644 index 0000000..240b9e6 --- /dev/null +++ b/backend/features/bb_session_get_courses.feature @@ -0,0 +1,16 @@ +Feature: Blackboard Session - Get Courses + In order to archive courses for the user + As a system + I want to get courses + +Scenario: Get courses when logged in + Given I have valid credentials + And I am logged in + When I get courses + Then the get courses response should be "Courses retrieved" + + Scenario: Get courses when not logged in + Given I have invalid username and password + And I am not logged in + When I get courses + Then the get courses response should be "Not logged in." \ No newline at end of file diff --git a/backend/features/bb_session_get_download_tasks.feature b/backend/features/bb_session_get_download_tasks.feature new file mode 100644 index 0000000..4aeee60 --- /dev/null +++ b/backend/features/bb_session_get_download_tasks.feature @@ -0,0 +1,16 @@ +Feature: Blackboard Session - Get Download Tasks + In order to return the courses to the user + As a system + I want to get the download tasks + +Scenario: Get download tasks when logged in + Given I have valid credentials + And I am logged in + When I get download tasks + Then the get download tasks response should be "Download tasks retrieved" + + Scenario: Get download tasks when not logged in + Given I have invalid username and password + And I am not logged in + When I get download tasks + Then the get download tasks response should be "Not logged in." \ No newline at end of file diff --git a/backend/features/bb_session_login.feature b/backend/features/bb_session_login.feature new file mode 100644 index 0000000..a4ae8f5 --- /dev/null +++ b/backend/features/bb_session_login.feature @@ -0,0 +1,20 @@ +Feature: Blackboard Session - Login + In order to access Blackboard + As a system + I want to login + +Scenario: Valid credentials login + Given I have valid credentials + When I login + Then the response should be "Login successful." + + Scenario: Invalid both username and password + Given I have invalid username and password + When I login + Then the response should be "The username you entered cannot be identified." + + Scenario: I attempt to login when already logged in + Given I have valid credentials + And I am logged in + When I login + Then the response should be "Already logged in." \ No newline at end of file diff --git a/backend/features/bb_session_manager_creating_sessions.feature b/backend/features/bb_session_manager_creating_sessions.feature new file mode 100644 index 0000000..85bb74c --- /dev/null +++ b/backend/features/bb_session_manager_creating_sessions.feature @@ -0,0 +1,15 @@ +Feature: Blackboard Sesssion Manager - Creating a new session for a user + In order to use blackboard + As a user + I want to create a new session + + Scenario: Creating a new blackboard session for a user + Given a blackboard session manager + When I request a session for user "Alice" + Then a new session should be created for "Alice" + + Scenario: Storing a blackboard session for a user + Given a blackboard session manager + And a blackboard session for user "Charlie" + When I store the session for user "Charlie" + Then the session for "Charlie" should be stored in the manager \ No newline at end of file diff --git a/backend/features/bb_session_manager_deleting_sessions.feature b/backend/features/bb_session_manager_deleting_sessions.feature new file mode 100644 index 0000000..67e5aed --- /dev/null +++ b/backend/features/bb_session_manager_deleting_sessions.feature @@ -0,0 +1,27 @@ +Feature: Blackboard Session Manager - Deleting Sessions + In order to correctly delete user data + As a system + I want to delete blackboard sessions + +Scenario: Deleting a blackboard session for a user + Given a blackboard session manager + And an existing session for user "Eve" + When I delete the session for user "Eve" + Then the session for "Eve" should be removed + + Scenario: Cleaning up inactive sessions + Given a blackboard session manager + And inactive sessions older than 3600 seconds + When I clean up inactive sessions + Then all sessions older than 3600 seconds should be removed + + Scenario: Attempting to delete a session for a non-existent user + Given a blackboard session manager + When I delete the session for user "NonExistentUser" + Then no session should be removed + + Scenario: Cleaning up with no inactive sessions + Given a blackboard session manager + And no inactive sessions + When I clean up inactive sessions + Then no session should be removed \ No newline at end of file diff --git a/backend/features/bb_session_manager_modifying_sessions.feature b/backend/features/bb_session_manager_modifying_sessions.feature new file mode 100644 index 0000000..c839b21 --- /dev/null +++ b/backend/features/bb_session_manager_modifying_sessions.feature @@ -0,0 +1,10 @@ +Feature: Blackboard session management - Modifying users sessions + In order to modify users sessions to keep them alive + As a blackboard administrator + I want to be able to modify users sessions + + Scenario: Updating the last activity time of a session + Given a blackboard session manager + And an existing session for user "George" + When I update the last activity time for "George"'s session + Then the last activity time for "George"'s session should be updated diff --git a/backend/features/bb_session_manager_retrieving_sessions.feature b/backend/features/bb_session_manager_retrieving_sessions.feature new file mode 100644 index 0000000..21d4bca --- /dev/null +++ b/backend/features/bb_session_manager_retrieving_sessions.feature @@ -0,0 +1,38 @@ +Feature: Blackboard Session Manager: Retrieving a session + In order to keep track of users + As a system + I want to be able to retrieve a blackboard session for a user + +Scenario: Retrieving an existing blackboard session for a user + Given a blackboard session manager + And an existing session for user "Bob" + When I request a session for user "Bob" + Then the existing session for "Bob" should be returned + + Scenario: Retrieving a blackboard session by username + Given a blackboard session manager + And an existing session for user "David" + When I retrieve a session by username "David" + Then the session for "David" should be returned + + Scenario: Retrieving a blackboard session by session ID + Given a blackboard session manager + And an existing session with ID "session123" + When I retrieve a session by session ID "session123" + Then the session with ID "session123" should be returned + + Scenario: Attempting to retrieve a session for a non-existent user + Given a blackboard session manager + When I retrieve a session by username "NonExistentUser" + Then no session should be returned + + Scenario: Attempting to retrieve a session with a non-existent session ID + Given a blackboard session manager + When I retrieve a session by session ID "nonExistentSession123" + Then no session should be returned + + Scenario: Multiple users sharing the same session ID + Given a blackboard session manager + And an existing session with ID "sharedSession123" for users "Harry" and "Irene" + When I retrieve a session by session ID "sharedSession123" + Then the same session should be returned for both "Harry" and "Irene" \ No newline at end of file diff --git a/backend/features/blackboard_session.feature b/backend/features/blackboard_session.feature deleted file mode 100644 index a4bfe4e..0000000 --- a/backend/features/blackboard_session.feature +++ /dev/null @@ -1,53 +0,0 @@ -Feature: Blackboard Session Testing - - Scenario: Valid credentials login - Given I have valid credentials - When I login - Then the response should be "Login successful." - - Scenario: Invalid both username and password - Given I have invalid username and password - When I login - Then the response should be "The username you entered cannot be identified." - - Scenario: I attempt to login when already logged in - Given I have valid credentials - And I am logged in - When I login - Then the response should be "Already logged in." - - Scenario: Enable instructors when logged in - Given I have valid credentials - And I am logged in - When I enable instructors - Then the enable instructors response should be "Instructors enabled" - - Scenario: Enable instructors when not logged in - Given I have invalid username and password - And I am not logged in - When I enable instructors - Then the enable instructors response should be "Not logged in." - - Scenario: Get courses when logged in - Given I have valid credentials - And I am logged in - When I get courses - Then the get courses response should be "Courses retrieved" - - Scenario: Get courses when not logged in - Given I have invalid username and password - And I am not logged in - When I get courses - Then the get courses response should be "Not logged in." - - Scenario: Get download tasks when logged in - Given I have valid credentials - And I am logged in - When I get download tasks - Then the get download tasks response should be "Download tasks retrieved" - - Scenario: Get download tasks when not logged in - Given I have invalid username and password - And I am not logged in - When I get download tasks - Then the get download tasks response should be "Not logged in." \ No newline at end of file diff --git a/backend/features/blackboard_session_manager.feature b/backend/features/blackboard_session_manager.feature deleted file mode 100644 index 77a43d4..0000000 --- a/backend/features/blackboard_session_manager.feature +++ /dev/null @@ -1,75 +0,0 @@ -Feature: Blackboard Session Management - - Scenario: Creating a new blackboard session for a user - Given a blackboard session manager - When I request a session for user "Alice" - Then a new session should be created for "Alice" - - Scenario: Retrieving an existing blackboard session for a user - Given a blackboard session manager - And an existing session for user "Bob" - When I request a session for user "Bob" - Then the existing session for "Bob" should be returned - - Scenario: Storing a blackboard session for a user - Given a blackboard session manager - And a blackboard session for user "Charlie" - When I store the session for user "Charlie" - Then the session for "Charlie" should be stored in the manager - - Scenario: Retrieving a blackboard session by username - Given a blackboard session manager - And an existing session for user "David" - When I retrieve a session by username "David" - Then the session for "David" should be returned - - Scenario: Retrieving a blackboard session by session ID - Given a blackboard session manager - And an existing session with ID "session123" - When I retrieve a session by session ID "session123" - Then the session with ID "session123" should be returned - - Scenario: Deleting a blackboard session for a user - Given a blackboard session manager - And an existing session for user "Eve" - When I delete the session for user "Eve" - Then the session for "Eve" should be removed - - Scenario: Cleaning up inactive sessions - Given a blackboard session manager - And inactive sessions older than 3600 seconds - When I clean up inactive sessions - Then all sessions older than 3600 seconds should be removed - - Scenario: Attempting to retrieve a session for a non-existent user - Given a blackboard session manager - When I retrieve a session by username "NonExistentUser" - Then no session should be returned - - Scenario: Attempting to retrieve a session with a non-existent session ID - Given a blackboard session manager - When I retrieve a session by session ID "nonExistentSession123" - Then no session should be returned - - Scenario: Attempting to delete a session for a non-existent user - Given a blackboard session manager - When I delete the session for user "NonExistentUser" - Then no session should be removed - - Scenario: Cleaning up with no inactive sessions - Given a blackboard session manager - And no inactive sessions - When I clean up inactive sessions - Then no session should be removed - - Scenario: Updating the last activity time of a session - Given a blackboard session manager - And an existing session for user "George" - When I update the last activity time for "George"'s session - Then the last activity time for "George"'s session should be updated - - Scenario: Multiple users sharing the same session ID - Given a blackboard session manager - And an existing session with ID "sharedSession123" for users "Harry" and "Irene" - When I retrieve a session by session ID "sharedSession123" - Then the same session should be returned for both "Harry" and "Irene" \ No newline at end of file From b18ccb53e542afe66d461a10de8e1c296d7dfcfe Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:02:24 -0500 Subject: [PATCH 3/8] Added App.py Lv1 Coverage --- backend/features/app_login.feature | 24 ++++++++ ...ession_enable_instructors_and_time.feature | 28 ++++----- backend/features/environment.py | 4 ++ backend/features/steps/all_steps.py | 57 +++++++++++++++++++ backend/usernames.py | 2 +- 5 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 backend/features/app_login.feature create mode 100644 backend/features/environment.py diff --git a/backend/features/app_login.feature b/backend/features/app_login.feature new file mode 100644 index 0000000..cb78717 --- /dev/null +++ b/backend/features/app_login.feature @@ -0,0 +1,24 @@ +Feature: Backend Flask App - Login + In order to protect directory + As a system + I want users to have to login + + Scenario: Successful Login + Given App is running + When I pass valid credentials to the login endpoint + Then The response of "200" and "Logged in Successfully" should be returned + + Scenario: Unsuccessful Login - Incorrect username and password + Given App is running + When I pass an incorrect username and password to the login endpoint + Then The response of "401" and "The username you entered cannot be identified." should be returned + + Scenario: Unsuccessful Login - Incorrect password + Given App is running + When I pass an incorrect password to the login endpoint + Then The response of "401" and "The password you entered was incorrect." should be returned + + Scenario: Unsuccessful Login - Incorrect username + Given App is running + When I pass an incorrect username to the login endpoint + Then The response of "401" and "The username you entered cannot be identified." should be returned \ No newline at end of file diff --git a/backend/features/bb_session_enable_instructors_and_time.feature b/backend/features/bb_session_enable_instructors_and_time.feature index 8b7f76f..4198100 100644 --- a/backend/features/bb_session_enable_instructors_and_time.feature +++ b/backend/features/bb_session_enable_instructors_and_time.feature @@ -1,16 +1,16 @@ -Feature: Blackboard Session - Enable Insturctors and Time - In order to correctly name courses - As a system - I want to enable instructors and course season +Feature: Blackboard Session - Enable Insturctors and Time + In order to correctly name courses + As a system + I want to enable instructors and course season - Scenario: Enable instructors when logged in - Given I have valid credentials - And I am logged in - When I enable instructors - Then the enable instructors response should be "Instructors enabled" + Scenario: Enable instructors when logged in + Given I have valid credentials + And I am logged in + When I enable instructors + Then the enable instructors response should be "Instructors enabled" - Scenario: Enable instructors when not logged in - Given I have invalid username and password - And I am not logged in - When I enable instructors - Then the enable instructors response should be "Not logged in." \ No newline at end of file + Scenario: Enable instructors when not logged in + Given I have invalid username and password + And I am not logged in + When I enable instructors + Then the enable instructors response should be "Not logged in." \ No newline at end of file diff --git a/backend/features/environment.py b/backend/features/environment.py new file mode 100644 index 0000000..5e408de --- /dev/null +++ b/backend/features/environment.py @@ -0,0 +1,4 @@ +from app import app + +def before_all(context): + context.client = app.test_client() \ No newline at end of file diff --git a/backend/features/steps/all_steps.py b/backend/features/steps/all_steps.py index 3e9a7d3..58e0eeb 100644 --- a/backend/features/steps/all_steps.py +++ b/backend/features/steps/all_steps.py @@ -1,6 +1,8 @@ import os +import random import time import uuid +import usernames as usernames from behave import given, when, then from blackboard_session import BlackboardSession from blackboard_session_manager import BlackboardSessionManager @@ -90,6 +92,10 @@ def step_impl(context): username='InvalidUsername', password='InvalidPassword') context.logged_in = context.session.is_logged_in +@given('App is running') +def step_impl(context): + assert context.client + #* When steps @when('I request a session for user "{username}"') def step_impl(context, username): @@ -207,6 +213,41 @@ def step_impl(context): context.session.get_download_tasks() context.get_download_tasks_response = context.session.response +@when('I pass valid credentials to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username=os.getenv('TEST_USERNAME'), + password=os.getenv('TEST_PASSWORD') + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['message'] == 'Logged in successfully' + +@when('I pass an incorrect username and password to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username='InvalidUsername', + password='InvalidPassword' + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'The username you entered cannot be identified.' + +@when('I pass an incorrect password to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username=random.choice(list(usernames.usernames)), + password='InvalidPassword' + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'The password you entered was incorrect.' + +@when('I pass an incorrect username to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username='InvalidUsername', + password=os.getenv('TEST_PASSWORD') + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'The username you entered cannot be identified.' #* Then steps @then('a new session should be created for "{username}"') @@ -313,3 +354,19 @@ def step_impl(context, message): @then('the get download tasks response should be "{message}"') def step_impl(context, message): assert context.get_download_tasks_response == message + +@then('The response of "200" and "Logged in Successfully" should be returned') +def step_impl(context): + assert context.page.get_json() == {'message': 'Logged in successfully'} + +@then('The response of "401" and "The username you entered cannot be identified." should be returned') +def step_impl(context): + assert context.page.get_json() == {'error': 'The username you entered cannot be identified.'} + +@then('The response of "401" and "The password you entered was incorrect." should be returned') +def step_impl(context): + assert context.page.get_json() == {'error': 'The password you entered was incorrect.'} + +@then('The response of "429" and "Too many requests, please try again later." should be returned') +def step_impl(context): + assert context.page.get_json() == {'error': 'Too many requests, please try again later.'} \ No newline at end of file diff --git a/backend/usernames.py b/backend/usernames.py index 67a9532..bba02a3 100644 --- a/backend/usernames.py +++ b/backend/usernames.py @@ -1 +1 @@ -usernames = {'babdulnour', 'dacharya', 'madel', 'hahmed', 'jalhiyafi', 'fali', 'oaljarrah', 'balzahab', 'ralzahabi', 'sameer', 'patkinso', 'tatkinso', 'dbabb', 'cbai1', 'abalsamykamaraj', 'jbaqersad', 'jbastiaan', 'sbazinski', 'ebell', 'jberry', 'kbrinkerbrouwer', 'cbrown1', 'jbrown1', 'mbrown', 'kbruning', 'rbushart', 'mcallaha', 'pcarralero', 'kchao', 'acheng', 'lconley', 'sdas', 'gdavis', 'ydong', 'cduan', 'deasley', 'meblenkamp', 'keddy', 'wedwards', 'belahi', 'femami', 'sfarhat', 'mfarmer', 'dfoster', 'lgandy'} +usernames = {'babdulnour', 'dacharya', 'madel', 'hahmed', 'jalhiyafi', 'fali', 'oaljarrah', 'balzahab', 'ralzahabi', 'sameer', 'patkinso', 'tatkinso', 'dbabb', 'cbai1', 'abalsamykamaraj', 'jbaqersad', 'jbastiaan', 'sbazinski', 'ebell', 'jberry', 'kbrinkerbrouwer', 'cbrown1', 'jbrown1', 'mbrown', 'kbruning', 'rbushart', 'mcallaha', 'pcarralero', 'kchao', 'acheng', 'lconley', 'sdas', 'gdavis', 'ydong', 'cduan', 'deasley', 'meblenkamp', 'keddy', 'wedwards', 'belahi', 'femami', 'sfarhat', 'mfarmer', 'dfoster', 'lgandy'} \ No newline at end of file From ed6a08282649e2552a0fdf5b2a25c178074dd2d5 Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 23 Jan 2024 12:48:28 -0500 Subject: [PATCH 4/8] Added dev Environement Variable --- .github/workflows/BDD-Tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/BDD-Tests.yml b/.github/workflows/BDD-Tests.yml index 41d73dc..5daa1de 100644 --- a/.github/workflows/BDD-Tests.yml +++ b/.github/workflows/BDD-Tests.yml @@ -29,6 +29,7 @@ jobs: env: TEST_USERNAME: ${{ secrets.TEST_USERNAME }} TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }} + ENVIRONMENT: dev run: | cd backend behave From 83e88c76493a2b50d5adefa695c666c8e892f71c Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:24:39 -0500 Subject: [PATCH 5/8] Adding More Tests To Fix last 4 tests in app_login.feature --- backend/app.py | 9 ++- backend/features/app_login.feature | 58 +++++++++++++--- backend/features/steps/all_steps.py | 103 +++++++++++++++++++++++----- 3 files changed, 142 insertions(+), 28 deletions(-) diff --git a/backend/app.py b/backend/app.py index bbbe9b1..be6b41e 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,4 +1,5 @@ from functools import wraps +from json import JSONDecodeError import logging import os import threading @@ -71,7 +72,11 @@ def index(): @app.route('/login', methods=['POST']) @cross_origin(supports_credentials=True) def login(): - data = request.json + try: + data = request.get_json() + except JSONDecodeError: + return jsonify({'error': 'Invalid JSON format'}), 400 + username = data.get('username') password = data.get('password') @@ -89,7 +94,7 @@ def login(): bb_session_manager.put_bb_session(username, bb_session) resp = make_response( - jsonify({'message': 'Logged in successfully'})) + jsonify({'message': 'Logged in successfully'}), 200) resp.set_cookie('user_session', bb_session.session_id, max_age=3600, secure=True, httponly=True) return resp diff --git a/backend/features/app_login.feature b/backend/features/app_login.feature index cb78717..3857c30 100644 --- a/backend/features/app_login.feature +++ b/backend/features/app_login.feature @@ -3,22 +3,60 @@ Feature: Backend Flask App - Login As a system I want users to have to login - Scenario: Successful Login - Given App is running - When I pass valid credentials to the login endpoint - Then The response of "200" and "Logged in Successfully" should be returned + Scenario: Successful Login + Given the app is running + When I pass valid credentials to the login endpoint + Then the response of "200" and "Logged in successfully" should be returned + And cookies should be set Scenario: Unsuccessful Login - Incorrect username and password - Given App is running + Given the app is running When I pass an incorrect username and password to the login endpoint - Then The response of "401" and "The username you entered cannot be identified." should be returned + Then the response of "401" and "The username you entered cannot be identified." should be returned Scenario: Unsuccessful Login - Incorrect password - Given App is running + Given the app is running When I pass an incorrect password to the login endpoint - Then The response of "401" and "The password you entered was incorrect." should be returned + Then the response of "401" and "The password you entered was incorrect." should be returned Scenario: Unsuccessful Login - Incorrect username - Given App is running + Given the app is running When I pass an incorrect username to the login endpoint - Then The response of "401" and "The username you entered cannot be identified." should be returned \ No newline at end of file + Then the response of "401" and "The username you entered cannot be identified." should be returned + + Scenario: Unsuccessful Login - Missing password + Given the app is running + When I pass only a username to the login endpoint + Then the response of "400" and "Missing username or password" should be returned + + Scenario: Unsuccessful Login - Missing username + Given the app is running + When I pass only a password to the login endpoint + Then the response of "400" and "Missing username or password" should be returned + + Scenario: Unsuccessful Login - Missing username and password + Given the app is running + When I pass no credentials to the login endpoint + Then the response of "400" and "Missing username or password" should be returned + + Scenario: Unsuccessful Login - Invalid JSON Format in Request + Given the app is running + When I pass data in an invalid JSON format to the login endpoint + Then the response of "400" and "Invalid request format" should be returned + + Scenario: Server Error During Login Process + Given the app is running + When I pass valid credentials but the server encounters an internal error during login + Then the response of "500" and the specific error message should be returned + + Scenario: Already Logged In + Given the app is running + And the user is already logged in + When I pass valid credentials to the login endpoint again + Then the response of "200" and "Already logged in" should be returned + + Scenario: Session Cookie Attributes Verification + Given the app is running + When I pass valid credentials to the login endpoint + Then the response should include a 'Set-Cookie' header with 'user_session' cookie + And the cookie should have 'HttpOnly' and 'Secure' attributes set diff --git a/backend/features/steps/all_steps.py b/backend/features/steps/all_steps.py index 58e0eeb..0ac59a8 100644 --- a/backend/features/steps/all_steps.py +++ b/backend/features/steps/all_steps.py @@ -66,8 +66,8 @@ def step_impl(context, session_id, user1, user2): @given('I have valid credentials') def step_impl(context): - context.username = os.getenv('TEST_USERNAME') - context.password = os.getenv('TEST_PASSWORD') + context.username = os.environ.get('TEST_USERNAME') + context.password = os.environ.get('TEST_PASSWORD') context.session = BlackboardSession( username=context.username, password=context.password) @@ -92,7 +92,7 @@ def step_impl(context): username='InvalidUsername', password='InvalidPassword') context.logged_in = context.session.is_logged_in -@given('App is running') +@given('the app is running') def step_impl(context): assert context.client @@ -216,8 +216,8 @@ def step_impl(context): @when('I pass valid credentials to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( - username=os.getenv('TEST_USERNAME'), - password=os.getenv('TEST_PASSWORD') + username=os.environ.get('TEST_USERNAME'), + password=os.environ.get('TEST_PASSWORD') ), headers={'Content-Type': 'application/json'}, follow_redirects=True) response = context.page.get_json() assert response['message'] == 'Logged in successfully' @@ -244,11 +244,62 @@ def step_impl(context): def step_impl(context): context.page = context.client.post('/login', json=dict( username='InvalidUsername', - password=os.getenv('TEST_PASSWORD') + password=os.environ.get('TEST_PASSWORD') ), headers={'Content-Type': 'application/json'}, follow_redirects=True) response = context.page.get_json() assert response['error'] == 'The username you entered cannot be identified.' +@when('I pass only a username to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username='InvalidUsername', + password='' + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'Missing username or password' + +@when('I pass only a password to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username='', + password='InvalidPassword' + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'Missing username or password' + +@when('I pass no credentials to the login endpoint') +def step_impl(context): + context.page = context.client.post('/login', json=dict( + username='', + password='' + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'Missing username or password' + +@when('I pass data in an invalid JSON format to the login endpoint') +def step_impl(context): + invalid_json_data = 'Invalid JSON format' + context.page = context.client.post('/login', data=invalid_json_data, headers={'Content-Type': 'application/json'}, follow_redirects=True) + response = context.page.get_json() + assert response['error'] == 'Invalid JSON payload received.' + + +@when('I pass valid credentials but the server encounters an internal error when logging in') +def step_impl(context): + with patch('blackboard_session.BlackboardSession.login') as mock_login: + mock_login.side_effect = Exception("Internal server error") + try: + context.session.login() + except Exception as e: + context.exception_message = str(e) + +@when('the user is already logged in') +def step_impl(context): + # Assuming 'context.session' is an instance of BlackboardSession + context.session.login() # First login + context.second_login_response = context.session.login() # Second login attempt + + #* Then steps @then('a new session should be created for "{username}"') def step_impl(context, username): @@ -355,18 +406,38 @@ def step_impl(context, message): def step_impl(context, message): assert context.get_download_tasks_response == message -@then('The response of "200" and "Logged in Successfully" should be returned') -def step_impl(context): - assert context.page.get_json() == {'message': 'Logged in successfully'} +@then('the response of "{status_code}" and "{message}" should be returned') +def step_impl(context, status_code, message): + expected_status_code = int(status_code) -@then('The response of "401" and "The username you entered cannot be identified." should be returned') -def step_impl(context): - assert context.page.get_json() == {'error': 'The username you entered cannot be identified.'} + # Ensure that context.page contains the response object + assert hasattr(context, 'page'), "context does not have 'page' attribute. Make sure the HTTP request was made." + + # Get the actual status code from the response + actual_status_code = context.page.status_code + assert actual_status_code == expected_status_code, f"Expected status code {expected_status_code}, got {actual_status_code}" + + # Parse the JSON response body + response_json = context.page.get_json() -@then('The response of "401" and "The password you entered was incorrect." should be returned') + # Check the message or error based on the status code + if expected_status_code == 200: + assert response_json.get('message') == message, f"Expected message '{message}', got '{response_json.get('message')}'" + else: + assert response_json.get('error') == message, f"Expected error message '{message}', got '{response_json.get('error')}'" + +@then('cookies should be set') def step_impl(context): - assert context.page.get_json() == {'error': 'The password you entered was incorrect.'} + # Ensure that context.page contains the response object + assert hasattr(context, 'page'), "context does not have 'page' attribute. Make sure the HTTP request was made." + + # Check if the 'user_session' cookie is set in the response + cookies = context.page.headers.get('Set-Cookie') + assert cookies is not None, "No cookies were set in the response." + + # Further check for the specific 'user_session' cookie + assert 'user_session' in cookies, "The 'user_session' cookie was not set in the response." -@then('The response of "429" and "Too many requests, please try again later." should be returned') +@then('an internal server error should be raised') def step_impl(context): - assert context.page.get_json() == {'error': 'Too many requests, please try again later.'} \ No newline at end of file + assert context.exception_message == "Internal server error" \ No newline at end of file From 3964ddf9f340f1b31e17a14f2a60f6af1ff47227 Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:07:57 -0500 Subject: [PATCH 6/8] Added test coverage for app login --- backend/app.py | 4 +- backend/features/app_login.feature | 15 +----- backend/features/steps/all_steps.py | 83 ++++++++++++++++++----------- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/backend/app.py b/backend/app.py index be6b41e..dd40be8 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,9 +1,7 @@ from functools import wraps -from json import JSONDecodeError import logging import os import threading -import time from dotenv import load_dotenv from flask import Flask, abort, after_this_request, jsonify, make_response, request, send_from_directory @@ -74,7 +72,7 @@ def index(): def login(): try: data = request.get_json() - except JSONDecodeError: + except Exception as e: return jsonify({'error': 'Invalid JSON format'}), 400 username = data.get('username') diff --git a/backend/features/app_login.feature b/backend/features/app_login.feature index 3857c30..d758b52 100644 --- a/backend/features/app_login.feature +++ b/backend/features/app_login.feature @@ -42,21 +42,10 @@ Feature: Backend Flask App - Login Scenario: Unsuccessful Login - Invalid JSON Format in Request Given the app is running When I pass data in an invalid JSON format to the login endpoint - Then the response of "400" and "Invalid request format" should be returned - - Scenario: Server Error During Login Process - Given the app is running - When I pass valid credentials but the server encounters an internal error during login - Then the response of "500" and the specific error message should be returned + Then the response of "400" and "Invalid JSON format" should be returned Scenario: Already Logged In Given the app is running And the user is already logged in - When I pass valid credentials to the login endpoint again - Then the response of "200" and "Already logged in" should be returned - - Scenario: Session Cookie Attributes Verification - Given the app is running When I pass valid credentials to the login endpoint - Then the response should include a 'Set-Cookie' header with 'user_session' cookie - And the cookie should have 'HttpOnly' and 'Secure' attributes set + Then the response of "200" and "Already logged in" should be returned \ No newline at end of file diff --git a/backend/features/steps/all_steps.py b/backend/features/steps/all_steps.py index 0ac59a8..7497ead 100644 --- a/backend/features/steps/all_steps.py +++ b/backend/features/steps/all_steps.py @@ -15,7 +15,7 @@ load_dotenv() -#* Given steps +# * Given steps @given('a blackboard session manager') def step_impl(context): context.manager = BlackboardSessionManager() @@ -92,11 +92,33 @@ def step_impl(context): username='InvalidUsername', password='InvalidPassword') context.logged_in = context.session.is_logged_in + @given('the app is running') def step_impl(context): assert context.client -#* When steps + +@given('an existing session with ID "{session_id}"') +def step_impl(context, session_id): + new_session = BlackboardSession(session_id, time.time()) + test_username = "test_user_for_" + session_id + context.manager.bb_sessions[session_id] = new_session + context.manager.user_session_map[test_username] = session_id + +@given('the user is already logged in') +def step_impl(context): + login_response = context.client.post('/login', json=dict( + username=os.environ.get('TEST_USERNAME'), + password=os.environ.get('TEST_PASSWORD') + ), headers={'Content-Type': 'application/json'}, follow_redirects=True) + + assert login_response.status_code == 200, "Failed to log in during setup." + response_data = login_response.get_json() + assert response_data.get('message') == 'Logged in successfully', "Login was not successful during setup." + + context.login_response = login_response + +# * When steps @when('I request a session for user "{username}"') def step_impl(context, username): context.session = context.manager.get_bb_session(username) @@ -129,14 +151,6 @@ def step_impl(context): context.manager.clean_up_inactive_sessions() -@given('an existing session with ID "{session_id}"') -def step_impl(context, session_id): - new_session = BlackboardSession(session_id, time.time()) - test_username = "test_user_for_" + session_id - context.manager.bb_sessions[session_id] = new_session - context.manager.user_session_map[test_username] = session_id - - @when('I update the last activity time for "{username}"\'s session') def step_impl(context, username): session = context.manager.retrieve_bb_session_by_username(username) @@ -213,6 +227,7 @@ def step_impl(context): context.session.get_download_tasks() context.get_download_tasks_response = context.session.response + @when('I pass valid credentials to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( @@ -220,7 +235,8 @@ def step_impl(context): password=os.environ.get('TEST_PASSWORD') ), headers={'Content-Type': 'application/json'}, follow_redirects=True) response = context.page.get_json() - assert response['message'] == 'Logged in successfully' + assert 'message' in response + @when('I pass an incorrect username and password to the login endpoint') def step_impl(context): @@ -231,6 +247,7 @@ def step_impl(context): response = context.page.get_json() assert response['error'] == 'The username you entered cannot be identified.' + @when('I pass an incorrect password to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( @@ -240,6 +257,7 @@ def step_impl(context): response = context.page.get_json() assert response['error'] == 'The password you entered was incorrect.' + @when('I pass an incorrect username to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( @@ -249,6 +267,7 @@ def step_impl(context): response = context.page.get_json() assert response['error'] == 'The username you entered cannot be identified.' + @when('I pass only a username to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( @@ -258,6 +277,7 @@ def step_impl(context): response = context.page.get_json() assert response['error'] == 'Missing username or password' + @when('I pass only a password to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( @@ -267,6 +287,7 @@ def step_impl(context): response = context.page.get_json() assert response['error'] == 'Missing username or password' + @when('I pass no credentials to the login endpoint') def step_impl(context): context.page = context.client.post('/login', json=dict( @@ -276,12 +297,14 @@ def step_impl(context): response = context.page.get_json() assert response['error'] == 'Missing username or password' + @when('I pass data in an invalid JSON format to the login endpoint') def step_impl(context): invalid_json_data = 'Invalid JSON format' - context.page = context.client.post('/login', data=invalid_json_data, headers={'Content-Type': 'application/json'}, follow_redirects=True) + context.page = context.client.post('/login', data=invalid_json_data, headers={ + 'Content-Type': 'application/json'}, follow_redirects=True) response = context.page.get_json() - assert response['error'] == 'Invalid JSON payload received.' + assert response['error'] == 'Invalid JSON format' @when('I pass valid credentials but the server encounters an internal error when logging in') @@ -292,15 +315,15 @@ def step_impl(context): context.session.login() except Exception as e: context.exception_message = str(e) - + + @when('the user is already logged in') def step_impl(context): - # Assuming 'context.session' is an instance of BlackboardSession - context.session.login() # First login - context.second_login_response = context.session.login() # Second login attempt + context.session.login() + context.second_login_response = context.session.login() -#* Then steps +# * Then steps @then('a new session should be created for "{username}"') def step_impl(context, username): assertpy.assert_that(context.session).is_not_none() @@ -406,38 +429,38 @@ def step_impl(context, message): def step_impl(context, message): assert context.get_download_tasks_response == message + @then('the response of "{status_code}" and "{message}" should be returned') def step_impl(context, status_code, message): expected_status_code = int(status_code) - # Ensure that context.page contains the response object - assert hasattr(context, 'page'), "context does not have 'page' attribute. Make sure the HTTP request was made." + assert hasattr( + context, 'page'), "context does not have 'page' attribute. Make sure the HTTP request was made." - # Get the actual status code from the response actual_status_code = context.page.status_code assert actual_status_code == expected_status_code, f"Expected status code {expected_status_code}, got {actual_status_code}" - # Parse the JSON response body response_json = context.page.get_json() - # Check the message or error based on the status code if expected_status_code == 200: - assert response_json.get('message') == message, f"Expected message '{message}', got '{response_json.get('message')}'" + assert response_json.get( + 'message') == message, f"Expected message '{message}', got '{response_json.get('message')}'" else: - assert response_json.get('error') == message, f"Expected error message '{message}', got '{response_json.get('error')}'" + assert response_json.get( + 'error') == message, f"Expected error message '{message}', got '{response_json.get('error')}'" + @then('cookies should be set') def step_impl(context): - # Ensure that context.page contains the response object - assert hasattr(context, 'page'), "context does not have 'page' attribute. Make sure the HTTP request was made." + assert hasattr( + context, 'page'), "context does not have 'page' attribute. Make sure the HTTP request was made." - # Check if the 'user_session' cookie is set in the response cookies = context.page.headers.get('Set-Cookie') assert cookies is not None, "No cookies were set in the response." - # Further check for the specific 'user_session' cookie assert 'user_session' in cookies, "The 'user_session' cookie was not set in the response." + @then('an internal server error should be raised') def step_impl(context): - assert context.exception_message == "Internal server error" \ No newline at end of file + assert context.exception_message == "Internal server error" From b962ef4b69d3c65dbc2348932640589315625355 Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:58:04 -0500 Subject: [PATCH 7/8] Fixed Logout Logout wasn't correctly deleting blackboard sessions --- backend/app.py | 2 +- backend/blackboard_session_manager.py | 13 ++++++++++++- backend/features/environment.py | 5 ++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/app.py b/backend/app.py index dd40be8..cfb4b2a 100644 --- a/backend/app.py +++ b/backend/app.py @@ -110,7 +110,7 @@ def logout(): user_session = request.cookies.get('user_session') if user_session: # Remove the session from BlackboardSessionManager - bb_session_manager.delete_bb_session(user_session) + bb_session_manager.delete_bb_session_by_id(user_session) # Clear the user's session cookie resp = make_response(jsonify({'message': 'Logged out successfully'})) diff --git a/backend/blackboard_session_manager.py b/backend/blackboard_session_manager.py index d2677c6..fc77953 100644 --- a/backend/blackboard_session_manager.py +++ b/backend/blackboard_session_manager.py @@ -48,7 +48,18 @@ def delete_bb_session(self, username): with self.lock: session_id = self.user_session_map.pop(username, None) if session_id: - return self.bb_sessions.pop(session_id, None) + del self.bb_sessions[session_id] + return True + return False + + def delete_bb_session_by_id(self, session_id): + with self.lock: + for user, sid in self.user_session_map.items(): + if sid == session_id: + del self.user_session_map[user] + del self.bb_sessions[session_id] + return True + return False def clean_up_inactive_sessions(self, inactivity_threshold_seconds=3600): with self.lock: diff --git a/backend/features/environment.py b/backend/features/environment.py index 5e408de..170aeca 100644 --- a/backend/features/environment.py +++ b/backend/features/environment.py @@ -1,4 +1,7 @@ from app import app def before_all(context): - context.client = app.test_client() \ No newline at end of file + context.client = app.test_client() + +def after_scenario(context, scenario): + context.client.post('/logout') From 748aec39a4f3f7ee2302ced75dbbb6c01ff85a5a Mon Sep 17 00:00:00 2001 From: Jaydin_MacBook <74679492+TheManWhoLikesToCode@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:19:22 -0500 Subject: [PATCH 8/8] refactored ignore and removed unused files --- .gitignore | 23 +++++++++++++---------- backend/support/.DS_Store | Bin 6148 -> 0 bytes backend/support/GhostScript/README.txt | 1 - 3 files changed, 13 insertions(+), 11 deletions(-) delete mode 100644 backend/support/.DS_Store delete mode 100644 backend/support/GhostScript/README.txt diff --git a/.gitignore b/.gitignore index b9d6852..597e829 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,21 @@ backend/client_secrets.json +backend/.env +backend/credentials.json +backend/support/.DS_Store + client_secrets.json mycreds.txt -backend/.env + frontend/.env +frontend/.DS_Store + *.pyc *.DS_Store -backend/support/.DS_Store + credentials.json -backend/credentials.json + .DS_Store -frontend/.DS_Store -backend/support/.DS_Store -frontend/.DS_Store -frontend/.DS_Store -.DS_Store -frontend/.DS_Store -*.env \ No newline at end of file + +*.env + +.vscode \ No newline at end of file diff --git a/backend/support/.DS_Store b/backend/support/.DS_Store deleted file mode 100644 index 204332ba520b8e3d29128325db3c3723565b1f02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO;6iE5SVovc{b^{ zHjhj>it{oX*n~XI5c1_yUZ!T!GvhKH+T6%Z1dX84Z*5Jd-QE4R?!McZwe|Gk9+Iz>8ym&j$@<3_Vs3?a_h3wgA97+*V-o=OO24kDF{eG7kaE5x^z;$`A~jkWfTU4f!`V6-Vf5^`~T$c{l5*8 zK^PDQ{!a!}{Wv=A;o1D%y6}{I*J|iDC=16`4xdtB*sB<^d=+m&t-!C@0EQkbhwwn; OL%`A?gD~(<8F&QS