diff --git a/.changeset/gold-adults-warn.md b/.changeset/gold-adults-warn.md new file mode 100644 index 00000000..4c9681aa --- /dev/null +++ b/.changeset/gold-adults-warn.md @@ -0,0 +1,5 @@ +--- +"@tus/s3-store": patch +--- + +Fix zero byte files only storing a .info file. Now correctly stores an empty file. diff --git a/packages/s3-store/src/index.ts b/packages/s3-store/src/index.ts index 954f5395..5768f2c0 100644 --- a/packages/s3-store/src/index.ts +++ b/packages/s3-store/src/index.ts @@ -185,7 +185,7 @@ export class S3Store extends DataStore { 'upload-id': Metadata?.['upload-id'] as string, file: new Upload({ id, - size: file.size ? Number.parseInt(file.size, 10) : undefined, + size: Number.isFinite(file.size) ? Number.parseInt(file.size, 10) : undefined, offset: Number.parseInt(file.offset, 10), metadata: file.metadata, creation_date: file.creation_date, diff --git a/packages/s3-store/test/index.ts b/packages/s3-store/test/index.ts index d33d1886..014892f3 100644 --- a/packages/s3-store/test/index.ts +++ b/packages/s3-store/test/index.ts @@ -242,6 +242,42 @@ describe('S3DataStore', () => { } }) + it('should successfully upload a zero byte file', async function () { + const store = this.datastore as S3Store + const size = 0 + const upload = new Upload({ + id: shared.testId('zero-byte-file'), + size, + offset: 0, + }) + + await store.create(upload) + + const offset = await store.write( + Readable.from(Buffer.alloc(size)), + upload.id, + upload.offset + ) + assert.equal(offset, size, 'Write should return 0 offset') + + // Check .info file via getUpload + const finalUpload = await store.getUpload(upload.id) + assert.equal(finalUpload.offset, size, '.info file should show 0 offset') + + // @ts-expect-error private + const s3Client = store.client + try { + const headResult = await s3Client.getObject({ + Bucket: s3ClientConfig.bucket, + Key: upload.id, + }) + + assert.equal(headResult.ContentLength, size, 'File should exist in S3 with 0 bytes') + } catch (error) { + assert.fail(`Zero byte file was not uploaded to S3: ${error.message}`) + } + }) + shared.shouldHaveStoreMethods() shared.shouldCreateUploads() shared.shouldRemoveUploads() // Termination extension