Skip to content

Commit

Permalink
Add lobby game status api
Browse files Browse the repository at this point in the history
  • Loading branch information
s9891326 committed Nov 3, 2023
1 parent 024c0a6 commit da8d0de
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 6 deletions.
1 change: 1 addition & 0 deletions love_letter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Configuration:
"LOBBY_ISSUER", "https://dev-1l0ixjw8yohsluoi.us.auth0.com/"
)
LOBBY_AUDIENCE = os.environ.get("LOBBY_ISSUER", "https://api.gaas.waterballsa.tw")
LOBBY_USERS_ME_API = "https://api.gaas.waterballsa.tw/users/me"


config = Configuration()
21 changes: 21 additions & 0 deletions love_letter/usecase/lobby_game_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from love_letter.models import Game
from love_letter.models.event import GetStatusEvent
from love_letter.usecase.common import Presenter, game_repository


class LobbyGameStatusInput:
game_id: str
player_id: str


class LobbyGameStatus:
def execute(self, input: LobbyGameStatusInput, presenter: Presenter):
game: Game = game_repository.get(input.game_id)
presenter.present(events=[GetStatusEvent(game)])

@classmethod
def input(cls, game_id: str, player_id: str) -> LobbyGameStatusInput:
input = LobbyGameStatusInput()
input.game_id = game_id
input.player_id = player_id
return input
14 changes: 13 additions & 1 deletion love_letter/web/app.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import Union

import uvicorn
from fastapi import Depends, FastAPI
from fastapi import Depends, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

from love_letter.models import GuessCard, ToSomeoneCard
from love_letter.usecase.create_game import CreateGame
from love_letter.usecase.get_status import GetStatus
from love_letter.usecase.join_game import JoinGame
from love_letter.usecase.lobby_game_status import LobbyGameStatus
from love_letter.usecase.lobby_start_game import LobbyStartGame
from love_letter.usecase.play_card import PlayCard
from love_letter.usecase.start_game import StartGame
Expand All @@ -23,6 +24,7 @@
PlayCardPresenter,
GetStatusPresenter,
LobbyStartGamePresenter,
LobbyGameStatusPresenter,
)

# isort: on
Expand Down Expand Up @@ -99,6 +101,16 @@ async def lobby_start_game(players: LobbyPlayers):
return presenter.as_view_model()


@app.get("/games/{game_id}/status", response_model=GameStatus)
async def lobby_game_status(request: Request, game_id: str):
jwt_token = request.headers.get("Authorization")
player_id = JWTBearer.get_player_id(jwt_token)
presenter = LobbyGameStatusPresenter.presenter()
LobbyGameStatus().execute(LobbyGameStatus.input(game_id, player_id), presenter)
game = presenter.as_view_model()
return build_player_view(game, player_id)


@app.get("/health")
async def health():
return {"success": True}
Expand Down
17 changes: 17 additions & 0 deletions love_letter/web/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
from functools import lru_cache

import jwt
import requests
from fastapi import HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from starlette.requests import Request
Expand Down Expand Up @@ -63,3 +65,18 @@ def create_jwt():
key="",
)
return f"Bearer {token}"

@staticmethod
@lru_cache
def get_player_id(jwt_token: str) -> str:
response = ""
try:
response = requests.get(
config.LOBBY_USERS_ME_API, headers={"Authorization": jwt_token}
)
except Exception as e:
print(e)

if response and response.status_code == 200:
return response.json()["id"]
raise ValueError("Not found player id")
12 changes: 12 additions & 0 deletions love_letter/web/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,15 @@ def as_view_model(self):
@classmethod
def presenter(cls) -> "LobbyStartGamePresenter":
return LobbyStartGamePresenter()


class LobbyGameStatusPresenter(Presenter):
def as_view_model(self):
for event in self.events:
if isinstance(event, GetStatusEvent):
return event.game
raise BaseException("Game is unavailable.")

@classmethod
def presenter(cls) -> "LobbyGameStatusPresenter":
return LobbyGameStatusPresenter()
90 changes: 85 additions & 5 deletions tests/test_lobby_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from starlette.testclient import TestClient
from unittest.mock import patch

from fastapi.testclient import TestClient

from love_letter.config import config
from love_letter.models import Round
from love_letter.web.app import app
from love_letter.web.auth import JWTBearer
from love_letter.web.dto import GameStatus
Expand All @@ -12,8 +15,18 @@ class LobbyTestCase(LoveLetterRepositoryAwareTestCase):
def setUp(self) -> None:
self.t: TestClient = TestClient(app)
self.jwt_token: str = JWTBearer.create_jwt()
self.game_id: str = self.__class__.game_id
# disable random-picker for the first round
# it always returns the first player
self.origin_choose_one_randomly = Round.choose_one_randomly
Round.choose_one_randomly = lambda players: players[0]

@classmethod
def setUpClass(cls):
cls.game_id: str = ""

def tearDown(self) -> None:
Round.choose_one_randomly = self.origin_choose_one_randomly
self.t.close()

def test_health_api(self):
Expand All @@ -22,7 +35,7 @@ def test_health_api(self):
self.assertEqual(response.status_code, 200)
self.assertTrue(data["success"])

def test_start_game(self):
def test_1_start_game(self):
players = [
{"id": "6497f6f226b40d440b9a90cc", "nickname": "板橋金城武"},
{"id": "6498112b26b40d440b9a90ce", "nickname": "三重彭于晏"},
Expand All @@ -39,14 +52,81 @@ def test_start_game(self):

data = response.json()
self.assertTrue(data["url"].startswith(config.FRONTEND_HOST))
game_id = data["url"].split("/")[-1]
self.game_id = data["url"].split("/")[-1]
self.__class__.game_id = self.game_id
status_of_player: GameStatus = GameStatus.parse_obj(
get_status(game_id, players[0]["nickname"])
get_status(self.game_id, players[0]["nickname"])
)

status_players = status_of_player.players
self.assertEqual(status_of_player.game_id, game_id)
self.assertEqual(status_of_player.game_id, self.game_id)
self.assertEqual(
[p.name for p in status_players], [p["nickname"] for p in players]
)
self.assertEqual([p.id for p in status_players], [p["id"] for p in players])

@patch("love_letter.web.app.JWTBearer")
def test_2_game_status(self, mock_jwt_bearer):
# 把呼叫大平台 /users/me 的api給mock掉,因為我們自己創建的jwt token無法取得對應的player_id
mock_jwt_bearer.get_player_id.return_value = "6497f6f226b40d440b9a90cc"
response = self.t.get(
f"/games/{self.game_id}/status", headers={"Authorization": self.jwt_token}
)
data = response.json()

# 檢查是否mock了mock_jwt_bearer.get_player_id方法
mock_jwt_bearer.get_player_id.assert_called_once_with(self.jwt_token)
self.assertEqual(response.status_code, 200)
self.assertEqual(data["game_id"], self.game_id)
self.assertEqual(
{
"game_id": self.game_id,
"events": [{"type": "round_started", "winner": None}],
"players": [
{"id": "6497f6f226b40d440b9a90cc", "name": "板橋金城武", "score": 0},
{"id": "6498112b26b40d440b9a90ce", "name": "三重彭于晏", "score": 0},
{"id": "6499df157fed0c21a4fd0425", "name": "蘆洲劉德華", "score": 0},
{"id": "649836ed7fed0c21a4fd0423", "name": "永和周杰倫", "score": 0},
],
"rounds": [
{
"players": [
{
"seen_cards": [],
"cards": [],
"name": "板橋金城武",
"out": False,
},
{
"cards": [],
"seen_cards": [],
"name": "三重彭于晏",
"out": False,
},
{
"cards": [],
"seen_cards": [],
"name": "蘆洲劉德華",
"out": False,
},
{
"cards": [],
"seen_cards": [],
"name": "永和周杰倫",
"out": False,
},
],
"turn_player": {
"seen_cards": [],
"cards": [],
"name": "板橋金城武",
"out": False,
},
"winner": None,
"start_player": "板橋金城武",
}
],
"final_winner": None,
},
data,
)

0 comments on commit da8d0de

Please sign in to comment.