Files
hockey_new/tests/test_stable_vmix_input_identity.py

118 lines
5.6 KiB
Python

from pathlib import Path
import re
import subprocess
from hockey_data.agent_bridge import _stable_vmix_inventory_fingerprint
ROOT = Path(__file__).resolve().parents[1]
def test_stable_project_fingerprint_ignores_input_number_order_and_title_when_key_exists():
before = [
{
"key": "key-score",
"number": "22",
"title": "Верхний счёт",
"type": "GT",
"fields": [
{"name": "Таймер.Text", "type": "text", "index": "0"},
{"name": "HOME.Text", "type": "text", "index": "1"},
],
},
{
"key": "key-penalty",
"number": "114",
"title": "Удаление HOME",
"type": "GT",
"fields": [{"name": "Таймер.Text", "type": "text", "index": "0"}],
},
]
after = [
{
"key": "key-penalty",
"number": "7",
"title": "Удаление HOME (новое имя)",
"type": "GT",
"fields": [{"name": "Таймер.Text", "type": "text", "index": "4"}],
},
{
"key": "key-score",
"number": "91",
"title": "Счёт",
"type": "GT",
"fields": [
{"name": "HOME.Text", "type": "text", "index": "9"},
{"name": "Таймер.Text", "type": "text", "index": "2"},
],
},
]
assert _stable_vmix_inventory_fingerprint(before) == _stable_vmix_inventory_fingerprint(after)
def test_legacy_project_fingerprint_uses_title_instead_of_number():
before = [{"key": "", "number": "3", "title": "Score", "type": "GT", "fields": []}]
after = [{"key": "", "number": "99", "title": "Score", "type": "GT", "fields": []}]
assert _stable_vmix_inventory_fingerprint(before) == _stable_vmix_inventory_fingerprint(after)
def _run_functions(source, names, scenario):
# Execute the shipped function bodies; test doubles supply only page state.
functions = []
for name in names:
match = re.search(r"(?m)^ function " + re.escape(name) + r"\(", source)
assert match, name
following = re.search(r"(?m)^ (?:async )?function ", source[match.end():])
assert following, name
functions.append(source[match.start():match.end() + following.start()])
script = "const assert = require('node:assert/strict');\n" + "\n".join(functions) + "\n" + scenario
result = subprocess.run(["node", "-e", script], text=True, capture_output=True, timeout=10)
assert result.returncode == 0, result.stderr
def test_shortcut_editor_persists_title_and_migrates_legacy_selectors():
app_js = (ROOT / "ui_builder" / "static" / "app.js").read_text(encoding="utf-8")
_run_functions(app_js, ["vmixInventoryInputs", "vmixInputRef", "vmixInputByRef"], r'''
const score = {key: 'score-guid', number: '5', title: 'Score'};
const other = {key: 'other-guid', number: '9', title: 'Other'};
const state = {shortcutInventory: {inventory: {inputs: [score, other]}}};
assert.equal(vmixInputRef(score), 'Score');
assert.equal(vmixInputRef({key: 'only-guid', number: '5'}), '');
for (const legacy of ['score-guid', '5']) assert.equal(vmixInputRef(vmixInputByRef(legacy)), 'Score');
const saved = vmixInputRef(score);
score.number = '9'; other.number = '5'; score.key = 'new-score-guid';
assert.equal(vmixInputByRef(saved), score);
assert.equal(vmixInputRef(vmixInputByRef(saved)), 'Score');
assert.equal(vmixInputByRef('missing'), null);
''')
def test_mapping_links_save_exact_title_and_clear_every_positional_number():
mapping_js = (ROOT / "hockey_data" / "static" / "admin-directories.js").read_text(encoding="utf-8")
_run_functions(mapping_js, ["mappingInventoryInputs", "mappingFieldKind", "mappingRowMatchesInput",
"mappingInputTitleProblem", "mappingSetLink", "collectMappingFields"], r'''
const input = {key: 'current-guid', number: '17', title: 'Score', fields: [{name: 'Home.Text', type: 'text'}]};
const profile = {inventory: {inputs: [input]}, fields: [{data_key:'old', vmix_input_key:'old-guid',
vmix_input_number:'5', vmix_input_title:'Score', vmix_field:'Home.Text', rule:{enabled:true, left_key:'old'}}]};
const state = {mappingActiveProfile:profile};
const statuses = []; function setStatus(message){statuses.push(message);}
mappingSetLink(profile, input, input.fields[0], {key:'score.home'});
assert.equal(profile.fields.length, 1);
assert.equal(profile.fields[0].vmix_input_title, 'Score');
assert.equal(profile.fields[0].vmix_input_key, 'current-guid');
assert.equal(profile.fields[0].vmix_input_number, '');
assert.equal(profile.fields[0].rule.left_key, 'score.home');
profile.fields.push({data_key:'legacy', vmix_input_key:'another-guid', vmix_input_number:'42', vmix_input_title:'Other', vmix_field:'Away.Text'});
profile.fields.push({data_key:'orphaned-legacy', vmix_input_key:'', vmix_input_number:'7', vmix_input_title:'', vmix_field:'Legacy.Text'});
const collected = collectMappingFields();
assert.deepEqual(collected.map(row=>row.vmix_input_number), ['', '', '']);
assert.deepEqual(collected.map(row=>row.vmix_input_title), ['Score', 'Other', '']);
assert.deepEqual(collected.map(row=>row.sort_order), [0, 1, 2]);
assert.equal(profile.fields[1].vmix_input_number, '42', 'Collecting must not mutate draft rows');
const previous = JSON.stringify(profile.fields);
profile.inventory.inputs.push({...input, key:'duplicate-guid', number:'18'});
mappingSetLink(profile, input, input.fields[0], {key:'unsafe'});
assert.equal(JSON.stringify(profile.fields), previous);
assert.ok(statuses.length && statuses[0].includes('не уникально'));
''')