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);
});