11import { describe , expect , it , vi } from "vitest" ;
2+ import { REDACTED_SENTINEL } from "../../config/redact-snapshot.js" ;
23
34let writtenConfig : unknown = null ;
5+ let loadedConfig : unknown = {
6+ skills : {
7+ entries : { } ,
8+ } ,
9+ } ;
410
511vi . mock ( "../../config/config.js" , ( ) => {
612 return {
7- loadConfig : ( ) => ( {
8- skills : {
9- entries : { } ,
10- } ,
11- } ) ,
13+ loadConfig : ( ) => loadedConfig ,
1214 writeConfigFile : async ( cfg : unknown ) => {
1315 writtenConfig = cfg ;
1416 } ,
@@ -20,6 +22,11 @@ const { skillsHandlers } = await import("./skills.js");
2022describe ( "skills.update" , ( ) => {
2123 it ( "strips embedded CR/LF from apiKey" , async ( ) => {
2224 writtenConfig = null ;
25+ loadedConfig = {
26+ skills : {
27+ entries : { } ,
28+ } ,
29+ } ;
2330
2431 let ok : boolean | null = null ;
2532 let error : unknown = null ;
@@ -53,6 +60,11 @@ describe("skills.update", () => {
5360
5461 it ( "redacts apiKey and secret env values from the response but writes full values to config" , async ( ) => {
5562 writtenConfig = null ;
63+ loadedConfig = {
64+ skills : {
65+ entries : { } ,
66+ } ,
67+ } ;
5668
5769 let responseResult : unknown = null ;
5870 await skillsHandlers [ "skills.update" ] ( {
@@ -90,10 +102,57 @@ describe("skills.update", () => {
90102
91103 // Response must not expose plaintext secrets
92104 const config = ( responseResult as { config : Record < string , unknown > } ) . config ;
93- expect ( config . apiKey ) . toBe ( "__OPENCLAW_REDACTED__" ) ;
105+ expect ( config . apiKey ) . toBe ( REDACTED_SENTINEL ) ;
94106 const env = config . env as Record < string , string > ;
95- expect ( env . GEMINI_API_KEY ) . toBe ( "__OPENCLAW_REDACTED__" ) ;
107+ expect ( env . GEMINI_API_KEY ) . toBe ( REDACTED_SENTINEL ) ;
96108 // Non-secret env values should still be present
97109 expect ( env . BRAVE_REGION ) . toBe ( "us" ) ;
98110 } ) ;
111+
112+ it ( "keeps existing secrets when clients submit redacted sentinel values" , async ( ) => {
113+ writtenConfig = null ;
114+ loadedConfig = {
115+ skills : {
116+ entries : {
117+ "demo-skill" : {
118+ apiKey : "secret-api-key-123" ,
119+ env : {
120+ GEMINI_API_KEY : "secret-env-key-456" ,
121+ BRAVE_REGION : "us" ,
122+ } ,
123+ } ,
124+ } ,
125+ } ,
126+ } ;
127+
128+ await skillsHandlers [ "skills.update" ] ( {
129+ params : {
130+ skillKey : "demo-skill" ,
131+ apiKey : REDACTED_SENTINEL ,
132+ env : {
133+ GEMINI_API_KEY : REDACTED_SENTINEL ,
134+ BRAVE_REGION : "eu" ,
135+ } ,
136+ } ,
137+ req : { } as never ,
138+ client : null as never ,
139+ isWebchatConnect : ( ) => false ,
140+ context : { } as never ,
141+ respond : ( ) => { } ,
142+ } ) ;
143+
144+ expect ( writtenConfig ) . toMatchObject ( {
145+ skills : {
146+ entries : {
147+ "demo-skill" : {
148+ apiKey : "secret-api-key-123" ,
149+ env : {
150+ GEMINI_API_KEY : "secret-env-key-456" ,
151+ BRAVE_REGION : "eu" ,
152+ } ,
153+ } ,
154+ } ,
155+ } ,
156+ } ) ;
157+ } ) ;
99158} ) ;
0 commit comments