refactor: extract helpers into kmeContentSourceAdapterHelpers.js
Move getValidToken, validateSettings, extractHydraItems, and buildSitemapXml out of the proxy IIFE into src/globalVariables/kmeContentSourceAdapterHelpers.js following the literal function body pattern (auto-loaded by server.js, injected as 'kmeContentSourceAdapterHelpers' into VM context). oidcAuthFlow() and sitemapFlow() remain in the proxy script as they own req/res. Update unit and contract tests to evaluate the helpers file with the same mock dependencies used in each VM context, ensuring error-throwing axios overrides are correctly seen by the helpers' closures. All 31 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
128
src/globalVariables/kmeContentSourceAdapterHelpers.js
Normal file
128
src/globalVariables/kmeContentSourceAdapterHelpers.js
Normal file
@@ -0,0 +1,128 @@
|
||||
// Helpers for kmeContentSourceAdapter.js
|
||||
// This file is the literal body of a function — no imports or exports.
|
||||
// server.js wraps and executes it as: (function() { <this file> })()
|
||||
// Context globals available: redis, axios, console, xmlBuilder, URLSearchParams, kme_CSA_settings
|
||||
|
||||
/**
|
||||
* Returns the first missing required field name, or null if all present.
|
||||
* @param {object} settings
|
||||
* @param {string[]} requiredFields
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function validateSettings(settings, requiredFields) {
|
||||
for (const field of requiredFields) {
|
||||
if (!settings[field]) return field;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts vkm:SearchResultItemFragment objects from the two-level hydra:member
|
||||
* structure returned by the KME Knowledge Search Service:
|
||||
* data["hydra:member"][n] → SearchResultItem
|
||||
* data["hydra:member"][n]["hydra:member"] → SearchResultItemFragment[] (has vkm:url)
|
||||
* @param {object} data – response.data from the search API
|
||||
* @returns {object[]}
|
||||
*/
|
||||
function extractHydraItems(data) {
|
||||
const topMembers = data['hydra:member'] ?? [];
|
||||
return topMembers.flatMap(resultItem => resultItem['hydra:member'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Sitemaps-protocol 0.9 XML document from the given items.
|
||||
* Uses xmlBuilder from the enclosing VM context.
|
||||
* @param {object[]} items – SearchResultItemFragment objects with vkm:url
|
||||
* @param {string} proxyBaseUrl – base URL for <loc> values
|
||||
* @returns {string} serialised XML
|
||||
*/
|
||||
function buildSitemapXml(items, proxyBaseUrl) {
|
||||
const doc = xmlBuilder({ version: '1.0', encoding: 'UTF-8' });
|
||||
const urlset = doc.ele('urlset', { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' });
|
||||
for (const item of items) {
|
||||
const vkmUrl = item['vkm:url'];
|
||||
if (!vkmUrl) continue; // silently omit items with empty/missing vkm:url
|
||||
const loc = `${proxyBaseUrl}?kmeURL=${encodeURIComponent(vkmUrl)}`;
|
||||
urlset.ele('url').ele('loc').txt(loc).up().up();
|
||||
}
|
||||
return doc.end({ prettyPrint: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a valid OIDC id_token using the shared Redis cache and stampede guard.
|
||||
* Closes over redis, kme_CSA_settings, axios, console, URLSearchParams from VM context.
|
||||
* Throws on any failure — callers are responsible for error handling.
|
||||
* @param {string} [reqUrl] – used only for debug logging
|
||||
* @param {string} [reqMethod] – used only for debug logging
|
||||
* @returns {Promise<string>} id_token
|
||||
*/
|
||||
async function getValidToken(reqUrl, reqMethod) {
|
||||
const { tokenUrl, username, clientId, scope } = kme_CSA_settings;
|
||||
|
||||
console.debug({ message: 'Checking token cache', url: reqUrl, method: reqMethod });
|
||||
const cachedToken = await redis.hGet('authorization', 'token');
|
||||
const expiry = parseFloat(await redis.hGet('authorization', 'expiry') ?? '0');
|
||||
const isValid = cachedToken !== null && Date.now() / 1000 < expiry;
|
||||
|
||||
if (isValid) {
|
||||
console.debug({ message: 'Token cache hit', expiresIn: Math.round(expiry - Date.now() / 1000) + 's' });
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
// Stampede guard — if a fetch is already in flight, queue on it
|
||||
if (kme_CSA_settings._pendingFetch && typeof kme_CSA_settings._pendingFetch.then === 'function') {
|
||||
console.debug({ message: 'Token fetch in flight, queuing request' });
|
||||
await kme_CSA_settings._pendingFetch;
|
||||
console.debug({ message: 'Queued request unblocked, responding' });
|
||||
return await redis.hGet('authorization', 'token');
|
||||
}
|
||||
|
||||
console.info({ message: 'Token cache miss, fetching fresh token', tokenUrl });
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password: kme_CSA_settings.password,
|
||||
client_id: clientId,
|
||||
scope,
|
||||
});
|
||||
|
||||
let resolvePending;
|
||||
let rejectPending;
|
||||
kme_CSA_settings._pendingFetch = new Promise((resolve, reject) => {
|
||||
resolvePending = resolve;
|
||||
rejectPending = reject;
|
||||
});
|
||||
kme_CSA_settings._pendingFetch.catch(() => {});
|
||||
|
||||
try {
|
||||
console.debug({ message: 'Requesting new token', url: tokenUrl, method: 'POST' });
|
||||
const response = await axios.post(tokenUrl, params, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
const { id_token, expires_in } = response.data;
|
||||
if (!id_token) throw new Error('id_token missing from response');
|
||||
if (!expires_in) throw new Error('expires_in missing from response');
|
||||
|
||||
await redis.hSet('authorization', 'token', id_token);
|
||||
await redis.hSet('authorization', 'expiry', String(expires_in));
|
||||
console.info({ message: 'Token fetched and cached', expiresAt: new Date(expires_in * 1000).toISOString() });
|
||||
|
||||
resolvePending();
|
||||
return id_token;
|
||||
} catch (fetchErr) {
|
||||
console.error({ message: 'Token fetch failed', error: fetchErr.message, code: fetchErr.code });
|
||||
rejectPending(fetchErr);
|
||||
throw fetchErr;
|
||||
} finally {
|
||||
kme_CSA_settings._pendingFetch = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
validateSettings,
|
||||
extractHydraItems,
|
||||
buildSitemapXml,
|
||||
getValidToken,
|
||||
};
|
||||
@@ -1,95 +1,16 @@
|
||||
(async () => {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared helper: obtain a valid OIDC id_token (cache-hit → stampede-guard →
|
||||
// fetch → hSet). Throws on any failure so callers can handle the error.
|
||||
// ---------------------------------------------------------------------------
|
||||
async function getValidToken() {
|
||||
const { tokenUrl, username, clientId, scope } = kme_CSA_settings;
|
||||
|
||||
// Read token cache from Redis
|
||||
console.debug({ message: 'Checking token cache', url: req.url, method: req.method });
|
||||
const cachedToken = await redis.hGet('authorization', 'token');
|
||||
const expiry = parseFloat(await redis.hGet('authorization', 'expiry') ?? '0');
|
||||
const isValid = cachedToken !== null && Date.now() / 1000 < expiry;
|
||||
|
||||
// Cache HIT → return immediately
|
||||
if (isValid) {
|
||||
console.debug({ message: 'Token cache hit', expiresIn: Math.round(expiry - Date.now() / 1000) + 's' });
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
// Stampede guard — if a fetch is already in flight, queue on it
|
||||
if (kme_CSA_settings._pendingFetch && typeof kme_CSA_settings._pendingFetch.then === 'function') {
|
||||
console.debug({ message: 'Token fetch in flight, queuing request' });
|
||||
await kme_CSA_settings._pendingFetch;
|
||||
console.debug({ message: 'Queued request unblocked, responding' });
|
||||
// Re-read token from Redis after the in-flight fetch completes
|
||||
return await redis.hGet('authorization', 'token');
|
||||
}
|
||||
|
||||
// Cache MISS → fetch fresh token
|
||||
console.info({ message: 'Token cache miss, fetching fresh token', tokenUrl });
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password: kme_CSA_settings.password,
|
||||
client_id: clientId,
|
||||
scope,
|
||||
});
|
||||
|
||||
// Set up stampede guard before fetching
|
||||
let resolvePending;
|
||||
let rejectPending;
|
||||
kme_CSA_settings._pendingFetch = new Promise((resolve, reject) => {
|
||||
resolvePending = resolve;
|
||||
rejectPending = reject;
|
||||
});
|
||||
// Prevent an unhandled-rejection when no concurrent request is waiting on this promise
|
||||
kme_CSA_settings._pendingFetch.catch(() => {});
|
||||
|
||||
try {
|
||||
console.debug({ message: 'Requesting new token', url: tokenUrl, method: 'POST' });
|
||||
const response = await axios.post(tokenUrl, params, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
const { id_token, expires_in } = response.data;
|
||||
if (!id_token) throw new Error('id_token missing from response');
|
||||
if (!expires_in) throw new Error('expires_in missing from response');
|
||||
|
||||
// Write to Redis cache
|
||||
await redis.hSet('authorization', 'token', id_token);
|
||||
await redis.hSet('authorization', 'expiry', String(expires_in));
|
||||
console.info({ message: 'Token fetched and cached', expiresAt: new Date(expires_in * 1000).toISOString() });
|
||||
|
||||
// Resolve the pending fetch promise so waiting requests can proceed
|
||||
resolvePending();
|
||||
return id_token;
|
||||
} catch (fetchErr) {
|
||||
console.error({ message: 'Token fetch failed', error: fetchErr.message, code: fetchErr.code });
|
||||
rejectPending(fetchErr);
|
||||
throw fetchErr;
|
||||
} finally {
|
||||
kme_CSA_settings._pendingFetch = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OIDC auth flow — existing non-sitemap behaviour, unchanged
|
||||
// ---------------------------------------------------------------------------
|
||||
async function oidcAuthFlow() {
|
||||
// Validate required kme_CSA_settings fields
|
||||
const requiredFields = ['tokenUrl', 'username', 'password', 'clientId', 'scope'];
|
||||
for (const field of requiredFields) {
|
||||
if (!kme_CSA_settings[field]) {
|
||||
throw new Error('missing required field: ' + field);
|
||||
}
|
||||
}
|
||||
const missingField = kmeContentSourceAdapterHelpers.validateSettings(
|
||||
kme_CSA_settings,
|
||||
['tokenUrl', 'username', 'password', 'clientId', 'scope'],
|
||||
);
|
||||
if (missingField) throw new Error('missing required field: ' + missingField);
|
||||
|
||||
await getValidToken();
|
||||
await kmeContentSourceAdapterHelpers.getValidToken(req.url, req.method);
|
||||
|
||||
// Respond success
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('Authorized');
|
||||
}
|
||||
@@ -98,33 +19,29 @@
|
||||
// Sitemap flow — GET /sitemap.xml
|
||||
// ---------------------------------------------------------------------------
|
||||
async function sitemapFlow() {
|
||||
// Settings validation guard (FR-011, R-005)
|
||||
const requiredSitemapFields = ['searchApiBaseUrl', 'tenant', 'proxyBaseUrl'];
|
||||
for (const field of requiredSitemapFields) {
|
||||
if (!kme_CSA_settings[field]) {
|
||||
console.error({ message: 'Sitemap config error', missingField: field });
|
||||
const missingSitemapField = kmeContentSourceAdapterHelpers.validateSettings(
|
||||
kme_CSA_settings,
|
||||
['searchApiBaseUrl', 'tenant', 'proxyBaseUrl'],
|
||||
);
|
||||
if (missingSitemapField) {
|
||||
console.error({ message: 'Sitemap config error', missingField: missingSitemapField });
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
res.end('Configuration error: missing required field: ' + field);
|
||||
res.end('Configuration error: missing required field: ' + missingSitemapField);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { searchApiBaseUrl, tenant, proxyBaseUrl } = kme_CSA_settings;
|
||||
|
||||
// Also validate OIDC fields before attempting token fetch
|
||||
const requiredOidcFields = ['tokenUrl', 'username', 'password', 'clientId', 'scope'];
|
||||
for (const field of requiredOidcFields) {
|
||||
if (!kme_CSA_settings[field]) {
|
||||
throw new Error('missing required field: ' + field);
|
||||
}
|
||||
}
|
||||
const missingOidcField = kmeContentSourceAdapterHelpers.validateSettings(
|
||||
kme_CSA_settings,
|
||||
['tokenUrl', 'username', 'password', 'clientId', 'scope'],
|
||||
);
|
||||
if (missingOidcField) throw new Error('missing required field: ' + missingOidcField);
|
||||
|
||||
try {
|
||||
// Obtain valid token (shared cache + stampede guard)
|
||||
console.debug({ message: 'Sitemap flow: obtaining token', url: req.url });
|
||||
const token = await getValidToken();
|
||||
const token = await kmeContentSourceAdapterHelpers.getValidToken(req.url, req.method);
|
||||
|
||||
// Call Knowledge Search Service
|
||||
const searchUrl = `${searchApiBaseUrl}/${tenant}/search?query=*&size=100&category=vkm:ArticleCategory`;
|
||||
console.info({ message: 'Sitemap flow: calling search API', url: searchUrl });
|
||||
const searchResponse = await axios.get(searchUrl, {
|
||||
@@ -132,24 +49,10 @@
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Extract vkm:SearchResultItemFragment objects from two-level hydra:member structure:
|
||||
// response.data["hydra:member"] → SearchResultItem[]
|
||||
// each SearchResultItem["hydra:member"] → SearchResultItemFragment[] (contains vkm:url)
|
||||
const topMembers = searchResponse.data['hydra:member'] ?? [];
|
||||
const items = topMembers.flatMap(resultItem => resultItem['hydra:member'] ?? []);
|
||||
const items = kmeContentSourceAdapterHelpers.extractHydraItems(searchResponse.data);
|
||||
console.debug({ message: 'Sitemap flow: items received', count: items.length });
|
||||
|
||||
// Build sitemap XML (R-003, FR-008)
|
||||
const doc = xmlBuilder({ version: '1.0', encoding: 'UTF-8' });
|
||||
const urlset = doc.ele('urlset', { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' });
|
||||
for (const item of items) {
|
||||
const vkmUrl = item['vkm:url'];
|
||||
if (!vkmUrl) continue; // silently omit items with empty/missing vkm:url (FR-006)
|
||||
const loc = `${proxyBaseUrl}?kmeURL=${encodeURIComponent(vkmUrl)}`;
|
||||
urlset.ele('url').ele('loc').txt(loc).up().up();
|
||||
}
|
||||
const xml = doc.end({ prettyPrint: false });
|
||||
|
||||
const xml = kmeContentSourceAdapterHelpers.buildSitemapXml(items, proxyBaseUrl);
|
||||
console.info({ message: 'Sitemap flow: sending response', items: items.length });
|
||||
res.writeHead(200, { 'Content-Type': 'application/xml' });
|
||||
res.end(xml);
|
||||
|
||||
@@ -15,6 +15,16 @@ const proxyPath = join(__dirname, '../../src/proxyScripts/kmeContentSourceAdapte
|
||||
const proxyCode = readFileSync(proxyPath, 'utf-8');
|
||||
const proxyScript = new vm.Script(proxyCode, { filename: 'kmeContentSourceAdapter.js' });
|
||||
|
||||
const helpersPath = join(__dirname, '../../src/globalVariables/kmeContentSourceAdapterHelpers.js');
|
||||
const helpersCode = readFileSync(helpersPath, 'utf-8');
|
||||
const helpersWrapped = `(function() {\n${helpersCode}\n})()`;
|
||||
const helpersScript = new vm.Script(helpersWrapped, { filename: 'kmeContentSourceAdapterHelpers.js' });
|
||||
|
||||
/** Evaluate the helpers file with the provided deps (mirrors server.js loadGlobalVariables). */
|
||||
function makeHelpers(deps) {
|
||||
return helpersScript.runInContext(vm.createContext(deps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a minimal HTTP server that handles all requests with a fixed JSON body.
|
||||
* @param {number} statusCode
|
||||
@@ -78,19 +88,18 @@ describe('proxy HTTP contract: 200 OK', () => {
|
||||
|
||||
try {
|
||||
const res = makeRes();
|
||||
const ctx = vm.createContext({
|
||||
URLSearchParams,
|
||||
console,
|
||||
axios,
|
||||
xmlBuilder,
|
||||
redis: makeRedisFake(),
|
||||
kme_CSA_settings: {
|
||||
const redis = makeRedisFake();
|
||||
const kme_CSA_settings = {
|
||||
tokenUrl: mock.url,
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
clientId: 'client',
|
||||
scope: 'openid',
|
||||
},
|
||||
};
|
||||
const deps = { URLSearchParams, console, axios, xmlBuilder, redis, kme_CSA_settings };
|
||||
const ctx = vm.createContext({
|
||||
...deps,
|
||||
kmeContentSourceAdapterHelpers: makeHelpers(deps),
|
||||
req: { url: '/', method: 'GET', headers: {} },
|
||||
res,
|
||||
});
|
||||
@@ -116,19 +125,18 @@ describe('proxy HTTP contract: 401 Unauthorized', () => {
|
||||
|
||||
try {
|
||||
const res = makeRes();
|
||||
const ctx = vm.createContext({
|
||||
URLSearchParams,
|
||||
console,
|
||||
axios,
|
||||
xmlBuilder,
|
||||
redis: makeRedisFake(),
|
||||
kme_CSA_settings: {
|
||||
const redis = makeRedisFake();
|
||||
const kme_CSA_settings = {
|
||||
tokenUrl: mock.url,
|
||||
username: 'bad-user',
|
||||
password: 'bad-pass',
|
||||
clientId: 'client',
|
||||
scope: 'openid',
|
||||
},
|
||||
};
|
||||
const deps = { URLSearchParams, console, axios, xmlBuilder, redis, kme_CSA_settings };
|
||||
const ctx = vm.createContext({
|
||||
...deps,
|
||||
kmeContentSourceAdapterHelpers: makeHelpers(deps),
|
||||
req: { url: '/', method: 'GET', headers: {} },
|
||||
res,
|
||||
});
|
||||
@@ -160,13 +168,7 @@ describe('sitemap endpoint', () => {
|
||||
redis.hSet('authorization', 'expiry', '9999999999');
|
||||
|
||||
const res = makeRes();
|
||||
const ctx = vm.createContext({
|
||||
URLSearchParams,
|
||||
console,
|
||||
axios,
|
||||
xmlBuilder,
|
||||
redis,
|
||||
kme_CSA_settings: {
|
||||
const kme_CSA_settings = {
|
||||
tokenUrl: tokenUrl ?? 'http://127.0.0.1:1', // not used (cache hit)
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
@@ -175,7 +177,11 @@ describe('sitemap endpoint', () => {
|
||||
searchApiBaseUrl: searchUrl,
|
||||
tenant: 'test',
|
||||
proxyBaseUrl: 'https://proxy.example.com',
|
||||
},
|
||||
};
|
||||
const deps = { URLSearchParams, console, axios, xmlBuilder, redis, kme_CSA_settings };
|
||||
const ctx = vm.createContext({
|
||||
...deps,
|
||||
kmeContentSourceAdapterHelpers: makeHelpers(deps),
|
||||
req: { url: '/sitemap.xml', method: 'GET', headers: {} },
|
||||
res,
|
||||
});
|
||||
@@ -273,19 +279,18 @@ describe('non-sitemap endpoint (regression)', () => {
|
||||
|
||||
try {
|
||||
const res = makeRes();
|
||||
const ctx = vm.createContext({
|
||||
URLSearchParams,
|
||||
console,
|
||||
axios,
|
||||
xmlBuilder,
|
||||
redis: makeRedisFake(),
|
||||
kme_CSA_settings: {
|
||||
const redis = makeRedisFake();
|
||||
const kme_CSA_settings = {
|
||||
tokenUrl: mock.url,
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
clientId: 'client',
|
||||
scope: 'openid',
|
||||
},
|
||||
};
|
||||
const deps = { URLSearchParams, console, axios, xmlBuilder, redis, kme_CSA_settings };
|
||||
const ctx = vm.createContext({
|
||||
...deps,
|
||||
kmeContentSourceAdapterHelpers: makeHelpers(deps),
|
||||
req: { url: '/', method: 'GET', headers: {} },
|
||||
res,
|
||||
});
|
||||
|
||||
@@ -13,6 +13,19 @@ const proxyPath = join(__dirname, '../../src/proxyScripts/kmeContentSourceAdapte
|
||||
const proxyCode = readFileSync(proxyPath, 'utf-8');
|
||||
const proxyScript = new vm.Script(proxyCode, { filename: 'kmeContentSourceAdapter.js' });
|
||||
|
||||
const helpersPath = join(__dirname, '../../src/globalVariables/kmeContentSourceAdapterHelpers.js');
|
||||
const helpersCode = readFileSync(helpersPath, 'utf-8');
|
||||
const helpersWrapped = `(function() {\n${helpersCode}\n})()`;
|
||||
const helpersScript = new vm.Script(helpersWrapped, { filename: 'kmeContentSourceAdapterHelpers.js' });
|
||||
|
||||
/**
|
||||
* Evaluate the helpers file in a context built from the provided deps, returning
|
||||
* the helpers object. Mirrors how server.js loads globalVariables/ JS files.
|
||||
*/
|
||||
function makeHelpers(deps) {
|
||||
return helpersScript.runInContext(vm.createContext(deps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a minimal VM context satisfying the vm-context contract.
|
||||
* @param {import('node:test').TestContext} t
|
||||
@@ -43,7 +56,7 @@ function makeContext(t, overrides = {}) {
|
||||
get headers() { return headers; },
|
||||
};
|
||||
|
||||
const kme_CSA_settings = {
|
||||
const defaultSettings = {
|
||||
tokenUrl: 'https://auth.example.com/token',
|
||||
username: 'testuser',
|
||||
password: 'testpass',
|
||||
@@ -51,22 +64,39 @@ function makeContext(t, overrides = {}) {
|
||||
scope: 'openid',
|
||||
};
|
||||
|
||||
const axiosMock = {
|
||||
const defaultAxiosMock = {
|
||||
post: t.mock.fn(async () => ({
|
||||
data: { id_token: 'mock-token', expires_in: 9_999_999_999 },
|
||||
})),
|
||||
get: t.mock.fn(async () => ({
|
||||
data: { items: [] },
|
||||
data: { 'hydra:member': [] },
|
||||
})),
|
||||
};
|
||||
|
||||
// Resolve the final axios and settings — overrides take precedence.
|
||||
// Helpers must close over the SAME axios/settings that the VM context will use,
|
||||
// otherwise tests that pass error-throwing axios overrides would get helpers
|
||||
// that still use the success-returning default.
|
||||
const resolvedAxios = overrides.axios ?? defaultAxiosMock;
|
||||
const resolvedSettings = overrides.kme_CSA_settings ?? defaultSettings;
|
||||
|
||||
const kmeContentSourceAdapterHelpers = makeHelpers({
|
||||
URLSearchParams,
|
||||
console,
|
||||
axios: resolvedAxios,
|
||||
redis,
|
||||
kme_CSA_settings: resolvedSettings,
|
||||
xmlBuilder,
|
||||
});
|
||||
|
||||
const ctx = vm.createContext({
|
||||
URLSearchParams,
|
||||
console,
|
||||
axios: axiosMock,
|
||||
axios: resolvedAxios,
|
||||
redis,
|
||||
kme_CSA_settings,
|
||||
kme_CSA_settings: defaultSettings,
|
||||
xmlBuilder,
|
||||
kmeContentSourceAdapterHelpers,
|
||||
req: { url: '/', method: 'GET', headers: {} },
|
||||
res,
|
||||
...overrides,
|
||||
@@ -76,7 +106,7 @@ function makeContext(t, overrides = {}) {
|
||||
ctx._redis = redis;
|
||||
ctx._res = res;
|
||||
ctx._store = _store;
|
||||
ctx._axios = axiosMock;
|
||||
ctx._axios = resolvedAxios;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
@@ -291,15 +321,23 @@ describe('stampede guard', () => {
|
||||
const res1 = makeRes(t);
|
||||
const res2 = makeRes(t);
|
||||
|
||||
// Helpers must share the same redis/kme_CSA_settings/axios so the stampede guard works
|
||||
const sharedHelpers = makeHelpers({
|
||||
URLSearchParams, console, axios: sharedAxios,
|
||||
redis, kme_CSA_settings, xmlBuilder,
|
||||
});
|
||||
|
||||
const ctx1 = vm.createContext({
|
||||
URLSearchParams, console, axios: sharedAxios,
|
||||
redis, kme_CSA_settings, xmlBuilder,
|
||||
kmeContentSourceAdapterHelpers: sharedHelpers,
|
||||
req: { url: '/', method: 'GET', headers: {} },
|
||||
res: res1,
|
||||
});
|
||||
const ctx2 = vm.createContext({
|
||||
URLSearchParams, console, axios: sharedAxios,
|
||||
redis, kme_CSA_settings, xmlBuilder,
|
||||
kmeContentSourceAdapterHelpers: sharedHelpers,
|
||||
req: { url: '/', method: 'GET', headers: {} },
|
||||
res: res2,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user