WebHost: fix missing timezone in tracker if-modified-since handling (#4125)

* WebHost: fix missing timezone in tracker if-modified-since handling

and add a test for it

* WebHost, Test: fix running test_tracker in parallel
This commit is contained in:
black-sliver 2024-11-07 09:51:40 +01:00 committed by GitHub
parent a0207e0286
commit 345d5154a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 109 additions and 6 deletions

View File

@ -5,7 +5,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, NamedTuple,
from uuid import UUID
from email.utils import parsedate_to_datetime
from flask import render_template, make_response, Response, request
from flask import make_response, render_template, request, Request, Response
from werkzeug.exceptions import abort
from MultiServer import Context, get_saving_second
@ -298,17 +298,25 @@ class TrackerData:
return self._multidata.get("spheres", [])
def _process_if_request_valid(incoming_request, room: Optional[Room]) -> Optional[Response]:
def _process_if_request_valid(incoming_request: Request, room: Optional[Room]) -> Optional[Response]:
if not room:
abort(404)
if_modified = incoming_request.headers.get("If-Modified-Since", None)
if if_modified:
if_modified = parsedate_to_datetime(if_modified)
if_modified_str: Optional[str] = incoming_request.headers.get("If-Modified-Since", None)
if if_modified_str:
if_modified = parsedate_to_datetime(if_modified_str)
if if_modified.tzinfo is None:
abort(400) # standard requires "GMT" timezone
# database may use datetime.utcnow(), which is timezone-naive. convert to timezone-aware.
last_activity = room.last_activity
if last_activity.tzinfo is None:
last_activity = room.last_activity.replace(tzinfo=datetime.timezone.utc)
# if_modified has less precision than last_activity, so we bring them to same precision
if if_modified >= room.last_activity.replace(microsecond=0):
if if_modified >= last_activity.replace(microsecond=0):
return make_response("", 304)
return None
@app.route("/tracker/<suuid:tracker>/<int:tracked_team>/<int:tracked_player>")
def get_player_tracker(tracker: UUID, tracked_team: int, tracked_player: int, generic: bool = False) -> Response:

Binary file not shown.

View File

@ -0,0 +1,95 @@
import os
import pickle
from pathlib import Path
from typing import ClassVar
from uuid import UUID, uuid4
from flask import url_for
from . import TestBase
class TestTracker(TestBase):
room_id: UUID
tracker_uuid: UUID
log_filename: str
data: ClassVar[bytes]
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
with (Path(__file__).parent / "data" / "One_Archipelago.archipelago").open("rb") as f:
cls.data = f.read()
def setUp(self) -> None:
from pony.orm import db_session
from MultiServer import Context as MultiServerContext
from Utils import user_path
from WebHostLib.models import GameDataPackage, Room, Seed
super().setUp()
multidata = MultiServerContext.decompress(self.data)
with self.client.session_transaction() as session:
session["_id"] = uuid4()
self.tracker_uuid = uuid4()
with db_session:
# store game datapackage(s)
for game, game_data in multidata["datapackage"].items():
if not GameDataPackage.get(checksum=game_data["checksum"]):
GameDataPackage(checksum=game_data["checksum"],
data=pickle.dumps(game_data))
# create an empty seed and a room from it
seed = Seed(multidata=self.data, owner=session["_id"])
room = Room(seed=seed, owner=session["_id"], tracker=self.tracker_uuid)
self.room_id = room.id
self.log_filename = user_path("logs", f"{self.room_id}.txt")
def tearDown(self) -> None:
from pony.orm import db_session, select
from WebHostLib.models import Command, Room
with db_session:
for command in select(command for command in Command if command.room.id == self.room_id): # type: ignore
command.delete()
room: Room = Room.get(id=self.room_id)
room.seed.delete()
room.delete()
try:
os.unlink(self.log_filename)
except FileNotFoundError:
pass
def test_valid_if_modified_since(self) -> None:
"""
Verify that we get a 200 response for valid If-Modified-Since
"""
with self.app.app_context(), self.app.test_request_context():
response = self.client.get(
url_for(
"get_player_tracker",
tracker=self.tracker_uuid,
tracked_team=0,
tracked_player=1,
),
headers={"If-Modified-Since": "Wed, 21 Oct 2015 07:28:00 GMT"},
)
self.assertEqual(response.status_code, 200)
def test_invalid_if_modified_since(self) -> None:
"""
Verify that we get a 400 response for invalid If-Modified-Since
"""
with self.app.app_context(), self.app.test_request_context():
response = self.client.get(
url_for(
"get_player_tracker",
tracker=self.tracker_uuid,
tracked_team=1,
tracked_player=0,
),
headers={"If-Modified-Since": "Wed, 21 Oct 2015 07:28:00"}, # missing timezone
)
self.assertEqual(response.status_code, 400)