Skip to content
Open
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
4 changes: 2 additions & 2 deletions docs/index.html

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions src/controllers/llmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,21 @@ function LlmoController(ctx) {
// Handles requests to the LLMO sheet data endpoint
const getLlmoSheetData = async (context) => {
const { log } = context;
const { siteId, dataSource, sheetType } = context.params;
const {
siteId, dataSource, sheetType, subType,
} = context.params;
const { env } = context;
try {
log.info(`validating LLMO sheet data for siteId: ${siteId}, dataSource: ${dataSource}, sheetType: ${sheetType}`);
const { llmoConfig } = await getSiteAndValidateLlmo(context);
const sheetURL = sheetType ? `${llmoConfig.dataFolder}/${sheetType}/${dataSource}.json` : `${llmoConfig.dataFolder}/${dataSource}.json`;
let sheetURL = `${llmoConfig.dataFolder}/`;
if (sheetType) {
sheetURL += `${sheetType}/`;
}
if (subType) {
sheetURL += `${subType}/`;
}
sheetURL += `${dataSource}.json`;

// Add limit, offset and sheet query params to the url
const url = new URL(`${LLMO_SHEETDATA_SOURCE_URL}/${sheetURL}`);
Expand Down
1 change: 1 addition & 0 deletions src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export default function getRouteHandlers(
// LLMO Specific Routes
'GET /sites/:siteId/llmo/sheet-data/:dataSource': llmoController.getLlmoSheetData,
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource': llmoController.getLlmoSheetData,
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:subType/:dataSource': llmoController.getLlmoSheetData,
'GET /sites/:siteId/llmo/config': llmoController.getLlmoConfig,
'GET /sites/:siteId/llmo/questions': llmoController.getLlmoQuestions,
'POST /sites/:siteId/llmo/questions': llmoController.addLlmoQuestion,
Expand Down
143 changes: 143 additions & 0 deletions test/controllers/llmo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,117 @@ describe('LlmoController', () => {
);
});

it('should handle empty string subType parameter', async () => {
const mockResponse = {
ok: true,
json: sinon.stub().resolves({ data: 'test-data' }),
};
tracingFetchStub.resolves(mockResponse);

// Add empty string subType
mockContext.params.subType = '';

const result = await controller.getLlmoSheetData(mockContext);

expect(result.status).to.equal(200);
const responseBody = await result.json();
expect(responseBody).to.deep.equal({ data: 'test-data' });
// Empty string should be treated as falsy and not added to URL
expect(tracingFetchStub).to.have.been.calledWith(
'https://main--project-elmo-ui-data--adobe.aem.live/test-folder/test-data.json',
{
headers: {
Authorization: 'token test-api-key',
'User-Agent': 'test-user-agent',
'Accept-Encoding': 'gzip',
},
},
);
});

it('should handle null subType parameter', async () => {
const mockResponse = {
ok: true,
json: sinon.stub().resolves({ data: 'test-data' }),
};
tracingFetchStub.resolves(mockResponse);

// Add null subType
mockContext.params.subType = null;

const result = await controller.getLlmoSheetData(mockContext);

expect(result.status).to.equal(200);
const responseBody = await result.json();
expect(responseBody).to.deep.equal({ data: 'test-data' });
// Null value should be treated as falsy and not added to URL
expect(tracingFetchStub).to.have.been.calledWith(
'https://main--project-elmo-ui-data--adobe.aem.live/test-folder/test-data.json',
{
headers: {
Authorization: 'token test-api-key',
'User-Agent': 'test-user-agent',
'Accept-Encoding': 'gzip',
},
},
);
});

it('should handle undefined subType parameter', async () => {
const mockResponse = {
ok: true,
json: sinon.stub().resolves({ data: 'test-data' }),
};
tracingFetchStub.resolves(mockResponse);

// Ensure subType is undefined
delete mockContext.params.subType;

const result = await controller.getLlmoSheetData(mockContext);

expect(result.status).to.equal(200);
const responseBody = await result.json();
expect(responseBody).to.deep.equal({ data: 'test-data' });
// Undefined value should be treated as falsy and not added to URL
expect(tracingFetchStub).to.have.been.calledWith(
'https://main--project-elmo-ui-data--adobe.aem.live/test-folder/test-data.json',
{
headers: {
Authorization: 'token test-api-key',
'User-Agent': 'test-user-agent',
'Accept-Encoding': 'gzip',
},
},
);
});

it('should proxy data with subType parameter successfully', async () => {
const mockResponse = {
ok: true,
json: sinon.stub().resolves({ data: 'subtype-data' }),
};
tracingFetchStub.resolves(mockResponse);

// Add subType to the context params
mockContext.params.subType = 'detailed';

const result = await controller.getLlmoSheetData(mockContext);

expect(result.status).to.equal(200);
const responseBody = await result.json();
expect(responseBody).to.deep.equal({ data: 'subtype-data' });
expect(tracingFetchStub).to.have.been.calledWith(
'https://main--project-elmo-ui-data--adobe.aem.live/test-folder/detailed/test-data.json',
{
headers: {
Authorization: 'token test-api-key',
'User-Agent': 'test-user-agent',
'Accept-Encoding': 'gzip',
},
},
);
});

it('should proxy data with sheetType parameter successfully', async () => {
const mockResponse = {
ok: true,
Expand Down Expand Up @@ -588,6 +699,38 @@ describe('LlmoController', () => {
expect(responseBody.message).to.include('Network error');
});

it('should handle external API errors with subType parameter', async () => {
const mockResponse = {
ok: false,
status: 404,
statusText: 'Not Found',
};
tracingFetchStub.resolves(mockResponse);

// Add subType to the context params
mockContext.params.subType = 'detailed';

const result = await controller.getLlmoSheetData(mockContext);

expect(result.status).to.equal(400);
const responseBody = await result.json();
expect(responseBody.message).to.include('External API returned 404: Not Found');
});

it('should handle network errors with subType parameter', async () => {
const networkError = new Error('Network error');
tracingFetchStub.rejects(networkError);

// Add subType to the context params
mockContext.params.subType = 'detailed';

const result = await controller.getLlmoSheetData(mockContext);

expect(result.status).to.equal(400);
const responseBody = await result.json();
expect(responseBody.message).to.include('Network error');
});

it('should use fallback API key when env.LLMO_HLX_API_KEY is undefined', async () => {
const mockResponse = {
ok: true,
Expand Down
3 changes: 3 additions & 0 deletions test/routes/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ describe('getRouteHandlers', () => {
'PATCH /sites/:siteId/config/cdn-logs',
'GET /sites/:siteId/llmo/sheet-data/:dataSource',
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource',
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:subType/:dataSource',
'GET /sites/:siteId/llmo/config',
'GET /sites/:siteId/llmo/questions',
'POST /sites/:siteId/llmo/questions',
Expand Down Expand Up @@ -528,6 +529,8 @@ describe('getRouteHandlers', () => {
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:dataSource'].paramNames).to.deep.equal(['siteId', 'dataSource']);
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource'].handler).to.equal(mockLlmoController.getLlmoSheetData);
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource'].paramNames).to.deep.equal(['siteId', 'sheetType', 'dataSource']);
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:subType/:dataSource'].handler).to.equal(mockLlmoController.getLlmoSheetData);
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:subType/:dataSource'].paramNames).to.deep.equal(['siteId', 'sheetType', 'subType', 'dataSource']);
expect(dynamicRoutes['GET /sites/:siteId/llmo/config'].handler).to.equal(mockLlmoController.getLlmoConfig);
expect(dynamicRoutes['GET /sites/:siteId/llmo/config'].paramNames).to.deep.equal(['siteId']);
expect(dynamicRoutes['GET /sites/:siteId/llmo/questions'].handler).to.equal(mockLlmoController.getLlmoQuestions);
Expand Down