mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 10:43:56 +08:00
🌴 Sync 2026-10-06 02:20:56
This commit is contained in:
1 parent
5c201dd3af
commit
487c4fad50
111 files changed
+4641
-746
No files matched your search
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-only
|
||||
*
|
||||
* `hp.repairNodeRefs()` - the repair that keeps a deleted node from becoming a
|
||||
* permanent generation failure.
|
||||
*
|
||||
* Deleting a node from the grid stages a plain `uci.remove`; nothing rewrites
|
||||
* `config.main_node` / `main_udp_node` / the urltest member lists. The
|
||||
* generator then dies on every later run:
|
||||
*
|
||||
* cannot build main-out: the node it refers to does not exist (it may have
|
||||
* been deleted, or the reference is stale).
|
||||
*
|
||||
* Measured on the device: generation exit 254 with exactly that message, the
|
||||
* reload aborted, and the UI still reported the save as applied. The repair
|
||||
* runs before every save of the node map (including the footer's Save & Apply,
|
||||
* which calls map.save() itself).
|
||||
*
|
||||
* The helper takes the cursor as an argument, so this drives it with a fake
|
||||
* one - no form, no browser. `nil` and `urltest` are not section names and a
|
||||
* reference that still resolves must be left alone: both are asserted, because
|
||||
* a repair that "fixes" a working configuration is worse than none.
|
||||
*
|
||||
* Usage: node tests/frontend-node-refs.js <repo-root>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { loadLuciModule } = require('./lib/luci-module.js');
|
||||
|
||||
const root = path.resolve(process.argv[2] || '.');
|
||||
|
||||
const hp = loadLuciModule(
|
||||
path.join(root, 'htdocs/luci-static/resources/homeproxy-pro.js'),
|
||||
{
|
||||
baseclass: { extend: (o) => o },
|
||||
form: { DynamicList: { extend: (o) => o } },
|
||||
fs: {}, rpc: {}, uci: {}, ui: {}
|
||||
});
|
||||
|
||||
let checks = 0, failures = 0;
|
||||
|
||||
function check(what, ok, detail) {
|
||||
checks++;
|
||||
if (ok)
|
||||
return;
|
||||
failures++;
|
||||
console.error(`FAIL ${what}${detail ? ': ' + detail : ''}`);
|
||||
}
|
||||
|
||||
/* A cursor over a fixed set of sections, recording the writes. */
|
||||
function fake_uci(sections, options) {
|
||||
const state = Object.assign({}, options);
|
||||
const writes = [];
|
||||
|
||||
return {
|
||||
writes: writes,
|
||||
state: state,
|
||||
get: function(cfg, a, b) {
|
||||
if (b !== undefined)
|
||||
return state[a + '.' + b];
|
||||
return Object.prototype.hasOwnProperty.call(sections, a) ? sections[a] : null;
|
||||
},
|
||||
set: function(cfg, section, option, value) {
|
||||
writes.push({ section: section, option: option, value: value });
|
||||
state[section + '.' + option] = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof hp.repairNodeRefs !== 'function') {
|
||||
console.error('FAIL homeproxy-pro.js does not export repairNodeRefs()');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/* 1. A dangling main_node is cleared. */
|
||||
{
|
||||
const uci = fake_uci({ cfgOTHER: 'node' }, { 'config.main_node': 'cfgGONE' });
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check('a dangling main_node is reported', repaired.includes('main_node'), JSON.stringify(repaired));
|
||||
check('a dangling main_node becomes nil', uci.state['config.main_node'] === 'nil',
|
||||
String(uci.state['config.main_node']));
|
||||
}
|
||||
|
||||
/* 2. A main_node that resolves is untouched. */
|
||||
{
|
||||
const uci = fake_uci({ cfgALIVE: 'node' }, { 'config.main_node': 'cfgALIVE' });
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check('a resolving main_node is not repaired', repaired.length === 0, JSON.stringify(repaired));
|
||||
check('a resolving main_node keeps its value', uci.state['config.main_node'] === 'cfgALIVE');
|
||||
check('a resolving main_node is not written', uci.writes.length === 0, JSON.stringify(uci.writes));
|
||||
}
|
||||
|
||||
/* 3. The two sentinels are not section names. */
|
||||
for (const sentinel of [ 'nil', 'urltest' ]) {
|
||||
const uci = fake_uci({}, { 'config.main_node': sentinel });
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check(`main_node='${sentinel}' is left alone`, repaired.length === 0 && uci.state['config.main_node'] === sentinel,
|
||||
JSON.stringify([ repaired, uci.state['config.main_node'] ]));
|
||||
}
|
||||
|
||||
/* 4. main_udp_node is repaired the same way (the UDP node is a separate
|
||||
* reference and used to be forgotten by the subscription cleanup). */
|
||||
{
|
||||
const uci = fake_uci({}, { 'config.main_udp_node': 'cfgGONE' });
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check('a dangling main_udp_node is reported', repaired.includes('main_udp_node'), JSON.stringify(repaired));
|
||||
check('a dangling main_udp_node becomes nil', uci.state['config.main_udp_node'] === 'nil');
|
||||
}
|
||||
|
||||
/* 5. urltest member lists drop only the missing members. */
|
||||
{
|
||||
const uci = fake_uci({ cfgA: 'node', cfgB: 'node' }, {
|
||||
'config.main_urltest_nodes': [ 'cfgA', 'cfgGONE', 'cfgB' ]
|
||||
});
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check('a stale urltest member is reported', repaired.includes('main_urltest_nodes'), JSON.stringify(repaired));
|
||||
check('a stale urltest member is dropped and the rest kept',
|
||||
JSON.stringify(uci.state['config.main_urltest_nodes']) === JSON.stringify([ 'cfgA', 'cfgB' ]),
|
||||
JSON.stringify(uci.state['config.main_urltest_nodes']));
|
||||
}
|
||||
|
||||
/* 6. A fully valid urltest list is untouched. */
|
||||
{
|
||||
const uci = fake_uci({ cfgA: 'node', cfgB: 'node' }, {
|
||||
'config.main_urltest_nodes': [ 'cfgA', 'cfgB' ]
|
||||
});
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check('a valid urltest list is not repaired', repaired.length === 0 && uci.writes.length === 0,
|
||||
JSON.stringify([ repaired, uci.writes ]));
|
||||
}
|
||||
|
||||
/* 7. An empty or absent list is not a repair either. */
|
||||
{
|
||||
const uci = fake_uci({}, {});
|
||||
const repaired = hp.repairNodeRefs(uci, 'homeproxy-pro');
|
||||
check('absent references are not repaired', repaired.length === 0 && uci.writes.length === 0);
|
||||
}
|
||||
|
||||
console.log(`frontend node references: ${checks} checks, ${failures} failures`);
|
||||
process.exit(failures ? 1 : 0);
|
||||
Reference in new issue
Block a user