apify-test-tools 0.6.4-beta.0 → 0.6.4-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/lib/lib.ts CHANGED
@@ -92,29 +92,24 @@ export const testStandbyActor = <I = any, O = any>(
92
92
 
93
93
  vitestTest.runIf(shouldRun)(name, options, async <T extends TestContext>(context: T) => {
94
94
  const standbyTask = await createStandbyTask(actorName, config.get(actorName)?.buildNumber);
95
- const { annotate } = context;
96
95
  const { expect, ...rest } = context;
97
96
 
98
- // NOTE: we need to wrap `fn` in try-catch so that we can always clean up (delete the task) afterwards
97
+ // NOTE: we wrap `fn` in try/finally so cleanup (deleting the task) always runs afterwards
99
98
  try {
100
99
  await fn({
101
100
  expect: extendExpect(expect),
102
- callStandby: createStartStandbyFn(standbyTask),
101
+ callStandby: createStartStandbyFn(standbyTask, context, name),
103
102
  ...rest,
104
103
  });
105
- } catch {
106
- /* */
107
- }
108
-
109
- const { taskId } = standbyTask;
110
- const runs = (await apifyClient.task(taskId).runs().list()).items;
111
- for (const run of runs) {
112
- const runLink = generateRunLink(run);
113
- await annotate(`${name} - ${runLink}`, 'run_link');
114
- }
115
-
116
- if (taskId) {
117
- await apifyClient.task(taskId).delete();
104
+ } finally {
105
+ const { taskId } = standbyTask;
106
+ // we want to delete the task at the end of the test
107
+ await apifyClient
108
+ .task(taskId)
109
+ .delete()
110
+ .catch((error) => {
111
+ console.error(`Failed to delete standby task "${taskId}": ${error}`);
112
+ });
118
113
  }
119
114
  });
120
115
  };
@@ -129,7 +124,7 @@ export const testTestActor = <T>(
129
124
  expect: extendExpect(expect),
130
125
  // @ts-expect-error: this just to test custom matchers
131
126
  // eslint-disable-next-line @typescript-eslint/no-empty-function
132
- run: () => {},
127
+ run: () => { },
133
128
  ...rest,
134
129
  });
135
130
  });
@@ -138,20 +133,44 @@ export const testTestActor = <T>(
138
133
  export const it = testActor;
139
134
 
140
135
  /**
141
- * Creates a function the accepts input for a standby actor and sends request containing input
136
+ * Creates a function that accepts input for a standby actor and sends a request containing the input
142
137
  * to the task's standby url.
143
138
  */
144
- const createStartStandbyFn = <I, O>(standbyTask: StandbyTask) => {
145
- const { standbyUrl } = standbyTask;
146
- return async ({ input }: Pick<RunOptions<I>, 'input'>) => {
147
- const response = await fetch(standbyUrl, {
139
+ const createStartStandbyFn = <I, O>(standbyTask: StandbyTask, { annotate }: TestContext, testName: string) => {
140
+ const { standbyUrl, taskId } = standbyTask;
141
+ const annotatedRuns = new Set<string>();
142
+
143
+ // We annotate all the runs of the task, though it will be only one run
144
+ // for most of the tests. To avoid annotating the same run multiple times
145
+ // (in case the test calls the standby more than once), we use `annotatedRuns`
146
+ // set to keep track of which runs have already been annotated.
147
+ const annotateStandbyRuns = async () => {
148
+ const runs = (await apifyClient.task(taskId).runs().list()).items;
149
+ for (const run of runs) {
150
+ if (!annotatedRuns.has(run.id)) {
151
+ const runLink = generateRunLink(run);
152
+ await annotate(`${testName} - ${runLink}`, 'run_link');
153
+ }
154
+ annotatedRuns.add(run.id);
155
+ }
156
+ };
157
+
158
+ return async ({
159
+ input,
160
+ path = '',
161
+ headers = {},
162
+ }: Pick<RunOptions<I>, 'input'> & { path?: string; headers?: Record<string, string> }) => {
163
+ const response = await fetch(standbyUrl + path, {
148
164
  headers: {
165
+ ...headers,
149
166
  Authorization: `Bearer ${apifyClient.token}`,
150
167
  },
151
168
  method: 'POST',
152
169
  body: JSON.stringify(input),
153
170
  });
154
171
 
172
+ await annotateStandbyRuns();
173
+
155
174
  const data = (await response.json()) as O;
156
175
  return {
157
176
  data,
@@ -166,10 +185,6 @@ interface StandbyTask {
166
185
  taskId: string;
167
186
  }
168
187
 
169
- const randomInt = (min: number, max: number) => {
170
- return Math.floor(Math.random() * (max - min + 1)) + min;
171
- };
172
-
173
188
  /**
174
189
  * Creates a task with specific `build` - either `buildNumber` or default.
175
190
  *
@@ -201,16 +216,15 @@ const createStandbyTask = async (actorNameOrId: string, buildNumber?: string): P
201
216
  };
202
217
 
203
218
  try {
204
- const title = `Test task - ${build}:${actorNameOrId}`.slice(0, 62);
219
+ const title = `Test task - ${build}`.slice(0, 62);
220
+ const randomValueLength = 15;
205
221
  // we try to create unique task name containing only `a-z0-9-` characters and at most 63 characters long
206
- const name = `${randomInt(1, 1_000_000)}${title
207
- .toLowerCase()
208
- .replaceAll(/\s+/g, '')
209
- .replaceAll(/[^a-z0-9-]+/g, '-')}`.slice(0, 62);
222
+ const randomValue = Math.random().toString(10).slice(2).padEnd(randomValueLength, '0');
223
+ const name = `test-${randomValue.slice(0, randomValueLength)}`;
210
224
  const newTask = (await apifyClient.tasks().create({
211
225
  actId: actorNameOrId,
212
226
  actorStandby: actorStandbyOptions,
213
- description: `Task for testing standby version ${build}`,
227
+ description: `Task for testing standby version ${build} of actor "${actorNameOrId}"`,
214
228
  title,
215
229
  name,
216
230
  })) as Task & { standbyUrl?: string };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-test-tools",
3
- "version": "0.6.4-beta.0",
3
+ "version": "0.6.4-beta.2",
4
4
  "type": "module",
5
5
  "description": "TBD",
6
6
  "repository": {
@@ -1,13 +1,7 @@
1
1
  import type { MockInstance } from 'vitest';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import {
5
- getBranchOnlyChangedFiles,
6
- getChangedFiles,
7
- getCommits,
8
- hasMergeFromTarget,
9
- parseBaseCommit,
10
- } from '../../../bin/git.js';
4
+ import { getChangedFiles, getCommits, parseBaseCommit } from '../../../bin/git.js';
11
5
  import * as Utils from '../../../bin/utils.js';
12
6
 
13
7
  describe('getCommits', () => {
@@ -123,86 +117,6 @@ describe('getChangedFiles', () => {
123
117
  });
124
118
  });
125
119
 
126
- describe('hasMergeFromTarget', () => {
127
- const sourceBranch = 'feature-branch';
128
- const targetBranch = 'main';
129
- const mergeSha = 'f'.repeat(40);
130
- const branchParentSha = 'b'.repeat(40);
131
- const targetParentSha = 't'.repeat(40);
132
-
133
- let gitCommandSpy: MockInstance;
134
-
135
- beforeEach(() => {
136
- gitCommandSpy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace');
137
- });
138
-
139
- it('should return false when there are no merge commits on the branch', () => {
140
- gitCommandSpy.mockImplementation((cmd: string) => {
141
- if (cmd.includes('--merges')) return '';
142
- return '';
143
- });
144
-
145
- expect(hasMergeFromTarget(sourceBranch, targetBranch)).toBe(false);
146
- expect(gitCommandSpy).toHaveBeenCalledWith(
147
- `git log --merges --pretty=format:%H ${targetBranch}..${sourceBranch}`,
148
- );
149
- });
150
-
151
- it('should return true when a merge commit has a parent reachable from targetBranch', () => {
152
- gitCommandSpy.mockImplementation((cmd: string) => {
153
- if (cmd.includes('--merges')) return mergeSha;
154
- if (cmd.includes('--pretty=format:%P')) return `${branchParentSha} ${targetParentSha}`;
155
- if (cmd.startsWith(`git merge-base ${branchParentSha}`)) return branchParentSha; // not ancestor
156
- if (cmd.startsWith(`git merge-base ${targetParentSha}`)) return targetParentSha; // is ancestor
157
- return '';
158
- });
159
-
160
- expect(hasMergeFromTarget(sourceBranch, targetBranch)).toBe(true);
161
- });
162
-
163
- it('should return false when the merge commit parent is not reachable from targetBranch (unrelated branch merge)', () => {
164
- const unrelatedSha = 'e'.repeat(40);
165
- const differentMergeBase = '0'.repeat(40);
166
- gitCommandSpy.mockImplementation((cmd: string) => {
167
- if (cmd.includes('--merges')) return mergeSha;
168
- if (cmd.includes('--pretty=format:%P')) return `${branchParentSha} ${unrelatedSha}`;
169
- // merge-base returns something other than the parent — not an ancestor
170
- if (cmd.startsWith('git merge-base')) return differentMergeBase;
171
- return '';
172
- });
173
-
174
- expect(hasMergeFromTarget(sourceBranch, targetBranch)).toBe(false);
175
- });
176
- });
177
-
178
- describe('getBranchOnlyChangedFiles', () => {
179
- const sourceBranch = 'feature-branch';
180
- const targetBranch = 'main';
181
-
182
- let gitCommandSpy: MockInstance;
183
-
184
- beforeEach(() => {
185
- gitCommandSpy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace');
186
- });
187
-
188
- it('should return files touched by non-merge commits', () => {
189
- gitCommandSpy.mockReturnValue('README.md\n\nactors/foo_bar/src/main.ts\n');
190
-
191
- const result = getBranchOnlyChangedFiles(sourceBranch, targetBranch);
192
-
193
- expect(result).toStrictEqual(['README.md', 'actors/foo_bar/src/main.ts']);
194
- expect(gitCommandSpy).toHaveBeenCalledWith(
195
- `git log --no-merges --name-only --pretty=format: ${targetBranch}..${sourceBranch}`,
196
- );
197
- });
198
-
199
- it('should return empty array when there are no non-merge commits', () => {
200
- gitCommandSpy.mockReturnValue('');
201
-
202
- expect(getBranchOnlyChangedFiles(sourceBranch, targetBranch)).toStrictEqual([]);
203
- });
204
- });
205
-
206
120
  const VALID_SHA = 'a'.repeat(40);
207
121
  const VALID_JSON = JSON.stringify({ sha: VALID_SHA, author: 'test', date: 'now', message: 'msg' });
208
122