v3.23.6: the event name keywords in the test-script editor
A script's `event` value may be a code (a hex byte or a number) or one of the server's name keywords (location_status, access_tech, network_rejection, data_connection, ... - events.py's EVENT_NAMES); the API accepts both. The PWA parsed every value as a hex string: the step list showed 'EVENT 0xaccess_tech' (the unparsed fallback), the editor fell back to event 03 (Location status) with the location field set, and saving then rewrote the step to that event - silently corrupting name-authored scripts (the two Alfa2G trigger scripts use access_tech/data_connection). A mirrored EVENT_ALIASES map plus an eventCode() resolver (number / hex byte / name -> code) now feed the step summary, the editor's event block and the event check; the summary's fallback no longer prefixes a non-numeric value with 0x, and the editor keeps its canonical hex form on save (the same code). Drift guard: the aliases live in frontend/tests/event_vectors.json, asserted by the Node test (the PWA's map + resolver) and the Python test (events.EVENT_NAMES + resolve_event). Tests: 856 frontend + 839 Python; sw cache simple-v419.
This commit is contained in:
@@ -31,6 +31,8 @@ function extractFunc(src, name) {
|
||||
// Rewrite top-level const -> var so the maps leak out of sloppy-mode eval.
|
||||
eval(extractBlock('const CMD_NAMES = {', 'function cmdQualifierShort').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'cmdQualifierShort'));
|
||||
eval(extractBlock('const EVENT_ALIASES = {', 'function eventCode').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'eventCode'));
|
||||
eval(extractBlock('const EVENT_NAMES = {', 'const REJECTION_CAUSES = [').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const REJECTION_CAUSES = [', 'const EVENT_FORMS = {').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const EVENT_FORMS = {', 'const PLI_QUALIFIERS = [').replace(/^const /gm, 'var '));
|
||||
@@ -324,12 +326,38 @@ test('the event form previews the bytes the send would deliver', () => {
|
||||
assert.strictEqual(els['event-send-preview'].value, '');
|
||||
});
|
||||
|
||||
test('a name-keyword event points at the right form', () => {
|
||||
// the server's name keywords (used by the applet test scripts) resolve to
|
||||
// their code, so the editor shows the right event and prefills its fields
|
||||
// instead of falling back to event 03
|
||||
const cfg = EVENT_FORMS[eventCode('access_tech')];
|
||||
assert.ok(cfg, 'the access-tech form');
|
||||
assert.deepStrictEqual(cfg.fields.map(f => f.id), ['tech']);
|
||||
assert.strictEqual(cfg.build({ tech: 8 }), 'BF0108');
|
||||
const dc = EVENT_FORMS[eventCode('data_connection')];
|
||||
assert.ok(dc, 'the data-connection form');
|
||||
assert.ok(dc.fields.some(f => f.id === 'status') && dc.fields.some(f => f.id === 'type'));
|
||||
});
|
||||
|
||||
test('the shared event vectors match the PWA builders', () => {
|
||||
// the same fixture is asserted against the Python builders by
|
||||
// tests/test_events.py - a change on either side fails the other suite
|
||||
// until the fixture and both implementations agree
|
||||
const vectors = JSON.parse(fs.readFileSync(path.join(__dirname, 'event_vectors.json'), 'utf8'));
|
||||
assert.ok(vectors.events.length && vectors.scts.length, 'the fixture must not be empty');
|
||||
// the event name aliases (the server's keywords) resolve to the codes
|
||||
assert.ok(vectors.aliases, 'the fixture must carry the aliases');
|
||||
const wantAliases = {};
|
||||
for (const [name, hex] of Object.entries(vectors.aliases)) {
|
||||
wantAliases[name] = parseInt(hex, 16);
|
||||
assert.strictEqual(eventCode(name), parseInt(hex, 16), name);
|
||||
}
|
||||
assert.deepStrictEqual(EVENT_ALIASES, wantAliases);
|
||||
assert.deepStrictEqual(eventCode(0x0B), 0x0B);
|
||||
assert.deepStrictEqual(eventCode('0B'), 0x0B);
|
||||
assert.deepStrictEqual(eventCode('0x1d'), 0x1D);
|
||||
assert.ok(Number.isNaN(eventCode('nonsense')));
|
||||
assert.ok(Number.isNaN(eventCode('')));
|
||||
for (const v of vectors.events) {
|
||||
const build = EVENT_FORMS[parseInt(v.event, 16)].build;
|
||||
assert.strictEqual(build(v.fields).toUpperCase(), v.hex,
|
||||
|
||||
@@ -18,6 +18,20 @@
|
||||
{"event": "1D", "fields": {"status": "2", "type": "1", "ti": "", "mcc": "250", "loc_status": "2"}, "hex": "9D0102AA01011C0100130752F010000000019B0102"},
|
||||
{"event": "1D", "fields": {"status": "0", "type": "2", "ti": "AA", "mcc": "262", "mnc": "01", "lac": "0001", "cell": "0002", "tech": "7", "loc_status": "1", "apn": "web.operator.example", "pdp_type": "4"}, "hex": "9D0100AA01021C01AA130762F21000010002BF01079B0101C7147765622E6F70657261746F722E6578616D706C650B0104"}
|
||||
],
|
||||
"aliases": {
|
||||
"location status": "03",
|
||||
"location_status": "03",
|
||||
"location-status": "03",
|
||||
"access technology change": "0B",
|
||||
"access_tech": "0B",
|
||||
"access-technology-change": "0B",
|
||||
"network rejection": "12",
|
||||
"network_rejection": "12",
|
||||
"network-rejection": "12",
|
||||
"data connection status change": "1D",
|
||||
"data_connection": "1D",
|
||||
"data-connection-status-change": "1D"
|
||||
},
|
||||
"scts": [
|
||||
{"year": 2026, "month": 9, "day": 30, "hour": 21, "min": 55, "sec": 0, "tz_east_min": 180, "hex": "62900312550021"},
|
||||
{"year": 2026, "month": 1, "day": 5, "hour": 8, "min": 7, "sec": 9, "tz_east_min": -150, "hex": "62105080709009"}
|
||||
|
||||
@@ -31,6 +31,9 @@ function extractFunc(src, name, asyncFn) {
|
||||
eval(extractBlock('const CMD_NAMES = {', 'function cmdQualifierShort').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const EVENT_NAMES = {', 'const EVENT_FORMS = {').replace(/^const /gm, 'var '));
|
||||
eval(extractBlock('const EVENT_FORMS = {', 'const PLI_QUALIFIERS = [').replace(/^const /gm, 'var '));
|
||||
// the event name aliases (the server's EVENT_NAMES keywords) + the resolver
|
||||
eval(extractBlock('const EVENT_ALIASES = {', 'function eventCode').replace(/^const /gm, 'var '));
|
||||
eval(extractFunc(html, 'eventCode'));
|
||||
// the builders' pure helpers (eventFormFieldsHtml/Values are the shared form)
|
||||
eval(extractFunc(html, 'bytesToHex'));
|
||||
eval(extractFunc(html, 'eventAddressTlv'));
|
||||
@@ -195,6 +198,24 @@ test('testStepSummary renders actions', () => {
|
||||
assert.strictEqual(
|
||||
testStepSummary({ type: 'action', kind: 'status', params: { attempts: 5 } }),
|
||||
'STATUS x5 (poll)');
|
||||
// the event action's value may be a name keyword (the server's alias
|
||||
// form), the editor's hex byte or a number - all show the event name
|
||||
for (const ev of ['access_tech', '0B', '0b', 11, 0x0B]) {
|
||||
assert.strictEqual(
|
||||
testStepSummary({ type: 'action', kind: 'event', params: { event: ev } }),
|
||||
'EVENT Access technology change (single access technology)', String(ev));
|
||||
}
|
||||
assert.strictEqual(
|
||||
testStepSummary({ type: 'action', kind: 'event', params: { event: 'data_connection' },
|
||||
check: { sw: { mode: 'mask', value: '91??' } } }),
|
||||
'EVENT Data connection status change · SW ~91??');
|
||||
// a value that is neither a code nor a known name is shown as-is
|
||||
assert.strictEqual(
|
||||
testStepSummary({ type: 'action', kind: 'event', params: { event: 'nonsense' } }),
|
||||
'EVENT nonsense');
|
||||
assert.strictEqual(
|
||||
testStepSummary({ type: 'action', kind: 'event', params: { event: '1F' } }),
|
||||
'EVENT Slices status change');
|
||||
assert.strictEqual(
|
||||
testStepSummary({ type: 'action', kind: 'proactive-drain',
|
||||
params: { attempts: 5, respond: { result: 'ok' },
|
||||
|
||||
Reference in New Issue
Block a user