def test_create(self):
        # Test usage of Menu.create to check if Menus are constructed the same way as through their constructor

        with self.app.app_context():
            db.session.add_all(self.campuses)

            Menu.create(self.campuses[0], utils.DAYS['MON'])
            Menu.create(self.campuses[1], utils.DAYS['TUE'])
            Menu.create(self.campuses[0], utils.DAYS['WED'])
            Menu.create(self.campuses[1], utils.DAYS['THU'])
            Menu.create(self.campuses[0], utils.DAYS['FRI'])

            db.session.commit()
Ejemplo n.º 2
0
    def test_simple_constructors(self):
        # Test constructor of MenuItem model

        with self.app.app_context():
            db.session.add_all(self.campuses)

            translatable1, _ = self.create_translation(
                {'en': 'Translation 1: en'}, 'en', has_context=True)
            translatable2, _ = self.create_translation(
                {'en': 'Translation 2: en'}, 'en', has_context=True)
            translatable3, _ = self.create_translation(
                {'en': 'Translation 3: en'}, 'en', has_context=True)

            menu = Menu.create(self.campuses[0], utils.DAYS['MON'])

            # Required if we need to get menu.id, otherwise it would return None
            # db.session.flush()

            # XXX: Use constructor here to test, rather than the appropriate method
            MenuItem(menu, translatable1.id, CourseType.SUB,
                     CourseSubType.NORMAL, Decimal('1.0'), None)
            MenuItem(menu, translatable2.id, CourseType.PASTA,
                     CourseSubType.NORMAL, Decimal('1.0'), Decimal('4.0'))
            MenuItem(menu, translatable3.id, CourseType.SOUP,
                     CourseSubType.VEGAN, Decimal('1.0'), Decimal('2.0'))

            db.session.commit()
    def test_invalid_constructors(self):
        # Test constructor of Campus model

        with self.app.app_context():
            db.session.add_all(self.campuses)

            with self.assertRaises(ValueError):
                Menu(None, utils.DAYS['MON'])

            with self.assertRaises(ValueError):
                Menu('id', utils.DAYS['MON'])

            with self.assertRaises(ValueError):
                Menu(self.campuses[0].id, None)

            with self.assertRaises(ValueError):
                Menu(self.campuses[0].id, '2020-02-20')
    def test_simple_constructors(self):
        # Test constructor of Menu model

        with self.app.app_context():
            db.session.add_all(self.campuses)

            # XXX: Use constructor here to test, rather than the appropriate method
            menu1 = Menu(self.campuses[0].id, utils.DAYS['MON'])
            menu2 = Menu(self.campuses[1].id, utils.DAYS['TUE'])
            menu3 = Menu(self.campuses[0].id, utils.DAYS['WED'])
            menu4 = Menu(self.campuses[1].id, utils.DAYS['THU'])
            menu5 = Menu(self.campuses[0].id, utils.DAYS['FRI'])

            db.session.add(menu1)
            db.session.add(menu2)
            db.session.add(menu3)
            db.session.add(menu4)
            db.session.add(menu5)
            db.session.commit()
Ejemplo n.º 5
0
    def _send_subscription_menu_message(
        user: users.User, message: messages.SubscriptionMenuMessage
    ) -> messages.MessageSendResult:
        campus = user.get_campus_for_day(message.date)
        if campus is None:
            # If no campus for selected day, just success it
            return messages.MessageSendResult.SUCCESS

        locale = user.get_locale() or translation.LANGUAGE_DUTCH

        data = message.get_prepared(campus, locale, user.get_provider_name())

        if data is None:
            menu = Menu.get_menu(campus, message.date)

            date_str = util.date_to_string(locale, menu.menu_day)

            title = localisation.REPLY_MENU_START(locale).format(
                campus=campus.name, date=date_str)
            text = komidabot.menu.get_short_menu_text(menu, message.translator,
                                                      locale, CourseType.DAILY,
                                                      CourseType.PASTA,
                                                      CourseType.GRILL)

            if text is None or text == '':
                return messages.MessageSendResult.ERROR

            data = {
                'notification': {
                    'lang': locale,
                    'badge':
                    'https://komidabot.xyz/assets/icons/notification-badge-android-72x72.png',
                    'title': title,
                    'body': text,
                    'renotify': False,
                    'requireInteraction': False,
                    'actions': [],
                    'silent': True,
                }
            }

            message.set_prepared(campus, locale, user.get_provider_name(),
                                 data)

        subscription_information = copy.deepcopy(user.get_data())
        subscription_information['endpoint'] = user.get_internal_id()

        return MessageHandler._send_notification(subscription_information,
                                                 copy.deepcopy(data))
Ejemplo n.º 6
0
    def test_add_menu_item(self):
        # Test usage of Menu.add_menu_item to check if MenuItems are constructed the same way as through their
        # constructor

        with self.app.app_context():
            db.session.add_all(self.campuses)

            translatable1, _ = self.create_translation(
                {'en': 'Translation 1: en'}, 'en', has_context=True)
            translatable2, _ = self.create_translation(
                {'en': 'Translation 2: en'}, 'en', has_context=True)
            translatable3, _ = self.create_translation(
                {'en': 'Translation 3: en'}, 'en', has_context=True)

            menu = Menu.create(self.campuses[0], utils.DAYS['MON'])

            # Required if we need to get menu.id, otherwise it would return None
            # db.session.flush()

            menu_item1 = menu.add_menu_item(translatable1, CourseType.SUB,
                                            CourseSubType.NORMAL,
                                            [CourseAttributes.SNACK], [],
                                            Decimal('1.0'), None)
            menu_item2 = menu.add_menu_item(translatable2, CourseType.PASTA,
                                            CourseSubType.NORMAL,
                                            [CourseAttributes.PASTA], [],
                                            Decimal('1.0'), Decimal('4.0'))
            menu_item3 = menu.add_menu_item(translatable3, CourseType.SOUP,
                                            CourseSubType.VEGAN,
                                            [CourseAttributes.SOUP], [],
                                            Decimal('1.0'), Decimal('2.0'))

            db.session.commit()

            self.assertEqual(len(menu.menu_items), 3)
            self.assertNotEqual(menu_item1, menu_item2)
            self.assertNotEqual(menu_item1, menu_item3)
            self.assertNotEqual(menu_item2, menu_item3)
            self.assertIn(menu_item1, menu.menu_items)
            self.assertIn(menu_item2, menu.menu_items)
            self.assertIn(menu_item3, menu.menu_items)
    def test_create_no_add_first(self):
        # Tests usage of Menu.create with add_to_db=False, and manually adding it afterwards

        with self.app.app_context():
            db.session.add_all(self.campuses)

            translatable1, _ = self.create_translation(
                {'en': 'Translation 1: en'}, 'en', has_context=True)
            translatable2, _ = self.create_translation(
                {'en': 'Translation 2: en'}, 'en', has_context=True)
            translatable3, _ = self.create_translation(
                {'en': 'Translation 3: en'}, 'en', has_context=True)

            menu = Menu.create(self.campuses[0],
                               utils.DAYS['MON'],
                               add_to_db=False)

            menu_item1 = menu.add_menu_item(translatable1, CourseType.SUB,
                                            CourseSubType.NORMAL,
                                            [CourseAttributes.SNACK], [],
                                            Decimal('1.0'), None)
            menu_item2 = menu.add_menu_item(translatable2, CourseType.PASTA,
                                            CourseSubType.NORMAL,
                                            [CourseAttributes.PASTA], [],
                                            Decimal('1.0'), Decimal('4.0'))
            menu_item3 = menu.add_menu_item(translatable3, CourseType.SOUP,
                                            CourseSubType.VEGAN,
                                            [CourseAttributes.SOUP], [],
                                            Decimal('1.0'), Decimal('2.0'))

            db.session.add(menu)
            db.session.commit()

            items = MenuItem.query.filter_by(menu_id=menu.id).order_by(
                MenuItem.id).all()

            self.assertIn(menu_item1, items)
            self.assertIn(menu_item2, items)
            self.assertIn(menu_item3, items)
Ejemplo n.º 8
0
    def send_message(self, user, message: messages.Message) -> messages.MessageSendResult:
        if user.id.provider != PROVIDER_ID:
            raise ValueError('User id is not for Stub Provider')

        if isinstance(message, messages.TextMessage):
            if user.id not in self.message_log:
                self.message_log[user.id] = []

            text = message.text

            self.message_log[user.id].append(text)

            return messages.MessageSendResult.SUCCESS
        elif isinstance(message, messages.MenuMessage):
            if user.id not in self.message_log:
                self.message_log[user.id] = []

            text = komidabot.menu.get_menu_text(message.menu, message.translator, user.get_locale())

            self.message_log[user.id].append(text)

            return messages.MessageSendResult.SUCCESS
        elif isinstance(message, messages.SubscriptionMenuMessage):
            if user.id not in self.message_log:
                self.message_log[user.id] = []

            campus = user.get_campus_for_day(message.date)
            menu = Menu.get_menu(campus, message.date)

            text = komidabot.menu.get_menu_text(menu, message.translator, user.get_locale())

            self.message_log[user.id].append(text)

            return messages.MessageSendResult.SUCCESS
        else:
            return messages.MessageSendResult.UNSUPPORTED
Ejemplo n.º 9
0
    def test_get_translation(self):
        # Test that translation requests are passed through

        with self.app.app_context():
            trans = self.translator

            db.session.add_all(self.campuses)

            translatable1, _ = self.create_translation(
                {
                    'en': 'Translation 1: en',
                    'nl': 'Translation 1: nl'
                },
                'en',
                has_context=True)
            translatable2, _ = self.create_translation(
                {
                    'en': 'Translation 2: en',
                    'nl': 'Translation 2: nl'
                },
                'en',
                has_context=True)
            translatable3, _ = self.create_translation(
                {
                    'en': 'Translation 3: en',
                    'nl': 'Translation 3: nl'
                },
                'en',
                has_context=True)

            menu = Menu.create(self.campuses[0], utils.DAYS['MON'])

            # Required if we need to get menu.id, otherwise it would return None
            # db.session.flush()

            menu_item1 = menu.add_menu_item(translatable1, CourseType.SUB,
                                            CourseSubType.NORMAL,
                                            [CourseAttributes.SNACK], [],
                                            Decimal('1.0'), None)
            menu_item2 = menu.add_menu_item(translatable2, CourseType.PASTA,
                                            CourseSubType.NORMAL,
                                            [CourseAttributes.PASTA], [],
                                            Decimal('1.0'), Decimal('4.0'))
            menu_item3 = menu.add_menu_item(translatable3, CourseType.SOUP,
                                            CourseSubType.VEGAN,
                                            [CourseAttributes.SOUP], [],
                                            Decimal('1.0'), Decimal('2.0'))

            db.session.commit()

            self.assertEqual(menu_item1.get_translation('en', trans),
                             translatable1.get_translation('en', trans))
            self.assertEqual(menu_item1.get_translation('nl', trans),
                             translatable1.get_translation('nl', trans))
            self.assertEqual(menu_item1.get_translation('fr', trans),
                             translatable1.get_translation('fr', trans))
            self.assertEqual(menu_item2.get_translation('en', trans),
                             translatable2.get_translation('en', trans))
            self.assertEqual(menu_item2.get_translation('nl', trans),
                             translatable2.get_translation('nl', trans))
            self.assertEqual(menu_item2.get_translation('fr', trans),
                             translatable2.get_translation('fr', trans))
            self.assertEqual(menu_item3.get_translation('en', trans),
                             translatable3.get_translation('en', trans))
            self.assertEqual(menu_item3.get_translation('nl', trans),
                             translatable3.get_translation('nl', trans))
            self.assertEqual(menu_item3.get_translation('fr', trans),
                             translatable3.get_translation('fr', trans))
Ejemplo n.º 10
0
    def trigger_received(self, trigger: triggers.Trigger):
        with self.lock:  # TODO: Maybe only lock on critical sections?
            app = get_app()
            verbose = app.config.get('VERBOSE')

            if verbose:
                print('Komidabot received a trigger: {}'.format(type(trigger).__name__), flush=True)
                print(repr(trigger), flush=True)

            if isinstance(trigger, triggers.SubscriptionTrigger):
                dispatch_daily_menus(trigger)
                return

            if triggers.AtAdminAspect in trigger:
                return  # Don't process messages targeted at the admin

            locale = None
            message_handled = False

            # XXX: Disabled once more because responses aren't reliably in the language the user expects it to be
            # if triggers.LocaleAspect in trigger and trigger[triggers.LocaleAspect].confidence > 0.9:
            #     locale = trigger[triggers.LocaleAspect].locale

            if triggers.SenderAspect in trigger:
                sender = trigger[triggers.SenderAspect].sender
                campuses = Campus.get_all()

                # This ensures that when a user is marked as reachable in case they were unreachable at some point
                # TODO: We no longer mark users as reachable, need to think over the proper course of action
                # if sender.mark_reachable():
                #     db.session.commit()

                if locale is None:
                    locale = sender.get_locale()

                if triggers.NewUserAspect in trigger:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_NEW_USER(locale)))
                    msg = localisation.REPLY_INSTRUCTIONS(locale).format(
                        campuses=', '.join([campus.short_name.lower() for campus in campuses if campus.active])
                    )
                    sender.send_message(messages.TextMessage(trigger, msg))
                    sender.set_is_notified_new_site(True)
                    db.session.commit()

                    message_handled = True

                # TODO: Is this really how we want to handle input?
                #       Maybe we can add an IntentAspect, where the intent is the desired action the bot should take
                #       next? Ex. intents: admin message, get help, get menu, set preference (language, subscriptions)
                if isinstance(trigger, triggers.TextTrigger):
                    text = trigger.text
                    split = text.lower().split(' ')

                    if sender.is_admin():
                        if split[0] == 'setup':
                            if app.config.get('PRODUCTION'):
                                sender.send_message(messages.TextMessage(trigger, 'Not running setup on production'))
                                return
                            recreate_db()
                            create_standard_values()
                            import_dump(app.config['DUMP_FILE'])
                            sender.send_message(messages.TextMessage(trigger, 'Setup done'))
                            return
                        elif split[0] == 'update':
                            sender.send_message(messages.TextMessage(trigger, 'Updating menus...'))
                            update_menus(*split[1:])
                            sender.send_message(messages.TextMessage(trigger, 'Done updating menus...'))
                            return
                        elif split[0] == 'psid':  # TODO: Deprecated?
                            sender.send_message(messages.TextMessage(trigger, 'Your ID is {}'.format(sender.id.id)))
                            return

                    # TODO: Allow users to send more manual commands
                    #       See also the note prefacing the containing block
                    if not message_handled and split[0] == 'help':
                        msg = localisation.REPLY_INSTRUCTIONS(locale).format(
                            campuses=', '.join([campus.short_name.lower() for campus in campuses if campus.active])
                        )
                        sender.send_message(messages.TextMessage(trigger, msg))
                        return

                if app.config.get('COVID19_DISABLED'):
                    sender.send_message(messages.TextMessage(trigger, localisation.COVID19_UNAVAILABLE(locale)))
                    return

                requested_dates = []
                default_date = False

                if triggers.DatetimeAspect in trigger:
                    date_times = trigger[triggers.DatetimeAspect]
                    # TODO: Date parsing needs improving
                    requested_dates, invalid_date = nlp_dates.extract_days(date_times)

                    if invalid_date:
                        sender.send_message(messages.TextMessage(trigger, localisation.REPLY_INVALID_DATE(locale)))
                        return

                if len(requested_dates) > 1:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_TOO_MANY_DAYS(locale)))
                    return
                elif len(requested_dates) == 1:
                    date = requested_dates[0]
                else:
                    default_date = True
                    date = datetime.datetime.now().date()

                # TODO: How about getting the menu for the next day after a certain time of day?
                #       Only if we're returning the default day

                day = Day(date.isoweekday())

                if day == Day.SATURDAY or day == Day.SUNDAY:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_WEEKEND(locale)))
                    return

                requested_campuses = []
                default_campus = False

                if isinstance(trigger, triggers.TextTrigger):
                    text = trigger.text.lower()
                    for campus in campuses:
                        if not campus.active:
                            continue

                        for kw in campus.get_keywords():
                            if text.count(kw) > 0:
                                requested_campuses.append(campus)
                                break  # Prevent the same campus from being added multiple times

                if len(requested_campuses) > 1:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_TOO_MANY_CAMPUSES(locale)))
                    return
                elif len(requested_campuses) == 1:
                    campus = requested_campuses[0]
                else:
                    default_campus = True
                    campus = sender.get_campus_for_day(date)

                    if campus is None:  # User has no campus for the specified day
                        campus = Campus.get_by_short_name('cmi')

                if not campus.active:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_CAMPUS_INACTIVE(locale)
                                                             .format(campus=campus.name)))
                    return

                if message_handled and default_campus and default_date:
                    if isinstance(trigger, triggers.TextTrigger):
                        for word in ['menu', 'lunch', 'eten']:
                            if word in trigger.text:
                                break
                        else:
                            return
                    else:
                        return

                # if default_date and default_campus:
                #     if isinstance(trigger, triggers.TextTrigger):
                #         sender.send_message(messages.TextMessage(trigger,
                # localisation.REPLY_NO_DATE_OR_CAMPUS(locale)))
                #         msg = localisation.REPLY_INSTRUCTIONS(locale).format(
                #             campuses=', '.join([campus.short_name for campus in campuses])
                #         )
                #         sender.send_message(messages.TextMessage(trigger, msg))
                #         return
                #
                #     # User did not send a text message, so we'll continue anyway

                if not default_campus:
                    sender.set_campus_for_day(campus, date)
                    db.session.commit()

                if sender.get_is_notified_new_site() is False and sender.is_feature_active('new_site_notifications'):
                    if sender.send_message(messages.TextMessage(trigger, localisation.MESSAGE_NEW_SITE(locale))) \
                            == messages.MessageSendResult.SUCCESS:
                        sender.set_is_notified_new_site(True)
                        db.session.commit()

                closed = ClosingDays.find_is_closed(campus, date)

                if closed:
                    translation = closed.translatable.get_translation(locale, app.translator)

                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_CAMPUS_CLOSED(locale)
                                                             .format(campus=campus.name, date=str(date),
                                                                     reason=translation.translation)))
                    return

                # menu = komidabot.menu.prepare_menu_text(campus, date, app.translator, locale)
                menu = Menu.get_menu(campus, date)

                if menu is None:
                    sender.send_message(messages.TextMessage(trigger, localisation.REPLY_NO_MENU(locale)
                                                             .format(campus=campus.name, date=str(date))))
                else:
                    # sender.send_message(messages.TextMessage(trigger, menu))
                    sender.send_message(messages.MenuMessage(trigger, menu, app.translator))