diff --git a/App/controllers/__init__.py b/App/controllers/__init__.py index 0cb8fd1..f981d3d 100644 --- a/App/controllers/__init__.py +++ b/App/controllers/__init__.py @@ -2,4 +2,5 @@ from .auth import * from .initialize import * from .admin import * -from .staff import * +from .staff import * +from .report import * diff --git a/App/controllers/admin.py b/App/controllers/admin.py index aa2d9ca..c48025f 100644 --- a/App/controllers/admin.py +++ b/App/controllers/admin.py @@ -1,34 +1,51 @@ -from App.models import Shift -from App.database import db -from datetime import datetime -from App.controllers.user import get_user - from App.models import Shift, Schedule from App.database import db -from datetime import datetime from App.controllers.user import get_user +from App.controllers.schedule import generate_report -def create_schedule(admin_id, scheduleName): #Not sure why this was missing +#create an empty schedule with start and end date +def create_schedule(start_date, end_date, admin_id): admin = get_user(admin_id) + if not admin or admin.role != "admin": raise PermissionError("Only admins can create schedules") - - new_schedule = Schedule( - created_by=admin_id, - name=scheduleName, - created_at=datetime.utcnow() - ) - + + new_schedule = Schedule(start_date, end_date, admin_id) + db.session.add(new_schedule) db.session.commit() - return new_schedule -def schedule_shift(admin_id, staff_id, schedule_id, start_time, end_time): +#assign all staff to a weekly schedule using a scheduling strategy +def schedule_week(strategy, schedule_id, staff_list, admin_id): admin = get_user(admin_id) - staff = get_user(staff_id) + + if not admin or admin.role != "admin": + raise PermissionError("Only admins can create schedules") schedule = db.session.get(Schedule, schedule_id) + if not schedule: + raise ValueError("Invalid schedule ID") + + valid_staff = [] + for staff_id in staff_list: + staff = get_user(staff_id) + if staff and staff.role == "staff": + valid_staff.append(staff) + else: + print(f"Skipping invalid staff ID: {staff_id}") + + if not valid_staff: + raise ValueError("No valid staff members to schedule") + + strategy.fill_schedule(valid_staff, schedule) + + +#create a shift and add it to a schedule +def schedule_shift(schedule_id, start_time, end_time, staff_id, admin_id): + admin = get_user(admin_id) + staff = get_user(staff_id) + schedule = db.session.get(Schedule, schedule_id) if not admin or admin.role != "admin": raise PermissionError("Only admins can schedule shifts") @@ -49,10 +66,14 @@ def schedule_shift(admin_id, staff_id, schedule_id, start_time, end_time): return new_shift - -def get_shift_report(admin_id): +#view the shift report for a given schedule +def view_report(schedule_id, admin_id): admin = get_user(admin_id) if not admin or admin.role != "admin": raise PermissionError("Only admins can view shift reports") - return [shift.get_json() for shift in Shift.query.order_by(Shift.start_time).all()] \ No newline at end of file + schedule = db.session.get(Schedule, schedule_id) + if not schedule: + raise ValueError("Invalid schedule ID") + + return generate_report(schedule_id) \ No newline at end of file diff --git a/App/controllers/report.py b/App/controllers/report.py new file mode 100644 index 0000000..afb88a2 --- /dev/null +++ b/App/controllers/report.py @@ -0,0 +1,40 @@ +from App.models.schedule import Schedule + + +# This controller summarises the attendance of staff for a given schedule +def get_summary(scheduleID): + schedule = Schedule.query.get(scheduleID) + if not schedule: + raise ValueError("Schedule not found") + + days = {} + for shift in schedule.shifts: + day = shift.start_time.strftime("%Y-%m-%d") + + if day not in days: + days[day] = { + "assigned": set(), + "present": set(), + "late": set(), + "missed": set(), + } + + staff = shift.staff + + # Add assigned staff + days[day]["assigned"].add(staff.username) + + # Record staff's attendance + if shift.clock_in: + days[day]["present"].add(staff.username) + if shift.clock_in > shift.start_time: + days[day]["late"].add(staff.username) + else: + days[day]["missed"].add(staff.username) + + # Convert sets to lists + for day, record in days.items(): + for key in record: + record[key] = list(record[key]) + + return {"schedule_id": schedule.id, "days": days} diff --git a/App/controllers/schedule.py b/App/controllers/schedule.py new file mode 100644 index 0000000..6000375 --- /dev/null +++ b/App/controllers/schedule.py @@ -0,0 +1,15 @@ +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.models.schedule import Schedule + + +def create_even_schedule(all_staff, shifts): + # write function + pass + +def create_minimum_schedule(all_staff, shifts): + # write function + pass + +def create_day_night_schedule(all_staff, shifts): + # write function + pass \ No newline at end of file diff --git a/App/controllers/staff.py b/App/controllers/staff.py index 6c21d3a..fbdc467 100644 --- a/App/controllers/staff.py +++ b/App/controllers/staff.py @@ -3,41 +3,65 @@ from datetime import datetime from App.controllers.user import get_user -def get_combined_roster(staff_id): +def viewShifts(staff_id, schedule_id): + # Shows only the current staff member's shifts for a given schedule + staff = get_user(staff_id) - if not staff or staff.role != "staff": - raise PermissionError("Only staff can view roster") - return [shift.get_json() for shift in Shift.query.order_by(Shift.start_time).all()] + + if not staff or staff.role.lower() != "staff": + raise PermissionError("Only staff can view their shifts") + shifts = Shift.query.filter_by( + staff_id=staff_id + ).order_by(Shift.start_time).all() + return [s.get_json() for s in shifts] + + +def viewSchedule(staff_id, schedule_id): + # Combined view of all shifts in a schedule for staff members + # Replaces get_combined_roster() + + staff = get_user(staff_id) + + if not staff or staff.role.lower() != "staff": + raise PermissionError("Only staff can view schedule rosters") + + shifts = Shift.query.filter_by( + schedule_id=schedule_id + ).order_by(Shift.start_time).all() + + return [s.get_json() for s in shifts] + + def clock_in(staff_id, shift_id): staff = get_user(staff_id) - if not staff or staff.role != "staff": + if not staff or staff.role.lower() != "staff": raise PermissionError("Only staff can clock in") - - shift = db.session.get(Shift, shift_id) + + shift = Shift.get_shift(shift_id) if not shift or shift.staff_id != staff_id: raise ValueError("Invalid shift for staff") shift.clock_in = datetime.now() + shift.updateStatus() #automatically updates the status for the clock in db.session.commit() return shift def clock_out(staff_id, shift_id): staff = get_user(staff_id) - if not staff or staff.role != "staff": + if not staff or staff.role.lower() != "staff": raise PermissionError("Only staff can clock out") - shift = db.session.get(Shift, shift_id) + shift = Shift.get_shift(shift_id) + if not shift or shift.staff_id != staff_id: raise ValueError("Invalid shift for staff") shift.clock_out = datetime.now() + shift.updateStatus() #automatically updates the status for the clock out db.session.commit() - return shift - -def get_shift(shift_id): - shift = db.session.get(Shift, shift_id) + return shift \ No newline at end of file diff --git a/App/controllers/user.py b/App/controllers/user.py index 7570136..bfd93d4 100644 --- a/App/controllers/user.py +++ b/App/controllers/user.py @@ -1,6 +1,5 @@ from App.models import User, Admin, Staff, Shift from App.database import db -from datetime import datetime VALID_ROLES = {"user", "staff", "admin"} @@ -35,6 +34,16 @@ def get_all_users_json(): return [] return [user.get_json() for user in users] +#added method for scheduling purposes +def get_all_users_by_role(role): + return User.query.filter_by(role=role).all() + +def get_all_users_by_role_json(role): + users = get_all_users_by_role(role) + if not users: + return [] + return [user.get_json() for user in users] + def update_user(id, username): user = get_user(id) if user: @@ -42,3 +51,7 @@ def update_user(id, username): db.session.commit() return user return None + +#not sure if this method should be added here +def get_all_shifts(): + return Shift.query.all() diff --git a/App/models/__init__.py b/App/models/__init__.py index 91d63f0..1e59180 100644 --- a/App/models/__init__.py +++ b/App/models/__init__.py @@ -2,5 +2,5 @@ from App.models.admin import Admin from App.models.staff import Staff from App.models.schedule import Schedule -from App.models.shift import Shift - +from App.models.shift import Shift +from App.models.report import Report diff --git a/App/models/day_night_scheduler.py b/App/models/day_night_scheduler.py new file mode 100644 index 0000000..c399c36 --- /dev/null +++ b/App/models/day_night_scheduler.py @@ -0,0 +1,9 @@ +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.models.scheduling_strategy import SchedulingStrategy + +class DayNightScheduler(SchedulingStrategy): + + def schedule_shift(self): + # return Schedule object -> create_day_night_schedule(self.all_staff, self.shifts) + # this method will be in Schedule controller + pass \ No newline at end of file diff --git a/App/models/even_scheduler.py b/App/models/even_scheduler.py new file mode 100644 index 0000000..e49bee0 --- /dev/null +++ b/App/models/even_scheduler.py @@ -0,0 +1,9 @@ +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.models.scheduling_strategy import SchedulingStrategy + +class EvenScheduler(SchedulingStrategy): + + def schedule_shift(self): + # return Schedule object -> create_even_schedule(self.all_staff, self.shifts) + # this method will be in Schedule controller + pass diff --git a/App/models/minimum_scheduler.py b/App/models/minimum_scheduler.py new file mode 100644 index 0000000..09b9ee8 --- /dev/null +++ b/App/models/minimum_scheduler.py @@ -0,0 +1,9 @@ +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.models.scheduling_strategy import SchedulingStrategy + +class MinimumScheduler(SchedulingStrategy): + + def schedule_shift(self): + # return Schedule object -> create_minimum_schedule(self.all_staff, self.shifts) + # this method will be in Schedule controller + pass \ No newline at end of file diff --git a/App/models/report.py b/App/models/report.py new file mode 100644 index 0000000..f659f55 --- /dev/null +++ b/App/models/report.py @@ -0,0 +1,24 @@ +from App.database import db + +class Report(db.Model): + id = db.Column(db.Integer, primary_key=True) + admin_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) + generated_date = db.Column(db.DateTime, nullable=False) + + admin = db.relationship("Admin", backref="reports", foreign_keys=[admin_id]) + + def get_json(self): + return { + "id": self.id, + "admin_id": self.admin_id, + "admin_name": self.admin.username, + "generated_date": self.generated_date.isoformat() + } + + def __init__(self, admin_id, generated_date): + self.id = id + self.admin_id = admin_id + self.generated_date = generated_date + + + \ No newline at end of file diff --git a/App/models/schedule.py b/App/models/schedule.py index 64c0e24..798ef76 100644 --- a/App/models/schedule.py +++ b/App/models/schedule.py @@ -3,22 +3,31 @@ class Schedule(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - created_by = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) + start_date = db.Column(db.DateTime, nullable=False) + end_date = db.Column(db.DateTime, nullable=False) + admin_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) shifts = db.relationship("Shift", backref="schedule", lazy=True) - def shift_count(self): - return len(self.shifts) - def get_json(self): return { "id": self.id, - "name": self.name, - "created_at": self.created_at.isoformat(), - "created_by": self.created_by, - "shift_count": self.shift_count(), - "shifts": [shift.get_json() for shift in self.shifts] + "start_date": self.start_date.isoformat(), + "end_date": self.end_date.isoformat(), + "admin_id": self.admin_id, } + def __init__(self, start_date, end_date, admin_id): + self.start_date = start_date + self.end_date = end_date + self.admin_id = admin_id + + def get_shifts(self): + return self.shifts + + def add_shift(self, shift): + shift.schedule = self + return self + def remove_shift(self, shift): + shift.schedule = None + return self \ No newline at end of file diff --git a/App/models/scheduling_strategy.py b/App/models/scheduling_strategy.py new file mode 100644 index 0000000..deb4e4b --- /dev/null +++ b/App/models/scheduling_strategy.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod +from App.controllers.user import get_all_users_by_role, get_all_shifts + +class SchedulingStrategy(ABC): + # hello + # gets updated list from db upon initialization + def __init__(self): + super().__init__() + self.all_staff = get_all_users_by_role("staff") + self.shifts = get_all_shifts() + + @abstractmethod + def schedule_shift(self): + #return Schedule object in concrete classes + pass \ No newline at end of file diff --git a/App/models/shift.py b/App/models/shift.py index 0467dee..29c6fca 100644 --- a/App/models/shift.py +++ b/App/models/shift.py @@ -1,25 +1,76 @@ -from datetime import datetime from App.database import db +from datetime import datetime +from enum import Enum as PyEnum +from sqlalchemy import Enum class Shift(db.Model): id = db.Column(db.Integer, primary_key=True) staff_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) - schedule_id = db.Column(db.Integer, db.ForeignKey("schedule.id"), nullable=True) - start_time = db.Column(db.DateTime, nullable=False) end_time = db.Column(db.DateTime, nullable=False) clock_in = db.Column(db.DateTime, nullable=True) clock_out = db.Column(db.DateTime, nullable=True) + status = db.Column(db.String, default="Scheduled") staff = db.relationship("Staff", backref="shifts", foreign_keys=[staff_id]) - def get_json(self): - return { - "id": self.id, - "staff_id": self.staff_id, - "staff_name": self.staff.username if self.staff else None, - "start_time": self.start_time.isoformat(), - "schedule_id": self.schedule_id, - "end_time": self.end_time.isoformat(), - "clock_in": self.clock_in.isoformat() if self.clock_in else None, - "clock_out": self.clock_out.isoformat() if self.clock_out else None - } + @classmethod + def get_shift(cls, shift_id): + return db.session.get(cls, shift_id) + + def assignStaff(self, staff): + # Links a staff obj to this Shift + # Updates staff_id and relationship + + self.staff = staff + self.staff_id = staff.id + db.session.commit() + + + def getHours(self): + # Calculate hours worked using clock-in and clock-out times + # Returns float (hours). Returns 0 if not completed + + if not self.clock_in or not self.clock_out: + return 0.0 + + duration = self.clock_out - self.clock_in + hours = duration.total_seconds() / 3600 + return round(hours, 2) + + + def updateStatus(self): + # Update shift status based on timing and presence of clock-in/out. + + now = datetime.now() + + # Completed -> has both clock-in and clock-out + if self.clock_in and self.clock_out: + self.status = "Completed" + + # Ongoing -> clocked in but not out yet + elif self.clock_in and not self.clock_out: + self.status = "Ongoing" + + # Late -> start_time passed, but staff hasn't clocked in, but clock-in still possible + elif now > self.start_time and not self.clock_in and now < self.end_time: + self.status = "Late" + + # Missed -> end_time passed and no clock-in + elif now > self.end_time and not self.clock_in: + self.status = "Missed" + + # Scheduled -> future shift not yet started + elif now < self.start_time: + self.status = "Scheduled" + + db.session.commit() + + def get_json(self): + return { + "id": self.id, + "schedule_id": self.schedule_id, + "end_time": self.end_time.isoformat(), + "clock_in": self.clock_in.isoformat() if self.clock_in else None, + "clock_out": self.clock_out.isoformat() if self.clock_out else None, + "status": self.status + } \ No newline at end of file diff --git a/App/services/scheduler.py b/App/services/scheduler.py new file mode 100644 index 0000000..00208fb --- /dev/null +++ b/App/services/scheduler.py @@ -0,0 +1,13 @@ +from App.services import scheduling_strategy + +class Scheduler(): + # class diagram uses composition so I assume Scheduler class should be + # initialized with a strategy + def __init__(self, strategy): + self.setStrategy(strategy) + + def set_strategy(self, strategy): + self.strategy = strategy + + def fill_schedule(self, staff, schedule): + self.strategy.fill_schedule() \ No newline at end of file diff --git a/App/services/scheduling_strategy.py b/App/services/scheduling_strategy.py new file mode 100644 index 0000000..71f942f --- /dev/null +++ b/App/services/scheduling_strategy.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from App.models.staff import Staff +from App.models.schedule import Schedule +from App.controllers.user import get_all_users_by_role, get_all_shifts + +class SchedulingStrategy(ABC): + @abstractmethod + def __init__(self): + super().__init__() + + # or just get list of staff and shifts from db upon initializing + ''' + def __init__(self): + super().__init__() + self.all_staff = get_all_users_by_role("staff") + self.shifts = get_all_shifts() + ''' + + + @abstractmethod + def fill_schedule(self, staff, schedule): + #return Schedule object in concrete classes + pass \ No newline at end of file diff --git a/App/services/strategies/day_night_scheduler.py b/App/services/strategies/day_night_scheduler.py new file mode 100644 index 0000000..f7561f9 --- /dev/null +++ b/App/services/strategies/day_night_scheduler.py @@ -0,0 +1,11 @@ +from App.models.staff import Staff +from App.models.schedule import Schedule +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.services.scheduling_strategy import SchedulingStrategy + +class DayNightScheduler(SchedulingStrategy): + + def fill_schedule(self, staff, schedule): + # return Schedule object -> create_day_night_schedule(self.all_staff, self.shifts) + # this method will be in Schedule controller + pass \ No newline at end of file diff --git a/App/services/strategies/even_scheduler.py b/App/services/strategies/even_scheduler.py new file mode 100644 index 0000000..b33c904 --- /dev/null +++ b/App/services/strategies/even_scheduler.py @@ -0,0 +1,11 @@ +from App.models.staff import Staff +from App.models.schedule import Schedule +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.services.scheduling_strategy import SchedulingStrategy + +class EvenScheduler(SchedulingStrategy): + + def fill_schedule(self, staff, schedule): + # return Schedule object -> create_even_schedule(self.all_staff, self.shifts) + # this method will be in Schedule controller + pass diff --git a/App/services/strategies/minimum_scheduler.py b/App/services/strategies/minimum_scheduler.py new file mode 100644 index 0000000..c684aee --- /dev/null +++ b/App/services/strategies/minimum_scheduler.py @@ -0,0 +1,11 @@ +from App.models.staff import Staff +from App.models.schedule import Schedule +from App.controllers.user import get_all_users_by_role, get_all_shifts +from App.services.scheduling_strategy import SchedulingStrategy + +class MinimumScheduler(SchedulingStrategy): + + def fill_schedule(self, staff, schedule): + # return Schedule object -> create_minimum_schedule(self.all_staff, self.shifts) + # this method will be in Schedule controller + pass \ No newline at end of file diff --git a/App/tests/test_app.py b/App/tests/test_app.py index e52b6a5..8d44242 100644 --- a/App/tests/test_app.py +++ b/App/tests/test_app.py @@ -3,51 +3,44 @@ from App.main import create_app from App.database import db, create_db from datetime import datetime, timedelta -from App.models import User, Schedule, Shift -from App.controllers import ( - create_user, - get_all_users_json, - loginCLI, - get_user, - update_user, - schedule_shift, - get_shift_report, - get_combined_roster, - clock_in, - clock_out, - get_shift -) +from App.models import User, Schedule, Shift, Staff, Report +from App.controllers import (create_user, get_all_users_json, loginCLI, + get_user, update_user, schedule_shift, + get_shift_report, get_combined_roster, clock_in, + clock_out, get_shift, get_summary) +# Test get_all_users_by_role(role) and get_all_users_by_role_json(role) LOGGER = logging.getLogger(__name__) - ''' Unit Tests ''' - class UserUnitTests(unittest.TestCase): -# User unit tests + # User unit tests def test_new_user_admin(self): - user = create_user("bot", "bobpass","admin") + user = create_user("bot", "bobpass", "admin") assert user.username == "bot" def test_new_user_staff(self): - user = create_user("pam", "pampass","staff") + user = create_user("pam", "pampass", "staff") assert user.username == "pam" def test_create_user_invalid_role(self): - user = create_user("jim", "jimpass","ceo") + user = create_user("jim", "jimpass", "ceo") assert user == None - def test_get_json(self): user = User("bob", "bobpass", "admin") user_json = user.get_json() - self.assertDictEqual(user_json, {"id":None, "username":"bob", "role":"admin"}) - + self.assertDictEqual(user_json, { + "id": None, + "username": "bob", + "role": "admin" + }) + def test_hashed_password(self): password = "mypass" user = User(username="tester", password=password) @@ -59,6 +52,7 @@ def test_check_password(self): user = User("bob", password) assert user.check_password(password) # Admin unit tests + def test_schedule_shift_valid(self): admin = create_user("admin1", "adminpass", "admin") staff = create_user("staff1", "staffpass", "staff") @@ -85,8 +79,9 @@ def test_schedule_shift_invalid(self): start = datetime(2025, 10, 22, 8, 0, 0) end = datetime(2025, 10, 22, 16, 0, 0) try: - shift = schedule_shift(admin.id, staff.id, invalid_schedule_id, start, end) - assert shift is None + shift = schedule_shift(admin.id, staff.id, invalid_schedule_id, + start, end) + assert shift is None except Exception: assert True @@ -106,7 +101,7 @@ def test_get_shift_report(self): shift2 = schedule_shift(admin.id, staff.id, schedule.id, datetime(2025, 10, 27, 8, 0, 0), datetime(2025, 10, 27, 16, 0, 0)) - + report = get_shift_report(admin.id) assert len(report) >= 2 assert report[0]["staff_id"] == staff.id @@ -121,6 +116,7 @@ def test_get_shift_report_invalid(self): except PermissionError as e: assert str(e) == "Only admins can view shift reports" # Staff unit tests + def test_get_combined_roster_valid(self): staff = create_user("staff3", "pass123", "staff") admin = create_user("admin3", "adminpass", "admin") @@ -201,7 +197,8 @@ def test_clock_out_valid(self): def test_clock_out_invalid_user(self): admin = create_user("admin_invalid_out", "adminpass", "admin") - schedule = Schedule(name="Invalid ClockOut Schedule", created_by=admin.id) + schedule = Schedule(name="Invalid ClockOut Schedule", + created_by=admin.id) db.session.add(schedule) db.session.commit() @@ -217,22 +214,77 @@ def test_clock_out_invalid_user(self): def test_clock_out_invalid_shift(self): staff = create_user("staff_invalid_shift_out", "staffpass", "staff") with pytest.raises(ValueError) as e: - clock_out(staff.id, 999) + clock_out(staff.id, 999) assert str(e.value) == "Invalid shift for staff" + + +# Report unit tests + + def test_get_summary(self): + admin = create_user("admin_invalid_out", "adminpass", "admin") + db.session.add(admin) + db.session.commit() + + alice = Staff("Alice", "alicepass") + bob = Staff("Bob", "bobpass") + db.session.add_all([alice, bob]) + db.session.commit() + + schedule = Schedule(datetime(2025, 11, 17, 8), datetime(2025, 11, 21, 16), admin.id) + db.session.add(schedule) + db.session.commit() + db.session.add(schedule) + db.session.commit() + + shift1 = Shift(staff_id=alice.id, + schedule_id=schedule.id, + start_time=datetime(2025, 11, 17, 8), + end_time=datetime(2025, 11, 17, 16), + clock_in=datetime(2025, 11, 17, 8, 5), + clock_out=datetime(2025, 11, 17, 16)) + + shift2 = Shift(staff_id=bob.id, + schedule_id=schedule.id, + start_time=datetime(2025, 11, 17, 8), + end_time=datetime(2025, 11, 17, 16), + clock_in=datetime(2025, 11, 17, 8, 0), + clock_out=datetime(2025, 11, 17, 16)) + + db.session.add_all([shift1, shift2]) + db.session.commit() + + summary = get_summary(schedule.id) + + assert summary["schedule_id"] == schedule.id + assert "2025-11-17" in summary["days"] + + day_data = summary["days"]["2025-11-17"] + assert alice.username in day_data["assigned"] + assert bob.username in day_data["assigned"] + assert alice.username in day_data["late"] + assert bob.username not in day_data["late"] + assert len(day_data["missed"]) == 0 ''' Integration Tests ''' + + @pytest.fixture(autouse=True) def clean_db(): db.drop_all() create_db() db.session.remove() yield + + # 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'}) + app = create_app({ + 'TESTING': True, + 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db' + }) create_db() db.session.remove() yield app.test_client() @@ -240,19 +292,28 @@ def empty_db(): def test_authenticate(): - user = User("bob", "bobpass","user") + user = User("bob", "bobpass", "user") assert loginCLI("bob", "bobpass") != None + class UsersIntegrationTests(unittest.TestCase): def test_get_all_users_json(self): - user = create_user("bot", "bobpass","admin") - user = create_user("pam", "pampass","staff") + user = create_user("bot", "bobpass", "admin") + user = create_user("pam", "pampass", "staff") users_json = get_all_users_json() - self.assertListEqual([{"id":1, "username":"bot", "role":"admin"}, {"id":2, "username":"pam","role":"staff"}], users_json) + self.assertListEqual([{ + "id": 1, + "username": "bot", + "role": "admin" + }, { + "id": 2, + "username": "pam", + "role": "staff" + }], users_json) def test_update_user(self): - user = create_user("bot", "bobpass","admin") + user = create_user("bot", "bobpass", "admin") update_user(1, "ronnie") user = get_user(1) assert user.username == "ronnie" @@ -262,17 +323,25 @@ def test_create_and_get_user(self): retrieved = get_user(user.id) self.assertEqual(retrieved.username, "alex") self.assertEqual(retrieved.role, "staff") - + def test_get_all_users_json_integration(self): create_user("bot", "bobpass", "admin") create_user("pam", "pampass", "staff") users_json = get_all_users_json() expected = [ - {"id": 1, "username": "bot", "role": "admin"}, - {"id": 2, "username": "pam", "role": "staff"}, + { + "id": 1, + "username": "bot", + "role": "admin" + }, + { + "id": 2, + "username": "pam", + "role": "staff" + }, ] self.assertEqual(users_json, expected) - + def test_admin_schedule_shift_for_staff(self): admin = create_user("admin1", "adminpass", "admin") staff = create_user("staff1", "staffpass", "staff") @@ -326,12 +395,11 @@ def test_staff_clock_in_and_out(self): clock_in(staff.id, shift.id) clock_out(staff.id, shift.id) - updated_shift = get_shift(shift.id) self.assertIsNotNone(updated_shift.clock_in) self.assertIsNotNone(updated_shift.clock_out) self.assertLess(updated_shift.clock_in, updated_shift.clock_out) - + def test_admin_generate_shift_report(self): admin = create_user("boss", "boss123", "admin") staff = create_user("sam", "sampass", "staff") @@ -347,7 +415,8 @@ def test_admin_generate_shift_report(self): report = get_shift_report(admin.id) self.assertTrue(any("sam" in r["staff_name"] for r in report)) - self.assertTrue(all("start_time" in r and "end_time" in r for r in report)) + self.assertTrue( + all("start_time" in r and "end_time" in r for r in report)) def test_permission_restrictions(self): admin = create_user("admin", "adminpass", "admin") @@ -368,4 +437,4 @@ def test_permission_restrictions(self): get_combined_roster(admin.id) with self.assertRaises(PermissionError): - get_shift_report(staff.id) \ No newline at end of file + get_shift_report(staff.id)