@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
22import type { OpenClawConfig } from "../config/types.openclaw.js" ;
33import {
44 PluginLruCache ,
5+ createConfigScopedPromiseLoader ,
56 resolveConfigScopedRuntimeCacheValue ,
67 type ConfigScopedRuntimeCache ,
78} from "./plugin-cache-primitives.js" ;
@@ -84,3 +85,64 @@ describe("resolveConfigScopedRuntimeCacheValue", () => {
8485 expect ( load ) . toHaveBeenCalledOnce ( ) ;
8586 } ) ;
8687} ) ;
88+
89+ describe ( "createConfigScopedPromiseLoader" , ( ) => {
90+ it ( "dedupes concurrent default loads" , async ( ) => {
91+ let calls = 0 ;
92+ const loader = createConfigScopedPromiseLoader ( async ( ) => `loaded-${ ++ calls } ` ) ;
93+
94+ await expect ( Promise . all ( [ loader . load ( ) , loader . load ( ) ] ) ) . resolves . toEqual ( [
95+ "loaded-1" ,
96+ "loaded-1" ,
97+ ] ) ;
98+ await expect ( loader . load ( ) ) . resolves . toBe ( "loaded-1" ) ;
99+ expect ( calls ) . toBe ( 1 ) ;
100+ } ) ;
101+
102+ it ( "caches loads by config object" , async ( ) => {
103+ const firstConfig = { plugins : { load : { disabled : true } } } as OpenClawConfig ;
104+ const secondConfig = { plugins : { load : { disabled : false } } } as OpenClawConfig ;
105+ const load = vi . fn ( async ( config ?: OpenClawConfig ) =>
106+ config === firstConfig ? "first" : "second" ,
107+ ) ;
108+ const loader = createConfigScopedPromiseLoader ( load ) ;
109+
110+ await expect ( loader . load ( firstConfig ) ) . resolves . toBe ( "first" ) ;
111+ await expect ( loader . load ( firstConfig ) ) . resolves . toBe ( "first" ) ;
112+ await expect ( loader . load ( secondConfig ) ) . resolves . toBe ( "second" ) ;
113+
114+ expect ( load ) . toHaveBeenCalledTimes ( 2 ) ;
115+ } ) ;
116+
117+ it ( "evicts rejected loads so retries can recover" , async ( ) => {
118+ const config = { } as OpenClawConfig ;
119+ let calls = 0 ;
120+ const loader = createConfigScopedPromiseLoader ( async ( ) => {
121+ calls += 1 ;
122+ if ( calls === 1 ) {
123+ throw new Error ( "transient" ) ;
124+ }
125+ return "recovered" ;
126+ } ) ;
127+
128+ await expect ( loader . load ( config ) ) . rejects . toThrow ( "transient" ) ;
129+ await expect ( loader . load ( config ) ) . resolves . toBe ( "recovered" ) ;
130+ expect ( calls ) . toBe ( 2 ) ;
131+ } ) ;
132+
133+ it ( "clears default and config-scoped entries" , async ( ) => {
134+ const config = { } as OpenClawConfig ;
135+ let calls = 0 ;
136+ const loader = createConfigScopedPromiseLoader (
137+ async ( owner ?: OpenClawConfig ) => `${ owner ? "config" : "default" } -${ ++ calls } ` ,
138+ ) ;
139+
140+ await expect ( loader . load ( ) ) . resolves . toBe ( "default-1" ) ;
141+ await expect ( loader . load ( config ) ) . resolves . toBe ( "config-2" ) ;
142+
143+ loader . clear ( ) ;
144+
145+ await expect ( loader . load ( ) ) . resolves . toBe ( "default-3" ) ;
146+ await expect ( loader . load ( config ) ) . resolves . toBe ( "config-4" ) ;
147+ } ) ;
148+ } ) ;
0 commit comments