Download test_flows.py from rairo/RentNow: direct link, hf CLI and curl.
- Browser
- Download file 8.99 kB
-
https://huggingface.co/spaces/rairo/RentNow/resolve/main/test_flows.py
- Command line
-
hf download hf://spaces/rairo/RentNow/test_flows.py
-
curl -L -o test_flows.py https://huggingface.co/spaces/rairo/RentNow/resolve/main/test_flows.py
8.99 kB
| """Conversation regressions. No credentials, network calls or real payments.""" | |
| import os | |
| import unittest | |
| from unittest.mock import patch | |
| os.environ.pop('FIREBASE', None) | |
| os.environ.pop('GOOGLE_API_KEY', None) | |
| with patch('dotenv.load_dotenv'): | |
| import main as bot | |
| import utility as U | |
| import whatsapp_client as wa | |
| class RentalFlows(unittest.TestCase): | |
| def setUp(self): | |
| self.phone = '+263770000099' | |
| U.db = None | |
| U._mem.clear() | |
| self.messages = [] | |
| self.images = [] | |
| self.patches = [ | |
| patch.object(wa, 'send_text_message', side_effect=lambda m, text: self.messages.append(text) or True), | |
| patch.object(wa, 'send_image_message', side_effect=lambda m, url, caption='': self.images.append((url, caption)) or True), | |
| patch.object(wa, 'send_document_message', return_value=True), | |
| patch.object(U, 'gemini_ack_search', return_value='Matching homes'), | |
| patch.object(U, 'gemini_detect_intent', return_value={'intent': 'SEARCH', 'criteria': {}}), | |
| ] | |
| bot.logger.disabled = True | |
| for p in self.patches: | |
| p.start() | |
| self.addCleanup(p.stop) | |
| def say(self, text): | |
| bot._process_text_inner(text, self.phone) | |
| return bot.get_s(self.phone) | |
| def browse(self): | |
| U.save_profile(self.phone, {'name': 'Test Renter'}) | |
| bot.show_results(self.phone, {}) | |
| return bot.get_s(self.phone) | |
| def test_all_text_menu_options_visible(self): | |
| wa.send_buttons_as_text(self.phone, 'Features', bot.FIND_AMENS) | |
| self.assertIn('8. Other (type)', self.messages[-1]) | |
| def test_guided_any_choices_reach_summary(self): | |
| self.say('menu') | |
| self.say('1') | |
| self.assertEqual(self.say('1')['step'], 'type') | |
| self.assertEqual(self.say('2')['step'], 'suburb') | |
| self.assertEqual(self.say('8')['step'], 'budget') | |
| self.assertEqual(self.say('6')['step'], 'amen') | |
| state = self.say('skip') | |
| self.assertEqual(state['step'], 'summary') | |
| self.assertEqual(state['data']['type'], 'cottage') | |
| self.assertEqual(self.say('1')['menu'], 'browse') | |
| def test_budget_range_preserves_lower_bound(self): | |
| bot.start_find(self.phone, {'type': 'cottage', 'suburb': 'Avondale'}) | |
| self.say('2') | |
| state = self.say('7') | |
| self.assertEqual(state['data']['min_budget'], 200) | |
| self.assertEqual(state['data']['max_budget'], 400) | |
| def test_over_800_has_no_upper_bound(self): | |
| bot.start_find(self.phone, {'type': 'house', 'suburb': 'Harare'}) | |
| self.assertEqual(self.say('5')['step'], 'amen') | |
| state = self.say('7') | |
| self.assertGreater(state['data']['min_budget'], 800) | |
| self.assertEqual(state['data']['max_budget'], 0) | |
| def test_compact_features(self): | |
| self.assertEqual(bot._pick_multi('123', bot.FIND_AMENS), ['Borehole', 'Solar', 'Parking']) | |
| self.assertEqual(bot._pick_multi('7', bot.FIND_AMENS), []) | |
| def test_natural_input_from_feature_step(self): | |
| bot.start_find(self.phone, {'type': 'cottage', 'suburb': 'Westgate', 'max_budget': 400}) | |
| state = self.say('1 bed cottage in Mount Pleasant with borehole solar parking under $400') | |
| self.assertEqual(state['step'], 'summary') | |
| self.assertEqual(state['data']['type'], 'cottage') | |
| self.assertEqual(state['data']['suburb'], 'Mount Pleasant') | |
| self.assertEqual(len(state['data']['amenities']), 3) | |
| def test_edit_budget_returns_to_summary(self): | |
| bot._finish_find(self.phone, {'type': 'cottage', 'suburb': 'Avondale', 'max_budget': 400, 'features_chosen': True}) | |
| self.say('2') | |
| self.assertEqual(self.say('3')['step'], 'budget') | |
| state = self.say('350') | |
| self.assertEqual(state['step'], 'summary') | |
| self.assertEqual(state['data']['max_budget'], 350) | |
| def test_first_result_is_free_preview(self): | |
| self.browse() | |
| state = self.say('1') | |
| self.assertEqual(state['screen'], 'preview') | |
| self.assertFalse(any(k.startswith('pay:') for k in U._mem)) | |
| self.assertTrue(self.images) | |
| def test_payment_then_selected_unlock_and_repeat_preview(self): | |
| original = self.browse() | |
| lid = original['results'][0] | |
| self.say('1') | |
| state = self.say('1') | |
| self.assertEqual(state['menu'], 'await_pay') | |
| self.assertEqual(state['results'], original['results']) | |
| state = self.say('1') | |
| self.assertEqual(state['screen'], 'pass_ready') | |
| self.assertEqual(U.active_pass(self.phone)['credits_used'], 0) | |
| state = self.say('1') | |
| self.assertEqual(state['screen'], 'full') | |
| self.assertTrue(U.is_unlocked(self.phone, lid)) | |
| self.assertEqual(U.active_pass(self.phone)['credits_used'], 1) | |
| self.say('back to results') | |
| self.say('1') | |
| self.assertEqual(U.active_pass(self.phone)['credits_used'], 1) | |
| def test_account_capture_retains_selected_property(self): | |
| bot.show_results(self.phone, {}) | |
| original = bot.get_s(self.phone) | |
| self.assertEqual(self.say('unlock 2')['menu'], 'need_name') | |
| state = self.say('Test Renter') | |
| self.assertEqual(state['menu'], 'await_pay') | |
| self.assertEqual(state['results'], original['results']) | |
| self.assertEqual(state['pending_unlock'], original['results'][1]) | |
| def test_cancel_preserves_results_without_pass(self): | |
| original = self.browse() | |
| self.say('unlock 2') | |
| state = self.say('2') | |
| self.assertEqual(state['menu'], 'browse') | |
| self.assertEqual(state['results'], original['results']) | |
| self.assertIsNone(U.active_pass(self.phone)) | |
| def test_continue_after_pass_does_not_consume_credit(self): | |
| self.browse() | |
| self.say('unlock 2') | |
| self.say('1') | |
| state = self.say('2') | |
| self.assertEqual(state['screen'], 'results') | |
| self.assertEqual(U.active_pass(self.phone)['credits_used'], 0) | |
| def test_full_gallery_and_viewing_request(self): | |
| original = self.browse() | |
| lid = original['results'][0] | |
| self.say('unlock 1') | |
| self.say('1') | |
| self.say('1') | |
| state = self.say('1') | |
| self.assertEqual(state['kind'], 'viewing') | |
| self.assertEqual(U._mem['pay:' + state['payment_id']]['amount_cents'], 500) | |
| self.assertIn('$2 goes to the landlord', '\n'.join(self.messages)) | |
| state = self.say('1') | |
| self.assertEqual(state['menu'], 'browse') | |
| viewings = [v for k, v in U._mem.items() if k.startswith('view:')] | |
| self.assertEqual(len(viewings), 1) | |
| self.assertEqual(viewings[0]['status'], 'paid_appointment_pending') | |
| self.assertEqual(viewings[0]['listing_id'], lid) | |
| def test_locked_property_cannot_buy_viewing(self): | |
| self.browse() | |
| self.say('1') | |
| self.say('request viewing') | |
| self.assertFalse(any(k.startswith('pay:') for k in U._mem)) | |
| def test_more_handles_listing_without_ranking(self): | |
| self.browse() | |
| state = self.say('more') | |
| self.assertEqual(state['shown'], 6) | |
| self.say('back') | |
| self.assertIn('NEW', self.messages[-1]) | |
| def test_constraints_filter_all_required_features_and_minimum(self): | |
| fixture = [dict(id='a', type='cottage', suburb='Mt Pleasant', rent_cents=35000, amenities=['solar', 'parking']), | |
| dict(id='b', type='cottage', suburb='Mount Pleasant', rent_cents=15000, amenities=['solar']), | |
| dict(id='c', type='cottage', suburb='Mount Pleasant', rent_cents=85000, amenities=['solar', 'parking'])] | |
| with patch.object(U, 'get_listings', return_value=fixture): | |
| rows = U.search_listings({'suburb': 'Mount Pleasant', 'min_budget': 200, 'max_budget': 400, 'amenities': ['solar', 'parking']}) | |
| self.assertEqual([r['id'] for r in rows], ['a']) | |
| def test_unknown_suburb_and_other_features(self): | |
| bot.start_find(self.phone, {'type': 'cottage'}) | |
| self.say('7') | |
| self.say('Chishawasha') | |
| self.say('400') | |
| self.say('8') | |
| state = self.say('swimming pool') | |
| self.assertEqual(state['step'], 'summary') | |
| self.assertEqual(state['data']['amenities'], ['swimming pool']) | |
| def test_pass_expires_and_exhausts(self): | |
| from datetime import datetime, timezone, timedelta | |
| p = U.issue_pass(self.phone, 'test-payment') | |
| p['expires_at'] = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat() | |
| self.assertIsNone(U.active_pass(self.phone)) | |
| p['expires_at'] = (datetime.now(timezone.utc) + timedelta(days=1)).isoformat() | |
| for lid in ['a', 'b', 'c']: | |
| self.assertTrue(U.consume_unlock(self.phone, lid)[0]) | |
| self.assertIsNone(U.active_pass(self.phone)) | |
| self.assertFalse(U.consume_unlock(self.phone, 'd')[0]) | |
| self.assertTrue(U.consume_unlock(self.phone, 'a')[0]) | |
| if __name__ == '__main__': | |
| unittest.main() | |