Skip to content

Commit

Permalink
Merge branch 'main' into cc_forget_pass
Browse files Browse the repository at this point in the history
  • Loading branch information
NewSoupVi authored Jul 25, 2024
2 parents 8ee57d3 + f34da74 commit 352d595
Show file tree
Hide file tree
Showing 300 changed files with 12,494 additions and 11,882 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ venv/
ENV/
env.bak/
venv.bak/
.code-workspace
*.code-workspace
shell.nix

# Spyder project settings
Expand Down
4 changes: 2 additions & 2 deletions MultiServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,7 @@ def _cmd_remaining(self) -> bool:
if self.ctx.remaining_mode == "enabled":
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
Expand All @@ -1365,7 +1365,7 @@ def _cmd_remaining(self) -> bool:
if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
if remaining_item_ids:
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.client.slot.game][item_id]
self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id]
for item_id in remaining_item_ids))
else:
self.output("No remaining items found.")
Expand Down
14 changes: 7 additions & 7 deletions UndertaleClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _cmd_resync(self):
def _cmd_patch(self):
"""Patch the game. Only use this command if /auto_patch fails."""
if isinstance(self.ctx, UndertaleContext):
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
os.makedirs(name=Utils.user_path("Undertale"), exist_ok=True)
self.ctx.patch_game()
self.output("Patched.")

Expand All @@ -43,7 +43,7 @@ def _cmd_savepath(self, directory: str):
def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
"""Patch the game automatically."""
if isinstance(self.ctx, UndertaleContext):
os.makedirs(name=os.path.join(os.getcwd(), "Undertale"), exist_ok=True)
os.makedirs(name=Utils.user_path("Undertale"), exist_ok=True)
tempInstall = steaminstall
if not os.path.isfile(os.path.join(tempInstall, "data.win")):
tempInstall = None
Expand All @@ -62,7 +62,7 @@ def _cmd_auto_patch(self, steaminstall: typing.Optional[str] = None):
for file_name in os.listdir(tempInstall):
if file_name != "steam_api.dll":
shutil.copy(os.path.join(tempInstall, file_name),
os.path.join(os.getcwd(), "Undertale", file_name))
Utils.user_path("Undertale", file_name))
self.ctx.patch_game()
self.output("Patching successful!")

Expand Down Expand Up @@ -111,12 +111,12 @@ def __init__(self, server_address, password):
self.save_game_folder = os.path.expandvars(r"%localappdata%/UNDERTALE")

def patch_game(self):
with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "rb") as f:
with open(Utils.user_path("Undertale", "data.win"), "rb") as f:
patchedFile = bsdiff4.patch(f.read(), undertale.data_path("patch.bsdiff"))
with open(os.path.join(os.getcwd(), "Undertale", "data.win"), "wb") as f:
with open(Utils.user_path("Undertale", "data.win"), "wb") as f:
f.write(patchedFile)
os.makedirs(name=os.path.join(os.getcwd(), "Undertale", "Custom Sprites"), exist_ok=True)
with open(os.path.expandvars(os.path.join(os.getcwd(), "Undertale", "Custom Sprites",
os.makedirs(name=Utils.user_path("Undertale", "Custom Sprites"), exist_ok=True)
with open(os.path.expandvars(Utils.user_path("Undertale", "Custom Sprites",
"Which Character.txt")), "w") as f:
f.writelines(["// Put the folder name of the sprites you want to play as, make sure it is the only "
"line other than this one.\n", "frisk"])
Expand Down
2 changes: 2 additions & 0 deletions WebHostLib/customserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,12 @@ def _done(self, task: asyncio.Future):
def run(self):
while 1:
next_room = rooms_to_run.get(block=True, timeout=None)
gc.collect(0)
task = asyncio.run_coroutine_threadsafe(start_room(next_room), loop)
self._tasks.append(task)
task.add_done_callback(self._done)
logging.info(f"Starting room {next_room} on {name}.")
del task # delete reference to task object

starter = Starter()
starter.daemon = True
Expand Down
4 changes: 4 additions & 0 deletions kvui.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,10 @@ def _handle_color(self, node: JSONMessagePart):
return self._handle_text(node)

def _handle_text(self, node: JSONMessagePart):
# All other text goes through _handle_color, and we don't want to escape markup twice,
# or mess up text that already has intentional markup applied to it
if node.get("type", "text") == "text":
node["text"] = escape_markup(node["text"])
for ref in node.get("refs", []):
node["text"] = f"[ref={self.ref_count}|{ref}]{node['text']}[/ref]"
self.ref_count += 1
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
try:
requirement = 'cx-Freeze==7.0.0'
requirement = 'cx-Freeze==7.2.0'
import pkg_resources
try:
pkg_resources.require(requirement)
Expand Down
36 changes: 36 additions & 0 deletions test/webhost/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import unittest
import typing
from uuid import uuid4

from flask import Flask
from flask.testing import FlaskClient


class TestBase(unittest.TestCase):
app: typing.ClassVar[Flask]
client: FlaskClient

@classmethod
def setUpClass(cls) -> None:
from WebHostLib import app as raw_app
from WebHost import get_app

raw_app.config["PONY"] = {
"provider": "sqlite",
"filename": ":memory:",
"create_db": True,
}
raw_app.config.update({
"TESTING": True,
"DEBUG": True,
})
try:
cls.app = get_app()
except AssertionError as e:
# since we only have 1 global app object, this might fail, but luckily all tests use the same config
if "register_blueprint" not in e.args[0]:
raise
cls.app = raw_app

def setUp(self) -> None:
self.client = self.app.test_client()
25 changes: 5 additions & 20 deletions test/webhost/test_api_generate.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
import io
import unittest
import json
import yaml

from . import TestBase

class TestDocs(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
from WebHostLib import app as raw_app
from WebHost import get_app
raw_app.config["PONY"] = {
"provider": "sqlite",
"filename": ":memory:",
"create_db": True,
}
raw_app.config.update({
"TESTING": True,
})
app = get_app()

cls.client = app.test_client()

def test_correct_error_empty_request(self):
class TestAPIGenerate(TestBase):
def test_correct_error_empty_request(self) -> None:
response = self.client.post("/api/generate")
self.assertIn("No options found. Expected file attachment or json weights.", response.text)

def test_generation_queued_weights(self):
def test_generation_queued_weights(self) -> None:
options = {
"Tester1":
{
Expand All @@ -43,7 +28,7 @@ def test_generation_queued_weights(self):
self.assertTrue(json_data["text"].startswith("Generation of seed "))
self.assertTrue(json_data["text"].endswith(" started successfully."))

def test_generation_queued_file(self):
def test_generation_queued_file(self) -> None:
options = {
"game": "Archipelago",
"name": "Tester",
Expand Down
192 changes: 192 additions & 0 deletions test/webhost/test_host_room.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import os
from uuid import UUID, uuid4, uuid5

from flask import url_for

from . import TestBase


class TestHostFakeRoom(TestBase):
room_id: UUID
log_filename: str

def setUp(self) -> None:
from pony.orm import db_session
from Utils import user_path
from WebHostLib.models import Room, Seed

super().setUp()

with self.client.session_transaction() as session:
session["_id"] = uuid4()
with db_session:
# create an empty seed and a room from it
seed = Seed(multidata=b"", owner=session["_id"])
room = Room(seed=seed, owner=session["_id"], tracker=uuid4())
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_display_log_missing_full(self) -> None:
"""
Verify that we get a 200 response even if log is missing.
This is required to not get an error for fetch.
"""
with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("display_log", room=self.room_id))
self.assertEqual(response.status_code, 200)

def test_display_log_missing_range(self) -> None:
"""
Verify that we get a full response for missing log even if we asked for range.
This is required for the JS logic to differentiate between log update and log error message.
"""
with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("display_log", room=self.room_id), headers={
"Range": "bytes=100-"
})
self.assertEqual(response.status_code, 200)

def test_display_log_denied(self) -> None:
"""Verify that only the owner can see the log."""
other_client = self.app.test_client()
with self.app.app_context(), self.app.test_request_context():
response = other_client.get(url_for("display_log", room=self.room_id))
self.assertEqual(response.status_code, 403)

def test_display_log_missing_room(self) -> None:
"""Verify log for missing room gives an error as opposed to missing log for existing room."""
missing_room_id = uuid5(uuid4(), "") # rooms are always uuid4, so this can't exist
other_client = self.app.test_client()
with self.app.app_context(), self.app.test_request_context():
response = other_client.get(url_for("display_log", room=missing_room_id))
self.assertEqual(response.status_code, 404)

def test_display_log_full(self) -> None:
"""Verify full log response."""
with open(self.log_filename, "w", encoding="utf-8") as f:
text = "x" * 200
f.write(text)

with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("display_log", room=self.room_id))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.get_data(True), text)

def test_display_log_range(self) -> None:
"""Verify that Range header in request gives a range in response."""
with open(self.log_filename, "w", encoding="utf-8") as f:
f.write(" " * 100)
text = "x" * 100
f.write(text)

with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("display_log", room=self.room_id), headers={
"Range": "bytes=100-"
})
self.assertEqual(response.status_code, 206)
self.assertEqual(response.get_data(True), text)

def test_display_log_range_bom(self) -> None:
"""Verify that a BOM in the log file is skipped for range."""
with open(self.log_filename, "w", encoding="utf-8-sig") as f:
f.write(" " * 100)
text = "x" * 100
f.write(text)
self.assertEqual(f.tell(), 203) # including BOM

with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("display_log", room=self.room_id), headers={
"Range": "bytes=100-"
})
self.assertEqual(response.status_code, 206)
self.assertEqual(response.get_data(True), text)

def test_host_room_missing(self) -> None:
"""Verify that missing room gives a 404 response."""
missing_room_id = uuid5(uuid4(), "") # rooms are always uuid4, so this can't exist
with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("host_room", room=missing_room_id))
self.assertEqual(response.status_code, 404)

def test_host_room_own(self) -> None:
"""Verify that own room gives the full output."""
with open(self.log_filename, "w", encoding="utf-8-sig") as f:
text = "* should be visible *"
f.write(text)

with self.app.app_context(), self.app.test_request_context():
response = self.client.get(url_for("host_room", room=self.room_id))
response_text = response.get_data(True)
self.assertEqual(response.status_code, 200)
self.assertIn("href=\"/seed/", response_text)
self.assertIn(text, response_text)

def test_host_room_other(self) -> None:
"""Verify that non-own room gives the reduced output."""
from pony.orm import db_session
from WebHostLib.models import Room

with db_session:
room: Room = Room.get(id=self.room_id)
room.last_port = 12345

with open(self.log_filename, "w", encoding="utf-8-sig") as f:
text = "* should not be visible *"
f.write(text)

other_client = self.app.test_client()
with self.app.app_context(), self.app.test_request_context():
response = other_client.get(url_for("host_room", room=self.room_id))
response_text = response.get_data(True)
self.assertEqual(response.status_code, 200)
self.assertNotIn("href=\"/seed/", response_text)
self.assertNotIn(text, response_text)
self.assertIn("/connect ", response_text)
self.assertIn(":12345", response_text)

def test_host_room_own_post(self) -> None:
"""Verify command from owner gets queued for the server and response is redirect."""
from pony.orm import db_session, select
from WebHostLib.models import Command

with self.app.app_context(), self.app.test_request_context():
response = self.client.post(url_for("host_room", room=self.room_id), data={
"cmd": "/help"
})
self.assertEqual(response.status_code, 302, response.text)\

with db_session:
commands = select(command for command in Command if command.room.id == self.room_id) # type: ignore
self.assertIn("/help", (command.commandtext for command in commands))

def test_host_room_other_post(self) -> None:
"""Verify command from non-owner does not get queued for the server."""
from pony.orm import db_session, select
from WebHostLib.models import Command

other_client = self.app.test_client()
with self.app.app_context(), self.app.test_request_context():
response = other_client.post(url_for("host_room", room=self.room_id), data={
"cmd": "/help"
})
self.assertLess(response.status_code, 500)

with db_session:
commands = select(command for command in Command if command.room.id == self.room_id) # type: ignore
self.assertNotIn("/help", (command.commandtext for command in commands))
3 changes: 3 additions & 0 deletions worlds/ahit/Regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@
# See above comment
"Time Rift - Deep Sea": ["Alpine Free Roam", "Nyakuza Free Roam", "Contractual Obligations",
"Murder on the Owl Express"],

# was causing test failures
"Time Rift - Balcony": ["Alpine Free Roam"],
}


Expand Down
Loading

0 comments on commit 352d595

Please sign in to comment.