def test_should_not_be_winning_if_bid_lower_than_current_price() -> None: auction = AuctionFactory(bids=[Bid(id=1, bidder_id=1, amount=get_dollars("10.00"))]) lower_bid_bidder_id = 2 auction.place_bid(bidder_id=lower_bid_bidder_id, amount=get_dollars("5.00")) assert lower_bid_bidder_id not in auction.winners
def test_Auction_FirstBid_EmitsEvent(place_bid_uc: PlacingBid, event_bus: Mock, auction_id: AuctionId, auction_title: str) -> None: place_bid_uc.execute(PlacingBidInputDto(1, auction_id, get_dollars("100"))) event_bus.post.assert_called_once_with( WinningBidPlaced(auction_id, 1, get_dollars("100"), auction_title))
def test_saves_auction_changes( connection: Connection, another_bidder_id: int, bid_model: RowProxy, auction_model_with_a_bid: RowProxy, ends_at: datetime, event_bus_mock: Mock, ) -> None: new_bid_price = get_dollars(bid_model.amount * 2) auction = Auction( id=auction_model_with_a_bid.id, title=auction_model_with_a_bid.title, starting_price=get_dollars(auction_model_with_a_bid.starting_price), ends_at=ends_at, bids=[ Bid(bid_model.id, bid_model.bidder_id, get_dollars(bid_model.amount)), Bid(None, another_bidder_id, new_bid_price), ], ended=True, ) SqlAlchemyAuctionsRepo(connection, event_bus_mock).save(auction) assert connection.execute(select([func.count() ]).select_from(bids)).scalar() == 2 proxy = connection.execute( select([ auctions ]).where(auctions.c.id == auction_model_with_a_bid.id)).first() assert proxy.current_price == new_bid_price.amount assert proxy.ended
def test_Auction_BidLowerThanCurrentPrice_IsLosing( place_bid_uc: PlacingBid, output_boundary: PlacingBidOutputBoundaryFake, auction_id: AuctionId) -> None: place_bid_uc.execute(PlacingBidInputDto(1, auction_id, get_dollars("5"))) assert output_boundary.dto == PlacingBidOutputDto( is_winner=False, current_price=get_dollars("10"))
def test_BeginningAuction_SocksFor10DollarsEndingInFuture_emitsEvent( beginning_auction_uc: BeginningAuction, event_bus_mock: Mock) -> None: input_dto = BeginningAuctionInputDto(1, "Socks", get_dollars("10.00"), datetime.now() + timedelta(days=7)) beginning_auction_uc.execute(input_dto) event_bus_mock.post.assert_called_once_with( AuctionBegan(1, get_dollars("10.00"), "Socks"))
def _row_to_dto(auction_proxy: RowProxy) -> AuctionDto: return AuctionDto( id=auction_proxy.id, title=auction_proxy.title, current_price=get_dollars(auction_proxy.current_price), starting_price=get_dollars(auction_proxy.starting_price), ends_at=auction_proxy.ends_at, )
def test_should_emit_winning_if_overbids() -> None: auction = AuctionFactory(bids=[Bid(id=1, bidder_id=1, amount=get_dollars("15.00"))]) winning_amount = auction.current_price + get_dollars("1.00") auction.place_bid(bidder_id=2, amount=winning_amount) expected_winning_event = WinningBidPlaced(auction.id, 2, winning_amount, auction.title) expected_overbid_event = BidderHasBeenOverbid(auction.id, 1, winning_amount, auction.title) assert auction.domain_events == [expected_winning_event, expected_overbid_event]
def test_should_emit_event_upon_overbid() -> None: bid_that_will_lose = Bid(id=1, bidder_id=1, amount=get_dollars("15.00")) auction = AuctionFactory(bids=[bid_that_will_lose]) new_bid_amount = get_dollars("20.00") auction.place_bid(bidder_id=2, amount=new_bid_amount) expected_event = BidderHasBeenOverbid(auction.id, bid_that_will_lose.bidder_id, new_bid_amount, auction.title) assert expected_event in auction.domain_events
def test_Auction_FirstBidHigherThanIntialPrice_IsWinning( place_bid_uc: PlacingBid, output_boundary: PlacingBidOutputBoundaryFake, auction_id: AuctionId) -> None: place_bid_uc.execute(PlacingBidInputDto(1, auction_id, get_dollars("100"))) expected_dto = PlacingBidOutputDto(is_winner=True, current_price=get_dollars("100")) assert output_boundary.dto == expected_dto
def test_Auction_Overbid_IsWinning( place_bid_uc: PlacingBid, output_boundary: PlacingBidOutputBoundaryFake, auction_id: AuctionId) -> None: place_bid_uc.execute(PlacingBidInputDto(1, auction_id, get_dollars("100"))) place_bid_uc.execute(PlacingBidInputDto(2, auction_id, get_dollars("120"))) assert output_boundary.dto == PlacingBidOutputDto( is_winner=True, current_price=get_dollars("120"))
def test_should_return_highest_bids_user_id_for_winners_list() -> None: auction = AuctionFactory( bids=[ Bid(id=1, bidder_id=1, amount=get_dollars("101")), Bid(id=2, bidder_id=2, amount=get_dollars("15")), Bid(id=3, bidder_id=3, amount=get_dollars("100")), ] ) assert auction.winners == [1]
def test_Auction_OverbidFromWinner_EmitsWinningBidEventOnly( place_bid_uc: PlacingBid, event_bus: Mock, auction_id: AuctionId, auction_title: str) -> None: place_bid_uc.execute(PlacingBidInputDto(3, auction_id, get_dollars("100"))) event_bus.post.reset_mock() place_bid_uc.execute(PlacingBidInputDto(3, auction_id, get_dollars("120"))) event_bus.post.assert_called_once_with( WinningBidPlaced(auction_id, 3, get_dollars("120"), auction_title))
def test_PlacingBid_BiddingOnEndedAuction_RaisesException( beginning_auction_uc: BeginningAuction, place_bid_uc: PlacingBid) -> None: yesterday = datetime.now(tz=pytz.UTC) - timedelta(days=1) with freeze_time(yesterday): beginning_auction_uc.execute( BeginningAuctionInputDto(1, "Bar", get_dollars("1.00"), yesterday + timedelta(hours=1))) with pytest.raises(BidOnEndedAuction): place_bid_uc.execute(PlacingBidInputDto(1, 1, get_dollars("2.00")))
def _row_to_entity(self, auction_proxy: RowProxy, bids_proxies: List[RowProxy]) -> Auction: auction_bids = [ Bid(bid.id, bid.bidder_id, get_dollars(bid.amount)) for bid in bids_proxies ] return Auction( auction_proxy.id, auction_proxy.title, get_dollars(auction_proxy.starting_price), auction_bids, auction_proxy.ends_at.replace(tzinfo=pytz.UTC), auction_proxy.ended, )
def test_should_emit_winning_event_if_the_first_offer() -> None: auction = AuctionFactory() winning_amount = auction.current_price + get_dollars("1.00") auction.place_bid(bidder_id=1, amount=winning_amount) assert auction.domain_events == [WinningBidPlaced(auction.id, 1, winning_amount, auction.title)]
def test_should_emit_auction_ended(yesterday: datetime) -> None: auction = AuctionFactory(bids=[Bid(id=1, bidder_id=1, amount=get_dollars("15.00"))], ends_at=yesterday) auction.end_auction() expected_event = AuctionEnded(auction.id, auction.winners[0], auction.current_price, auction.title) assert auction.domain_events == [expected_event]
def test_should_withdraw_the_only_bid() -> None: auction = AuctionFactory(bids=[Bid(id=1, bidder_id=1, amount=get_dollars("50"))]) auction.withdraw_bids([1]) assert auction.winners == [] assert auction.current_price == auction.starting_price
def test_adding_new_payment_is_reflected_on_pending_payments_list( facade: PaymentsFacade, connection: Connection, event_bus: Mock) -> None: customer_id = 1 assert facade.get_pending_payments(customer_id) == [] payment_uuid = uuid.uuid4() amount = get_dollars("15.00") description = "Example" # with patch.object(event_bus, "post") as post_mock: facade.start_new_payment(payment_uuid, customer_id, amount, description) (row, ) = connection.execute(payments.select()).fetchall() assert dict(row) == { "uuid": str(payment_uuid), "customer_id": customer_id, "amount": int(amount.amount * 100), "currency": amount.currency.iso_code, "status": PaymentStatus.NEW.value, "description": description, "charge_id": None, } pending_payments = facade.get_pending_payments(customer_id) assert pending_payments == [ PaymentDto(payment_uuid, amount, description, PaymentStatus.NEW.value) ] event_bus.post.assert_called_once_with( PaymentStarted(payment_uuid, customer_id))
class BeginningAuctionInputDtoFactory(factory.Factory): class Meta: model = BeginningAuctionInputDto auction_id = factory.Sequence(lambda n: n) title = factory.Faker("name") starting_price = get_dollars("0.99") ends_at = factory.Faker("future_datetime", end_date="+7d")
def test_gets_existing_auction( connection: Connection, auction_model_with_a_bid: RowProxy, bid_model: RowProxy, ends_at: datetime, event_bus_mock: Mock, ) -> None: auction = SqlAlchemyAuctionsRepo(connection, event_bus_mock).get( auction_model_with_a_bid.id) assert auction.id == auction_model_with_a_bid.id assert auction.title == auction_model_with_a_bid.title assert auction.starting_price == get_dollars( auction_model_with_a_bid.starting_price) assert auction.current_price == get_dollars(bid_model.amount) assert auction.ends_at == ends_at assert set(auction.bids) == { Bid(bid_model.id, bid_model.bidder_id, get_dollars(bid_model.amount)) }
class AuctionFactory(factory.Factory): class Meta: model = Auction id = factory.Sequence(lambda n: n) bids = factory.List([]) title = factory.Faker("name") starting_price = get_dollars("10.00") ends_at = factory.Faker("future_datetime", end_date="+7d") ended = False
def test_AuctionsRepo_UponSavingAuction_PostsPendingEventsViaEventBus( connection: Connection, another_bidder_id: int, auction_model_with_a_bid: RowProxy, event_bus_mock: Mock) -> None: repo = SqlAlchemyAuctionsRepo(connection, event_bus_mock) auction = repo.get(auction_model_with_a_bid.id) auction.place_bid(another_bidder_id, auction.current_price + get_dollars("1.00")) repo.save(auction) event_bus_mock.post.assert_called()
def test_AuctionsRepo_UponSavingAuction_ClearsPendingEvents( connection: Connection, another_bidder_id: int, auction_model_with_a_bid: RowProxy, event_bus_mock: Mock) -> None: repo = SqlAlchemyAuctionsRepo(connection, event_bus_mock) auction = repo.get(auction_model_with_a_bid.id) auction.place_bid(another_bidder_id, auction.current_price + get_dollars("1.00")) repo.save(auction) assert len(auction.domain_events) == 0
def test_charge_then_capture(api_consumer: ApiConsumer, source: str, api_key: str) -> None: charge_id = api_consumer.charge(get_dollars("15.00"), source) api_consumer.capture(charge_id) response = requests.get(f"{Request.url}/v1/charges/{charge_id}", auth=(api_key, "")) capture_json = response.json() assert capture_json["amount"] == 1500 # cents assert capture_json["currency"] == "usd" assert capture_json["captured"]
def test_Auction_OverbidFromOtherBidder_EmitsEvent(place_bid_uc: PlacingBid, event_bus: Mock, auction_id: AuctionId, auction_title: str) -> None: place_bid_uc.execute(PlacingBidInputDto(1, auction_id, get_dollars("100"))) event_bus.post.reset_mock() place_bid_uc.execute(PlacingBidInputDto(2, auction_id, get_dollars("120"))) event_bus.post.assert_has_calls( [ call( WinningBidPlaced(auction_id, 2, get_dollars("120"), auction_title)), call( BidderHasBeenOverbid(auction_id, 1, get_dollars("120"), auction_title)), ], any_order=True, ) assert event_bus.post.call_count == 2
def test_removes_withdrawn_bids(connection: Connection, bid_model: RowProxy, auction_model_with_a_bid: dict, ends_at: datetime, event_bus_mock: Mock) -> None: auction = Auction( id=auction_model_with_a_bid.id, title=auction_model_with_a_bid.title, starting_price=get_dollars(auction_model_with_a_bid.starting_price), ends_at=ends_at, bids=[ Bid(bid_model.id, bid_model.bidder_id, get_dollars(bid_model.amount)) ], ended=False, ) auction.withdraw_bids([bid_model.id]) SqlAlchemyAuctionsRepo(connection, event_bus_mock).save(auction) assert connection.execute(select([func.count() ]).select_from(bids)).scalar() == 0
def test_Auction_OverbidFromOtherBidder_EmitsEvents( beginning_auction_uc: BeginningAuction, place_bid_uc: PlacingBid, event_bus: Mock) -> None: auction_id = 1 tomorrow = datetime.now(tz=pytz.UTC) + timedelta(days=1) beginning_auction_uc.execute( BeginningAuctionInputDto(auction_id, "Foo", get_dollars("1.00"), tomorrow)) place_bid_uc.execute(PlacingBidInputDto(1, auction_id, get_dollars("2.0"))) event_bus.post.reset_mock() place_bid_uc.execute(PlacingBidInputDto(2, auction_id, get_dollars("3.0"))) event_bus.post.assert_has_calls( [ call(WinningBidPlaced(auction_id, 2, get_dollars("3.0"), "Foo")), call(BidderHasBeenOverbid(auction_id, 1, get_dollars("3.0"), "Foo")), ], any_order=True, ) assert event_bus.post.call_count == 2
def test_unsuccessful_charge(facade: PaymentsFacade, inserted_payment: dict, connection: Connection, event_bus: Mock) -> None: payment_uuid = uuid.UUID(inserted_payment["uuid"]) with patch.object(ApiConsumer, "charge", side_effect=PaymentFailedError) as charge_mock: facade.charge(payment_uuid, inserted_payment["customer_id"], "token") charge_mock.assert_called_once_with( get_dollars(inserted_payment["amount"] / 100), "token") assert get_payment( connection, inserted_payment["uuid"]).status == PaymentStatus.FAILED.value event_bus.post.assert_called_once_with( PaymentFailed(payment_uuid, inserted_payment["customer_id"]))
def test_successful_charge_updates_status(facade: PaymentsFacade, inserted_payment: dict, connection: Connection, event_bus: Mock) -> None: payment_uuid = uuid.UUID(inserted_payment["uuid"]) charge_id = "SOME_CHARGE_ID" with patch.object(ApiConsumer, "charge", return_value=charge_id) as charge_mock: facade.charge(uuid.UUID(inserted_payment["uuid"]), inserted_payment["customer_id"], "token") charge_mock.assert_called_once_with( get_dollars(inserted_payment["amount"] / 100), "token") payment_row = get_payment(connection, inserted_payment["uuid"]) assert payment_row.status == PaymentStatus.CHARGED.value assert payment_row.charge_id == charge_id event_bus.post.assert_called_once_with( PaymentCharged(payment_uuid, inserted_payment["customer_id"]))
def test_should_start_new_payment_upon_auction_ended( saga: PayingForWonItemSaga, payments_facade_mock: Mock, customer_relationship_mock: Mock, mocked_uuid4: uuid.UUID, saga_data: PayingForWonItemSagaData, ) -> None: event = AuctionEnded(auction_id=1, winner_id=2, winning_bid=get_dollars("99.99"), auction_title="irrelevant") saga.handle(event, saga_data) payments_facade_mock.start_new_payment.assert_called_once_with( mocked_uuid4, event.winner_id, event.winning_bid, event.auction_title) customer_relationship_mock.send_email_about_winning.assert_called_once_with( event.winner_id, event.winning_bid, event.auction_title) assert saga_data.state == SagaState.PAYMENT_STARTED assert saga_data.winning_bid == event.winning_bid assert saga_data.auction_title == event.auction_title assert saga_data.timeout_at == datetime.now() + timedelta(days=3) assert saga_data.winner_id == event.winner_id assert saga_data.auction_id == event.auction_id