Skip to content

Commit 7ed503f

Browse files
test: add UTs
1 parent b624de2 commit 7ed503f

File tree

2 files changed

+229
-4
lines changed

2 files changed

+229
-4
lines changed

src/client/metadataApiDeploy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ export class MetadataApiDeploy extends MetadataTransfer<
272272
}>;
273273
}>({
274274
method: 'POST',
275+
// this will need to be api.salesforce once changes are in prod
275276
url: 'https://test.api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts',
276277
headers: {
277278
'x-client-name': 'afdx',

test/client/metadataApiDeploy.test.ts

Lines changed: 228 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import { basename, join, sep } from 'node:path';
1717
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup';
1818
import chai, { assert, expect } from 'chai';
1919
import { AnyJson, ensureString, getString } from '@salesforce/ts-types';
20-
import { envVars, Lifecycle, Messages, PollingClient, StatusResult } from '@salesforce/core';
20+
import { Connection, envVars, Lifecycle, Messages, PollingClient, StatusResult } from '@salesforce/core';
2121
import { Duration } from '@salesforce/kit';
2222
import deepEqualInAnyOrder from 'deep-equal-in-any-order';
2323
import * as sinon from 'sinon';
24+
import fs from 'graceful-fs';
2425
import {
2526
ComponentSet,
2627
ComponentStatus,
@@ -70,15 +71,14 @@ describe('MetadataApiDeploy', () => {
7071

7172
describe('Lifecycle', () => {
7273
describe('start', () => {
73-
it('should not convert zip, but read from fs');
74-
it('should not mdapiDir, but generate zip buffer from it');
75-
7674
it('should convert to metadata format and create zip', async () => {
7775
const components = new ComponentSet([matchingContentFile.COMPONENT]);
7876
const { operation, convertStub } = await stubMetadataDeploy($$, testOrg, {
7977
components,
8078
});
8179

80+
expect(components.getAiAuthoringBundles().toArray()).to.be.empty;
81+
8282
await operation.start();
8383

8484
expect(convertStub.calledWith(components, 'metadata', { type: 'zip' })).to.be.true;
@@ -1297,4 +1297,228 @@ describe('MetadataApiDeploy', () => {
12971297
expect(mdOpts.apiOptions).to.have.property('singlePackage', true);
12981298
});
12991299
});
1300+
1301+
describe('AiAuthoringBundle compilation', () => {
1302+
const aabType = registry.types.aiauthoringbundle;
1303+
const aabName = 'TestAAB';
1304+
const aabContentDir = join('path', 'to', 'aiAuthoringBundles', aabName);
1305+
const agentFileName = `${aabName}.agent`;
1306+
const agentContent = 'test agent script content';
1307+
1308+
const createAABComponent = (): SourceComponent =>
1309+
SourceComponent.createVirtualComponent(
1310+
{
1311+
name: aabName,
1312+
type: aabType,
1313+
xml: join(aabContentDir, `${aabName}${META_XML_SUFFIX}`),
1314+
content: aabContentDir,
1315+
},
1316+
[
1317+
{
1318+
dirPath: join('path', 'to', 'aiAuthoringBundles'),
1319+
children: [aabName],
1320+
},
1321+
{
1322+
dirPath: aabContentDir,
1323+
children: [agentFileName],
1324+
},
1325+
]
1326+
);
1327+
1328+
it('should throw error with correct data when compilation fails', async () => {
1329+
const aabComponent = createAABComponent();
1330+
const components = new ComponentSet([aabComponent]);
1331+
1332+
// Stub retrieveMaxApiVersion on prototype before getting connection
1333+
$$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0');
1334+
const connection = await testOrg.getConnection();
1335+
1336+
const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent);
1337+
1338+
$$.SANDBOX.stub(connection, 'getConnectionOptions').returns({
1339+
accessToken: 'test-access-token',
1340+
instanceUrl: 'https://test.salesforce.com',
1341+
});
1342+
1343+
const compileErrors = [
1344+
{ description: 'Syntax error on line 5', lineStart: 5, colStart: 10 },
1345+
{ description: 'Missing token', lineStart: 8, colStart: 15 },
1346+
];
1347+
1348+
// Configure connection.request stub (already created by TestContext)
1349+
let callCount = 0;
1350+
(connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => {
1351+
callCount++;
1352+
if (request.url?.includes('agentforce/bootstrap/nameduser')) {
1353+
return Promise.resolve({ access_token: 'named-user-token' });
1354+
}
1355+
if (request.url?.includes('einstein/ai-agent')) {
1356+
return Promise.resolve({ status: 'failure' as const, errors: compileErrors });
1357+
}
1358+
// For other requests, return empty object (deploy stub handles its own requests)
1359+
return Promise.resolve({});
1360+
});
1361+
1362+
const { operation } = await stubMetadataDeploy($$, testOrg, { components });
1363+
1364+
try {
1365+
await operation.start();
1366+
expect.fail('Should have thrown AgentCompilationError');
1367+
} catch (error: unknown) {
1368+
const err = error as { name?: string; message?: string };
1369+
expect(err).to.have.property('name', 'AgentCompilationError');
1370+
expect(err.message).to.include(`${aabName}.agent: Syntax error on line 5 5:10`);
1371+
expect(err.message).to.include(`${aabName}.agent: Missing token 8:15`);
1372+
}
1373+
1374+
expect(readFileStub.calledOnce).to.be.true;
1375+
expect(callCount).to.be.at.least(2);
1376+
});
1377+
1378+
it('should not throw error when compilation succeeds', async () => {
1379+
const aabComponent = createAABComponent();
1380+
const components = new ComponentSet([aabComponent]);
1381+
1382+
// Stub retrieveMaxApiVersion on prototype before getting connection
1383+
$$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0');
1384+
const connection = await testOrg.getConnection();
1385+
1386+
const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent);
1387+
1388+
$$.SANDBOX.stub(connection, 'getConnectionOptions').returns({
1389+
accessToken: 'test-access-token',
1390+
instanceUrl: 'https://test.salesforce.com',
1391+
});
1392+
1393+
// Configure connection.request stub (already created by TestContext)
1394+
let callCount = 0;
1395+
(connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => {
1396+
callCount++;
1397+
if (request.url?.includes('agentforce/bootstrap/nameduser')) {
1398+
return Promise.resolve({ access_token: 'named-user-token' });
1399+
}
1400+
if (request.url?.includes('einstein/ai-agent')) {
1401+
return Promise.resolve({ status: 'success' as const, errors: [] });
1402+
}
1403+
// For other requests, return empty object (deploy stub handles its own requests)
1404+
return Promise.resolve({});
1405+
});
1406+
1407+
const { operation } = await stubMetadataDeploy($$, testOrg, { components });
1408+
1409+
// Should not throw
1410+
await operation.start();
1411+
1412+
expect(readFileStub.calledOnce).to.be.true;
1413+
expect(callCount).to.be.at.least(2);
1414+
});
1415+
1416+
it('should not compile when no AABs present in component set', async () => {
1417+
const components = new ComponentSet([COMPONENT]);
1418+
1419+
// Stub retrieveMaxApiVersion on prototype before getting connection
1420+
$$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0');
1421+
const connection = await testOrg.getConnection();
1422+
1423+
const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile');
1424+
// Track calls to connection.request to verify compilation wasn't attempted
1425+
const compileCallCount = { count: 0 };
1426+
(connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => {
1427+
const url = request.url ?? '';
1428+
if (url.includes('einstein/ai-agent') || url.includes('agentforce/bootstrap')) {
1429+
compileCallCount.count++;
1430+
}
1431+
// For other requests, return empty object (deploy stub handles its own requests)
1432+
return Promise.resolve({});
1433+
});
1434+
1435+
const { operation } = await stubMetadataDeploy($$, testOrg, { components });
1436+
1437+
await operation.start();
1438+
1439+
// Verify compilation endpoints were not called
1440+
expect(readFileStub.called).to.be.false;
1441+
expect(compileCallCount.count).to.equal(0);
1442+
});
1443+
1444+
it('should handle multiple AABs in parallel', async () => {
1445+
const aab1 = SourceComponent.createVirtualComponent(
1446+
{
1447+
name: 'AAB1',
1448+
type: aabType,
1449+
xml: join('path', 'to', 'aiAuthoringBundles', 'AAB1', `AAB1${META_XML_SUFFIX}`),
1450+
content: join('path', 'to', 'aiAuthoringBundles', 'AAB1'),
1451+
},
1452+
[
1453+
{
1454+
dirPath: join('path', 'to', 'aiAuthoringBundles'),
1455+
children: ['AAB1'],
1456+
},
1457+
{
1458+
dirPath: join('path', 'to', 'aiAuthoringBundles', 'AAB1'),
1459+
children: ['AAB1.agent'],
1460+
},
1461+
]
1462+
);
1463+
1464+
const aab2 = SourceComponent.createVirtualComponent(
1465+
{
1466+
name: 'AAB2',
1467+
type: aabType,
1468+
xml: join('path', 'to', 'aiAuthoringBundles', 'AAB2', `AAB2${META_XML_SUFFIX}`),
1469+
content: join('path', 'to', 'aiAuthoringBundles', 'AAB2'),
1470+
},
1471+
[
1472+
{
1473+
dirPath: join('path', 'to', 'aiAuthoringBundles'),
1474+
children: ['AAB2'],
1475+
},
1476+
{
1477+
dirPath: join('path', 'to', 'aiAuthoringBundles', 'AAB2'),
1478+
children: ['AAB2.agent'],
1479+
},
1480+
]
1481+
);
1482+
1483+
const components = new ComponentSet([aab1, aab2]);
1484+
1485+
// Stub retrieveMaxApiVersion on prototype before getting connection
1486+
$$.SANDBOX.stub(Connection.prototype, 'retrieveMaxApiVersion').resolves('60.0');
1487+
const connection = await testOrg.getConnection();
1488+
1489+
const readFileStub = $$.SANDBOX.stub(fs.promises, 'readFile').resolves(agentContent);
1490+
1491+
$$.SANDBOX.stub(connection, 'getConnectionOptions').returns({
1492+
accessToken: 'test-access-token',
1493+
instanceUrl: 'https://test.salesforce.com',
1494+
});
1495+
1496+
// Configure connection.request stub (already created by TestContext)
1497+
// Handle multiple AABs: 2 nameduser + 2 compile calls
1498+
let namedUserCallCount = 0;
1499+
let compileCallCount = 0;
1500+
(connection.request as sinon.SinonStub).callsFake((request: { url?: string }) => {
1501+
if (request.url?.includes('agentforce/bootstrap/nameduser')) {
1502+
namedUserCallCount++;
1503+
return Promise.resolve({ access_token: 'named-user-token' });
1504+
}
1505+
if (request.url?.includes('einstein/ai-agent')) {
1506+
compileCallCount++;
1507+
return Promise.resolve({ status: 'success' as const, errors: [] });
1508+
}
1509+
// For other requests, return empty object (deploy stub handles its own requests)
1510+
return Promise.resolve({});
1511+
});
1512+
1513+
const { operation } = await stubMetadataDeploy($$, testOrg, { components });
1514+
1515+
await operation.start();
1516+
1517+
// Should read both agent files
1518+
expect(readFileStub.callCount).to.equal(2);
1519+
// Should call compile endpoint twice (once per AAB) and nameduser twice
1520+
expect(namedUserCallCount).to.equal(2);
1521+
expect(compileCallCount).to.equal(2);
1522+
});
1523+
});
13001524
});

0 commit comments

Comments
 (0)