477128de81
The exe shipped config/app_config.json AND src/player_auth.json inside the
bundle, and because a frozen app runs with cwd = _internal/, the player loaded
that snapshot as its live auth state. A stale snapshot therefore made a fresh
build boot "already authenticated" against an old server and play an outdated
playlist. It also meant every install inherited the build machine's server_ip,
screen_name and auth_code.
Both files are now excluded from the bundle (app_config.json is no longer added
to datas, player_auth.json is excluded from Tree(src)), and the runtime hook no
longer copies a config into place on first run.
New behaviour, all in src/main.py so it applies to the Pi build too:
- The player starts with blank credentials. A missing file, an empty or
unparseable file, missing keys, and leftover placeholder values
(localhost, 127.0.0.1, kivy-player, 1234567) all count as UNCONFIGURED.
config_is_configured() is the single source of truth for that decision.
- After the splash video a notice appears ("Player is not configured"), and
after 5 seconds the Settings screen opens automatically so the operator can
enter the server details.
- Saving valid values writes config/app_config.json next to the .exe and
starts playback immediately - no restart needed.
- On a machine that IS configured, the notice and Settings are skipped and the
cached playlist plays straight away.
- Settings refuses to close while the three required fields are blank, so it
cannot be dismissed into a permanently blank screen with no way back.
- The 30s playlist timer does not fight the setup flow while unconfigured.
on_intro_finished() is the single decision point after the splash; both intro
paths (video end and "no intro file") go through it so they cannot drift apart.
Also loads config over DEFAULT_CONFIG rather than replacing it, so a partial or
older config file keeps working defaults instead of losing keys.
Note for future changes: when adding a new REQUIRED config key, add it to
CONFIG_REQUIRED_KEYS or the first-run flow will not ask for it.
Verified on the packaged exe by removing the config to simulate a fresh install:
setup_required_shown -> setup_opening_settings exactly 5s later ->
setup_completed, with the config written next to the exe and playback resuming.
Restarting with that config produced no setup events at all.
Covered by windows/test_first_run_setup.py.
112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
"""Tests the first-run setup decision logic in src/main.py.
|
|
|
|
The rule that matters: a player is "configured" only when it has REAL server
|
|
settings. A missing file, an empty file, unparseable JSON, missing keys, and
|
|
leftover placeholder values must ALL count as unconfigured, so the app shows
|
|
the setup notice instead of silently trying to reach "localhost".
|
|
|
|
Run: windows\\venv\\Scripts\\python.exe windows\\test_first_run_setup.py
|
|
Exit code 0 = PASS.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SRC = Path(__file__).resolve().parent.parent / 'src'
|
|
sys.path.insert(0, str(SRC))
|
|
|
|
import main as app # noqa: E402
|
|
|
|
|
|
def main():
|
|
print('=' * 68)
|
|
print(' First-run setup detection test')
|
|
print('=' * 68)
|
|
|
|
must_be_unconfigured = {
|
|
'None': None,
|
|
'empty dict': {},
|
|
'empty config file': {},
|
|
'placeholder defaults': {
|
|
'server_ip': 'localhost',
|
|
'screen_name': 'kivy-player',
|
|
'quickconnect_key': '1234567',
|
|
},
|
|
'all blank': {
|
|
'server_ip': '', 'screen_name': '', 'quickconnect_key': '',
|
|
},
|
|
'missing keys': {'server_ip': '192.168.0.110'},
|
|
'whitespace only': {
|
|
'server_ip': ' ', 'screen_name': '\t', 'quickconnect_key': ' ',
|
|
},
|
|
'loopback': {
|
|
'server_ip': '127.0.0.1', 'screen_name': 'PC', 'quickconnect_key': '9',
|
|
},
|
|
'placeholder screen name': {
|
|
'server_ip': '192.168.0.110',
|
|
'screen_name': 'kivy-player',
|
|
'quickconnect_key': '8887779',
|
|
},
|
|
}
|
|
|
|
must_be_configured = {
|
|
'real settings': {
|
|
'server_ip': '192.168.0.110',
|
|
'screen_name': 'DESKTOP-NJLBQKH',
|
|
'quickconnect_key': '8887779',
|
|
},
|
|
'hostname as ip': {
|
|
'server_ip': 'digi-signage.local',
|
|
'screen_name': 'Player1',
|
|
'quickconnect_key': '0123456',
|
|
},
|
|
'extra keys ignored': {
|
|
'server_ip': '10.0.0.5', 'screen_name': 'Sign1',
|
|
'quickconnect_key': '424242', 'weblink': {'engine': 'auto'},
|
|
},
|
|
}
|
|
|
|
ok = True
|
|
|
|
for label, value in must_be_unconfigured.items():
|
|
got = app.config_is_configured(value)
|
|
flag = 'ok ' if got is False else 'FAIL'
|
|
if got is not False:
|
|
ok = False
|
|
print(f' [{flag}] unconfigured: {label:24} -> {got}')
|
|
|
|
print()
|
|
for label, value in must_be_configured.items():
|
|
got = app.config_is_configured(value)
|
|
flag = 'ok ' if got is True else 'FAIL'
|
|
if got is not True:
|
|
ok = False
|
|
print(f' [{flag}] configured: {label:24} -> {got}')
|
|
|
|
# The defaults the app starts from must themselves be "unconfigured",
|
|
# otherwise a fresh install would look ready to sync.
|
|
print()
|
|
default_ok = app.config_is_configured(app.DEFAULT_CONFIG) is False
|
|
print(f' [{"ok " if default_ok else "FAIL"}] DEFAULT_CONFIG is unconfigured '
|
|
f'-> {app.config_is_configured(app.DEFAULT_CONFIG)}')
|
|
if not default_ok:
|
|
ok = False
|
|
|
|
# Required keys must actually be the ones enforced.
|
|
print(f'\n required keys: {app.CONFIG_REQUIRED_KEYS}')
|
|
print(f' notice delay : {app.SETUP_NOTICE_SECONDS}s before Settings opens')
|
|
|
|
# The notice must wait a few seconds (the user asked for 5).
|
|
if app.SETUP_NOTICE_SECONDS != 5:
|
|
print(f' FAIL: expected a 5s notice, got {app.SETUP_NOTICE_SECONDS}')
|
|
ok = False
|
|
|
|
print('=' * 68)
|
|
print(' RESULT:', 'PASS' if ok else 'FAIL')
|
|
print('=' * 68)
|
|
return 0 if ok else 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|