11import {
22 bulkUpdateSubscriptions ,
3+ cycleSubscriptionStatus ,
34 deleteSubscription ,
5+ filterAuditLog ,
46 getAdminDashboardData ,
7+ toggleMerchantStatus ,
58 updateUserRole ,
69 upsertSubscription ,
10+ type AdminUserRecord ,
11+ type MerchantRecord ,
12+ type SubscriptionAdminRecord ,
713} from '../adminDashboardService' ;
814
15+ // ── Fixtures ──────────────────────────────────────────────────────────────
16+
17+ function makeMerchant ( overrides : Partial < MerchantRecord > = { } ) : MerchantRecord {
18+ return { id : 'merch_1' , name : 'Test' , status : 'active' , activePlans : 5 , monthlyRevenue : 1000 , ...overrides } ;
19+ }
20+
21+ function makeSub ( overrides : Partial < SubscriptionAdminRecord > = { } ) : SubscriptionAdminRecord {
22+ return {
23+ id : 'sub_1' , name : 'Pro Plan' , merchantId : 'merch_1' , merchantName : 'Test' ,
24+ amount : 29 , currency : 'USD' , status : 'active' , ...overrides ,
25+ } ;
26+ }
27+
28+ function makeUser ( overrides : Partial < AdminUserRecord > = { } ) : AdminUserRecord {
29+ return { id : 'user_1' , name : 'Test User' , email : 'test@example.com' , role : 'analyst' , ...overrides } ;
30+ }
31+
32+ // ── getAdminDashboardData ────────────────────────────────────────────────
33+
934describe ( 'adminDashboardService' , ( ) => {
1035 it ( 'returns seeded dashboard data' , ( ) => {
1136 const data = getAdminDashboardData ( 'admin' ) ;
@@ -15,6 +40,13 @@ describe('adminDashboardService', () => {
1540 expect ( data . auditLog . length ) . toBeGreaterThan ( 0 ) ;
1641 } ) ;
1742
43+ it ( 'returns immutable copies — mutations do not affect source' , ( ) => {
44+ const a = getAdminDashboardData ( 'admin' ) ;
45+ const b = getAdminDashboardData ( 'admin' ) ;
46+ a . merchants [ 0 ] . status = 'suspended' ;
47+ expect ( b . merchants [ 0 ] . status ) . not . toBe ( 'suspended' ) ;
48+ } ) ;
49+
1850 it ( 'supports bulk pause operations for elevated roles' , ( ) => {
1951 const data = getAdminDashboardData ( 'analyst' ) ;
2052 const updated = bulkUpdateSubscriptions ( data . subscriptions , [ 'sub_1' ] , 'analyst' ) ;
@@ -38,3 +70,160 @@ describe('adminDashboardService', () => {
3870 expect ( nextUsers . find ( ( user ) => user . id === 'user_2' ) ?. role ) . toBe ( 'support' ) ;
3971 } ) ;
4072} ) ;
73+
74+ // ── toggleMerchantStatus ─────────────────────────────────────────────────
75+
76+ describe ( 'toggleMerchantStatus' , ( ) => {
77+ it ( 'admin: suspends an active merchant' , ( ) => {
78+ expect ( toggleMerchantStatus ( makeMerchant ( { status : 'active' } ) , 'admin' ) . status ) . toBe ( 'suspended' ) ;
79+ } ) ;
80+
81+ it ( 'admin: activates a suspended merchant' , ( ) => {
82+ expect ( toggleMerchantStatus ( makeMerchant ( { status : 'suspended' } ) , 'admin' ) . status ) . toBe ( 'active' ) ;
83+ } ) ;
84+
85+ it ( 'non-admin roles: return merchant unchanged' , ( ) => {
86+ const m = makeMerchant ( { status : 'active' } ) ;
87+ expect ( toggleMerchantStatus ( m , 'analyst' ) . status ) . toBe ( 'active' ) ;
88+ expect ( toggleMerchantStatus ( m , 'support' ) . status ) . toBe ( 'active' ) ;
89+ } ) ;
90+
91+ it ( 'does not mutate the original object' , ( ) => {
92+ const m = makeMerchant ( { status : 'active' } ) ;
93+ toggleMerchantStatus ( m , 'admin' ) ;
94+ expect ( m . status ) . toBe ( 'active' ) ;
95+ } ) ;
96+ } ) ;
97+
98+ // ── upsertSubscription ───────────────────────────────────────────────────
99+
100+ describe ( 'upsertSubscription' , ( ) => {
101+ it ( 'admin: prepends a new draft subscription' , ( ) => {
102+ const result = upsertSubscription ( [ makeSub ( ) ] , 'admin' ) ;
103+ expect ( result . length ) . toBe ( 2 ) ;
104+ expect ( result [ 0 ] . status ) . toBe ( 'draft' ) ;
105+ } ) ;
106+
107+ it ( 'analyst: can create a draft' , ( ) => {
108+ expect ( upsertSubscription ( [ makeSub ( ) ] , 'analyst' ) . length ) . toBe ( 2 ) ;
109+ } ) ;
110+
111+ it ( 'support: cannot create subscriptions' , ( ) => {
112+ expect ( upsertSubscription ( [ makeSub ( ) ] , 'support' ) . length ) . toBe ( 1 ) ;
113+ } ) ;
114+ } ) ;
115+
116+ // ── cycleSubscriptionStatus ──────────────────────────────────────────────
117+
118+ describe ( 'cycleSubscriptionStatus' , ( ) => {
119+ it ( 'admin: draft → active' , ( ) => {
120+ expect ( cycleSubscriptionStatus ( makeSub ( { status : 'draft' } ) , 'admin' ) . status ) . toBe ( 'active' ) ;
121+ } ) ;
122+
123+ it ( 'admin: active → paused' , ( ) => {
124+ expect ( cycleSubscriptionStatus ( makeSub ( { status : 'active' } ) , 'admin' ) . status ) . toBe ( 'paused' ) ;
125+ } ) ;
126+
127+ it ( 'admin: paused → active' , ( ) => {
128+ expect ( cycleSubscriptionStatus ( makeSub ( { status : 'paused' } ) , 'admin' ) . status ) . toBe ( 'active' ) ;
129+ } ) ;
130+
131+ it ( 'support: returns subscription unchanged' , ( ) => {
132+ expect ( cycleSubscriptionStatus ( makeSub ( { status : 'active' } ) , 'support' ) . status ) . toBe ( 'active' ) ;
133+ } ) ;
134+ } ) ;
135+
136+ // ── deleteSubscription ───────────────────────────────────────────────────
137+
138+ describe ( 'deleteSubscription' , ( ) => {
139+ it ( 'admin: removes the matching subscription' , ( ) => {
140+ const subs = [ makeSub ( { id : 'sub_a' } ) , makeSub ( { id : 'sub_b' } ) ] ;
141+ const result = deleteSubscription ( subs , 'sub_a' , 'admin' ) ;
142+ expect ( result . length ) . toBe ( 1 ) ;
143+ expect ( result [ 0 ] . id ) . toBe ( 'sub_b' ) ;
144+ } ) ;
145+
146+ it ( 'non-admin: returns list unchanged' , ( ) => {
147+ const subs = [ makeSub ( ) ] ;
148+ expect ( deleteSubscription ( subs , 'sub_1' , 'analyst' ) . length ) . toBe ( 1 ) ;
149+ expect ( deleteSubscription ( subs , 'sub_1' , 'support' ) . length ) . toBe ( 1 ) ;
150+ } ) ;
151+ } ) ;
152+
153+ // ── bulkUpdateSubscriptions ──────────────────────────────────────────────
154+
155+ describe ( 'bulkUpdateSubscriptions' , ( ) => {
156+ it ( 'pauses only selected subscriptions' , ( ) => {
157+ const subs = [ makeSub ( { id : 'a' , status : 'active' } ) , makeSub ( { id : 'b' , status : 'active' } ) ] ;
158+ const result = bulkUpdateSubscriptions ( subs , [ 'a' ] , 'admin' ) ;
159+ expect ( result . find ( ( s ) => s . id === 'a' ) ! . status ) . toBe ( 'paused' ) ;
160+ expect ( result . find ( ( s ) => s . id === 'b' ) ! . status ) . toBe ( 'active' ) ;
161+ } ) ;
162+
163+ it ( 'support: returns list unchanged' , ( ) => {
164+ const subs = [ makeSub ( { status : 'active' } ) ] ;
165+ expect ( bulkUpdateSubscriptions ( subs , [ 'sub_1' ] , 'support' ) [ 0 ] . status ) . toBe ( 'active' ) ;
166+ } ) ;
167+
168+ it ( 'empty selection: no changes' , ( ) => {
169+ const subs = [ makeSub ( { status : 'active' } ) ] ;
170+ expect ( bulkUpdateSubscriptions ( subs , [ ] , 'admin' ) [ 0 ] . status ) . toBe ( 'active' ) ;
171+ } ) ;
172+ } ) ;
173+
174+ // ── updateUserRole ───────────────────────────────────────────────────────
175+
176+ describe ( 'updateUserRole' , ( ) => {
177+ it ( 'admin: viewer → analyst' , ( ) => {
178+ expect ( updateUserRole ( [ makeUser ( { id : 'u1' , role : 'viewer' } ) ] , 'u1' , 'admin' ) [ 0 ] . role ) . toBe ( 'analyst' ) ;
179+ } ) ;
180+
181+ it ( 'admin: analyst → support' , ( ) => {
182+ expect ( updateUserRole ( [ makeUser ( { id : 'u1' , role : 'analyst' } ) ] , 'u1' , 'admin' ) [ 0 ] . role ) . toBe ( 'support' ) ;
183+ } ) ;
184+
185+ it ( 'admin: support → viewer' , ( ) => {
186+ expect ( updateUserRole ( [ makeUser ( { id : 'u1' , role : 'support' } ) ] , 'u1' , 'admin' ) [ 0 ] . role ) . toBe ( 'viewer' ) ;
187+ } ) ;
188+
189+ it ( 'non-admin: returns list unchanged' , ( ) => {
190+ const users = [ makeUser ( { id : 'u1' , role : 'analyst' } ) ] ;
191+ expect ( updateUserRole ( users , 'u1' , 'analyst' ) [ 0 ] . role ) . toBe ( 'analyst' ) ;
192+ expect ( updateUserRole ( users , 'u1' , 'support' ) [ 0 ] . role ) . toBe ( 'analyst' ) ;
193+ } ) ;
194+ } ) ;
195+
196+ // ── filterAuditLog ───────────────────────────────────────────────────────
197+
198+ describe ( 'filterAuditLog' , ( ) => {
199+ const { auditLog } = getAdminDashboardData ( 'admin' ) ;
200+
201+ it ( 'filters by resourceType' , ( ) => {
202+ const results = filterAuditLog ( auditLog , { resourceType : 'merchant' } ) ;
203+ expect ( results . length ) . toBeGreaterThan ( 0 ) ;
204+ expect ( results . every ( ( e ) => e . resourceType === 'merchant' ) ) . toBe ( true ) ;
205+ } ) ;
206+
207+ it ( 'filters by action' , ( ) => {
208+ const results = filterAuditLog ( auditLog , { action : 'subscription.paused' } ) ;
209+ expect ( results . every ( ( e ) => e . action === 'subscription.paused' ) ) . toBe ( true ) ;
210+ } ) ;
211+
212+ it ( 'filters by actorId' , ( ) => {
213+ const results = filterAuditLog ( auditLog , { actorId : 'user_1' } ) ;
214+ expect ( results . every ( ( e ) => e . actorId === 'user_1' ) ) . toBe ( true ) ;
215+ } ) ;
216+
217+ it ( 'returns all events when no filter criteria given' , ( ) => {
218+ expect ( filterAuditLog ( auditLog , { } ) . length ) . toBe ( auditLog . length ) ;
219+ } ) ;
220+
221+ it ( 'returns empty array when nothing matches' , ( ) => {
222+ expect ( filterAuditLog ( auditLog , { actorId : 'nobody' } ) . length ) . toBe ( 0 ) ;
223+ } ) ;
224+
225+ it ( 'applies multiple criteria as AND' , ( ) => {
226+ const results = filterAuditLog ( auditLog , { resourceType : 'subscription' , actorId : 'user_2' } ) ;
227+ expect ( results . every ( ( e ) => e . resourceType === 'subscription' && e . actorId === 'user_2' ) ) . toBe ( true ) ;
228+ } ) ;
229+ } ) ;
0 commit comments