11import fs from "node:fs/promises" ;
22import path from "node:path" ;
33import { describe , expect , it , vi } from "vitest" ;
4- import { resolveAgentModelFallbackValues , resolveAgentModelPrimaryValue } from "./model-input.js" ;
54
65const { loadConfig, migrateLegacyConfig, readConfigFileSnapshot, validateConfigObject } =
76 await vi . importActual < typeof import ( "./config.js" ) > ( "./config.js" ) ;
@@ -64,18 +63,15 @@ function expectInvalidIssuePath(config: unknown, expectedPath: string) {
6463 }
6564}
6665
67- function expectRoutingAllowFromLegacySnapshot (
66+ function expectSnapshotInvalidRootKey (
6867 ctx : { snapshot : ConfigSnapshot ; parsed : unknown } ,
69- expectedAllowFrom : string [ ] ,
68+ key : string ,
7069) {
71- expect ( ctx . snapshot . valid ) . toBe ( true ) ;
72- expect ( ctx . snapshot . legacyIssues . some ( ( issue ) => issue . path === "routing.allowFrom" ) ) . toBe ( true ) ;
73- const parsed = ctx . parsed as {
74- routing ?: { allowFrom ?: string [ ] } ;
75- channels ?: unknown ;
76- } ;
77- expect ( parsed . routing ?. allowFrom ) . toEqual ( expectedAllowFrom ) ;
78- expect ( parsed . channels ) . toBeUndefined ( ) ;
70+ expect ( ctx . snapshot . valid ) . toBe ( false ) ;
71+ expect ( ctx . snapshot . legacyIssues ) . toEqual ( [ ] ) ;
72+ expect ( ctx . snapshot . issues [ 0 ] ?. path ) . toBe ( "" ) ;
73+ expect ( ctx . snapshot . issues [ 0 ] ?. message ) . toContain ( `"${ key } "` ) ;
74+ expect ( ( ctx . parsed as Record < string , unknown > ) [ key ] ) . toBeTruthy ( ) ;
7975}
8076
8177describe ( "legacy config detection" , ( ) => {
@@ -207,30 +203,25 @@ describe("legacy config detection", () => {
207203 } ) ;
208204 expect ( res . ok ) . toBe ( false ) ;
209205 if ( ! res . ok ) {
210- expect ( res . issues . some ( ( i ) => i . path === "agent.model" ) ) . toBe ( true ) ;
206+ expect ( res . issues [ 0 ] ?. path ) . toBe ( "" ) ;
207+ expect ( res . issues [ 0 ] ?. message ) . toContain ( '"agent"' ) ;
211208 }
212209 } ) ;
213- it ( "migrates telegram.requireMention to channels. telegram.groups.*. requireMention" , async ( ) => {
210+ it ( "does not rewrite removed telegram.requireMention migrations " , async ( ) => {
214211 const res = migrateLegacyConfig ( {
215212 telegram : { requireMention : false } ,
216213 } ) ;
217- expect ( res . changes ) . toContain (
218- 'Moved telegram.requireMention → channels.telegram.groups."*".requireMention.' ,
219- ) ;
220- expect ( res . config ?. channels ?. telegram ?. groups ?. [ "*" ] ?. requireMention ) . toBe ( false ) ;
221- expect (
222- ( res . config ?. channels ?. telegram as { requireMention ?: boolean } | undefined ) ?. requireMention ,
223- ) . toBeUndefined ( ) ;
214+ expect ( res . changes ) . toEqual ( [ ] ) ;
215+ expect ( res . config ) . toBeNull ( ) ;
224216 } ) ;
225- it ( "migrates messages.tts.enabled to messages.tts.auto " , async ( ) => {
217+ it ( "does not rewrite removed messages.tts.enabled migrations " , async ( ) => {
226218 const res = migrateLegacyConfig ( {
227219 messages : { tts : { enabled : true } } ,
228220 } ) ;
229- expect ( res . changes ) . toContain ( "Moved messages.tts.enabled → messages.tts.auto (always)." ) ;
230- expect ( res . config ?. messages ?. tts ?. auto ) . toBe ( "always" ) ;
231- expect ( res . config ?. messages ?. tts ?. enabled ) . toBeUndefined ( ) ;
221+ expect ( res . changes ) . toEqual ( [ ] ) ;
222+ expect ( res . config ) . toBeNull ( ) ;
232223 } ) ;
233- it ( "migrates legacy model config to agent.models + model lists " , async ( ) => {
224+ it ( "does not rewrite removed legacy model config migrations " , async ( ) => {
234225 const res = migrateLegacyConfig ( {
235226 agent : {
236227 model : "anthropic/claude-opus-4-5" ,
@@ -241,28 +232,12 @@ describe("legacy config detection", () => {
241232 modelAliases : { Opus : "anthropic/claude-opus-4-5" } ,
242233 } ,
243234 } ) ;
244-
245- expect ( resolveAgentModelPrimaryValue ( res . config ?. agents ?. defaults ?. model ) ) . toBe (
246- "anthropic/claude-opus-4-5" ,
247- ) ;
248- expect ( resolveAgentModelFallbackValues ( res . config ?. agents ?. defaults ?. model ) ) . toEqual ( [
249- "openai/gpt-4.1-mini" ,
250- ] ) ;
251- expect ( resolveAgentModelPrimaryValue ( res . config ?. agents ?. defaults ?. imageModel ) ) . toBe (
252- "openai/gpt-4.1-mini" ,
253- ) ;
254- expect ( resolveAgentModelFallbackValues ( res . config ?. agents ?. defaults ?. imageModel ) ) . toEqual ( [
255- "anthropic/claude-opus-4-5" ,
256- ] ) ;
257- expect ( res . config ?. agents ?. defaults ?. models ?. [ "anthropic/claude-opus-4-5" ] ) . toMatchObject ( {
258- alias : "Opus" ,
259- } ) ;
260- expect ( res . config ?. agents ?. defaults ?. models ?. [ "openai/gpt-4.1-mini" ] ) . toBeTruthy ( ) ;
261- expect ( ( res . config as { agent ?: unknown } | undefined ) ?. agent ) . toBeUndefined ( ) ;
235+ expect ( res . changes ) . toEqual ( [ ] ) ;
236+ expect ( res . config ) . toBeNull ( ) ;
262237 } ) ;
263- it ( "flags legacy config in snapshot" , async ( ) => {
238+ it ( "rejects removed routing.allowFrom in snapshot" , async ( ) => {
264239 await withSnapshotForConfig ( { routing : { allowFrom : [ "+15555550123" ] } } , async ( ctx ) => {
265- expectRoutingAllowFromLegacySnapshot ( ctx , [ "+15555550123" ] ) ;
240+ expectSnapshotInvalidRootKey ( ctx , "routing" ) ;
266241 } ) ;
267242 } ) ;
268243 it ( "flags top-level memorySearch as legacy in snapshot" , async ( ) => {
@@ -283,17 +258,9 @@ describe("legacy config detection", () => {
283258 } ,
284259 ) ;
285260 } ) ;
286- it ( "flags legacy provider sections in snapshot" , async ( ) => {
261+ it ( "rejects removed legacy provider sections in snapshot" , async ( ) => {
287262 await withSnapshotForConfig ( { whatsapp : { allowFrom : [ "+1555" ] } } , async ( ctx ) => {
288- expect ( ctx . snapshot . valid ) . toBe ( true ) ;
289- expect ( ctx . snapshot . legacyIssues . some ( ( issue ) => issue . path === "whatsapp" ) ) . toBe ( true ) ;
290-
291- const parsed = ctx . parsed as {
292- channels ?: unknown ;
293- whatsapp ?: unknown ;
294- } ;
295- expect ( parsed . channels ) . toBeUndefined ( ) ;
296- expect ( parsed . whatsapp ) . toBeTruthy ( ) ;
263+ expectSnapshotInvalidRootKey ( ctx , "whatsapp" ) ;
297264 } ) ;
298265 } ) ;
299266 it ( "does not auto-migrate claude-cli auth profile mode on load" , async ( ) => {
@@ -326,9 +293,18 @@ describe("legacy config detection", () => {
326293 expect ( parsed . auth ?. profiles ?. [ "anthropic:claude-cli" ] ?. mode ) . toBe ( "token" ) ;
327294 } ) ;
328295 } ) ;
329- it ( "flags routing.allowFrom in snapshot" , async ( ) => {
296+ it ( "still flags memorySearch in snapshot under the shorter support window" , async ( ) => {
297+ await withSnapshotForConfig (
298+ { memorySearch : { provider : "local" , fallback : "none" } } ,
299+ async ( ctx ) => {
300+ expect ( ctx . snapshot . valid ) . toBe ( true ) ;
301+ expect ( ctx . snapshot . legacyIssues . some ( ( issue ) => issue . path === "memorySearch" ) ) . toBe ( true ) ;
302+ } ,
303+ ) ;
304+ } ) ;
305+ it ( "rejects removed routing.allowFrom in snapshot with other values" , async ( ) => {
330306 await withSnapshotForConfig ( { routing : { allowFrom : [ "+1666" ] } } , async ( ctx ) => {
331- expectRoutingAllowFromLegacySnapshot ( ctx , [ "+1666" ] ) ;
307+ expectSnapshotInvalidRootKey ( ctx , "routing" ) ;
332308 } ) ;
333309 } ) ;
334310 it ( "rejects bindings[].match.provider on load" , async ( ) => {
0 commit comments