diff --git a/.gitignore b/.gitignore index cfa62a9..ef4d255 100644 --- a/.gitignore +++ b/.gitignore @@ -153,4 +153,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -node_modules/ \ No newline at end of file +node_modules/ +instance_tracker.txt \ No newline at end of file diff --git a/App/controllers/__init__.py b/App/controllers/__init__.py index 98fb3b9..e75ec60 100644 --- a/App/controllers/__init__.py +++ b/App/controllers/__init__.py @@ -1,3 +1,12 @@ from .user import * from .auth import * from .initialize import * +from .lot import * +from .admin import * +from .student import * +from .group import * +from .studentGroup import * +from .lotGroup import * +from .rfp import * +from .bid import * +from .evaluation import * \ No newline at end of file diff --git a/App/controllers/admin.py b/App/controllers/admin.py new file mode 100644 index 0000000..d6ef3b2 --- /dev/null +++ b/App/controllers/admin.py @@ -0,0 +1,20 @@ +from App.models import Admin +from App.database import db + +def create_admin(username, password): + newadmin = Admin(username=username, password=password) + db.session.add(newadmin) + db.session.commit() + +def get_admin(id): + return db.session.get(Admin, id) + +def get_all_admins(): + return db.session.scalars(db.select(Admin)).all() + +def get_all_admins_json(): + admins = get_all_admins() + if not admins: + return [] + admins = [admin.get_json() for admin in admins] + return admins \ No newline at end of file diff --git a/App/controllers/bid.py b/App/controllers/bid.py new file mode 100644 index 0000000..670b662 --- /dev/null +++ b/App/controllers/bid.py @@ -0,0 +1,29 @@ +from App.models import Bid +from App.database import db + +def create_bid(lotID, sourceGroupID, receipientGroupID, bidDocumentLink): + newbid = Bid(lotID, sourceGroupID, receipientGroupID, bidDocumentLink) + db.session.add(newbid) + db.session.commit() + return newbid + +def get_bid(id): + return db.session.get(Bid, id) + +def get_all_bids(): + return db.session.scalars(db.select(Bid)).all() + +def get_all_bids_json(): + bids = get_all_bids() + if not bids: + return [] + bids = [bid.get_json() for bid in bids] + return bids + +def remove_bid(id): + bid = get_bid(id) + if bid: + db.session.delete(bid) + db.session.commit() + return True + return False \ No newline at end of file diff --git a/App/controllers/evaluation.py b/App/controllers/evaluation.py new file mode 100644 index 0000000..3726519 --- /dev/null +++ b/App/controllers/evaluation.py @@ -0,0 +1,64 @@ +from App.models import Evaluation +from App.database import db + +def create_evaluation(sourceGroupID, receipientGroupID, bidID, lotID, specsMet, presentation, professionalism, budget): + neweval = Evaluation(sourceGroupID, receipientGroupID, bidID, lotID, specsMet, presentation, professionalism, budget) + db.session.add(neweval) + db.session.commit() + return neweval + +def edit_evaluation(id, specsMet, presentation, professionalism, budget, deviceType=None, resolution=None, os=None, cpu=None, ram=None, drive=None, gpu=None, peripherals=None, features=None, io=None): + eva = get_evaluation(id) + if eva: + eva.overallScore = round((((specsMet + presentation + professionalism + budget)/25) * 10), 1) + + if deviceType: + eva.deviceType = deviceType + if resolution: + eva.resolution = resolution + if os: + eva.os = os + if cpu: + eva.cpu = cpu + if ram: + eva.ram = ram + if drive: + eva.drive = drive + if gpu: + eva.gpu = gpu + if peripherals: + eva.peripherals = peripherals + if features: + eva.features = features + if io: + eva.io = io + + db.session.commit() + return True + return False + +def select_evaluation(id): + evaluation = get_evaluation(id) + evaluation.status = "selected" + db.session.commit() + +def get_evaluation(id): + return db.session.get(Evaluation, id) + +def get_all_evaluations(): + return db.session.scalars(db.select(Evaluation)).all() + +def get_all_evaluations_json(): + evals = get_all_evaluations() + if not evals: + return [] + evals = [eva.get_json() for eva in evals] + return evals + +def remove_evaluation(id): + eva = get_evaluation(id) + if eva: + db.session.delete(eva) + db.session.commit() + return True + return False \ No newline at end of file diff --git a/App/controllers/group.py b/App/controllers/group.py new file mode 100644 index 0000000..16e8bc5 --- /dev/null +++ b/App/controllers/group.py @@ -0,0 +1,36 @@ +from App.models import Group +from App.database import db + +def create_group(groupName): + group = Group(groupName) + db.session.add(group) + db.session.flush() + group.set_generated_name() + db.session.commit() + return group + +def approve_group(id): + group = get_group(id) + group.status = "approved" + db.session.commit() + +def get_group(id): + return db.session.get(Group, id) + +def get_all_groups(): + return db.session.scalars(db.select(Group)).all() + +def get_all_groups_json(): + groups = get_all_groups() + if not groups: + return [] + groups = [group.get_json() for group in groups] + return groups + +def remove_group(id): + group = get_group(id) + if group: + db.session.delete(group) + db.session.commit() + return True + return False \ No newline at end of file diff --git a/App/controllers/lot.py b/App/controllers/lot.py new file mode 100644 index 0000000..b182e18 --- /dev/null +++ b/App/controllers/lot.py @@ -0,0 +1,77 @@ +from App.models import Lot +from App.database import db +from sqlalchemy.orm.attributes import flag_modified + +def create_lot(labType, labSize, budget): + newlot = Lot(labType, labSize, budget) + db.session.add(newlot) + db.session.flush() + newlot.set_generated_name() + db.session.commit() + +def get_lot(id): + return db.session.get(Lot, id) + +def get_all_lots(): + return db.session.scalars(db.select(Lot)).all() + +def get_all_lots_json(): + lots = get_all_lots() + if not lots: + return [] + lots = [lot.get_json() for lot in lots] + return lots + +def edit_lot(id, labType=None, labSize=None, budget=None): + lot = get_lot(id) + if lot: + if labType: + lot.labType = labType + + if labSize: + lot.labSize = labSize + + if budget: + lot.budget = budget + + db.session.commit() + +def edit_lotRFP_details(id, deviceType=None, resolution=None, os=None, cpu=None, ram=None, drive=None, gpu=None, peripherals=None, features=None, io=None): + lot = get_lot(id) + if lot: + if deviceType: + lot.deviceType = deviceType + if resolution: + lot.resolution = resolution + if os: + lot.os = os + if cpu: + lot.cpu = cpu + if ram: + lot.ram = ram + if drive: + lot.drive = drive + if gpu: + lot.gpu = gpu + if peripherals: + lot.peripherals = peripherals + if features: + lot.features = features + if io: + lot.io = io + + db.session.commit() + return lot + return None + +def get_lotRFP_details_json(id): + lot = get_lot(id) + if lot: + return lot.specs + return None + +def remove_lot(id): + lot = get_lot(id) + if lot: + db.session.delete(lot) + db.session.commit() \ No newline at end of file diff --git a/App/controllers/lotGroup.py b/App/controllers/lotGroup.py new file mode 100644 index 0000000..c506ef9 --- /dev/null +++ b/App/controllers/lotGroup.py @@ -0,0 +1,28 @@ +from App.models import LotGroup +from App.database import db + +def add_lotGroup(lotID, groupID): + newentry = LotGroup(lotID, groupID) + db.session.add(newentry) + db.session.commit() + +def get_lotGroup(lotID, groupID): + return db.session.get(LotGroup, (lotID, groupID)) + +def get_all_lotGroups(): + return db.session.scalars(db.select(LotGroup)).all() + +def get_all_lotGroups_json(): + entries = get_all_lotGroups() + if not entries: + return [] + entries = [entry.get_json() for entry in entries] + return entries + +def remove_lotGroup(lotID, groupID): + entry = get_lotGroup(lotID, groupID) + if entry: + db.session.delete(entry) + db.session.commit() + return True + return False \ No newline at end of file diff --git a/App/controllers/rfp.py b/App/controllers/rfp.py new file mode 100644 index 0000000..952109d --- /dev/null +++ b/App/controllers/rfp.py @@ -0,0 +1,48 @@ +from App.models import RFP +from App.database import db +from .lot import get_lot + +def create_rfp(groupID, lotID): + rfp = RFP(groupID, lotID) + lot = get_lot(lotID) + + rfp.deviceType = lot.deviceType + rfp.resolution = lot.resolution + rfp.os = lot.os + rfp.cpu = lot.cpu + rfp.ram = lot.ram + rfp.drive = lot.drive + rfp.gpu = lot.gpu + rfp.peripherals = lot.peripherals + rfp.features = lot.features + rfp.io = lot.io + + db.session.add(rfp) + db.session.commit() + return rfp + +def approve_rfp(groupID, lotID): + rfp = get_rfp(groupID, lotID) + rfp.status = "approved" + db.session.commit() + +def get_rfp(groupID, lotID): + return db.session.get(RFP, (groupID, lotID)) + +def get_all_rfps(): + return db.session.scalars(db.select(RFP)).all() + +def get_all_rfps_json(): + rfps = get_all_rfps() + if not rfps: + return [] + rfps = [rfp.get_json() for rfp in rfps] + return rfps + +def remove_rfp(groupID, lotID): + rfp = get_rfp(groupID, lotID) + if rfp: + db.session.delete(rfp) + db.session.commit() + return True + return False \ No newline at end of file diff --git a/App/controllers/student.py b/App/controllers/student.py new file mode 100644 index 0000000..e99dc9c --- /dev/null +++ b/App/controllers/student.py @@ -0,0 +1,20 @@ +from App.models import Student +from App.database import db + +def create_student(username, password): + newstudent = Student(username=username, password=password) + db.session.add(newstudent) + db.session.commit() + +def get_student(id): + return db.session.get(Student, id) + +def get_all_students(): + return db.session.scalars(db.select(Student)).all() + +def get_all_students_json(): + students = get_all_students() + if not students: + return [] + students = [student.get_json() for student in students] + return students \ No newline at end of file diff --git a/App/controllers/studentGroup.py b/App/controllers/studentGroup.py new file mode 100644 index 0000000..f75a060 --- /dev/null +++ b/App/controllers/studentGroup.py @@ -0,0 +1,28 @@ +from App.models import StudentGroup +from App.database import db + +def add_studentGroup(studentID, groupID): + newentry = StudentGroup(studentID, groupID) + db.session.add(newentry) + db.session.commit() + +def get_studentGroup(studentID, groupID): + return db.session.get(StudentGroup, (studentID, groupID)) + +def get_all_studentGroups(): + return db.session.scalars(db.select(StudentGroup)).all() + +def get_all_studentGroups_json(): + entries = get_all_studentGroups() + if not entries: + return [] + entries = [entry.get_json() for entry in entries] + return entries + +def remove_studentGroup(studentID, groupID): + entry = get_studentGroup(studentID, groupID) + if entry: + db.session.delete(entry) + db.session.commit() + return True + return False \ No newline at end of file diff --git a/App/models/RFP.py b/App/models/RFP.py new file mode 100644 index 0000000..d150ca8 --- /dev/null +++ b/App/models/RFP.py @@ -0,0 +1,37 @@ +from App.database import db + +class RFP(db.Model): + groupID = db.Column(db.Integer, db.ForeignKey('group.id'), primary_key=True) + lotID = db.Column(db.Integer, db.ForeignKey('lot.id'), primary_key=True) + status = db.Column(db.String(30), default="requested") + deviceType = db.Column(db.String(1000), default="") + resolution = db.Column(db.String(1000), default="") + os = db.Column(db.String(1000), default="") + cpu = db.Column(db.String(1000), default="") + ram = db.Column(db.String(1000), default="") + drive = db.Column(db.String(1000), default="") + gpu = db.Column(db.String(1000), default="") + peripherals = db.Column(db.String(1000), default="") + features = db.Column(db.String(1000), default="") + io = db.Column(db.String(1000), default="") + + def __init__(self, groupID, lotID): + self.groupID = groupID + self.lotID = lotID + + def get_json(self): + return { + 'groupID': self.groupID, + 'lotID': self.lotID, + 'status': self.status, + 'Type': self.deviceType, + 'Screen Size & Resolution': self.resolution, + 'Operating System(s)': self.os, + 'CPU': self.cpu, + 'Memory (RAM)': self.ram, + 'Hard Drive': self.drive, + 'Graphics': self.gpu, + 'External Peripherals': self.peripherals, + 'Features': self.features, + 'I/O': self.io + } \ No newline at end of file diff --git a/App/models/__init__.py b/App/models/__init__.py index 82da278..12a5df4 100644 --- a/App/models/__init__.py +++ b/App/models/__init__.py @@ -1 +1,10 @@ -from .user import * \ No newline at end of file +from .user import * +from .admin import * +from .student import * +from .lot import * +from .group import * +from .lotGroup import * +from .studentGroup import * +from .RFP import * +from .bid import * +from .evaluation import * \ No newline at end of file diff --git a/App/models/admin.py b/App/models/admin.py new file mode 100644 index 0000000..2e7777a --- /dev/null +++ b/App/models/admin.py @@ -0,0 +1,21 @@ +from App.database import db +from .user import User + +class Admin(User): + id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) + + __mapper_args__ = { + 'polymorphic_identity': 'admin' + } + + def __init__(self, username, password): + super().__init__(username, password) + + def is_admin(self): + return True + + def is_student(self): + return False + + def get_json(self): + return {**super().get_json(), 'role': 'admin'} \ No newline at end of file diff --git a/App/models/bid.py b/App/models/bid.py new file mode 100644 index 0000000..624831c --- /dev/null +++ b/App/models/bid.py @@ -0,0 +1,27 @@ +from App.database import db +from datetime import datetime + +class Bid(db.Model): + id = db.Column(db.Integer, primary_key=True) + lotID = db.Column(db.Integer, db.ForeignKey('lot.id'), nullable=False) + sourceGroupID = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) + receipientGroupID = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) + timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + bidDocumentLink = db.Column(db.String(1000), nullable=False) + + def __init__(self, lotID, sourceGroupID, receipientGroupID, bidDocumentLink): + self.lotID = lotID + self.sourceGroupID = sourceGroupID + self.receipientGroupID = receipientGroupID + self.bidDocumentLink = bidDocumentLink + self.timestamp = datetime.utcnow() + + def get_json(self): + return { + 'id': self.id, + 'lotID': self.lotID, + 'sourceGroupID': self.sourceGroupID, + 'receipientGroupID': self.receipientGroupID, + 'timestamp': self.timestamp.isoformat(), + 'bidDocumentLink': self.bidDocumentLink + } \ No newline at end of file diff --git a/App/models/evaluation.py b/App/models/evaluation.py new file mode 100644 index 0000000..501ab69 --- /dev/null +++ b/App/models/evaluation.py @@ -0,0 +1,37 @@ +from App.database import db + +class Evaluation(db.Model): + id = db.Column(db.Integer, primary_key=True) + sourceGroupID = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) + receipientGroupID = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=False) + bidID = db.Column(db.Integer, db.ForeignKey('bid.id'), nullable=False) + lotID = db.Column(db.Integer, db.ForeignKey('lot.id'), nullable=False) + status = db.Column(db.String(30), default="draft") + overallScore = db.Column(db.Float, default=0.0) + deviceType = db.Column(db.String(1000), default="") + resolution = db.Column(db.String(1000), default="") + os = db.Column(db.String(1000), default="") + cpu = db.Column(db.String(1000), default="") + ram = db.Column(db.String(1000), default="") + drive = db.Column(db.String(1000), default="") + gpu = db.Column(db.String(1000), default="") + peripherals = db.Column(db.String(1000), default="") + features = db.Column(db.String(1000), default="") + io = db.Column(db.String(1000), default="") + + def __init__(self, sourceGroupID, receipientGroupID, bidID, lotID, specsMet, presentation, professionalism, budget): + self.bidID = bidID + self.lotID = lotID + self.sourceGroupID = sourceGroupID + self.receipientGroupID = receipientGroupID + self.overallScore = round((((specsMet + presentation + professionalism + budget)/25) * 10), 1) + + def get_json(self): + return { + 'id': self.id, + 'sourceGroupID': self.sourceGroupID, + 'receipientGroupID': self.receipientGroupID, + 'bidID': self.bidID, + 'lotID': self.lotID, + 'overallScore': self.overallScore + } \ No newline at end of file diff --git a/App/models/group.py b/App/models/group.py new file mode 100644 index 0000000..4061b85 --- /dev/null +++ b/App/models/group.py @@ -0,0 +1,19 @@ +from App.database import db + +class Group(db.Model): + id = db.Column(db.Integer, primary_key=True) + groupName = db.Column(db.String(30), nullable=False, unique=True) + status = db.Column(db.String(30), nullable=False, default="requested") + + def __init__(self, groupName): + self.groupName = groupName + + def set_generated_name(self): + self.groupName = f"G{self.id} {self.groupName}" + + def get_json(self): + return { + 'id': self.id, + 'groupName': self.groupName, + 'status': self.status + } \ No newline at end of file diff --git a/App/models/lot.py b/App/models/lot.py new file mode 100644 index 0000000..5f56dd1 --- /dev/null +++ b/App/models/lot.py @@ -0,0 +1,35 @@ +from App.database import db + +class Lot(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(10)) + labType = db.Column(db.String(30), nullable=False) + labSize = db.Column(db.Integer, nullable=False) + budget = db.Column(db.Float, nullable=False) + deviceType = db.Column(db.String(1000), default="") + resolution = db.Column(db.String(1000), default="") + os = db.Column(db.String(1000), default="") + cpu = db.Column(db.String(1000), default="") + ram = db.Column(db.String(1000), default="") + drive = db.Column(db.String(1000), default="") + gpu = db.Column(db.String(1000), default="") + peripherals = db.Column(db.String(1000), default="") + features = db.Column(db.String(1000), default="") + io = db.Column(db.String(1000), default="") + + def __init__(self, labType, labSize, budget): + self.labType = labType + self.labSize = labSize + self.budget = budget + + def set_generated_name(self): + self.name = f"Lot {self.id}" + + def get_json(self): + return { + 'id': self.id, + 'name': self.name, + 'labType': self.labType, + 'labSize': self.labSize, + 'budget': self.budget + } \ No newline at end of file diff --git a/App/models/lotGroup.py b/App/models/lotGroup.py new file mode 100644 index 0000000..85cf21a --- /dev/null +++ b/App/models/lotGroup.py @@ -0,0 +1,15 @@ +from App.database import db + +class LotGroup(db.Model): + lotID = db.Column(db.Integer, db.ForeignKey('lot.id'), primary_key=True) + groupID = db.Column(db.Integer, db.ForeignKey('group.id'), primary_key=True) + + def __init__(self, lotID, groupID): + self.lotID = lotID + self.groupID = groupID + + def get_json(self): + return { + 'lotID': self.lotID, + 'groupID': self.groupID + } \ No newline at end of file diff --git a/App/models/student.py b/App/models/student.py new file mode 100644 index 0000000..1c7d8dd --- /dev/null +++ b/App/models/student.py @@ -0,0 +1,21 @@ +from App.database import db +from .user import User + +class Student(User): + id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) + + __mapper_args__ = { + 'polymorphic_identity': 'student' + } + + def __init__(self, username, password): + super().__init__(username, password) + + def is_admin(self): + return False + + def is_student(self): + return True + + def get_json(self): + return {**super().get_json(), 'role': 'student'} \ No newline at end of file diff --git a/App/models/studentGroup.py b/App/models/studentGroup.py new file mode 100644 index 0000000..47493a7 --- /dev/null +++ b/App/models/studentGroup.py @@ -0,0 +1,15 @@ +from App.database import db + +class StudentGroup(db.Model): + studentID = db.Column(db.Integer, db.ForeignKey('student.id'), primary_key=True) + groupID = db.Column(db.Integer, db.ForeignKey('group.id'), primary_key=True) + + def __init__(self, studentID, groupID): + self.studentID = studentID + self.groupID = groupID + + def get_json(self): + return { + 'studentID': self.studentID, + 'groupID': self.groupID + } \ No newline at end of file diff --git a/App/models/user.py b/App/models/user.py index 9ed25ad..31cdc8f 100644 --- a/App/models/user.py +++ b/App/models/user.py @@ -5,6 +5,12 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), nullable=False, unique=True) password = db.Column(db.String(256), nullable=False) + type = db.Column(db.String(50)) + + __mapper_args__ = { + 'polymorphic_on': type, + 'polymorphic_identity': 'user' + } def __init__(self, username, password): self.username = username diff --git a/App/tests/__init__.py b/App/tests/__init__.py index f5c872f..8e14424 100644 --- a/App/tests/__init__.py +++ b/App/tests/__init__.py @@ -1 +1,2 @@ -from .test_app import * \ No newline at end of file +from .tests_1_unit_test import * +from .tests_2_integration_test import * \ No newline at end of file diff --git a/App/tests/test_app.py b/App/tests/test_app.py deleted file mode 100644 index cca1277..0000000 --- a/App/tests/test_app.py +++ /dev/null @@ -1,79 +0,0 @@ -import os, tempfile, pytest, logging, unittest -from werkzeug.security import check_password_hash, generate_password_hash - -from App.main import create_app -from App.database import db, create_db -from App.models import User -from App.controllers import ( - create_user, - get_all_users_json, - login, - get_user, - get_user_by_username, - update_user -) - - -LOGGER = logging.getLogger(__name__) - -''' - Unit Tests -''' -class UserUnitTests(unittest.TestCase): - - def test_new_user(self): - user = User("bob", "bobpass") - assert user.username == "bob" - - # pure function no side effects or integrations called - def test_get_json(self): - user = User("bob", "bobpass") - user_json = user.get_json() - self.assertDictEqual(user_json, {"id":None, "username":"bob"}) - - def test_hashed_password(self): - password = "mypass" - hashed = generate_password_hash(password) - user = User("bob", password) - assert user.password != password - - def test_check_password(self): - password = "mypass" - user = User("bob", password) - assert user.check_password(password) - -''' - Integration Tests -''' - -# This fixture creates an empty database for the test and deletes it after the test -# scope="class" would execute the fixture once and resued for all methods in the class -@pytest.fixture(autouse=True, scope="module") -def empty_db(): - app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) - create_db() - yield app.test_client() - db.drop_all() - - -def test_authenticate(): - user = create_user("bob", "bobpass") - assert login("bob", "bobpass") != None - -class UsersIntegrationTests(unittest.TestCase): - - def test_create_user(self): - user = create_user("rick", "bobpass") - assert user.username == "rick" - - def test_get_all_users_json(self): - users_json = get_all_users_json() - self.assertListEqual([{"id":1, "username":"bob"}, {"id":2, "username":"rick"}], users_json) - - # Tests data changes in the database - def test_update_user(self): - update_user(1, "ronnie") - user = get_user(1) - assert user.username == "ronnie" - - diff --git a/App/tests/tests_1_unit_test.py b/App/tests/tests_1_unit_test.py new file mode 100644 index 0000000..4afd474 --- /dev/null +++ b/App/tests/tests_1_unit_test.py @@ -0,0 +1,254 @@ +import pytest +import unittest +from datetime import datetime +from werkzeug.security import generate_password_hash + +from App.main import create_app +from App.database import db, create_db +from App.models import User, Admin, Student, Group, StudentGroup, Lot, LotGroup, Bid, Evaluation, RFP + + +class AdminUnitTests(unittest.TestCase): + @pytest.mark.run(order=1) + def test_new_admin(self): + admin = Admin("alice", "alicepass") + assert admin.username == "alice" + + @pytest.mark.run(order=2) + def test_admin_get_json(self): + admin = Admin("alice", "alicepass") + admin_json = admin.get_json() + assert admin_json["username"] == "alice" + assert admin_json["role"] == "admin" + + @pytest.mark.run(order=3) + def test_admin_hashed_password(self): + admin = Admin("alice", "alicepass") + assert admin.password != "alicepass" + + @pytest.mark.run(order=4) + def test_admin_check_password(self): + admin = Admin("alice", "alicepass") + assert admin.check_password("alicepass") + + @pytest.mark.run(order=5) + def test_admin_is_admin(self): + admin = Admin("alice", "alicepass") + assert admin.is_admin() + + @pytest.mark.run(order=6) + def test_admin_is_not_student(self): + admin = Admin("alice", "alicepass") + assert not admin.is_student() + + +class StudentUnitTests(unittest.TestCase): + @pytest.mark.run(order=7) + def test_new_student(self): + student = Student("bob", "bobpass") + assert student.username == "bob" + + @pytest.mark.run(order=8) + def test_student_get_json(self): + student = Student("bob", "bobpass") + student_json = student.get_json() + assert student_json["username"] == "bob" + assert student_json["role"] == "student" + + @pytest.mark.run(order=9) + def test_student_hashed_password(self): + student = Student("bob", "bobpass") + assert student.password != "bobpass" + + @pytest.mark.run(order=10) + def test_student_check_password(self): + student = Student("bob", "bobpass") + assert student.check_password("bobpass") + + @pytest.mark.run(order=11) + def test_student_is_student(self): + student = Student("bob", "bobpass") + assert student.is_student() + + @pytest.mark.run(order=12) + def test_student_is_not_admin(self): + student = Student("bob", "bobpass") + assert not student.is_admin() + + +class GroupUnitTests(unittest.TestCase): + @pytest.mark.run(order=13) + def test_new_group(self): + group = Group("Algorithms") + assert group.groupName == "Algorithms" + + @pytest.mark.run(order=14) + def test_group_get_json(self): + group = Group("Algorithms") + group_json = group.get_json() + assert group_json["groupName"] == "Algorithms" + + +class StudentGroupUnitTests(unittest.TestCase): + @pytest.mark.run(order=15) + def test_new_student_group(self): + student_group = StudentGroup(1, 2) + assert student_group.studentID == 1 + assert student_group.groupID == 2 + + @pytest.mark.run(order=16) + def test_student_group_get_json(self): + student_group = StudentGroup(1, 2) + sg_json = student_group.get_json() + self.assertDictEqual(sg_json, {"studentID": 1, "groupID": 2}) + + +class LotUnitTests(unittest.TestCase): + @pytest.mark.run(order=17) + def test_new_lot(self): + lot = Lot("Biology", 30, 5000.00) + assert lot.labType == "Biology" + + @pytest.mark.run(order=18) + def test_lot_lab_size(self): + lot = Lot("Biology", 30, 5000.00) + assert lot.labSize == 30 + + @pytest.mark.run(order=19) + def test_lot_budget(self): + lot = Lot("Biology", 30, 5000.00) + assert lot.budget == 5000.00 + + @pytest.mark.run(order=20) + def test_lot_get_json(self): + lot = Lot("Biology", 30, 5000.00) + lot_json = lot.get_json() + self.assertDictEqual(lot_json, { + "id": None, + "name": None, + "labType": "Biology", + "labSize": 30, + "budget": 5000.00 + }) + + @pytest.mark.run(order=21) + def test_lot_budget_comparison(self): + lot = Lot("Biology", 30, 5000.00) + assert 4999.99 < lot.budget + assert 5000.01 > lot.budget + + +class LotGroupUnitTests(unittest.TestCase): + @pytest.mark.run(order=22) + def test_new_lot_group(self): + lot_group = LotGroup(1, 2) + assert lot_group.lotID == 1 + assert lot_group.groupID == 2 + + @pytest.mark.run(order=23) + def test_lot_group_get_json(self): + lot_group = LotGroup(1, 2) + lg_json = lot_group.get_json() + self.assertDictEqual(lg_json, {"lotID": 1, "groupID": 2}) + + +class RFPUnitTests(unittest.TestCase): + @pytest.mark.run(order=24) + def test_new_RFP(self): + rfp = RFP(1, 10) + assert rfp + assert rfp.groupID == 1 + assert rfp.lotID == 10 + + @pytest.mark.run(order=25) + def test_RFP_get_json(self): + rfp = RFP(1, 10) + assert rfp + self.assertDictEqual({ + 'groupID': 1, + 'lotID': 10, + 'status': None, + 'Type': None, + 'Screen Size & Resolution': None, + 'Operating System(s)': None, + 'CPU': None, + 'Memory (RAM)': None, + 'Hard Drive': None, + 'Graphics': None, + 'External Peripherals': None, + 'Features': None, + 'I/O': None + }, rfp.get_json()) + + +class BidUnitTests(unittest.TestCase): + @pytest.mark.run(order=26) + def test_new_bid(self): + bid = Bid(1, 2, 3, "http://example.com/doc") + assert bid.lotID == 1 + assert bid.sourceGroupID == 2 + assert bid.receipientGroupID == 3 + + @pytest.mark.run(order=27) + def test_bid_document_link(self): + bid = Bid(1, 2, 3, "http://example.com/doc") + assert bid.bidDocumentLink == "http://example.com/doc" + + @pytest.mark.run(order=28) + def test_bid_timestamp_auto_set(self): + bid = Bid(1, 2, 3, "http://example.com/doc") + assert isinstance(bid.timestamp, datetime) + + @pytest.mark.run(order=29) + def test_bid_timestamp_is_recent(self): + bid = Bid(1, 2, 3, "http://example.com/doc") + delta = datetime.utcnow() - bid.timestamp + assert delta.seconds < 5 + + @pytest.mark.run(order=30) + def test_bid_get_json(self): + bid = Bid(1, 2, 3, "http://example.com/doc") + bid_json = bid.get_json() + assert bid_json["lotID"] == 1 + assert bid_json["sourceGroupID"] == 2 + assert bid_json["receipientGroupID"] == 3 + assert bid_json["bidDocumentLink"] == "http://example.com/doc" + assert "timestamp" in bid_json + + +class EvaluationUnitTests(unittest.TestCase): + @pytest.mark.run(order=31) + def test_new_evaluation(self): + sourceGroupID = 1 + receipientGroupID = 2 + bidID = 2 + lotID = 1 + specsMet = 5 + presentation = 3 + professionalism = 2 + budget = 4 + + evaluation = Evaluation(sourceGroupID, receipientGroupID, bidID, lotID, specsMet, presentation, professionalism, budget) + assert evaluation + assert evaluation.overallScore == 5.6 + + @pytest.mark.run(order=32) + def test_evaluation_get_json(self): + sourceGroupID = 1 + receipientGroupID = 2 + bidID = 2 + lotID = 1 + specsMet = 5 + presentation = 3 + professionalism = 2 + budget = 4 + + evaluation = Evaluation(sourceGroupID, receipientGroupID, bidID, lotID, specsMet, presentation, professionalism, budget) + self.assertDictEqual({ + 'id': None, + 'sourceGroupID': 1, + 'receipientGroupID': 2, + 'bidID': 2, + 'lotID': 1, + 'overallScore': 5.6 + }, evaluation.get_json()) \ No newline at end of file diff --git a/App/tests/tests_2_integration_test.py b/App/tests/tests_2_integration_test.py new file mode 100644 index 0000000..069fec1 --- /dev/null +++ b/App/tests/tests_2_integration_test.py @@ -0,0 +1,475 @@ +import pytest +import unittest +from datetime import datetime +from werkzeug.security import generate_password_hash + +from App.main import create_app +from App.database import db, create_db +from App.models import User, Admin, Student, Group, StudentGroup, Lot, LotGroup, Bid, Evaluation, RFP +from App.controllers import * + +@pytest.fixture(autouse=True, scope="module") +def empty_db(): + app = create_app({'TESTING': True, 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'}) + create_db() + yield app.test_client() + db.drop_all() + +@pytest.mark.run(order=33) +def test_authenticate(): + user = create_user("bob", "bobpass") + assert login("bob", "bobpass") != None + +class UsersIntegrationTests(unittest.TestCase): + @pytest.mark.run(order=34) + def test_user_children_are_users(self): + admin = Admin("jack", "jackpass") + db.session.add(admin) + student = Student("cooper", "cooperpass") + db.session.add(student) + db.session.commit() + users_json = get_all_users_json() + self.assertListEqual([{"id":1, "username":"bob"}, {"id":2, "username":"jack", "role":"admin"}, {"id":3, "username":"cooper", "role":"student"}], users_json) + + # Tests data changes in the database + @pytest.mark.run(order=35) + def test_update_user(self): + update_user(1, "ronnie") + user = get_user(1) + assert user.username == "ronnie" + +class Workflow1IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=36) + def test_creating_lots(self): + db.drop_all() + create_db() + create_lot("GIS Lab", 20, 160000.00) + create_lot("Government Office Lab", 12, 110000.00) + lots_json = get_all_lots_json() + self.assertListEqual([ + { + 'id': 1, + 'name': "Lot 1", + 'labType': "GIS Lab", + 'labSize': 20, + 'budget': 160000.00 + }, + { + 'id': 2, + 'name': "Lot 2", + 'labType': "Government Office Lab", + 'labSize': 12, + 'budget': 110000.00 + } + ], lots_json) + + @pytest.mark.run(order=37) + def test_edit_lots(self): + lot = get_lot(1) + assert lot.budget == 160000.00 + edit_lot(1, budget=170000.00) + lot = get_lot(1) + assert lot.budget == 170000.00 + + @pytest.mark.run(order=38) + def test_remove_lot(self): + lot = get_lot(1) + assert lot + remove_lot(1) + lot = get_lot(1) + assert not lot + +class Workflow2IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=39) + def test_create_groupRequest(self): + remove_lot(2) + create_lot("GIS Lab", 20, 160000.00) + create_lot("Government Office Lab", 12, 110000.00) + create_lot("University Computer Lab", 40, 250000.00) + create_lot("Data Center", 1000, 25000000.00) + + create_student("jack", "jackpass") + create_student("cooper", "cooperpass") + create_student("john", "johnpass") + create_student("tony", "tonypass") + + create_student("peper", "peperpass") + create_student("steve", "stevepass") + create_student("clint", "clintpass") + create_student("bruce", "brucepass") + + groupName = "TechNova Solution" + members = [1,2,3,4] + + group = create_group(groupName) + + self.assertDictEqual({ + 'id': 1, + 'groupName': "G1 TechNova Solution", + 'status': "requested" + }, group.get_json()) + + for member in members: + add_studentGroup(member, group.id) + + self.assertListEqual([ + { + 'studentID': 1, + 'groupID': 1 + }, + { + 'studentID': 2, + 'groupID': 1 + }, + { + 'studentID': 3, + 'groupID': 1 + }, + { + 'studentID': 4, + 'groupID': 1 + }, + ], get_all_studentGroups_json()) + + @pytest.mark.run(order=40) + def test_trying_to_create_group_request_with_a_duplicate_member(self): + groupName = "ANK Productions" + members = [1,2,3,4] + + duplicates = [] + + for member in members: + existing = db.session.scalars(db.select(StudentGroup).filter_by(studentID = member)).first() + if existing: + duplicates.append(member) + + assert duplicates == [1,2,3,4] + + members = [5,6,7,8] + group = create_group(groupName) + + for member in members: + add_studentGroup(member, group.id) + +class Workflow3IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=41) + def test_approving_groupReuests(self): + groupID = 1 + Lot1ID = 1 + Lot2ID = 2 + add_lotGroup(Lot1ID, groupID) + add_lotGroup(Lot2ID, groupID) + + approve_group(groupID) + + self.assertListEqual([ + { + 'lotID': 1, + 'groupID': 1 + }, + { + 'lotID': 2, + 'groupID': 1 + } + ], get_all_lotGroups_json()) + + group = get_group(1) + assert group.status == "approved" + + @pytest.mark.run(order=42) + def test_rejecting_a_group_request(self): + groupID = 2 + + entries = db.session.scalars(db.select(StudentGroup).filter_by(groupID = groupID)).all() + + for entry in entries: + removed = remove_studentGroup(entry.studentID, groupID) + assert removed + + entries = db.session.scalars(db.select(LotGroup).filter_by(groupID = groupID)).all() + + for entry in entries: + removed = remove_lotGroup(entry.lotID, groupID) + assert removed + + removed = remove_group(groupID) + assert removed + +class Workflow4IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=43) + def test_remove_group(self): + groupID = 1 + + entries = db.session.scalars(db.select(StudentGroup).filter_by(groupID = groupID)).all() + + for entry in entries: + removed = remove_studentGroup(entry.studentID, groupID) + assert removed + + entries = db.session.scalars(db.select(LotGroup).filter_by(groupID = groupID)).all() + + for entry in entries: + removed = remove_lotGroup(entry.lotID, groupID) + assert removed + + removed = remove_group(groupID) + assert removed + + groupName = "TechNova Solution" + members = [1,2,3,4] + Lot1ID = 1 + Lot2ID = 2 + + group = create_group(groupName) + + for member in members: + add_studentGroup(member, group.id) + + add_lotGroup(Lot1ID, group.id) + add_lotGroup(Lot2ID, group.id) + + approve_group(group.id) + + groupName = "ANK Productions" + members = [5,6,7,8] + Lot1ID = 3 + Lot2ID = 4 + + group = create_group(groupName) + + for member in members: + add_studentGroup(member, group.id) + + add_lotGroup(Lot1ID, group.id) + add_lotGroup(Lot2ID, group.id) + + approve_group(group.id) + +class Workflow5IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=44) + def test_save_rfp_details(self): + lotID = 1 + deviceType = "Workstation/Laptop/Tablet" + resolution = "" + os = "Mac/Windows/Android/IOS/Linux/Chromium" + cpu = "Core and frequency range eg (quad-core @ 2.2 - 3.0 GHz)" + ram = "" + drive = "" + gpu = "" + peripherals = "" + features = "" + io = "" + + lot = edit_lotRFP_details(lotID, deviceType=deviceType, os=os, cpu=cpu) + assert lot.deviceType == "Workstation/Laptop/Tablet" + assert lot.resolution == "" + assert lot.os == "Mac/Windows/Android/IOS/Linux/Chromium" + assert lot.cpu == "Core and frequency range eg (quad-core @ 2.2 - 3.0 GHz)" + assert lot.ram == "" + assert lot.drive == "" + assert lot.gpu == "" + assert lot.peripherals == "" + assert lot.features == "" + assert lot.io == "" + + lotID = 2 + deviceType = "" + resolution = "" + os = "" + cpu = "" + ram = "" + drive = "HDD/SSD, speed and capacity range, (list if secondary storage)" + gpu = "Integrated/Dedicated with memory range" + peripherals = "" + features = "Touch screen, WIFI version, bluetooth, Capture Card, Integrated Speakers, Integrated Webcam, Fingerprint reader, LTE" + io = "" + + lot = edit_lotRFP_details(lotID, drive=drive, gpu=gpu, features=features) + assert lot.deviceType == "" + assert lot.resolution == "" + assert lot.os == "" + assert lot.cpu == "" + assert lot.ram == "" + assert lot.drive == "HDD/SSD, speed and capacity range, (list if secondary storage)" + assert lot.gpu == "Integrated/Dedicated with memory range" + assert lot.peripherals == "" + assert lot.features == "Touch screen, WIFI version, bluetooth, Capture Card, Integrated Speakers, Integrated Webcam, Fingerprint reader, LTE" + assert lot.io == "" + + @pytest.mark.run(order=45) + def test_submit_rfp_details(self): + lotID = 1 + groupID = 1 + + rfp = create_rfp(groupID, lotID) + + rfp = get_rfp(groupID, lotID) + assert rfp.deviceType == "Workstation/Laptop/Tablet" + assert rfp.resolution == "" + assert rfp.os == "Mac/Windows/Android/IOS/Linux/Chromium" + assert rfp.cpu == "Core and frequency range eg (quad-core @ 2.2 - 3.0 GHz)" + assert rfp.ram == "" + assert rfp.drive == "" + assert rfp.gpu == "" + assert rfp.peripherals == "" + assert rfp.features == "" + assert rfp.io == "" + assert rfp.status == "requested" + + @pytest.mark.run(order=46) + def test_submit_duplicate_rfp_details(self): + lotID = 1 + groupID = 1 + + rfp = db.session.scalars(db.select(RFP).filter_by(groupID=groupID, lotID=lotID)).first() + assert rfp + assert rfp.status == "requested" + + lotID = 2 + + rfp = create_rfp(groupID, lotID) + assert rfp + + rfp = get_rfp(groupID, lotID) + assert rfp.deviceType == "" + assert rfp.resolution == "" + assert rfp.os == "" + assert rfp.cpu == "" + assert rfp.ram == "" + assert rfp.drive == "HDD/SSD, speed and capacity range, (list if secondary storage)" + assert rfp.gpu == "Integrated/Dedicated with memory range" + assert rfp.peripherals == "" + assert rfp.features == "Touch screen, WIFI version, bluetooth, Capture Card, Integrated Speakers, Integrated Webcam, Fingerprint reader, LTE" + assert rfp.io == "" + assert rfp.status == "requested" + +class Workflow6IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=47) + def test_approve_rfp_Request(self): + lotID = 1 + groupID = 1 + + approve_rfp(groupID, lotID) + + rfp = get_rfp(groupID, lotID) + assert rfp.status == "approved" + + @pytest.mark.run(order=48) + def test_reject_rfp_Request(self): + groupID = 1 + lotID = 2 + + removed = remove_rfp(groupID, lotID) + assert removed + +class Workflow7IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=49) + def test_remove_rfp(self): + groupID = 1 + lotID = 1 + + removed = remove_rfp(groupID, lotID) + assert removed + + rfp = create_rfp(groupID, lotID) + approve_rfp(groupID, lotID) + + groupID = 2 + lotID = 3 + rfp = create_rfp(groupID, lotID) + approve_rfp(groupID, lotID) + +class Workflow8IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=50) + def test_place_bid(self): + sourceGroupID = 1 + receipientGroupID = 2 + lotID = 3 + bidDocumentLink = "www.exampleBid.com" + + bid = create_bid(lotID, sourceGroupID, receipientGroupID, bidDocumentLink) + + assert bid + assert bid.sourceGroupID == 1 + assert bidDocumentLink == "www.exampleBid.com" + assert receipientGroupID == 2 + assert lotID == 3 + +class Workflow9IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=51) + def test_remove_bid(self): + bidID = 1 + + removed = remove_bid(bidID) + assert removed + + sourceGroupID = 1 + receipientGroupID = 2 + lotID = 3 + bidDocumentLink = "www.exampleBid.com" + + bid = create_bid(lotID, sourceGroupID, receipientGroupID, bidDocumentLink) + + sourceGroupID = 2 + receipientGroupID = 1 + lotID = 1 + bidDocumentLink = "www.exampleBid.com" + + bid = create_bid(lotID, sourceGroupID, receipientGroupID, bidDocumentLink) + +class Workflow10IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=52) + def test_create_evaluation(self): + sourceGroupID = 1 + receipientGroupID = 2 + bidID = 2 + lotID = 1 + specsMet = 5 + presentation = 3 + professionalism = 2 + budget = 4 + + evaluation = create_evaluation(sourceGroupID, receipientGroupID, bidID, lotID, specsMet, presentation, professionalism, budget) + + assert evaluation + assert evaluation.status == "draft" + assert evaluation.overallScore == 5.6 + +class Workflow11IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=53) + def test_edit_evaluation(self): + evaluationID = 1 + specsMet = 9 + presentation = 3 + professionalism = 2 + budget = 4 + deviceType = "device type comment" + resolution = "resolution comment" + ram = "ram comment" + + edited = edit_evaluation(evaluationID, specsMet, presentation, professionalism, budget, deviceType=deviceType, resolution=resolution, ram=ram) + assert edited + + evaluation = get_evaluation(evaluationID) + assert evaluation.overallScore == 7.2 + assert evaluation.deviceType == "device type comment" + assert evaluation.resolution == "resolution comment" + assert evaluation.ram == "ram comment" + + @pytest.mark.run(order=54) + def test_select_evaluation(self): + evaluationID = 1 + + select_evaluation(evaluationID) + + evaluation = get_evaluation(evaluationID) + assert evaluation + assert evaluation.status == "selected" + +class Workflow12IntegrationTests(unittest.TestCase): + @pytest.mark.run(order=55) + def test_remove_evaluation(self): + evaluationID = 1 + + removed = remove_evaluation(evaluationID) + assert removed \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5bda9f8..fb497ba 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/test.bat b/test.bat new file mode 100644 index 0000000..315fe02 --- /dev/null +++ b/test.bat @@ -0,0 +1,2 @@ +cls +python -m pytest \ No newline at end of file