@@ -16,6 +16,12 @@ import { IConfigurationService, ConfigurationTarget } from '../../../../platform
1616import { WorkspaceService } from '../../../services/configuration/browser/configurationService.js' ;
1717import { IEnvironmentService } from '../../../../platform/environment/common/environment.js' ;
1818import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js' ;
19+ // --- Start Positron ---
20+ import { ILogService , NullLogService } from '../../../../platform/log/common/log.js' ;
21+ import { INotificationService , NoOpNotification } from '../../../../platform/notification/common/notification.js' ;
22+ import { IProductService } from '../../../../platform/product/common/productService.js' ;
23+ import { Extensions as ConfigurationMigrationExtensions , IConfigurationMigrationRegistry } from '../../../common/configuration.js' ;
24+ // --- End Positron ---
1925
2026suite ( 'MainThreadConfiguration' , function ( ) {
2127
@@ -60,6 +66,10 @@ suite('MainThreadConfiguration', function () {
6066 instantiationService . stub ( IEnvironmentService , {
6167 isBuilt : false
6268 } ) ;
69+ // --- Start Positron ---
70+ instantiationService . stub ( ILogService , new NullLogService ( ) ) ;
71+ instantiationService . stub ( IProductService , { trustedExtensionPublishers : [ 'posit' , 'rstudio' ] } ) ;
72+ // --- End Positron ---
6373 } ) ;
6474
6575 teardown ( ( ) => {
@@ -236,4 +246,104 @@ suite('MainThreadConfiguration', function () {
236246
237247 assert . strictEqual ( ConfigurationTarget . WORKSPACE_FOLDER , target . args [ 0 ] [ 3 ] ) ;
238248 } ) ;
249+
250+ // --- Start Positron ---
251+ suite ( 'registerConfigurationMigrations' , function ( ) {
252+
253+ const OWNED_KEY = 'extHostConfigMigration.oldKey' ;
254+ const OWNER_EXT = 'test.owner' ;
255+
256+ suiteSetup ( ( ) => {
257+ Registry . as < IConfigurationRegistry > ( Extensions . Configuration ) . registerConfiguration ( {
258+ id : 'extHostConfigMigration' ,
259+ type : 'object' ,
260+ extensionInfo : { id : OWNER_EXT } ,
261+ properties : {
262+ [ OWNED_KEY ] : { type : 'boolean' , description : 'test key for migration tests' }
263+ }
264+ } ) ;
265+ } ) ;
266+
267+ let migrationRegistry : IConfigurationMigrationRegistry ;
268+ let registerSpy : sinon . SinonSpy ;
269+ let warnSpy : sinon . SinonSpy ;
270+
271+ setup ( ( ) => {
272+ migrationRegistry = Registry . as < IConfigurationMigrationRegistry > ( ConfigurationMigrationExtensions . ConfigurationMigration ) ;
273+ registerSpy = sinon . spy ( migrationRegistry , 'registerConfigurationMigrations' ) ;
274+ const logService = new NullLogService ( ) ;
275+ warnSpy = sinon . spy ( logService , 'warn' ) ;
276+ instantiationService . stub ( ILogService , logService ) ;
277+ instantiationService . stub ( INotificationService , { notify : ( ) => new NoOpNotification ( ) } ) ;
278+ instantiationService . stub ( IWorkspaceContextService , < IWorkspaceContextService > { getWorkbenchState : ( ) => WorkbenchState . FOLDER } ) ;
279+ } ) ;
280+
281+ teardown ( ( ) => {
282+ registerSpy . restore ( ) ;
283+ } ) ;
284+
285+ test ( 'owned key is accepted and migrateFn maps value correctly' , function ( ) {
286+ const testObject = instantiationService . createInstance ( MainThreadConfiguration , SingleProxyRPCProtocol ( proxy ) ) ;
287+
288+ testObject . $registerConfigurationMigrations ( OWNER_EXT , [ { key : OWNED_KEY , migrateTo : 'extHostConfigMigration.newKey' } ] ) ;
289+
290+ assert . ok ( registerSpy . calledOnce , 'registerConfigurationMigrations should be called once' ) ;
291+ const [ migrations ] = registerSpy . args [ 0 ] as [ Array < { key : string ; migrateFn : ( v : unknown , accessor : ( k : string ) => unknown ) => unknown } > ] ;
292+ assert . strictEqual ( migrations . length , 1 ) ;
293+ assert . strictEqual ( migrations [ 0 ] . key , OWNED_KEY ) ;
294+ // accessor returns undefined → new key not yet set → migration copies value
295+ const result = migrations [ 0 ] . migrateFn ( 'testValue' , ( ) => undefined ) ;
296+ assert . deepStrictEqual ( result , [
297+ [ OWNED_KEY , { value : undefined } ] ,
298+ [ 'extHostConfigMigration.newKey' , { value : 'testValue' } ] ,
299+ ] ) ;
300+ } ) ;
301+
302+ test ( 'unowned key is rejected and a warning is logged' , function ( ) {
303+ const testObject = instantiationService . createInstance ( MainThreadConfiguration , SingleProxyRPCProtocol ( proxy ) ) ;
304+
305+ testObject . $registerConfigurationMigrations ( 'other.extension' , [ { key : OWNED_KEY , migrateTo : 'extHostConfigMigration.newKey' } ] ) ;
306+
307+ assert . ok ( warnSpy . calledOnce , 'warn should be called for unowned key' ) ;
308+ assert . ok ( registerSpy . notCalled , 'registerConfigurationMigrations should not be called' ) ;
309+ } ) ;
310+
311+ test ( 'trusted publisher bypasses ownership check' , function ( ) {
312+ const testObject = instantiationService . createInstance ( MainThreadConfiguration , SingleProxyRPCProtocol ( proxy ) ) ;
313+
314+ testObject . $registerConfigurationMigrations ( 'posit.extension' , [ { key : OWNED_KEY , migrateTo : 'extHostConfigMigration.newKey' } ] ) ;
315+
316+ assert . ok ( registerSpy . calledOnce , 'posit publisher should be able to migrate unowned key' ) ;
317+ assert . ok ( warnSpy . notCalled , 'no warning should be logged for posit publisher' ) ;
318+
319+ registerSpy . resetHistory ( ) ;
320+ warnSpy . resetHistory ( ) ;
321+
322+ testObject . $registerConfigurationMigrations ( 'rstudio.rstudio-workbench' , [ { key : OWNED_KEY , migrateTo : 'extHostConfigMigration.newKey' } ] ) ;
323+
324+ assert . ok ( registerSpy . calledOnce , 'rstudio publisher should be able to migrate unowned key' ) ;
325+ assert . ok ( warnSpy . notCalled , 'no warning should be logged for rstudio publisher' ) ;
326+ } ) ;
327+
328+ test ( 'unregistered key is accepted when it matches extension namespace' , function ( ) {
329+ const testObject = instantiationService . createInstance ( MainThreadConfiguration , SingleProxyRPCProtocol ( proxy ) ) ;
330+ const droppedKey = `${ OWNER_EXT } .droppedKey` ; // never registered; simulates rename-and-remove
331+
332+ testObject . $registerConfigurationMigrations ( OWNER_EXT , [ { key : droppedKey , migrateTo : 'extHostConfigMigration.newKey' } ] ) ;
333+
334+ assert . ok ( registerSpy . calledOnce , 'migration for unregistered key in extension namespace should be accepted' ) ;
335+ assert . ok ( warnSpy . notCalled , 'no warning should be logged for namespace-owned key' ) ;
336+ } ) ;
337+
338+ test ( 'unregistered key outside extension namespace is rejected' , function ( ) {
339+ const testObject = instantiationService . createInstance ( MainThreadConfiguration , SingleProxyRPCProtocol ( proxy ) ) ;
340+ const foreignKey = 'other.publisher.droppedKey' ; // unregistered and wrong namespace
341+
342+ testObject . $registerConfigurationMigrations ( OWNER_EXT , [ { key : foreignKey , migrateTo : 'extHostConfigMigration.newKey' } ] ) ;
343+
344+ assert . ok ( warnSpy . calledOnce , 'warn should be called for unregistered key outside extension namespace' ) ;
345+ assert . ok ( registerSpy . notCalled , 'migration should not be registered' ) ;
346+ } ) ;
347+ } ) ;
348+ // --- End Positron ---
239349} ) ;
0 commit comments