@@ -226,6 +226,118 @@ describe('MetadataManager', () => {
226226 } ) ;
227227 } ) ;
228228
229+ describe ( 'register — loader protocol filtering' , ( ) => {
230+ it ( 'should persist to datasource: protocol loaders' , async ( ) => {
231+ const dbLoader : MetadataLoader = {
232+ contract : { name : 'database' , protocol : 'datasource:' as const , capabilities : { read : true , write : true , watch : false , list : true } } ,
233+ load : vi . fn ( ) . mockResolvedValue ( { data : null } ) ,
234+ loadMany : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
235+ exists : vi . fn ( ) . mockResolvedValue ( false ) ,
236+ stat : vi . fn ( ) . mockResolvedValue ( null ) ,
237+ list : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
238+ save : vi . fn ( ) . mockResolvedValue ( { success : true } ) ,
239+ } ;
240+
241+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ dbLoader ] } ) ;
242+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
243+
244+ expect ( dbLoader . save ) . toHaveBeenCalledWith ( 'object' , 'account' , { name : 'account' } ) ;
245+ } ) ;
246+
247+ it ( 'should NOT persist to file: protocol loaders' , async ( ) => {
248+ const fsLoader : MetadataLoader = {
249+ contract : { name : 'filesystem' , protocol : 'file:' as const , capabilities : { read : true , write : true , watch : true , list : true } } ,
250+ load : vi . fn ( ) . mockResolvedValue ( { data : null } ) ,
251+ loadMany : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
252+ exists : vi . fn ( ) . mockResolvedValue ( false ) ,
253+ stat : vi . fn ( ) . mockResolvedValue ( null ) ,
254+ list : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
255+ save : vi . fn ( ) . mockResolvedValue ( { success : true } ) ,
256+ } ;
257+
258+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ fsLoader ] } ) ;
259+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
260+
261+ expect ( fsLoader . save ) . not . toHaveBeenCalled ( ) ;
262+ } ) ;
263+
264+ it ( 'should NOT persist to memory: protocol loaders' , async ( ) => {
265+ const memLoader : MetadataLoader = {
266+ contract : { name : 'memory' , protocol : 'memory:' as const , capabilities : { read : true , write : true , watch : false , list : true } } ,
267+ load : vi . fn ( ) . mockResolvedValue ( { data : null } ) ,
268+ loadMany : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
269+ exists : vi . fn ( ) . mockResolvedValue ( false ) ,
270+ stat : vi . fn ( ) . mockResolvedValue ( null ) ,
271+ list : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
272+ save : vi . fn ( ) . mockResolvedValue ( { success : true } ) ,
273+ } ;
274+
275+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ memLoader ] } ) ;
276+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
277+
278+ expect ( memLoader . save ) . not . toHaveBeenCalled ( ) ;
279+ } ) ;
280+
281+ it ( 'should still store in in-memory registry regardless of loaders' , async ( ) => {
282+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ ] } ) ;
283+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
284+
285+ const result = await m . get ( 'object' , 'account' ) ;
286+ expect ( result ) . toEqual ( { name : 'account' } ) ;
287+ } ) ;
288+ } ) ;
289+
290+ describe ( 'unregister — loader protocol filtering' , ( ) => {
291+ it ( 'should delete from datasource: protocol loaders' , async ( ) => {
292+ const deleteFn = vi . fn ( ) ;
293+ const dbLoader : MetadataLoader = {
294+ contract : { name : 'database' , protocol : 'datasource:' as const , capabilities : { read : true , write : true , watch : false , list : true } } ,
295+ load : vi . fn ( ) . mockResolvedValue ( { data : null } ) ,
296+ loadMany : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
297+ exists : vi . fn ( ) . mockResolvedValue ( false ) ,
298+ stat : vi . fn ( ) . mockResolvedValue ( null ) ,
299+ list : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
300+ save : vi . fn ( ) . mockResolvedValue ( { success : true } ) ,
301+ delete : deleteFn ,
302+ } as any ;
303+
304+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ dbLoader ] } ) ;
305+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
306+ await m . unregister ( 'object' , 'account' ) ;
307+
308+ expect ( deleteFn ) . toHaveBeenCalledWith ( 'object' , 'account' ) ;
309+ } ) ;
310+
311+ it ( 'should NOT delete from file: protocol loaders' , async ( ) => {
312+ const deleteFn = vi . fn ( ) ;
313+ const fsLoader : MetadataLoader = {
314+ contract : { name : 'filesystem' , protocol : 'file:' as const , capabilities : { read : true , write : true , watch : true , list : true } } ,
315+ load : vi . fn ( ) . mockResolvedValue ( { data : null } ) ,
316+ loadMany : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
317+ exists : vi . fn ( ) . mockResolvedValue ( false ) ,
318+ stat : vi . fn ( ) . mockResolvedValue ( null ) ,
319+ list : vi . fn ( ) . mockResolvedValue ( [ ] ) ,
320+ save : vi . fn ( ) . mockResolvedValue ( { success : true } ) ,
321+ delete : deleteFn ,
322+ } as any ;
323+
324+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ fsLoader ] } ) ;
325+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
326+ await m . unregister ( 'object' , 'account' ) ;
327+
328+ expect ( deleteFn ) . not . toHaveBeenCalled ( ) ;
329+ } ) ;
330+
331+ it ( 'should remove from in-memory registry' , async ( ) => {
332+ const m = new MetadataManager ( { formats : [ 'json' ] , loaders : [ ] } ) ;
333+ await m . register ( 'object' , 'account' , { name : 'account' } ) ;
334+ expect ( await m . get ( 'object' , 'account' ) ) . toEqual ( { name : 'account' } ) ;
335+
336+ await m . unregister ( 'object' , 'account' ) ;
337+ expect ( await m . get ( 'object' , 'account' ) ) . toBeUndefined ( ) ;
338+ } ) ;
339+ } ) ;
340+
229341 describe ( 'registerLoader' , ( ) => {
230342 it ( 'should register a new loader' , async ( ) => {
231343 const newLoader = new MemoryLoader ( ) ;
@@ -340,6 +452,7 @@ describe('MetadataPlugin', () => {
340452 registerLoader = vi . fn ( ) ;
341453 stopWatching = vi . fn ( ) ;
342454 setTypeRegistry = vi . fn ( ) ;
455+ setDatabaseDriver = vi . fn ( ) ;
343456 register = vi . fn ( ) ;
344457 } ;
345458 return { NodeMetadataManager : MockNodeMetadataManager } ;
@@ -382,6 +495,53 @@ describe('MetadataPlugin', () => {
382495 // start should call logger.info at least once
383496 expect ( ctx . logger . info ) . toHaveBeenCalled ( ) ;
384497 } ) ;
498+
499+ it ( 'should bridge driver service to MetadataManager in start()' , async ( ) => {
500+ const { MetadataPlugin } = await import ( './plugin.js' ) ;
501+ const plugin = new MetadataPlugin ( { rootDir : '/tmp/test' , watch : false } ) ;
502+
503+ const mockDriver = { name : 'mock-driver' , find : vi . fn ( ) , create : vi . fn ( ) } ;
504+ const services = new Map < string , any > ( ) ;
505+ services . set ( 'driver.mock-driver' , mockDriver ) ;
506+
507+ const ctx = createMockPluginContext ( ) ;
508+ ctx . getServices = vi . fn ( ) . mockReturnValue ( services ) ;
509+
510+ await plugin . init ( ctx ) ;
511+ await plugin . start ( ctx ) ;
512+
513+ // Verify setDatabaseDriver was called on the manager with the driver
514+ const manager = ( plugin as any ) . manager ;
515+ expect ( manager . setDatabaseDriver ) . toHaveBeenCalledWith ( mockDriver ) ;
516+ } ) ;
517+
518+ it ( 'should not fail when no driver service is available' , async ( ) => {
519+ const { MetadataPlugin } = await import ( './plugin.js' ) ;
520+ const plugin = new MetadataPlugin ( { rootDir : '/tmp/test' , watch : false } ) ;
521+
522+ const ctx = createMockPluginContext ( ) ;
523+ ctx . getServices = vi . fn ( ) . mockReturnValue ( new Map ( ) ) ;
524+
525+ await plugin . init ( ctx ) ;
526+ // Should not throw
527+ await expect ( plugin . start ( ctx ) ) . resolves . not . toThrow ( ) ;
528+
529+ // setDatabaseDriver should not have been called
530+ const manager = ( plugin as any ) . manager ;
531+ expect ( manager . setDatabaseDriver ) . not . toHaveBeenCalled ( ) ;
532+ } ) ;
533+
534+ it ( 'should gracefully handle getServices errors' , async ( ) => {
535+ const { MetadataPlugin } = await import ( './plugin.js' ) ;
536+ const plugin = new MetadataPlugin ( { rootDir : '/tmp/test' , watch : false } ) ;
537+
538+ const ctx = createMockPluginContext ( ) ;
539+ ctx . getServices = vi . fn ( ) . mockImplementation ( ( ) => { throw new Error ( 'services unavailable' ) ; } ) ;
540+
541+ await plugin . init ( ctx ) ;
542+ // Should not throw even when getServices fails
543+ await expect ( plugin . start ( ctx ) ) . resolves . not . toThrow ( ) ;
544+ } ) ;
385545} ) ;
386546
387547// ---------- Helpers ----------
0 commit comments