@camaradesuk/git-worktree-tools 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -27
- package/dist/cli/cleanpr.js +74 -53
- package/dist/cli/cleanpr.js.map +1 -1
- package/dist/cli/cleanpr.test.js +2 -0
- package/dist/cli/cleanpr.test.js.map +1 -1
- package/dist/cli/lswt.js +32 -56
- package/dist/cli/lswt.js.map +1 -1
- package/dist/cli/lswt.test.js +17 -27
- package/dist/cli/lswt.test.js.map +1 -1
- package/dist/cli/newpr.d.ts +13 -1
- package/dist/cli/newpr.d.ts.map +1 -1
- package/dist/cli/newpr.js +350 -151
- package/dist/cli/newpr.js.map +1 -1
- package/dist/cli/newpr.test.js +314 -5
- package/dist/cli/newpr.test.js.map +1 -1
- package/dist/cli/prs.d.ts +3 -10
- package/dist/cli/prs.d.ts.map +1 -1
- package/dist/cli/prs.js +6 -168
- package/dist/cli/prs.js.map +1 -1
- package/dist/cli/prs.test.js +55 -0
- package/dist/cli/prs.test.js.map +1 -1
- package/dist/cli/wt/clean.d.ts +6 -2
- package/dist/cli/wt/clean.d.ts.map +1 -1
- package/dist/cli/wt/clean.js +401 -20
- package/dist/cli/wt/clean.js.map +1 -1
- package/dist/cli/wt/clean.test.d.ts +8 -0
- package/dist/cli/wt/clean.test.d.ts.map +1 -0
- package/dist/cli/wt/clean.test.js +624 -0
- package/dist/cli/wt/clean.test.js.map +1 -0
- package/dist/cli/wt/completion.d.ts +3 -0
- package/dist/cli/wt/completion.d.ts.map +1 -1
- package/dist/cli/wt/completion.js +80 -9
- package/dist/cli/wt/completion.js.map +1 -1
- package/dist/cli/wt/completion.test.js +102 -0
- package/dist/cli/wt/completion.test.js.map +1 -1
- package/dist/cli/wt/config.d.ts +3 -1
- package/dist/cli/wt/config.d.ts.map +1 -1
- package/dist/cli/wt/config.js +323 -32
- package/dist/cli/wt/config.js.map +1 -1
- package/dist/cli/wt/config.test.d.ts +2 -0
- package/dist/cli/wt/config.test.d.ts.map +1 -1
- package/dist/cli/wt/config.test.js +206 -26
- package/dist/cli/wt/config.test.js.map +1 -1
- package/dist/cli/wt/interactive-menu.d.ts +2 -0
- package/dist/cli/wt/interactive-menu.d.ts.map +1 -1
- package/dist/cli/wt/interactive-menu.js +346 -73
- package/dist/cli/wt/interactive-menu.js.map +1 -1
- package/dist/cli/wt/interactive-menu.test.d.ts +4 -2
- package/dist/cli/wt/interactive-menu.test.d.ts.map +1 -1
- package/dist/cli/wt/interactive-menu.test.js +383 -323
- package/dist/cli/wt/interactive-menu.test.js.map +1 -1
- package/dist/cli/wt/link.d.ts +3 -1
- package/dist/cli/wt/link.d.ts.map +1 -1
- package/dist/cli/wt/link.js +125 -38
- package/dist/cli/wt/link.js.map +1 -1
- package/dist/cli/wt/list.d.ts +4 -1
- package/dist/cli/wt/list.d.ts.map +1 -1
- package/dist/cli/wt/list.js +85 -16
- package/dist/cli/wt/list.js.map +1 -1
- package/dist/cli/wt/list.test.d.ts +10 -0
- package/dist/cli/wt/list.test.d.ts.map +1 -0
- package/dist/cli/wt/list.test.js +157 -0
- package/dist/cli/wt/list.test.js.map +1 -0
- package/dist/cli/wt/new.d.ts +8 -2
- package/dist/cli/wt/new.d.ts.map +1 -1
- package/dist/cli/wt/new.js +91 -46
- package/dist/cli/wt/new.js.map +1 -1
- package/dist/cli/wt/prs.d.ts +2 -1
- package/dist/cli/wt/prs.d.ts.map +1 -1
- package/dist/cli/wt/prs.js +3 -164
- package/dist/cli/wt/prs.js.map +1 -1
- package/dist/cli/wt/run-command.d.ts +4 -2
- package/dist/cli/wt/run-command.d.ts.map +1 -1
- package/dist/cli/wt/run-command.js +6 -4
- package/dist/cli/wt/run-command.js.map +1 -1
- package/dist/cli/wt/state.d.ts +3 -1
- package/dist/cli/wt/state.d.ts.map +1 -1
- package/dist/cli/wt/state.js +74 -10
- package/dist/cli/wt/state.js.map +1 -1
- package/dist/cli/wt/state.test.d.ts +9 -0
- package/dist/cli/wt/state.test.d.ts.map +1 -0
- package/dist/cli/wt/state.test.js +127 -0
- package/dist/cli/wt/state.test.js.map +1 -0
- package/dist/cli/wt/wt.test.d.ts +2 -2
- package/dist/cli/wt/wt.test.js +430 -212
- package/dist/cli/wt/wt.test.js.map +1 -1
- package/dist/cli/wt.d.ts.map +1 -1
- package/dist/cli/wt.js +50 -36
- package/dist/cli/wt.js.map +1 -1
- package/dist/cli/wt.unit.test.js +16 -38
- package/dist/cli/wt.unit.test.js.map +1 -1
- package/dist/cli/wtconfig.d.ts +1 -0
- package/dist/cli/wtconfig.d.ts.map +1 -1
- package/dist/cli/wtconfig.js +213 -21
- package/dist/cli/wtconfig.js.map +1 -1
- package/dist/cli/wtconfig.test.js +3 -0
- package/dist/cli/wtconfig.test.js.map +1 -1
- package/dist/cli/wtlink.js +116 -73
- package/dist/cli/wtlink.js.map +1 -1
- package/dist/cli/wtstate.js +21 -2
- package/dist/cli/wtstate.js.map +1 -1
- package/dist/e2e/wt/interactive-menu.e2e.test.js +17 -17
- package/dist/e2e/wt/interactive-menu.e2e.test.js.map +1 -1
- package/dist/lib/ai/types.d.ts +12 -0
- package/dist/lib/ai/types.d.ts.map +1 -1
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/cleanpr/args.d.ts.map +1 -1
- package/dist/lib/cleanpr/args.js +20 -0
- package/dist/lib/cleanpr/args.js.map +1 -1
- package/dist/lib/cleanpr/types.d.ts +6 -0
- package/dist/lib/cleanpr/types.d.ts.map +1 -1
- package/dist/lib/cleanpr/worktree-info.d.ts.map +1 -1
- package/dist/lib/cleanpr/worktree-info.js +1 -6
- package/dist/lib/cleanpr/worktree-info.js.map +1 -1
- package/dist/lib/cleanpr/worktree-info.test.js +10 -13
- package/dist/lib/cleanpr/worktree-info.test.js.map +1 -1
- package/dist/lib/colors.d.ts +5 -0
- package/dist/lib/colors.d.ts.map +1 -1
- package/dist/lib/colors.js +13 -6
- package/dist/lib/colors.js.map +1 -1
- package/dist/lib/config-editor.d.ts.map +1 -1
- package/dist/lib/config-editor.js.map +1 -1
- package/dist/lib/config-migration/detector.d.ts +25 -0
- package/dist/lib/config-migration/detector.d.ts.map +1 -0
- package/dist/lib/config-migration/detector.js +372 -0
- package/dist/lib/config-migration/detector.js.map +1 -0
- package/dist/lib/config-migration/detector.test.d.ts +5 -0
- package/dist/lib/config-migration/detector.test.d.ts.map +1 -0
- package/dist/lib/config-migration/detector.test.js +201 -0
- package/dist/lib/config-migration/detector.test.js.map +1 -0
- package/dist/lib/config-migration/index.d.ts +29 -0
- package/dist/lib/config-migration/index.d.ts.map +1 -0
- package/dist/lib/config-migration/index.js +33 -0
- package/dist/lib/config-migration/index.js.map +1 -0
- package/dist/lib/config-migration/reporter.d.ts +53 -0
- package/dist/lib/config-migration/reporter.d.ts.map +1 -0
- package/dist/lib/config-migration/reporter.js +257 -0
- package/dist/lib/config-migration/reporter.js.map +1 -0
- package/dist/lib/config-migration/reporter.test.d.ts +5 -0
- package/dist/lib/config-migration/reporter.test.d.ts.map +1 -0
- package/dist/lib/config-migration/reporter.test.js +305 -0
- package/dist/lib/config-migration/reporter.test.js.map +1 -0
- package/dist/lib/config-migration/runner.d.ts +46 -0
- package/dist/lib/config-migration/runner.d.ts.map +1 -0
- package/dist/lib/config-migration/runner.js +364 -0
- package/dist/lib/config-migration/runner.js.map +1 -0
- package/dist/lib/config-migration/runner.test.d.ts +5 -0
- package/dist/lib/config-migration/runner.test.d.ts.map +1 -0
- package/dist/lib/config-migration/runner.test.js +235 -0
- package/dist/lib/config-migration/runner.test.js.map +1 -0
- package/dist/lib/config-migration/types.d.ts +120 -0
- package/dist/lib/config-migration/types.d.ts.map +1 -0
- package/dist/lib/config-migration/types.js +70 -0
- package/dist/lib/config-migration/types.js.map +1 -0
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config-validation.js +6 -0
- package/dist/lib/config-validation.js.map +1 -1
- package/dist/lib/config-validation.test.js +25 -0
- package/dist/lib/config-validation.test.js.map +1 -1
- package/dist/lib/config.d.ts +31 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +2 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +3 -15
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/constants.d.ts +12 -4
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +24 -5
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.js +88 -29
- package/dist/lib/constants.test.js.map +1 -1
- package/dist/lib/deprecation.d.ts +18 -0
- package/dist/lib/deprecation.d.ts.map +1 -0
- package/dist/lib/deprecation.js +28 -0
- package/dist/lib/deprecation.js.map +1 -0
- package/dist/lib/deprecation.test.d.ts +2 -0
- package/dist/lib/deprecation.test.d.ts.map +1 -0
- package/dist/lib/deprecation.test.js +71 -0
- package/dist/lib/deprecation.test.js.map +1 -0
- package/dist/lib/hooks/confirmation.d.ts +49 -0
- package/dist/lib/hooks/confirmation.d.ts.map +1 -0
- package/dist/lib/hooks/confirmation.js +147 -0
- package/dist/lib/hooks/confirmation.js.map +1 -0
- package/dist/lib/hooks/confirmation.test.d.ts +7 -0
- package/dist/lib/hooks/confirmation.test.d.ts.map +1 -0
- package/dist/lib/hooks/confirmation.test.js +300 -0
- package/dist/lib/hooks/confirmation.test.js.map +1 -0
- package/dist/lib/hooks/executor.d.ts +16 -1
- package/dist/lib/hooks/executor.d.ts.map +1 -1
- package/dist/lib/hooks/executor.js +53 -4
- package/dist/lib/hooks/executor.js.map +1 -1
- package/dist/lib/hooks/index.d.ts +4 -2
- package/dist/lib/hooks/index.d.ts.map +1 -1
- package/dist/lib/hooks/index.js +3 -2
- package/dist/lib/hooks/index.js.map +1 -1
- package/dist/lib/hooks/types.d.ts +16 -0
- package/dist/lib/hooks/types.d.ts.map +1 -1
- package/dist/lib/hooks/types.js +12 -0
- package/dist/lib/hooks/types.js.map +1 -1
- package/dist/lib/logger.d.ts +40 -155
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +349 -420
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/logger.test.d.ts +10 -1
- package/dist/lib/logger.test.d.ts.map +1 -1
- package/dist/lib/logger.test.js +658 -258
- package/dist/lib/logger.test.js.map +1 -1
- package/dist/lib/lswt/action-executors.d.ts +2 -0
- package/dist/lib/lswt/action-executors.d.ts.map +1 -1
- package/dist/lib/lswt/action-executors.js +4 -3
- package/dist/lib/lswt/action-executors.js.map +1 -1
- package/dist/lib/lswt/action-executors.test.js +7 -0
- package/dist/lib/lswt/action-executors.test.js.map +1 -1
- package/dist/lib/lswt/args.d.ts.map +1 -1
- package/dist/lib/lswt/args.js +15 -1
- package/dist/lib/lswt/args.js.map +1 -1
- package/dist/lib/lswt/environment.d.ts +21 -2
- package/dist/lib/lswt/environment.d.ts.map +1 -1
- package/dist/lib/lswt/environment.js +73 -32
- package/dist/lib/lswt/environment.js.map +1 -1
- package/dist/lib/lswt/environment.test.js +79 -1
- package/dist/lib/lswt/environment.test.js.map +1 -1
- package/dist/lib/lswt/index.d.ts +1 -0
- package/dist/lib/lswt/index.d.ts.map +1 -1
- package/dist/lib/lswt/index.js +2 -0
- package/dist/lib/lswt/index.js.map +1 -1
- package/dist/lib/lswt/table.d.ts +15 -0
- package/dist/lib/lswt/table.d.ts.map +1 -0
- package/dist/lib/lswt/table.js +61 -0
- package/dist/lib/lswt/table.js.map +1 -0
- package/dist/lib/lswt/table.test.d.ts +5 -0
- package/dist/lib/lswt/table.test.d.ts.map +1 -0
- package/dist/lib/lswt/table.test.js +262 -0
- package/dist/lib/lswt/table.test.js.map +1 -0
- package/dist/lib/lswt/types.d.ts +4 -0
- package/dist/lib/lswt/types.d.ts.map +1 -1
- package/dist/lib/lswt/worktree-info.d.ts.map +1 -1
- package/dist/lib/lswt/worktree-info.js +1 -6
- package/dist/lib/lswt/worktree-info.js.map +1 -1
- package/dist/lib/lswt/worktree-info.test.js +5 -17
- package/dist/lib/lswt/worktree-info.test.js.map +1 -1
- package/dist/lib/newpr/args.d.ts.map +1 -1
- package/dist/lib/newpr/args.js +36 -1
- package/dist/lib/newpr/args.js.map +1 -1
- package/dist/lib/newpr/hook-runner.d.ts +11 -0
- package/dist/lib/newpr/hook-runner.d.ts.map +1 -1
- package/dist/lib/newpr/hook-runner.js +49 -1
- package/dist/lib/newpr/hook-runner.js.map +1 -1
- package/dist/lib/newpr/hook-runner.test.js +121 -0
- package/dist/lib/newpr/hook-runner.test.js.map +1 -1
- package/dist/lib/newpr/plan-generator.d.ts +121 -0
- package/dist/lib/newpr/plan-generator.d.ts.map +1 -0
- package/dist/lib/newpr/plan-generator.js +185 -0
- package/dist/lib/newpr/plan-generator.js.map +1 -0
- package/dist/lib/newpr/plan-generator.test.d.ts +7 -0
- package/dist/lib/newpr/plan-generator.test.d.ts.map +1 -0
- package/dist/lib/newpr/plan-generator.test.js +387 -0
- package/dist/lib/newpr/plan-generator.test.js.map +1 -0
- package/dist/lib/newpr/types.d.ts +12 -0
- package/dist/lib/newpr/types.d.ts.map +1 -1
- package/dist/lib/prs/actions.d.ts +5 -1
- package/dist/lib/prs/actions.d.ts.map +1 -1
- package/dist/lib/prs/actions.js +12 -10
- package/dist/lib/prs/actions.js.map +1 -1
- package/dist/lib/prs/actions.test.js +48 -5
- package/dist/lib/prs/actions.test.js.map +1 -1
- package/dist/lib/prs/command.d.ts +21 -0
- package/dist/lib/prs/command.d.ts.map +1 -0
- package/dist/lib/prs/command.js +175 -0
- package/dist/lib/prs/command.js.map +1 -0
- package/dist/lib/prs/command.test.d.ts +11 -0
- package/dist/lib/prs/command.test.d.ts.map +1 -0
- package/dist/lib/prs/command.test.js +409 -0
- package/dist/lib/prs/command.test.js.map +1 -0
- package/dist/lib/prs/interactive.d.ts.map +1 -1
- package/dist/lib/prs/interactive.js +15 -2
- package/dist/lib/prs/interactive.js.map +1 -1
- package/dist/lib/prs/interactive.test.js +153 -0
- package/dist/lib/prs/interactive.test.js.map +1 -1
- package/dist/lib/prs/types.d.ts +15 -0
- package/dist/lib/prs/types.d.ts.map +1 -1
- package/dist/lib/ui/error.d.ts +31 -0
- package/dist/lib/ui/error.d.ts.map +1 -0
- package/dist/lib/ui/error.js +47 -0
- package/dist/lib/ui/error.js.map +1 -0
- package/dist/lib/ui/error.test.d.ts +2 -0
- package/dist/lib/ui/error.test.d.ts.map +1 -0
- package/dist/lib/ui/error.test.js +143 -0
- package/dist/lib/ui/error.test.js.map +1 -0
- package/dist/lib/ui/index.d.ts +15 -0
- package/dist/lib/ui/index.d.ts.map +1 -0
- package/dist/lib/ui/index.js +19 -0
- package/dist/lib/ui/index.js.map +1 -0
- package/dist/lib/ui/output.d.ts +18 -0
- package/dist/lib/ui/output.d.ts.map +1 -0
- package/dist/lib/ui/output.js +31 -0
- package/dist/lib/ui/output.js.map +1 -0
- package/dist/lib/ui/output.test.d.ts +2 -0
- package/dist/lib/ui/output.test.d.ts.map +1 -0
- package/dist/lib/ui/output.test.js +59 -0
- package/dist/lib/ui/output.test.js.map +1 -0
- package/dist/lib/ui/spinner.d.ts +10 -0
- package/dist/lib/ui/spinner.d.ts.map +1 -0
- package/dist/lib/ui/spinner.js +10 -0
- package/dist/lib/ui/spinner.js.map +1 -0
- package/dist/lib/ui/status.d.ts +65 -0
- package/dist/lib/ui/status.d.ts.map +1 -0
- package/dist/lib/ui/status.js +100 -0
- package/dist/lib/ui/status.js.map +1 -0
- package/dist/lib/ui/status.test.d.ts +2 -0
- package/dist/lib/ui/status.test.d.ts.map +1 -0
- package/dist/lib/ui/status.test.js +158 -0
- package/dist/lib/ui/status.test.js.map +1 -0
- package/dist/lib/ui/table.d.ts +39 -0
- package/dist/lib/ui/table.d.ts.map +1 -0
- package/dist/lib/ui/table.js +45 -0
- package/dist/lib/ui/table.js.map +1 -0
- package/dist/lib/ui/table.test.d.ts +2 -0
- package/dist/lib/ui/table.test.d.ts.map +1 -0
- package/dist/lib/ui/table.test.js +115 -0
- package/dist/lib/ui/table.test.js.map +1 -0
- package/dist/lib/ui/theme.d.ts +34 -0
- package/dist/lib/ui/theme.d.ts.map +1 -0
- package/dist/lib/ui/theme.js +37 -0
- package/dist/lib/ui/theme.js.map +1 -0
- package/dist/lib/ui/theme.test.d.ts +2 -0
- package/dist/lib/ui/theme.test.d.ts.map +1 -0
- package/dist/lib/ui/theme.test.js +76 -0
- package/dist/lib/ui/theme.test.js.map +1 -0
- package/dist/lib/wtconfig/environment.d.ts +18 -1
- package/dist/lib/wtconfig/environment.d.ts.map +1 -1
- package/dist/lib/wtconfig/environment.js +60 -24
- package/dist/lib/wtconfig/environment.js.map +1 -1
- package/dist/lib/wtconfig/environment.test.js +45 -1
- package/dist/lib/wtconfig/environment.test.js.map +1 -1
- package/dist/lib/wtlink/config-manifest.test.js +26 -0
- package/dist/lib/wtlink/config-manifest.test.js.map +1 -1
- package/dist/lib/wtlink/link-configs.js +7 -7
- package/dist/lib/wtlink/link-configs.js.map +1 -1
- package/dist/lib/wtlink/validate-manifest.d.ts.map +1 -1
- package/dist/lib/wtlink/validate-manifest.js +5 -5
- package/dist/lib/wtlink/validate-manifest.js.map +1 -1
- package/dist/lib/wtstate/args.d.ts.map +1 -1
- package/dist/lib/wtstate/args.js +2 -0
- package/dist/lib/wtstate/args.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +264 -44
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/server.test.js +111 -0
- package/dist/mcp/server.test.js.map +1 -1
- package/package.json +3 -1
- package/schemas/worktreerc.schema.json +23 -0
package/dist/lib/logger.test.js
CHANGED
|
@@ -1,292 +1,692 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Comprehensive tests for logger.ts (consola-based)
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - parseLogLevel mapping
|
|
6
|
+
* - LogLevel compatibility export
|
|
7
|
+
* - initializeLogger level resolution (flag precedence)
|
|
8
|
+
* - DEBUG=newpr deprecation path
|
|
9
|
+
* - AuditFileReporter (write, JSONL, directory creation, rotation)
|
|
10
|
+
* - ConditionalStderrReporter (verbose/non-verbose conditional output)
|
|
11
|
+
* - Process exit handler (synchronous audit summary)
|
|
3
12
|
*/
|
|
4
13
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
5
14
|
import fs from 'fs';
|
|
6
15
|
import path from 'path';
|
|
7
16
|
import os from 'os';
|
|
8
|
-
import { parseLogLevel, initializeLogger, logger, LogLevel } from './logger.js';
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
import { parseLogLevel, initializeLogger, logger, LogLevel, setAuditContext, _resetForTesting, } from './logger.js';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Test-level helpers
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/** Saved env vars to restore after each test */
|
|
22
|
+
let savedEnv;
|
|
23
|
+
/** Per-test temp directory */
|
|
24
|
+
let tempDir;
|
|
25
|
+
function createTempDir() {
|
|
26
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'logger-test-'));
|
|
27
|
+
}
|
|
28
|
+
function cleanupTempDir(dir) {
|
|
29
|
+
if (dir && fs.existsSync(dir)) {
|
|
30
|
+
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wait for an audit log file to have non-empty content.
|
|
35
|
+
* Polls every 50ms up to the given timeout (default 2s).
|
|
36
|
+
* WriteStream flush timing varies across platforms/Node versions.
|
|
37
|
+
*/
|
|
38
|
+
async function waitForAuditContent(filePath, timeoutMs = 2000) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
while (Date.now() - start < timeoutMs) {
|
|
41
|
+
try {
|
|
42
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
43
|
+
if (content.length > 0)
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// file may not exist yet
|
|
48
|
+
}
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
50
|
+
}
|
|
51
|
+
// Final attempt — return whatever is there (may be empty, test will fail with a clear message)
|
|
52
|
+
try {
|
|
53
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// parseLogLevel
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
describe('parseLogLevel', () => {
|
|
63
|
+
it('parses "debug" to 4', () => {
|
|
64
|
+
expect(parseLogLevel('debug')).toBe(4);
|
|
65
|
+
});
|
|
66
|
+
it('parses "info" to 3', () => {
|
|
67
|
+
expect(parseLogLevel('info')).toBe(3);
|
|
68
|
+
});
|
|
69
|
+
it('parses "warn" to 1', () => {
|
|
70
|
+
expect(parseLogLevel('warn')).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
it('parses "warning" to 1', () => {
|
|
73
|
+
expect(parseLogLevel('warning')).toBe(1);
|
|
74
|
+
});
|
|
75
|
+
it('parses "error" to 0', () => {
|
|
76
|
+
expect(parseLogLevel('error')).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
it('parses "silent" to -999', () => {
|
|
79
|
+
expect(parseLogLevel('silent')).toBe(-999);
|
|
80
|
+
});
|
|
81
|
+
it('parses "trace" to 5', () => {
|
|
82
|
+
expect(parseLogLevel('trace')).toBe(5);
|
|
83
|
+
});
|
|
84
|
+
it('parses "verbose" to 4 (alias for debug)', () => {
|
|
85
|
+
expect(parseLogLevel('verbose')).toBe(4);
|
|
86
|
+
});
|
|
87
|
+
it('is case insensitive — "DEBUG" returns 4', () => {
|
|
88
|
+
expect(parseLogLevel('DEBUG')).toBe(4);
|
|
89
|
+
});
|
|
90
|
+
it('is case insensitive — mixed case "Error" returns 0', () => {
|
|
91
|
+
expect(parseLogLevel('Error')).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
it('returns undefined for "unknown"', () => {
|
|
94
|
+
expect(parseLogLevel('unknown')).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
it('returns undefined for empty string', () => {
|
|
97
|
+
expect(parseLogLevel('')).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
it('returns undefined for numeric strings', () => {
|
|
100
|
+
expect(parseLogLevel('99')).toBeUndefined();
|
|
101
|
+
expect(parseLogLevel('3')).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
it('trims whitespace', () => {
|
|
104
|
+
expect(parseLogLevel(' info ')).toBe(3);
|
|
105
|
+
expect(parseLogLevel('\tdebug\n')).toBe(4);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// LogLevel compatibility export
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
describe('LogLevel compatibility export', () => {
|
|
112
|
+
it('LogLevel.SILENT exists and equals -999', () => {
|
|
113
|
+
expect(LogLevel.SILENT).toBe(-999);
|
|
114
|
+
});
|
|
115
|
+
it('LogLevel.ERROR equals 0', () => {
|
|
116
|
+
expect(LogLevel.ERROR).toBe(0);
|
|
117
|
+
});
|
|
118
|
+
it('LogLevel.WARN equals 1', () => {
|
|
119
|
+
expect(LogLevel.WARN).toBe(1);
|
|
120
|
+
});
|
|
121
|
+
it('LogLevel.INFO equals 3', () => {
|
|
122
|
+
expect(LogLevel.INFO).toBe(3);
|
|
123
|
+
});
|
|
124
|
+
it('LogLevel.DEBUG equals 4', () => {
|
|
125
|
+
expect(LogLevel.DEBUG).toBe(4);
|
|
126
|
+
});
|
|
127
|
+
it('LogLevel.TRACE equals 5', () => {
|
|
128
|
+
expect(LogLevel.TRACE).toBe(5);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Logger singleton
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
describe('Logger singleton', () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
_resetForTesting();
|
|
137
|
+
});
|
|
138
|
+
it('returns same instance on repeated access', () => {
|
|
139
|
+
const instance1 = logger;
|
|
140
|
+
const instance2 = logger;
|
|
141
|
+
expect(instance1).toBe(instance2);
|
|
142
|
+
});
|
|
143
|
+
it('defaults to level 3 (INFO) after reset', () => {
|
|
144
|
+
expect(logger.level).toBe(3);
|
|
145
|
+
});
|
|
146
|
+
it('has standard logging methods', () => {
|
|
147
|
+
expect(typeof logger.error).toBe('function');
|
|
148
|
+
expect(typeof logger.warn).toBe('function');
|
|
149
|
+
expect(typeof logger.info).toBe('function');
|
|
150
|
+
expect(typeof logger.debug).toBe('function');
|
|
151
|
+
expect(typeof logger.trace).toBe('function');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// initializeLogger — level resolution
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
describe('initializeLogger level resolution', () => {
|
|
13
158
|
beforeEach(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
159
|
+
_resetForTesting();
|
|
160
|
+
savedEnv = {
|
|
161
|
+
GWT_LOG_LEVEL: process.env.GWT_LOG_LEVEL,
|
|
162
|
+
DEBUG: process.env.DEBUG,
|
|
163
|
+
NO_COLOR: process.env.NO_COLOR,
|
|
164
|
+
};
|
|
17
165
|
delete process.env.GWT_LOG_LEVEL;
|
|
18
|
-
delete process.env.
|
|
19
|
-
// Create temp dir for file tests
|
|
20
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'logger-test-'));
|
|
166
|
+
delete process.env.DEBUG;
|
|
21
167
|
});
|
|
22
168
|
afterEach(() => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
169
|
+
_resetForTesting();
|
|
170
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
171
|
+
if (value === undefined) {
|
|
172
|
+
delete process.env[key];
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
process.env[key] = value;
|
|
176
|
+
}
|
|
28
177
|
}
|
|
29
178
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
expect(parseLogLevel('warn')).toBe(LogLevel.WARN);
|
|
35
|
-
expect(parseLogLevel('warning')).toBe(LogLevel.WARN);
|
|
36
|
-
expect(parseLogLevel('info')).toBe(LogLevel.INFO);
|
|
37
|
-
expect(parseLogLevel('debug')).toBe(LogLevel.DEBUG);
|
|
38
|
-
expect(parseLogLevel('trace')).toBe(LogLevel.TRACE);
|
|
39
|
-
expect(parseLogLevel('verbose')).toBe(LogLevel.DEBUG);
|
|
40
|
-
});
|
|
41
|
-
it('is case insensitive', () => {
|
|
42
|
-
expect(parseLogLevel('SILENT')).toBe(LogLevel.SILENT);
|
|
43
|
-
expect(parseLogLevel('Error')).toBe(LogLevel.ERROR);
|
|
44
|
-
expect(parseLogLevel('DEBUG')).toBe(LogLevel.DEBUG);
|
|
45
|
-
});
|
|
46
|
-
it('parses numeric log levels', () => {
|
|
47
|
-
expect(parseLogLevel('0')).toBe(LogLevel.SILENT);
|
|
48
|
-
expect(parseLogLevel('1')).toBe(LogLevel.ERROR);
|
|
49
|
-
expect(parseLogLevel('2')).toBe(LogLevel.WARN);
|
|
50
|
-
expect(parseLogLevel('3')).toBe(LogLevel.INFO);
|
|
51
|
-
expect(parseLogLevel('4')).toBe(LogLevel.DEBUG);
|
|
52
|
-
expect(parseLogLevel('5')).toBe(LogLevel.TRACE);
|
|
53
|
-
});
|
|
54
|
-
it('returns undefined for invalid levels', () => {
|
|
55
|
-
expect(parseLogLevel('invalid')).toBeUndefined();
|
|
56
|
-
expect(parseLogLevel('99')).toBeUndefined();
|
|
57
|
-
expect(parseLogLevel('')).toBeUndefined();
|
|
58
|
-
});
|
|
59
|
-
it('trims whitespace', () => {
|
|
60
|
-
expect(parseLogLevel(' info ')).toBe(LogLevel.INFO);
|
|
61
|
-
});
|
|
179
|
+
it('quiet flag sets level to 0 (error only) regardless of env', () => {
|
|
180
|
+
process.env.GWT_LOG_LEVEL = 'debug';
|
|
181
|
+
initializeLogger({ quiet: true });
|
|
182
|
+
expect(logger.level).toBe(0);
|
|
62
183
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const instance2 = logger;
|
|
67
|
-
expect(instance1).toBe(instance2);
|
|
68
|
-
});
|
|
69
|
-
it('defaults to INFO level', () => {
|
|
70
|
-
logger.initialize({});
|
|
71
|
-
expect(logger.getLevel()).toBe(LogLevel.INFO);
|
|
72
|
-
});
|
|
73
|
-
it('sets level from config', () => {
|
|
74
|
-
logger.initialize({ level: LogLevel.DEBUG });
|
|
75
|
-
expect(logger.getLevel()).toBe(LogLevel.DEBUG);
|
|
76
|
-
});
|
|
77
|
-
it('respects environment variable', () => {
|
|
78
|
-
process.env.GWT_LOG_LEVEL = 'debug';
|
|
79
|
-
logger.initialize({});
|
|
80
|
-
expect(logger.getLevel()).toBe(LogLevel.DEBUG);
|
|
81
|
-
});
|
|
82
|
-
it('prefers config over environment', () => {
|
|
83
|
-
process.env.GWT_LOG_LEVEL = 'debug';
|
|
84
|
-
logger.initialize({ level: LogLevel.ERROR });
|
|
85
|
-
expect(logger.getLevel()).toBe(LogLevel.ERROR);
|
|
86
|
-
});
|
|
184
|
+
it('verbose flag sets level to 4 (debug)', () => {
|
|
185
|
+
initializeLogger({ verbose: true });
|
|
186
|
+
expect(logger.level).toBe(4);
|
|
87
187
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
expect(logger.isLevelEnabled(LogLevel.WARN)).toBe(true);
|
|
93
|
-
expect(logger.isLevelEnabled(LogLevel.INFO)).toBe(false);
|
|
94
|
-
expect(logger.isLevelEnabled(LogLevel.DEBUG)).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
it('isDebug returns true for DEBUG and higher', () => {
|
|
97
|
-
logger.initialize({ level: LogLevel.INFO });
|
|
98
|
-
expect(logger.isDebug()).toBe(false);
|
|
99
|
-
logger.setLevel(LogLevel.DEBUG);
|
|
100
|
-
expect(logger.isDebug()).toBe(true);
|
|
101
|
-
logger.setLevel(LogLevel.TRACE);
|
|
102
|
-
expect(logger.isDebug()).toBe(true);
|
|
103
|
-
});
|
|
104
|
-
it('isTrace returns true only for TRACE', () => {
|
|
105
|
-
logger.initialize({ level: LogLevel.DEBUG });
|
|
106
|
-
expect(logger.isTrace()).toBe(false);
|
|
107
|
-
logger.setLevel(LogLevel.TRACE);
|
|
108
|
-
expect(logger.isTrace()).toBe(true);
|
|
109
|
-
});
|
|
188
|
+
it('GWT_LOG_LEVEL=debug with no flags sets level to 4', () => {
|
|
189
|
+
process.env.GWT_LOG_LEVEL = 'debug';
|
|
190
|
+
initializeLogger({});
|
|
191
|
+
expect(logger.level).toBe(4);
|
|
110
192
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
beforeEach(() => {
|
|
116
|
-
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
117
|
-
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
118
|
-
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
119
|
-
});
|
|
120
|
-
afterEach(() => {
|
|
121
|
-
consoleLogSpy.mockRestore();
|
|
122
|
-
consoleWarnSpy.mockRestore();
|
|
123
|
-
consoleErrorSpy.mockRestore();
|
|
124
|
-
});
|
|
125
|
-
it('info logs at INFO level', () => {
|
|
126
|
-
logger.initialize({ level: LogLevel.INFO, timestamps: false, colors: false });
|
|
127
|
-
logger.info('test message');
|
|
128
|
-
expect(consoleLogSpy).toHaveBeenCalled();
|
|
129
|
-
expect(consoleLogSpy.mock.calls[0][0]).toContain('test message');
|
|
130
|
-
});
|
|
131
|
-
it('debug logs at DEBUG level', () => {
|
|
132
|
-
logger.initialize({ level: LogLevel.DEBUG, timestamps: false, colors: false });
|
|
133
|
-
logger.debug('debug message');
|
|
134
|
-
expect(consoleLogSpy).toHaveBeenCalled();
|
|
135
|
-
expect(consoleLogSpy.mock.calls[0][0]).toContain('debug message');
|
|
136
|
-
});
|
|
137
|
-
it('debug does not log at INFO level', () => {
|
|
138
|
-
logger.initialize({ level: LogLevel.INFO, timestamps: false, colors: false });
|
|
139
|
-
logger.debug('debug message');
|
|
140
|
-
expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('debug message'));
|
|
141
|
-
});
|
|
142
|
-
it('warn uses console.warn', () => {
|
|
143
|
-
logger.initialize({ level: LogLevel.WARN, timestamps: false, colors: false });
|
|
144
|
-
logger.warn('warning message');
|
|
145
|
-
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
146
|
-
expect(consoleWarnSpy.mock.calls[0][0]).toContain('warning message');
|
|
147
|
-
});
|
|
148
|
-
it('error uses console.error', () => {
|
|
149
|
-
logger.initialize({ level: LogLevel.ERROR, timestamps: false, colors: false });
|
|
150
|
-
logger.error('error message');
|
|
151
|
-
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
152
|
-
expect(consoleErrorSpy.mock.calls[0][0]).toContain('error message');
|
|
153
|
-
});
|
|
154
|
-
it('formats messages with placeholders', () => {
|
|
155
|
-
logger.initialize({ level: LogLevel.INFO, timestamps: false, colors: false });
|
|
156
|
-
// Test basic %s placeholder
|
|
157
|
-
logger.info('Hello %s', 'world');
|
|
158
|
-
expect(consoleLogSpy).toHaveBeenCalled();
|
|
159
|
-
expect(consoleLogSpy.mock.calls[0][0]).toContain('Hello world');
|
|
160
|
-
});
|
|
161
|
-
it('appends extra arguments', () => {
|
|
162
|
-
logger.initialize({ level: LogLevel.INFO, timestamps: false, colors: false });
|
|
163
|
-
logger.info('Message', 'extra1', 'extra2');
|
|
164
|
-
expect(consoleLogSpy).toHaveBeenCalled();
|
|
165
|
-
expect(consoleLogSpy.mock.calls[0][0]).toContain('extra1');
|
|
166
|
-
expect(consoleLogSpy.mock.calls[0][0]).toContain('extra2');
|
|
167
|
-
});
|
|
193
|
+
it('GWT_LOG_LEVEL=warn with no flags sets level to 1', () => {
|
|
194
|
+
process.env.GWT_LOG_LEVEL = 'warn';
|
|
195
|
+
initializeLogger({});
|
|
196
|
+
expect(logger.level).toBe(1);
|
|
168
197
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
level: LogLevel.INFO,
|
|
174
|
-
logFile,
|
|
175
|
-
consoleOutput: false,
|
|
176
|
-
});
|
|
177
|
-
logger.info('file log message');
|
|
178
|
-
// Wait for write to complete
|
|
179
|
-
await logger.close();
|
|
180
|
-
const content = fs.readFileSync(logFile, 'utf8');
|
|
181
|
-
expect(content).toContain('file log message');
|
|
182
|
-
});
|
|
183
|
-
it('creates log file directory if needed', async () => {
|
|
184
|
-
const logFile = path.join(tempDir, 'subdir', 'test.log');
|
|
185
|
-
logger.initialize({
|
|
186
|
-
level: LogLevel.INFO,
|
|
187
|
-
logFile,
|
|
188
|
-
consoleOutput: false,
|
|
189
|
-
});
|
|
190
|
-
logger.info('nested log message');
|
|
191
|
-
await logger.close();
|
|
192
|
-
expect(fs.existsSync(logFile)).toBe(true);
|
|
193
|
-
});
|
|
194
|
-
it('writes JSON format to file', async () => {
|
|
195
|
-
const logFile = path.join(tempDir, 'json.log');
|
|
196
|
-
logger.initialize({
|
|
197
|
-
level: LogLevel.INFO,
|
|
198
|
-
logFile,
|
|
199
|
-
consoleOutput: false,
|
|
200
|
-
});
|
|
201
|
-
logger.info('json message');
|
|
202
|
-
await logger.close();
|
|
203
|
-
const content = fs.readFileSync(logFile, 'utf8');
|
|
204
|
-
const entry = JSON.parse(content.trim());
|
|
205
|
-
expect(entry.level).toBe('INFO');
|
|
206
|
-
expect(entry.message).toBe('json message');
|
|
207
|
-
expect(entry.timestamp).toBeDefined();
|
|
208
|
-
});
|
|
198
|
+
it('quiet flag overrides GWT_LOG_LEVEL=debug (CLI flag wins)', () => {
|
|
199
|
+
process.env.GWT_LOG_LEVEL = 'debug';
|
|
200
|
+
initializeLogger({ quiet: true });
|
|
201
|
+
expect(logger.level).toBe(0);
|
|
209
202
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const err = new Error('Test error');
|
|
214
|
-
err.stack = 'Error: Test error\n at test.ts:1:1';
|
|
215
|
-
// The errorWithStack method combines the optional message with the error message
|
|
216
|
-
const msg = `Something went wrong: ${err.message}`;
|
|
217
|
-
expect(msg).toBe('Something went wrong: Test error');
|
|
218
|
-
});
|
|
219
|
-
it('errorWithStack method exists and is callable', () => {
|
|
220
|
-
expect(typeof logger.errorWithStack).toBe('function');
|
|
221
|
-
const err = new Error('Test');
|
|
222
|
-
// Should not throw
|
|
223
|
-
expect(() => {
|
|
224
|
-
logger.setLevel(LogLevel.SILENT); // Suppress output
|
|
225
|
-
logger.errorWithStack(err);
|
|
226
|
-
}).not.toThrow();
|
|
227
|
-
});
|
|
203
|
+
it('no flags and no env var defaults to level 3 (INFO)', () => {
|
|
204
|
+
initializeLogger({});
|
|
205
|
+
expect(logger.level).toBe(3);
|
|
228
206
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
207
|
+
it('quiet flag takes priority over verbose when both are set', () => {
|
|
208
|
+
initializeLogger({ quiet: true, verbose: true });
|
|
209
|
+
expect(logger.level).toBe(0);
|
|
210
|
+
});
|
|
211
|
+
it('GWT_LOG_LEVEL with invalid value falls back to INFO', () => {
|
|
212
|
+
process.env.GWT_LOG_LEVEL = 'bananas';
|
|
213
|
+
initializeLogger({});
|
|
214
|
+
expect(logger.level).toBe(3);
|
|
215
|
+
});
|
|
216
|
+
it('verbose flag overrides GWT_LOG_LEVEL=warn (CLI flag wins)', () => {
|
|
217
|
+
process.env.GWT_LOG_LEVEL = 'warn';
|
|
218
|
+
initializeLogger({ verbose: true });
|
|
219
|
+
expect(logger.level).toBe(4);
|
|
220
|
+
});
|
|
221
|
+
it('sets reporters when called', () => {
|
|
222
|
+
initializeLogger({});
|
|
223
|
+
expect(logger.options.reporters).toBeDefined();
|
|
224
|
+
expect(logger.options.reporters.length).toBeGreaterThan(0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// DEBUG=newpr deprecation path
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
describe('DEBUG=newpr deprecation path', () => {
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
|
+
let stderrSpy;
|
|
233
|
+
beforeEach(() => {
|
|
234
|
+
_resetForTesting();
|
|
235
|
+
savedEnv = {
|
|
236
|
+
GWT_LOG_LEVEL: process.env.GWT_LOG_LEVEL,
|
|
237
|
+
DEBUG: process.env.DEBUG,
|
|
238
|
+
};
|
|
239
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
240
|
+
delete process.env.DEBUG;
|
|
241
|
+
stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
242
|
+
});
|
|
243
|
+
afterEach(() => {
|
|
244
|
+
stderrSpy.mockRestore();
|
|
245
|
+
_resetForTesting();
|
|
246
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
247
|
+
if (value === undefined) {
|
|
248
|
+
delete process.env[key];
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
process.env[key] = value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
it('DEBUG=newpr sets level to 4 (debug) and prints deprecation warning', () => {
|
|
256
|
+
process.env.DEBUG = 'newpr';
|
|
257
|
+
initializeLogger({});
|
|
258
|
+
expect(logger.level).toBe(4);
|
|
259
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('WARNING: DEBUG=newpr is deprecated'));
|
|
260
|
+
});
|
|
261
|
+
it('DEBUG=newpr deprecation warning fires exactly once across multiple calls', () => {
|
|
262
|
+
process.env.DEBUG = 'newpr';
|
|
263
|
+
initializeLogger({});
|
|
264
|
+
initializeLogger({});
|
|
265
|
+
const deprecationCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('deprecated'));
|
|
266
|
+
expect(deprecationCalls).toHaveLength(1);
|
|
267
|
+
});
|
|
268
|
+
it('DEBUG=* activates debug level and prints deprecation warning', () => {
|
|
269
|
+
process.env.DEBUG = '*';
|
|
270
|
+
initializeLogger({});
|
|
271
|
+
expect(logger.level).toBe(4);
|
|
272
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('deprecated'));
|
|
273
|
+
});
|
|
274
|
+
it('DEBUG=1 activates debug level and prints deprecation warning', () => {
|
|
275
|
+
process.env.DEBUG = '1';
|
|
276
|
+
initializeLogger({});
|
|
277
|
+
expect(logger.level).toBe(4);
|
|
278
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('deprecated'));
|
|
279
|
+
});
|
|
280
|
+
it('DEBUG=something_else does NOT activate debug — level stays at default', () => {
|
|
281
|
+
process.env.DEBUG = 'something_else';
|
|
282
|
+
initializeLogger({});
|
|
283
|
+
expect(logger.level).toBe(3);
|
|
284
|
+
const deprecationCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('deprecated'));
|
|
285
|
+
expect(deprecationCalls).toHaveLength(0);
|
|
286
|
+
});
|
|
287
|
+
it('GWT_LOG_LEVEL takes priority over DEBUG=newpr', () => {
|
|
288
|
+
process.env.GWT_LOG_LEVEL = 'warn';
|
|
289
|
+
process.env.DEBUG = 'newpr';
|
|
290
|
+
initializeLogger({});
|
|
291
|
+
expect(logger.level).toBe(1);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// _resetForTesting
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
describe('_resetForTesting', () => {
|
|
298
|
+
it('resets logger level to INFO (3)', () => {
|
|
299
|
+
logger.level = 0;
|
|
300
|
+
_resetForTesting();
|
|
301
|
+
expect(logger.level).toBe(3);
|
|
302
|
+
});
|
|
303
|
+
it('clears reporters', () => {
|
|
304
|
+
initializeLogger({ verbose: true });
|
|
305
|
+
_resetForTesting();
|
|
306
|
+
expect(logger.options.reporters).toEqual([]);
|
|
307
|
+
});
|
|
308
|
+
it('resets deprecation warning flag so it can fire again', () => {
|
|
309
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
310
|
+
const origDebug = process.env.DEBUG;
|
|
311
|
+
const origGwt = process.env.GWT_LOG_LEVEL;
|
|
312
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
313
|
+
process.env.DEBUG = 'newpr';
|
|
314
|
+
initializeLogger({});
|
|
315
|
+
_resetForTesting();
|
|
316
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
317
|
+
process.env.DEBUG = 'newpr';
|
|
318
|
+
initializeLogger({});
|
|
319
|
+
const deprecationCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('deprecated'));
|
|
320
|
+
expect(deprecationCalls).toHaveLength(2);
|
|
321
|
+
stderrSpy.mockRestore();
|
|
322
|
+
if (origDebug === undefined)
|
|
323
|
+
delete process.env.DEBUG;
|
|
324
|
+
else
|
|
325
|
+
process.env.DEBUG = origDebug;
|
|
326
|
+
if (origGwt === undefined)
|
|
327
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
328
|
+
else
|
|
329
|
+
process.env.GWT_LOG_LEVEL = origGwt;
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// AuditFileReporter
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
describe('AuditFileReporter', () => {
|
|
336
|
+
beforeEach(() => {
|
|
337
|
+
_resetForTesting();
|
|
338
|
+
tempDir = createTempDir();
|
|
339
|
+
savedEnv = {
|
|
340
|
+
GWT_LOG_LEVEL: process.env.GWT_LOG_LEVEL,
|
|
341
|
+
DEBUG: process.env.DEBUG,
|
|
342
|
+
};
|
|
343
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
344
|
+
delete process.env.DEBUG;
|
|
345
|
+
});
|
|
346
|
+
afterEach(() => {
|
|
347
|
+
_resetForTesting();
|
|
348
|
+
cleanupTempDir(tempDir);
|
|
349
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
350
|
+
if (value === undefined) {
|
|
351
|
+
delete process.env[key];
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
process.env[key] = value;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
it('writes a log entry to the audit log file', async () => {
|
|
359
|
+
const constantsMod = await import('./constants.js');
|
|
360
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
361
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
362
|
+
initializeLogger({ commandName: 'test-cmd' });
|
|
363
|
+
logger.info('test audit message');
|
|
364
|
+
const auditPath = path.join(tempDir, 'audit.log');
|
|
365
|
+
const content = await waitForAuditContent(auditPath);
|
|
366
|
+
expect(content).toContain('test audit message');
|
|
367
|
+
stderrSpy.mockRestore();
|
|
368
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
369
|
+
});
|
|
370
|
+
it('audit log entries contain timestamp, level, and message', async () => {
|
|
371
|
+
const constantsMod = await import('./constants.js');
|
|
372
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
373
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
374
|
+
initializeLogger({ commandName: 'test-cmd' });
|
|
375
|
+
logger.warn('something went wrong');
|
|
376
|
+
const auditPath = path.join(tempDir, 'audit.log');
|
|
377
|
+
const content = await waitForAuditContent(auditPath);
|
|
378
|
+
expect(content).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
|
|
379
|
+
expect(content).toMatch(/WARN/);
|
|
380
|
+
expect(content).toContain('something went wrong');
|
|
381
|
+
stderrSpy.mockRestore();
|
|
382
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
383
|
+
});
|
|
384
|
+
it('writes JSONL entries when json mode is active', async () => {
|
|
385
|
+
const constantsMod = await import('./constants.js');
|
|
386
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
387
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
388
|
+
initializeLogger({ json: true, commandName: 'test-cmd' });
|
|
389
|
+
logger.info('json test message');
|
|
390
|
+
const auditPath = path.join(tempDir, 'audit.log');
|
|
391
|
+
const content = (await waitForAuditContent(auditPath)).trim();
|
|
392
|
+
const lines = content.split('\n').filter((l) => l.trim().length > 0);
|
|
393
|
+
for (const line of lines) {
|
|
394
|
+
const parsed = JSON.parse(line);
|
|
395
|
+
expect(parsed).toHaveProperty('timestamp');
|
|
396
|
+
expect(parsed).toHaveProperty('level');
|
|
397
|
+
expect(parsed).toHaveProperty('message');
|
|
398
|
+
}
|
|
399
|
+
const hasMessage = lines.some((line) => {
|
|
400
|
+
const parsed = JSON.parse(line);
|
|
401
|
+
return parsed.message.includes('json test message');
|
|
252
402
|
});
|
|
403
|
+
expect(hasMessage).toBe(true);
|
|
404
|
+
stderrSpy.mockRestore();
|
|
405
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
253
406
|
});
|
|
254
|
-
|
|
255
|
-
|
|
407
|
+
it('creates audit directory automatically if it does not exist', async () => {
|
|
408
|
+
const nestedDir = path.join(tempDir, 'nested', 'subdir');
|
|
409
|
+
const constantsMod = await import('./constants.js');
|
|
410
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(nestedDir);
|
|
411
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
412
|
+
expect(fs.existsSync(nestedDir)).toBe(false);
|
|
413
|
+
initializeLogger({ commandName: 'test-cmd' });
|
|
414
|
+
expect(fs.existsSync(nestedDir)).toBe(true);
|
|
415
|
+
stderrSpy.mockRestore();
|
|
416
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Rotation
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
describe('Audit log rotation', () => {
|
|
423
|
+
beforeEach(() => {
|
|
424
|
+
_resetForTesting();
|
|
425
|
+
tempDir = createTempDir();
|
|
426
|
+
savedEnv = {
|
|
427
|
+
GWT_LOG_LEVEL: process.env.GWT_LOG_LEVEL,
|
|
428
|
+
DEBUG: process.env.DEBUG,
|
|
429
|
+
};
|
|
430
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
431
|
+
delete process.env.DEBUG;
|
|
432
|
+
});
|
|
433
|
+
afterEach(() => {
|
|
434
|
+
_resetForTesting();
|
|
435
|
+
cleanupTempDir(tempDir);
|
|
436
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
437
|
+
if (value === undefined) {
|
|
438
|
+
delete process.env[key];
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
process.env[key] = value;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
it('rotates a file larger than 10MB to audit.log.1', async () => {
|
|
446
|
+
const constantsMod = await import('./constants.js');
|
|
447
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
448
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
449
|
+
const auditPath = path.join(tempDir, 'audit.log');
|
|
450
|
+
const bigContent = 'x'.repeat(10 * 1024 * 1024 + 100);
|
|
451
|
+
fs.writeFileSync(auditPath, bigContent);
|
|
452
|
+
initializeLogger({ commandName: 'test-cmd' });
|
|
453
|
+
expect(fs.existsSync(path.join(tempDir, 'audit.log.1'))).toBe(true);
|
|
454
|
+
const rotatedContent = fs.readFileSync(path.join(tempDir, 'audit.log.1'), 'utf-8');
|
|
455
|
+
expect(rotatedContent).toBe(bigContent);
|
|
456
|
+
logger.info('post-rotation entry');
|
|
457
|
+
await waitForAuditContent(auditPath);
|
|
458
|
+
expect(fs.existsSync(auditPath)).toBe(true);
|
|
459
|
+
const newSize = fs.statSync(auditPath).size;
|
|
460
|
+
expect(newSize).toBeLessThan(10 * 1024 * 1024);
|
|
461
|
+
stderrSpy.mockRestore();
|
|
462
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
463
|
+
});
|
|
464
|
+
it('shifts existing rotated files: .1 becomes .2, original becomes .1, oldest deleted', async () => {
|
|
465
|
+
const constantsMod = await import('./constants.js');
|
|
466
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
467
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
468
|
+
const auditPath = path.join(tempDir, 'audit.log');
|
|
469
|
+
const bigContent = 'x'.repeat(10 * 1024 * 1024 + 100);
|
|
470
|
+
fs.writeFileSync(auditPath, bigContent);
|
|
471
|
+
fs.writeFileSync(auditPath + '.1', 'rotated-1-content');
|
|
472
|
+
fs.writeFileSync(auditPath + '.2', 'rotated-2-content-to-be-deleted');
|
|
473
|
+
initializeLogger({ commandName: 'test-cmd' });
|
|
474
|
+
expect(fs.readFileSync(auditPath + '.2', 'utf-8')).toBe('rotated-1-content');
|
|
475
|
+
expect(fs.readFileSync(auditPath + '.1', 'utf-8')).toBe(bigContent);
|
|
476
|
+
stderrSpy.mockRestore();
|
|
477
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
478
|
+
});
|
|
479
|
+
it('rotation failure does not crash the tool', async () => {
|
|
480
|
+
const constantsMod = await import('./constants.js');
|
|
481
|
+
const badDir = path.join(tempDir, 'readonly-test');
|
|
482
|
+
fs.mkdirSync(badDir);
|
|
483
|
+
const auditPath = path.join(badDir, 'audit.log');
|
|
484
|
+
const bigContent = 'x'.repeat(10 * 1024 * 1024 + 100);
|
|
485
|
+
fs.writeFileSync(auditPath, bigContent);
|
|
486
|
+
fs.chmodSync(badDir, 0o444);
|
|
487
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(badDir);
|
|
488
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
489
|
+
expect(() => initializeLogger({ commandName: 'test-cmd' })).not.toThrow();
|
|
490
|
+
fs.chmodSync(badDir, 0o755);
|
|
491
|
+
stderrSpy.mockRestore();
|
|
492
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
// ConditionalStderrReporter
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
describe('ConditionalStderrReporter', () => {
|
|
499
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
500
|
+
let stderrSpy;
|
|
501
|
+
beforeEach(() => {
|
|
502
|
+
_resetForTesting();
|
|
503
|
+
savedEnv = {
|
|
504
|
+
GWT_LOG_LEVEL: process.env.GWT_LOG_LEVEL,
|
|
505
|
+
DEBUG: process.env.DEBUG,
|
|
506
|
+
};
|
|
507
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
508
|
+
delete process.env.DEBUG;
|
|
509
|
+
stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
510
|
+
});
|
|
511
|
+
afterEach(() => {
|
|
512
|
+
stderrSpy.mockRestore();
|
|
513
|
+
_resetForTesting();
|
|
514
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
515
|
+
if (value === undefined) {
|
|
516
|
+
delete process.env[key];
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
process.env[key] = value;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
describe('non-verbose mode (default)', () => {
|
|
256
524
|
beforeEach(() => {
|
|
257
|
-
|
|
525
|
+
initializeLogger({});
|
|
258
526
|
});
|
|
259
|
-
|
|
260
|
-
|
|
527
|
+
it('error writes to stderr', () => {
|
|
528
|
+
logger.error('err-msg');
|
|
529
|
+
const errorCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('err-msg'));
|
|
530
|
+
expect(errorCalls.length).toBeGreaterThan(0);
|
|
261
531
|
});
|
|
262
|
-
it('
|
|
263
|
-
|
|
264
|
-
|
|
532
|
+
it('warn writes to stderr', () => {
|
|
533
|
+
logger.warn('wrn-msg');
|
|
534
|
+
const warnCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('wrn-msg'));
|
|
535
|
+
expect(warnCalls.length).toBeGreaterThan(0);
|
|
265
536
|
});
|
|
266
|
-
it('
|
|
267
|
-
|
|
268
|
-
|
|
537
|
+
it('info does NOT write to stderr', () => {
|
|
538
|
+
logger.info('inf-msg');
|
|
539
|
+
const infoCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('inf-msg'));
|
|
540
|
+
expect(infoCalls.length).toBe(0);
|
|
269
541
|
});
|
|
270
|
-
it('
|
|
542
|
+
it('debug does NOT write to stderr', () => {
|
|
543
|
+
logger.debug('dbg-msg');
|
|
544
|
+
const debugCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('dbg-msg'));
|
|
545
|
+
expect(debugCalls.length).toBe(0);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
describe('verbose mode', () => {
|
|
549
|
+
beforeEach(() => {
|
|
271
550
|
initializeLogger({ verbose: true });
|
|
272
|
-
expect(logger.getLevel()).toBe(LogLevel.DEBUG);
|
|
273
551
|
});
|
|
274
|
-
it('
|
|
275
|
-
|
|
276
|
-
|
|
552
|
+
it('error writes to stderr', () => {
|
|
553
|
+
logger.error('err-verbose');
|
|
554
|
+
const errorCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('err-verbose'));
|
|
555
|
+
expect(errorCalls.length).toBeGreaterThan(0);
|
|
277
556
|
});
|
|
278
|
-
it('
|
|
279
|
-
|
|
280
|
-
|
|
557
|
+
it('warn writes to stderr', () => {
|
|
558
|
+
logger.warn('wrn-verbose');
|
|
559
|
+
const warnCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('wrn-verbose'));
|
|
560
|
+
expect(warnCalls.length).toBeGreaterThan(0);
|
|
281
561
|
});
|
|
282
|
-
it('
|
|
283
|
-
|
|
284
|
-
|
|
562
|
+
it('info writes to stderr in verbose mode', () => {
|
|
563
|
+
logger.info('inf-verbose');
|
|
564
|
+
const infoCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('inf-verbose'));
|
|
565
|
+
expect(infoCalls.length).toBeGreaterThan(0);
|
|
285
566
|
});
|
|
286
|
-
it('
|
|
287
|
-
|
|
288
|
-
|
|
567
|
+
it('debug writes to stderr in verbose mode', () => {
|
|
568
|
+
logger.debug('dbg-verbose');
|
|
569
|
+
const debugCalls = stderrSpy.mock.calls.filter((call) => typeof call[0] === 'string' && call[0].includes('dbg-verbose'));
|
|
570
|
+
expect(debugCalls.length).toBeGreaterThan(0);
|
|
289
571
|
});
|
|
290
572
|
});
|
|
291
573
|
});
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
// Process exit handler / audit session summary
|
|
576
|
+
// ---------------------------------------------------------------------------
|
|
577
|
+
describe('Process exit handler', () => {
|
|
578
|
+
beforeEach(() => {
|
|
579
|
+
_resetForTesting();
|
|
580
|
+
tempDir = createTempDir();
|
|
581
|
+
savedEnv = {
|
|
582
|
+
GWT_LOG_LEVEL: process.env.GWT_LOG_LEVEL,
|
|
583
|
+
DEBUG: process.env.DEBUG,
|
|
584
|
+
};
|
|
585
|
+
delete process.env.GWT_LOG_LEVEL;
|
|
586
|
+
delete process.env.DEBUG;
|
|
587
|
+
});
|
|
588
|
+
afterEach(() => {
|
|
589
|
+
_resetForTesting();
|
|
590
|
+
cleanupTempDir(tempDir);
|
|
591
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
592
|
+
if (value === undefined) {
|
|
593
|
+
delete process.env[key];
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
process.env[key] = value;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
it('exit handler writes audit summary via fs.appendFileSync', async () => {
|
|
601
|
+
const constantsMod = await import('./constants.js');
|
|
602
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
603
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
604
|
+
const appendSpy = vi.spyOn(fs, 'appendFileSync');
|
|
605
|
+
initializeLogger({ commandName: 'test-exit' });
|
|
606
|
+
setAuditContext({ prNumber: 42 });
|
|
607
|
+
process.emit('exit', 0);
|
|
608
|
+
expect(appendSpy).toHaveBeenCalled();
|
|
609
|
+
const lastCall = appendSpy.mock.calls.find((call) => typeof call[1] === 'string' && call[1].includes('test-exit'));
|
|
610
|
+
expect(lastCall).toBeDefined();
|
|
611
|
+
const writtenContent = lastCall[1];
|
|
612
|
+
expect(writtenContent).toContain('test-exit');
|
|
613
|
+
expect(writtenContent).toContain('exit=0');
|
|
614
|
+
appendSpy.mockRestore();
|
|
615
|
+
stderrSpy.mockRestore();
|
|
616
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
617
|
+
});
|
|
618
|
+
it('exit handler includes duration as a positive number', async () => {
|
|
619
|
+
const constantsMod = await import('./constants.js');
|
|
620
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
621
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
622
|
+
const appendSpy = vi.spyOn(fs, 'appendFileSync');
|
|
623
|
+
initializeLogger({ commandName: 'duration-test' });
|
|
624
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
625
|
+
process.emit('exit', 0);
|
|
626
|
+
const exitCall = appendSpy.mock.calls.find((call) => typeof call[1] === 'string' && call[1].includes('duration-test'));
|
|
627
|
+
expect(exitCall).toBeDefined();
|
|
628
|
+
const writtenContent = exitCall[1];
|
|
629
|
+
const durationMatch = writtenContent.match(/duration=(\d+)ms/);
|
|
630
|
+
expect(durationMatch).toBeTruthy();
|
|
631
|
+
const duration = parseInt(durationMatch[1], 10);
|
|
632
|
+
expect(duration).toBeGreaterThanOrEqual(0);
|
|
633
|
+
appendSpy.mockRestore();
|
|
634
|
+
stderrSpy.mockRestore();
|
|
635
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
636
|
+
});
|
|
637
|
+
it('exit handler writes JSON format when json mode is active', async () => {
|
|
638
|
+
const constantsMod = await import('./constants.js');
|
|
639
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
640
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
641
|
+
const appendSpy = vi.spyOn(fs, 'appendFileSync');
|
|
642
|
+
initializeLogger({ json: true, commandName: 'json-exit-test' });
|
|
643
|
+
setAuditContext({ prNumber: 99 });
|
|
644
|
+
process.emit('exit', 1);
|
|
645
|
+
const exitCall = appendSpy.mock.calls.find((call) => typeof call[1] === 'string' && call[1].includes('json-exit-test'));
|
|
646
|
+
expect(exitCall).toBeDefined();
|
|
647
|
+
const writtenContent = exitCall[1].trim();
|
|
648
|
+
const parsed = JSON.parse(writtenContent);
|
|
649
|
+
expect(parsed.command).toBe('json-exit-test');
|
|
650
|
+
expect(parsed.exitCode).toBe(1);
|
|
651
|
+
expect(parsed.prNumber).toBe(99);
|
|
652
|
+
expect(parsed.type).toBe('session');
|
|
653
|
+
expect(parsed.durationMs).toBeGreaterThanOrEqual(0);
|
|
654
|
+
appendSpy.mockRestore();
|
|
655
|
+
stderrSpy.mockRestore();
|
|
656
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
657
|
+
});
|
|
658
|
+
it('exit handler fails silently if audit log path is inaccessible', async () => {
|
|
659
|
+
const constantsMod = await import('./constants.js');
|
|
660
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue('/nonexistent/path/deep/nest');
|
|
661
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
662
|
+
initializeLogger({ commandName: 'fail-silently-test' });
|
|
663
|
+
expect(() => process.emit('exit', 0)).not.toThrow();
|
|
664
|
+
stderrSpy.mockRestore();
|
|
665
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
// setAuditContext
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
describe('setAuditContext', () => {
|
|
672
|
+
beforeEach(() => {
|
|
673
|
+
_resetForTesting();
|
|
674
|
+
});
|
|
675
|
+
it('merges additional metadata into audit context', async () => {
|
|
676
|
+
const constantsMod = await import('./constants.js');
|
|
677
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
678
|
+
tempDir = createTempDir();
|
|
679
|
+
vi.spyOn(constantsMod, 'getGlobalDataDir').mockReturnValue(tempDir);
|
|
680
|
+
const appendSpy = vi.spyOn(fs, 'appendFileSync');
|
|
681
|
+
initializeLogger({ commandName: 'context-test' });
|
|
682
|
+
setAuditContext({ worktreePath: '/tmp/worktree', prNumber: 123 });
|
|
683
|
+
process.emit('exit', 0);
|
|
684
|
+
const exitCall = appendSpy.mock.calls.find((call) => typeof call[1] === 'string' && call[1].includes('context-test'));
|
|
685
|
+
expect(exitCall).toBeDefined();
|
|
686
|
+
appendSpy.mockRestore();
|
|
687
|
+
stderrSpy.mockRestore();
|
|
688
|
+
vi.mocked(constantsMod.getGlobalDataDir).mockRestore();
|
|
689
|
+
cleanupTempDir(tempDir);
|
|
690
|
+
});
|
|
691
|
+
});
|
|
292
692
|
//# sourceMappingURL=logger.test.js.map
|