apify-test-tools 0.9.0-beta.0 → 0.9.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.9.0-beta.0",
3
+ "version": "0.9.0-beta.2",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { selectActors } from '../../../bin/actor-filtering.js';
4
+ import type { ActorConfig } from '../../../bin/types.js';
5
+
6
+ const actor = (actorFullName: string): ActorConfig => ({
7
+ actorFullName,
8
+ folder: actorFullName.split('/')[1],
9
+ tokenEnvVar: 'TOKEN',
10
+ dockerContextDir: '.',
11
+ contextPaths: [],
12
+ });
13
+
14
+ const configs = [actor('owner/a'), actor('owner/b'), actor('owner/c')];
15
+ const names = (result: ActorConfig[]) => result.map((c) => c.actorFullName);
16
+
17
+ describe('selectActors', () => {
18
+ it('returns all actors when neither filter is set', () => {
19
+ expect(names(selectActors({ actors: [], ignore: [] }, configs))).toStrictEqual([
20
+ 'owner/a',
21
+ 'owner/b',
22
+ 'owner/c',
23
+ ]);
24
+ });
25
+
26
+ it('keeps only the actors listed in --actors', () => {
27
+ expect(names(selectActors({ actors: ['owner/a', 'owner/c'], ignore: [] }, configs))).toStrictEqual([
28
+ 'owner/a',
29
+ 'owner/c',
30
+ ]);
31
+ });
32
+
33
+ it('drops the actors listed in --ignore', () => {
34
+ expect(names(selectActors({ actors: [], ignore: ['owner/b'] }, configs))).toStrictEqual(['owner/a', 'owner/c']);
35
+ });
36
+
37
+ it('applies --actors first, then removes --ignore from that subset', () => {
38
+ expect(names(selectActors({ actors: ['owner/a', 'owner/b'], ignore: ['owner/b'] }, configs))).toStrictEqual([
39
+ 'owner/a',
40
+ ]);
41
+ });
42
+
43
+ it('can select down to nothing when --actors and --ignore overlap fully', () => {
44
+ expect(selectActors({ actors: ['owner/a'], ignore: ['owner/a'] }, configs)).toStrictEqual([]);
45
+ });
46
+
47
+ it('throws listing every unknown name across both filters', () => {
48
+ expect(() => selectActors({ actors: ['owner/x'], ignore: ['owner/y'] }, configs)).toThrow(
49
+ 'The following actors from the filter config do not exist: owner/x, owner/y',
50
+ );
51
+ });
52
+ });
@@ -12,6 +12,8 @@ vi.mock('node:fs/promises', () => ({ default: fsMock }));
12
12
 
13
13
  afterEach(() => vi.restoreAllMocks());
14
14
 
15
+ const emptyActorSelection = { actors: [], ignore: [] };
16
+
15
17
  const validConfig = (actors: object[]) => JSON.stringify({ actors });
16
18
  const actorJson = (fields: Record<string, unknown> = {}) => JSON.stringify(fields);
17
19
 
@@ -39,7 +41,7 @@ describe('readConfigFile', () => {
39
41
  'actors/shopify/.actor/actor.json': actorJson({ dockerContextDir: '../../..' }),
40
42
  });
41
43
 
42
- const result = await readConfigFile();
44
+ const result = await readConfigFile(emptyActorSelection);
43
45
  expectFileRead('actors/shopify/.actor/actor.json');
44
46
  expect(result).toEqual([
45
47
  {
@@ -60,7 +62,7 @@ describe('readConfigFile', () => {
60
62
  '.actor/actor.json': actorJson({}),
61
63
  });
62
64
 
63
- const result = await readConfigFile();
65
+ const result = await readConfigFile(emptyActorSelection);
64
66
  expect(result[0].folder).toBe('');
65
67
  expectFileRead('.actor/actor.json');
66
68
  });
@@ -73,7 +75,7 @@ describe('readConfigFile', () => {
73
75
  'actors/web-scraper/.actor/actor.json': actorJson({}),
74
76
  });
75
77
 
76
- const result = await readConfigFile();
78
+ const result = await readConfigFile(emptyActorSelection);
77
79
  expect(result[0].dockerContextDir).toBe('actors/web-scraper');
78
80
  expect(result[0].contextPaths).toEqual(['actors/web-scraper']);
79
81
  });
@@ -86,7 +88,7 @@ describe('readConfigFile', () => {
86
88
  'actors/shopify/.actor/actor.json': actorJson({ dockerContextDir: '../../..' }),
87
89
  });
88
90
 
89
- const result = await readConfigFile();
91
+ const result = await readConfigFile(emptyActorSelection);
90
92
  expect(result[0].dockerContextDir).toBe('');
91
93
  });
92
94
 
@@ -103,7 +105,7 @@ describe('readConfigFile', () => {
103
105
  'actors/shopify/.actor/actor.json': actorJson({ dockerContextDir: '../../..' }),
104
106
  });
105
107
 
106
- const result = await readConfigFile();
108
+ const result = await readConfigFile(emptyActorSelection);
107
109
  expect(result[0].contextPaths).toEqual(['actors/shopify', 'packages']);
108
110
  });
109
111
 
@@ -121,7 +123,7 @@ describe('readConfigFile', () => {
121
123
  'actors/email-sender/.actor/actor.json': actorJson({}),
122
124
  });
123
125
 
124
- const result = await readConfigFile();
126
+ const result = await readConfigFile(emptyActorSelection);
125
127
  expect(result).toHaveLength(2);
126
128
  expect(result[0].actorFullName).toBe('apify/web-scraper');
127
129
  expect(result[1].actorFullName).toBe('other-team/email-sender');
@@ -129,17 +131,17 @@ describe('readConfigFile', () => {
129
131
 
130
132
  it('throws when config file is missing', async () => {
131
133
  fsMock.readFile.mockRejectedValue(new Error('ENOENT'));
132
- await expect(readConfigFile()).rejects.toThrow('not found');
134
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('not found');
133
135
  });
134
136
 
135
137
  it('throws when config file contains invalid JSON', async () => {
136
138
  fsMock.readFile.mockResolvedValue('{bad json');
137
- await expect(readConfigFile()).rejects.toThrow('invalid JSON');
139
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('invalid JSON');
138
140
  });
139
141
 
140
142
  it('throws when actors array is missing', async () => {
141
143
  fsMock.readFile.mockResolvedValue(JSON.stringify({ notActors: [] }));
142
- await expect(readConfigFile()).rejects.toThrow('"actors" array');
144
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('"actors" array');
143
145
  });
144
146
 
145
147
  it('throws on duplicate folders', async () => {
@@ -151,7 +153,7 @@ describe('readConfigFile', () => {
151
153
  'actors/shopify/.actor/actor.json': actorJson({}),
152
154
  });
153
155
 
154
- await expect(readConfigFile()).rejects.toThrow('Duplicate folder');
156
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Duplicate folder');
155
157
  });
156
158
 
157
159
  it('throws on duplicate folders after normalization ("." and "")', async () => {
@@ -163,7 +165,7 @@ describe('readConfigFile', () => {
163
165
  '.actor/actor.json': actorJson({}),
164
166
  });
165
167
 
166
- await expect(readConfigFile()).rejects.toThrow('Duplicate folder');
168
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Duplicate folder');
167
169
  });
168
170
 
169
171
  it('throws when actor.json is missing', async () => {
@@ -173,7 +175,7 @@ describe('readConfigFile', () => {
173
175
  ]),
174
176
  });
175
177
 
176
- await expect(readConfigFile()).rejects.toThrow('Cannot read');
178
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Cannot read');
177
179
  });
178
180
 
179
181
  it('throws when folder is missing', async () => {
@@ -181,7 +183,7 @@ describe('readConfigFile', () => {
181
183
  [CONFIG_FILE_NAME]: validConfig([{ actorFullName: 'apify/shopify', tokenEnvVar: 'APIFY_TOKEN_APIFY' }]),
182
184
  });
183
185
 
184
- await expect(readConfigFile()).rejects.toThrow(/Invalid "folder"/);
186
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow(/Invalid "folder"/);
185
187
  });
186
188
 
187
189
  it('throws when folder is not a string', async () => {
@@ -191,7 +193,7 @@ describe('readConfigFile', () => {
191
193
  ]),
192
194
  });
193
195
 
194
- await expect(readConfigFile()).rejects.toThrow(/Invalid "folder"/);
196
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow(/Invalid "folder"/);
195
197
  });
196
198
 
197
199
  it('throws when actorFullName is missing', async () => {
@@ -200,7 +202,7 @@ describe('readConfigFile', () => {
200
202
  'actors/shopify/.actor/actor.json': actorJson({}),
201
203
  });
202
204
 
203
- await expect(readConfigFile()).rejects.toThrow('Invalid "actorFullName"');
205
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Invalid "actorFullName"');
204
206
  });
205
207
 
206
208
  it('throws when actorFullName has no slash', async () => {
@@ -211,7 +213,7 @@ describe('readConfigFile', () => {
211
213
  'actors/shopify/.actor/actor.json': actorJson({}),
212
214
  });
213
215
 
214
- await expect(readConfigFile()).rejects.toThrow('Invalid "actorFullName"');
216
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Invalid "actorFullName"');
215
217
  });
216
218
 
217
219
  it('throws when actorFullName has empty parts', async () => {
@@ -222,7 +224,7 @@ describe('readConfigFile', () => {
222
224
  'actors/shopify/.actor/actor.json': actorJson({}),
223
225
  });
224
226
 
225
- await expect(readConfigFile()).rejects.toThrow('Invalid "actorFullName"');
227
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Invalid "actorFullName"');
226
228
  });
227
229
 
228
230
  it('throws when overrideActorContext is not an array', async () => {
@@ -238,7 +240,7 @@ describe('readConfigFile', () => {
238
240
  'actors/shopify/.actor/actor.json': actorJson({}),
239
241
  });
240
242
 
241
- await expect(readConfigFile()).rejects.toThrow('Invalid "overrideActorContext"');
243
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Invalid "overrideActorContext"');
242
244
  });
243
245
 
244
246
  it('throws when overrideActorContext contains non-strings', async () => {
@@ -254,7 +256,7 @@ describe('readConfigFile', () => {
254
256
  'actors/shopify/.actor/actor.json': actorJson({}),
255
257
  });
256
258
 
257
- await expect(readConfigFile()).rejects.toThrow('Invalid "overrideActorContext"');
259
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow('Invalid "overrideActorContext"');
258
260
  });
259
261
 
260
262
  it('throws when overrideActorContext entries overlap (one is a prefix of another)', async () => {
@@ -270,7 +272,7 @@ describe('readConfigFile', () => {
270
272
  'actors/shopify/.actor/actor.json': actorJson({}),
271
273
  });
272
274
 
273
- await expect(readConfigFile()).rejects.toThrow(/overlap/);
275
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow(/overlap/);
274
276
  });
275
277
 
276
278
  it('throws when overrideActorContext contains the repo root alongside another entry', async () => {
@@ -286,10 +288,10 @@ describe('readConfigFile', () => {
286
288
  'actors/shopify/.actor/actor.json': actorJson({}),
287
289
  });
288
290
 
289
- await expect(readConfigFile()).rejects.toThrow(/overlap/);
291
+ await expect(readConfigFile(emptyActorSelection)).rejects.toThrow(/overlap/);
290
292
  });
291
293
 
292
- it('throws when overrideActorContext does not include the actor own folder', async () => {
294
+ it('adds the actor own folder automatically when overrideActorContext does not cover it', async () => {
293
295
  mockFiles({
294
296
  [CONFIG_FILE_NAME]: validConfig([
295
297
  {
@@ -302,7 +304,8 @@ describe('readConfigFile', () => {
302
304
  'actors/shopify/.actor/actor.json': actorJson({}),
303
305
  });
304
306
 
305
- await expect(readConfigFile()).rejects.toThrow('not reachable through its own context paths');
307
+ const result = await readConfigFile(emptyActorSelection);
308
+ expect(result[0].contextPaths).toEqual(['code', 'shared', 'actors/shopify']);
306
309
  });
307
310
 
308
311
  it('strips trailing slashes from folder and overrideActorContext entries', async () => {
@@ -318,7 +321,7 @@ describe('readConfigFile', () => {
318
321
  'actors/shopify/.actor/actor.json': actorJson({}),
319
322
  });
320
323
 
321
- const result = await readConfigFile();
324
+ const result = await readConfigFile(emptyActorSelection);
322
325
  expect(result[0].folder).toBe('actors/shopify');
323
326
  expect(result[0].contextPaths).toEqual(['actors/shopify', 'packages']);
324
327
  });
@@ -336,7 +339,43 @@ describe('readConfigFile', () => {
336
339
  'actors/shopify/.actor/actor.json': actorJson({}),
337
340
  });
338
341
 
339
- const result = await readConfigFile();
342
+ const result = await readConfigFile(emptyActorSelection);
340
343
  expect(result[0].contextPaths).toEqual(['actors/shopify', 'code', 'shared']);
341
344
  });
345
+
346
+ describe('actor selection', () => {
347
+ const twoActors = () =>
348
+ mockFiles({
349
+ [CONFIG_FILE_NAME]: validConfig([
350
+ { folder: 'actors/a', actorFullName: 'team/a', tokenEnvVar: 'TOKEN' },
351
+ { folder: 'actors/b', actorFullName: 'team/b', tokenEnvVar: 'TOKEN' },
352
+ ]),
353
+ 'actors/a/.actor/actor.json': actorJson({}),
354
+ 'actors/b/.actor/actor.json': actorJson({}),
355
+ });
356
+
357
+ const fullNames = (result: { actorFullName: string }[]) => result.map((c) => c.actorFullName);
358
+
359
+ it('returns all actors when the selection is empty', async () => {
360
+ twoActors();
361
+ expect(fullNames(await readConfigFile(emptyActorSelection))).toEqual(['team/a', 'team/b']);
362
+ });
363
+
364
+ it('keeps only the selected actors', async () => {
365
+ twoActors();
366
+ expect(fullNames(await readConfigFile({ actors: ['team/a'], ignore: [] }))).toEqual(['team/a']);
367
+ });
368
+
369
+ it('drops ignored actors', async () => {
370
+ twoActors();
371
+ expect(fullNames(await readConfigFile({ actors: [], ignore: ['team/a'] }))).toEqual(['team/b']);
372
+ });
373
+
374
+ it('throws on an unknown actor name', async () => {
375
+ twoActors();
376
+ await expect(readConfigFile({ actors: ['team/nope'], ignore: [] })).rejects.toThrow(
377
+ 'do not exist: team/nope',
378
+ );
379
+ });
380
+ });
342
381
  });