Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions src/handlers/unknown.js

This file was deleted.

15 changes: 12 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import headHandler from './handlers/head.js';
import getHandler from './handlers/get.js';
import postHandler from './handlers/post.js';
import deleteHandler from './handlers/delete.js';
import unknownHandler from './handlers/unknown.js';

export default {
/**
Expand All @@ -29,7 +28,17 @@ export default {
return daResp({ status: 204 });
}

const daCtx = await getDaCtx(req, env);
let daCtx;
try {
daCtx = await getDaCtx(req, env);
} catch (e) {
if (e.message === 'Invalid path') {
return daResp({ status: 400 });
}
console.error('Error computing context', e);
return daResp({ status: 500 });
}

const { authorized, key } = daCtx;
if (!authorized) {
const status = daCtx.users[0].email === 'anonymous' ? 401 : 403;
Expand Down Expand Up @@ -57,7 +66,7 @@ export default {
respObj = await deleteHandler({ req, env, daCtx });
break;
default:
respObj = unknownHandler();
respObj = { status: 405 };
}

if (!respObj) return daResp({ status: 404 });
Expand Down
14 changes: 14 additions & 0 deletions src/utils/daCtx.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ export default async function getDaCtx(req, env) {
const path = parts.filter((part) => part !== '');
const keyBase = path.join('/');

// Compare incoming pathname and intent we understand from it
const pnlc = pathname.toLocaleLowerCase();
let validPath = '';
if (api) validPath += `/${api}`;
if (org) validPath += `/${org}`;
if (keyBase) validPath += `/${keyBase}`;

// Normalize paths by removing trailing slashes before comparison
const normalize = (p) => p.replace(/\/+$/, '');
if (normalize(pnlc) !== normalize(validPath)) {
// if intent is not what is requested (+/- trailing slash), declare invalid
throw new Error('Invalid path');
}

// Get the final source name
daCtx.filename = path.pop() || '';

Expand Down
21 changes: 0 additions & 21 deletions test/handlers/unknown.test.js

This file was deleted.

49 changes: 47 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ describe('fetch', () => {
});

it('should return a response object for unknown', async () => {
const resp = await handler.fetch({ url: 'https://www.example.com', method: 'BLAH' }, {});
assert.strictEqual(resp.status, 501);
const resp = await handler.fetch({ url: 'https://www.example.com/endpoint/repo/path/file.html', method: 'BLAH' }, {});
assert.strictEqual(resp.status, 405);
});

it('should return 401 when not authorized and not logged in', async () => {
Expand Down Expand Up @@ -54,4 +54,49 @@ describe('fetch', () => {
const resp = await handler.fetch({ method: 'GET', url: 'http://www.example.com/' }, {});
assert.strictEqual(resp.status, 404);
});

it('should return 500 when getDaCtx throws unexpected error', async () => {
const hnd = await esmock('../src/index.js', {
'../src/utils/daCtx.js': {
default: async () => {
throw new Error('Unexpected ctx error');
},
},
});

const resp = await hnd.fetch({ method: 'GET', url: 'http://www.example.com/source/org/repo/file.html' }, {});
assert.strictEqual(resp.status, 500);
});
});

describe('invalid routes', () => {
const fetchStatus = async (path, method) => {
const resp = await handler.fetch({ method, url: `http://www.sample.com${path}` }, {});
return resp.status;
};

const test = async (path, status) => {
const methods = ['GET', 'POST', 'PUT', 'DELETE'];
for (const method of methods) {
// eslint-disable-next-line no-await-in-loop
const s = await fetchStatus(path, method);
assert.strictEqual(s, status);
}
};

it('return 400 for invalid paths', async () => {
await test('/source//org/repo/path/file.html', 400);
await test('/source/org//repo/path/file.html', 400);
await test('/source/org/repo//path/file.html', 400);
await test('/source/org/repo/path//file.html', 400);
});

it('return 404 for unknown paths', async () => {
await test('/unknown/owner/repo/path/file.html', 404);
});

it('return 405 for unknown methods', async () => {
const status = await fetchStatus('/source/owner/repo/path/file.html', 'BLAH');
assert.strictEqual(status, 405);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
99914b932bd37a50b983c5e7c90ae93b
99 changes: 85 additions & 14 deletions test/it/smoke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,24 @@ const SERVER_PORT = 8788;
const SERVER_URL = `http://localhost:${SERVER_PORT}`;
const S3_DIR = './test/it/bucket';

const ORG = 'test-org';
const REPO = 'test-repo';

describe('Integration Tests: smoke tests', function () {
let s3rver;
let devServer;

before(async function () {
// Increase timeout for server startup
this.timeout(30000);

// Clear wrangler state to start fresh - needed only for local testing
const fs = await import('fs');
const wranglerState = path.join(process.cwd(), '.wrangler/state');
if (fs.existsSync(wranglerState)) {
fs.rmSync(wranglerState, { recursive: true });
}

s3rver = new S3rver({
port: S3_PORT,
address: '127.0.0.1',
Expand Down Expand Up @@ -100,11 +111,9 @@ describe('Integration Tests: smoke tests', function () {
});

it('should get a object via HTTP request', async () => {
const org = 'test-org';
const repo = 'test-repo';
const pathname = 'test-folder/page1.html';

const url = `${SERVER_URL}/source/${org}/${repo}/${pathname}`;
const url = `${SERVER_URL}/source/${ORG}/${REPO}/${pathname}`;
const resp = await fetch(url);

assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`);
Expand All @@ -114,11 +123,9 @@ describe('Integration Tests: smoke tests', function () {
});

it('should list objects via HTTP request', async () => {
const org = 'test-org';
const repo = 'test-repo';
const key = 'test-folder';

const url = `${SERVER_URL}/list/${org}/${repo}/${key}`;
const url = `${SERVER_URL}/list/${ORG}/${REPO}/${key}`;
const resp = await fetch(url);

assert.strictEqual(resp.status, 200, `Expected 200 OK, got ${resp.status}`);
Expand All @@ -131,8 +138,6 @@ describe('Integration Tests: smoke tests', function () {
});

it('should post an object via HTTP request', async () => {
const org = 'test-org';
const repo = 'test-repo';
const key = 'test-folder/page3';
const ext = '.html';

Expand All @@ -142,7 +147,7 @@ describe('Integration Tests: smoke tests', function () {
const htmlFile = new File([htmlBlob], 'page3.html', { type: 'text/html' });
formData.append('data', htmlFile);

const url = `${SERVER_URL}/source/${org}/${repo}/${key}${ext}`;
const url = `${SERVER_URL}/source/${ORG}/${REPO}/${key}${ext}`;
let resp = await fetch(url, {
method: 'POST',
body: formData,
Expand All @@ -151,17 +156,83 @@ describe('Integration Tests: smoke tests', function () {
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--test-repo--test-org.aem.page/${key}`);
assert.strictEqual(body.aem.liveUrl, `https://main--test-repo--test-org.aem.live/${key}`);
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}`);
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, '<html><body><h1>Page 3</h1></body></html>');
});

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