* Update server-side database and dynamic resource code for Python 3 * Preserve existing Flipnote, UGO, NTFT, CSS, localization, and Hatena assets * Port Flipnote upload and movie endpoints * Update front page UGO menu generators * Preserve existing database and sample Flipnote data * Add regression tests for database operations and binary formats * Add server resource loading tests * Maintain compatibility with the original DSi Flipnote Studio protocol
125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def install_twisted_stub():
|
|
"""Install only the Twisted surface needed to smoke-test this project."""
|
|
if importlib.util.find_spec("twisted") is not None:
|
|
return
|
|
|
|
twisted = types.ModuleType("twisted")
|
|
web = types.ModuleType("twisted.web")
|
|
resource_mod = types.ModuleType("twisted.web.resource")
|
|
static_mod = types.ModuleType("twisted.web.static")
|
|
internet_mod = types.ModuleType("twisted.internet")
|
|
|
|
class Resource:
|
|
isLeaf = False
|
|
|
|
def __init__(self):
|
|
self.children = {}
|
|
|
|
def putChild(self, name, child):
|
|
if not isinstance(name, bytes):
|
|
raise TypeError("Twisted child names must be bytes")
|
|
self.children[name] = child
|
|
|
|
def getChild(self, name, request):
|
|
return self
|
|
|
|
def getChildWithDefault(self, name, request):
|
|
return self.children.get(name, self.getChild(name, request))
|
|
|
|
class File(Resource):
|
|
def __init__(self, path):
|
|
super().__init__()
|
|
self.path = path
|
|
|
|
def render(self, request):
|
|
p = Path(self.path)
|
|
return p.read_bytes() if p.is_file() else b""
|
|
|
|
class Reactor:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def callLater(self, delay, func, *args, **kwargs):
|
|
self.calls.append((delay, func, args, kwargs))
|
|
return object()
|
|
|
|
def callInThread(self, func, *args, **kwargs):
|
|
return func(*args, **kwargs)
|
|
|
|
resource_mod.Resource = Resource
|
|
static_mod.File = File
|
|
web.resource = resource_mod
|
|
web.static = static_mod
|
|
internet_mod.reactor = Reactor()
|
|
twisted.web = web
|
|
twisted.internet = internet_mod
|
|
|
|
sys.modules.update(
|
|
{
|
|
"twisted": twisted,
|
|
"twisted.web": web,
|
|
"twisted.web.resource": resource_mod,
|
|
"twisted.web.static": static_mod,
|
|
"twisted.internet": internet_mod,
|
|
}
|
|
)
|
|
|
|
|
|
class ResourceTreeTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
install_twisted_stub()
|
|
cls.old_cwd = Path.cwd()
|
|
import os
|
|
os.chdir(ROOT)
|
|
import hatena
|
|
cls.hatena = hatena
|
|
cls.root = hatena.Setup()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
import os
|
|
os.chdir(cls.old_cwd)
|
|
|
|
def test_core_resource_tree_loads(self):
|
|
region = self.root.dsResource.region
|
|
expected = {
|
|
b"index.ugo",
|
|
b"inbox.ugo",
|
|
b"movie",
|
|
b"post",
|
|
b"frontpage",
|
|
b"help",
|
|
b"eula_list.tsv",
|
|
}
|
|
self.assertTrue(expected.issubset(region.children))
|
|
self.assertIn(b"flipnote.post", region.children[b"post"].children)
|
|
self.assertIn(b"hotmovies.ugo", region.children[b"frontpage"].children)
|
|
self.assertIn(b"likedmovies.ugo", region.children[b"frontpage"].children)
|
|
self.assertIn(b"newmovies.ugo", region.children[b"frontpage"].children)
|
|
|
|
def test_prepacked_ugo_resources_are_binary(self):
|
|
region = self.root.dsResource.region
|
|
self.assertTrue(region.children[b"index.ugo"].ugofile.startswith(b"UGAR"))
|
|
self.assertTrue(region.children[b"inbox.ugo"].ugofile.startswith(b"UGAR"))
|
|
|
|
def test_hot_page_builds_from_database(self):
|
|
hot = self.root.dsResource.region.children[b"frontpage"].children[b"hotmovies.ugo"]
|
|
self.assertGreaterEqual(len(hot.pages), 1)
|
|
self.assertTrue(hot.pages[0].startswith(b"UGAR"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
