def test_first_tip_challenge(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) with app.app_context(): db = get_db() block = Block(blockhash="0x1", number=BLOCK_NUMBER) user = User( blockhash="0x1", blocknumber=BLOCK_NUMBER, txhash="xyz", user_id=1, is_current=True, handle="TestHandle", handle_lc="testhandle", wallet="0x1", is_creator=False, is_verified=False, name="test_name", created_at=datetime.now(), updated_at=datetime.now(), ) with db.scoped_session() as session: bus = ChallengeEventBus(redis_conn) session.query(Challenge).filter( Challenge.id == "send-first-tip").update({ "active": True, "starting_block": BLOCK_NUMBER }) # Register events with the bus bus.register_listener(ChallengeEvent.send_tip, send_first_tip_challenge_manager) session.add(block) session.flush() session.add(user) session.flush() bus.dispatch( ChallengeEvent.send_tip, BLOCK_NUMBER, 1, # user_id {}, ) bus.flush() bus.process_events(session) session.flush() state = send_first_tip_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.is_complete
def dispatch_play(offset: int, session: Session, bus: ChallengeEventBus): play = create_play(offset) session.add(play) session.flush() bus.dispatch( ChallengeEvent.track_listen, BLOCK_NUMBER, 1, {"created_at": play.created_at.timestamp()}, )
def test_listen_streak_challenge(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) # Register events with the bus bus.register_listener(ChallengeEvent.track_listen, listen_streak_challenge_manager) with app.app_context(): db = get_db() with db.scoped_session() as session: setup_challenges(session) # wrapped dispatch play def dp(offset): return dispatch_play(offset, session, bus) scope_and_process = make_scope_and_process(bus, session) # Make sure plays increment the step count scope_and_process(lambda: dp(0)) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 1 and not state.is_complete scope_and_process(lambda: dp(1)) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 2 and not state.is_complete # Make sure the step count resets if the user missed a day scope_and_process(lambda: dp(3)) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 1 and not state.is_complete # Add more plays to increment the step count scope_and_process(lambda: dp(4)) scope_and_process(lambda: dp(5)) scope_and_process(lambda: dp(6)) scope_and_process(lambda: dp(7)) scope_and_process(lambda: dp(8)) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 6 and not state.is_complete # Make sure that is_complete is set when step count hits 7 scope_and_process(lambda: dp(9)) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 7 and state.is_complete == True
def dispatch_new_user_signup(referrer: int, referred_user_id: int, session: Session, bus: ChallengeEventBus): session.add(create_user(referred_user_id)) session.add(create_user_referral(referrer, referred_user_id)) session.flush() bus.dispatch( ChallengeEvent.referral_signup, BLOCK_NUMBER, referrer, {"referred_user_id": referred_user_id}, ) bus.dispatch(ChallengeEvent.referred_signup, BLOCK_NUMBER, referred_user_id)
def test_rejects_invalid_events(app): setup_challenges(app) with app.app_context(): db = get_db() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) with db.scoped_session() as session: mgr = ChallengeManager("test_challenge_1", DefaultUpdater()) TEST_EVENT = "TEST_EVENT" bus.register_listener(TEST_EVENT, mgr) with bus.use_scoped_dispatch_queue(): bus.dispatch(TEST_EVENT, None, 1) bus.dispatch(TEST_EVENT, 1, None) bus.dispatch(TEST_EVENT, 1, 1, 1) (count, did_error) = bus.process_events(session) assert count == 0 assert did_error == False
def setup_verified_test(session): # Setup blocks = [ Block(blockhash="0x1", number=1, parenthash="", is_current=False), Block(blockhash="0x2", number=2, parenthash="", is_current=True), ] users = [ User( blockhash="0x1", blocknumber=1, user_id=1, is_current=True, wallet="0xFakeWallet1", created_at=datetime.now(), updated_at=datetime.now(), is_verified=False, ), User( blockhash="0x2", blocknumber=2, user_id=2, is_current=True, wallet="0xFakeWallet2", created_at=datetime.now(), updated_at=datetime.now(), is_verified=True, ), ] challenges = [ Challenge( id="referrals", type=ChallengeType.aggregate, active=True, amount="1", step_count=5, ), Challenge( id="ref-v", type=ChallengeType.aggregate, active=True, amount="1", step_count=500, ), ] # Wipe any existing challenges in the DB from running migrations, etc session.query(Challenge).delete() session.commit() session.add_all(blocks) session.commit() session.add_all(users) session.add_all(challenges) session.commit() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) bus.register_listener(DEFAULT_EVENT, referral_challenge_manager) bus.register_listener(DEFAULT_EVENT, verified_referral_challenge_manager) return bus
def test_inactive_challenge(app): setup_challenges(app) with app.app_context(): db = get_db() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) with db.scoped_session() as session: mgr = ChallengeManager("some_inactive_challenge", DefaultUpdater()) TEST_EVENT = "TEST_EVENT" bus.register_listener(TEST_EVENT, mgr) with bus.use_scoped_dispatch_queue(): bus.dispatch(TEST_EVENT, 100, 1, {}) bus.process_events(session) state = mgr.get_user_challenge_state(session, ["1"]) # We should not have any UserChallenges created for the # inactive challenge!! assert len(state) == 0
def dispatch_trending_challenges( challenge_bus: ChallengeEventBus, challenge_event: ChallengeEvent, latest_blocknumber: int, tracks, version: str, date: datetime, type: TrendingType, ): for idx, track in enumerate(tracks): challenge_bus.dispatch( challenge_event, latest_blocknumber, track["owner_id"], { "id": track["track_id"], "user_id": track["owner_id"], "rank": idx + 1, "type": str(type), "version": str(version), "week": date_to_week(date), }, )
def test_multiple_listens(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) # Register events with the bus bus.register_listener(ChallengeEvent.track_listen, listen_streak_challenge_manager) with app.app_context(): db = get_db() with db.scoped_session() as session: setup_challenges(session) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"]) # make sure empty to start assert len(state) == 0 def dp(offset): return dispatch_play(offset, session, bus) scope_and_process = make_scope_and_process(bus, session) scope_and_process(lambda: dp(1)) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 1 scope_and_process(lambda: (dp(2), dp(3), dp(4), dp(5))) state = listen_streak_challenge_manager.get_user_challenge_state( session, ["1"])[0] # This will actually "reset" the listen count, because # we dedupe multiple play events in a single call to process # and in this case, we pick the one with the greatest timestamp # which is > 2 days, thus resetting. # Not the greatest behavior, but shouldn't have user facing # impact. # we really want to just ensure that this doesn't crash assert state.current_step_count == 1
def test_anon_listen(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) # Register events with the bus bus.register_listener(ChallengeEvent.track_listen, listen_streak_challenge_manager) with app.app_context(): db = get_db() with db.scoped_session() as session: setup_challenges(session) with bus.use_scoped_dispatch_queue(): bus.dispatch( ChallengeEvent.track_listen, BLOCK_NUMBER, None, {"created_at": datetime.now()}, ) (num_processed, error) = bus.process_events(session) assert not error assert num_processed == 0
def get_challenges_metadata( session: Session, event_bus: ChallengeEventBus, challenges: List[UserChallenge], ) -> List[Dict]: # Break it up into map per challenge type challenge_map: Dict[str, List[str]] = defaultdict(lambda: []) specifier_metadata_map: Dict[str, Dict] = {} for challenge in challenges: challenge_map[challenge.challenge_id].append(challenge.specifier) for challenge_type, specifiers in challenge_map.items(): manager = event_bus.get_manager(challenge_type) metadatas = manager.get_metadata(session, specifiers) for i, specifier in enumerate(specifiers): metadata = metadatas[i] specifier_metadata_map[specifier] = metadata # Finally, re-sort the metadata return [specifier_metadata_map[c.specifier] for c in challenges]
def dispatch_challenge_repost(bus: ChallengeEventBus, repost, block_number): bus.dispatch(ChallengeEvent.repost, block_number, repost.user_id)
def setup_db(session): blocks = [Block(blockhash="0x1", number=1, parenthash="", is_current=True)] users = [ User( blockhash="0x1", blocknumber=1, user_id=1, is_current=True, wallet="0x38C68fF3926bf4E68289672F75ee1543117dD9B3", created_at=datetime.now(), updated_at=datetime.now(), ) ] challenges = [ Challenge( id="boolean_challenge_1", type=ChallengeType.boolean, active=True, amount="5", ), Challenge( id="boolean_challenge_2", type=ChallengeType.boolean, active=True, amount="5", ), Challenge( id="boolean_challenge_3", type=ChallengeType.boolean, active=True, amount="5", ), # No progress on this, but active # should be returned Challenge( id="boolean_challenge_4", type=ChallengeType.boolean, active=True, amount="5", ), # Inactive, with no progress Challenge( id="boolean_challenge_5", type=ChallengeType.boolean, active=False, amount="5", ), # Inactive, WITH progress Challenge( id="boolean_challenge_6", type=ChallengeType.boolean, active=False, amount="5", ), Challenge( id="trending_challenge_1", type=ChallengeType.trending, active=True, amount="5", ), Challenge( id="aggregate_challenge_1", type=ChallengeType.aggregate, active=True, amount="5", step_count=3, ), Challenge( id="aggregate_challenge_2", type=ChallengeType.aggregate, active=True, amount="5", step_count=2, ), Challenge( id="aggregate_challenge_3", type=ChallengeType.aggregate, active=True, amount="5", step_count=2, ), Challenge(id="trending_1", type=ChallengeType.trending, active=True, amount="5"), Challenge(id="trending_2", type=ChallengeType.trending, active=True, amount="5"), Challenge(id="trending_3", type=ChallengeType.trending, active=True, amount="5"), ] user_challenges = [ # Finished the first challenge, disbursed UserChallenge( challenge_id="boolean_challenge_1", user_id=1, specifier="1", is_complete=True, ), # Did finish the second challenge, did not disburse UserChallenge( challenge_id="boolean_challenge_2", user_id=1, specifier="1", is_complete=True, ), # Did not finish challenge 3 UserChallenge( challenge_id="boolean_challenge_3", user_id=1, specifier="1", is_complete=False, ), # Inactive challenge UserChallenge( challenge_id="boolean_challenge_6", user_id=1, specifier="1", is_complete=True, ), UserChallenge( challenge_id="aggregate_challenge_1", user_id=1, specifier="1-2", # compound specifiers, like if user1 invites user2 is_complete=True, ), # Ensure that a non-complete user-challenge isn't counted towards # aggregate challenge score UserChallenge( challenge_id="aggregate_challenge_1", user_id=1, specifier="1-3", is_complete=False, ), UserChallenge( challenge_id="aggregate_challenge_2", user_id=1, specifier="1-2", is_complete=True, ), UserChallenge( challenge_id="aggregate_challenge_2", user_id=1, specifier="1-3", is_complete=True, ), # Trending 1 should be finished and included UserChallenge( challenge_id="trending_1", user_id=1, specifier="06-01-2020", is_complete=True, ), # Trending 2 should not be included UserChallenge( challenge_id="trending_2", user_id=1, specifier="06-01-2020", is_complete=False, ), ] disbursements = [ ChallengeDisbursement( challenge_id="boolean_challenge_1", user_id=1, amount="5", signature="1", slot=1, specifier="1", ) ] # Wipe any existing challenges in the DB from running migrations, etc session.query(Challenge).delete() session.commit() session.add_all(blocks) session.commit() session.add_all(users) session.commit() session.add_all(challenges) session.commit() session.add_all(user_challenges) session.commit() session.add_all(disbursements) redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) challenge_types = [ "boolean_challenge_1", "boolean_challenge_2", "boolean_challenge_3", "boolean_challenge_4", "boolean_challenge_5", "boolean_challenge_6", "trending_challenge_1", "aggregate_challenge_1", "aggregate_challenge_2", "aggregate_challenge_3", "trending_1", "trending_2", "trending_3", ] for ct in challenge_types: bus.register_listener( DEFAULT_EVENT, ChallengeManager(ct, DefaultUpdater()), ) return bus
def enqueue_trending_challenges(db: SessionManager, redis: Redis, challenge_bus: ChallengeEventBus, date: datetime): logger.info( "calculate_trending_challenges.py | Start calculating trending challenges" ) update_start = time.time() with db.scoped_session( ) as session, challenge_bus.use_scoped_dispatch_queue(): latest_blocknumber = get_latest_blocknumber_via_redis(session, redis) if latest_blocknumber is None: logger.error( "calculate_trending_challenges.py | Unable to get latest block number" ) return trending_track_versions = trending_strategy_factory.get_versions_for_type( TrendingType.TRACKS).keys() time_range = "week" for version in trending_track_versions: strategy = trending_strategy_factory.get_strategy( TrendingType.TRACKS, version) top_tracks = _get_trending_tracks_with_session( session, {"time": time_range}, strategy) top_tracks = top_tracks[:TRENDING_LIMIT] dispatch_trending_challenges( challenge_bus, ChallengeEvent.trending_track, latest_blocknumber, top_tracks, version, date, TrendingType.TRACKS, ) # Cache underground trending underground_trending_versions = trending_strategy_factory.get_versions_for_type( TrendingType.UNDERGROUND_TRACKS).keys() for version in underground_trending_versions: strategy = trending_strategy_factory.get_strategy( TrendingType.UNDERGROUND_TRACKS, version) underground_args: GetUndergroundTrendingTrackcArgs = { "offset": 0, "limit": TRENDING_LIMIT, } top_tracks = _get_underground_trending_with_session( session, underground_args, strategy, False) dispatch_trending_challenges( challenge_bus, ChallengeEvent.trending_underground, latest_blocknumber, top_tracks, version, date, TrendingType.UNDERGROUND_TRACKS, ) trending_playlist_versions = trending_strategy_factory.get_versions_for_type( TrendingType.PLAYLISTS).keys() for version in trending_playlist_versions: strategy = trending_strategy_factory.get_strategy( TrendingType.PLAYLISTS, version) playlists_args: GetTrendingPlaylistsArgs = { "limit": TRENDING_LIMIT, "offset": 0, "time": time_range, } trending_playlists = _get_trending_playlists_with_session( session, playlists_args, strategy, False) for idx, playlist in enumerate(trending_playlists): challenge_bus.dispatch( ChallengeEvent.trending_playlist, latest_blocknumber, playlist["playlist_owner_id"], { "id": playlist["playlist_id"], "user_id": playlist["playlist_owner_id"], "rank": idx + 1, "type": str(TrendingType.PLAYLISTS), "version": str(version), "week": date_to_week(date), }, ) update_end = time.time() update_total = update_end - update_start logger.info( f"calculate_trending_challenges.py | Finished calculating trending in {update_total} seconds" )
def update_user_events( session: Session, user_record: User, events: UserEventsMetadata, bus: ChallengeEventBus, ) -> None: """Updates the user events table""" try: if not isinstance(events, dict) or not user_record.blocknumber: # There is something wrong with events, don't process it return # Get existing UserEvents entry existing_user_events = (session.query(UserEvents).filter_by( user_id=user_record.user_id, is_current=True).one_or_none()) existing_referrer = (existing_user_events.referrer if existing_user_events else None) existing_mobile_user = (existing_user_events.is_mobile_user if existing_user_events else False) user_events = UserEvents( user_id=user_record.user_id, is_current=True, blocknumber=user_record.blocknumber, blockhash=user_record.blockhash, referrer=existing_referrer, is_mobile_user=existing_mobile_user, ) for event, value in events.items(): if (event == "referrer" and isinstance(value, int) and user_events.referrer is None and user_record.user_id != value): user_events.referrer = value bus.dispatch( ChallengeEvent.referral_signup, user_record.blocknumber, value, {"referred_user_id": user_record.user_id}, ) bus.dispatch( ChallengeEvent.referred_signup, user_record.blocknumber, user_record.user_id, ) elif (event == "is_mobile_user" and isinstance(value, bool) and not user_events.is_mobile_user): user_events.is_mobile_user = value if value: bus.dispatch( ChallengeEvent.mobile_install, user_record.blocknumber, user_record.user_id, ) # Only add a row if there's an update if (existing_user_events is None or user_events.is_mobile_user != existing_mobile_user or user_events.referrer != existing_referrer): # Mark existing UserEvents entries as not current session.query(UserEvents).filter_by(user_id=user_record.user_id, is_current=True).update( {"is_current": False}) session.add(user_events) except Exception as e: logger.error( f"index.py | users.py | Fatal updating user events while indexing {e}", exc_info=True, )
def test_profile_completion_challenge_with_playlists(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) # create user with app.app_context(): db = get_db() block = Block(blockhash="0x1", number=BLOCK_NUMBER) user = User( blockhash="0x1", blocknumber=BLOCK_NUMBER, txhash="xyz", user_id=1, is_current=True, handle="TestHandle", handle_lc="testhandle", wallet="0x123", is_creator=False, is_verified=False, name="test_name", created_at=datetime.now(), updated_at=datetime.now(), ) with db.scoped_session() as session: bus = ChallengeEventBus(redis_conn) # set challenge as active for purposes of test session.query(Challenge).filter( Challenge.id == "profile-completion").update({ "active": True, "starting_block": BLOCK_NUMBER }) # Register events with the bus bus.register_listener(ChallengeEvent.profile_update, profile_challenge_manager) bus.register_listener(ChallengeEvent.repost, profile_challenge_manager) bus.register_listener(ChallengeEvent.follow, profile_challenge_manager) bus.register_listener(ChallengeEvent.favorite, profile_challenge_manager) session.add(block) session.flush() session.add(user) # Process dummy event just to get this thing initted bus.dispatch(ChallengeEvent.profile_update, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] # We should have completed a single step (name) assert state.current_step_count == 1 and not state.is_complete # Do a repost repost = Repost( blockhash="0x1", blocknumber=BLOCK_NUMBER, user_id=1, repost_item_id=1, repost_type=RepostType.playlist, is_current=True, is_delete=False, created_at=datetime.now(), ) session.add(repost) session.flush() bus.dispatch(ChallengeEvent.repost, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 2 and not state.is_complete # Do a save save = Save( blockhash="0x1", blocknumber=BLOCK_NUMBER, user_id=1, save_item_id=1, save_type=SaveType.playlist, is_current=True, is_delete=False, created_at=datetime.now(), ) session.add(save) session.flush() bus.dispatch(ChallengeEvent.favorite, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) session.flush() state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 3 and not state.is_complete # Do 1 follow, then 5 total follows follow = Follow( blockhash="0x1", blocknumber=BLOCK_NUMBER, is_current=True, is_delete=False, created_at=datetime.now(), follower_user_id=1, followee_user_id=2, ) session.add(follow) session.flush() bus.dispatch(ChallengeEvent.follow, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) session.flush() state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] # Assert 1 follow didn't do anything assert state.current_step_count == 3 and not state.is_complete follows = [ Follow( blockhash="0x1", blocknumber=BLOCK_NUMBER, is_current=True, is_delete=False, created_at=datetime.now(), follower_user_id=1, followee_user_id=3, ), Follow( blockhash="0x1", blocknumber=BLOCK_NUMBER, is_current=True, is_delete=False, created_at=datetime.now(), follower_user_id=1, followee_user_id=4, ), Follow( blockhash="0x1", blocknumber=BLOCK_NUMBER, is_current=True, is_delete=False, created_at=datetime.now(), follower_user_id=1, followee_user_id=5, ), Follow( blockhash="0x1", blocknumber=BLOCK_NUMBER, is_current=True, is_delete=False, created_at=datetime.now(), follower_user_id=1, followee_user_id=6, ), ] session.add_all(follows) session.flush() bus.dispatch(ChallengeEvent.follow, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 4 and not state.is_complete # profile_picture session.query(User).filter(User.user_id == 1).update( {"profile_picture": "profilepictureurl"}) session.flush() bus.dispatch(ChallengeEvent.profile_update, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 5 and not state.is_complete # profile description session.query(User).filter(User.user_id == 1).update( {"bio": "profiledescription"}) session.flush() bus.dispatch(ChallengeEvent.profile_update, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 6 and not state.is_complete # Undo it, ensure that our count goes down session.query(User).filter(User.user_id == 1).update({"bio": None}) session.flush() bus.dispatch(ChallengeEvent.profile_update, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 5 and not state.is_complete # profile_cover_photo session.query(User).filter(User.user_id == 1).update({ "bio": "profiledescription", "cover_photo": "test_cover_photo" }) session.flush() bus.dispatch(ChallengeEvent.profile_update, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 7 and state.is_complete == True # ensure that if we lose some data now that the thing is complete, we don't change the status of the challenge session.query(User).filter(User.user_id == 1).update( {"cover_photo": None}) session.flush() bus.dispatch(ChallengeEvent.profile_update, BLOCK_NUMBER, 1) bus.flush() bus.process_events(session) state = profile_challenge_manager.get_user_challenge_state( session, ["1"])[0] assert state.current_step_count == 7 and state.is_complete == True
def setup_listen_streak_challenge(session): # Setup blocks = [ Block(blockhash="0x1", number=1, parenthash="", is_current=False), Block(blockhash="0x2", number=2, parenthash="", is_current=True), ] users = [ User( blockhash="0x1", blocknumber=1, user_id=1, is_current=True, wallet="0xFakeWallet1", created_at=datetime.now(), updated_at=datetime.now(), is_verified=False, ), User( blockhash="0x2", blocknumber=2, user_id=2, is_current=True, wallet="0xFakeWallet2", created_at=datetime.now(), updated_at=datetime.now(), is_verified=True, ), ] challenges = [ Challenge( id="listen-streak", type=ChallengeType.numeric, active=True, amount="1", step_count=7, ) ] user_challenges = [ UserChallenge( challenge_id="listen-streak", user_id=1, specifier="1", is_complete=False, current_step_count=5, ), UserChallenge( challenge_id="listen-streak", user_id=2, specifier="2", is_complete=False, current_step_count=5, ), ] listen_streak_challenges = [ ListenStreakChallenge( user_id=1, last_listen_date=datetime.now() - timedelta(hours=12), listen_streak=5, ), ListenStreakChallenge( user_id=2, last_listen_date=datetime.now() - timedelta(hours=50), listen_streak=5, ), ] # Wipe any existing challenges in the DB from running migrations, etc session.query(Challenge).delete() session.commit() session.add_all(blocks) session.commit() session.add_all(users) session.add_all(challenges) session.commit() session.add_all(user_challenges) session.commit() session.add_all(listen_streak_challenges) session.commit() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) bus.register_listener(DEFAULT_EVENT, listen_streak_challenge_manager) return bus
def get_empty_metadata(event_bus: ChallengeEventBus, challenges: List[Challenge]): return [event_bus.get_manager(c.id).get_default_metadata() for c in challenges]
def setup_extra_metadata_test(session): blocks = [Block(blockhash="0x1", number=1, parenthash="", is_current=True)] users = [ User( blockhash="0x1", blocknumber=1, user_id=1, is_current=True, wallet="0x38C68fF3926bf4E68289672F75ee1543117dD9B3", created_at=datetime.now(), updated_at=datetime.now(), ) ] challenges = [ # Test numeric challenges # Numeric 1 with default extra data, no completion Challenge( id="numeric_1", type=ChallengeType.numeric, active=True, amount="5", step_count=5, ), # Numeric 2 with some extra data Challenge( id="numeric_2", type=ChallengeType.numeric, active=True, amount="5", step_count=5, ), ] user_challenges = [ UserChallenge( challenge_id="numeric_2", user_id=1, specifier="1", is_complete=False, current_step_count=5, ), ] session.query(Challenge).delete() session.commit() session.add_all(blocks) session.commit() session.add_all(users) session.commit() session.add_all(challenges) session.commit() session.add_all(user_challenges) session.commit() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) bus.register_listener( DEFAULT_EVENT, ChallengeManager("numeric_1", NumericCustomUpdater())) bus.register_listener( DEFAULT_EVENT, ChallengeManager("numeric_2", NumericCustomUpdater())) return bus
def test_catches_exceptions_in_single_processor(app): """Ensure that if a single processor fails, the others still succeed""" with app.app_context(): db = get_db() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) with db.scoped_session() as session: session.add_all([ Challenge( id="test_challenge_1", type=ChallengeType.numeric, amount="5", step_count=3, active=True, ), Challenge( id="test_challenge_2", type=ChallengeType.numeric, amount="5", step_count=3, active=True, ), ]) session.commit() correct_manager = ChallengeManager("test_challenge_1", DefaultUpdater()) broken_manager = ChallengeManager("test_challenge_2", BrokenUpdater()) TEST_EVENT = "TEST_EVENT" TEST_EVENT_2 = "TEST_EVENT_2" bus.register_listener(TEST_EVENT, correct_manager) bus.register_listener(TEST_EVENT_2, broken_manager) with bus.use_scoped_dispatch_queue(): # dispatch the broken one first bus.dispatch(TEST_EVENT_2, 101, 1) bus.dispatch(TEST_EVENT, 101, 1) try: bus.process_events(session) except: # pylint: disable=W0707 raise Exception("Shouldn't have propogated error!") challenge_1_state = correct_manager.get_user_challenge_state( session, ["1"]) # Make sure that the 'correct_manager' still executes assert len(challenge_1_state) == 1 assert challenge_1_state[0].current_step_count == 1 # Make sure broken manager didn't do anything challenge_2_state = broken_manager.get_user_challenge_state( session, ["1"]) assert len(challenge_2_state) == 0 # Try the other order with bus.use_scoped_dispatch_queue(): # dispatch the correct one first bus.dispatch(TEST_EVENT, 101, 1) bus.dispatch(TEST_EVENT_2, 101, 1) try: bus.process_events(session) except: # pylint: disable=W0707 raise Exception("Shouldn't have propogated error!") challenge_1_state = correct_manager.get_user_challenge_state( session, ["1"]) assert len(challenge_1_state) == 1 assert challenge_1_state[0].current_step_count == 2 # Make sure broken manager didn't do anything challenge_2_state = broken_manager.get_user_challenge_state( session, ["1"]) assert len(challenge_2_state) == 0
def test_aggregates(app): setup_challenges(app) with app.app_context(): db = get_db() redis_conn = redis.Redis.from_url(url=REDIS_URL) with db.scoped_session() as session: bus = ChallengeEventBus(redis_conn) agg_challenge = ChallengeManager("test_challenge_3", AggregateUpdater()) agg_challenge.process(session, "test_event", []) TEST_EVENT = "TEST_EVENT" bus.register_listener( TEST_EVENT, ChallengeManager("test_challenge_1", DefaultUpdater())) bus.register_listener( TEST_EVENT, ChallengeManager("test_challenge_2", DefaultUpdater())) # - Multiple events with the same user_id but diff specifiers get created bus.register_listener(TEST_EVENT, agg_challenge) bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 2}) bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 3}) bus.flush() bus.process_events(session) state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"]) assert len(state) == 2 # Also make sure the thing is incomplete res = get_challenges(1, False, session, bus) agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"] assert agg_chal["is_complete"] == False # - Multiple events with the same specifier get deduped bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 4}) bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 4}) bus.flush() bus.process_events(session) state = agg_challenge.get_user_challenge_state(session, ["1-4"]) assert len(state) == 1 # - If we've maxed the # of challenges, don't create any more bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 5}) bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 6}) bus.flush() bus.process_events(session) def get_user_challenges(): return (session.query(UserChallenge).filter( UserChallenge.challenge_id == "test_challenge_3", UserChallenge.user_id == 1, ).all()) assert len(get_user_challenges()) == 5 bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 7}) bus.flush() bus.process_events(session) assert len(get_user_challenges()) == 5 # Test get_challenges res = get_challenges(1, False, session, bus) agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"] assert agg_chal["is_complete"] == True # Assert all user challenges have proper finishing block # user_challenges = get_user_challenges() for uc in user_challenges: assert uc.completed_blocknumber == 100
def test_in_memory_queue(app): setup_challenges(app) with app.app_context(): db = get_db() redis_conn = redis.Redis.from_url(url=REDIS_URL) bus = ChallengeEventBus(redis_conn) with db.scoped_session() as session, bus.use_scoped_dispatch_queue(): agg_challenge = ChallengeManager("test_challenge_3", AggregateUpdater()) agg_challenge.process(session, "test_event", []) TEST_EVENT = "TEST_EVENT" bus.register_listener( TEST_EVENT, ChallengeManager("test_challenge_1", DefaultUpdater())) bus.register_listener( TEST_EVENT, ChallengeManager("test_challenge_2", DefaultUpdater())) # - Multiple events with the same user_id but diff specifiers get created bus.register_listener(TEST_EVENT, agg_challenge) bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 2}) bus.dispatch(TEST_EVENT, 100, 1, {"referred_id": 3}) bus.process_events(session) # no events should be processed because we haven't dispatched yet state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"]) assert len(state) == 0 bus.process_events(session) state = agg_challenge.get_user_challenge_state(session, ["1-2", "1-3"]) assert len(state) == 2 # Also make sure the thing is incomplete res = get_challenges(1, False, session, bus) agg_chal = {c["challenge_id"]: c for c in res}["test_challenge_3"] assert agg_chal["is_complete"] == False redis_conn = redis.Redis.from_url(url=REDIS_URL)
def test_track_upload_challenge(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) # create user with app.app_context(): db = get_db() today = date.today() block1 = Block(blockhash="0x1", number=1) block2 = Block(blockhash="0x2", number=30000000) block3 = Block(blockhash="0x3", number=30000001) user = User( blockhash="0x1", blocknumber=1, txhash="xyz", user_id=1, handle="TestHandle", handle_lc="testhandle", is_current=True, created_at=today - timedelta(days=100), updated_at=today - timedelta(days=100), ) track1 = Track( blockhash="0x1", blocknumber=1, txhash="xyz", owner_id=1, track_id=1, route_id="1", track_segments=[], is_unlisted=False, is_current=True, is_delete=False, created_at=today - timedelta(days=100), updated_at=today - timedelta(days=100), ) track2 = Track( blockhash="0x2", blocknumber=30000000, txhash="yzx", owner_id=1, track_id=2, route_id="2", track_segments=[], is_unlisted=False, is_current=True, is_delete=False, created_at=today - timedelta(days=1), updated_at=today - timedelta(days=1), ) track3 = Track( blockhash="0x3", blocknumber=30000001, txhash="zxy", owner_id=1, track_id=3, route_id="3", track_segments=[], is_unlisted=False, is_current=True, is_delete=False, created_at=today, updated_at=today, ) track4 = Track( blockhash="0x3", blocknumber=30000001, txhash="abc", owner_id=1, track_id=4, route_id="4", track_segments=[], is_unlisted=False, is_current=True, is_delete=False, created_at=today, updated_at=today, ) unlisted_track = Track( blockhash="0x3", blocknumber=30000001, txhash="cba", owner_id=1, track_id=5, route_id="5", track_segments=[], is_unlisted=True, is_current=True, is_delete=False, created_at=today, updated_at=today, ) stem = Track( blockhash="0x3", blocknumber=30000001, txhash="stem", owner_id=1, track_id=6, route_id="6", track_segments=[], is_unlisted=False, is_current=True, is_delete=False, created_at=today, updated_at=today, stem_of={"parent_track_id": 4, "category": "bass"}, ) with db.scoped_session() as session: bus = ChallengeEventBus(redis_conn) # Register events with the bus bus.register_listener( ChallengeEvent.track_upload, track_upload_challenge_manager ) # set challenge as active for purposes of test session.query(Challenge).filter(Challenge.id == "track-upload").update( {"active": True} ) session.add(block1) session.add(block2) session.add(block3) session.flush() session.add(user) session.add(track1) # Process dummy event at block number before this challenge is added bus.dispatch(ChallengeEvent.track_upload, 1, 1) bus.flush() bus.process_events(session) user_challenges = track_upload_challenge_manager.get_user_challenge_state( session, ["1"] ) # We should not have registered a count for this event assert not user_challenges # Process dummy event at block number when challenge is added session.add(track2) bus.dispatch(ChallengeEvent.track_upload, 30000000, 1) bus.flush() bus.process_events(session) user_challenge = track_upload_challenge_manager.get_user_challenge_state( session, ["1"] )[0] # We should have completed a single step (one track upload) assert user_challenge.current_step_count == 1 assert not user_challenge.is_complete # Ensure unlisted tracks and stems are not counted session.add(unlisted_track) bus.dispatch(ChallengeEvent.track_upload, 30000001, 1) session.add(stem) bus.dispatch(ChallengeEvent.track_upload, 30000001, 1) bus.flush() bus.process_events(session) user_challenge = track_upload_challenge_manager.get_user_challenge_state( session, ["1"] )[0] # Ensure stem is not counted assert user_challenge.current_step_count == 1 # Process two more dummy events to reach the step count (i.e. 3) for completion session.add(track3) bus.dispatch(ChallengeEvent.track_upload, 30000001, 1) session.add(track4) bus.dispatch(ChallengeEvent.track_upload, 30000001, 1) bus.flush() bus.process_events(session) user_challenge = track_upload_challenge_manager.get_user_challenge_state( session, ["1"] )[0] # We should have completed the challenge assert user_challenge.current_step_count == 3 assert user_challenge.is_complete # ensure that if we lose some data now that the thing is complete, we don't change the status of the challenge session.query(Track).filter(Track.owner_id == user.user_id).update( {"is_delete": True} ) session.flush() bus.dispatch(ChallengeEvent.track_upload, 3, 1) bus.flush() bus.process_events(session) user_challenge = track_upload_challenge_manager.get_user_challenge_state( session, ["1"] )[0] # The challenge should still be completed assert user_challenge.current_step_count == 3 assert user_challenge.is_complete
def test_referral_challenge(app): redis_conn = redis.Redis.from_url(url=REDIS_URL) with app.app_context(): db = get_db() block = Block(blockhash="0x1", number=BLOCK_NUMBER) referrer = User( blockhash="0x1", blocknumber=BLOCK_NUMBER, txhash="xyz", user_id=1, is_current=True, handle="Referrer", handle_lc="referrer", wallet="0x1", is_creator=False, is_verified=False, name="referrer_name", created_at=datetime.now(), updated_at=datetime.now(), ) with db.scoped_session() as session: # Setup bus = ChallengeEventBus(redis_conn) bus.register_listener(ChallengeEvent.referred_signup, referred_challenge_manager) bus.register_listener(ChallengeEvent.referral_signup, referral_challenge_manager) bus.register_listener(ChallengeEvent.referral_signup, verified_referral_challenge_manager) session.add(block) session.flush() session.add(referrer) session.flush() # set challenge as active for purposes of test session.query(Challenge).filter( or_( Challenge.id == "referred", Challenge.id == "referrals", Challenge.id == "ref-v", )).update({ "active": True, "starting_block": BLOCK_NUMBER }) # Test: # Ensure a single referral from single signup # despite many challenge events dispatch_new_user_signup(referrer.user_id, 2, session, bus) for _ in range(0, 4): bus.dispatch( ChallengeEvent.referral_signup, BLOCK_NUMBER, referrer.user_id, {"referred_user_id": 2}, ) bus.dispatch(ChallengeEvent.referred_signup, BLOCK_NUMBER, 2) bus.flush() bus.process_events(session) challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == referrer.user_id).all()) assert len(challenges) == 1 # Test: # Multiple signups # - Referrer is capped at 5 # - Referred can keep going dispatch_new_user_signup(referrer.user_id, 3, session, bus) dispatch_new_user_signup(referrer.user_id, 4, session, bus) dispatch_new_user_signup(referrer.user_id, 5, session, bus) dispatch_new_user_signup(referrer.user_id, 6, session, bus) dispatch_new_user_signup(referrer.user_id, 7, session, bus) dispatch_new_user_signup(referrer.user_id, 8, session, bus) dispatch_new_user_signup(referrer.user_id, 9, session, bus) dispatch_new_user_signup(referrer.user_id, 10, session, bus) dispatch_new_user_signup(referrer.user_id, 11, session, bus) bus.flush() bus.process_events(session) challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == referrer.user_id, UserChallenge.challenge_id == "referrals", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 5 challenges = (session.query(UserChallenge).filter( UserChallenge.challenge_id == "referred", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 10 # Test: # Ensure there are no verified user referrals created yet challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == referrer.user_id, UserChallenge.challenge_id == "ref-v", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 0 # Test: verified users # - Ensure that verified user referrals aren't counted # for referrer credit # - Ensure that a verified user challenge exists verified_user = User( blockhash="0x1", blocknumber=BLOCK_NUMBER, txhash="xyz", user_id=12, is_current=True, handle="VerifiedReferrer", handle_lc="verifiedreferrer", wallet="0x1", is_creator=False, is_verified=True, name="referrer_name", created_at=datetime.now(), updated_at=datetime.now(), ) session.add(verified_user) session.flush() dispatch_new_user_signup(verified_user.user_id, 13, session, bus) bus.flush() bus.process_events(session) # Ensure no regular referral created challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == verified_user.user_id, UserChallenge.challenge_id == "referrals", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 0 # Ensure one verified referral created challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == verified_user.user_id, UserChallenge.challenge_id == "ref-v", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 1 # Test: verified max count # - Ensure with > 5000 verified referrals, we cap at 5000 # - No regular referrals are made for i in range(5010): dispatch_new_user_signup(verified_user.user_id, 14 + i, session, bus) if i % 500 == 0: bus.flush() bus.process_events(session) bus.flush() bus.process_events(session) # Ensure 5000 verified referral created challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == verified_user.user_id, UserChallenge.challenge_id == "ref-v", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 5000 # Ensure no regular referral created challenges = (session.query(UserChallenge).filter( UserChallenge.user_id == verified_user.user_id, UserChallenge.challenge_id == "referrals", UserChallenge.is_complete == True, ).all()) assert len(challenges) == 0
def get_challenges( user_id: int, show_historical: bool, session: Session, event_bus: ChallengeEventBus, ) -> List[ChallengeResponse]: challenges_and_disbursements: List[Tuple[UserChallenge, ChallengeDisbursement]] = ( session.query(UserChallenge, ChallengeDisbursement) # Need to do outerjoin because some challenges # may not have disbursements .outerjoin( ChallengeDisbursement, and_( ChallengeDisbursement.specifier == UserChallenge.specifier, ChallengeDisbursement.challenge_id == UserChallenge.challenge_id, ), ).filter(UserChallenge.user_id == user_id) ).all() # Filter to challenges that have active managers # (in practice, all challenge should) challenges_and_disbursements = [ c for c in challenges_and_disbursements if event_bus.does_manager_exist(c[0].challenge_id) ] # Combine aggregates all_challenges: List[Challenge] = (session.query(Challenge)).all() all_challenges_map = {challenge.id: challenge for challenge in all_challenges} # grab user challenges # if not historical, filter only to *active* challenges existing_user_challenges: List[UserChallenge] = [ i[0] for i in challenges_and_disbursements if show_historical or all_challenges_map[i[0].challenge_id].active ] disbursements: List[ChallengeDisbursement] = [ i[1] for i in challenges_and_disbursements ] regular_user_challenges: List[ChallengeResponse] = [] aggregate_user_challenges_map: DefaultDict[str, List[UserChallenge]] = defaultdict( lambda: [] ) # Get extra metadata existing_metadata = get_challenges_metadata( session, event_bus, existing_user_challenges ) for i, user_challenge in enumerate(existing_user_challenges): parent_challenge = all_challenges_map[user_challenge.challenge_id] if parent_challenge.type == ChallengeType.aggregate: # Filter out aggregate user_challenges that aren't complete. # this probably shouldn't even happen (what does it mean?) if user_challenge.is_complete: aggregate_user_challenges_map[user_challenge.challenge_id].append( user_challenge ) else: # If we're a trending challenge, don't add if the user_challenge is incomplete if ( parent_challenge.type == ChallengeType.trending and not user_challenge.is_complete ): continue user_challenge_dict = to_challenge_response( user_challenge, parent_challenge, disbursements[i], existing_metadata[i], ) override_step_count = event_bus.get_manager( parent_challenge.id ).get_override_challenge_step_count(session, user_id) if override_step_count is not None and not user_challenge.is_complete: user_challenge_dict["current_step_count"] = override_step_count regular_user_challenges.append(user_challenge_dict) rolled_up: List[ChallengeResponse] = [] for (challenge_id, challenges) in aggregate_user_challenges_map.items(): parent_challenge = all_challenges_map[challenge_id] rolled_up.append(rollup_aggregates(challenges, parent_challenge)) # Return empty user challenges for active challenges that are non-hidden # and visible for the current user active_non_hidden_challenges: List[Challenge] = [ challenge for challenge in all_challenges if ( challenge.active and not challenge.type == ChallengeType.trending and event_bus.get_manager(challenge.id).should_show_challenge_for_user( session, user_id ) ) ] existing_challenge_ids = { user_challenge.challenge_id for user_challenge in existing_user_challenges } needs_user_challenge = [ challenge for challenge in active_non_hidden_challenges if challenge.id not in existing_challenge_ids ] empty_metadata = get_empty_metadata(event_bus, needs_user_challenge) empty_challenges = create_empty_user_challenges( user_id, needs_user_challenge, empty_metadata ) combined = regular_user_challenges + rolled_up + empty_challenges return combined
def dispatch_favorite(bus: ChallengeEventBus, save, block_number): bus.dispatch(ChallengeEvent.favorite, block_number, save.user_id)
def dispatch_challenge_follow(bus: ChallengeEventBus, follow, block_number): bus.dispatch(ChallengeEvent.follow, block_number, follow.follower_user_id)
def process_transfer_instruction( session: Session, redis: Redis, instruction: TransactionMessageInstruction, account_keys: List[str], meta: ResultMeta, tx_sig: str, slot: int, challenge_event_bus: ChallengeEventBus, timestamp: datetime.datetime, ): # The transaction might list sender/receiver in a different order in the pubKeys. # The "accounts" field of the instruction has the mapping of accounts to pubKey index sender_index = instruction["accounts"][TRANSFER_SENDER_ACCOUNT_INDEX] receiver_index = instruction["accounts"][TRANSFER_RECEIVER_ACCOUNT_INDEX] sender_account = account_keys[sender_index] receiver_account = account_keys[receiver_index] # Accounts to refresh balance logger.info( f"index_user_bank.py | Balance refresh accounts: {sender_account}, {receiver_account}" ) user_id_accounts = refresh_user_balances( session, redis, [sender_account, receiver_account]) # If there are two userbanks to update, it was a transfer from user to user # Index as a user_tip if user_id_accounts and len(user_id_accounts) == 2: sender_user_id: Optional[int] = None receiver_user_id: Optional[int] = None for user_id_account in user_id_accounts: if user_id_account[1] == sender_account: sender_user_id = user_id_account[0] elif user_id_account[1] == receiver_account: receiver_user_id = user_id_account[0] if sender_user_id is None or receiver_user_id is None: logger.error( f"index_user_bank.py | ERROR: Unexpected user ids in results: {user_id_accounts}" ) return # Find the right pre/post balances using the account indexes # for the sender/receiver accounts since they aren't necessarily given in order pre_sender_balance_dict = next( (balance for balance in meta["preTokenBalances"] if balance["accountIndex"] == sender_index), None, ) pre_receiver_balance_dict = next( (balance for balance in meta["preTokenBalances"] if balance["accountIndex"] == receiver_index), None, ) post_sender_balance_dict = next( (balance for balance in meta["postTokenBalances"] if balance["accountIndex"] == sender_index), None, ) post_receiver_balance_dict = next( (balance for balance in meta["postTokenBalances"] if balance["accountIndex"] == receiver_index), None, ) if (pre_sender_balance_dict is None or pre_receiver_balance_dict is None or post_sender_balance_dict is None or post_receiver_balance_dict is None): logger.error( "index_user_bank.py | ERROR: Sender or Receiver balance missing!" ) return pre_sender_balance = int( pre_sender_balance_dict["uiTokenAmount"]["amount"]) post_sender_balance = int( post_sender_balance_dict["uiTokenAmount"]["amount"]) pre_receiver_balance = int( pre_receiver_balance_dict["uiTokenAmount"]["amount"]) post_receiver_balance = int( post_receiver_balance_dict["uiTokenAmount"]["amount"]) sent_amount = pre_sender_balance - post_sender_balance received_amount = post_receiver_balance - pre_receiver_balance if sent_amount != received_amount: logger.error( f"index_user_bank.py | ERROR: Sent and received amounts don't match. Sent = {sent_amount}, Received = {received_amount}" ) return user_tip = UserTip( signature=tx_sig, amount=sent_amount, sender_user_id=sender_user_id, receiver_user_id=receiver_user_id, slot=slot, created_at=timestamp, ) logger.debug(f"index_user_bank.py | Creating tip {user_tip}") session.add(user_tip) challenge_event_bus.dispatch(ChallengeEvent.send_tip, slot, sender_user_id)
def test_trending_challenge_job(app): with app.app_context(): db = get_db() redis_conn = redis.Redis.from_url(url=REDIS_URL) test_entities = { "tracks": [ { "track_id": 1, "owner_id": 1 }, { "track_id": 2, "owner_id": 2 }, { "track_id": 3, "owner_id": 3 }, { "track_id": 4, "owner_id": 4 }, { "track_id": 5, "owner_id": 5 }, { "track_id": 6, "owner_id": 2 }, { "track_id": 7, "owner_id": 3 }, { "track_id": 8, "owner_id": 3 }, { "track_id": 9, "is_unlisted": True, "owner_id": 3 }, { "track_id": 11, "owner_id": 1 }, { "track_id": 12, "owner_id": 2 }, { "track_id": 13, "owner_id": 3 }, { "track_id": 14, "owner_id": 4 }, { "track_id": 15, "owner_id": 5 }, ], "playlists": [ { "playlist_id": 1, "playlist_owner_id": 1, "playlist_name": "name", "description": "description", "playlist_contents": { "track_ids": [ { "track": 1, "time": 1 }, { "track": 2, "time": 2 }, { "track": 3, "time": 3 }, ] }, }, { "playlist_id": 2, "playlist_owner_id": 2, "playlist_name": "name", "description": "description", "playlist_contents": { "track_ids": [ { "track": 1, "time": 1 }, { "track": 2, "time": 2 }, { "track": 3, "time": 3 }, ] }, }, { "playlist_id": 3, "is_album": True, "playlist_owner_id": 3, "playlist_name": "name", "description": "description", "playlist_contents": { "track_ids": [ { "track": 1, "time": 1 }, { "track": 2, "time": 2 }, { "track": 3, "time": 3 }, ] }, }, { "playlist_id": 4, "playlist_owner_id": 4, "playlist_name": "name", "description": "description", "playlist_contents": { "track_ids": [ { "track": 1, "time": 1 }, { "track": 2, "time": 2 }, { "track": 3, "time": 3 }, ] }, }, { "playlist_id": 5, "playlist_owner_id": 5, "playlist_name": "name", "description": "description", "playlist_contents": { "track_ids": [ { "track": 1, "time": 1 }, { "track": 2, "time": 2 }, { "track": 3, "time": 3 }, ] }, }, ], "users": [ { "user_id": 1, "handle": "user1" }, { "user_id": 2, "handle": "user2" }, { "user_id": 3, "handle": "user3" }, { "user_id": 4, "handle": "user4" }, { "user_id": 5, "handle": "user5" }, ], "follows": [ { "follower_user_id": 1, "followee_user_id": 2, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 1, "followee_user_id": 3, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 2, "followee_user_id": 3, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 2, "followee_user_id": 4, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 3, "followee_user_id": 6, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 4, "followee_user_id": 5, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 5, "followee_user_id": 1, "created_at": datetime.now() - timedelta(days=8), }, { "follower_user_id": 6, "followee_user_id": 3, "created_at": datetime.now() - timedelta(days=8), }, ], "reposts": [ { "repost_item_id": 1, "repost_type": "track", "user_id": 2 }, { "repost_item_id": 1, "repost_type": "playlist", "user_id": 2 }, { "repost_item_id": 3, "repost_type": "track", "user_id": 3 }, { "repost_item_id": 1, "repost_type": "playlist", "user_id": 3 }, { "repost_item_id": 4, "repost_type": "track", "user_id": 1 }, { "repost_item_id": 5, "repost_type": "track", "user_id": 1 }, { "repost_item_id": 6, "repost_type": "track", "user_id": 1 }, ], "saves": [ { "save_item_id": 1, "save_type": "track", "user_id": 2 }, { "save_item_id": 1, "save_type": "track", "user_id": 3 }, { "save_item_id": 4, "save_type": "track", "user_id": 1 }, { "save_item_id": 5, "save_type": "track", "user_id": 1 }, { "save_item_id": 6, "save_type": "track", "user_id": 1 }, { "save_item_id": 1, "save_type": "playlist", "user_id": 4 }, { "save_item_id": 2, "save_type": "playlist", "user_id": 3 }, { "save_item_id": 3, "save_type": "playlist", "user_id": 2 }, { "save_item_id": 4, "save_type": "playlist", "user_id": 1 }, { "save_item_id": 5, "save_type": "playlist", "user_id": 2 }, ], "plays": [{ "item_id": 1 } for _ in range(55)] + [{ "item_id": 2 } for _ in range(60)] + [{ "item_id": 3 } for _ in range(70)] + [{ "item_id": 4 } for _ in range(90)] + [{ "item_id": 5 } for _ in range(80)] + [{ "item_id": 6 } for _ in range(40)] + [{ "item_id": 11 } for _ in range(200)] + [{ "item_id": 12 } for _ in range(200)] + [{ "item_id": 13 } for _ in range(200)] + [{ "item_id": 14 } for _ in range(200)] + [{ "item_id": 15 } for _ in range(200)], } populate_mock_db(db, test_entities, BLOCK_NUMBER + 1) bus = ChallengeEventBus(redis_conn) # Register events with the bus bus.register_listener( ChallengeEvent.trending_underground, trending_underground_track_challenge_manager, ) bus.register_listener(ChallengeEvent.trending_track, trending_track_challenge_manager) bus.register_listener(ChallengeEvent.trending_playlist, trending_playlist_challenge_manager) trending_date = datetime.fromisoformat("2021-08-20") with db.scoped_session() as session: _update_aggregate_plays(session) _update_aggregate_track(session) _update_aggregate_user(session) session.execute("REFRESH MATERIALIZED VIEW aggregate_interval_plays") session.execute("REFRESH MATERIALIZED VIEW trending_params") trending_track_versions = trending_strategy_factory.get_versions_for_type( TrendingType.TRACKS).keys() for version in trending_track_versions: strategy = trending_strategy_factory.get_strategy( TrendingType.TRACKS, version) if strategy.use_mat_view: strategy.update_track_score_query(session) session.commit() enqueue_trending_challenges(db, redis_conn, bus, trending_date) with db.scoped_session() as session: session.query(Challenge).filter( or_( Challenge.id == "tp", Challenge.id == "tt", Challenge.id == "tut", )).update({ "active": True, "starting_block": BLOCK_NUMBER }) bus.process_events(session) session.flush() trending_tracks = (session.query(TrendingResult).filter( TrendingResult.type == str(TrendingType.TRACKS)).all()) assert len(trending_tracks) == 5 user_trending_tracks_challenges = (session.query(UserChallenge).filter( UserChallenge.challenge_id == "tt").all()) assert len(user_trending_tracks_challenges) == 5 ranks = { "2021-08-20:1", "2021-08-20:2", "2021-08-20:3", "2021-08-20:4", "2021-08-20:5", } for challenge in user_trending_tracks_challenges: assert challenge.specifier in ranks ranks.remove(challenge.specifier) trending_playlists = (session.query(TrendingResult).filter( TrendingResult.type == str(TrendingType.PLAYLISTS)).all()) assert len(trending_playlists) == 5
def dispatch_challenge_track_upload(bus: ChallengeEventBus, block_number: int, track_record): bus.dispatch(ChallengeEvent.track_upload, block_number, track_record.owner_id)