def test_bid_higher_than_maximum_price_fails(self): strategy = HighestContributionBiddingStrategy( minimum_bid_amount=Decimal(5), maximum_price=Decimal(100)) strategy.auction = Mock() strategy.start() bidder_1 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is micheal's son", first_name="Billie", last_name="Jean", ) bidder_2 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is not my son", first_name="Billie", last_name="Jean", ) bidder_1.persistent_user = Mock(id=1) bidder_2.persistent_user = Mock(id=2) bidder_1.initial_balance = 60 bidder_2.initial_balance = 60 strategy.bid(bidder=bidder_1, amount=Decimal(40)) strategy.bid(bidder=bidder_2, amount=Decimal(40)) with self.assertRaises(InsufficientBalanceError): strategy.bid(bidder=bidder_2, amount=Decimal(40))
def test_bid_bidder_has_insufficient_balance(self): strategy = IncrementBiddingStrategy(initial_price=Decimal(3.00)) strategy.auction = Mock() strategy.start() bidder_1 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is micheal's son", first_name="Billie", last_name="Jean", ) bidder_1.initial_balance = 10 with self.assertRaises(InsufficientBalanceError): strategy.bid(bidder=bidder_1, amount=Decimal(20)) strategy.stop()
def get_or_create_runtime_user(self, user: User): from bidpazari.core.runtime.user import RuntimeUser runtime_user = self.get_user_by_id(user.id) if runtime_user is None: runtime_user = RuntimeUser.from_persistent_user(user) runtime_user.connect() return runtime_user
async def login(context: CommandContext, username: str, password: str): if context.runtime_user: raise CommandFailed("You are already logged in!") try: user = User.objects.get(username=username) except User.DoesNotExist: raise CommandFailed("Incorrect username or password.") if user.check_password(password): runtime_user = RuntimeUser.from_persistent_user(user) else: raise CommandFailed("Incorrect username or password.") runtime_user.connect() context.runtime_user = runtime_user return {"user": {"id": runtime_user.id}}
def test_get_current_winner_and_amount(self): strategy = HighestContributionBiddingStrategy( minimum_bid_amount=Decimal(5), maximum_price=Decimal(150)) strategy.auction = Mock() strategy.start() bidder_1 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is micheal's son", first_name="Billie", last_name="Jean", ) bidder_2 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is not my son", first_name="Billie", last_name="Jean", ) bidder_1.persistent_user = Mock(id=1) bidder_2.persistent_user = Mock(id=2) bidder_1.initial_balance = 60 bidder_2.initial_balance = 60 self.assertEqual(strategy.get_current_winner_and_amount(), (None, None)) self.assertEqual(strategy.get_current_price(), Decimal(0)) strategy.bid(bidder=bidder_1, amount=Decimal(40)) self.assertEqual(strategy.get_current_winner_and_amount(), (bidder_1, Decimal(40))) strategy.bid(bidder=bidder_1, amount=Decimal(20)) self.assertEqual(strategy.get_current_winner_and_amount(), (bidder_1, Decimal(60))) strategy.bid(bidder=bidder_2, amount=Decimal(50)) self.assertEqual(strategy.get_current_winner_and_amount(), (bidder_1, Decimal(60)))
async def create_user( context: CommandContext, username: str, password: str, email: str, first_name: str, last_name: str, ): """ Creates an user and immediately logs in. """ user = User.objects.create_user( username=username, password=password, email=email, first_name=first_name, last_name=last_name, ) runtime_user = RuntimeUser.from_persistent_user(user) context.runtime_user = runtime_user return {'user': {'id': user.id}}
def test_get_current_winner_and_amount(self): strategy = IncrementBiddingStrategy(initial_price=Decimal(3.00)) strategy.auction = Mock() strategy.start() self.assertEqual((None, None), strategy.get_current_winner_and_amount()) bidder_1 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is micheal's son", first_name="Billie", last_name="Jean", ) bidder_2 = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is not my son", first_name="Billie", last_name="Jean", ) bidder_1.persistent_user = Mock(id=1) bidder_2.persistent_user = Mock(id=2) bidder_1.initial_balance = 10 bidder_2.initial_balance = 20 self.assertEqual(Decimal(3), strategy.get_current_price()) strategy.bid(bidder=bidder_1, amount=Decimal(4)) strategy.bid(bidder=bidder_2, amount=Decimal(5)) self.assertEqual(Decimal(5), strategy.get_current_price()) strategy.bid(bidder=bidder_1, amount=Decimal(6)) strategy.stop() self.assertEqual((bidder_1, Decimal(6)), strategy.get_current_winner_and_amount())
def test_highest_contribution_bidding_maximum_price(self): john = RuntimeUser( username="******", email="*****@*****.**", password_raw="11441144", first_name="John", last_name="Sims", ) jimmy = RuntimeUser( username="******", email="*****@*****.**", password_raw="12345678", first_name="James", last_name="Hetfield", ) jack = RuntimeUser( username="******", email="*****@*****.**", password_raw="i_secretly_like_johnny_walker", first_name="Jack", last_name="Daniels", ) john.persist() jimmy.persist() jack.persist() john.connect() jimmy.connect() jack.connect() john.add_balance_transaction(Decimal(150.00)) jimmy.add_balance_transaction(Decimal(140.00)) jack.add_balance_transaction(Decimal(130.00)) scarf = Item.objects.create(title="Scarf", description="A really cool scarf", item_type="Clothing") jimmy_has_scarf = UserHasItem.objects.create( item=scarf, user=jimmy.persistent_user) auction = runtime_manager.create_auction( uhi=jimmy_has_scarf, bidding_strategy_identifier="highest_contribution", minimum_bid_amount=Decimal(2.00), maximum_price=Decimal(100), ) auction.start() auction.bid(jack, Decimal(30.00)) auction.bid(john, Decimal(50.00)) auction.bid(jack, Decimal(30.00)) print(auction.auction_report) print(auction.auction_history) john.persistent_user.refresh_from_db() jimmy.persistent_user.refresh_from_db() jack.persistent_user.refresh_from_db() jimmy_has_scarf.refresh_from_db() john.disconnect() jimmy.disconnect() jack.disconnect() self.assertTrue(jimmy_has_scarf.is_sold) self.assertEqual(john.initial_balance, Decimal(100)) self.assertEqual(jimmy.initial_balance, Decimal(250)) self.assertEqual(jack.initial_balance, Decimal(70)) self.assertEqual(john.persistent_user.balance, Decimal(100)) self.assertEqual(jimmy.persistent_user.balance, Decimal(250)) self.assertEqual(jack.persistent_user.balance, Decimal(70))
def test_decrement_bidding_with_no_winners(self): pass john = RuntimeUser( username="******", email="*****@*****.**", password_raw="11441144", first_name="John", last_name="Sims", ) jimmy = RuntimeUser( username="******", email="*****@*****.**", password_raw="12345678", first_name="James", last_name="Hetfield", ) john.persist() jimmy.persist() john.connect() jimmy.connect() john.add_balance_transaction(Decimal(100.00)) jimmy.add_balance_transaction(Decimal(40.00)) scarf = Item.objects.create(title="Scarf", description="A really cool scarf", item_type="Clothing") jimmy_has_scarf = UserHasItem.objects.create( item=scarf, user=jimmy.persistent_user) auction = runtime_manager.create_auction( uhi=jimmy_has_scarf, bidding_strategy_identifier="decrement", initial_price=Decimal(150.00), price_decrement_rate=Decimal(50.00), tick_ms=20, ) auction.start() self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(150.00)) sleep(25 / 1000) self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(100.00)) sleep(25 / 1000) self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(50.00)) sleep(25 / 1000) self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(0.00)) sleep(250 / 1000) print(auction.auction_report) print(auction.auction_history) john.persistent_user.refresh_from_db() jimmy.persistent_user.refresh_from_db() jimmy_has_scarf.refresh_from_db() john.disconnect() jimmy.disconnect() self.assertFalse(jimmy_has_scarf.is_sold) self.assertEqual(john.initial_balance, Decimal(100)) self.assertEqual(jimmy.initial_balance, Decimal(40)) self.assertEqual(john.persistent_user.balance, Decimal(100)) self.assertEqual(jimmy.persistent_user.balance, Decimal(40))
def test_decrement_bidding_and_auction_watching(self): john = RuntimeUser( username="******", email="*****@*****.**", password_raw="11441144", first_name="John", last_name="Sims", ) jimmy = RuntimeUser( username="******", email="*****@*****.**", password_raw="12345678", first_name="James", last_name="Hetfield", ) jimmy.watch_method = lambda *args, **kwargs: None patcher = patch.object(jimmy, "watch_method") m_jimmy_watch_method = patcher.start() john.persist() jimmy.persist() john.connect() jimmy.connect() john.add_balance_transaction(Decimal(100.00)) jimmy.add_balance_transaction(Decimal(40.00)) scarf = Item.objects.create(title="Scarf", description="A really cool scarf", item_type="Clothing") jimmy_has_scarf = UserHasItem.objects.create( item=scarf, user=jimmy.persistent_user) auction = runtime_manager.create_auction( uhi=jimmy_has_scarf, bidding_strategy_identifier="decrement", initial_price=Decimal(150.00), price_decrement_rate=Decimal(25.00), tick_ms=200, ) auction.register_user_to_updates(jimmy.watch_method) # this is mocked auction.start() self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(150.00)) with self.assertRaises(InsufficientBalanceError): auction.bid( john ) # no amount means that user is bidding to the current price sleep(250 / 1000) self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(125.00)) sleep(250 / 1000) self.assertEqual(auction.bidding_strategy.get_current_price(), Decimal(100.00)) auction.bid(john) # this terminates the auction m_jimmy_watch_method.assert_has_calls([ call(event_type="auction_started"), call(event_type="price_decremented", new_price=Decimal(125.00)), call(event_type="price_decremented", new_price=Decimal(100.00)), call(event_type="bid_received"), # TODO Fix this some day call(event_type="price_decremented", new_price=Decimal(75.00)), call(event_type="auction_stopped"), ]) print(auction.auction_report) print(auction.auction_history) john.persistent_user.refresh_from_db() jimmy.persistent_user.refresh_from_db() jimmy_has_scarf.refresh_from_db() john.disconnect() jimmy.disconnect() self.assertTrue(jimmy_has_scarf.is_sold) self.assertEqual(john.initial_balance, Decimal(0)) self.assertEqual(jimmy.initial_balance, Decimal(140)) self.assertEqual(john.persistent_user.balance, Decimal(0)) self.assertEqual(jimmy.persistent_user.balance, Decimal(140)) patcher.stop()
def test_increment_bidding_exceptions(self): john = RuntimeUser( username="******", email="*****@*****.**", password_raw="11441144", first_name="John", last_name="Sims", ) jimmy = RuntimeUser( username="******", email="*****@*****.**", password_raw="12345678", first_name="James", last_name="Hetfield", ) jack = RuntimeUser( username="******", email="*****@*****.**", password_raw="i_secretly_like_johnny_walker", first_name="Jack", last_name="Daniels", ) john.persist() jimmy.persist() jack.persist() john.connect() jimmy.connect() jack.connect() john.add_balance_transaction(Decimal(50.00)) jimmy.add_balance_transaction(Decimal(40.00)) jack.add_balance_transaction(Decimal(30.00)) scarf = Item.objects.create(title="Scarf", description="A really cool scarf", item_type="Clothing") jimmy_has_scarf = UserHasItem.objects.create( item=scarf, user=jimmy.persistent_user) auction = runtime_manager.create_auction( uhi=jimmy_has_scarf, bidding_strategy_identifier="increment", initial_price=Decimal(5.00), minimum_increment=Decimal(2.00), maximum_price=Decimal(15.00), ) # Cannot bid while auction is closed with self.assertRaises(BiddingNotAllowed) as cm: auction.bid(john, Decimal(123.00)) self.assertEqual(cm.exception.reason, BiddingErrorReason.AuctionClosed) auction.start() # Cannot bid in own auction with self.assertRaises(BiddingNotAllowed) as cm: auction.bid(jimmy, Decimal(1.00)) self.assertEqual(cm.exception.reason, BiddingErrorReason.OwnAuction) # Lower than initial price with self.assertRaises(BiddingNotAllowed) as cm: auction.bid(john, Decimal(1.00)) self.assertEqual(cm.exception.reason, BiddingErrorReason.InsufficientAmount) # Lower than minimum increment with self.assertRaises(BiddingNotAllowed) as cm: auction.bid(john, Decimal(6.00)) self.assertEqual(cm.exception.reason, BiddingErrorReason.InsufficientAmount) # Higher than reservable balance with self.assertRaises(InsufficientBalanceError): auction.bid(john, Decimal(666.00)) # Happy path auction.bid(john, Decimal(7.00)) self.assertEqual(john.reservable_balance, Decimal(43.00)) auction.bid(john, Decimal(50.00)) # This closes the auction. self.assertEqual(john.reservable_balance, Decimal(0.00)) print(auction.auction_report) print(auction.auction_history) # Cannot bid while auction is closed with self.assertRaises(BiddingNotAllowed) as cm: auction.bid(john, Decimal(123.00)) self.assertEqual(cm.exception.reason, BiddingErrorReason.AuctionClosed) john.persistent_user.refresh_from_db() jimmy.persistent_user.refresh_from_db() jack.persistent_user.refresh_from_db() jimmy_has_scarf.refresh_from_db() john.disconnect() jimmy.disconnect() jack.disconnect() self.assertTrue(jimmy_has_scarf.is_sold) self.assertEqual(john.initial_balance, Decimal(0)) self.assertEqual(jimmy.initial_balance, Decimal(90)) self.assertEqual(jack.initial_balance, Decimal(30)) self.assertEqual(john.persistent_user.balance, Decimal(0)) self.assertEqual(jimmy.persistent_user.balance, Decimal(90)) self.assertEqual(jack.persistent_user.balance, Decimal(30))
def test_all(self): user = RuntimeUser( username="******", email="*****@*****.**", password_raw="the kid is micheal's son", first_name="Billie", last_name="Jean", ) user.persist() # Test verification number and verification flows actual_verification_number = user.persistent_user.verification_number self.assertEqual(user.persistent_user.verification_status, User.UNVERIFIED) with self.assertRaises(UserVerificationError): user.verify("this is obviously not the correct validation number") self.assertEqual(user.persistent_user.verification_status, User.UNVERIFIED) user.verify(actual_verification_number) self.assertEqual(user.persistent_user.verification_status, User.VERIFIED) # Test password change flows with self.assertRaises(InvalidPassword): user.change_password( new_password="******", old_password="******", ) newly_generated_password = user.change_password(new_password=None, old_password=None) self.assertEqual(len(newly_generated_password), 16) user.change_password(new_password="******", old_password=newly_generated_password) # Test item listing scenarios pencil = Item.objects.create(title="Pencil", item_type="Stationary") notepad = Item.objects.create(title="Notepad", item_type="Stationary") coffee_mug = Item.objects.create(title="Coffee Mug", item_type="Kitchen") towel = Item.objects.create(title="Towel", item_type="Kitchen", on_sale=True) watch = Item.objects.create(title="Watch", item_type="Accessory", on_sale=True) items = [pencil, notepad, coffee_mug, towel, watch] for item in items: UserHasItem.objects.create(user=user.persistent_user, item=item, is_sold=False) self.assertSetEqual(user.list_items(), {pencil, notepad, coffee_mug, towel, watch}) self.assertSetEqual(user.list_items(on_sale=True), {towel, watch}) self.assertSetEqual(user.list_items(on_sale=False), {pencil, notepad, coffee_mug}) self.assertSetEqual(user.list_items(item_type="Kitchen"), {coffee_mug, towel}) self.assertSetEqual(user.list_items(item_type="Kitchen", on_sale=True), {towel}) # Test balance user.add_balance_transaction(Decimal(30.00)) self.assertEqual(user.initial_balance, Decimal(30.00)) user.add_balance_transaction(Decimal(-5.00)) self.assertEqual(user.initial_balance, Decimal(25.00)) # Test transaction history john = RuntimeUser( username="******", email="*****@*****.**", password_raw="11441144", first_name="John", last_name="Sims", ) john.persist() # towel and watch are already on sale at this point Transaction.objects.create( source=user.persistent_user, destination=john.persistent_user, amount=Decimal(140.00), item=notepad, ) Transaction.objects.create( source=john.persistent_user, destination=user.persistent_user, amount=Decimal(230.00), item=coffee_mug, ) print(user.transaction_history) # Test item watching capabilities watcher_method = lambda item_on_auction, starting_price: None user.watcher_method = watcher_method patcher = patch.object(user, "watcher_method") m_user_watcher_method = patcher.start() user.register_item_watcher(user.watcher_method, item_type=None) auction = Mock(item=Mock(item_type="Furniture"), initial_price=Decimal(10.99)) runtime_manager.notify_users_of_new_auction(auction) m_user_watcher_method.assert_called_once() runtime_manager.item_watchers.clear() m_user_watcher_method.reset_mock() user.register_item_watcher(user.watcher_method, item_type="Vanity") auction = Mock(item=Mock(item_type="Vanity"), initial_price=Decimal(10.99)) runtime_manager.notify_users_of_new_auction(auction) m_user_watcher_method.assert_called_once() runtime_manager.item_watchers.clear() m_user_watcher_method.reset_mock() user.register_item_watcher(user.watcher_method, item_type="Kitchen") auction = Mock(item=Mock(item_type="Furniture"), initial_price=Decimal(10.99)) runtime_manager.notify_users_of_new_auction(auction) m_user_watcher_method.assert_not_called() patcher.stop()