refactor(sync): sanitize menu tree repair logging

This commit is contained in:
Johannes Millan 2026-05-12 11:49:11 +02:00
parent 78f3a1e180
commit 01d4e6be6f
3 changed files with 85 additions and 5 deletions

View file

@ -466,6 +466,9 @@ Initial candidate-file audit:
applied fixes now route through `SyncLogger` with path/type/count metadata
only; raw invalid values, defaults, and full Typia error objects stay out of
exportable logs.
- `op-log/validation/repair-menu-tree.ts`: menu-tree repair logs now use
`SyncLogger` metadata for removed references/invalid nodes; raw node objects
and folder names stay out of exportable logs.
- `op-log/util/sync-file-prefix.ts`: now delegates to the package helper with
app-supplied prefix and error construction. The app-facing shim should remain
until consumers are deliberately switched to injected/configured helpers.

View file

@ -3,8 +3,17 @@ import {
MenuTreeKind,
MenuTreeState,
} from '../../features/menu-tree/store/menu-tree.model';
import { OP_LOG_SYNC_LOGGER } from '../core/sync-logger.adapter';
describe('repairMenuTree', () => {
let logSpy: jasmine.Spy;
let warnSpy: jasmine.Spy;
beforeEach(() => {
logSpy = spyOn(OP_LOG_SYNC_LOGGER, 'log').and.stub();
warnSpy = spyOn(OP_LOG_SYNC_LOGGER, 'warn').and.stub();
});
it('should remove orphaned project references from projectTree', () => {
const validProjectIds = new Set(['project1', 'project2']);
const validTagIds = new Set<string>();
@ -188,4 +197,40 @@ describe('repairMenuTree', () => {
expect(result.projectTree).toEqual([{ k: MenuTreeKind.PROJECT, id: 'project1' }]);
expect(result.tagTree).toEqual([{ k: MenuTreeKind.TAG, id: 'tag1' }]);
});
it('should log invalid nodes without raw node values', () => {
const privateNodeName = 'Private Folder Name';
const validProjectIds = new Set<string>();
const validTagIds = new Set<string>();
const menuTree: MenuTreeState = {
projectTree: [
{
k: 'unexpected',
id: 'invalid-node-id',
name: privateNodeName,
} as any,
],
tagTree: [],
};
const result = repairMenuTree(menuTree, validProjectIds, validTagIds);
expect(result.projectTree).toEqual([]);
expect(warnSpy).toHaveBeenCalledWith(
'[repair-menu-tree] Removed invalid node',
jasmine.objectContaining({
treeType: 'projectTree',
nodeKind: 'unknown',
hasNodeId: true,
}),
);
const serializedLogArgs = JSON.stringify([
...logSpy.calls.allArgs(),
...warnSpy.calls.allArgs(),
]);
expect(serializedLogArgs).not.toContain(privateNodeName);
expect(serializedLogArgs).not.toContain('invalid-node-id');
});
});

View file

@ -3,7 +3,39 @@ import {
MenuTreeState,
MenuTreeTreeNode,
} from '../../features/menu-tree/store/menu-tree.model';
import { OpLog } from '../../core/log';
import { OP_LOG_SYNC_LOGGER } from '../core/sync-logger.adapter';
const KNOWN_MENU_TREE_KINDS = new Set<string>(Object.values(MenuTreeKind));
const getNodeKindForLog = (kind: unknown): string =>
typeof kind === 'string' && KNOWN_MENU_TREE_KINDS.has(kind) ? kind : 'unknown';
const logOrphanedReferenceRemoval = (
treeType: 'projectTree' | 'tagTree',
referenceType: 'project' | 'tag',
referenceId: string,
): void => {
OP_LOG_SYNC_LOGGER.log('[repair-menu-tree] Removed orphaned reference', {
treeType,
referenceType,
referenceId,
});
};
const logInvalidNodeRemoval = (
treeType: 'projectTree' | 'tagTree',
node: MenuTreeTreeNode,
): void => {
OP_LOG_SYNC_LOGGER.warn('[repair-menu-tree] Removed invalid node', {
treeType,
nodeKind: getNodeKindForLog(node.k),
hasNodeId: typeof node.id === 'string',
childCount:
node.k === MenuTreeKind.FOLDER && Array.isArray(node.children)
? node.children.length
: undefined,
});
};
/**
* Repairs menuTree by removing orphaned project/tag references
@ -17,7 +49,7 @@ export const repairMenuTree = (
validProjectIds: Set<string>,
validTagIds: Set<string>,
): MenuTreeState => {
OpLog.log('Repairing menuTree - removing orphaned references');
OP_LOG_SYNC_LOGGER.log('[repair-menu-tree] Repairing orphaned references');
/**
* Recursively filters tree nodes, removing orphaned project/tag references
@ -41,18 +73,18 @@ export const repairMenuTree = (
if (validProjectIds.has(node.id)) {
filtered.push(node);
} else {
OpLog.log(`Removing orphaned project reference ${node.id} from ${treeType}`);
logOrphanedReferenceRemoval(treeType, 'project', node.id);
}
} else if (treeType === 'tagTree' && node.k === MenuTreeKind.TAG) {
// Keep tag only if it exists
if (validTagIds.has(node.id)) {
filtered.push(node);
} else {
OpLog.log(`Removing orphaned tag reference ${node.id} from ${treeType}`);
logOrphanedReferenceRemoval(treeType, 'tag', node.id);
}
} else {
// kind mismatch or unknown
OpLog.warn(`Removing invalid node from ${treeType}:`, node);
logInvalidNodeRemoval(treeType, node);
}
}