1+ import { setTimeout } from 'node:timers/promises' ;
2+
3+ import c from 'ansi-colors' ;
14import type { StoreCollectionListOptions } from 'apify-client' ;
2- import { ApifyClient } from 'apify-client' ;
5+ import { ApifyClient , LoggerActorRedirect } from 'apify-client' ;
6+ import express from 'express' ;
7+
8+ import { LEVELS , Log } from '@apify/log' ;
39
10+ import type { ApifyResponse } from '../src/http_client' ;
411import { Browser , DEFAULT_OPTIONS , validateRequest } from './_helper' ;
5- import { mockServer } from './mock_server/server' ;
12+ import { createDefaultApp , mockServer } from './mock_server/server' ;
13+ import { MOCKED_ACTOR_LOGS_PROCESSED , StatusGenerator } from './mock_server/test_utils' ;
614
715describe ( 'Store' , ( ) => {
816 let baseUrl : string | undefined ;
@@ -33,17 +41,17 @@ describe('Store', () => {
3341 page . close ( ) . catch ( ( ) => { } ) ;
3442 } ) ;
3543
36- test ( 'list() works' , async ( ) => {
37- const opts = {
38- limit : 5 ,
39- offset : 3 ,
40- search : 'my search' ,
41- sortBy : 'my sort' ,
42- category : 'my category' ,
43- username : 'my username' ,
44- pricingModel : 'my pricing model' ,
45- } ;
44+ const opts = {
45+ limit : 5 ,
46+ offset : 3 ,
47+ search : 'my search' ,
48+ sortBy : 'my sort' ,
49+ category : 'my category' ,
50+ username : 'my username' ,
51+ pricingModel : 'my pricing model' ,
52+ } ;
4653
54+ test ( 'list() works' , async ( ) => {
4755 const res : any = client && ( await client . store ( ) . list ( opts ) ) ;
4856 expect ( res . id ) . toEqual ( 'store-list' ) ;
4957 validateRequest ( opts ) ;
@@ -53,7 +61,136 @@ describe('Store', () => {
5361 opts ,
5462 ) ;
5563 expect ( browserRes . id ) . toEqual ( 'store-list' ) ;
56- expect ( browserRes ) . toEqual ( res ) ;
64+ const { [ Symbol . asyncIterator ] : _ , ...expectedResponse } = res ;
65+ expect ( browserRes ) . toEqual ( expectedResponse ) ;
5766 validateRequest ( opts ) ;
5867 } ) ;
5968} ) ;
69+
70+ describe ( 'actor.store.list as async iterable' , ( ) => {
71+ // Test using store().list() as an async iterable
72+ const client : ApifyClient = new ApifyClient ( ) ;
73+
74+ const createItems = ( count : number ) => {
75+ return new Array ( count ) . fill ( 'some actor details' ) ;
76+ } ;
77+
78+ const exampleResponseData = {
79+ total : 2500 ,
80+ count : 0 ,
81+ offset : 0 ,
82+ limit : 0 ,
83+ desc : false ,
84+ items : createItems ( 1000 ) ,
85+ } ;
86+
87+ const testCases = [
88+ {
89+ testName : 'Known total items, no offset, no limit' ,
90+ options : { } ,
91+ responseDataOverrides : [
92+ { count : 1000 , limit : 1000 } ,
93+ { count : 1000 , limit : 1000 , offset : 1000 } ,
94+ { count : 500 , limit : 1000 , offset : 2000 , items : createItems ( 500 ) } ,
95+ ] ,
96+ expectedItems : 2500 ,
97+ } ,
98+ {
99+ testName : 'Known total items, user offset, no limit' ,
100+ options : { offset : 1000 } ,
101+ responseDataOverrides : [
102+ { count : 1000 , limit : 1000 , offset : 1000 } ,
103+ { count : 500 , limit : 1000 , offset : 2000 , items : createItems ( 500 ) } ,
104+ ] ,
105+ expectedItems : 1500 ,
106+ } ,
107+ {
108+ testName : 'Known total items, no offset, user limit' ,
109+ options : { limit : 1100 } ,
110+ responseDataOverrides : [
111+ { count : 1000 , limit : 1000 } ,
112+ { count : 100 , limit : 100 , offset : 1000 , items : createItems ( 100 ) } ,
113+ ] ,
114+ expectedItems : 1100 ,
115+ } ,
116+ {
117+ testName : 'Known total items, user offset, user limit' ,
118+ options : { offset : 1000 , limit : 1100 } ,
119+ responseDataOverrides : [
120+ { count : 1000 , limit : 1000 , offset : 1000 } ,
121+ { count : 100 , limit : 100 , offset : 2000 , items : createItems ( 100 ) } ,
122+ ] ,
123+ expectedItems : 1100 ,
124+ } ,
125+ {
126+ testName : 'Unknown total items, no offset, no limit' ,
127+ options : { } ,
128+ responseDataOverrides : [
129+ { total : undefined , count : 1000 , limit : 1000 } ,
130+ { total : undefined , count : 1000 , limit : 1000 , offset : 1000 } ,
131+ { total : undefined , count : 500 , limit : 1000 , offset : 2000 , items : createItems ( 500 ) } ,
132+ { total : undefined , count : 0 , limit : 1000 , offset : 2500 , items : [ ] } , // In this case, iterator had to try as it does not know if there is more or not and there is no user limit.
133+ ] ,
134+ expectedItems : 2500 ,
135+ } ,
136+ {
137+ testName : 'Unknown total items, user offset, no limit' ,
138+ options : { offset : 1000 } ,
139+ responseDataOverrides : [
140+ { total : undefined , count : 1000 , limit : 1000 , offset : 1000 } ,
141+ { total : undefined , count : 500 , limit : 1000 , offset : 2000 , items : createItems ( 500 ) } ,
142+ { total : undefined , count : 0 , limit : 1000 , offset : 2500 , items : [ ] } , // In this case, iterator had to try as it does not know if there is more or not and there is no user limit.
143+ ] ,
144+ expectedItems : 1500 ,
145+ } ,
146+ {
147+ testName : 'Unknown total items, no offset, user limit' ,
148+ options : { limit : 1100 } ,
149+ responseDataOverrides : [
150+ { total : undefined , count : 1000 , limit : 1000 } ,
151+ { total : undefined , count : 100 , limit : 100 , offset : 1000 , items : createItems ( 100 ) } ,
152+ ] ,
153+ expectedItems : 1100 ,
154+ } ,
155+ {
156+ testName : 'Unknown total items, user offset, user limit' ,
157+ options : { offset : 1000 , limit : 1100 } ,
158+ responseDataOverrides : [
159+ { total : undefined , count : 1000 , limit : 1000 , offset : 1000 } ,
160+ { total : undefined , count : 100 , limit : 100 , offset : 2000 , items : createItems ( 100 ) } ,
161+ ] ,
162+ expectedItems : 1100 ,
163+ } ,
164+ ] ;
165+
166+ test . each ( testCases ) ( '$testName' , async ( { options, responseDataOverrides, expectedItems } ) => {
167+ // Simulate 2500 actors in store and 8 possible combinations of user options and API responses.
168+
169+ function * mockedResponsesGenerator ( ) {
170+ for ( const responseDataOverride of responseDataOverrides ) {
171+ yield { data : { data : { ...exampleResponseData , ...responseDataOverride } } } as ApifyResponse ;
172+ }
173+ }
174+
175+ const mockedResponses = mockedResponsesGenerator ( ) ;
176+
177+ const storeClient = client . store ( ) ;
178+ const mockedClient = jest . spyOn ( storeClient . httpClient , 'call' ) . mockImplementation ( async ( ) => {
179+ const next = mockedResponses . next ( ) ;
180+ if ( next . done ) {
181+ // Return a default or dummy ApifyResponse here
182+ return { data : { } } as ApifyResponse < unknown > ;
183+ }
184+ return next . value ;
185+ } ) ;
186+
187+ const pages = await client . store ( ) . list ( options ) ;
188+
189+ const totalItems : any [ ] = [ ] ;
190+ for await ( const page of pages ) {
191+ totalItems . push ( ...page . items ) ;
192+ }
193+ mockedClient . mockRestore ( ) ;
194+ expect ( totalItems . length ) . toBe ( expectedItems ) ;
195+ } ) ;
196+ } ) ;
0 commit comments