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
Example #2
0
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()},
    )
Example #3
0
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
Example #4
0
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
Example #8
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),
            },
        )
Example #9
0
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
Example #10
0
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
Example #11
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
Example #14
0
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"
    )
Example #15
0
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
Example #18
0
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)
Example #23
0
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
Example #24
0
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
Example #25
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
Example #26
0
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
Example #30
0
def dispatch_challenge_track_upload(bus: ChallengeEventBus, block_number: int,
                                    track_record):
    bus.dispatch(ChallengeEvent.track_upload, block_number,
                 track_record.owner_id)