1- import { jest } from '@jest/globals ' ;
2- import errors from '@tryghost/errors ' ;
1+ import { describe , test , mock , before , beforeEach } from 'node:test ' ;
2+ import assert from 'node:assert/strict ' ;
33
44// Shared mockApi object
55const mockApi = {
66 members : {
7- browse : jest . fn ( ) ,
8- edit : jest . fn ( )
7+ browse : mock . fn ( ) ,
8+ edit : mock . fn ( )
99 }
1010} ;
1111
12- describe ( 'add-member-comp-from-csv' , ( ) => {
13- let fsUtils ;
14- let getTiers ;
15- let addMemberCompFromCsv ;
16- let mockFsUtils ;
17-
18- beforeAll ( async ( ) => {
19- await jest . unstable_mockModule ( '@tryghost/mg-fs-utils' , ( ) => ( {
20- default : {
21- csv : {
22- parseCSV : jest . fn ( )
23- }
24- }
25- } ) ) ;
12+ const mockParseCSV = mock . fn ( ) ;
13+ const mockGetTiers = mock . fn ( ) ;
14+
15+ // Mock bluebird to use native Promise — avoids V8 structured clone
16+ // serialization issues with bluebird promise objects in node:test IPC
17+ const NativePromise = Promise ;
18+ NativePromise . mapSeries = async ( arr , fn ) => {
19+ const results = [ ] ;
20+ for ( let i = 0 ; i < arr . length ; i ++ ) {
21+ results . push ( await fn ( arr [ i ] , i ) ) ;
22+ }
23+ return results ;
24+ } ;
25+ NativePromise . delay = ms => new NativePromise ( resolve => setTimeout ( resolve , ms ) ) ;
26+ mock . module ( 'bluebird' , {
27+ defaultExport : NativePromise
28+ } ) ;
29+
30+ mock . module ( '@tryghost/mg-fs-utils' , {
31+ defaultExport : {
32+ csv : {
33+ parseCSV : mockParseCSV
34+ }
35+ }
36+ } ) ;
2637
27- await jest . unstable_mockModule ( '@tryghost/admin-api' , ( ) => ( {
28- default : jest . fn ( ( ) => mockApi )
29- } ) ) ;
38+ mock . module ( '@tryghost/admin-api' , {
39+ defaultExport : function GhostAdminAPI ( ) {
40+ return mockApi ;
41+ }
42+ } ) ;
43+
44+ mock . module ( '../lib/admin-api-call.js' , {
45+ namedExports : { getTiers : mockGetTiers }
46+ } ) ;
47+
48+ // Mock @tryghost /errors to avoid serialization issues with custom error classes
49+ // in the node:test child process IPC
50+ class MockInternalServerError extends Error {
51+ constructor ( { message, context} ) {
52+ super ( message ) ;
53+ this . context = context ;
54+ }
55+ }
56+ mock . module ( '@tryghost/errors' , {
57+ defaultExport : { InternalServerError : MockInternalServerError }
58+ } ) ;
3059
31- await jest . unstable_mockModule ( '../lib/admin-api-call.js' , ( ) => ( {
32- getTiers : jest . fn ( )
33- } ) ) ;
60+ describe ( 'add-member-comp-from-csv' , ( ) => {
61+ let addMemberCompFromCsv ;
3462
35- fsUtils = ( await import ( '@tryghost/mg-fs-utils' ) ) . default ;
36- getTiers = ( await import ( '../lib/admin-api-call.js' ) ) . getTiers ;
63+ before ( async ( ) => {
3764 addMemberCompFromCsv = ( await import ( '../tasks/add-member-comp-from-csv.js' ) ) . default ;
3865 } ) ;
3966
4067 beforeEach ( ( ) => {
41- // Reset all mock functions
42- mockApi . members . browse . mockReset ( ) ;
43- mockApi . members . edit . mockReset ( ) ;
44- mockFsUtils = fsUtils ;
45- } ) ;
46-
47- afterEach ( ( ) => {
48- jest . clearAllMocks ( ) ;
68+ mockApi . members . browse . mock . resetCalls ( ) ;
69+ mockApi . members . edit . mock . resetCalls ( ) ;
70+ mockParseCSV . mock . resetCalls ( ) ;
71+ mockGetTiers . mock . resetCalls ( ) ;
4972 } ) ;
5073
5174 test ( 'should successfully process valid CSV rows' , async ( ) => {
5275 const csvData = [
5376 { email : 'test1@example.com' , expireAt : '2024-12-31' , tierName : 'Premium' } ,
5477 { email : 'test2@example.com' , expireAt : '2024-12-31' , tierName : 'Basic' }
5578 ] ;
56- mockFsUtils . csv . parseCSV . mockResolvedValue ( csvData ) ;
79+ mockParseCSV . mock . mockImplementation ( ( ) => Promise . resolve ( csvData ) ) ;
5780
58- mockApi . members . browse . mockImplementation ( ( { filter} ) => {
81+ mockApi . members . browse . mock . mockImplementation ( ( { filter} ) => {
5982 if ( filter === 'email:test1@example.com' ) {
6083 return Promise . resolve ( [ { id : '1' , email : 'test1@example.com' } ] ) ;
6184 }
@@ -69,9 +92,9 @@ describe('add-member-comp-from-csv', () => {
6992 { id : 'tier1' , name : 'Premium' } ,
7093 { id : 'tier2' , name : 'Basic' }
7194 ] ;
72- getTiers . mockImplementation ( ( ) => Promise . resolve ( tiers ) ) ;
95+ mockGetTiers . mock . mockImplementation ( ( ) => Promise . resolve ( tiers ) ) ;
7396
74- mockApi . members . edit . mockImplementation ( ( { id} ) => {
97+ mockApi . members . edit . mock . mockImplementation ( ( { id} ) => {
7598 return Promise . resolve ( {
7699 id,
77100 email : id === '1' ? 'test1@example.com' : 'test2@example.com' ,
@@ -87,69 +110,70 @@ describe('add-member-comp-from-csv', () => {
87110
88111 await task . run ( ) ;
89112
90- expect ( mockApi . members . browse ) . toHaveBeenCalledTimes ( 2 ) ;
91- expect ( mockApi . members . edit ) . toHaveBeenCalledTimes ( 2 ) ;
113+ assert . strictEqual ( mockApi . members . browse . mock . callCount ( ) , 2 ) ;
114+ assert . strictEqual ( mockApi . members . edit . mock . callCount ( ) , 2 ) ;
92115 } ) ;
93116
94117 test ( 'should handle missing members' , async ( ) => {
95118 const csvData = [
96119 { email : 'nonexistent@example.com' , expireAt : '2024-12-31' , tierName : 'Premium' }
97120 ] ;
98- mockFsUtils . csv . parseCSV . mockResolvedValue ( csvData ) ;
121+ mockParseCSV . mock . mockImplementation ( ( ) => Promise . resolve ( csvData ) ) ;
99122
100- mockApi . members . browse . mockResolvedValue ( [ ] ) ;
123+ mockApi . members . browse . mock . mockImplementation ( ( ) => Promise . resolve ( [ ] ) ) ;
101124
102125 const task = addMemberCompFromCsv . getTaskRunner ( {
103126 apiURL : 'http://localhost:2368' ,
104127 adminAPIKey : 'test-key' ,
105128 csvPath : 'test.csv'
106129 } ) ;
107130
108- await expect ( task . run ( ) ) . rejects . toThrow ( ' Failed to process some rows' ) ;
131+ await assert . rejects ( task . run ( ) , / F a i l e d t o p r o c e s s s o m e r o w s / ) ;
109132 } ) ;
110133
111134 test ( 'should handle missing tiers' , async ( ) => {
112135 const csvData = [
113136 { email : 'test@example.com' , expireAt : '2024-12-31' , tierName : 'NonexistentTier' }
114137 ] ;
115- mockFsUtils . csv . parseCSV . mockResolvedValue ( csvData ) ;
138+ mockParseCSV . mock . mockImplementation ( ( ) => Promise . resolve ( csvData ) ) ;
116139
117- mockApi . members . browse . mockResolvedValue ( [ { id : '1' , email : 'test@example.com' } ] ) ;
140+ mockApi . members . browse . mock . mockImplementation ( ( ) => Promise . resolve ( [ { id : '1' , email : 'test@example.com' } ] ) ) ;
118141
119142 const tiers = [ { id : 'tier1' , name : 'Premium' } ] ;
120- getTiers . mockImplementation ( ( ) => Promise . resolve ( tiers ) ) ;
143+ mockGetTiers . mock . mockImplementation ( ( ) => Promise . resolve ( tiers ) ) ;
121144
122145 const task = addMemberCompFromCsv . getTaskRunner ( {
123146 apiURL : 'http://localhost:2368' ,
124147 adminAPIKey : 'test-key' ,
125148 csvPath : 'test.csv'
126149 } ) ;
127150
128- await expect ( task . run ( ) ) . rejects . toThrow ( ' Failed to process some rows' ) ;
151+ await assert . rejects ( task . run ( ) , / F a i l e d t o p r o c e s s s o m e r o w s / ) ;
129152 } ) ;
130153
131154 test ( 'should handle API errors' , async ( ) => {
132155 const csvData = [
133156 { email : 'test@example.com' , expireAt : '2024-12-31' , tierName : 'Premium' }
134157 ] ;
135- mockFsUtils . csv . parseCSV . mockResolvedValue ( csvData ) ;
158+ mockParseCSV . mock . mockImplementation ( ( ) => Promise . resolve ( csvData ) ) ;
136159
137- mockApi . members . browse . mockResolvedValue ( [ { id : '1' , email : 'test@example.com' } ] ) ;
160+ mockApi . members . browse . mock . mockImplementation ( ( ) => Promise . resolve ( [ { id : '1' , email : 'test@example.com' } ] ) ) ;
138161
139162 const tiers = [ { id : 'tier1' , name : 'Premium' } ] ;
140- getTiers . mockImplementation ( ( ) => Promise . resolve ( tiers ) ) ;
163+ mockGetTiers . mock . mockImplementation ( ( ) => Promise . resolve ( tiers ) ) ;
141164
142- mockApi . members . edit . mockRejectedValue ( new errors . ValidationError ( {
165+ const validationError = new MockInternalServerError ( {
143166 message : 'Invalid data' ,
144167 context : 'The provided data is invalid'
145- } ) ) ;
168+ } ) ;
169+ mockApi . members . edit . mock . mockImplementation ( ( ) => Promise . reject ( validationError ) ) ;
146170
147171 const task = addMemberCompFromCsv . getTaskRunner ( {
148172 apiURL : 'http://localhost:2368' ,
149173 adminAPIKey : 'test-key' ,
150174 csvPath : 'test.csv'
151175 } ) ;
152176
153- await expect ( task . run ( ) ) . rejects . toThrow ( ' Failed to process some rows' ) ;
177+ await assert . rejects ( task . run ( ) , / F a i l e d t o p r o c e s s s o m e r o w s / ) ;
154178 } ) ;
155- } ) ;
179+ } ) ;
0 commit comments