Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api cleanup #41

Merged
merged 7 commits into from
Jul 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,26 @@ Available endpoints:
- `GET /api/v1/players/<player_id>` Get a player by ID
- `GET /api/v1/players?token=<token>` Get a player by their secret token.

Here's an example request to create a game:
Here's an example HTTP request to create a game:

```
POST /api/v1/games
?server_tok=secret_kgs
&b_tok=player_1_token
&w_tok=player_2_token
POST /api/v1/games HTTP/1.1
Content-Type: application/json
X-Auth-Server-Token: secret_kgs
X-Auth-Black-Player-Token: player_1_token
X-Auth-White-Player-Token: player_2_token

{
"black_id": 1,
"white_id": 2,
"game_server": "KGS",
'rated': True,
'result': 'W+R',
'date_played': '2015-02-26T10:30:00',
'game_record': '(;FF[4]GM[1]SZ[19]CA[UTF-8]BC[ja]WC[ja]EV[54th Japanese Judan]PB[Kono Takashi]BR[8p]PW[O Meien]WR[9p]KM[6.5]DT[2015-02-26]RE[W+R];B[qd];W[dp];B[pq];W[od])'
"server_id": 1,
"result": "W+R",
"date_played": "2015-02-26T10:30:00",
"game_record": "(;FF[4]GM[1]SZ[19]CA[UTF-8]BC[ja]WC[ja]EV[54th Japanese Judan]PB[Kono Takashi]BR[8p]PW[O Meien]WR[9p]KM[6.5]DT[2015-02-26]RE[W+R];B[qd];W[dp];B[pq];W[od])"
}
```

You can also submit a `game_url` in lieu of the `game_record` field.
`server_tok` is the game server's secret token, and `b_tok`, `w_tok` are the
player's secret tokens.
You can also submit a `game_url` in lieu of the `game_record` field. `server_token` is the game server's secret token, and `black_token`, `white_token` are the player's secret tokens. Your `server_id` can be discovered through the UI for online-ratings.

## Getting Started (Online Ratings backend developers)

Expand Down
37 changes: 18 additions & 19 deletions web/app/api_1_0/game_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,12 @@ def _result_str_valid(result):
return False


def validate_game_submission(queryparams, body_json):
def validate_game_submission(headers, body_json):
#required
data = {
'server_tok': queryparams.get('server_tok'),
'b_tok': queryparams.get('b_tok'),
'w_tok': queryparams.get('w_tok'),
'game_server': body_json.get('game_server'),
'server_id': body_json.get('server_id'),
'black_id': body_json.get('black_id'),
'white_id': body_json.get('white_id'),
'rated': body_json.get('rated'),
'result': body_json.get('result'),
'date_played': body_json.get('date_played'),
}
Expand All @@ -53,24 +49,28 @@ def validate_game_submission(queryparams, body_json):
'game_url': body_json.get('game_url')
})

gs = GoServer.query.filter_by(name=data['game_server'], token=data['server_tok']).first()
server_token = headers.get('X-Auth-Server-Token')
black_token = headers.get('X-Auth-Black-Player-Token')
white_token = headers.get('X-Auth-White-Player-Token')

if any(token is None for token in (server_token, black_token, white_token)):
raise ApiException('Did not submit required X-Auth-(Server-Token|Black-Player-Token|White_Player-Token) headers.', 403)

gs = GoServer.query.filter_by(id=data['server_id'], token=server_token).first()
if gs is None:
raise ApiException('server access token unknown or expired: %s' % data['server_tok'],
raise ApiException('server access token unknown or expired: %s' % server_token,
status_code=404)

b = Player.query.filter_by(id=data['black_id'], token=data['b_tok']).first()
b = Player.query.filter_by(id=data['black_id'], token=black_token).first()
if b is None or b.user_id is None:
raise ApiException('user access token unknown or expired: %s' % data['b_tok'],
raise ApiException('black player access token unknown or expired: %s' % black_token,
status_code=404)

w = Player.query.filter_by(id=data['white_id'], token=data['w_tok']).first()
w = Player.query.filter_by(id=data['white_id'], token=white_token).first()
if w is None or w.user_id is None:
raise ApiException('user access token unknown or expired: %s' % data['w_tok'],
raise ApiException('white player token unknown or expired: %s' % white_token,
status_code=404)

if type(data['rated']) != bool:
raise ApiException('rated must be set to True or False')

if not _result_str_valid(data['result']):
raise ApiException('format of result is incorrect')

Expand All @@ -93,12 +93,11 @@ def validate_game_submission(queryparams, body_json):
except TypeError:
raise ApiException(error='date_played must be in ISO 8601 format')

rated = data['rated']
logging.info(" White: %s, Black: %s " % (w,b))
game = Game(server_id=gs.id,
white_id=w.id,
black_id=b.id,
rated=rated,
rated=False,
date_played=date_played,
date_reported=datetime.now(),
result=data['result'],
Expand All @@ -110,11 +109,11 @@ def validate_game_submission(queryparams, body_json):
@requires_json
def create_game():
"""Post a new game result to the database."""
game = validate_game_submission(request.args, request.json)
game = validate_game_submission(request.headers, request.json)
db.session.add(game)
db.session.commit()
print("New game: %s " % str(game))
return jsonify(game.to_dict())
return jsonify(game.to_dict()), 201

@api.route('/games/<int:game_id>', methods=['GET'])
def get_game(game_id):
Expand Down
2 changes: 1 addition & 1 deletion web/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def to_dict(self):
"id": self.id,
"white_id": self.white_id,
"black_id": self.black_id,
"game_server": self.game_server.name,
"server_id": self.server_id,
"date_played": self.date_played.isoformat(),
"date_reported": self.date_reported.isoformat(),
"result": self.result,
Expand Down
1 change: 1 addition & 0 deletions web/app/templates/server.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<h1> Server info for {{server.name}} </h1>

<table border class="table">
<tr><td>server_id</td><td>{{ server.id }}</td></tr>
<tr><td>Name</td><td>{{ server.name }}</td></tr>
<tr><td>URL</td><td>{{ server.url }}</td></tr>
<tr><td>Players</td><td>{{ server.players|length }}</td></tr>
Expand Down
3 changes: 2 additions & 1 deletion web/app/templates/servers.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

{% if servers %}
<table border class="table">
<tr><th> Name </th> <th> URL </th> <th> Players </th> </tr>
<tr><th>server_id</th><th> Name </th> <th> URL </th> <th> Players </th> </tr>
{% for gs in servers %}
<tr>
<td>{{gs.id}}</td>
<td><a href="{{ url_for('ratings.server', server_id=gs.id) }}"> {{ gs.name}} </a></td>
<td>{{ gs.url }}</td>
<td>{{ gs.players|length }}</td></tr>
Expand Down
63 changes: 24 additions & 39 deletions web/tests/test_game_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
class TestGameResource(BaseTestCase):

games_endpoint = '/api/v1/games'
good_queryparams = {
'server_tok': 'secret_kgs',
'b_tok': 'secret_foo_KGS',
'w_tok': 'secret_bar_KGS',
good_headers = {
'X-Auth-Server-Token': 'secret_kgs',
'X-Auth-Black-Player-Token': 'secret_foo_KGS',
'X-Auth-White-Player-Token': 'secret_bar_KGS',
'Content-Type': 'application/json',
}
good_bodyparams = {
"black_id": 1,
"white_id": 2,
"game_server": "KGS",
'rated': True,
"server_id": 1,
'result': 'B+0.5',
'date_played': '2014-08-19T10:30:00',
'game_record': '\n'.join(open('tests/testsgf.sgf').readlines())
Expand All @@ -32,14 +32,13 @@ class TestGameResource(BaseTestCase):
def test_games_endpoint_success(self):
create_response = self.client.post(
self.games_endpoint,
query_string=self.good_queryparams,
data=json.dumps(self.good_bodyparams),
headers={"Content-Type": "application/json"}
headers=self.good_headers
)
created_game = create_response.json
for key, value in self.expected_return.items():
self.assertEqual(value, created_game[key])
self.assertEqual(create_response.status_code, 200)
self.assertEqual(create_response.status_code, 201)

get_response = self.client.get(os.path.join(
self.games_endpoint,
Expand All @@ -66,59 +65,45 @@ def test_games_endpoint_game_url(self):
bodyparams = self.good_bodyparams.copy()
bodyparams.pop("game_record")
bodyparams['game_url'] = game_url
r = self.client.post(self.games_endpoint, query_string=self.good_queryparams, data=json.dumps(bodyparams), headers={"Content-Type": "application/json"})
r = self.client.post(self.games_endpoint, data=json.dumps(bodyparams), headers=self.good_headers)
actual = r.json
for key, value in self.expected_return.items():
self.assertEqual(value, actual[key])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.status_code, 201)

def test_validate_missing_auth(self):
for k in self.good_queryparams.keys():
q = self.good_queryparams.copy()
for k in self.good_headers.keys():
if "X-Auth" not in k:
continue
q = self.good_headers.copy()
q.pop(k, None) # on each iteration, remove 1 param
with self.assertRaises(ApiException) as exception_context:
validate_game_submission(q, self.good_bodyparams)
self.assertIn(k, exception_context.exception.message)
self.assertEqual(exception_context.exception.status_code, 403)

def test_validate_missing_params(self):
for k in self.good_bodyparams.keys():
q = self.good_bodyparams.copy()
q.pop(k, None) # on each iteration, remove 1 param
with self.assertRaises(ApiException) as exception_context:
validate_game_submission(self.good_queryparams, q)
validate_game_submission(self.good_headers, q)
if k == 'game_record':
expected = 'One of game_record or game_url must be present'
self.assertEqual(expected, exception_context.exception.message)
else:
self.assertIn(k, exception_context.exception.message)

def test_validate_bad_user_token(self):
for param in ['w_tok', 'b_tok', 'server_tok']:
# User token is bad
q = self.good_queryparams.copy()
q[param] = 'bad_tok'
def test_validate_incorrect_auth(self):
for k in self.good_headers.keys():
if "X-Auth" not in k:
continue
q = self.good_headers.copy()
q[k] = 'bad_tok'
with self.assertRaises(ApiException) as exception_context:
validate_game_submission(q, self.good_bodyparams)

if param == 'server_tok':
expected = 'server access token unknown or expired: bad_tok'
else:
expected = 'user access token unknown or expired: bad_tok'
self.assertEqual(expected, exception_context.exception.message)

def test_validate_rated(self):
bodyparams = self.good_bodyparams.copy()
for is_rated in [True, False]:
bodyparams['rated'] = is_rated
game = validate_game_submission(self.good_queryparams, bodyparams)
self.assertEqual(game.rated, is_rated)

bodyparams['rated'] = '0'
with self.assertRaises(ApiException) as exception_context:
validate_game_submission(self.good_queryparams, bodyparams)

expected = 'rated must be set to True or False'
self.assertEqual(expected, exception_context.exception.message)
expected = 'token unknown or expired'
self.assertIn(expected, exception_context.exception.message)

def test_validate_result(self):
good_results = [
Expand Down