def test_line_charts(self): # TODO: This test throws errors related to missing data in games 1 and 4. For now we're not worried about this, # since game #3 is our realistic test case, but could be worth going back and debugging later. game_id = 3 user_ids = [1, 3, 4] compile_and_pack_player_leaderboard(game_id) with patch("backend.logic.base.time") as mock_base_time: mock_base_time.time.return_value = simulation_end_time make_the_field_charts(game_id) # this is basically the internals of async_update_all_games for one game for user_id in user_ids: serialize_and_pack_pending_orders(game_id, user_id) serialize_and_pack_portfolio_details(game_id, user_id) # Verify that the JSON objects for chart visuals were computed and cached as expected field_chart = s3_cache.unpack_s3_json("3/field_chart") self.assertIsNotNone(field_chart) self.assertIsNotNone(s3_cache.unpack_s3_json("3/1/current_balances")) self.assertIsNotNone(s3_cache.unpack_s3_json("3/3/current_balances")) self.assertIsNotNone(s3_cache.unpack_s3_json("3/4/current_balances")) # verify chart information test_user_data = [ x for x in field_chart["datasets"] if x["label"] == "cheetos" ][0] self.assertLessEqual(len(test_user_data["data"]), N_PLOT_POINTS)
def add_fulfilled_order_entry(game_id: int, user_id: int, order_id: int): """Add a fulfilled order to the fulfilled orders table without rebuilding the entire asset""" order_status_entry = query_to_dict(""" SELECT * FROM order_status WHERE order_id = %s ORDER BY id DESC LIMIT 0, 1""", order_id)[0] if order_status_entry["status"] == "fulfilled": order_entry = query_to_dict("SELECT * FROM orders WHERE id = %s;", order_id)[0] symbol = order_entry['symbol'] timestamp = order_status_entry["timestamp"] clear_price = order_status_entry["clear_price"] quantity = order_entry["quantity"] order_label = f"{symbol}/{int(quantity)} @ {USD_FORMAT.format(clear_price)}/{format_posix_time(timestamp)}" buy_or_sell = order_entry["buy_or_sell"] new_entry = { "order_label": order_label, "event_type": buy_or_sell, "Symbol": symbol, "Cleared on": timestamp, "Quantity": quantity, "Clear price": clear_price, "Basis": quantity * clear_price if buy_or_sell == "buy" else NA_TEXT_SYMBOL, "Balance (FIFO)": quantity if buy_or_sell == "buy" else NA_NUMERIC_VAL, "Realized P&L": 0 if buy_or_sell == "buy" else NA_NUMERIC_VAL, "Realized P&L (%)": 0 if buy_or_sell == "buy" else NA_NUMERIC_VAL, "Unrealized P&L": 0 if buy_or_sell == "buy" else NA_NUMERIC_VAL, "Unrealized P&L (%)": 0 if buy_or_sell == "buy" else NA_NUMERIC_VAL, "Market price": clear_price, "as of": timestamp, "color": NULL_RGBA } assert set(FULFILLED_ORDER_MAPPINGS.values()) - set(new_entry.keys()) == set() fulfilled_order_table = s3_cache.unpack_s3_json(f"{game_id}/{user_id}/{FULFILLED_ORDER_PREFIX}") fulfilled_order_table["data"] = [new_entry] + fulfilled_order_table["data"] s3_cache.set(f"{game_id}/{user_id}/{FULFILLED_ORDER_PREFIX}", json.dumps(fulfilled_order_table))
def test_splits(self): df = make_order_performance_table(self.game_id, self.user_id) pd.testing.assert_frame_equal(df, pd.DataFrame(self.RECORDS)) serialize_and_pack_order_performance_assets(self.game_id, self.user_id) perf_table = s3_cache.unpack_s3_json(f"{self.game_id}/{self.user_id}/{FULFILLED_ORDER_PREFIX}") perf_chart = s3_cache.unpack_s3_json(f"{self.game_id}/{self.user_id}/{ORDER_PERF_CHART_PREFIX}") perf_table_df = pd.DataFrame(perf_table["data"]) self.assertEqual(perf_table_df.shape, (22, 15)) buy_perf_entries = [x for x in perf_table["data"] if x["event_type"] == "buy"] order_labels_table = set([x["order_label"] for x in buy_perf_entries]) order_labels_chart = set([x["label"] for x in perf_chart["datasets"]]) self.assertEqual(order_labels_chart, order_labels_table) for label in order_labels_table: table_entry = [x for x in buy_perf_entries if x["order_label"] == label][0] chart_entry = [x for x in perf_chart["datasets"] if x["label"] == label][0] self.assertEqual(table_entry["color"], chart_entry["backgroundColor"])
def get_current_balances_table(): game_id = request.json.get("game_id") username = request.json.get("username") if username: user_id = get_user_ids([username])[0] else: user_id = decode_token(request) return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{user_id}/{CURRENT_BALANCES_PREFIX}"))
def get_pending_orders_table(): game_id = request.json.get("game_id") username = request.json.get("username") if username: user_id = get_user_ids([username])[0] else: user_id = decode_token(request) return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{user_id}/{PENDING_ORDERS_PREFIX}"))
def get_fulfilled_orders_table(): game_id = request.json.get("game_id") username = request.json.get("username") if username: user_id = get_user_ids([username])[0] else: user_id = decode_token(request) return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{user_id}/{FULFILLED_ORDER_PREFIX}"))
def api_game_info(): user_id = decode_token(request) game_id = request.json.get("game_id") game_info = get_game_info(game_id) game_info["is_host"] = game_info["creator_id"] == user_id game_info["user_status"] = get_user_invite_status_for_game(game_id, user_id) if game_info["game_status"] in ["active", "finished"]: game_info["leaderboard"] = s3_cache.unpack_s3_json(f"{game_id}/{LEADERBOARD_PREFIX}")["records"] return jsonify(game_info)
def order_performance_chart(): """This endpoints works exactly the same as balances_chart. See above for details """ game_id = request.json.get("game_id") username = request.json.get("username") if username: user_id = get_user_ids([username])[0] else: user_id = decode_token(request) return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{user_id}/{ORDER_PERF_CHART_PREFIX}"))
def test_single_player_visuals(self, mock_base_time, mock_game_time): mock_base_time.time.return_value = mock_game_time.time.return_value = simulation_end_time game_id = 8 user_id = 1 serialize_and_pack_pending_orders(game_id, user_id) serialize_and_pack_order_performance_assets(game_id, user_id) pending_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{user_id}/{PENDING_ORDERS_PREFIX}") fulfilled_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{user_id}/{FULFILLED_ORDER_PREFIX}") self.assertEqual(len(pending_orders_table["data"]), 0) self.assertEqual(len(fulfilled_orders_table["data"]), 2) self.assertEqual( set([x["Symbol"] for x in fulfilled_orders_table["data"]]), {"NVDA", "NKE"}) serialize_and_pack_order_performance_assets(game_id, user_id) self.assertIn(f"{game_id}/{user_id}/{ORDER_PERF_CHART_PREFIX}", s3_cache.keys()) op_chart = s3_cache.unpack_s3_json( f"{game_id}/{user_id}/{ORDER_PERF_CHART_PREFIX}") chart_stocks = set( [x["label"].split("/")[0] for x in op_chart["datasets"]]) expected_stocks = {"NKE", "NVDA"} self.assertEqual(chart_stocks, expected_stocks) # balances chart df = make_user_balances_chart_data(game_id, user_id) serialize_and_pack_balances_chart(df, game_id, user_id) balances_chart = s3_cache.unpack_s3_json( f"{game_id}/{user_id}/{BALANCES_CHART_PREFIX}") self.assertEqual(set([x["label"] for x in balances_chart["datasets"]]), {"NVDA", "NKE", "Cash"}) # leaderboard and field charts compile_and_pack_player_leaderboard(game_id) make_the_field_charts(game_id) field_chart = s3_cache.unpack_s3_json( f"{game_id}/{FIELD_CHART_PREFIX}") _index_names = query_to_dict("SELECT * FROM index_metadata") _index_names = [x["name"] for x in _index_names] self.assertEqual(set([x["label"] for x in field_chart["datasets"]]), set(["cheetos"] + _index_names))
def balances_chart(): """Be default, the frontend will load with username = null for the chart selector dropdown, and we'll show them their own chart. When the user proactively picks a username to checkout, this will be sent in the POST request to this endpoint and used to pull up the appropriate chart """ game_id = request.json.get("game_id") username = request.json.get("username") if username: user_id = get_user_ids([username])[0] else: user_id = decode_token(request) return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{user_id}/{BALANCES_CHART_PREFIX}"))
def test_visuals_after_hours(self): game_id = 5 start_time = 1591923966 self._start_game_runner(start_time, game_id) # These are the internals of the celery tasks that called to update their state serialize_and_pack_pending_orders(game_id, self.user_id) pending_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{PENDING_ORDERS_PREFIX}") self.assertEqual(pending_orders_table["data"][0]["Symbol"], self.stock_pick) self.assertEqual(len(pending_orders_table["data"]), 1) fulfilled_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{FULFILLED_ORDER_PREFIX}") self.assertEqual(fulfilled_orders_table["data"], []) serialize_and_pack_portfolio_details(game_id, self.user_id) current_balances = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{CURRENT_BALANCES_PREFIX}") self.assertEqual(len(current_balances["data"]), 0) with patch("backend.logic.base.time") as base_time_mock: base_time_mock.time.side_effect = [start_time] * 2 * 2 df = make_user_balances_chart_data(game_id, self.user_id) serialize_and_pack_balances_chart(df, game_id, self.user_id) balances_chart = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{BALANCES_CHART_PREFIX}") self.assertEqual(len(balances_chart["datasets"]), 1) self.assertEqual(balances_chart["datasets"][0]["label"], "Cash") self.assertEqual(balances_chart["datasets"][0]["backgroundColor"], NULL_RGBA) compile_and_pack_player_leaderboard(game_id) leaderboard = s3_cache.unpack_s3_json( f"{game_id}/{LEADERBOARD_PREFIX}") self.assertTrue( all([ x["cash_balance"] == STARTING_VIRTUAL_CASH for x in leaderboard["records"] ]))
def test_visuals_with_data(self, mock_base_time, mock_game_time): mock_base_time.time.return_value = mock_game_time.time.return_value = simulation_end_time game_id = 3 user_id = 1 serialize_and_pack_pending_orders(game_id, user_id) pending_order_table = s3_cache.unpack_s3_json( f"{game_id}/{user_id}/{PENDING_ORDERS_PREFIX}") self.assertNotIn("order_id", pending_order_table["headers"]) self.assertEqual(len(pending_order_table["headers"]), 9) user_ids = get_active_game_user_ids(game_id) for player_id in user_ids: serialize_and_pack_order_performance_assets(game_id, player_id) op_chart_3_1 = s3_cache.unpack_s3_json( f"{game_id}/{user_id}/{ORDER_PERF_CHART_PREFIX}") chart_stocks = set( [x["label"].split("/")[0] for x in op_chart_3_1["datasets"]]) expected_stocks = {"AMZN", "TSLA", "LYFT", "SPXU", "NVDA"} self.assertEqual(chart_stocks, expected_stocks) for user_id in user_ids: self.assertIn(f"{game_id}/{user_id}/{ORDER_PERF_CHART_PREFIX}", s3_cache.keys())
def serialize_and_pack_portfolio_comps_chart(df: pd.DataFrame, game_id: int): usernames = get_game_users(game_id) user_colors = assign_colors(usernames) datasets = [] if df["username"].nunique() == df.shape[0]: # if our portfolio dataframe only has as many rows as there are users in the game, this means that we've just # started the game, and can post a null chart to the field for username, color in user_colors.items(): null_chart = make_null_chart(username) datasets.append(null_chart["datasets"][0]) labels = null_chart["labels"] chart_json = dict(labels=list(labels), datasets=datasets) else: colors = [] for username in df["username"].unique(): colors.append(user_colors[username]) chart_json = make_chart_json(df, "username", "value", colors=colors) leaderboard = s3_cache.unpack_s3_json(f"{game_id}/{LEADERBOARD_PREFIX}") chart_json["leaderboard"] = leaderboard["records"] s3_cache.set(f"{game_id}/{FIELD_CHART_PREFIX}", json.dumps(chart_json))
def test_winner_payouts(self): """Use canonical game #3 to simulate a series of winner calculations on the test data. Note that since we only have a week of test data, we'll effectively recycle the same information via mocks """ game_id = 3 user_ids = get_active_game_user_ids(game_id) self.assertEqual(user_ids, [1, 3, 4]) game_info = get_game_info(game_id) n_players = len(user_ids) pot_size = n_players * game_info["buy_in"] self.assertEqual(pot_size, 300) last_payout_date = get_last_sidebet_payout(game_id) self.assertIsNone(last_payout_date) offset = make_date_offset(game_info["side_bets_period"]) self.assertEqual(offset, DateOffset(days=7)) start_time = game_info["start_time"] end_time = game_info["end_time"] self.assertEqual(start_time, simulation_start_time) n_sidebets = n_sidebets_in_game(start_time, end_time, offset) self.assertEqual(n_sidebets, 2) # we'll mock in daily portfolio values to speed up the time this test takes user_1_portfolio = portfolio_value_by_day(game_id, 1, start_time, end_time) user_3_portfolio = portfolio_value_by_day(game_id, 3, start_time, end_time) user_4_portfolio = portfolio_value_by_day(game_id, 4, start_time, end_time) # expected sidebet dates start_dt = posix_to_datetime(start_time) end_dt = posix_to_datetime(end_time) sidebet_dates = get_expected_sidebets_payout_dates( start_dt, end_dt, game_info["side_bets_perc"], offset) sidebet_dates_posix = [datetime_to_posix(x) for x in sidebet_dates] with patch("backend.logic.metrics.portfolio_value_by_day" ) as portfolio_mocks, patch( "backend.logic.base.time") as base_time_mock: time = Mock() time_1 = datetime_to_posix(posix_to_datetime(start_time) + offset) time_2 = end_time time.time.side_effect = base_time_mock.time.side_effect = [ time_1, time_2 ] portfolio_mocks.side_effect = [ user_1_portfolio, user_3_portfolio, user_4_portfolio ] * 4 winner_id, score = get_winner(game_id, start_time, end_time, game_info["benchmark"]) log_winners(game_id, time.time()) sidebet_entry = query_to_dict( "SELECT * FROM winners WHERE id = 1;")[0] self.assertEqual(sidebet_entry["winner_id"], winner_id) self.assertAlmostEqual(sidebet_entry["score"], score, 4) side_pot = pot_size * (game_info["side_bets_perc"] / 100) / n_sidebets self.assertEqual(sidebet_entry["payout"], side_pot * PERCENT_TO_USER) self.assertEqual(sidebet_entry["type"], "sidebet") self.assertEqual(sidebet_entry["start_time"], start_time) self.assertEqual(sidebet_entry["end_time"], sidebet_dates_posix[0]) self.assertEqual(sidebet_entry["timestamp"], time_1) log_winners(game_id, time.time()) sidebet_entry = query_to_dict( "SELECT * FROM winners WHERE id = 2;")[0] self.assertEqual(sidebet_entry["winner_id"], winner_id) self.assertAlmostEqual(sidebet_entry["score"], score, 4) self.assertEqual(sidebet_entry["payout"], side_pot * PERCENT_TO_USER) self.assertEqual(sidebet_entry["type"], "sidebet") self.assertEqual(sidebet_entry["start_time"], sidebet_dates_posix[0]) self.assertEqual(sidebet_entry["end_time"], sidebet_dates_posix[1]) self.assertEqual(sidebet_entry["timestamp"], time_2) overall_entry = query_to_dict( "SELECT * FROM winners WHERE id = 3;")[0] final_payout = pot_size * (1 - game_info["side_bets_perc"] / 100) self.assertEqual(overall_entry["payout"], final_payout * PERCENT_TO_USER) with self.engine.connect() as conn: df = pd.read_sql("SELECT * FROM winners", conn) self.assertEqual(df.shape, (3, 10)) serialize_and_pack_winners_table(game_id) payouts_table = s3_cache.unpack_s3_json(f"{game_id}/{PAYOUTS_PREFIX}") self.assertEqual(len(payouts_table["data"]), 3) self.assertTrue( sum([x["Type"] == "Sidebet" for x in payouts_table["data"]]), 2) self.assertTrue( sum([x["Type"] == "Overall" for x in payouts_table["data"]]), 1) for entry in payouts_table["data"]: if entry["Type"] == "Sidebet": self.assertEqual(entry["Payout"], side_pot * PERCENT_TO_USER) else: self.assertEqual(entry["Payout"], final_payout * PERCENT_TO_USER)
def removing_pending_order(game_id: int, user_id: int, order_id: int): fn = f"{game_id}/{user_id}/{PENDING_ORDERS_PREFIX}" order_json = s3_cache.unpack_s3_json(fn) order_json["data"] = [entry for entry in order_json["data"] if entry["order_id"] != order_id] s3_cache.set(fn, json.dumps(order_json))
def api_games_per_users(): return jsonify(s3_cache.unpack_s3_json(GAMES_PER_USER_PREFIX))
def api_orders_per_active_user(): return jsonify(s3_cache.unpack_s3_json(ORDERS_PER_ACTIVE_USER_PREFIX))
def get_leaderboard(): game_id = request.json.get("game_id") return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{LEADERBOARD_PREFIX}"))
def get_payouts_table(): game_id = request.json.get("game_id") return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{PAYOUTS_PREFIX}"))
def _start_game_runner(self, start_time, game_id): s3_cache.flushall() user_statuses = get_user_invite_statuses_for_pending_game(game_id) pending_user_usernames = [ x["username"] for x in user_statuses if x["status"] == "invited" ] pending_user_ids = get_user_ids(pending_user_usernames) # get all user IDs for the game. For this test case everyone is going or accept with self.engine.connect() as conn: result = conn.execute( "SELECT DISTINCT user_id FROM game_invites WHERE game_id = %s", game_id).fetchall() all_ids = [x[0] for x in result] self.user_id = all_ids[0] # all users accept their game invite with patch("backend.logic.games.time") as game_time_mock, patch( "backend.logic.base.time") as base_time_mock: game_time_mock.time.return_value = start_time time = Mock() time.time.return_value = base_time_mock.time.return_value = start_time for user_id in pending_user_ids: respond_to_game_invite(game_id, user_id, "joined", time.time()) # check that we have the balances that we expect sql = "SELECT balance, user_id from game_balances WHERE game_id = %s;" with self.engine.connect() as conn: df = pd.read_sql(sql, conn, params=[game_id]) self.assertTrue(df.shape, (0, 2)) sql = "SELECT balance, user_id from game_balances WHERE game_id = %s;" with self.engine.connect() as conn: df = pd.read_sql(sql, conn, params=[game_id]) self.assertTrue(df.shape, (4, 2)) # a couple things should have just happened here. We expect to have the following assets available to us # now in our redis cache: (1) an empty open orders table for each user, (2) an empty current balances table for # each user, (3) an empty field chart for each user, (4) an empty field chart, and (5) an initial game stats # list cache_keys = s3_cache.keys() self.assertIn(f"{game_id}/{LEADERBOARD_PREFIX}", cache_keys) self.assertIn(f"{game_id}/{FIELD_CHART_PREFIX}", cache_keys) for user_id in all_ids: self.assertIn(f"{game_id}/{user_id}/{CURRENT_BALANCES_PREFIX}", cache_keys) self.assertIn(f"{game_id}/{user_id}/{PENDING_ORDERS_PREFIX}", cache_keys) self.assertIn(f"{game_id}/{user_id}/{FULFILLED_ORDER_PREFIX}", cache_keys) self.assertIn(f"{game_id}/{user_id}/{BALANCES_CHART_PREFIX}", cache_keys) self.assertIn(f"{game_id}/{user_id}/{ORDER_PERF_CHART_PREFIX}", cache_keys) # quickly verify the structure of the chart assets. They should be blank, with transparent colors field_chart = s3_cache.unpack_s3_json( f"{game_id}/{FIELD_CHART_PREFIX}") self.assertEqual(len(field_chart["datasets"]), len(all_ids)) chart_colors = [x["backgroundColor"] for x in field_chart["datasets"]] self.assertTrue(all([x == NULL_RGBA for x in chart_colors])) leaderboard = s3_cache.unpack_s3_json( f"{game_id}/{LEADERBOARD_PREFIX}") self.assertEqual(len(leaderboard["records"]), len(all_ids)) self.assertTrue( all([ x["cash_balance"] == STARTING_VIRTUAL_CASH for x in leaderboard["records"] ])) a_current_balance_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{CURRENT_BALANCES_PREFIX}") self.assertEqual(a_current_balance_table["data"], []) an_open_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{PENDING_ORDERS_PREFIX}") self.assertEqual(an_open_orders_table["data"], []) a_fulfilled_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{FULFILLED_ORDER_PREFIX}") self.assertEqual(a_fulfilled_orders_table["data"], []) a_balances_chart = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{BALANCES_CHART_PREFIX}") self.assertEqual(len(a_balances_chart["datasets"]), 1) self.assertEqual(a_balances_chart["datasets"][0]["label"], "Cash") self.assertEqual(a_balances_chart["datasets"][0]["backgroundColor"], NULL_RGBA) # now have a user put an order. It should go straight to the queue and be reflected in the open orders table, # but they should not have any impact on the user's balances if the order is placed outside of trading day self.stock_pick = "TSLA" self.market_price = 1_000 with patch("backend.logic.games.time") as game_time_mock, patch( "backend.logic.base.time") as base_time_mock: game_time_mock.time.side_effect = [start_time + 1] * 2 base_time_mock.time.return_value = start_time + 1 stock_pick = self.stock_pick cash_balance = get_current_game_cash_balance(self.user_id, game_id) current_holding = get_current_stock_holding( self.user_id, game_id, stock_pick) order_id = place_order(user_id=self.user_id, game_id=game_id, symbol=self.stock_pick, buy_or_sell="buy", cash_balance=cash_balance, current_holding=current_holding, order_type="market", quantity_type="Shares", market_price=self.market_price, amount=1, time_in_force="day") serialize_and_pack_pending_orders(game_id, self.user_id) add_fulfilled_order_entry(game_id, self.user_id, order_id) serialize_and_pack_portfolio_details(game_id, self.user_id)
def field_chart(): game_id = request.json.get("game_id") return jsonify(s3_cache.unpack_s3_json(f"{game_id}/{FIELD_CHART_PREFIX}"))
def test_visuals_during_trading(self): # TODO: Add a canonical test with fully populated data """When a user first places an order, we don't necessarily expect that security to have generated any data, yet. There should be a blank chart until data is available """ game_id = 5 start_time = simulation_start_time self._start_game_runner(start_time, game_id) # now have a user put in a couple orders. Valid market orders should clear and reflect in the balances table, # valid stop/limit orders should post to pending orders serialize_and_pack_pending_orders(game_id, self.user_id) pending_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{PENDING_ORDERS_PREFIX}") fulfilled_orders_table = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{FULFILLED_ORDER_PREFIX}") # since the order has been filled, we expect a clear price to be present self.assertNotEqual(fulfilled_orders_table["data"][0]["Clear price"], NA_TEXT_SYMBOL) self.assertEqual(fulfilled_orders_table["data"][0]["Symbol"], self.stock_pick) self.assertEqual(pending_orders_table["data"], []) self.assertEqual(len(fulfilled_orders_table["data"]), 1) serialize_and_pack_portfolio_details(game_id, self.user_id) current_balances = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{CURRENT_BALANCES_PREFIX}") self.assertEqual(len(current_balances["data"]), 1) with patch("backend.logic.base.time") as base_time_mock: base_time_mock.time.side_effect = [start_time + 10] * 2 * 2 df = make_user_balances_chart_data(game_id, self.user_id) serialize_and_pack_balances_chart(df, game_id, self.user_id) balances_chart = s3_cache.unpack_s3_json( f"{game_id}/{self.user_id}/{BALANCES_CHART_PREFIX}") self.assertEqual(len(balances_chart["datasets"]), 2) stocks = set([x["label"] for x in balances_chart["datasets"]]) self.assertEqual(stocks, {"Cash", self.stock_pick}) self.assertNotIn( NULL_RGBA, [x["backgroundColor"] for x in balances_chart["datasets"]]) compile_and_pack_player_leaderboard(game_id) leaderboard = s3_cache.unpack_s3_json( f"{game_id}/{LEADERBOARD_PREFIX}") user_stat_entry = [ x for x in leaderboard["records"] if x["id"] == self.user_id ][0] self.assertEqual(user_stat_entry["cash_balance"], STARTING_VIRTUAL_CASH - self.market_price) self.assertTrue( all([ x["cash_balance"] == STARTING_VIRTUAL_CASH for x in leaderboard["records"] if x["id"] != self.user_id ])) fulfilled_orders_table = s3_cache.unpack_s3_json( f'{game_id}/{self.user_id}/{FULFILLED_ORDER_PREFIX}') self.assertIn("order_label", fulfilled_orders_table["data"][0].keys()) self.assertIn("color", fulfilled_orders_table["data"][0].keys())