diff --git a/lib/s3/s3.disk.ts b/lib/s3/s3.disk.ts
index db17d34..6a26d2e 100644
--- a/lib/s3/s3.disk.ts
+++ b/lib/s3/s3.disk.ts
@@ -338,7 +338,18 @@ export class S3Disk extends StorageDisk {
const signal = options?.signal;
if (!this.batchDelete || keys.length === 1) {
for (const key of keys) {
- await this.send({ operation: 'DeleteObject', method: 'DELETE', key: this.prefix + key, signal });
+ // S3 answers 204 for a missing key; Google Cloud Storage answers 404 NoSuchKey
+ await this.send({
+ operation: 'DeleteObject',
+ method: 'DELETE',
+ key: this.prefix + key,
+ notFoundKey: key,
+ signal,
+ }).catch((error: unknown) => {
+ if (!(error instanceof StorageFileNotFoundError)) {
+ throw error;
+ }
+ });
}
return;
}
diff --git a/tests/s3-responses.spec.ts b/tests/s3-responses.spec.ts
index 17bf41b..0cf17bb 100644
--- a/tests/s3-responses.spec.ts
+++ b/tests/s3-responses.spec.ts
@@ -411,6 +411,16 @@ describe('S3Disk requests', () => {
expect(calls.map((c) => `${c.method} ${c.url.pathname}${c.url.search}`)).toEqual(['DELETE /shop/only.txt']);
});
+ it('a DeleteObject that answers 404 NoSuchKey (Google Cloud Storage) is not an error', async () => {
+ const { disk: s3, calls } = disk(() => s3Error(404, 'NoSuchKey'), { batchDelete: false });
+ await expect(s3.delete('gone.txt')).resolves.toBeUndefined();
+ await expect(s3.delete(['a.txt', 'b.txt'])).resolves.toBeUndefined();
+ expect(calls).toHaveLength(3);
+
+ const noBucket = disk(() => s3Error(404, 'NoSuchBucket'));
+ await expect(noBucket.disk.delete('a.txt')).rejects.toMatchObject({ code: 'NoSuchBucket' });
+ });
+
it('deletes several keys in one quiet DeleteObjects with escaped, prefixed keys and a matching Content-MD5', async () => {
const { disk: s3, calls } = disk(() => xml(''), { prefix: 'p/' });
await s3.delete(['a&b.txt', '.txt']);