From 2a9fbeeda9d5cf447096eae745e71e4d63934d70 Mon Sep 17 00:00:00 2001 From: Daniel Kennedy Date: Tue, 24 Feb 2026 12:29:14 -0500 Subject: [PATCH] Add some more stream tests and handle an upload failure gracefully --- packages/artifact/__tests__/stream.test.ts | 67 +++++++++++++++++++ .../artifact/src/internal/upload/stream.ts | 4 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 packages/artifact/__tests__/stream.test.ts diff --git a/packages/artifact/__tests__/stream.test.ts b/packages/artifact/__tests__/stream.test.ts new file mode 100644 index 00000000..a5a6c417 --- /dev/null +++ b/packages/artifact/__tests__/stream.test.ts @@ -0,0 +1,67 @@ +import * as fs from 'fs' +import * as path from 'path' +import {createRawFileUploadStream} from '../src/internal/upload/stream.js' +import {noopLogs} from './common.js' + +const fixtures = { + testDirectory: path.join(__dirname, '_temp', 'stream-test'), + testFile: path.join(__dirname, '_temp', 'stream-test', 'test-file.txt'), + testContent: 'hello stream test' +} + +describe('createRawFileUploadStream', () => { + beforeAll(() => { + fs.mkdirSync(fixtures.testDirectory, {recursive: true}) + fs.writeFileSync(fixtures.testFile, fixtures.testContent) + }) + + beforeEach(() => { + noopLogs() + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should stream file contents through the upload stream', async () => { + const uploadStream = await createRawFileUploadStream(fixtures.testFile) + + const chunks: Buffer[] = [] + const result = await new Promise((resolve, reject) => { + uploadStream.on('data', chunk => chunks.push(Buffer.from(chunk))) + uploadStream.on('end', () => + resolve(Buffer.concat(chunks).toString('utf-8')) + ) + uploadStream.on('error', reject) + }) + + expect(result).toBe(fixtures.testContent) + }) + + it('should propagate file read errors through the upload stream', async () => { + const unreadableFile = path.join( + fixtures.testDirectory, + 'unreadable.txt' + ) + fs.writeFileSync(unreadableFile, 'secret') + fs.chmodSync(unreadableFile, 0o000) + + const uploadStream = await createRawFileUploadStream(unreadableFile) + + try { + await expect( + new Promise((resolve, reject) => { + uploadStream.on('data', resolve) + uploadStream.on('end', resolve) + uploadStream.on('error', reject) + }) + ).rejects.toThrow( + 'An error has occurred during file read for the artifact' + ) + } finally { + // Restore permissions so cleanup can delete the file + fs.chmodSync(unreadableFile, 0o644) + fs.unlinkSync(unreadableFile) + } + }) +}) diff --git a/packages/artifact/src/internal/upload/stream.ts b/packages/artifact/src/internal/upload/stream.ts index 8563f0be..f3b228b6 100644 --- a/packages/artifact/src/internal/upload/stream.ts +++ b/packages/artifact/src/internal/upload/stream.ts @@ -42,7 +42,9 @@ export async function createRawFileUploadStream( fileStream.on('error', error => { core.error('An error has occurred while reading the file for upload') core.info(String(error)) - throw new Error('An error has occurred during file read for the artifact') + uploadStream.destroy( + new Error('An error has occurred during file read for the artifact') + ) }) fileStream.pipe(uploadStream)