diff --git a/test/it/bucket/aem-content-local/.gitignore b/test/it/bucket/aem-content-local/.gitignore new file mode 100644 index 0000000..2d6e00e --- /dev/null +++ b/test/it/bucket/aem-content-local/.gitignore @@ -0,0 +1,6 @@ +# Ignore all S3rver generated files during integration tests +* +# But keep this directory in git +!.gitkeep +!.gitignore + diff --git a/test/it/bucket/aem-content-local/.gitkeep b/test/it/bucket/aem-content-local/.gitkeep new file mode 100644 index 0000000..72696af --- /dev/null +++ b/test/it/bucket/aem-content-local/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the empty directory is tracked by git +# S3rver will use this directory to store objects during integration tests \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_metadata.json b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_metadata.json deleted file mode 100644 index 3a48491..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "content-type": "application/json", - "x-amz-meta-id": "4875c756-42ce-4898-ae86-9b0d88146fbe", - "x-amz-meta-path": "test-repo/test-folder.props", - "x-amz-meta-timestamp": "1764844348129", - "x-amz-meta-users": "[{\"email\":\"anonymous\"}]", - "x-amz-meta-version": "7dd6da34-02a3-4d7f-be02-8170a695199b" -} \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_object b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_object deleted file mode 100644 index 9e26dfe..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_object +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_object.md5 b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_object.md5 deleted file mode 100644 index a3f7110..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder.props._S3rver_object.md5 +++ /dev/null @@ -1 +0,0 @@ -99914b932bd37a50b983c5e7c90ae93b \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_metadata.json b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_metadata.json deleted file mode 100644 index 860d8e5..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_metadata.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "content-type": "text/html" -} \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_object b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_object deleted file mode 100644 index cafecd1..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_object +++ /dev/null @@ -1 +0,0 @@ -

Page 1

\ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_object.md5 b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_object.md5 deleted file mode 100644 index fbe1eba..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page1.html._S3rver_object.md5 +++ /dev/null @@ -1 +0,0 @@ -faf1fc7148f6811144bc58803c37cb7a \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_metadata.json b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_metadata.json deleted file mode 100644 index 860d8e5..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_metadata.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "content-type": "text/html" -} \ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_object b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_object deleted file mode 100644 index 79ab2a5..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_object +++ /dev/null @@ -1 +0,0 @@ -

Page 2

\ No newline at end of file diff --git a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_object.md5 b/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_object.md5 deleted file mode 100644 index 10a8e8f..0000000 --- a/test/it/bucket/aem-content-local/test-org/test-repo/test-folder/page2.html._S3rver_object.md5 +++ /dev/null @@ -1 +0,0 @@ -552f5670094f56c281f89df8a933514b \ No newline at end of file diff --git a/test/it/it-tests.js b/test/it/it-tests.js new file mode 100644 index 0000000..2f9b58b --- /dev/null +++ b/test/it/it-tests.js @@ -0,0 +1,209 @@ +/* + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import assert from 'node:assert'; + +// eslint-disable-next-line func-names +export default (SERVER_URL, ORG, REPO) => describe('Integration Tests: it tests', function () { + // Enable bail to stop on first failure - tests are interdependent + this.bail(true); + + it('delete root folder should cleanup the bucket', async () => { + const url = `${SERVER_URL}/source/${ORG}/${REPO}`; + const resp = await fetch(url, { + method: 'DELETE', + }); + assert.strictEqual(resp.status, 204, `Expected 204 No Content, got ${resp.status}`); + + // validate bucket is empty + const listResp = await fetch(`${SERVER_URL}/list/${ORG}/${REPO}`); + assert.strictEqual(listResp.status, 200, `Expected 200 OK, got ${listResp.status}`); + const listBody = await listResp.json(); + assert.strictEqual(listBody.length, 0, `Expected 0 items, got ${listBody.length}`); + }); + + it('should create a repo via HTTP request', async () => { + const formData = new FormData(); + const blob = new Blob(['{}'], { type: 'application/json' }); + const file = new File([blob], `${REPO}.props`, { type: 'application/json' }); + formData.append('data', file); + + const resp = await fetch(`${SERVER_URL}/source/${ORG}/${REPO}/${REPO}.props`, { + method: 'POST', + body: formData, + }); + assert.ok([200, 201].includes(resp.status), `Expected 200 or 201 for marker, got ${resp.status}`); + }); + + it('should post an object via HTTP request', async () => { + // Now create the actual page + const key = 'test-folder/page1'; + const ext = '.html'; + + // Create FormData with the HTML file + const formData = new FormData(); + const blob = new Blob(['

Page 1

'], { type: 'text/html' }); + const file = new File([blob], 'page1.html', { type: 'text/html' }); + formData.append('data', file); + + const url = `${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`; + let resp = await fetch(url, { + method: 'POST', + body: formData, + }); + + assert.ok([200, 201].includes(resp.status), `Expected 200 or 201, got ${resp.status}`); + + let body = await resp.json(); + assert.strictEqual(body.source.editUrl, `https://da.live/edit#/${ORG}/${REPO}/${key}`); + assert.strictEqual(body.source.contentUrl, `https://content.da.live/${ORG}/${REPO}/${key}`); + assert.strictEqual(body.aem.previewUrl, `https://main--${REPO}--${ORG}.aem.page/${key}`); + assert.strictEqual(body.aem.liveUrl, `https://main--${REPO}--${ORG}.aem.live/${key}`); + + // validate page is here (include extension in GET request) + resp = await fetch(`${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`); + + assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); + + body = await resp.text(); + assert.strictEqual(body, '

Page 1

'); + + // create another page + const key2 = 'test-folder/page2'; + const ext2 = '.html'; + const formData2 = new FormData(); + const htmlBlob2 = new Blob(['

Page 2

'], { type: 'text/html' }); + const htmlFile2 = new File([htmlBlob2], 'page2.html', { type: 'text/html' }); + formData2.append('data', htmlFile2); + resp = await fetch(`${SERVER_URL}/source/${ORG}/${REPO}/${key2}${ext2}`, { + method: 'POST', + body: formData2, + }); + assert.ok([200, 201].includes(resp.status), `Expected 200 or 201, got ${resp.status}`); + }); + + it('should list objects via HTTP request', async () => { + const key = 'test-folder'; + + const url = `${SERVER_URL}/list/${ORG}/${REPO}/${key}`; + const resp = await fetch(url); + + assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); + + const body = await resp.json(); + + const fileNames = body.map((item) => item.name); + assert.ok(fileNames.includes('page1'), 'Should list page1'); + assert.ok(fileNames.includes('page2'), 'Should list page2'); + }); + + it('should list repos via HTTP request', async () => { + const url = `${SERVER_URL}/list/${ORG}`; + const resp = await fetch(url); + + assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); + + const body = await resp.json(); + assert.strictEqual(body.length, 1, `Expected 1 repo, got ${body.length}`); + assert.strictEqual(body[0].name, REPO, `Expected ${REPO}, got ${body[0].name}`); + }); + + it('should delete an object via HTTP request', async () => { + const key = 'test-folder/page2'; + const ext = '.html'; + + const url = `${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`; + let resp = await fetch(url, { + method: 'DELETE', + }); + assert.strictEqual(resp.status, 204, `Expected 204 No Content, got ${resp.status}`); + + // validate page is not here + resp = await fetch(`${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`); + assert.strictEqual(resp.status, 404, `Expected 404 Not Found, got ${resp.status}`); + }); + + it('should deal with no config found via HTTP request', async () => { + const url = `${SERVER_URL}/config/${ORG}`; + const resp = await fetch(url); + + assert.strictEqual(resp.status, 404, `Expected 404, got ${resp.status}`); + }); + + it('should delete root folder', async () => { + const url = `${SERVER_URL}/source/${ORG}/${REPO}`; + const resp = await fetch(url, { + method: 'DELETE', + }); + assert.strictEqual(resp.status, 204, `Previous test should have logged out, got ${resp.status}`); + }); + + it('should post and get org config via HTTP request', async () => { + // First POST the config - must include CONFIG write permission + const configData = JSON.stringify({ + total: 2, + limit: 2, + offset: 0, + data: [ + { path: 'CONFIG', actions: 'write', groups: 'anonymous' }, + { key: 'admin.role.all', value: 'test-value' }, + ], + ':type': 'sheet', + ':sheetname': 'permissions', + }); + + const formData = new FormData(); + formData.append('config', configData); + + let url = `${SERVER_URL}/config/${ORG}`; + let resp = await fetch(url, { + method: 'POST', + body: formData, + }); + + assert.ok([200, 201].includes(resp.status), `Expected 200 or 201, got ${resp.status}`); + + // Now GET the config + url = `${SERVER_URL}/config/${ORG}`; + resp = await fetch(url); + + assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); + + const body = await resp.json(); + assert.strictEqual(body.total, 2, `Expected 2, got ${body.total}`); + assert.strictEqual(body.data[0].path, 'CONFIG', `Expected CONFIG, got ${body.data[0].path}`); + assert.strictEqual(body.data[0].actions, 'write', `Expected write, got ${body.data[0].actions}`); + assert.strictEqual(body.data[1].key, 'admin.role.all', `Expected admin.role.all, got ${body.data[1].key}`); + assert.strictEqual(body.data[1].value, 'test-value', `Expected test-value, got ${body.data[1].value}`); + }); + + it('cannot recreate root folder because of auth (previous test should setup auth)', async () => { + const formData = new FormData(); + const blob = new Blob(['{}'], { type: 'application/json' }); + const file = new File([blob], `${REPO}.props`, { type: 'application/json' }); + formData.append('data', file); + + const resp = await fetch(`${SERVER_URL}/source/${ORG}/${REPO}/${REPO}.props`, { + method: 'POST', + body: formData, + }); + assert.strictEqual(resp.status, 401, `Previous test should have setup auth, got ${resp.status}`); + }); + + it('should logout via HTTP request', async () => { + const url = `${SERVER_URL}/logout`; + const resp = await fetch(url, { + method: 'POST', + }); + + assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); + }); +}); diff --git a/test/it/smoke.test.js b/test/it/smoke.test.js index b89709a..e15a473 100644 --- a/test/it/smoke.test.js +++ b/test/it/smoke.test.js @@ -10,12 +10,13 @@ * governing permissions and limitations under the License. */ /* eslint-disable prefer-arrow-callback, func-names */ -import assert from 'node:assert'; import S3rver from 's3rver'; import { spawn } from 'child_process'; import path from 'path'; import kill from 'tree-kill'; +import itTests from './it-tests.js'; + const S3_PORT = 4569; const SERVER_PORT = 8788; const SERVER_URL = `http://localhost:${SERVER_PORT}`; @@ -110,129 +111,5 @@ describe('Integration Tests: smoke tests', function () { } }); - it('should get a object via HTTP request', async () => { - const pathname = 'test-folder/page1.html'; - - const url = `${SERVER_URL}/source/${ORG}/${REPO}/${pathname}`; - const resp = await fetch(url); - - assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); - - const body = await resp.text(); - assert.strictEqual(body, '

Page 1

'); - }); - - it('should list objects via HTTP request', async () => { - const key = 'test-folder'; - - const url = `${SERVER_URL}/list/${ORG}/${REPO}/${key}`; - const resp = await fetch(url); - - assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); - - const body = await resp.json(); - - const fileNames = body.map((item) => item.name); - assert.ok(fileNames.includes('page1'), 'Should list page1'); - assert.ok(fileNames.includes('page2'), 'Should list page2'); - }); - - it('should post an object via HTTP request', async () => { - const key = 'test-folder/page3'; - const ext = '.html'; - - // Create FormData with the HTML file - const formData = new FormData(); - const htmlBlob = new Blob(['

Page 3

'], { type: 'text/html' }); - const htmlFile = new File([htmlBlob], 'page3.html', { type: 'text/html' }); - formData.append('data', htmlFile); - - const url = `${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`; - let resp = await fetch(url, { - method: 'POST', - body: formData, - }); - - assert.ok([200, 201].includes(resp.status), `Expected 200 or 201, got ${resp.status}`); - - let body = await resp.json(); - assert.strictEqual(body.source.editUrl, `https://da.live/edit#/${ORG}/${REPO}/${key}`); - assert.strictEqual(body.source.contentUrl, `https://content.da.live/${ORG}/${REPO}/${key}`); - assert.strictEqual(body.aem.previewUrl, `https://main--${REPO}--${ORG}.aem.page/${key}`); - assert.strictEqual(body.aem.liveUrl, `https://main--${REPO}--${ORG}.aem.live/${key}`); - - // validate page is here (include extension in GET request) - resp = await fetch(`${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`); - - assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); - - body = await resp.text(); - assert.strictEqual(body, '

Page 3

'); - }); - - it('should logout via HTTP request', async () => { - const url = `${SERVER_URL}/logout`; - const resp = await fetch(url, { - method: 'POST', - }); - - assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); - }); - - it('should list repos via HTTP request', async () => { - const url = `${SERVER_URL}/list/${ORG}`; - const resp = await fetch(url); - - assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); - - const body = await resp.json(); - assert.strictEqual(body.length, 1, `Expected 1 repo, got ${body.length}`); - assert.strictEqual(body[0].name, REPO, `Expected ${REPO}, got ${body[0].name}`); - }); - - it('should deal with no config found via HTTP request', async () => { - const url = `${SERVER_URL}/config/${ORG}`; - const resp = await fetch(url); - - assert.strictEqual(resp.status, 404, `Expected 404, got ${resp.status}`); - }); - - it('should post and get org config via HTTP request', async () => { - // First POST the config - must include CONFIG write permission - const configData = JSON.stringify({ - total: 2, - limit: 2, - offset: 0, - data: [ - { path: 'CONFIG', actions: 'write', groups: 'anonymous' }, - { key: 'admin.role.all', value: 'test-value' }, - ], - ':type': 'sheet', - ':sheetname': 'permissions', - }); - - const formData = new FormData(); - formData.append('config', configData); - - let url = `${SERVER_URL}/config/${ORG}`; - let resp = await fetch(url, { - method: 'POST', - body: formData, - }); - - assert.ok([200, 201].includes(resp.status), `Expected 200 or 201, got ${resp.status}`); - - // Now GET the config - url = `${SERVER_URL}/config/${ORG}`; - resp = await fetch(url); - - assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`); - - const body = await resp.json(); - assert.strictEqual(body.total, 2, `Expected 2, got ${body.total}`); - assert.strictEqual(body.data[0].path, 'CONFIG', `Expected CONFIG, got ${body.data[0].path}`); - assert.strictEqual(body.data[0].actions, 'write', `Expected write, got ${body.data[0].actions}`); - assert.strictEqual(body.data[1].key, 'admin.role.all', `Expected admin.role.all, got ${body.data[1].key}`); - assert.strictEqual(body.data[1].value, 'test-value', `Expected test-value, got ${body.data[1].value}`); - }); + itTests(SERVER_URL, ORG, REPO); });