scripts/inventory-logging.test.mjs
scripts/inventory-logging.test.mjsBrowse 5 files
4,288 tokens
18,262 bytes
Token encoding: o200k_base
Snapshot 506f736
← Back to SKILL.md
1import assert from 'node:assert/strict';2import test from 'node:test';3 4import { inventorySource, inventorySources } from './inventory-logging.mjs';5 6test('inventories static and dynamic logger calls', () => {7 const findings = inventorySource(`8 const logger = getLogger('fixture');9 logger.debug('ready');10 logger.warn(\`Failed for \${requestId}\`);11 logger.error('Request failed', error, response);12 `);13 14 assert.deepEqual(15 findings.map(({ method, shape, policy }) => ({ method, shape, policy })),16 [17 { method: 'debug', shape: 'static-message', policy: 'none' },18 { method: 'warn', shape: 'dynamic-message', policy: 'none' },19 { method: 'error', shape: 'payload', policy: 'none' },20 ],21 );22});23 24test('recognizes model and tool policy boundaries', () => {25 const findings = inventorySource(`26 import {27 logModelAndToolActionWarning,28 logModelActionError,29 logToolActionDebug,30 logToolActionError,31 } from './logger';32 33 const logger = getLogger('fixture');34 if (!logger.dontLogModelData) {35 logger.debug('Response:', response);36 }37 if (logger.dontLogToolData) {38 logger.warn('Tool data exposed', secret);39 } else {40 logger.warn('Tool failed', error);41 }42 logModelActionError(logger, 'Model failed', error, event);43 logModelAndToolActionWarning(logger, 'Run failed', error, event);44 logToolActionDebug(logger, 'Tool debug failed', error, toolCall);45 logToolActionError(logger, 'Tool failed', error, toolCall);46 `);47 48 assert.deepEqual(49 findings.map(({ method, policy }) => ({ method, policy })),50 [51 { method: 'debug', policy: 'model-guard' },52 { method: 'warn', policy: 'none' },53 { method: 'warn', policy: 'tool-guard' },54 { method: 'logModelActionError', policy: 'model-helper' },55 {56 method: 'logModelAndToolActionWarning',57 policy: 'model+tool-helper',58 },59 { method: 'logToolActionDebug', policy: 'tool-helper' },60 { method: 'logToolActionError', policy: 'tool-helper' },61 ],62 );63});64 65test('reads policy conditions from conditional expressions', () => {66 const findings = inventorySource(`67 const logger = getLogger('fixture');68 logger.dontLogToolData69 ? logger.warn('Tool logging disabled')70 : logger.warn('Tool failed', error);71 `);72 73 assert.deepEqual(74 findings.map(({ method, policy }) => ({ method, policy })),75 [76 { method: 'warn', policy: 'none' },77 { method: 'warn', policy: 'tool-guard' },78 ],79 );80});81 82test('requires guard polarity to guarantee sensitive logging is enabled', () => {83 const findings = inventorySource(`84 const logger = getLogger('fixture');85 if (!logger.dontLogToolData && ready) {86 logger.error('Protected tool data', secret);87 }88 if (logger.dontLogModelData || disabled) {89 logger.error('Potentially exposed model data', secret);90 } else {91 logger.error('Protected model data', secret);92 }93 if (!logger.dontLogModelData && !logger.dontLogToolData) {94 logger.error('Protected model and tool data', secret);95 }96 `);97 98 assert.deepEqual(99 findings.map(({ policy }) => policy),100 ['tool-guard', 'none', 'model-guard', 'model+tool-guard'],101 );102});103 104test('flags caught values passed to logger and console calls', () => {105 const findings = inventorySource(`106 const appLogger: Logger = getLogger('app');107 try {108 await run();109 } catch (reason) {110 appLogger.error('Run failed', reason);111 console.warn(\`Run failed: \${reason}\`);112 }113 `);114 115 assert.deepEqual(116 findings.map(({ kind, catchValue, policy }) => ({117 kind,118 catchValue,119 policy,120 })),121 [122 { kind: 'logger', catchValue: 'reason', policy: 'none' },123 { kind: 'console', catchValue: 'reason', policy: 'none' },124 ],125 );126});127 128test('flags promise rejection handler values as caught values', () => {129 const findings = inventorySource(`130 const logger = getLogger('fixture');131 run().catch((reason) => logger.warn('Run failed', reason));132 run().then(undefined, (error) => console.error('Run failed', error));133 process.on('unhandledRejection', (reason, promise) => {134 logger.error('Unhandled rejection', reason, promise);135 });136 `);137 138 assert.deepEqual(139 findings.map(({ catchValue }) => catchValue),140 ['reason', 'error', 'reason'],141 );142});143 144test('inventories logging method references passed as callbacks', () => {145 const findings = inventorySource(`146 const logger = getLogger('fixture');147 const { warn } = logger;148 run().catch(console.error);149 run().then(undefined, logger.error);150 process.on('unhandledRejection', warn);151 items.forEach(console.log);152 `);153 154 assert.deepEqual(155 findings.map(({ kind, method, catchValue }) => ({156 kind,157 method,158 catchValue,159 })),160 [161 {162 kind: 'console',163 method: 'error',164 catchValue: 'rejection reason',165 },166 {167 kind: 'logger',168 method: 'error',169 catchValue: 'rejection reason',170 },171 {172 kind: 'logger',173 method: 'warn',174 catchValue: 'rejection reason',175 },176 { kind: 'console', method: 'log', catchValue: null },177 ],178 );179});180 181test('flags destructured rejection values as caught values', () => {182 const findings = inventorySource(`183 const logger = getLogger('fixture');184 try {185 await run();186 } catch ({ message, cause: nestedCause }) {187 logger.error('Run failed', message, nestedCause);188 }189 run().catch(({ reason }) => logger.warn('Run failed', reason));190 `);191 192 assert.deepEqual(193 findings.map(({ catchValue }) => catchValue),194 ['message, nestedCause', 'reason'],195 );196});197 198test('resolves logger factories, imports, aliases, properties, and types', () => {199 const findings = inventorySource(`200 import defaultSink from './logger';201 import * as core from '@openai/agents-core';202 import { getLogger as createSink, logger as sharedSink } from './logger';203 import type { Logger as Sink } from './logger';204 205 const audit = createSink('audit');206 const alias = audit;207 const namespaced = core.getLogger('namespaced');208 defaultSink.error('Default failed', response);209 sharedSink.warn('Shared failed', response);210 audit.error('Audit failed', response);211 alias.info('Alias failed', response);212 namespaced.error('Namespaced failed', response);213 214 class Service {215 private readonly sink = createSink('service');216 report(problem, injected: Sink) {217 this.sink.error('Service failed', problem);218 injected.error('Injected failed', problem);219 }220 }221 222 class InheritedService {223 report(problem) {224 this.logger.error('Inherited logger failed', problem);225 }226 }227 `);228 229 assert.deepEqual(230 findings.map(({ method }) => method),231 ['error', 'warn', 'error', 'info', 'error', 'error', 'error', 'error'],232 );233});234 235test('resolves logger instances stored in object literals', () => {236 const findings = inventorySource(`237 const audit = getLogger('audit');238 const sinks = {239 audit: getLogger('nested-audit'),240 auditAlias: audit,241 audit,242 };243 sinks.audit.error('Nested audit failed', response);244 sinks['audit'].warn('Bracket audit failed', response);245 sinks.auditAlias.warn('Audit alias failed', response);246 sinks.audit.info('Shorthand audit failed', response);247 `);248 249 assert.deepEqual(250 findings.map(({ method }) => method),251 ['error', 'warn', 'warn', 'info'],252 );253});254 255test('resolves extracted Logger method aliases', () => {256 const findings = inventorySource(`257 const logger = getLogger('fixture');258 const { error: report, warn } = logger;259 const debug = logger['debug'];260 const reportAlias = report;261 262 report('Report failed', secret);263 warn('Warning', secret);264 debug('Debug value', secret);265 reportAlias('Alias failed', secret);266 `);267 268 assert.deepEqual(269 findings.map(({ kind, method }) => ({ kind, method })),270 [271 { kind: 'logger', method: 'error' },272 { kind: 'logger', method: 'warn' },273 { kind: 'logger', method: 'debug' },274 { kind: 'logger', method: 'error' },275 ],276 );277});278 279test('resolves Logger-typed object members', () => {280 const findings = inventorySource(`281 type ReportOptions = { logger?: Logger };282 interface AuditOptions {283 sink: Readonly<Logger>;284 }285 286 function report(options: { logger: Logger }) {287 options.logger.error('Inline options failed', secret);288 }289 function reportAlias(options: ReportOptions) {290 options.logger?.warn('Alias options failed', secret);291 }292 function reportInterface(options: AuditOptions) {293 options.sink.info('Interface options failed', secret);294 }295 function reportDestructured({ logger }: { logger: Logger }) {296 logger.error('Destructured options failed', secret);297 }298 function reportAliased({ logger: sink }: ReportOptions) {299 sink.warn('Aliased destructuring failed', secret);300 }301 `);302 303 assert.deepEqual(304 findings.map(({ method }) => method),305 ['error', 'warn', 'info', 'error', 'warn'],306 );307});308 309test('resolves imported Logger-valued object members', () => {310 const findings = inventorySources({311 'types.ts': `312 export type Logger = {313 namespace: string;314 debug(message: string, ...args: unknown[]): void;315 error(message: string, ...args: unknown[]): void;316 warn(message: string, ...args: unknown[]): void;317 dontLogModelData: boolean;318 dontLogToolData: boolean;319 };320 export interface ReportOptions {321 logger: Logger;322 }323 `,324 'report.ts': `325 import type { ReportOptions } from './types';326 327 export function report(options: ReportOptions) {328 options.logger.error('Imported options failed', secret);329 }330 `,331 });332 333 assert.deepEqual(334 findings.map(({ file, method }) => ({ file, method })),335 [{ file: 'report.ts', method: 'error' }],336 );337});338 339test('resolves Logger subtypes declared with heritage clauses', () => {340 const findings = inventorySource(`341 interface AuditLogger extends Logger {}342 interface NestedAuditLogger extends AuditLogger {}343 class ServiceLogger implements Logger {}344 class NestedServiceLogger extends ServiceLogger {}345 346 function report(347 audit: NestedAuditLogger,348 service: NestedServiceLogger,349 ) {350 audit.error('Audit failed', secret);351 service.warn('Service failed', secret);352 }353 `);354 355 assert.deepEqual(356 findings.map(({ method }) => method),357 ['error', 'warn'],358 );359});360 361test('inventories every direct console method', () => {362 const findings = inventorySource(`363 console.dir(secret);364 console.table(payload);365 console.trace(error);366 `);367 368 assert.deepEqual(369 findings.map(({ kind, method, policy }) => ({ kind, method, policy })),370 [371 { kind: 'console', method: 'dir', policy: 'none' },372 { kind: 'console', method: 'table', policy: 'none' },373 { kind: 'console', method: 'trace', policy: 'none' },374 ],375 );376});377 378test('recognizes globally qualified console receivers', () => {379 const findings = inventorySource(`380 globalThis.console.error('Global failure', secret);381 window.console.warn(secret);382 global.console.log(payload);383 const sink = globalThis.console;384 sink.trace(error);385 `);386 387 assert.deepEqual(388 findings.map(({ kind, method }) => ({ kind, method })),389 [390 { kind: 'console', method: 'error' },391 { kind: 'console', method: 'warn' },392 { kind: 'console', method: 'log' },393 { kind: 'console', method: 'trace' },394 ],395 );396});397 398test('resolves console object and extracted method aliases', () => {399 const findings = inventorySource(`400 const sink = console;401 const nestedSink = sink;402 const { warn, log: emit } = nestedSink;403 const trace = console.trace;404 const sinks = { raw: console };405 406 sink.error('Request failed', secret);407 warn(secret);408 emit(payload);409 trace(error);410 sinks.raw.error('Object member failed', secret);411 `);412 413 assert.deepEqual(414 findings.map(({ kind, method }) => ({ kind, method })),415 [416 { kind: 'console', method: 'error' },417 { kind: 'console', method: 'warn' },418 { kind: 'console', method: 'log' },419 { kind: 'console', method: 'trace' },420 { kind: 'console', method: 'error' },421 ],422 );423});424 425test('method alias analysis converges across reassignments', () => {426 const findings = inventorySource(`427 let emit = console.log;428 emit = console.error;429 430 const logger = getLogger('fixture');431 let report = logger.warn;432 report = logger.error;433 434 emit(secret);435 report('Failed', secret);436 `);437 438 assert.deepEqual(439 findings.map(({ kind, method }) => ({ kind, method })),440 [441 { kind: 'console', method: 'error|log' },442 { kind: 'logger', method: 'error|warn' },443 ],444 );445});446 447test('inventories computed methods on known logging receivers', () => {448 const findings = inventorySource(`449 const logger = getLogger('fixture');450 const level: 'error' | 'warn' = choose();451 logger[level]('Logger failed', secret);452 console[level]('Console failed', secret);453 const emit = console[level];454 emit(payload);455 `);456 457 assert.deepEqual(458 findings.map(({ kind, method }) => ({ kind, method })),459 [460 { kind: 'logger', method: 'computed' },461 { kind: 'console', method: 'computed' },462 { kind: 'console', method: 'computed' },463 ],464 );465});466 467test('resolves sensitive helper import aliases and namespace accesses', () => {468 const findings = inventorySource(`469 import { logToolActionError as reportFailure } from './logger';470 import * as logging from '@openai/agents-core/utils/internal';471 472 reportFailure(logger, 'Tool failed', error, payload);473 logging.logModelActionError(logger, 'Model failed', error, response);474 `);475 476 assert.deepEqual(477 findings.map(({ method, policy }) => ({ method, policy })),478 [479 { method: 'logToolActionError', policy: 'tool-helper' },480 { method: 'logModelActionError', policy: 'model-helper' },481 ],482 );483});484 485test('does not trust sensitive helper spellings without provenance', () => {486 const findings = inventorySource(`487 import {488 logModelActionError as unrelatedHelper,489 } from './unrelated';490 import * as unrelated from './unrelated';491 492 function logToolActionError(target, message, secret) {493 target(message, secret);494 }495 496 logToolActionError(sink, 'Local helper', secret);497 unrelatedHelper(sink, 'Imported helper', secret);498 unrelated.logToolActionError(sink, 'Namespace helper', secret);499 `);500 501 assert.equal(findings.length, 0);502});503 504test('fingerprints distinguish call sites and survive unrelated line shifts', () => {505 const source = `506 const logger = getLogger('fixture');507 function logModel(error) {508 logger.error('Operation failed', error);509 }510 function logTool(error) {511 logger.error('Operation failed', error);512 }513 `;514 const shiftedSource = `515 const unrelated = true;516 ${source}517 `;518 const findings = inventorySource(source);519 const shiftedFindings = inventorySource(shiftedSource);520 521 assert.equal(new Set(findings.map(({ fingerprint }) => fingerprint)).size, 2);522 assert.deepEqual(523 shiftedFindings.map(({ fingerprint }) => fingerprint),524 findings.map(({ fingerprint }) => fingerprint),525 );526});527 528test('fingerprints identify switch branches without order-dependent reuse', () => {529 const source = `530 const logger = getLogger('fixture');531 switch (kind) {532 case 'model':533 logger.error('Operation failed', error);534 break;535 case 'tool':536 logger.error('Operation failed', error);537 break;538 default:539 logger.error('Operation failed', error);540 }541 `;542 const sourceWithEarlierCase = source.replace(543 "case 'model':",544 `case 'new':545 logger.error('Operation failed', error);546 break;547 case 'model':`,548 );549 const findings = inventorySource(source);550 const shiftedFindings = inventorySource(sourceWithEarlierCase);551 552 assert.equal(new Set(findings.map(({ fingerprint }) => fingerprint)).size, 3);553 assert.deepEqual(554 shiftedFindings.slice(1).map(({ fingerprint }) => fingerprint),555 findings.map(({ fingerprint }) => fingerprint),556 );557 assert.match(findings[0].context, /switch:kind>case:'model'/);558 assert.match(findings[1].context, /switch:kind>case:'tool'/);559 assert.match(findings[2].context, /switch:kind>case:default/);560});561 562test('fingerprints identify try branches without order-dependent reuse', () => {563 const source = `564 const logger = getLogger('fixture');565 try {566 logger.error('Operation failed', error);567 } catch {568 logger.error('Operation failed', error);569 } finally {570 logger.error('Operation failed', error);571 }572 `;573 const sourceWithEarlierCall = source.replace(574 'try {',575 `logger.error('Operation failed', error);576 try {`,577 );578 const findings = inventorySource(source);579 const shiftedFindings = inventorySource(sourceWithEarlierCall);580 581 assert.equal(new Set(findings.map(({ fingerprint }) => fingerprint)).size, 3);582 assert.deepEqual(583 shiftedFindings.slice(1).map(({ fingerprint }) => fingerprint),584 findings.map(({ fingerprint }) => fingerprint),585 );586 assert.match(findings[0].context, /try:try/);587 assert.match(findings[1].context, /try:catch/);588 assert.match(findings[2].context, /try:finally/);589});590 591test('normalizes path separators before recording and hashing', () => {592 const source = `593 const logger = getLogger('fixture');594 logger.error('Operation failed', error);595 `;596 const windowsFinding = inventorySource(597 source,598 'packages\\agents-core\\src\\fixture.ts',599 )[0];600 const posixFinding = inventorySource(601 source,602 'packages/agents-core/src/fixture.ts',603 )[0];604 605 assert.equal(windowsFinding.file, 'packages/agents-core/src/fixture.ts');606 assert.equal(windowsFinding.fingerprint, posixFinding.fingerprint);607});608 609test('ignores unrelated methods that happen to share logger method names', () => {610 const findings = inventorySource(`611 controller.error(problem);612 loggerLikeButNotExact.info(details);613 `);614 615 assert.equal(findings.length, 0);616});617