diff --git a/5005CMD_new/__init__.py b/5005CMD_new/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/5005CMD_new/__pycache__/__init__.cpython-310.pyc b/5005CMD_new/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..9fbd409 Binary files /dev/null and b/5005CMD_new/__pycache__/__init__.cpython-310.pyc differ diff --git a/5005CMD_new/__pycache__/newApp.cpython-310.pyc b/5005CMD_new/__pycache__/newApp.cpython-310.pyc index b67d82c..de59566 100644 Binary files a/5005CMD_new/__pycache__/newApp.cpython-310.pyc and b/5005CMD_new/__pycache__/newApp.cpython-310.pyc differ diff --git a/5005CMD_new/instance/komodo_hub.db b/5005CMD_new/instance/komodo_hub.db index 5b6b85d..78185aa 100644 Binary files a/5005CMD_new/instance/komodo_hub.db and b/5005CMD_new/instance/komodo_hub.db differ diff --git a/5005CMD_new/newApp.py b/5005CMD_new/newApp.py index e66a5de..e14d081 100644 --- a/5005CMD_new/newApp.py +++ b/5005CMD_new/newApp.py @@ -1,3 +1,5 @@ +# * Gavin's Code * + # Import the required modules for functionality from flask import Flask, render_template, request, redirect, url_for, flash, abort, jsonify from flask_sqlalchemy import SQLAlchemy @@ -185,11 +187,12 @@ def send_message(): recipientID = request.form.get('recipientID') # Retrieve the 'recipientID' from form and it should correspond to a valid user in the system that is logged in content = request.form.get('content').strip() # Remove any leading/trailing whitespace + print(f"➡️ Incoming Message Data: recipientID={recipientID}, content={content}") if not recipientID or not content: # These are used to validate input - make sure not empty/ NULL flash("Recipient and content are required.", "danger") # If it proves to be empty, it shows a flash message to the user indicating the error return redirect(url_for('messages')) # Redirect the user back to the messaging dashboard - recipient_user = User.query.get(recipientID) + recipient_user = db.session.get(User, recipientID) if (not recipient_user or recipient_user.id == current_user.id or recipient_user.accountType == "public"): # Make absoloute sure on account type message account restrictions @@ -198,6 +201,7 @@ def send_message(): new_message = Message(senderID=current_user.id, recipientID=recipientID, content=content) # Security check to ensure confidentiality and protection - verify that the sender is a teacher and the recipient is an enrolled student [*mod*] db.session.add(new_message) # Add the new message to the database and add it + print(f"Message saved: {new_message}") db.session.commit() flash("Message sent successfully!", "success") # This is a flash message which indicates the message was sent successfully return redirect(url_for('messages')) # *Redirect* @@ -321,6 +325,5 @@ def add_security_headers(response): # Run Flask if __name__ == '__main__': with app.app_context(): - db.create_all() # Ensures database & tables exist app.run(debug=True) diff --git a/5005CMD_new/testing/__pycache__/test_authentication.cpython-310-pytest-8.3.5.pyc b/5005CMD_new/testing/__pycache__/test_authentication.cpython-310-pytest-8.3.5.pyc new file mode 100644 index 0000000..409fbba Binary files /dev/null and b/5005CMD_new/testing/__pycache__/test_authentication.cpython-310-pytest-8.3.5.pyc differ diff --git a/5005CMD_new/testing/__pycache__/test_msging_feature.cpython-310-pytest-8.3.5.pyc b/5005CMD_new/testing/__pycache__/test_msging_feature.cpython-310-pytest-8.3.5.pyc new file mode 100644 index 0000000..ac65ea7 Binary files /dev/null and b/5005CMD_new/testing/__pycache__/test_msging_feature.cpython-310-pytest-8.3.5.pyc differ diff --git a/5005CMD_new/testing/__pycache__/test_user_interface.cpython-310-pytest-8.3.5.pyc b/5005CMD_new/testing/__pycache__/test_user_interface.cpython-310-pytest-8.3.5.pyc new file mode 100644 index 0000000..ea3212e Binary files /dev/null and b/5005CMD_new/testing/__pycache__/test_user_interface.cpython-310-pytest-8.3.5.pyc differ diff --git a/5005CMD_new/testing/test_authentication.py b/5005CMD_new/testing/test_authentication.py new file mode 100644 index 0000000..7ee9564 --- /dev/null +++ b/5005CMD_new/testing/test_authentication.py @@ -0,0 +1,28 @@ +import pytest +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + "/..")) # Finds 'newApp.py' file with absoloute path +from newApp import app, db +from newApp import User # Import the User model for testing +from werkzeug.security import check_password_hash # Security test + +@pytest.fixture +def client(): + app.config['TESTING'] = True # Emulates a test client anf creates an in-memory database for testing + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # Here is a temporary database for tests + client = app.test_client() + with app.app_context(): + db.create_all() # Create tables in test database + yield client + db.session.remove() + db.drop_all() # Clean up after tests + +def test_registration(client): + inputVal = client.post('/register', data={ # Test the user's registration + 'username': 'testuser', # Inpute details[...] + 'password': 'Test1234', + 'accountType': 'student'}, + follow_redirects=True) + user = User.query.filter_by(username="testuser").first() + assert user is not None # Ensures the user is created + assert check_password_hash(user.password, "Test1234") # Verify password hashing for utmost security implementation diff --git a/5005CMD_new/testing/test_msging_feature.py b/5005CMD_new/testing/test_msging_feature.py new file mode 100644 index 0000000..e0a6853 --- /dev/null +++ b/5005CMD_new/testing/test_msging_feature.py @@ -0,0 +1,53 @@ +from newApp import app, db, Message, User +import pytest +from werkzeug.security import generate_password_hash # Import password hashing utility as this prevents test case pass (debug) + +@pytest.fixture +def client(): + app.config['TESTING'] = True # Setting uo a test client with a standalone database + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:' # Again use an in-memory database for testing + client = app.test_client() + with app.app_context(): + db.create_all() + yield client + db.session.remove() + db.drop_all() + +def test_send_message(client): + """ Test sending a message and ensuring it appears in recipient's inbox """ + with app.app_context(): + sender = User(username="sender", password=generate_password_hash("TestPassw0rd1234"), accountType="teacher") # User with hashed pass + recipient = User(username="recipient", password=generate_password_hash("TestPassw0rd1234"), accountType="student") # Second user with hashed pass + db.session.add(sender) + db.session.add(recipient) + db.session.flush() # Ensure the associated IDs are assigned before commited + db.session.commit() + print(f"Sender ID: {sender.id}, Recipient ID: {recipient.id}") + + loginVal = client.post('/login', data={ # Logs in the user before sending a message + 'username': sender.username, + 'password': "TestPassw0rd1234", # Plain text because hashing happens inside the login function + 'accountType': sender.accountType}, + follow_redirects=True) + print(f"Login Validation Status: {loginVal.status_code}") # Check 1 + print(f"Login Validation Data: {loginVal.data.decode()}") # Check 2 + assert loginVal.status_code == 200, "Login failed" # Status repr if login fails + with client.session_transaction() as session: # Simulates session as if user is loggedd in + session["_user_id"] = sender.id # Simulating Flask-Login session + session["_fresh"] = True # Marks session as fresh + + response = client.post('/messages/send', data={ # Sends a message once a session has started + 'recipientID': str(recipient.id), # Convert to a string + 'content': "This is a test message."}, + follow_redirects=True) + print(f"Message Send Response Status: {response.status_code}") + print(f"Message Send Response Data: {response.data.decode()}") + + db.session.flush() # Here I check the database + db.session.commit() # ^ + all_messages = Message.query.all() + print(f"All Messages in database: {all_messages}") + message = Message.query.filter_by(recipientID=recipient.id).first() + print(f"Retrieved Message: {message}") # Success + assert message is not None, "Message was not stored in database" # Assertions + assert message.content == "This is a test message.", "Message content not matched" # Check against my input test diff --git a/5005CMD_new/testing/test_user_interface.py b/5005CMD_new/testing/test_user_interface.py new file mode 100644 index 0000000..ff438ef --- /dev/null +++ b/5005CMD_new/testing/test_user_interface.py @@ -0,0 +1,30 @@ +from selenium import webdriver +import time + +def test_home_page_load(): + + driver = webdriver.Chrome() # Makes sure webdriver is installed - and the purpose of this is to ensure home page loads correctly + driver.get("http://127.0.0.1:5000") # Open the app URL associated with our system + time.sleep(2.5) # Allows the entire page to load + assert "Komodo Hub" in driver.title # Validates the page title + driver.quit() # Closes the browser + +def test_login_redirect(): + # Tests the login redirect directly + driver = webdriver.Chrome() + driver.get("http://127.0.0.1:5000") # URL page + loginRedirectButton = driver.find_element("link text", "Login") # Finds the UI with the login button + loginRedirectButton.click() + time.sleep(2) # Allows the page to load + assert "Login" in driver.title # Ensures the login page loads + driver.quit() # Exit to move on + +def test_register_redirect(): + # Tests the register redirect directly + driver = webdriver.Chrome() + driver.get("http://127.0.0.1:5000") # Opens the home page first + registerRedirectButton = driver.find_element("link text", "Register") # Finds the UI with the register/signup button + registerRedirectButton.click() # Clicks the button + time.sleep(2.5) # Allows 2 seconds for the page to load + assert "Signup" in driver.title, f"Expected 'Signup' in page title, but got '{driver.title}'" + driver.quit() # Close the browser after test has been completed diff --git a/5005CMD_new/testing/testing_access_control.py b/5005CMD_new/testing/testing_access_control.py new file mode 100644 index 0000000..645b6ce --- /dev/null +++ b/5005CMD_new/testing/testing_access_control.py @@ -0,0 +1,35 @@ +from newApp import app, db, User # Import necessary modules from the main application file +import pytest + +@pytest.fixture +def client(): + + app.config['TESTING'] = True # Enable 'Flask' testing mode + client = app.test_client() # Create a test client + with app.app_context(): + db.create_all() # Create respective tables in the in-memory database + yield client + db.session.remove() # Clears the database session after each test is done + db.drop_all() # This is done to clean the state for each test after preceding - *remember to remove after in all files * + +def test_student_restrictions(client): + + student = User(username="student_user", # Create respective info regarding student user account + password="TestPass123", + accountType="student") + db.session.add(student) # Adds the student to the database + db.session.commit() + response = client.get('/teacher_dashboard', follow_redirects=True) # Attempt to access teacher dashboard + assert response.status_code == 403 # Respective response for error code + +def test_teacher_access(client): + + teacher = User(username="teacher_user", password="TestPass123", accountType="teacher") # Create a teacher user + db.session.add(teacher) # Add the teacher data to the database + db.session.commit() # Commits the changes + response = client.get('/teacher_dashboard', follow_redirects=True) # Attempts to access teacher dashboard + assert response.status_code == 200 # Successful access code + +"""" A public user test was not done as there many features not yet implemented so seemed pointless yet + However, this was done to ensure each account type remained standalone and ensures public user account types + will stay secure too considering the security enforecments we have taken """ \ No newline at end of file