diff --git a/src/storage/version/put.js b/src/storage/version/put.js index d0f8bf8f..c88ab877 100644 --- a/src/storage/version/put.js +++ b/src/storage/version/put.js @@ -14,6 +14,7 @@ import { PutObjectCommand, } from '@aws-sdk/client-s3'; +import { EMPTY_DOC_SIZE } from '../../utils/constants.js'; import getS3Config from '../utils/config.js'; import { getUsersForMetadata, ifMatch, ifNoneMatch, @@ -114,9 +115,19 @@ export async function putObjectWithVersion(env, daCtx, update, body, guid) { const pps = current.metadata?.preparsingstore || '0'; // Store the body if preparsingstore is not defined, so a once-off store - const storeBody = !body && pps === '0'; + let storeBody = !body && pps === '0'; const Preparsingstore = storeBody ? Timestamp : pps; - const Label = storeBody ? 'Collab Parse' : update.label; + let Label = storeBody ? 'Collab Parse' : update.label; + + if (daCtx.method === 'PUT' && current.contentLength > EMPTY_DOC_SIZE && (!update.body || update.body.size <= EMPTY_DOC_SIZE)) { + // we are about to empty the document body + // this should never happen but in some cases it does + // we want then to store a version of the full document as a restore point + // eslint-disable-next-line no-console + console.warn('Empty body, creating a restore point'); + storeBody = true; + Label = 'Restore Point'; + } const versionResp = await putVersion(config, { Bucket: input.Bucket, diff --git a/src/utils/constants.js b/src/utils/constants.js index e53f9d6c..f674b13a 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -20,3 +20,7 @@ export const SUPPORTED_TYPES = [ 'image/svg+xml', 'video/mp4', ]; + +// this is the size of the empty document +//
+export const EMPTY_DOC_SIZE = 83; diff --git a/src/utils/daCtx.js b/src/utils/daCtx.js index bab8d229..84cefa41 100644 --- a/src/utils/daCtx.js +++ b/src/utils/daCtx.js @@ -44,6 +44,7 @@ export default async function getDaCtx(req, env) { users, fullKey, origin: new URL(req.url).origin, + method: req.method, }; // Sanitize the remaining path parts diff --git a/test/storage/version/put.test.js b/test/storage/version/put.test.js index b5497794..74f0639f 100644 --- a/test/storage/version/put.test.js +++ b/test/storage/version/put.test.js @@ -570,7 +570,7 @@ describe('Version Put', () => { }, }); - const resp = await putObjectWithVersion({}, {}, {}); + const resp = await putObjectWithVersion({}, { method: 'HEAD' }, {}); assert.equal(1, sentToS3.length); const input = sentToS3[0].input; assert.equal('', input.Body, 'Empty body for HEAD'); @@ -657,6 +657,88 @@ describe('Version Put', () => { assert(input2.Metadata.Version && input2.Metadata.Version !== 101); }); + it('Test putObjectWithVersion BODY - new BODY is empty creates a restore point', async () => { + const mockGetObject = async () => { + const metadata = { + id: 'idabc', + version: '101', + path: '/qwerty', + timestamp: 1234, + } + return { body: 'Somebody...', metadata, contentLength: 616 }; + }; + + const sentToS3 = []; + const s3Client = { + send: async (c) => { + sentToS3.push(c); + return { + $metadata: { + httpStatusCode: 200 + } + }; + } + }; + const mockS3Client = () => s3Client; + + const sentToS3_2 = []; + const s3Client2 = { + send: async (c) => { + sentToS3_2.push(c); + return { + $metadata: { + httpStatusCode: 200 + } + }; + } + }; + const mockS3Client2 = () => s3Client2; + + const { putObjectWithVersion } = await esmock('../../../src/storage/version/put.js', { + '../../../src/storage/object/get.js': { + default: mockGetObject + }, + '../../../src/storage/utils/version.js': { + ifNoneMatch: mockS3Client, + ifMatch: mockS3Client2 + }, + }); + + const update = { + org: 'o1', + body: '', + key: 'mypath', + type: 'test/plain', + } + const ctx = { + org: 'o1', + users: [{ email: 'hi@acme.com' }], + method: 'PUT', + } + + const resp = await putObjectWithVersion({}, ctx, update, true); + + assert.equal(1, sentToS3.length); + const input = sentToS3[0].input; + + assert.equal('Somebody...', input.Body); + assert.equal(616, input.ContentLength); + assert.equal('/qwerty', input.Metadata.Path); + assert.equal(1234, input.Metadata.Timestamp); + assert.equal('[{"email":"anonymous"}]', input.Metadata.Users); + assert.equal('Restore Point', input.Metadata.Label); + + assert.equal(1, sentToS3_2.length); + const input2 = sentToS3_2[0].input; + assert.equal('', input2.Body); + assert.equal(0, input2.ContentLength); + assert.equal('test/plain', input2.ContentType); + assert.equal('o1/mypath', input2.Key); + assert.equal('mypath', input2.Metadata.Path); + assert.equal('[{"email":"hi@acme.com"}]', input2.Metadata.Users); + assert(input2.Metadata.Version && input2.Metadata.Version !== 101); + }); + it('exception without metadata', async () => { const s3client1 = { send: async (c) => {