# deck-it - tests for obs # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import json import tempfile import threading import time import unittest from pathlib import Path import deckit.modules.obs as obs class Fake: connected = True def request(self, kind, data=None): Fake.last = (kind, data) return {"ok": True} class Actions(unittest.TestCase): def setUp(self): self.m = obs.make({"obs": {"host": "h", "port": 4455, "password": ""}, "client": Fake()}) def test_scene_and_record(self): self.m.actions["scene"]({"scene": "Live"}) self.assertEqual(Fake.last[0], "SetCurrentProgramScene") self.m.actions["record"]({"on": True}) self.assertEqual(Fake.last[0], "StartRecord") self.m.actions["record"]({"on": False}) self.assertEqual(Fake.last[0], "StopRecord") def test_mute_and_source(self): self.m.actions["mute"]({"input": "Mic", "muted": True}) self.assertEqual(Fake.last[0], "SetInputMute") self.m.actions["source"]({"scene": "S", "id": 2, "on": False}) self.assertEqual(Fake.last[0], "SetSceneItemEnabled") def test_properties_opens_the_source_dialog(self): self.m.actions["properties"]({"source": "Single Window"}) self.assertEqual(Fake.last, ("OpenInputPropertiesDialog", {"inputName": "Single Window"})) def test_state_offline(self): class Off: connected = False st = obs.make({"client": Off()}).state() self.assertFalse(st["connected"]) self.assertIn("error", st) def test_record_requires_a_bool(self): for bad in ({}, {"on": "false"}, {"on": 1}): with self.assertRaises(obs.ModuleError, msg=bad): self.m.actions["record"](bad) class Password(unittest.TestCase): def test_reads_the_password_obs_stores(self): p = Path(tempfile.mkdtemp()) / "config.json" p.write_text(json.dumps({"server_password": "sekret"})) self.assertEqual(obs._obs_password(p), "sekret") def test_missing_config_is_empty(self): self.assertEqual(obs._obs_password(Path("/nonexistent/x.json")), "") class Serialized(unittest.TestCase): def test_actions_do_not_overlap(self): active = 0 overlap = [] class Guard: connected = True def request(self, kind, data=None): nonlocal active active += 1 if active > 1: overlap.append(kind) time.sleep(0.02) active -= 1 return {} m = obs.make({"client": Guard()}) threads = [threading.Thread(target=m.actions["record"], args=({"on": True},)) for _ in range(5)] for t in threads: t.start() for t in threads: t.join() self.assertEqual(overlap, []) if __name__ == "__main__": unittest.main()