33 */
44import fs from "node:fs/promises" ;
55import path from "node:path" ;
6- import { describe , expect , it } from "vitest" ;
6+ import { afterEach , describe , expect , it } from "vitest" ;
7+ import { resetPluginStateStoreForTests } from "../plugin-state/plugin-state-store.js" ;
78import {
89 appendMemoryHostEvent ,
910 readMemoryHostEvents ,
@@ -18,11 +19,17 @@ function createDedupe(root: string, overrides?: { ttlMs?: number }) {
1819 return createPersistentDedupe ( {
1920 ttlMs : overrides ?. ttlMs ?? 24 * 60 * 60 * 1000 ,
2021 memoryMaxSize : 100 ,
21- fileMaxEntries : 1000 ,
22- resolveFilePath : ( namespace ) => path . join ( root , `${ namespace } .json` ) ,
22+ pluginId : "test-persistent-dedupe" ,
23+ namespacePrefix : "test-dedupe" ,
24+ stateMaxEntries : 1000 ,
25+ env : { ...process . env , OPENCLAW_STATE_DIR : root } ,
2326 } ) ;
2427}
2528
29+ afterEach ( ( ) => {
30+ resetPluginStateStoreForTests ( ) ;
31+ } ) ;
32+
2633describe ( "memory host event journal helpers" , ( ) => {
2734 it ( "appends and reads typed workspace events" , async ( ) => {
2835 const workspaceDir = await createTempDir ( "memory-host-events-" ) ;
@@ -94,8 +101,10 @@ describe("createPersistentDedupe", () => {
94101 const dedupe = createPersistentDedupe ( {
95102 ttlMs : Number . NaN ,
96103 memoryMaxSize : Number . NaN ,
97- fileMaxEntries : Number . NaN ,
98- resolveFilePath : ( namespace ) => path . join ( root , `${ namespace } .json` ) ,
104+ pluginId : "test-persistent-dedupe" ,
105+ namespacePrefix : "test-bounds" ,
106+ stateMaxEntries : Number . NaN ,
107+ env : { ...process . env , OPENCLAW_STATE_DIR : root } ,
99108 } ) ;
100109
101110 expect ( await dedupe . checkAndRecord ( "m1" , { namespace : "a" , now : 100 } ) ) . toBe ( true ) ;
@@ -104,33 +113,32 @@ describe("createPersistentDedupe", () => {
104113 expect ( dedupe . memorySize ( ) ) . toBe ( 0 ) ;
105114 } ) ;
106115
107- it ( "falls back to memory-only behavior on disk errors" , async ( ) => {
116+ it ( "uses legacy JSON paths only as SQLite namespace identifiers" , async ( ) => {
117+ const root = await createTempDir ( "openclaw-legacy-dedupe-" ) ;
118+ const legacyPath = path . join ( root , "legacy.json" ) ;
108119 const dedupe = createPersistentDedupe ( {
109120 ttlMs : 10_000 ,
110121 memoryMaxSize : 100 ,
111122 fileMaxEntries : 1000 ,
112- resolveFilePath : ( ) => path . join ( "/dev/null" , "dedupe.json" ) ,
123+ resolveFilePath : ( ) => legacyPath ,
124+ env : { ...process . env , OPENCLAW_STATE_DIR : root } ,
113125 } ) ;
114126
115- expect ( await dedupe . checkAndRecord ( "memory-only" , { namespace : "x" } ) ) . toBe ( true ) ;
116- expect ( await dedupe . checkAndRecord ( "memory-only" , { namespace : "x" } ) ) . toBe ( false ) ;
127+ expect ( await dedupe . checkAndRecord ( "sqlite-only" , { namespace : "x" } ) ) . toBe ( true ) ;
128+ expect ( await dedupe . checkAndRecord ( "sqlite-only" , { namespace : "x" } ) ) . toBe ( false ) ;
129+ await expect ( fs . access ( legacyPath ) ) . rejects . toThrow ( ) ;
117130 } ) ;
118131
119- it ( "warms empty namespaces and skips expired disk entries " , async ( ) => {
132+ it ( "warms empty namespaces and ignores retired JSON cache files " , async ( ) => {
120133 const root = await createTempDir ( "openclaw-dedupe-" ) ;
121134 const emptyReader = createDedupe ( root , { ttlMs : 10_000 } ) ;
122135 expect ( await emptyReader . warmup ( "nonexistent" ) ) . toBe ( 0 ) ;
123136
124- const oldNow = Date . now ( ) - 2000 ;
125- await fs . writeFile (
126- path . join ( root , "acct.json" ) ,
127- JSON . stringify ( { "old-msg" : oldNow , "new-msg" : Date . now ( ) } ) ,
128- ) ;
137+ await fs . writeFile ( path . join ( root , "acct.json" ) , JSON . stringify ( { "retired-msg" : Date . now ( ) } ) ) ;
129138
130139 const reader = createDedupe ( root , { ttlMs : 1000 } ) ;
131- expect ( await reader . warmup ( "acct" ) ) . toBe ( 1 ) ;
132- expect ( await reader . checkAndRecord ( "old-msg" , { namespace : "acct" } ) ) . toBe ( true ) ;
133- expect ( await reader . checkAndRecord ( "new-msg" , { namespace : "acct" } ) ) . toBe ( false ) ;
140+ expect ( await reader . warmup ( "acct" ) ) . toBe ( 0 ) ;
141+ expect ( await reader . checkAndRecord ( "retired-msg" , { namespace : "acct" } ) ) . toBe ( true ) ;
134142 } ) ;
135143} ) ;
136144
@@ -189,8 +197,10 @@ describe("createClaimableDedupe", () => {
189197 const writer = createClaimableDedupe ( {
190198 ttlMs : 10_000 ,
191199 memoryMaxSize : 100 ,
192- fileMaxEntries : 1000 ,
193- resolveFilePath : ( namespace ) => path . join ( root , `${ namespace } .json` ) ,
200+ pluginId : "test-claimable-dedupe" ,
201+ namespacePrefix : "test-claimable-dedupe" ,
202+ stateMaxEntries : 1000 ,
203+ env : { ...process . env , OPENCLAW_STATE_DIR : root } ,
194204 } ) ;
195205
196206 await expect ( writer . claim ( "m1" , { namespace : "acct" } ) ) . resolves . toEqual ( { kind : "claimed" } ) ;
@@ -199,8 +209,10 @@ describe("createClaimableDedupe", () => {
199209 const reader = createClaimableDedupe ( {
200210 ttlMs : 10_000 ,
201211 memoryMaxSize : 100 ,
202- fileMaxEntries : 1000 ,
203- resolveFilePath : ( namespace ) => path . join ( root , `${ namespace } .json` ) ,
212+ pluginId : "test-claimable-dedupe" ,
213+ namespacePrefix : "test-claimable-dedupe" ,
214+ stateMaxEntries : 1000 ,
215+ env : { ...process . env , OPENCLAW_STATE_DIR : root } ,
204216 } ) ;
205217
206218 expect ( await reader . hasRecent ( "m1" , { namespace : "acct" } ) ) . toBe ( true ) ;
0 commit comments