Skip to content
Draft
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
42 changes: 16 additions & 26 deletions src/llmo-onboarding-publish/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,32 @@ function getLlmoDataFolder(site) {
/**
* LLMO Onboarding Publish Handler
*
* Publishes the query-index file to admin.hlx.page for sites with LLMO configuration.
*
* This handler intentionally skips publish (and does not queue downstream analysis) when:
* - Site is not found: Nothing to publish, and no site data exists for analysis
* - Missing LLMO config: Site hasn't been onboarded to LLMO, so nothing to publish or analyze
*
* This is correct behavior because a "publish" step has no meaningful work to do for
* non-existent or non-configured sites, and queueing downstream analysis would be wasteful.
* Publishes the query-index file to admin.hlx.page for onboarding messages.
* Data folder is supplied by onboarding in auditContext, with fallback to site config.
*/
export default async function handler(message, context) {
const { log, dataAccess } = context;
const { siteId } = message;
const { log } = context;
const { siteId, auditContext = {} } = message;
let { dataFolder } = auditContext;

if (!siteId) {
log.error('[LLMO Onboarding Publish] Missing required field: siteId');
return;
}

// Prefer context.site (already fetched by src/index.js middleware for all handlers)
// Fall back to DB lookup only if not present (e.g., edge cases or direct invocation)
const site = context.site ?? await dataAccess.Site.findById(siteId);

if (!site) {
// Intentional: skip publish AND don't queue downstream - nothing to do for non-existent site
log.warn(`[LLMO Onboarding Publish] Site not found. Skipping publish for site ${siteId}`);
return;
}
if (!dataFolder) {
const site = context.site ?? await context.dataAccess?.Site?.findById?.(siteId);
dataFolder = getLlmoDataFolder(site);

const currentDataFolder = getLlmoDataFolder(site);
if (!currentDataFolder) {
// Intentional: skip publish AND don't queue downstream - site not onboarded to LLMO
log.warn(`[LLMO Onboarding Publish] Site ${siteId} has no LLMO data folder. Skipping.`);
return;
if (!dataFolder) {
log.warn('[LLMO Onboarding Publish] Missing dataFolder in message and site config. Skipping publish.', {
siteId,
});
return;
}
}

// publishToAdminHlx already swallows/logs Helix errors by design.
await publishToAdminHlx(LLMO_ONBOARDING_PUBLISH_FILENAME, currentDataFolder, log);
log.info(`[LLMO Onboarding Publish] Publish attempt finished for site ${siteId}`);
await publishToAdminHlx(LLMO_ONBOARDING_PUBLISH_FILENAME, dataFolder, log);
log.info(`[LLMO Onboarding Publish] Publish attempt finished for site ${siteId} and folder ${dataFolder}`);
}
110 changes: 39 additions & 71 deletions test/audits/llmo-onboarding-publish.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ describe('LLMO Onboarding Publish Handler', () => {
sandbox = sinon.createSandbox();

publishToAdminHlxStub = sandbox.stub().resolves();

site = {
getConfig: sandbox.stub().returns({
getLlmoDataFolder: sandbox.stub().returns('dev/example-com'),
getLlmoDataFolder: sandbox.stub().returns('dev/site-config-folder'),
}),
};

Expand All @@ -49,9 +48,6 @@ describe('LLMO Onboarding Publish Handler', () => {
findById: sandbox.stub().resolves(site),
},
},
sqs: {
sendMessage: sandbox.stub().resolves(),
},
};

const module = await esmock('../../src/llmo-onboarding-publish/handler.js', {
Expand All @@ -73,7 +69,6 @@ describe('LLMO Onboarding Publish Handler', () => {
auditContext: {
dataFolder: 'dev/example-com',
onboardingRunId: 'run-123',
triggerSource: 'llmo-onboard',
},
};

Expand All @@ -84,11 +79,11 @@ describe('LLMO Onboarding Publish Handler', () => {
'dev/example-com',
context.log,
);
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(context.dataAccess.Site.findById).to.not.have.been.called;
expect(response).to.be.undefined;
});

it('uses persisted llmo data folder even when message dataFolder differs', async () => {
it('publishes with message dataFolder value', async () => {
const message = {
siteId: 'site-123',
auditContext: {
Expand All @@ -100,23 +95,10 @@ describe('LLMO Onboarding Publish Handler', () => {

expect(publishToAdminHlxStub).to.have.been.calledOnceWithExactly(
'query-index',
'dev/example-com',
'dev/other-folder',
context.log,
);
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(response).to.be.undefined;
});

it('works without auditContext dataFolder in the message', async () => {
const message = { siteId: 'site-123', auditContext: {} };
const response = await handler(message, context);

expect(publishToAdminHlxStub).to.have.been.calledOnceWithExactly(
'query-index',
'dev/example-com',
context.log,
);
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(context.dataAccess.Site.findById).to.not.have.been.called;
expect(response).to.be.undefined;
});

Expand All @@ -136,74 +118,60 @@ describe('LLMO Onboarding Publish Handler', () => {
const response = await handler(message, context);

expect(context.log.error).to.have.been.called;
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(response).to.be.undefined;
});

it('skips publish when site is not found', async () => {
context.dataAccess.Site.findById.resolves(null);

const message = {
siteId: 'site-123',
auditContext: {
dataFolder: 'dev/example-com',
},
};

const response = await handler(message, context);

expect(publishToAdminHlxStub).to.not.have.been.called;
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(response).to.be.undefined;
});

it('skips publish when site has no llmo data folder', async () => {
site.getConfig.returns({
getLlmoDataFolder: sandbox.stub().returns(null),
});

const message = {
siteId: 'site-123',
auditContext: {
dataFolder: 'dev/example-com',
},
};

const response = await handler(message, context);

expect(publishToAdminHlxStub).to.not.have.been.called;
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(response).to.be.undefined;
});

it('returns early when siteId is missing', async () => {
const response = await handler({ auditContext: {} }, context);

expect(publishToAdminHlxStub).to.not.have.been.called;
expect(context.sqs.sendMessage).to.not.have.been.called;
expect(context.dataAccess.Site.findById).to.not.have.been.called;
expect(context.log.error).to.have.been.calledWith('[LLMO Onboarding Publish] Missing required field: siteId');
expect(response).to.be.undefined;
});

it('uses context.site when available and skips DB lookup', async () => {
it('falls back to context.site dataFolder when message dataFolder is missing', async () => {
context.site = site;

const message = { siteId: 'site-123' };
const response = await handler(message, context);
const response = await handler({ siteId: 'site-123', auditContext: {} }, context);

expect(context.dataAccess.Site.findById).to.not.have.been.called;
expect(publishToAdminHlxStub).to.have.been.calledOnce;
expect(publishToAdminHlxStub).to.have.been.calledOnceWithExactly(
'query-index',
'dev/site-config-folder',
context.log,
);
expect(response).to.be.undefined;
});

it('falls back to Site.findById when context.site is not set', async () => {
delete context.site;
it('falls back to Site.findById dataFolder when context.site is missing', async () => {
const response = await handler({ siteId: 'site-123', auditContext: {} }, context);

const message = { siteId: 'site-123' };
const response = await handler(message, context);
expect(context.dataAccess.Site.findById).to.have.been.calledOnceWithExactly('site-123');
expect(publishToAdminHlxStub).to.have.been.calledOnceWithExactly(
'query-index',
'dev/site-config-folder',
context.log,
);
expect(response).to.be.undefined;
});

expect(context.dataAccess.Site.findById).to.have.been.calledOnceWith('site-123');
expect(publishToAdminHlxStub).to.have.been.calledOnce;
it('returns early when dataFolder is missing in both message and site config', async () => {
context.site = {
getConfig: sandbox.stub().returns({
getLlmoDataFolder: sandbox.stub().returns(null),
}),
};

const response = await handler({ siteId: 'site-123', auditContext: {} }, context);

expect(publishToAdminHlxStub).to.not.have.been.called;
expect(context.log.warn).to.have.been.calledWith(
'[LLMO Onboarding Publish] Missing dataFolder in message and site config. Skipping publish.',
sinon.match({
siteId: 'site-123',
}),
);
expect(response).to.be.undefined;
});
});