Files
Kiwy-Signage/windows/test_first_run_setup.py
T
ske087 477128de81 First-run setup: ship no credentials, ask for them on first start
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.
2026-09-13 10:14:56 +03:00

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())