diff --git a/src/PipelineResponse.js b/src/PipelineResponse.js
index 4cca29ed..f6dff206 100644
--- a/src/PipelineResponse.js
+++ b/src/PipelineResponse.js
@@ -30,7 +30,7 @@ export class PipelineResponse {
document: undefined,
headers,
error: undefined,
- lastModifiedTime: 0,
+ lastModifiedSources: {},
});
}
diff --git a/src/html-pipe.js b/src/html-pipe.js
index b5f19e10..3fce9294 100644
--- a/src/html-pipe.js
+++ b/src/html-pipe.js
@@ -38,6 +38,7 @@ import { PipelineResponse } from './PipelineResponse.js';
import { validatePathInfo } from './utils/path.js';
import { initAuthRoute } from './utils/auth.js';
import fetchMappedMetadata from './steps/fetch-mapped-metadata.js';
+import { applyMetaLastModified, setLastModified } from './utils/last-modified.js';
/**
* Fetches the content and if not found, fetches the 404.html
@@ -162,6 +163,7 @@ export async function htmlPipe(state, req) {
log[level](`pipeline status: ${res.status} ${res.error}`);
res.headers.set('x-error', cleanupHeaderValue(res.error));
if (res.status < 500) {
+ setLastModified(state, res);
await setCustomResponseHeaders(state, req, res);
}
return res;
@@ -188,8 +190,10 @@ export async function htmlPipe(state, req) {
await render(state, req, res);
state.timer?.update('serialize');
await tohtml(state, req, res);
+ await applyMetaLastModified(state, res);
}
+ setLastModified(state, res);
await setCustomResponseHeaders(state, req, res);
await setXSurrogateKeyHeader(state, req, res);
} catch (e) {
diff --git a/src/json-pipe.js b/src/json-pipe.js
index dbbe5cb1..e4a4024a 100644
--- a/src/json-pipe.js
+++ b/src/json-pipe.js
@@ -14,7 +14,7 @@ import fetchConfigAll from './steps/fetch-config-all.js';
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
import { PipelineResponse } from './PipelineResponse.js';
import jsonFilter from './utils/json-filter.js';
-import { extractLastModified, updateLastModified } from './utils/last-modified.js';
+import { extractLastModified, recordLastModified, setLastModified } from './utils/last-modified.js';
import { authenticate } from './steps/authenticate.js';
import fetchConfig from './steps/fetch-config.js';
import { getPathInfo } from './utils/path.js';
@@ -85,7 +85,7 @@ async function fetchJsonContent(state, req, res) {
state.content.sourceLocation = ret.headers.get('x-amz-meta-x-source-location');
log.info(`source-location: ${state.content.sourceLocation}`);
- updateLastModified(state, res, extractLastModified(ret.headers));
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
} else {
// also add code surrogate key in case json is later added to code bus (#688)
state.content.sourceBus = 'code|content';
@@ -195,6 +195,7 @@ export async function jsonPipe(state, req) {
const keys = await computeSurrogateKeys(state);
res.headers.set('x-surrogate-key', keys.join(' '));
+ setLastModified(state, res);
await setCustomResponseHeaders(state, req, res);
return res;
} catch (e) {
diff --git a/src/sitemap-pipe.js b/src/sitemap-pipe.js
index 0dbd5734..750c29ab 100644
--- a/src/sitemap-pipe.js
+++ b/src/sitemap-pipe.js
@@ -20,6 +20,7 @@ import setXSurrogateKeyHeader from './steps/set-x-surrogate-key-header.js';
import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
import { PipelineStatusError } from './PipelineStatusError.js';
import { PipelineResponse } from './PipelineResponse.js';
+import { extractLastModified, recordLastModified, setLastModified } from './utils/last-modified.js';
async function generateSitemap(state) {
const {
@@ -118,6 +119,7 @@ export async function sitemapPipe(state, req) {
const ret = await generateSitemap(state);
if (ret.status === 200) {
res.status = 200;
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
delete res.error;
state.content.data = ret.body;
}
@@ -129,6 +131,7 @@ export async function sitemapPipe(state, req) {
state.timer?.update('serialize');
await renderCode(state, req, res);
+ setLastModified(state, res);
await setCustomResponseHeaders(state, req, res);
await setXSurrogateKeyHeader(state, req, res);
} catch (e) {
diff --git a/src/steps/fetch-404.js b/src/steps/fetch-404.js
index 22c45fbf..018506e5 100644
--- a/src/steps/fetch-404.js
+++ b/src/steps/fetch-404.js
@@ -9,7 +9,7 @@
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
-import { extractLastModified } from '../utils/last-modified.js';
+import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
import { getPathKey } from './set-x-surrogate-key-header.js';
/**
@@ -29,7 +29,7 @@ export default async function fetch404(state, req, res) {
// override last-modified if source-last-modified is set
const lastModified = extractLastModified(ret.headers);
if (lastModified) {
- ret.headers.set('last-modified', lastModified);
+ recordLastModified(state, res, 'content', lastModified);
}
// keep 404 response status
diff --git a/src/steps/fetch-config-all.js b/src/steps/fetch-config-all.js
index 1f84e011..003c0460 100644
--- a/src/steps/fetch-config-all.js
+++ b/src/steps/fetch-config-all.js
@@ -11,7 +11,7 @@
*/
import { PipelineStatusError } from '../PipelineStatusError.js';
-import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
+import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
import { globToRegExp, Modifiers } from '../utils/modifiers.js';
import { getOriginalHost } from './utils.js';
@@ -74,7 +74,7 @@ export default async function fetchConfigAll(state, req, res) {
if (state.type === 'html' && state.info.selector !== 'plain') {
// also update last-modified (only for extensionless html pipeline)
- updateLastModified(state, res, extractLastModified(ret.headers));
+ recordLastModified(state, res, 'configAll', extractLastModified(ret.headers));
}
// set custom preview and live hosts
state.previewHost = replaceParams(state.config.cdn?.preview?.host, state);
diff --git a/src/steps/fetch-config.js b/src/steps/fetch-config.js
index e9b72102..9a955ef7 100644
--- a/src/steps/fetch-config.js
+++ b/src/steps/fetch-config.js
@@ -9,7 +9,7 @@
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
-import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
+import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
import { PipelineStatusError } from '../PipelineStatusError.js';
/**
@@ -67,11 +67,11 @@ export default async function fetchConfig(state, req, res) {
const configLastModified = extractLastModified(ret.headers);
// update last modified of fstab
- updateLastModified(state, res, config.fstab?.lastModified || configLastModified);
+ recordLastModified(state, res, 'config', config.fstab?.lastModified || configLastModified);
// for html requests, also consider the HEAD config
if (state.type === 'html' && state.info.selector !== 'plain' && config.head?.lastModified) {
- updateLastModified(state, res, config.head.lastModified);
+ recordLastModified(state, res, 'head', config.head.lastModified);
}
}
diff --git a/src/steps/fetch-content.js b/src/steps/fetch-content.js
index 9010ef5c..9f94f3d2 100644
--- a/src/steps/fetch-content.js
+++ b/src/steps/fetch-content.js
@@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/
import { computeSurrogateKey } from '@adobe/helix-shared-utils';
-import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
+import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
/**
* Loads the content from either the content-bus or code-bus and stores it in `state.content`
@@ -67,7 +67,7 @@ export default async function fetchContent(state, req, res) {
state.content.sourceLocation = ret.headers.get('x-amz-meta-x-source-location');
log.info(`source-location: ${state.content.sourceLocation}`);
- updateLastModified(state, res, extractLastModified(ret.headers));
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
// reject requests to /index *after* checking for redirects
// (https://github.com/adobe/helix-pipeline-service/issues/290)
diff --git a/src/utils/last-modified.js b/src/utils/last-modified.js
index 299379f4..bae67334 100644
--- a/src/utils/last-modified.js
+++ b/src/utils/last-modified.js
@@ -11,30 +11,43 @@
*/
/**
- * Updates the context.content.lastModified if the time in `timeString` is newer than the existing
- * one if none exists yet. please note that it generates helper property `lastModifiedTime` in
- * unix epoch format.
- *
- * the date string will be a "http-date": https://httpwg.org/specs/rfc7231.html#http.date
+ * Records the last modified for the given source.
*
* @param {PipelineState} state
- * @param {PipelineResponse} res the pipeline context
+ * @param {PipelineResponse} res the pipeline response
+ * @param {string} source the source providing a last-modified date
* @param {string} httpDate http-date string
*/
-export function updateLastModified(state, res, httpDate) {
+export function recordLastModified(state, res, source, httpDate) {
if (!httpDate) {
return;
}
const { log } = state;
- const time = new Date(httpDate).getTime();
- if (Number.isNaN(time)) {
- log.warn(`updateLastModified date is invalid: ${httpDate}`);
+ const date = new Date(httpDate);
+ if (Number.isNaN(date.valueOf())) {
+ log.warn(`last-modified date is invalid: ${httpDate} for ${source}`);
return;
}
+ res.lastModifiedSources[source] = {
+ time: date.valueOf(),
+ date: date.toUTCString(),
+ };
+}
- if (time > (res.lastModifiedTime ?? 0)) {
- res.lastModifiedTime = time;
- res.headers.set('last-modified', httpDate);
+/**
+ * Calculates the last modified by using the latest date from all the recorded sources
+ * and sets it on the `last-modified` header.
+ *
+ * @param {PipelineState} state
+ * @param {PipelineResponse} res the pipeline response
+ */
+export function setLastModified(state, res) {
+ let latestTime = 0;
+ for (const { time, date } of Object.values(res.lastModifiedSources)) {
+ if (time > latestTime) {
+ latestTime = time;
+ res.headers.set('last-modified', date);
+ }
}
}
@@ -55,3 +68,13 @@ export function extractLastModified(headers) {
}
return headers.get('last-modified');
}
+
+/**
+ * Sets the metadata last modified entry to the one define in the page specific metadata if
+ * it exists. this allows to control the last-modified per metadata record.
+ * @param {PipelineState} state
+ * @param {PipelineResponse} res the pipeline response
+ */
+export function applyMetaLastModified(state, res) {
+ recordLastModified(state, res, 'metadata', state.content.meta.page['last-modified']);
+}
diff --git a/test/fixtures/content/generic-product/metadata.json b/test/fixtures/content/generic-product/metadata.json
index 8a8bdc4e..1ff24b81 100644
--- a/test/fixtures/content/generic-product/metadata.json
+++ b/test/fixtures/content/generic-product/metadata.json
@@ -60,6 +60,10 @@
"Keywords": "Exactomento Mapped Folder",
"og:publisher": "Adobe",
"Short Title": "E"
+ },
+ {
+ "URL": "/products/product2",
+ "last-modified": "2024-12-25T03:33:33Z"
}
]
}
diff --git a/test/json-pipe.test.js b/test/json-pipe.test.js
index 8acf759b..1aee8a8c 100644
--- a/test/json-pipe.test.js
+++ b/test/json-pipe.test.js
@@ -108,7 +108,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
)
@@ -144,7 +144,7 @@ describe('JSON Pipe Test', () => {
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'content-type': 'application/json',
'x-surrogate-key': 'Atrz_qDg26DmSe9a foobar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
});
});
@@ -168,7 +168,7 @@ describe('JSON Pipe Test', () => {
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'content-type': 'application/json',
'x-surrogate-key': 'Atrz_qDg26DmSe9a foobar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
});
});
@@ -206,7 +206,7 @@ describe('JSON Pipe Test', () => {
assert.deepStrictEqual(headers, {
'access-control-allow-origin': '*',
'content-security-policy': 'default-src \'self\'',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
'x-surrogate-key': 'Atrz_qDg26DmSe9a foobar',
'content-type': 'application/json',
});
@@ -311,7 +311,7 @@ describe('JSON Pipe Test', () => {
});
const headers = Object.fromEntries(resp.headers.entries());
assert.deepStrictEqual(headers, {
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
'x-surrogate-key': 'Atrz_qDg26DmSe9a foobar',
'content-type': 'application/json',
});
@@ -326,7 +326,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
'x-amz-meta-x-source-last-modified': 'Wed, 12 Oct 2009 15:50:00 GMT',
},
}),
@@ -343,7 +343,7 @@ describe('JSON Pipe Test', () => {
});
const headers = Object.fromEntries(resp.headers.entries());
assert.deepStrictEqual(headers, {
- 'last-modified': 'Wed, 12 Oct 2009 15:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 15:50:00 GMT',
'x-surrogate-key': 'Atrz_qDg26DmSe9a foobar',
'content-type': 'application/json',
});
@@ -364,7 +364,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
)
@@ -385,7 +385,7 @@ describe('JSON Pipe Test', () => {
});
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'content-type': 'application/json',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
'x-surrogate-key': 'SIMSxecp2CJXqGYs ref--repo--owner_code',
});
});
@@ -408,7 +408,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
)
@@ -426,7 +426,7 @@ describe('JSON Pipe Test', () => {
});
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'content-type': 'application/json',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
'x-surrogate-key': 'SIMSxecp2CJXqGYs ref--repo--owner_code',
});
});
@@ -529,7 +529,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
)
@@ -624,7 +624,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
)
@@ -648,7 +648,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
);
@@ -683,7 +683,7 @@ describe('JSON Pipe Test', () => {
headers: {
'content-type': 'application/json',
'x-amz-meta-x-source-location': 'foo-bar',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Mon, 12 Oct 2009 17:50:00 GMT',
},
}),
);
diff --git a/test/rendering.test.js b/test/rendering.test.js
index b27d0ee4..d51917fe 100644
--- a/test/rendering.test.js
+++ b/test/rendering.test.js
@@ -369,11 +369,11 @@ describe('Rendering', () => {
it('renders 404.html if content not found', async () => {
loader
.rewrite('404.html', 'super-test/404-test.html')
- .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Wed, 12 Oct 2009 17:50:00 GMT');
+ .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Mon, 12 Oct 2022 17:50:00 GMT');
const { body, headers } = await testRender('not-found-with-handler', 'html', 404);
assert.deepStrictEqual(Object.fromEntries(headers.entries()), {
'content-type': 'text/html; charset=utf-8',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Wed, 12 Oct 2022 17:50:00 GMT',
'x-surrogate-key': 'OYsA_wfqip5EuBu6 foo-id super-test--helix-pages--adobe_404 super-test--helix-pages--adobe_code',
'x-error': 'failed to load /not-found-with-handler.md from content-bus: 404',
'access-control-allow-origin': '*',
@@ -385,11 +385,11 @@ describe('Rendering', () => {
it('renders 404.html if content not found for .plain.html', async () => {
loader
.rewrite('super-test/404.html', 'super-test/404-test.html')
- .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Wed, 12 Oct 2009 17:50:00 GMT');
+ .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Mon, 12 Oct 2022 17:50:00 GMT');
const { body, headers } = await testRender('not-found-with-handler.plain.html', 'html', 404);
assert.deepStrictEqual(Object.fromEntries(headers.entries()), {
'content-type': 'text/html; charset=utf-8',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Wed, 12 Oct 2022 17:50:00 GMT',
'x-surrogate-key': 'OYsA_wfqip5EuBu6 foo-id super-test--helix-pages--adobe_404 super-test--helix-pages--adobe_code',
'x-error': 'failed to load /not-found-with-handler.md from content-bus: 404',
'access-control-allow-origin': '*',
@@ -400,11 +400,11 @@ describe('Rendering', () => {
it('renders 404.html if content not found for static html', async () => {
loader
.rewrite('super-test/404.html', 'super-test/404-test.html')
- .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Wed, 12 Oct 2009 17:50:00 GMT');
+ .headers('super-test/404-test.html', 'x-amz-meta-x-source-last-modified', 'Wed, 12 Oct 2022 17:50:00 GMT');
const { body, headers } = await testRender('not-found-with-handler.html', 'html', 404);
assert.deepStrictEqual(Object.fromEntries(headers.entries()), {
'content-type': 'text/html; charset=utf-8',
- 'last-modified': 'Wed, 12 Oct 2009 17:50:00 GMT',
+ 'last-modified': 'Wed, 12 Oct 2022 17:50:00 GMT',
'x-error': 'failed to load /not-found-with-handler.html from code-bus: 404',
'x-surrogate-key': 'ta3V7wR3zlRh1b0E foo-id super-test--helix-pages--adobe_404 super-test--helix-pages--adobe_code',
link: '; rel=modulepreload; as=script; crossorigin=use-credentials',
@@ -554,7 +554,7 @@ describe('Rendering', () => {
it('respect metadata with folder mapping: self and descendents', async () => {
loader.status('.helix/config-all.json', 404);
loader
- .headers('generic-product/metadata.json', 'last-modified', 'Thu Nov 07 2024 00:00:00 GMT+0000');
+ .headers('generic-product/metadata.json', 'last-modified', 'Thu, 07 Nov 2024 00:00:00 GMT');
let resp = await render(new URL('https://helix-pipeline.com/products'));
assert.strictEqual(resp.status, 200);
@@ -572,6 +572,18 @@ describe('Rendering', () => {
'last-modified': 'Fri, 30 Apr 2021 03:47:18 GMT',
'x-surrogate-key': 'AkcHu8fRFT7HarTR foo-id_metadata super-test--helix-pages--adobe_head foo-id AkcHu8fRFT7HarTR_metadata z8NGXvKB0X5Fzcnd',
});
+
+ // product2 has a custom last-modified defined in the metadata
+ resp = await render(new URL('https://helix-pipeline.com/products/product2'));
+ assert.strictEqual(resp.status, 200);
+ assert.match(resp.body, //);
+ assert.match(resp.body, //);
+ assert.match(resp.body, //);
+ assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
+ 'content-type': 'text/html; charset=utf-8',
+ 'last-modified': 'Wed, 25 Dec 2024 03:33:33 GMT',
+ 'x-surrogate-key': 'AkcHu8fRFT7HarTR foo-id_metadata super-test--helix-pages--adobe_head foo-id AkcHu8fRFT7HarTR_metadata G03gAJ9i4zOGySKf',
+ });
});
it('handles error while loading mapped metadata', async () => {
diff --git a/test/sitemap-pipe.test.js b/test/sitemap-pipe.test.js
index 74ea18e4..dee9acfe 100644
--- a/test/sitemap-pipe.test.js
+++ b/test/sitemap-pipe.test.js
@@ -18,6 +18,7 @@ import {
sitemapPipe, PipelineRequest, PipelineResponse, PipelineState,
} from '../src/index.js';
import { StaticS3Loader } from './StaticS3Loader.js';
+import { setLastModified } from '../src/utils/last-modified.js';
describe('Sitemap Pipe Test', () => {
it('responds with 500 for non sitemap', async () => {
@@ -44,6 +45,7 @@ describe('Sitemap Pipe Test', () => {
}),
new PipelineRequest(new URL('https://www.hlx.live/')),
);
+ setLastModified(null, resp);
assert.strictEqual(resp.status, 502);
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'content-type': 'text/plain; charset=utf-8',
@@ -67,6 +69,7 @@ describe('Sitemap Pipe Test', () => {
}),
new PipelineRequest(new URL('https://www.hlx.live/')),
);
+ setLastModified(null, resp);
assert.strictEqual(resp.status, 404);
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'access-control-allow-origin': '*',
@@ -92,6 +95,7 @@ describe('Sitemap Pipe Test', () => {
}),
new PipelineRequest(new URL('https://www.hlx.live/')),
);
+ setLastModified(null, resp);
assert.strictEqual(resp.status, 404);
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'access-control-allow-origin': '*',
@@ -117,6 +121,7 @@ describe('Sitemap Pipe Test', () => {
}),
new PipelineRequest(new URL('https://www.hlx.live/')),
);
+ setLastModified(null, resp);
assert.strictEqual(resp.status, 404);
assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), {
'access-control-allow-origin': '*',
diff --git a/test/steps/fetch-config.test.js b/test/steps/fetch-config.test.js
index 42a6604d..5e11d250 100644
--- a/test/steps/fetch-config.test.js
+++ b/test/steps/fetch-config.test.js
@@ -15,6 +15,7 @@ import { PipelineStatusError } from '../../src/PipelineStatusError.js';
import fetchConfig from '../../src/steps/fetch-config.js';
import { StaticS3Loader } from '../StaticS3Loader.js';
import { PipelineRequest, PipelineResponse } from '../../src/index.js';
+import { setLastModified } from '../../src/utils/last-modified.js';
describe('Fetch Config', () => {
it('updates last modified', async () => {
@@ -38,6 +39,7 @@ describe('Fetch Config', () => {
const req = new PipelineRequest('https://localhost:3000');
const res = new PipelineResponse();
await fetchConfig(state, req, res);
+ setLastModified(state, res);
assert.deepStrictEqual(state.helixConfig, {
fstab: {
data: {},
@@ -74,6 +76,7 @@ describe('Fetch Config', () => {
const req = new PipelineRequest('https://localhost:3000');
const res = new PipelineResponse();
await fetchConfig(state, req, res);
+ setLastModified(state, res);
assert.deepStrictEqual(Object.fromEntries(res.headers.entries()), {
'last-modified': 'Wed, 12 Jan 2022 09:33:01 GMT',
});
@@ -99,6 +102,7 @@ describe('Fetch Config', () => {
const req = new PipelineRequest('https://localhost:3000');
const res = new PipelineResponse();
await fetchConfig(state, req, res);
+ setLastModified(state, res);
assert.deepStrictEqual(state.helixConfig, {
version: 2,
content: {
@@ -138,8 +142,9 @@ describe('Fetch Config', () => {
const req = new PipelineRequest('https://localhost:3000');
const res = new PipelineResponse();
await fetchConfig(state, req, res);
+ setLastModified(state, res);
assert.deepStrictEqual(Object.fromEntries(res.headers.entries()), {
- 'last-modified': 'Wed, 14 Jan 2022 09:33:01 GMT',
+ 'last-modified': 'Fri, 14 Jan 2022 09:33:01 GMT',
});
});
@@ -171,8 +176,9 @@ describe('Fetch Config', () => {
const req = new PipelineRequest('https://localhost:3000');
const res = new PipelineResponse();
await fetchConfig(state, req, res);
+ setLastModified(state, res);
assert.deepStrictEqual(Object.fromEntries(res.headers.entries()), {
- 'last-modified': 'Wed, 16 Jan 2022 09:33:01 GMT',
+ 'last-modified': 'Sun, 16 Jan 2022 09:33:01 GMT',
});
});
@@ -206,8 +212,9 @@ describe('Fetch Config', () => {
const req = new PipelineRequest('https://localhost:3000');
const res = new PipelineResponse();
await fetchConfig(state, req, res);
+ setLastModified(state, res);
assert.deepStrictEqual(Object.fromEntries(res.headers.entries()), {
- 'last-modified': 'Wed, 14 Jan 2022 09:33:01 GMT',
+ 'last-modified': 'Fri, 14 Jan 2022 09:33:01 GMT',
});
});
diff --git a/test/utils/last-modified.test.js b/test/utils/last-modified.test.js
index 9f4fb575..ad8e92c5 100644
--- a/test/utils/last-modified.test.js
+++ b/test/utils/last-modified.test.js
@@ -13,7 +13,12 @@
/* eslint-env mocha */
import assert from 'assert';
-import { extractLastModified, updateLastModified } from '../../src/utils/last-modified.js';
+import {
+ extractLastModified,
+ setLastModified,
+ recordLastModified,
+} from '../../src/utils/last-modified.js';
+import { PipelineResponse } from '../../src/index.js';
describe('Last Modified Utils Test', () => {
/** @type PipelineState */
@@ -21,42 +26,47 @@ describe('Last Modified Utils Test', () => {
it('sets the last modified if missing', async () => {
/** @type PipelineResponse */
- const res = { headers: new Map() };
- updateLastModified(state, res, 'Wed, 12 Jan 2022 09:33:01 GMT');
+ const res = new PipelineResponse();
+ recordLastModified(state, res, 'source', 'Wed, 12 Jan 2022 09:33:01 GMT');
+ setLastModified(state, res);
assert.strictEqual(res.headers.get('last-modified'), 'Wed, 12 Jan 2022 09:33:01 GMT');
});
it('sets the last modified if newer', async () => {
/** @type PipelineResponse */
- const res = { headers: new Map() };
- updateLastModified(state, res, 'Wed, 12 Jan 2022 09:33:01 GMT');
- updateLastModified(state, res, 'Wed, 12 Jan 2022 14:33:01 GMT');
- updateLastModified(state, res, 'Wed, 12 Jan 2022 19:33:01 GMT');
+ const res = new PipelineResponse();
+ recordLastModified(state, res, 'source 0', 'Wed, 12 Jan 2022 09:33:01 GMT');
+ recordLastModified(state, res, 'source 1', 'Wed, 12 Jan 2022 14:33:01 GMT');
+ recordLastModified(state, res, 'source 2', 'Wed, 12 Jan 2022 19:33:01 GMT');
+ setLastModified(state, res);
assert.strictEqual(res.headers.get('last-modified'), 'Wed, 12 Jan 2022 19:33:01 GMT');
});
it('ignores the last modified if older', async () => {
/** @type PipelineResponse */
- const res = { headers: new Map() };
- updateLastModified(state, res, 'Wed, 12 Jan 2022 09:33:01 GMT');
- updateLastModified(state, res, 'Wed, 12 Jan 2022 08:33:01 GMT');
- updateLastModified(state, res, 'Wed, 12 Jan 2022 07:33:01 GMT');
+ const res = new PipelineResponse();
+ recordLastModified(state, res, 'source 0', 'Wed, 12 Jan 2022 09:33:01 GMT');
+ recordLastModified(state, res, 'source 1', 'Wed, 12 Jan 2022 08:33:01 GMT');
+ recordLastModified(state, res, 'source 2', 'Wed, 12 Jan 2022 07:33:01 GMT');
+ setLastModified(state, res);
assert.strictEqual(res.headers.get('last-modified'), 'Wed, 12 Jan 2022 09:33:01 GMT');
});
it('ignores invalid last modified', async () => {
/** @type PipelineResponse */
- const res = { headers: new Map() };
- updateLastModified(state, res, 'Wed, 12 Jan 2022 09:33:01 GMT');
- updateLastModified(state, res, 'Hello, world.');
+ const res = new PipelineResponse();
+ recordLastModified(state, res, 'source 0', 'Wed, 12 Jan 2022 09:33:01 GMT');
+ recordLastModified(state, res, 'source 1', 'Hello, world.');
+ setLastModified(state, res);
assert.strictEqual(res.headers.get('last-modified'), 'Wed, 12 Jan 2022 09:33:01 GMT');
});
it('ignores undefined last modified', async () => {
/** @type PipelineResponse */
- const res = { headers: new Map() };
- updateLastModified(state, res, 'Wed, 12 Jan 2022 09:33:01 GMT');
- updateLastModified(state, res, undefined);
+ const res = new PipelineResponse();
+ recordLastModified(state, res, 'source 0', 'Wed, 12 Jan 2022 09:33:01 GMT');
+ recordLastModified(state, res, 'source 1', undefined);
+ setLastModified(state, res);
assert.strictEqual(res.headers.get('last-modified'), 'Wed, 12 Jan 2022 09:33:01 GMT');
});