@@ -2,7 +2,8 @@ import fs from "node:fs/promises";
22import os from "node:os" ;
33import path from "node:path" ;
44import { afterEach , describe , expect , it , vi } from "vitest" ;
5- import { loadCronStore , resolveCronStorePath } from "./store.js" ;
5+ import { loadCronStore , resolveCronStorePath , saveCronStore } from "./store.js" ;
6+ import type { CronStoreFile } from "./types.js" ;
67
78async function makeStorePath ( ) {
89 const dir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-cron-store-" ) ) ;
@@ -15,6 +16,27 @@ async function makeStorePath() {
1516 } ;
1617}
1718
19+ function makeStore ( jobId : string , enabled : boolean ) : CronStoreFile {
20+ const now = Date . now ( ) ;
21+ return {
22+ version : 1 ,
23+ jobs : [
24+ {
25+ id : jobId ,
26+ name : `Job ${ jobId } ` ,
27+ enabled,
28+ createdAtMs : now ,
29+ updatedAtMs : now ,
30+ schedule : { kind : "every" , everyMs : 60_000 } ,
31+ sessionTarget : "main" ,
32+ wakeMode : "next-heartbeat" ,
33+ payload : { kind : "systemEvent" , text : `tick-${ jobId } ` } ,
34+ state : { } ,
35+ } ,
36+ ] ,
37+ } ;
38+ }
39+
1840describe ( "resolveCronStorePath" , ( ) => {
1941 afterEach ( ( ) => {
2042 vi . unstubAllEnvs ( ) ;
@@ -43,4 +65,30 @@ describe("cron store", () => {
4365 await expect ( loadCronStore ( store . storePath ) ) . rejects . toThrow ( / F a i l e d t o p a r s e c r o n s t o r e / i) ;
4466 await store . cleanup ( ) ;
4567 } ) ;
68+
69+ it ( "does not create a backup file when saving unchanged content" , async ( ) => {
70+ const store = await makeStorePath ( ) ;
71+ const payload = makeStore ( "job-1" , true ) ;
72+
73+ await saveCronStore ( store . storePath , payload ) ;
74+ await saveCronStore ( store . storePath , payload ) ;
75+
76+ await expect ( fs . stat ( `${ store . storePath } .bak` ) ) . rejects . toThrow ( ) ;
77+ await store . cleanup ( ) ;
78+ } ) ;
79+
80+ it ( "backs up previous content before replacing the store" , async ( ) => {
81+ const store = await makeStorePath ( ) ;
82+ const first = makeStore ( "job-1" , true ) ;
83+ const second = makeStore ( "job-2" , false ) ;
84+
85+ await saveCronStore ( store . storePath , first ) ;
86+ await saveCronStore ( store . storePath , second ) ;
87+
88+ const currentRaw = await fs . readFile ( store . storePath , "utf-8" ) ;
89+ const backupRaw = await fs . readFile ( `${ store . storePath } .bak` , "utf-8" ) ;
90+ expect ( JSON . parse ( currentRaw ) ) . toEqual ( second ) ;
91+ expect ( JSON . parse ( backupRaw ) ) . toEqual ( first ) ;
92+ await store . cleanup ( ) ;
93+ } ) ;
4694} ) ;
0 commit comments