Skip to content

Commit a34293f

Browse files
author
Kanishka
committed
feat: add sandbox configuration update API
- Add PATCH /configurations/sandbox endpoint for updating sandbox audit configurations - Implement updateSandboxConfig method in ConfigurationController with admin access control.
1 parent 88cc99c commit a34293f

File tree

6 files changed

+414
-4159
lines changed

6 files changed

+414
-4159
lines changed

package-lock.json

Lines changed: 231 additions & 4158 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"@adobe/helix-universal-logger": "3.0.27",
7474
"@adobe/spacecat-shared-athena-client": "1.3.0",
7575
"@adobe/spacecat-shared-brand-client": "1.1.20",
76-
"@adobe/spacecat-shared-data-access": "2.58.0",
76+
"@adobe/spacecat-shared-data-access": "https://gitpkg.now.sh/adobe/spacecat-shared/packages/spacecat-shared-data-access?feature/site-sandbox-configuration",
7777
"@adobe/spacecat-shared-gpt-client": "1.5.21",
7878
"@adobe/spacecat-shared-http-utils": "1.16.0",
7979
"@adobe/spacecat-shared-ims-client": "1.8.8",

src/controllers/configuration.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,49 @@ function ConfigurationController(ctx) {
8888
return ok(ConfigurationDto.toJSON(configuration));
8989
};
9090

91+
/**
92+
* Updates sandbox configuration for audit types.
93+
* @param {UniversalContext} context - Context of the request.
94+
* @return {Promise<Response>} Update result response.
95+
*/
96+
const updateSandboxConfig = async (context) => {
97+
if (!accessControlUtil.hasAdminAccess()) {
98+
return forbidden('Only admins can update sandbox configurations');
99+
}
100+
101+
const { sandboxConfigs } = context.data || {};
102+
103+
if (!sandboxConfigs || typeof sandboxConfigs !== 'object') {
104+
return badRequest('sandboxConfigs object is required');
105+
}
106+
107+
try {
108+
// Load global configuration
109+
const config = await Configuration.findByType('global');
110+
if (!config) {
111+
return notFound('Global configuration not found');
112+
}
113+
114+
// Update sandbox configurations
115+
const updatedConfig = await config.updateSandboxAuditConfigs(sandboxConfigs);
116+
// Save the updated configuration
117+
await Configuration.save(updatedConfig);
118+
119+
return ok({
120+
message: 'Sandbox configurations updated successfully',
121+
updatedConfigs: sandboxConfigs,
122+
totalUpdated: Object.keys(sandboxConfigs).length,
123+
});
124+
} catch (error) {
125+
return badRequest(`Error updating sandbox configuration: ${error.message}`);
126+
}
127+
};
128+
91129
return {
92130
getAll,
93131
getByVersion,
94132
getLatest,
133+
updateSandboxConfig,
95134
};
96135
}
97136

src/routes/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ export default function getRouteHandlers(
248248

249249
// Sandbox audit route
250250
'POST /sites/:siteId/sandbox/audit': sandboxAuditController.triggerAudit,
251+
252+
// Sandbox configuration route
253+
'PATCH /configurations/sandbox': configurationController.updateSandboxConfig,
251254
};
252255

253256
// Initialization of static and dynamic routes

test/controllers/configurations.test.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ describe('Configurations Controller', () => {
8888
'getAll',
8989
'getLatest',
9090
'getByVersion',
91+
'updateSandboxConfig',
9192
];
9293

9394
let mockDataAccess;
@@ -235,4 +236,140 @@ describe('Configurations Controller', () => {
235236
expect(result.status).to.equal(400);
236237
expect(error).to.have.property('message', 'Configuration version required to be an integer');
237238
});
239+
240+
describe('Sandbox Configuration Methods', () => {
241+
let mockGlobalConfig;
242+
243+
beforeEach(() => {
244+
mockGlobalConfig = {
245+
updateSandboxAuditConfigs: sandbox.stub(),
246+
};
247+
248+
mockDataAccess.Configuration.findByType = sandbox.stub().resolves(mockGlobalConfig);
249+
mockDataAccess.Configuration.save = sandbox.stub().resolves();
250+
});
251+
252+
describe('updateSandboxConfig', () => {
253+
it('should update sandbox configurations successfully', async () => {
254+
const requestContext = {
255+
data: {
256+
sandboxConfigs: {
257+
cwv: { expire: '10' },
258+
'meta-tags': { expire: '15' },
259+
},
260+
},
261+
};
262+
263+
const updatedConfig = { ...mockGlobalConfig };
264+
mockGlobalConfig.updateSandboxAuditConfigs.returns(updatedConfig);
265+
266+
const result = await configurationsController.updateSandboxConfig(requestContext);
267+
const response = await result.json();
268+
269+
expect(result.status).to.equal(200);
270+
expect(response).to.have.property('message', 'Sandbox configurations updated successfully');
271+
expect(response).to.have.property('updatedConfigs');
272+
expect(response.updatedConfigs).to.deep.equal({
273+
cwv: { expire: '10' },
274+
'meta-tags': { expire: '15' },
275+
});
276+
expect(response).to.have.property('totalUpdated', 2);
277+
expect(mockGlobalConfig.updateSandboxAuditConfigs).to.have.been.calledWith({
278+
cwv: { expire: '10' },
279+
'meta-tags': { expire: '15' },
280+
});
281+
expect(mockDataAccess.Configuration.save).to.have.been.calledWith(updatedConfig);
282+
});
283+
284+
it('should return bad request when sandboxConfigs is missing', async () => {
285+
const requestContext = {
286+
data: {},
287+
};
288+
289+
const result = await configurationsController.updateSandboxConfig(requestContext);
290+
const error = await result.json();
291+
292+
expect(result.status).to.equal(400);
293+
expect(error.message).to.include('sandboxConfigs object is required');
294+
});
295+
296+
it('should return bad request when context.data is undefined', async () => {
297+
const requestContext = {};
298+
299+
const result = await configurationsController.updateSandboxConfig(requestContext);
300+
const error = await result.json();
301+
302+
expect(result.status).to.equal(400);
303+
expect(error.message).to.include('sandboxConfigs object is required');
304+
});
305+
306+
it('should return bad request when sandboxConfigs is not an object', async () => {
307+
const requestContext = {
308+
data: {
309+
sandboxConfigs: 'invalid',
310+
},
311+
};
312+
313+
const result = await configurationsController.updateSandboxConfig(requestContext);
314+
const error = await result.json();
315+
316+
expect(result.status).to.equal(400);
317+
expect(error.message).to.include('sandboxConfigs object is required');
318+
});
319+
320+
it('should return forbidden for non-admin users', async () => {
321+
context.attributes.authInfo.withProfile({ is_admin: false });
322+
323+
const requestContext = {
324+
data: {
325+
sandboxConfigs: {
326+
cwv: { expire: '10' },
327+
},
328+
},
329+
};
330+
331+
const result = await configurationsController.updateSandboxConfig(requestContext);
332+
const error = await result.json();
333+
334+
expect(result.status).to.equal(403);
335+
expect(error.message).to.include('Only admins can update sandbox configurations');
336+
});
337+
338+
it('should return not found when global configuration does not exist', async () => {
339+
mockDataAccess.Configuration.findByType.resolves(null);
340+
341+
const requestContext = {
342+
data: {
343+
sandboxConfigs: {
344+
cwv: { expire: '10' },
345+
},
346+
},
347+
};
348+
349+
const result = await configurationsController.updateSandboxConfig(requestContext);
350+
const error = await result.json();
351+
352+
expect(result.status).to.equal(404);
353+
expect(error.message).to.include('Global configuration not found');
354+
});
355+
356+
it('should return bad request when updateSandboxAuditConfigs throws an error', async () => {
357+
const requestContext = {
358+
data: {
359+
sandboxConfigs: {
360+
cwv: { expire: '10' },
361+
},
362+
},
363+
};
364+
365+
mockGlobalConfig.updateSandboxAuditConfigs.throws(new Error('Update failed'));
366+
367+
const result = await configurationsController.updateSandboxConfig(requestContext);
368+
const error = await result.json();
369+
370+
expect(result.status).to.equal(400);
371+
expect(error.message).to.include('Error updating sandbox configuration: Update failed');
372+
});
373+
});
374+
});
238375
});

test/routes/index.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('getRouteHandlers', () => {
2929
getByVersion: sinon.stub(),
3030
getLatest: sinon.stub(),
3131
updateConfiguration: sinon.stub(),
32+
updateSandboxConfig: sinon.stub(),
3233
};
3334

3435
const mockHooksController = {
@@ -231,6 +232,7 @@ describe('getRouteHandlers', () => {
231232
'GET /configurations',
232233
'GET /configurations/latest',
233234
'PUT /configurations/latest',
235+
'PATCH /configurations/sandbox',
234236
'PATCH /configurations/sites/audits',
235237
'GET /organizations',
236238
'POST /organizations',
@@ -256,6 +258,7 @@ describe('getRouteHandlers', () => {
256258
expect(staticRoutes['GET /configurations']).to.equal(mockConfigurationController.getAll);
257259
expect(staticRoutes['GET /configurations/latest']).to.equal(mockConfigurationController.getLatest);
258260
expect(staticRoutes['PUT /configurations/latest']).to.equal(mockConfigurationController.updateConfiguration);
261+
expect(staticRoutes['PATCH /configurations/sandbox']).to.equal(mockConfigurationController.updateSandboxConfig);
259262
expect(staticRoutes['PATCH /configurations/sites/audits']).to.equal(mockSitesAuditsToggleController.execute);
260263
expect(staticRoutes['GET /organizations']).to.equal(mockOrganizationsController.getAll);
261264
expect(staticRoutes['POST /organizations']).to.equal(mockOrganizationsController.createOrganization);

0 commit comments

Comments
 (0)