@@ -4,7 +4,13 @@ import fsp from "node:fs/promises";
44import os from "node:os" ;
55import path from "node:path" ;
66import JSON5 from "json5" ;
7- import { afterAll , beforeAll , describe , expect , it , vi } from "vitest" ;
7+ import { afterAll , afterEach , beforeAll , describe , expect , it , vi } from "vitest" ;
8+ import { executeSqliteQueryTakeFirstSync , getNodeSqliteKysely } from "../infra/kysely-sync.js" ;
9+ import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js" ;
10+ import {
11+ closeOpenClawStateDatabaseForTest ,
12+ openOpenClawStateDatabase ,
13+ } from "../state/openclaw-state-db.js" ;
814import { createConfigIO } from "./io.js" ;
915import {
1016 maybeRecoverSuspiciousConfigRead ,
@@ -17,6 +23,7 @@ import {
1723import type { ConfigFileSnapshot } from "./types.js" ;
1824
1925const CONFIG_CLOBBER_SNAPSHOT_LIMIT = 32 ;
26+ type ConfigHealthDatabase = Pick < OpenClawStateKyselyDatabase , "config_health_entries" > ;
2027
2128describe ( "config observe recovery" , ( ) => {
2229 let fixtureRoot = "" ;
@@ -41,9 +48,31 @@ describe("config observe recovery", () => {
4148 } ) ;
4249
4350 afterAll ( async ( ) => {
51+ closeOpenClawStateDatabaseForTest ( ) ;
4452 await fsp . rm ( fixtureRoot , { recursive : true , force : true } ) ;
4553 } ) ;
4654
55+ afterEach ( ( ) => {
56+ closeOpenClawStateDatabaseForTest ( ) ;
57+ } ) ;
58+
59+ function readConfigHealthRow ( home : string , configPath : string ) {
60+ const { db } = openOpenClawStateDatabase ( { env : { HOME : home } as NodeJS . ProcessEnv } ) ;
61+ const healthDb = getNodeSqliteKysely < ConfigHealthDatabase > ( db ) ;
62+ return executeSqliteQueryTakeFirstSync (
63+ db ,
64+ healthDb
65+ . selectFrom ( "config_health_entries" )
66+ . select ( [
67+ "config_path" ,
68+ "last_known_good_json" ,
69+ "last_promoted_good_json" ,
70+ "last_observed_suspicious_signature" ,
71+ ] )
72+ . where ( "config_path" , "=" , configPath ) ,
73+ ) ;
74+ }
75+
4776 async function seedConfig ( configPath : string , config : Record < string , unknown > ) : Promise < void > {
4877 await fsp . mkdir ( path . dirname ( configPath ) , { recursive : true } ) ;
4978 await fsp . writeFile ( configPath , `${ JSON . stringify ( config , null , 2 ) } \n` , "utf-8" ) ;
@@ -243,47 +272,6 @@ describe("config observe recovery", () => {
243272 } ;
244273 }
245274
246- function withAsyncHealthWriteFailure (
247- deps : ObserveRecoveryDeps ,
248- healthPath : string ,
249- ) : ObserveRecoveryDeps {
250- const writeFile = deps . fs . promises . writeFile . bind ( deps . fs . promises ) ;
251- return {
252- ...deps ,
253- fs : {
254- ...deps . fs ,
255- promises : {
256- ...deps . fs . promises ,
257- writeFile : async ( target , data , options ) => {
258- if ( target === healthPath ) {
259- throw new Error ( "health write failed" ) ;
260- }
261- return await writeFile ( target , data , options ) ;
262- } ,
263- } ,
264- } ,
265- } ;
266- }
267-
268- function withSyncHealthWriteFailure (
269- deps : ObserveRecoveryDeps ,
270- healthPath : string ,
271- ) : ObserveRecoveryDeps {
272- const writeFileSync = deps . fs . writeFileSync . bind ( deps . fs ) ;
273- return {
274- ...deps ,
275- fs : {
276- ...deps . fs ,
277- writeFileSync : ( target , data , options ) => {
278- if ( target === healthPath ) {
279- throw new Error ( "health write failed" ) ;
280- }
281- return writeFileSync ( target , data , options ) ;
282- } ,
283- } ,
284- } ;
285- }
286-
287275 it ( "auto-restores suspicious update-channel-only roots from backup" , async ( ) => {
288276 await withSuiteHome ( async ( home ) => {
289277 const { deps, configPath, auditPath, warn } = makeDeps ( home ) ;
@@ -953,43 +941,42 @@ describe("config observe recovery", () => {
953941 } ) ;
954942 } ) ;
955943
956- it ( "logs async health- state write failures " , async ( ) => {
944+ it ( "writes async health state to SQLite " , async ( ) => {
957945 await withSuiteHome ( async ( home ) => {
958946 const { deps, configPath, warn } = makeDeps ( home ) ;
959947 const snapshot = await makeSnapshot ( configPath , recoverableTelegramConfig ) ;
960- const healthPath = path . join ( home , ".openclaw" , "logs" , "config-health.json" ) ;
961948
962949 await expect (
963- promoteConfigSnapshotToLastKnownGood ( {
964- deps : withAsyncHealthWriteFailure ( deps , healthPath ) ,
965- snapshot,
966- logger : deps . logger ,
967- } ) ,
950+ promoteConfigSnapshotToLastKnownGood ( { deps, snapshot, logger : deps . logger } ) ,
968951 ) . resolves . toBe ( true ) ;
969952
970- expectWarnContaining (
971- warn ,
972- `Config health-state write failed: ${ healthPath } : health write failed` ,
973- ) ;
953+ await expectPathMissing ( path . join ( home , ".openclaw" , "logs" , "config-health.json" ) ) ;
954+ const row = readConfigHealthRow ( home , configPath ) ;
955+ expect ( row ) . toMatchObject ( {
956+ config_path : configPath ,
957+ last_known_good_json : expect . any ( String ) ,
958+ last_promoted_good_json : expect . any ( String ) ,
959+ last_observed_suspicious_signature : null ,
960+ } ) ;
961+ expectWarnNotContaining ( warn , "Config health-state write failed" ) ;
974962 } ) ;
975963 } ) ;
976964
977- it ( "logs sync health- state write failures " , async ( ) => {
965+ it ( "writes sync health state to SQLite " , async ( ) => {
978966 await withSuiteHome ( async ( home ) => {
979967 const { deps, configPath, warn } = makeDeps ( home ) ;
980- const healthPath = path . join ( home , ".openclaw" , "logs" , "config-health.json" ) ;
981968 await seedConfigBackup ( configPath , recoverableTelegramConfig ) ;
982969 await writeClobberedUpdateChannel ( configPath ) ;
983970
984- recoverClobberedUpdateChannelSync ( {
985- deps : withSyncHealthWriteFailure ( deps , healthPath ) ,
986- configPath,
987- } ) ;
971+ recoverClobberedUpdateChannelSync ( { deps, configPath } ) ;
988972
989- expectWarnContaining (
990- warn ,
991- `Config health-state write failed: ${ healthPath } : health write failed` ,
992- ) ;
973+ await expectPathMissing ( path . join ( home , ".openclaw" , "logs" , "config-health.json" ) ) ;
974+ const row = readConfigHealthRow ( home , configPath ) ;
975+ expect ( row ) . toMatchObject ( {
976+ config_path : configPath ,
977+ last_observed_suspicious_signature : expect . any ( String ) ,
978+ } ) ;
979+ expectWarnNotContaining ( warn , "Config health-state write failed" ) ;
993980 } ) ;
994981 } ) ;
995982
0 commit comments