import json import tempfile import unittest from pathlib import Path from test_room import ROOT, server class HubTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.catalog = json.loads(Path(__file__).with_name('catalog_fixture.json').read_text(encoding='utf-8')) self.path = str(Path(self.temp.name) / 'state.sqlite3') self.fleet = server.Fleet(self.path, self.catalog, lambda: 100) self.room = self.fleet.rooms['cabinet_01'] self.room.last_seen = 100 self.room.gpio = 'real' self.room.state.update(session='test-session', phase='ready') self.data = dict(cabinet='cabinet_01', expected_session='test-session', action_id='coin_release', request_id='test-request-0001', reason='Test release') def tearDown(self): for r in self.fleet.rooms.values(): r.db.close() self.temp.cleanup() def test_durable_duplicate_does_not_repeat_output(self): self.fleet.operator('trigger_output', self.data) self.fleet.operator('trigger_output', self.data) self.assertEqual(len(self.room.state['commands']), 1) for r in self.fleet.rooms.values(): r.db.close() self.fleet = server.Fleet(self.path, self.catalog, lambda: 100) self.fleet.operator('trigger_output', self.data) self.assertEqual(len(self.fleet.rooms['cabinet_01'].state['commands']), 1) self.assertTrue(any('Test release' in h['message'] for h in self.fleet.rooms['cabinet_01'].state['history'])) def test_rejects_unsafe_or_stale_requests(self): for key,value in [('expected_session','old'),('action_id','unknown'),('reason',''),('request_id','short')]: with self.subTest(key=key), self.assertRaises(ValueError): self.fleet.operator('trigger_output', dict(self.data, **{key:value})) self.room.gpio = 'simulation' with self.assertRaises(ValueError): self.fleet.operator('trigger_output', self.data) self.room.gpio = 'real' self.room.last_seen = 0 with self.assertRaises(ValueError): self.fleet.operator('trigger_output', self.data) self.assertFalse(self.room.state['commands']) def test_pending_and_unprepared_rejected(self): self.room.state['phase']='idle' with self.assertRaises(ValueError): self.fleet.operator('trigger_output',self.data) self.room.state['phase']='ready' self.fleet.operator('trigger_output',self.data) with self.assertRaises(ValueError): self.fleet.operator('trigger_output',dict(self.data,request_id='test-request-0002')) def test_hub_config(self): p=Path(self.temp.name)/'hub.json' service=dict(id='trivia',name='Trivia',kind='trivia',url='http://trivia.local:8080') p.write_text(json.dumps(dict(services=[service]))) self.assertEqual(server.hub_config(p)['room_name'],'Glitch: the Dark Machine') for url in ['javascript:alert(1)','http://user:password@host','http://host/?token=secret','http://host/#secret','http://host:invalid']: p.write_text(json.dumps(dict(services=[dict(service,url=url)]))) with self.subTest(url=url),self.assertRaises(ValueError): server.hub_config(p) self.assertEqual(self.fleet.snapshot()['hub']['name'],'Escape Gallery')