@@ -468,6 +468,289 @@ describe('CreatorsService', () => {
468468 } ) ;
469469 } ) ;
470470
471+ describe ( 'happy path - createPlan' , ( ) => {
472+ it ( 'should create a plan with valid inputs and return plan with id, creator, asset, amount, intervalDays' , ( ) => {
473+ // Act
474+ const plan = service . createPlan ( 'creator1' , 'USDC' , '100.50' , 30 ) ;
475+
476+ // Assert
477+ expect ( plan ) . toBeDefined ( ) ;
478+ expect ( plan . id ) . toBeGreaterThan ( 0 ) ;
479+ expect ( plan . creator ) . toBe ( 'creator1' ) ;
480+ expect ( plan . asset ) . toBe ( 'USDC' ) ;
481+ expect ( plan . amount ) . toBe ( '100.50' ) ;
482+ expect ( plan . intervalDays ) . toBe ( 30 ) ;
483+ } ) ;
484+
485+ it ( 'should publish PlanCreatedEvent when EventBus is wired' , ( ) => {
486+ const publish = jest . fn ( ) ;
487+ const module = Test . createTestingModule ( {
488+ providers : [
489+ CreatorsService ,
490+ { provide : EventBus , useValue : { publish } } ,
491+ {
492+ provide : getRepositoryToken ( User ) ,
493+ useValue : {
494+ createQueryBuilder : jest . fn ( ( ) => mockQueryBuilder ) ,
495+ } ,
496+ } ,
497+ ] ,
498+ } ) ;
499+
500+ return module . compile ( ) . then ( ( m ) => {
501+ const svc = m . get < CreatorsService > ( CreatorsService ) ;
502+ svc . createPlan ( 'creator1' , 'XLM' , '50' , 7 ) ;
503+
504+ expect ( publish ) . toHaveBeenCalledTimes ( 1 ) ;
505+ expect ( publish . mock . calls [ 0 ] [ 0 ] . constructor . name ) . toBe ( 'PlanCreatedEvent' ) ;
506+ } ) ;
507+ } ) ;
508+ } ) ;
509+
510+ describe ( 'happy path - getPlan' , ( ) => {
511+ it ( 'should retrieve a created plan by id' , ( ) => {
512+ // Arrange
513+ const plan1 = service . createPlan ( 'creator1' , 'USDC' , '100' , 30 ) ;
514+
515+ // Act
516+ const retrieved = service . getPlan ( plan1 . id ) ;
517+
518+ // Assert
519+ expect ( retrieved ) . toBeDefined ( ) ;
520+ expect ( retrieved ?. id ) . toBe ( plan1 . id ) ;
521+ expect ( retrieved ?. creator ) . toBe ( 'creator1' ) ;
522+ } ) ;
523+
524+ it ( 'should return undefined for non-existent plan id' , ( ) => {
525+ // Act
526+ const retrieved = service . getPlan ( 99999 ) ;
527+
528+ // Assert
529+ expect ( retrieved ) . toBeUndefined ( ) ;
530+ } ) ;
531+ } ) ;
532+
533+ describe ( 'happy path - getCreatorPlans' , ( ) => {
534+ it ( 'should return all plans for a creator' , ( ) => {
535+ // Arrange
536+ service . createPlan ( 'alice' , 'USDC' , '100' , 30 ) ;
537+ service . createPlan ( 'alice' , 'XLM' , '50' , 7 ) ;
538+ service . createPlan ( 'bob' , 'USDC' , '75' , 14 ) ;
539+
540+ // Act
541+ const alicePlans = service . getCreatorPlans ( 'alice' ) ;
542+
543+ // Assert
544+ expect ( alicePlans ) . toHaveLength ( 2 ) ;
545+ expect ( alicePlans . every ( ( p ) => p . creator === 'alice' ) ) . toBe ( true ) ;
546+ } ) ;
547+
548+ it ( 'should return empty array for creator with no plans' , ( ) => {
549+ // Act
550+ const plans = service . getCreatorPlans ( 'nonexistent' ) ;
551+
552+ // Assert
553+ expect ( plans ) . toHaveLength ( 0 ) ;
554+ } ) ;
555+ } ) ;
556+
557+ describe ( 'happy path - findAllPlans' , ( ) => {
558+ it ( 'should return all plans paginated with default limit' , ( ) => {
559+ // Arrange
560+ service . createPlan ( 'alice' , 'USDC' , '100' , 30 ) ;
561+ service . createPlan ( 'bob' , 'XLM' , '50' , 7 ) ;
562+
563+ // Act
564+ const result = service . findAllPlans ( { limit : 20 } ) ;
565+
566+ // Assert
567+ expect ( result . data ) . toHaveLength ( 2 ) ;
568+ expect ( result . limit ) . toBe ( 20 ) ;
569+ expect ( result . hasMore ) . toBe ( false ) ;
570+ // nextCursor is the last plan's ID when there are results
571+ expect ( result . nextCursor ) . toBe ( '2' ) ;
572+ } ) ;
573+
574+ it ( 'should paginate correctly with cursor and limit' , ( ) => {
575+ // Arrange
576+ service . createPlan ( 'a' , 'USDC' , '1' , 1 ) ;
577+ service . createPlan ( 'b' , 'USDC' , '2' , 2 ) ;
578+ service . createPlan ( 'c' , 'USDC' , '3' , 3 ) ;
579+
580+ // Act - first page
581+ const page1 = service . findAllPlans ( { limit : 1 } ) ;
582+
583+ // Assert first page
584+ expect ( page1 . data ) . toHaveLength ( 1 ) ;
585+ expect ( page1 . hasMore ) . toBe ( true ) ;
586+ expect ( page1 . nextCursor ) . toBe ( '1' ) ;
587+
588+ // Act - second page
589+ const page2 = service . findAllPlans ( {
590+ cursor : page1 . nextCursor ?? undefined ,
591+ limit : 1 ,
592+ } ) ;
593+
594+ // Assert second page
595+ expect ( page2 . data ) . toHaveLength ( 1 ) ;
596+ expect ( page2 . data [ 0 ] . id ) . toBeGreaterThan ( page1 . data [ 0 ] . id ) ;
597+ } ) ;
598+
599+ it ( 'should return data with correct shape' , ( ) => {
600+ // Arrange
601+ service . createPlan ( 'creator1' , 'USDC' , '100.50' , 30 ) ;
602+
603+ // Act
604+ const result = service . findAllPlans ( { limit : 20 } ) ;
605+
606+ // Assert
607+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'id' ) ;
608+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'creator' ) ;
609+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'asset' ) ;
610+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'amount' ) ;
611+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'intervalDays' ) ;
612+ } ) ;
613+ } ) ;
614+
615+ describe ( 'happy path - findCreatorPlans' , ( ) => {
616+ it ( 'should return paginated plans for a specific creator' , ( ) => {
617+ // Arrange
618+ service . createPlan ( 'alice' , 'USDC' , '100' , 30 ) ;
619+ service . createPlan ( 'alice' , 'XLM' , '50' , 7 ) ;
620+ service . createPlan ( 'bob' , 'USDC' , '75' , 14 ) ;
621+
622+ // Act
623+ const result = service . findCreatorPlans ( 'alice' , { limit : 20 } ) ;
624+
625+ // Assert
626+ expect ( result . data ) . toHaveLength ( 2 ) ;
627+ expect ( result . data . every ( ( p ) => p . creator === 'alice' ) ) . toBe ( true ) ;
628+ expect ( result . hasMore ) . toBe ( false ) ;
629+ } ) ;
630+
631+ it ( 'should handle pagination with cursor for creator plans' , ( ) => {
632+ // Arrange
633+ service . createPlan ( 'alice' , 'USDC' , '1' , 1 ) ;
634+ service . createPlan ( 'alice' , 'XLM' , '2' , 2 ) ;
635+ service . createPlan ( 'alice' , 'ETH' , '3' , 3 ) ;
636+
637+ // Act
638+ const page1 = service . findCreatorPlans ( 'alice' , { limit : 1 } ) ;
639+ const page2 = service . findCreatorPlans ( 'alice' , {
640+ cursor : page1 . nextCursor ?? undefined ,
641+ limit : 1 ,
642+ } ) ;
643+
644+ // Assert
645+ expect ( page1 . data ) . toHaveLength ( 1 ) ;
646+ expect ( page2 . data ) . toHaveLength ( 1 ) ;
647+ expect ( page1 . data [ 0 ] . id ) . not . toBe ( page2 . data [ 0 ] . id ) ;
648+ } ) ;
649+
650+ it ( 'should return empty pagination for creator with no plans' , ( ) => {
651+ // Act
652+ const result = service . findCreatorPlans ( 'nonexistent' , { limit : 20 } ) ;
653+
654+ // Assert
655+ expect ( result . data ) . toHaveLength ( 0 ) ;
656+ expect ( result . hasMore ) . toBe ( false ) ;
657+ expect ( result . nextCursor ) . toBeNull ( ) ;
658+ } ) ;
659+ } ) ;
660+
661+ describe ( 'happy path - listCreators' , ( ) => {
662+ it ( 'should list all plans without chain merge' , async ( ) => {
663+ // Arrange
664+ service . createPlan ( 'alice' , 'USDC' , '100' , 30 ) ;
665+ service . createPlan ( 'bob' , 'XLM' , '50' , 7 ) ;
666+
667+ // Act
668+ const result = await service . listCreators ( false ) ;
669+
670+ // Assert
671+ expect ( result ) . toHaveLength ( 2 ) ;
672+ expect ( result [ 0 ] ) . toHaveProperty ( 'id' ) ;
673+ expect ( result [ 0 ] ) . toHaveProperty ( 'creator' ) ;
674+ expect ( result [ 0 ] ) . toHaveProperty ( 'asset' ) ;
675+ } ) ;
676+
677+ it ( 'should return sorted plans by id' , async ( ) => {
678+ // Arrange
679+ const plan1 = service . createPlan ( 'alice' , 'USDC' , '100' , 30 ) ;
680+ const plan2 = service . createPlan ( 'bob' , 'XLM' , '50' , 7 ) ;
681+
682+ // Act
683+ const result = await service . listCreators ( false ) ;
684+
685+ // Assert
686+ expect ( result [ 0 ] . id ) . toBe ( plan1 . id ) ;
687+ expect ( result [ 1 ] . id ) . toBe ( plan2 . id ) ;
688+ } ) ;
689+
690+ it ( 'should mark syncStatus as unknown when chain reader is not configured' , async ( ) => {
691+ // Arrange
692+ service . createPlan ( 'alice' , 'USDC' , '100' , 30 ) ;
693+
694+ // Act
695+ const result = await service . listCreators ( true ) ;
696+
697+ // Assert
698+ expect ( result [ 0 ] ) . toHaveProperty ( 'syncStatus' ) ;
699+ expect ( result [ 0 ] . syncStatus ) . toBe ( 'unknown' ) ;
700+ } ) ;
701+ } ) ;
702+
703+ describe ( 'happy path - searchCreators' , ( ) => {
704+ it ( 'should search creators and return paginated results' , async ( ) => {
705+ // Arrange
706+ const mockUsers : User [ ] = [
707+ createMockUser ( '1' , 'alice' , 'Alice Smith' ) ,
708+ createMockUser ( '2' , 'bob' , 'Bob Jones' ) ,
709+ ] ;
710+
711+ ( mockQueryBuilder . getRawAndEntities as jest . Mock ) . mockResolvedValue ( {
712+ entities : mockUsers ,
713+ raw : [ { creator_bio : 'Alice bio' } , { creator_bio : 'Bob bio' } ] ,
714+ } ) ;
715+
716+ // Act
717+ const result = await service . searchCreators ( {
718+ q : 'alice' ,
719+ limit : 20 ,
720+ } ) ;
721+
722+ // Assert
723+ expect ( result . data ) . toHaveLength ( 2 ) ;
724+ expect ( result . limit ) . toBe ( 20 ) ;
725+ expect ( result . hasMore ) . toBe ( false ) ;
726+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'id' ) ;
727+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'username' ) ;
728+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'display_name' ) ;
729+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'avatar_url' ) ;
730+ expect ( result . data [ 0 ] ) . toHaveProperty ( 'bio' ) ;
731+ } ) ;
732+
733+ it ( 'should return correct pagination metadata' , async ( ) => {
734+ // Arrange
735+ const mockUsers : User [ ] = Array . from ( { length : 6 } , ( _ , i ) =>
736+ createMockUser ( `${ i } ` , `user${ i } ` , `User ${ i } ` ) ,
737+ ) ;
738+
739+ ( mockQueryBuilder . getRawAndEntities as jest . Mock ) . mockResolvedValue ( {
740+ entities : mockUsers ,
741+ raw : mockUsers . map ( ( ) => ( { creator_bio : 'Bio' } ) ) ,
742+ } ) ;
743+
744+ // Act
745+ const result = await service . searchCreators ( { limit : 5 } ) ;
746+
747+ // Assert
748+ expect ( result . data ) . toHaveLength ( 5 ) ;
749+ expect ( result . hasMore ) . toBe ( true ) ;
750+ expect ( result . nextCursor ) . toBe ( 'user4' ) ;
751+ } ) ;
752+ } ) ;
753+
471754 describe ( 'logging and resilience' , ( ) => {
472755 it ( 'createPlan logs debug when EventBus is not wired' , async ( ) => {
473756 const module : TestingModule = await Test . createTestingModule ( {
0 commit comments