Skip to content

Commit ff155c5

Browse files
test: add unit tests
1 parent c046353 commit ff155c5

File tree

2 files changed

+209
-1
lines changed

2 files changed

+209
-1
lines changed

src/commands/agent/generate/template.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ const jsonToXml = <T>(filename: string, json: T, builder: XMLBuilder): void => {
233233
* @param genAiPlannerBundleMetaJson - The GenAiPlannerBundle metadata to read from
234234
* @returns { localTopics, localActions } - The local topics and the flattened local actions from all plugins
235235
*/
236-
const getLocalAssets = (
236+
export const getLocalAssets = (
237237
genAiPlannerBundleMetaJson: GenAiPlannerBundleExt
238238
): { localTopics: GenAiPlugin[]; localActions: GenAiFunction[] } => {
239239
const rawLocalTopics = genAiPlannerBundleMetaJson.GenAiPlannerBundle.localTopics;
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* Copyright 2026, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
18+
19+
import { join } from 'node:path';
20+
import { expect } from 'chai';
21+
import esmock from 'esmock';
22+
import { SfError } from '@salesforce/core';
23+
import { TestContext } from '@salesforce/core/testSetup';
24+
import { SfProject } from '@salesforce/core';
25+
import type { GenAiPlugin, GenAiFunction } from '@salesforce/types/metadata';
26+
import { getLocalAssets, type GenAiPlannerBundleExt } from '../../../../src/commands/agent/generate/template.js';
27+
28+
const MOCK_PROJECT_DIR = join(process.cwd(), 'test', 'mock-projects', 'agent-generate-template');
29+
30+
const BOT_XML_NGA = `<?xml version="1.0" encoding="UTF-8"?>
31+
<Bot xmlns="http://soap.sforce.com/2006/04/metadata">
32+
<agentDSLEnabled>true</agentDSLEnabled>
33+
<agentType>EinsteinServiceAgent</agentType>
34+
<botMlDomain><label>Test</label><name>TestBot</name></botMlDomain>
35+
<botSource>None</botSource>
36+
<label>Test Bot</label>
37+
<type>Conversational</type>
38+
</Bot>`;
39+
40+
describe('agent generate template', () => {
41+
const $$ = new TestContext();
42+
// Use existing bot in mock project so --agent-file exists check passes
43+
const agentFile = join(
44+
MOCK_PROJECT_DIR,
45+
'force-app',
46+
'main',
47+
'default',
48+
'bots',
49+
'Local_Info_Agent',
50+
'Local_Info_Agent.bot-meta.xml'
51+
);
52+
const runArgs = (): string[] => ['--agent-file', agentFile, '--agent-version', '1', '--json'];
53+
54+
beforeEach(() => {
55+
$$.inProject(true);
56+
const mockProject = {
57+
getPath: () => MOCK_PROJECT_DIR,
58+
getDefaultPackage: () => ({ fullPath: join(MOCK_PROJECT_DIR, 'force-app') }),
59+
} as unknown as SfProject;
60+
$$.SANDBOX.stub(SfProject, 'resolve').resolves(mockProject);
61+
$$.SANDBOX.stub(SfProject, 'getInstance').returns(mockProject);
62+
});
63+
64+
afterEach(() => {
65+
$$.restore();
66+
});
67+
68+
it('should throw nga-agent-not-supported when is an NGA agent', async () => {
69+
let readCount = 0;
70+
const readFileSyncMock = (): string => {
71+
readCount += 1;
72+
return BOT_XML_NGA;
73+
};
74+
const mod = await esmock('../../../../src/commands/agent/generate/template.js', {
75+
'node:fs': {
76+
readFileSync: readFileSyncMock,
77+
mkdirSync: () => {},
78+
writeFileSync: () => {},
79+
},
80+
});
81+
const AgentGenerateTemplate = mod.default;
82+
83+
try {
84+
await AgentGenerateTemplate.run(runArgs());
85+
expect.fail('Expected SfError (nga-agent-not-supported)');
86+
} catch (error) {
87+
expect(error).to.be.instanceOf(SfError);
88+
expect((error as SfError).message).to.match(/legacy agents|Agent Script|nga-agent-not-supported/i);
89+
}
90+
expect(readCount).to.equal(1);
91+
});
92+
93+
it('should throw local-topics-without-source when a local topic has no source', () => {
94+
const topicWithoutSource = { developerName: 'my_topic', fullName: 'my_topic' } as GenAiPlugin;
95+
const bundle = {
96+
GenAiPlannerBundle: {
97+
localTopicLinks: [],
98+
localTopics: [topicWithoutSource],
99+
},
100+
} as unknown as GenAiPlannerBundleExt;
101+
102+
try {
103+
getLocalAssets(bundle);
104+
expect.fail('Expected SfError (local-topics-without-source)');
105+
} catch (error) {
106+
expect(error).to.be.instanceOf(SfError);
107+
expect((error as SfError).message).to.match(/local topic|genAiPlugin|global topic|local-topics-without-source/i);
108+
}
109+
});
110+
111+
it('should throw local-actions-without-source when a local action has no source', () => {
112+
const actionWithoutSource = { developerName: 'my_action', fullName: 'my_action' } as GenAiFunction;
113+
const bundle = {
114+
GenAiPlannerBundle: {
115+
localTopicLinks: [],
116+
plannerActions: [actionWithoutSource],
117+
},
118+
} as unknown as GenAiPlannerBundleExt;
119+
120+
try {
121+
getLocalAssets(bundle);
122+
expect.fail('Expected SfError (local-actions-without-source)');
123+
} catch (error) {
124+
expect(error).to.be.instanceOf(SfError);
125+
expect((error as SfError).message).to.match(
126+
/local action|genAiFunction|global action|local-actions-without-source/i
127+
);
128+
}
129+
});
130+
it('returns topics without actions when plugins have no localActions', () => {
131+
const topic = {
132+
developerName: 'topic_a',
133+
fullName: 'topic_a',
134+
source: 'GlobalTopic_A',
135+
} as GenAiPlugin;
136+
const bundle = {
137+
GenAiPlannerBundle: {
138+
localTopicLinks: [],
139+
localTopics: [topic],
140+
},
141+
} as unknown as GenAiPlannerBundleExt;
142+
143+
const { localTopics, localActions } = getLocalAssets(bundle);
144+
145+
expect(localTopics).to.have.length(1);
146+
expect(localTopics[0].developerName).to.equal('topic_a');
147+
expect(localTopics[0].source).to.equal('GlobalTopic_A');
148+
expect(localActions).to.deep.equal([]);
149+
});
150+
151+
it('returns actions from both localActions (plugins) and plannerActions', () => {
152+
const actionFromPlugin = {
153+
developerName: 'plugin_action',
154+
fullName: 'plugin_action',
155+
source: 'GlobalPluginAction',
156+
} as GenAiFunction;
157+
const topicWithAction = {
158+
developerName: 'topic_b',
159+
fullName: 'topic_b',
160+
source: 'GlobalTopic_B',
161+
localActions: [actionFromPlugin],
162+
} as GenAiPlugin;
163+
const plannerAction = {
164+
developerName: 'planner_action',
165+
fullName: 'planner_action',
166+
source: 'GlobalPlannerAction',
167+
} as GenAiFunction;
168+
const bundle = {
169+
GenAiPlannerBundle: {
170+
localTopicLinks: [],
171+
localTopics: [topicWithAction],
172+
plannerActions: [plannerAction],
173+
},
174+
} as unknown as GenAiPlannerBundleExt;
175+
176+
const { localTopics, localActions } = getLocalAssets(bundle);
177+
178+
expect(localTopics).to.have.length(1);
179+
expect(localTopics[0].developerName).to.equal('topic_b');
180+
expect(localActions).to.have.length(2);
181+
expect(localActions[0].developerName).to.equal('plugin_action');
182+
expect(localActions[0].source).to.equal('GlobalPluginAction');
183+
expect(localActions[1].developerName).to.equal('planner_action');
184+
expect(localActions[1].source).to.equal('GlobalPlannerAction');
185+
});
186+
187+
it('returns only plannerActions when there are no localTopics', () => {
188+
const plannerAction = {
189+
developerName: 'solo_planner',
190+
fullName: 'solo_planner',
191+
source: 'GlobalSoloAction',
192+
} as GenAiFunction;
193+
const bundle = {
194+
GenAiPlannerBundle: {
195+
localTopicLinks: [],
196+
localTopics: [],
197+
plannerActions: [plannerAction],
198+
},
199+
} as unknown as GenAiPlannerBundleExt;
200+
201+
const { localTopics, localActions } = getLocalAssets(bundle);
202+
203+
expect(localTopics).to.deep.equal([]);
204+
expect(localActions).to.have.length(1);
205+
expect(localActions[0].developerName).to.equal('solo_planner');
206+
expect(localActions[0].source).to.equal('GlobalSoloAction');
207+
});
208+
});

0 commit comments

Comments
 (0)