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