fix: snapshot/profile scans skipped DF subtrees (parent_path off-by-one)

profilerScanCard's walkDir passed each child dir's own segment in
parent_path (dir.parentPath.concat(childSeg) as the child's parent path),
so /api/tree for every DF child walked the target as its own parent and
failed: DF.GSM, DF.TELECOM and DF.GSM-ACCESS subtrees were silently
missing from New snapshot, Profile from card and Profile from snapshot
(ADF children survived only because application names/AIDs resolve
globally).

- walkDir now stores each dir's full path and sends
  parent_path = fullPath.slice(0, -1) (omitted for MF); the child's full
  path is fullPath.concat(aid/fid/name)
- verified against the running server: the walker now enumerates 189 EFs
  and captures 92, matching the file-manager probe exactly (was 111/48)
- test: fake MF -> DF.GSM tree asserts parent_path [undefined, ['MF']]
  and that both subtrees' rules are produced; SW cache v132 -> v133.
This commit is contained in:
2026-09-13 21:39:15 +03:00
parent 0370fa58ad
commit c9ae494f62
3 changed files with 34 additions and 5 deletions
+7 -4
View File
@@ -7820,10 +7820,12 @@ async function profilerScanCard(ignoreFids, ignoreNames, fciMode, onProgress, ma
const files = [];
const seen = new Set();
// dir: { name, fid, parentSel (to select this dir), pathPrefix (rule-path segs for its children) }
// dir: { name, fid, parentSel (legacy), parentPath (full path of this dir),
// pathPrefix (rule-path segs for its children) }
async function walkDir(dir) {
let body = { name: dir.name, fid: dir.fid };
if (dir.parentPath && dir.parentPath.length) body.parent_path = dir.parentPath;
const parentPath = (dir.parentPath || []).slice(0, -1);
if (parentPath.length) body.parent_path = parentPath;
if (dir.parentSel) body.parent_sel = dir.parentSel;
let data;
try {
@@ -7835,12 +7837,13 @@ async function profilerScanCard(ignoreFids, ignoreNames, fciMode, onProgress, ma
const parentSel = (dir.name === 'MF') ? 'MF'
: (dir.name && dir.name.startsWith('ADF.')) ? dir.name
: (dir.fid ? dir.fid : dir.name);
const childSeg = c.aid ? c.aid.toUpperCase() : (c.fid ? c.fid : c.name);
if (c.isDir) {
// ADF roots use the AID, not the generic ADF fid
const childPrefix = c.aid ? [c.aid.toUpperCase()]
: dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name);
const childParentPath = (dir.parentPath || []).concat(c.aid ? c.aid.toUpperCase() : (c.fid ? c.fid : c.name));
await walkDir({ name: c.name, fid: c.fid, parentSel: parentSel, parentPath: childParentPath, pathPrefix: childPrefix });
const childDirPath = dir.parentPath.concat(childSeg);
await walkDir({ name: c.name, fid: c.fid, parentSel: parentSel, parentPath: childDirPath, pathPrefix: childPrefix });
} else {
const childPath = dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name).join('/');
if (seen.has(childPath)) continue;
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'otaman-v132';
const CACHE = 'otaman-v133';
const URLS = [
'index.html',
'help.html',
+26
View File
@@ -947,6 +947,32 @@ test('profilerScanCard snapshot mode builds snapshot entries with ICCID', async
delete global.pysimCustomFiles;
});
test('profilerScanCard walks nested dirs with their parent path, not their own segment', async () => {
global.pysimCustomFiles = [];
const treeCalls = [];
global.pysimFetch = async (path, body) => {
if (path === '/api/tree') {
treeCalls.push(body);
if (body.name === 'MF') return { exists: true, name: 'MF', children: [
{ name: 'EF.DIR', fid: '2f00', isDir: false },
{ name: 'DF.GSM', fid: '7f20', isDir: true },
] };
if (body.name === 'DF.GSM') return { exists: true, name: 'DF.GSM', children: [
{ name: 'EF.ADN', fid: '6f3a', isDir: false },
] };
throw new Error('unexpected tree ' + body.name);
}
if (path === '/api/select') return { name: 'X', fid: '0000', file_type: 'transparent', file_size: 1, record_len: null, num_of_rec: null, exists: true };
if (path === '/api/read') return { success: true, data: 'AA' };
throw new Error('unexpected ' + path);
};
const files = await profilerScanCard(new Set(), new Set(), 'type', undefined, new Set(), 'snapshot');
// MF root has no parent; DF.GSM must be looked up under MF, not under itself
assert.deepStrictEqual(treeCalls.map(b => b.parent_path), [undefined, ['MF']]);
assert.deepStrictEqual(files.map(f => f.path).sort(), ['MF/2F00', 'MF/7F20/6F3A']);
delete global.pysimCustomFiles;
});
test('profilerListSwitch toggles the profiles/snapshots tabs', () => {
const mkBtn = tab => ({
dataset: { listTab: tab },