"""New profiles must reach artwork, live catalog inventory and both README galleries.""" import importlib.util import json import shutil from pathlib import Path import tempfile import unittest ROOT=Path(__file__).resolve().parents[2] spec=importlib.util.spec_from_file_location('catalog_sync',ROOT/'scripts/sync_game_catalog.py') assert spec is not None and spec.loader is not None sync=importlib.util.module_from_spec(spec);spec.loader.exec_module(sync) ID='0100000000000001' class GameCatalogSync(unittest.TestCase): def setUp(self): self.temp=tempfile.TemporaryDirectory();self.addCleanup(self.temp.cleanup) self.root=Path(self.temp.name) (self.root/'dm/games').mkdir(parents=True) (self.root/'docs/images').mkdir(parents=True) (self.root/'desktop/NxNativeEasy').mkdir(parents=True) for name in ['README.md','README-es.md']:(self.root/name).write_text('Personal intro\n\n
\nPersonal footer\n') self.add_game(ID,'Example',image=True) def add_game(self,title,name,image=True): folder=self.root/'dm/games'/title;folder.mkdir() (folder/'README.md').write_text('# '+name) (folder/'game.tsv').write_text(f'name\t{name}\nbuild\tv0\t1.0\n') table=self.root/'dm/games/README.md' with table.open('a') as out:out.write(f'| [{name}]({title}/README.md) | `{title}` |\n') if image:(self.root/f'docs/images/{title}.png').write_bytes(b'synthetic image') def source(self,title=ID,name='Example'): return f'
{name}
{name}~1 GB
' def test_addition_and_version_change_propagate_everywhere_and_are_idempotent(self): sync.sync(self.root,write=True,upstream_html=self.source()) other='0100000000000002';self.add_game(other,'Another Game') with self.assertRaises(ValueError):sync.sync(self.root) sync.sync(self.root,write=True,upstream_html=self.source(other,'Another Game')) with (self.root/f'dm/games/{ID}/game.tsv').open('a') as out:out.write('build\tv1\t2.0\n') with self.assertRaises(ValueError):sync.sync(self.root) sync.sync(self.root,write=True) games=json.loads((self.root/sync.CATALOG).read_text())['games'] self.assertEqual(len(games),2) self.assertEqual(next(g for g in games if g['id']==ID)['versions'],['1.0','2.0']) self.assertEqual(set(json.loads((self.root/sync.ART).read_text())),{ID,other}) for name in ['README.md','README-es.md']: text=(self.root/name).read_text() self.assertIn('Personal intro',text);self.assertIn('Personal footer',text) self.assertIn(other,text);self.assertIn(ID,text) self.assertEqual(sync.sync(self.root),[]) def test_missing_image_or_game_note_stops_before_writing(self): original=(self.root/'README.md').read_text() with self.assertRaises(ValueError):sync.sync(self.root,write=True) self.assertEqual((self.root/'README.md').read_text(),original) (self.root/f'dm/games/{ID}/README.md').unlink() with self.assertRaises(ValueError):sync.outputs(self.root,self.source()) def test_external_image_is_rejected(self): outside=self.root/'outside.png';outside.write_bytes(b'synthetic') image=self.root/f'docs/images/{ID}.png';image.unlink();image.symlink_to(outside) with self.assertRaises(ValueError):sync.outputs(self.root,self.source()) def test_repository_catalog_is_current(self): self.assertEqual(sync.sync(ROOT),[]) def test_removed_profile_is_removed_from_every_generated_surface(self): other='0100000000000002';self.add_game(other,'Another Game') sync.sync(self.root,write=True,upstream_html=self.source()+self.source(other,'Another Game')) shutil.rmtree(self.root/'dm/games'/ID) table=self.root/'dm/games/README.md';table.write_text('\n'.join(line for line in table.read_text().splitlines() if ID not in line)+'\n') sync.sync(self.root,write=True) self.assertNotIn(ID,json.loads((self.root/sync.ART).read_text())) self.assertEqual([g['id'] for g in json.loads((self.root/sync.CATALOG).read_text())['games']],[other]) for name in ['README.md','README-es.md']:self.assertNotIn(ID,(self.root/name).read_text())