def test_commons(teacher, student, meetup, dropoff): """add 3 lessons, 2 of them with same meetup and dropoff. check returns of common_meetup and common_dropoff""" for _ in range(2): Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, ) second_meetup = Place.create(description="other", used_as=PlaceType.meetup.value, student=student) second_dropoff = Place.create(description="other", used_as=PlaceType.dropoff.value, student=student) Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow(), meetup_place=second_meetup, dropoff_place=second_dropoff, ) assert student.common_meetup == meetup assert student.common_dropoff == dropoff
def test_total_lessons_price(teacher, student, meetup, dropoff): st = Student.query.filter(Student.total_lessons_price == 0).first() # no lesson has been done yet assert st == student Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow() - timedelta(hours=2), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) st = Student.query.filter( Student.total_lessons_price == teacher.price).first() assert st == student student.update(price=1000) # this is still true because lessons have a fixed price once scheduled assert student.total_lessons_price == teacher.price Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow() - timedelta(hours=2), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) assert student.total_lessons_price == teacher.price + student.price
def test_teacher_available_hours_with_rules( teacher, student, requester, meetup, dropoff ): tomorrow = datetime.utcnow().replace(hour=7, minute=0) + timedelta(days=1) kwargs = { "teacher": teacher, "day": 1, "from_hour": tomorrow.hour, "from_minutes": tomorrow.minute, "to_hour": 23, "to_minutes": 59, "on_date": tomorrow, } WorkDay.create(**kwargs) for i in range(3): Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=teacher.lesson_duration, date=tomorrow + timedelta(minutes=i), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) hours_with_rules = list(teacher.available_hours(tomorrow, student=student)) hours_without_rules = list(teacher.available_hours(tomorrow)) assert hours_with_rules != hours_without_rules
def test_topics_in_progress(teacher, student, topic, meetup, dropoff, lesson): lesson_topic = LessonTopic(is_finished=False, topic_id=topic.id) lesson.topics.append(lesson_topic) lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, ) lesson_topic = LessonTopic(is_finished=False, topic_id=topic.id) lesson.topics.append(lesson_topic) lt = student._lesson_topics(is_finished=False) in_progress = student._topics_in_progress(lt) assert topic in in_progress lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, ) lesson_topic = LessonTopic(is_finished=True, topic_id=topic.id) lesson.topics.append(lesson_topic) lt = student._lesson_topics(is_finished=False) in_progress = student._topics_in_progress(lt) assert len(in_progress) == 0
def test_init_hours(student, teacher, meetup, dropoff): # TODO improve test logic initial_hours = LessonRule.hours date = datetime.utcnow().replace(hour=6, minute=0) + timedelta(days=2) Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=teacher.lesson_duration, date=date, meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) query = Appointment.query.filter( func.extract("day", Appointment.date) == date.day).filter( func.extract("month", Appointment.date) == date.month) new_hours = LessonRule.init_hours( date, student, teacher.work_hours_for_date(date), teacher.taken_appointments_tuples(query, only_approved=True), ) assert initial_hours != new_hours # we want to fill the gap after 6, so hours 7 and 8 should be really cold hour_8 = new_hours[1].score hour_7 = new_hours[0].score old_hour_7 = initial_hours[0].score old_hour_8 = initial_hours[1].score assert hour_7 < old_hour_7 assert hour_8 < old_hour_8
def test_lessons_done(teacher, student, meetup, dropoff): """create new lesson for student, check lessons_done updates""" old_lesson_number = student.lessons_done lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=60, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, ) assert student.lessons_done == old_lesson_number + 60 / teacher.lesson_duration assert student.lessons_done == lesson.lesson_number old_lesson_number = student.lessons_done Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow() + timedelta(hours=20), meetup_place=meetup, dropoff_place=dropoff, ) assert (student.lessons_done == old_lesson_number ) # because it's later than now, it hasn't changed st = Student.query.filter( Student.lessons_done == old_lesson_number).first() assert st.id == student.id
def test_student_blocked_hours_by_test( teacher, student, meetup, dropoff, auth, requester ): date = (datetime.utcnow() + timedelta(days=1)).replace(hour=13, minute=0) data = { "teacher": teacher, "from_hour": 13, "from_minutes": 0, "to_hour": 17, "to_minutes": 0, "on_date": date, } WorkDay.create(**data) Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=date, meetup_place=meetup, dropoff_place=dropoff, is_approved=False, type=AppointmentType.TEST.value, ) auth.login(email=student.user.email) resp = requester.post( f"/teacher/{teacher.id}/available_hours", json={"date": date.strftime(WORKDAY_DATE_FORMAT)}, ) hours = [hour[0] for hour in resp.json["data"]] assert all(date.strftime("%H:%M") not in hour for hour in hours)
def test_available_hours_route_with_places( teacher, student, meetup, dropoff, auth, requester, responses ): auth.login(email=student.user.email) tomorrow = datetime.utcnow() + timedelta(days=1) data = { "teacher": teacher, "from_hour": 7, "from_minutes": 0, "to_hour": 17, "to_minutes": 0, "on_date": tomorrow, } WorkDay.create(**data) date = tomorrow.replace(hour=16, minute=0) Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=date, meetup_place=meetup, dropoff_place=dropoff, is_approved=True, # only check places for approved lessons ) ret = { "destination_addresses": ["HaNassi Blvd, Haifa, Israel"], "origin_addresses": ["Gruenbaum St 3, Haifa, Israel"], "rows": [ { "elements": [ { "distance": {"text": "4.8 mi", "value": 7742}, "duration": {"text": "17 mins", "value": 1037}, "status": "OK", } ] } ], "status": "OK", } responses.add( responses.GET, "https://maps.googleapis.com/maps/api/distancematrix/json", body=json.dumps(ret), status=200, content_type="application/json", ) resp = requester.post( f"/teacher/{teacher.id}/available_hours", json={ "date": date.strftime(WORKDAY_DATE_FORMAT), "meetup_place_id": "test1", "dropoff_place_id": "test2", }, ) assert resp.json["data"]
def test_more_than_lessons_in_week(student, teacher, hours, meetup, dropoff): date = datetime.utcnow() + timedelta(days=2) rule = more_than_lessons_week.MoreThanLessonsWeek(date, student, hours) assert not rule.blacklisted()["start_hour"] for i in range(3): Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=teacher.lesson_duration, date=date + timedelta(minutes=i * teacher.lesson_duration), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) assert rule.blacklisted()["start_hour"]
def test_sort_data(teacher, student, meetup, dropoff): lessons = [] for _ in range(3): lessons.append( Appointment.create( teacher=teacher, student=student, creator=student.user, duration=40, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, )) args = {"order_by": "created_at desc"} lessons_from_db = Appointment.query.order_by( Appointment._sort_data(args)).all() assert lessons_from_db[0] == lessons[-1] args = {"order_by": "does_not_exist desc"} lessons_from_db = Appointment.query.order_by( Appointment._sort_data(args)).all() assert lessons_from_db args = {"order_by": "created_at huh"} lessons_from_db = Appointment.query.order_by( Appointment._sort_data(args)).all() assert lessons_from_db
def test_regular_students(student, teacher, hours, meetup, dropoff): date = datetime.utcnow() - timedelta(days=2) rule = regular_students.RegularStudents(date, student, hours) assert not rule.blacklisted()["start_hour"] for i in range(10): Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=teacher.lesson_duration, date=date + timedelta(minutes=i * teacher.lesson_duration), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) assert rule.blacklisted()["start_hour"]
def test_teacher_available_hours(teacher, student, requester, meetup, dropoff): tomorrow = datetime.utcnow().replace(hour=7, minute=0) + timedelta(days=1) kwargs = { "teacher": teacher, "day": 1, "from_hour": tomorrow.hour, "from_minutes": tomorrow.minute, "to_hour": 23, "to_minutes": 59, "on_date": tomorrow, } WorkDay.create(**kwargs) assert next(teacher.available_hours(tomorrow))[0] == tomorrow # we create a non approved lesson - available hours should still contain its date lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=teacher.lesson_duration, date=tomorrow, meetup_place=meetup, dropoff_place=dropoff, is_approved=False, ) assert next(teacher.available_hours(tomorrow, only_approved=True))[0] == tomorrow assert next(teacher.available_hours(tomorrow, only_approved=False))[0] != tomorrow
def test_filter_data(teacher, student, meetup, dropoff): date = datetime.utcnow() + timedelta(days=100) lesson = Appointment.create( teacher=teacher, student=student, creator=student.user, duration=40, date=date, meetup_place=meetup, dropoff_place=dropoff, ) # date=ge:DATE date = datetime.strftime(date, DATE_FORMAT) lessons_from_db = Appointment.query.filter( Appointment._filter_data("date", f"ge:{date}")).all() assert lessons_from_db[0] == lesson # student_id=2 lessons_from_db = Appointment.query.filter( Appointment._filter_data("student_id", 2)).all() assert not lessons_from_db # date=gggg:DATE lessons_from_db = Appointment.query.filter( Appointment._filter_data( "date", f"ggfggg:{date}")).all() # supposed to translate to equal assert lessons_from_db[0] == lesson # date=DATE with pytest.raises(ValueError): lessons_from_db = Appointment.query.filter( Appointment._filter_data("date", f"{date}")).all()
def test_filter_and_sort(user, teacher, student, meetup, dropoff): """test that limit is maxed to 100, base query, custom date, non allowed filters""" date = datetime.utcnow() + timedelta(days=100) for x in range(101): user = User.create(email=f"{x}@test.com", password="******", name="teacher", area="test") teacher = Teacher.create(user=user, price=100, lesson_duration=40, is_approved=True, crn=9) Appointment.create( teacher=teacher, student=student, creator=student.user, duration=40, date=date, meetup_place=meetup, dropoff_place=dropoff, ) args = MultiDict([("teacher_id", teacher.id)]) # not allowed query = None lessons_from_db = Appointment.filter_and_sort(args, query=query) assert len(lessons_from_db) == 102 args = MultiDict([("date", date.strftime(WORKDAY_DATE_FORMAT))]) lessons_from_db = Appointment.filter_and_sort( args, query=query, custom_date=lambda x: datetime.strptime(x, WORKDAY_DATE_FORMAT), ) assert not lessons_from_db query = Appointment.query.filter_by(teacher_id=3) args = MultiDict() lessons_from_db = Appointment.filter_and_sort(args, query=query) assert len(lessons_from_db) == 1 query = None args = MultiDict([("limit", 100_000_000_000_000)]) lessons_from_db = Appointment.filter_and_sort(args, query=query, with_pagination=True) assert len(lessons_from_db.items) == 100
def test_filter_multiple_params(teacher, student, meetup, dropoff): date = datetime.utcnow() + timedelta(days=100) month_start = date.replace(day=1, hour=0, minute=0, second=0, microsecond=0) month_range = calendar.monthrange(month_start.year, month_start.month) month_end = date.replace( month=month_start.month, day=month_range[1], hour=0, minute=0, second=0, microsecond=0, ) duration = 1200 lesson = Appointment.create( teacher=teacher, student=student, creator=student.user, duration=duration, date=date, meetup_place=meetup, dropoff_place=dropoff, ) Appointment.create( teacher=teacher, student=student, creator=student.user, duration=duration, date=date + timedelta(days=100), # so it won't be the same month meetup_place=meetup, dropoff_place=dropoff, ) month_end = datetime.strftime(month_end, DATE_FORMAT) month_start = datetime.strftime(month_start, DATE_FORMAT) lessons_from_db = (Appointment.query.filter( Appointment._filter_data("date", f"ge:{month_start}")).filter( Appointment._filter_data("date", f"le:{month_end}")).all()) assert len(lessons_from_db) == 1 assert lessons_from_db[0] == lesson
def test_taken_appointments_tuples(teacher, student, meetup, dropoff, lesson): taken_lessons = teacher.taken_appointments_tuples( Appointment.query, only_approved=False ) assert len(taken_lessons) == 1 assert isinstance(taken_lessons[0], tuple) Appointment.create( teacher=teacher, student=student, # schedule to 5 days from now to it won't bother with no test creator=teacher.user, duration=40, date=(datetime.utcnow() + timedelta(days=5)), meetup_place=meetup, dropoff_place=dropoff, is_approved=False, ) taken_lessons = teacher.taken_appointments_tuples( Appointment.query, only_approved=True ) assert len(taken_lessons) == 1
def test_lesson_topics(teacher, student, topic, meetup, dropoff): lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, ) lesson_topic = LessonTopic(is_finished=False, topic_id=topic.id) lesson.topics.append(lesson_topic) lt = student._lesson_topics(is_finished=False) assert lesson_topic in lt assert isinstance(lt, BaseQuery)
def test_total_lessons_price_with_different_prices(teacher, student, meetup, dropoff): st = Student.query.filter(Student.total_lessons_price == 0).first() # no lesson has been done yet assert st == student price = 100 prices = price for x in range(3): Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow() - timedelta(hours=x), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, price=price * x, ) prices += price * x assert student.total_lessons_price == prices st = Student.query.filter(Student.total_lessons_price == prices).first() assert st == student
def test_available_hours_route(teacher, student, meetup, dropoff, auth, requester): auth.login(email=teacher.user.email) tomorrow = datetime.utcnow() + timedelta(days=1) date = tomorrow.strftime(WORKDAY_DATE_FORMAT) time_and_date = date + "T13:30:20.123123Z" data = { "teacher": teacher, "from_hour": 13, "from_minutes": 0, "to_hour": 17, "to_minutes": 0, "on_date": tomorrow, } WorkDay.create(**data) # now let's add a lesson lesson_date = datetime.strptime(time_and_date, DATE_FORMAT) lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=lesson_date, meetup_place=meetup, dropoff_place=dropoff, is_approved=False, ) resp = requester.post(f"/teacher/{teacher.id}/available_hours", json={"date": date}) assert len(resp.json["data"]) == 6 lesson.update(is_approved=True) resp = requester.post( f"/teacher/{teacher.id}/available_hours", json={"date": date, "duration": "120"} ) assert len(resp.json["data"]) == 1 auth.logout() # if we login as student, we shouldn't this non approved lesson date auth.login(email=student.user.email) lesson.update(is_approved=False) resp = requester.post(f"/teacher/{teacher.id}/available_hours", json={"date": date}) hours = [hour[0] for hour in resp.json["data"]] assert all(lesson_date.strftime("%H:%M") not in hour for hour in hours)
def test_balance(teacher, student, meetup, dropoff): # we have one lesson currently and 0 payments, but the lesson hasn't yet happened assert student.balance == 0 lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow() - timedelta(hours=2), meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) assert student.balance == -teacher.price lesson.update(is_approved=False) assert student.balance == 0 st = Student.query.filter(Student.balance == 0).first() assert st == student Payment.create(amount=teacher.price, teacher=teacher, student=student) assert student.balance == teacher.price
def new_appointment(): data = flask.request.get_json() if not data.get("date"): raise RouteError("Please insert the date of the appointment.") appointment = Appointment.create(**get_data(data, current_user)) # send fcm to the user who wasn't the one creating the lesson user_to_send_to = appointment.teacher.user body_text = gettext( "%(student)s wants to schedule a new lesson at %(date)s. Click here to check it out.", student=appointment.student.user.name, date=format_datetime( appointment.date, locale=LOCALE, format="short", tzinfo=timezone(TIMEZONE) ), ) if appointment.creator == appointment.teacher.user and appointment.student: user_to_send_to = appointment.student.user body_text = gettext( "%(teacher)s scheduled a new lesson at %(value)s. Click here to check it out.", teacher=appointment.teacher.user.name, value=format_datetime( appointment.date, locale=LOCALE, format="short", tzinfo=timezone(TIMEZONE), ), ) if user_to_send_to.firebase_token: logger.debug(f"sending fcm to {user_to_send_to} for new appointment") try: FCM.notify( token=user_to_send_to.firebase_token, title=gettext("New Lesson!"), body=body_text, ) except NotificationError: pass return {"data": appointment.to_dict()}, 201
def create_lesson( teacher, student, meetup, dropoff, date, duration=40, deleted=False, is_approved=True, **kwargs, ): return Appointment.create( teacher=teacher, student=student, creator=student.user if student else teacher.user, duration=duration, date=date.replace(second=0, microsecond=0), meetup_place=meetup, dropoff_place=dropoff, deleted=deleted, is_approved=is_approved, **kwargs, )
def test_filter_topics(teacher, student, meetup, dropoff, topic, lesson): """make topic in-progress and check. then make it finished and check again""" topic2 = Topic.create(title="aa", min_lesson_number=3, max_lesson_number=5) lesson_topic = LessonTopic(is_finished=False, topic_id=topic.id) lesson.topics.append(lesson_topic) assert topic in student.topics(is_finished=False) # let's create another lesson with same topic lesson = Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=40, date=datetime.utcnow(), meetup_place=meetup, dropoff_place=dropoff, ) lesson_topic = LessonTopic(is_finished=True, topic_id=topic.id) lesson.topics.append(lesson_topic) lesson_topic2 = LessonTopic(is_finished=False, topic_id=topic2.id) lesson.topics.append(lesson_topic2) assert topic in student.topics(is_finished=True) assert topic2 in student.topics(is_finished=False)
def setup_db(app): User.create(email="*****@*****.**", password="******", name="test", area="test", phone="044444444") User.create( email="*****@*****.**", password="******", name="admin", area="test", is_admin=True, phone="055555555", ) teacher_user = User.create(email="*****@*****.**", password="******", name="teacher", area="test") teacher = Teacher.create( user=teacher_user, price=100, lesson_duration=40, is_approved=True, crn=999999999, invoice_api_key=DEMO_API_KEY, ) Car.create(teacher=teacher, number=1111111111) student_user = User.create(email="*****@*****.**", password="******", name="student", area="test") student = Student.create(user=student_user, teacher=teacher, creator=teacher.user, is_approved=True) meetup = Place.create( description="test", used_as=PlaceType.meetup.value, student=student, google_id="ID1", ) dropoff = Place.create( description="test", used_as=PlaceType.dropoff.value, student=student, google_id="ID2", ) WorkDay.create( teacher=teacher, day=1, from_hour=0, to_hour=23, to_minutes=59, on_date=(datetime.utcnow() + timedelta(days=2)).date(), ) # 2 days from now Topic.create(title="topic test", min_lesson_number=1, max_lesson_number=5) Appointment.create( teacher=teacher, student=student, # schedule to 5 days from now to it won't bother with no test creator=teacher.user, duration=40, date=(datetime.utcnow() + timedelta(days=5)), meetup_place=meetup, dropoff_place=dropoff, )
def test_place_distances(student, teacher, meetup, dropoff, hours, responses): date = datetime.utcnow().replace(hour=16, minute=0) Appointment.create( teacher=teacher, student=student, creator=teacher.user, duration=teacher.lesson_duration, date=date, meetup_place=meetup, dropoff_place=dropoff, is_approved=True, ) ret = { "destination_addresses": ["HaNassi Blvd, Haifa, Israel"], "origin_addresses": ["Gruenbaum St 3, Haifa, Israel"], "rows": [{ "elements": [{ "distance": { "text": "4.8 mi", "value": 7742 }, "duration": { "text": "17 mins", "value": 1037 }, "status": "OK", }] }], "status": "OK", } responses.add( responses.GET, "https://maps.googleapis.com/maps/api/distancematrix/json", body=json.dumps(ret), status=200, content_type="application/json", ) rule = place_distance.PlaceDistances(date, student, hours, ("test1", "test2")) assert not rule.blacklisted()["start_hour"] ret = { "destination_addresses": ["Arlozorov St, Tel Aviv-Yafo, Israel"], "origin_addresses": ["Gruenbaum St 3, Haifa, Israel"], "rows": [{ "elements": [{ "distance": { "text": "59.4 mi", "value": 95649 }, "duration": { "text": "1 hour 16 mins", "value": 4539 }, "status": "OK", }] }], "status": "OK", } responses.replace( responses.GET, "https://maps.googleapis.com/maps/api/distancematrix/json", body=json.dumps(ret), status=200, content_type="application/json", ) rule = place_distance.PlaceDistances(date, student, hours, ("test2", "test3")) blacklist = rule.blacklisted() assert blacklist["start_hour"] assert blacklist["end_hour"]