From 5c759cb0627ffccdac027f61cd15a37b7f7448ec Mon Sep 17 00:00:00 2001 From: kuntal1461 Date: Wed, 19 Aug 2026 17:45:32 +0530 Subject: [PATCH] fix: handle missing job logs --- src/lib/utils.ts | 4 ++- test/local/lib/utils.test.ts | 60 +++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 99d8784e8..e66a167f5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -595,7 +595,9 @@ export const outputJobLog = async ({ } const log = await client.log(logId).get(); - process.stderr.write(log!); + if (log != null) { + process.stderr.write(log); + } return; } diff --git a/test/local/lib/utils.test.ts b/test/local/lib/utils.test.ts index 719efa9d2..3482e58cb 100644 --- a/test/local/lib/utils.test.ts +++ b/test/local/lib/utils.test.ts @@ -1,12 +1,15 @@ import { existsSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; +import process from 'node:process'; import axios from 'axios'; +import { ACTOR_JOB_STATUSES } from '@apify/consts'; + import { execWithLog } from '../../../src/lib/exec.js'; import { ensureFolderExistsSync } from '../../../src/lib/files.js'; import { inputFileRegExp } from '../../../src/lib/input-key.js'; -import { createActZip, downloadAndUnzip, getActorLocalFilePaths } from '../../../src/lib/utils.js'; +import { createActZip, downloadAndUnzip, getActorLocalFilePaths, outputJobLog } from '../../../src/lib/utils.js'; import { useTempPath } from '../../__setup__/hooks/useTempPath.js'; import { withRetries } from '../../__setup__/hooks/withRetries.js'; @@ -104,6 +107,61 @@ describe('Utils', () => { }); }); + describe('outputJobLog()', () => { + const originalNoLogs = process.env.APIFY_NO_LOGS_IN_TESTS; + + const makeClientWithLog = (log: string | null | undefined) => + ({ + log: () => ({ + get: async () => log, + stream: async () => undefined, + }), + }) as never; + + const terminalJob = { + id: 'job-id', + status: ACTOR_JOB_STATUSES.SUCCEEDED, + } as never; + + beforeEach(() => { + delete process.env.APIFY_NO_LOGS_IN_TESTS; + }); + + afterEach(() => { + vitest.restoreAllMocks(); + + if (originalNoLogs === undefined) { + delete process.env.APIFY_NO_LOGS_IN_TESTS; + } else { + process.env.APIFY_NO_LOGS_IN_TESTS = originalNoLogs; + } + }); + + it('writes an existing terminal job log to stderr', async () => { + const stderrWrite = vitest.spyOn(process.stderr, 'write').mockImplementation(() => true); + + await outputJobLog({ job: terminalJob, apifyClient: makeClientWithLog('hello\n') }); + + expect(stderrWrite).toHaveBeenCalledWith('hello\n'); + }); + + it.each([undefined, null])('does not throw or write when a terminal job log is %s', async (log) => { + const stderrWrite = vitest.spyOn(process.stderr, 'write').mockImplementation(() => true); + + await expect(outputJobLog({ job: terminalJob, apifyClient: makeClientWithLog(log) })).resolves.toBeUndefined(); + + expect(stderrWrite).not.toHaveBeenCalled(); + }); + + it('keeps empty terminal job logs as valid write input', async () => { + const stderrWrite = vitest.spyOn(process.stderr, 'write').mockImplementation(() => true); + + await outputJobLog({ job: terminalJob, apifyClient: makeClientWithLog('') }); + + expect(stderrWrite).toHaveBeenCalledWith(''); + }); + }); + describe('input file regex', () => { const validFiles = ['INPUT', 'INPUT.json', 'INPUT.bin'];