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()
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()
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))
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)
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
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))
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))