@@ -2745,3 +2745,107 @@ describe('auth lockout window is enforced again (#7169)', () => {
27452745 }
27462746 } ) ;
27472747} ) ;
2748+
2749+ describe ( 'SettingsService — a clear goes through an env pin, every other write does not (#30385)' , ( ) => {
2750+ // Two keys a pin can sit on: one below the global rung, one on it.
2751+ const PINNED = {
2752+ namespace : 'pinned' ,
2753+ version : 1 ,
2754+ label : 'Pinned' ,
2755+ scope : 'tenant' ,
2756+ readPermission : 'setup.access' ,
2757+ writePermission : 'setup.write' ,
2758+ specifiers : [
2759+ { type : 'text' , key : 'motto' , label : 'Motto' , scope : 'tenant' , default : 'none' } ,
2760+ { type : 'text' , key : 'banner' , label : 'Banner' , scope : 'global' , default : 'none' } ,
2761+ ] ,
2762+ } as any ;
2763+ const orgAdmin = { enforced : true , permissions : [ 'setup.access' , 'setup.write' ] , userId : 'usr_org_admin' } ;
2764+ const platformAdmin = {
2765+ enforced : true ,
2766+ permissions : [ 'setup.access' , 'setup.write' , 'manage_platform_settings' ] ,
2767+ userId : 'usr_platform_admin' ,
2768+ } ;
2769+ const refusalOf = ( p : Promise < unknown > ) => p . then ( ( ) => undefined , ( e : any ) => e ) ;
2770+
2771+ /** A stored value under a pin that is set after it: the operator's recovery. */
2772+ async function pinnedOverStored ( ) {
2773+ const env : Record < string , string | undefined > = { } ;
2774+ const svc = new SettingsService ( { env } ) ;
2775+ svc . registerManifest ( PINNED ) ;
2776+ await svc . set ( 'pinned' , 'motto' , 'Stored' ) ;
2777+ env . OS_PINNED_MOTTO = 'Pinned' ;
2778+ return { env, svc } ;
2779+ }
2780+
2781+ it ( 'a clear clears the stored value beneath the pin and changes nothing in force: the answer still reads env, locked' , async ( ) => {
2782+ const { env, svc } = await pinnedOverStored ( ) ;
2783+ const changes : unknown [ ] = [ ] ;
2784+ svc . subscribe ( 'pinned' , ( e ) => { changes . push ( e ) ; } ) ;
2785+
2786+ const answer = await svc . set ( 'pinned' , 'motto' , null ) ;
2787+ expect ( answer ) . toMatchObject ( { value : 'Pinned' , source : 'env' , locked : true } ) ;
2788+ expect ( answer . lockedReason ) . toContain ( 'OS_PINNED_MOTTO' ) ;
2789+ expect ( await svc . get ( 'pinned' , 'motto' ) ) . toMatchObject ( { value : 'Pinned' , source : 'env' , locked : true } ) ;
2790+ expect ( changes ) . toEqual ( [ expect . objectContaining ( { key : 'motto' , action : 'reset' } ) ] ) ;
2791+
2792+ // Lift the pin: the key falls back to its default, not to the old value.
2793+ delete env . OS_PINNED_MOTTO ;
2794+ expect ( await svc . get ( 'pinned' , 'motto' ) ) . toMatchObject ( { value : 'none' , source : 'default' , locked : false } ) ;
2795+ } ) ;
2796+
2797+ it ( '`undefined` is a clear too, alongside an ordinary write in the same batch' , async ( ) => {
2798+ const { env, svc } = await pinnedOverStored ( ) ;
2799+ await svc . setMany ( 'pinned' , { motto : undefined , banner : 'Saved' } ) ;
2800+ delete env . OS_PINNED_MOTTO ;
2801+ expect ( ( await svc . get ( 'pinned' , 'motto' ) ) . source ) . toBe ( 'default' ) ;
2802+ expect ( ( await svc . get ( 'pinned' , 'banner' ) ) . value ) . toBe ( 'Saved' ) ;
2803+ } ) ;
2804+
2805+ it ( 'every other write is refused SETTINGS_LOCKED as before, the empty string included, and the whole batch stores nothing' , async ( ) => {
2806+ const { env, svc } = await pinnedOverStored ( ) ;
2807+ for ( const value of [ 'Other' , '' ] ) {
2808+ const refusal = await refusalOf ( svc . set ( 'pinned' , 'motto' , value ) ) ;
2809+ expect ( refusal ) . toBeInstanceOf ( SettingsLockedError ) ;
2810+ expect ( refusal ) . toMatchObject ( { code : 'SETTINGS_LOCKED' , reason : 'locked-by-env' , key : 'motto' } ) ;
2811+ }
2812+ const batch = await refusalOf ( svc . setMany ( 'pinned' , { banner : 'Saved' , motto : 'Other' } ) ) ;
2813+ expect ( batch ) . toMatchObject ( { code : 'SETTINGS_LOCKED' , reason : 'locked-by-env' , key : 'motto' } ) ;
2814+
2815+ delete env . OS_PINNED_MOTTO ;
2816+ expect ( await svc . get ( 'pinned' , 'motto' ) ) . toMatchObject ( { value : 'Stored' , source : 'tenant' } ) ;
2817+ expect ( ( await svc . get ( 'pinned' , 'banner' ) ) . source ) . toBe ( 'default' ) ;
2818+ } ) ;
2819+
2820+ it ( 'a locked row at an upper rung still refuses a clear below it' , async ( ) => {
2821+ const { env, svc } = await pinnedOverStored ( ) ;
2822+ await ( svc as any ) . upsertRow ( {
2823+ namespace : 'pinned' , key : 'motto' , scope : 'global' , user_id : null , value : 'Platform motto' ,
2824+ value_enc : null , encrypted : false , locked : true , locked_reason : 'Set by the platform.' ,
2825+ } ) ;
2826+
2827+ const refusal = await refusalOf ( svc . set ( 'pinned' , 'motto' , null ) ) ;
2828+ expect ( refusal ) . toBeInstanceOf ( SettingsLockedError ) ;
2829+ expect ( refusal ) . toMatchObject ( { code : 'SETTINGS_LOCKED' , reason : 'locked-by-global' , key : 'motto' } ) ;
2830+
2831+ delete env . OS_PINNED_MOTTO ;
2832+ expect ( await svc . get ( 'pinned' , 'motto' ) ) . toMatchObject ( { value : 'Platform motto' , source : 'global' , locked : true } ) ;
2833+ } ) ;
2834+
2835+ it ( 'a locked row at the write\'s own rung refuses a non-platform clear, and the platform writes through it' , async ( ) => {
2836+ const env : Record < string , string | undefined > = { OS_PINNED_BANNER : 'Pinned banner' } ;
2837+ const svc = new SettingsService ( { env } ) ;
2838+ svc . registerManifest ( PINNED ) ;
2839+ await ( svc as any ) . upsertRow ( {
2840+ namespace : 'pinned' , key : 'banner' , scope : 'global' , user_id : null , value : 'Platform banner' ,
2841+ value_enc : null , encrypted : false , locked : true , locked_reason : 'Set by the platform.' ,
2842+ } ) ;
2843+
2844+ const refusal = await refusalOf ( svc . setMany ( 'pinned' , { banner : null } , orgAdmin ) ) ;
2845+ expect ( refusal ) . toBeInstanceOf ( SettingsLockedError ) ;
2846+ expect ( refusal ) . toMatchObject ( { code : 'SETTINGS_LOCKED' , reason : 'locked-by-global' , key : 'banner' } ) ;
2847+
2848+ const answer = await svc . setMany ( 'pinned' , { banner : null } , platformAdmin ) ;
2849+ expect ( answer . banner ) . toMatchObject ( { value : 'Pinned banner' , source : 'env' , locked : true } ) ;
2850+ } ) ;
2851+ } ) ;
0 commit comments