@@ -6,6 +6,7 @@ import type { DatabaseSync } from "node:sqlite";
66import { setTimeout as scheduleNativeTimeout } from "node:timers" ;
77import type { Mock } from "vitest" ;
88import { afterAll , afterEach , beforeAll , beforeEach , describe , expect , it , vi } from "vitest" ;
9+ import { withMockedWindowsPlatform } from "../../../../src/test-utils/vitest-spies.js" ;
910
1011const { logWarnMock, logDebugMock, logInfoMock } = vi . hoisted ( ( ) => ( {
1112 logWarnMock : vi . fn ( ) ,
@@ -1917,47 +1918,47 @@ describe("QmdMemoryManager", () => {
19171918 } ) ;
19181919
19191920 it ( "resolves bare qmd command to a Windows-compatible spawn invocation" , async ( ) => {
1920- const platformSpy = vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
1921- const previousPath = process . env . PATH ;
1922- try {
1923- const nodeModulesDir = path . join ( tmpRoot , "node_modules" ) ;
1924- const shimDir = path . join ( nodeModulesDir , ".bin" ) ;
1925- const packageDir = path . join ( nodeModulesDir , "qmd" ) ;
1926- const scriptPath = path . join ( packageDir , "dist" , "cli.js" ) ;
1927- await fs . mkdir ( path . dirname ( scriptPath ) , { recursive : true } ) ;
1928- await fs . mkdir ( shimDir , { recursive : true } ) ;
1929- await fs . writeFile ( path . join ( shimDir , "qmd.cmd" ) , "@echo off\r\n" , "utf8" ) ;
1930- await fs . writeFile (
1931- path . join ( packageDir , "package.json" ) ,
1932- JSON . stringify ( { name : "qmd" , version : "0.0.0" , bin : { qmd : "dist/cli.js" } } ) ,
1933- "utf8" ,
1934- ) ;
1935- await fs . writeFile ( scriptPath , "module.exports = {};\n" , "utf8" ) ;
1936- process . env . PATH = `${ shimDir } ;${ previousPath ?? "" } ` ;
1921+ await withMockedWindowsPlatform ( async ( ) => {
1922+ const previousPath = process . env . PATH ;
1923+ try {
1924+ const nodeModulesDir = path . join ( tmpRoot , "node_modules" ) ;
1925+ const shimDir = path . join ( nodeModulesDir , ".bin" ) ;
1926+ const packageDir = path . join ( nodeModulesDir , "qmd" ) ;
1927+ const scriptPath = path . join ( packageDir , "dist" , "cli.js" ) ;
1928+ await fs . mkdir ( path . dirname ( scriptPath ) , { recursive : true } ) ;
1929+ await fs . mkdir ( shimDir , { recursive : true } ) ;
1930+ await fs . writeFile ( path . join ( shimDir , "qmd.cmd" ) , "@echo off\r\n" , "utf8" ) ;
1931+ await fs . writeFile (
1932+ path . join ( packageDir , "package.json" ) ,
1933+ JSON . stringify ( { name : "qmd" , version : "0.0.0" , bin : { qmd : "dist/cli.js" } } ) ,
1934+ "utf8" ,
1935+ ) ;
1936+ await fs . writeFile ( scriptPath , "module.exports = {};\n" , "utf8" ) ;
1937+ process . env . PATH = `${ shimDir } ;${ previousPath ?? "" } ` ;
19371938
1938- const { manager } = await createManager ( { mode : "status" } ) ;
1939- await manager . sync ( { reason : "manual" } ) ;
1939+ const { manager } = await createManager ( { mode : "status" } ) ;
1940+ await manager . sync ( { reason : "manual" } ) ;
19401941
1941- const qmdCalls = spawnMock . mock . calls . filter ( ( call : unknown [ ] ) => {
1942- const args = call [ 1 ] as string [ ] | undefined ;
1943- return (
1944- Array . isArray ( args ) &&
1945- args . some ( ( token ) => token === "update" || token === "search" || token === "query" )
1946- ) ;
1947- } ) ;
1948- expect ( qmdCalls . length ) . toBeGreaterThan ( 0 ) ;
1949- for ( const call of qmdCalls ) {
1950- const command = String ( call [ 0 ] ) ;
1951- const options = call [ 2 ] as { shell ?: boolean } | undefined ;
1952- expect ( command ) . not . toMatch ( / ( ^ | [ \\ / ] ) q m d \. c m d $ / i) ;
1953- expect ( options ?. shell ) . not . toBe ( true ) ;
1954- }
1942+ const qmdCalls = spawnMock . mock . calls . filter ( ( call : unknown [ ] ) => {
1943+ const args = call [ 1 ] as string [ ] | undefined ;
1944+ return (
1945+ Array . isArray ( args ) &&
1946+ args . some ( ( token ) => token === "update" || token === "search" || token === "query" )
1947+ ) ;
1948+ } ) ;
1949+ expect ( qmdCalls . length ) . toBeGreaterThan ( 0 ) ;
1950+ for ( const call of qmdCalls ) {
1951+ const command = String ( call [ 0 ] ) ;
1952+ const options = call [ 2 ] as { shell ?: boolean } | undefined ;
1953+ expect ( command ) . not . toMatch ( / ( ^ | [ \\ / ] ) q m d \. c m d $ / i) ;
1954+ expect ( options ?. shell ) . not . toBe ( true ) ;
1955+ }
19551956
1956- await manager . close ( ) ;
1957- } finally {
1958- platformSpy . mockRestore ( ) ;
1959- process . env . PATH = previousPath ;
1960- }
1957+ await manager . close ( ) ;
1958+ } finally {
1959+ process . env . PATH = previousPath ;
1960+ }
1961+ } ) ;
19611962 } ) ;
19621963
19631964 it ( "keeps mixed Han-script BM25 queries intact before qmd search" , async ( ) => {
@@ -3210,125 +3211,125 @@ describe("QmdMemoryManager", () => {
32103211 } ) ;
32113212
32123213 it ( "resolves mcporter to a direct Windows entrypoint without enabling shell mode" , async ( ) => {
3213- const platformSpy = vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
3214- const previousPath = process . env . PATH ;
3215- try {
3216- const nodeModulesDir = path . join ( tmpRoot , "node_modules" ) ;
3217- const shimDir = path . join ( nodeModulesDir , ".bin" ) ;
3218- const packageDir = path . join ( nodeModulesDir , "mcporter" ) ;
3219- const scriptPath = path . join ( packageDir , "dist" , "cli.js" ) ;
3220- await fs . mkdir ( path . dirname ( scriptPath ) , { recursive : true } ) ;
3221- await fs . mkdir ( shimDir , { recursive : true } ) ;
3222- await fs . writeFile ( path . join ( shimDir , "mcporter.cmd" ) , "@echo off\r\n" , "utf8" ) ;
3223- await fs . writeFile (
3224- path . join ( packageDir , "package.json" ) ,
3225- JSON . stringify ( { name : "mcporter" , version : "0.0.0" , bin : { mcporter : "dist/cli.js" } } ) ,
3226- "utf8" ,
3227- ) ;
3228- await fs . writeFile ( scriptPath , "module.exports = {};\n" , "utf8" ) ;
3229- process . env . PATH = `${ shimDir } ;${ previousPath ?? "" } ` ;
3230-
3231- cfg = {
3232- ...cfg ,
3233- memory : {
3234- backend : "qmd" ,
3235- qmd : {
3236- includeDefaultMemory : false ,
3237- update : { interval : "0s" , debounceMs : 60_000 , onBoot : false } ,
3238- paths : [ { path : workspaceDir , pattern : "**/*.md" , name : "workspace" } ] ,
3239- mcporter : { enabled : true , serverName : "qmd" , startDaemon : false } ,
3214+ await withMockedWindowsPlatform ( async ( ) => {
3215+ const previousPath = process . env . PATH ;
3216+ try {
3217+ const nodeModulesDir = path . join ( tmpRoot , "node_modules" ) ;
3218+ const shimDir = path . join ( nodeModulesDir , ".bin" ) ;
3219+ const packageDir = path . join ( nodeModulesDir , "mcporter" ) ;
3220+ const scriptPath = path . join ( packageDir , "dist" , "cli.js" ) ;
3221+ await fs . mkdir ( path . dirname ( scriptPath ) , { recursive : true } ) ;
3222+ await fs . mkdir ( shimDir , { recursive : true } ) ;
3223+ await fs . writeFile ( path . join ( shimDir , "mcporter.cmd" ) , "@echo off\r\n" , "utf8" ) ;
3224+ await fs . writeFile (
3225+ path . join ( packageDir , "package.json" ) ,
3226+ JSON . stringify ( { name : "mcporter" , version : "0.0.0" , bin : { mcporter : "dist/cli.js" } } ) ,
3227+ "utf8" ,
3228+ ) ;
3229+ await fs . writeFile ( scriptPath , "module.exports = {};\n" , "utf8" ) ;
3230+ process . env . PATH = `${ shimDir } ;${ previousPath ?? "" } ` ;
3231+
3232+ cfg = {
3233+ ...cfg ,
3234+ memory : {
3235+ backend : "qmd" ,
3236+ qmd : {
3237+ includeDefaultMemory : false ,
3238+ update : { interval : "0s" , debounceMs : 60_000 , onBoot : false } ,
3239+ paths : [ { path : workspaceDir , pattern : "**/*.md" , name : "workspace" } ] ,
3240+ mcporter : { enabled : true , serverName : "qmd" , startDaemon : false } ,
3241+ } ,
32403242 } ,
3241- } ,
3242- } as OpenClawConfig ;
3243+ } as OpenClawConfig ;
32433244
3244- spawnMock . mockImplementation ( ( _cmd : string , args : string [ ] ) => {
3245- const child = createMockChild ( { autoClose : false } ) ;
3246- if ( args [ 0 ] === "call" ) {
3247- emitAndClose ( child , "stdout" , JSON . stringify ( { results : [ ] } ) ) ;
3245+ spawnMock . mockImplementation ( ( _cmd : string , args : string [ ] ) => {
3246+ const child = createMockChild ( { autoClose : false } ) ;
3247+ if ( args [ 0 ] === "call" ) {
3248+ emitAndClose ( child , "stdout" , JSON . stringify ( { results : [ ] } ) ) ;
3249+ return child ;
3250+ }
3251+ emitAndClose ( child , "stdout" , "[]" ) ;
32483252 return child ;
3249- }
3250- emitAndClose ( child , "stdout" , "[]" ) ;
3251- return child ;
3252- } ) ;
3253+ } ) ;
32533254
3254- const { manager } = await createManager ( ) ;
3255- await manager . search ( "hello" , { sessionKey : "agent:main:slack:dm:u123" } ) ;
3255+ const { manager } = await createManager ( ) ;
3256+ await manager . search ( "hello" , { sessionKey : "agent:main:slack:dm:u123" } ) ;
32563257
3257- const mcporterCall = spawnMock . mock . calls . find ( ( call : unknown [ ] ) =>
3258- ( call [ 1 ] as string [ ] | undefined ) ?. includes ( "call" ) ,
3259- ) ;
3260- const searchCall = requireValue ( mcporterCall , "mcporter search call missing" ) ;
3261- const callCommand = searchCall [ 0 ] ;
3262- expect ( typeof callCommand ) . toBe ( "string" ) ;
3263- const options = searchCall [ 2 ] as { shell ?: boolean } | undefined ;
3264- expect ( callCommand ) . not . toBe ( "mcporter.cmd" ) ;
3265- expect ( options ?. shell ) . not . toBe ( true ) ;
3258+ const mcporterCall = spawnMock . mock . calls . find ( ( call : unknown [ ] ) =>
3259+ ( call [ 1 ] as string [ ] | undefined ) ?. includes ( "call" ) ,
3260+ ) ;
3261+ const searchCall = requireValue ( mcporterCall , "mcporter search call missing" ) ;
3262+ const callCommand = searchCall [ 0 ] ;
3263+ expect ( typeof callCommand ) . toBe ( "string" ) ;
3264+ const options = searchCall [ 2 ] as { shell ?: boolean } | undefined ;
3265+ expect ( callCommand ) . not . toBe ( "mcporter.cmd" ) ;
3266+ expect ( options ?. shell ) . not . toBe ( true ) ;
32663267
3267- await manager . close ( ) ;
3268- } finally {
3269- platformSpy . mockRestore ( ) ;
3270- process . env . PATH = previousPath ;
3271- }
3268+ await manager . close ( ) ;
3269+ } finally {
3270+ process . env . PATH = previousPath ;
3271+ }
3272+ } ) ;
32723273 } ) ;
32733274
32743275 it ( "fails closed on Windows EINVAL cmd-shim failures instead of retrying through the shell" , async ( ) => {
3275- const platformSpy = vi . spyOn ( process , "platform" , "get" ) . mockReturnValue ( "win32" ) ;
3276- const previousPath = process . env . PATH ;
3277- try {
3278- const shimDir = await fs . mkdtemp ( path . join ( tmpRoot , "mcporter-shim-" ) ) ;
3279- await fs . writeFile ( path . join ( shimDir , "mcporter.cmd" ) , "@echo off\n" ) ;
3280- process . env . PATH = `${ shimDir } ;${ previousPath ?? "" } ` ;
3281-
3282- cfg = {
3283- ...cfg ,
3284- memory : {
3285- backend : "qmd" ,
3286- qmd : {
3287- includeDefaultMemory : false ,
3288- update : { interval : "0s" , debounceMs : 60_000 , onBoot : false } ,
3289- paths : [ { path : workspaceDir , pattern : "**/*.md" , name : "workspace" } ] ,
3290- mcporter : { enabled : true , serverName : "qmd" , startDaemon : false } ,
3276+ await withMockedWindowsPlatform ( async ( ) => {
3277+ const previousPath = process . env . PATH ;
3278+ try {
3279+ const shimDir = await fs . mkdtemp ( path . join ( tmpRoot , "mcporter-shim-" ) ) ;
3280+ await fs . writeFile ( path . join ( shimDir , "mcporter.cmd" ) , "@echo off\n" ) ;
3281+ process . env . PATH = `${ shimDir } ;${ previousPath ?? "" } ` ;
3282+
3283+ cfg = {
3284+ ...cfg ,
3285+ memory : {
3286+ backend : "qmd" ,
3287+ qmd : {
3288+ includeDefaultMemory : false ,
3289+ update : { interval : "0s" , debounceMs : 60_000 , onBoot : false } ,
3290+ paths : [ { path : workspaceDir , pattern : "**/*.md" , name : "workspace" } ] ,
3291+ mcporter : { enabled : true , serverName : "qmd" , startDaemon : false } ,
3292+ } ,
32913293 } ,
3292- } ,
3293- } as OpenClawConfig ;
3294+ } as OpenClawConfig ;
32943295
3295- let firstCallCommand : string | null = null ;
3296- spawnMock . mockImplementation ( ( cmd : string , args : string [ ] ) => {
3297- if ( args [ 0 ] === "call" && firstCallCommand === null ) {
3298- firstCallCommand = cmd ;
3299- }
3300- if ( args [ 0 ] === "call" && typeof cmd === "string" && cmd . toLowerCase ( ) . endsWith ( ".cmd" ) ) {
3296+ let firstCallCommand : string | null = null ;
3297+ spawnMock . mockImplementation ( ( cmd : string , args : string [ ] ) => {
3298+ if ( args [ 0 ] === "call" && firstCallCommand === null ) {
3299+ firstCallCommand = cmd ;
3300+ }
3301+ if ( args [ 0 ] === "call" && typeof cmd === "string" && cmd . toLowerCase ( ) . endsWith ( ".cmd" ) ) {
3302+ const child = createMockChild ( { autoClose : false } ) ;
3303+ queueMicrotask ( ( ) => {
3304+ const err = Object . assign ( new Error ( "spawn EINVAL" ) , { code : "EINVAL" } ) ;
3305+ child . emit ( "error" , err ) ;
3306+ } ) ;
3307+ return child ;
3308+ }
33013309 const child = createMockChild ( { autoClose : false } ) ;
3302- queueMicrotask ( ( ) => {
3303- const err = Object . assign ( new Error ( "spawn EINVAL" ) , { code : "EINVAL" } ) ;
3304- child . emit ( "error" , err ) ;
3305- } ) ;
3310+ emitAndClose ( child , "stdout" , "[]" ) ;
33063311 return child ;
3307- }
3308- const child = createMockChild ( { autoClose : false } ) ;
3309- emitAndClose ( child , "stdout" , "[]" ) ;
3310- return child ;
3311- } ) ;
3312+ } ) ;
33123313
3313- const { manager } = await createManager ( ) ;
3314- await expect (
3315- manager . search ( "hello" , { sessionKey : "agent:main:slack:dm:u123" } ) ,
3316- ) . rejects . toThrow ( / w i t h o u t s h e l l e x e c u t i o n | E I N V A L / ) ;
3317- const attemptedCmdShim = ( firstCallCommand ?? "" ) . toLowerCase ( ) . endsWith ( ".cmd" ) ;
3318- if ( attemptedCmdShim ) {
3319- expect (
3320- spawnMock . mock . calls . some (
3321- ( call : unknown [ ] ) =>
3322- call [ 0 ] === "mcporter" &&
3323- ( call [ 2 ] as { shell ?: boolean } | undefined ) ?. shell === true ,
3324- ) ,
3325- ) . toBe ( false ) ;
3314+ const { manager } = await createManager ( ) ;
3315+ await expect (
3316+ manager . search ( "hello" , { sessionKey : "agent:main:slack:dm:u123" } ) ,
3317+ ) . rejects . toThrow ( / w i t h o u t s h e l l e x e c u t i o n | E I N V A L / ) ;
3318+ const attemptedCmdShim = ( firstCallCommand ?? "" ) . toLowerCase ( ) . endsWith ( ".cmd" ) ;
3319+ if ( attemptedCmdShim ) {
3320+ expect (
3321+ spawnMock . mock . calls . some (
3322+ ( call : unknown [ ] ) =>
3323+ call [ 0 ] === "mcporter" &&
3324+ ( call [ 2 ] as { shell ?: boolean } | undefined ) ?. shell === true ,
3325+ ) ,
3326+ ) . toBe ( false ) ;
3327+ }
3328+ await manager . close ( ) ;
3329+ } finally {
3330+ process . env . PATH = previousPath ;
33263331 }
3327- await manager . close ( ) ;
3328- } finally {
3329- platformSpy . mockRestore ( ) ;
3330- process . env . PATH = previousPath ;
3331- }
3332+ } ) ;
33323333 } ) ;
33333334
33343335 it ( "passes manager-scoped XDG env to mcporter commands" , async ( ) => {
0 commit comments