Skip to content

Commit 3c3d017

Browse files
test: add unit tests
1 parent b098ae5 commit 3c3d017

File tree

3 files changed

+156
-9
lines changed

3 files changed

+156
-9
lines changed

src/components/agent-preview-react.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,22 @@ export const saveTranscriptsToFile = (
7171
}
7272
};
7373

74-
const getTraces = async (
74+
export const getTraces = async (
7575
agent: AgentPreviewBase,
7676
sessionId: string,
7777
messageIds: string[],
7878
logger: Logger
7979
): Promise<PlannerResponse[]> => {
80-
try {
81-
const traces = await agent.traces(sessionId, messageIds);
82-
return traces;
83-
} catch (e) {
84-
const sfError = SfError.wrap(e);
85-
logger.info(`Error obtaining traces: ${sfError.name} - ${sfError.message}`, { sessionId, messageIds });
86-
return [];
80+
if (messageIds.length > 0) {
81+
try {
82+
const traces = await agent.traces(sessionId, messageIds);
83+
return traces;
84+
} catch (e) {
85+
const sfError = SfError.wrap(e);
86+
logger.info(`Error obtaining traces: ${sfError.name} - ${sfError.message}`, { sessionId, messageIds });
87+
}
8788
}
89+
return [];
8890
};
8991

9092
/**

test/components/agent-preview-react.test.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ import * as os from 'node:os';
1919
import * as path from 'node:path';
2020
import { describe, it, beforeEach, afterEach } from 'mocha';
2121
import { expect } from 'chai';
22+
import sinon, { SinonStubbedInstance } from 'sinon';
2223
import type { AgentPreviewSendResponse } from '@salesforce/agents';
23-
import { saveTranscriptsToFile } from '../../src/components/agent-preview-react.js';
24+
import { PlannerResponse } from '@salesforce/agents/lib/types.js';
25+
import type { Logger } from '@salesforce/core';
26+
import type { AgentPreviewBase } from '@salesforce/agents';
27+
import { saveTranscriptsToFile, getTraces } from '../../src/components/agent-preview-react.js';
28+
import { trace1, trace2 } from '../testData.js';
2429

2530
describe('AgentPreviewReact saveTranscriptsToFile', () => {
2631
let testDir: string;
@@ -139,4 +144,76 @@ describe('AgentPreviewReact saveTranscriptsToFile', () => {
139144
// Should parse as valid JSON
140145
expect(() => JSON.parse(content) as unknown).to.not.throw();
141146
});
147+
148+
it('should write traces.json when traces are provided', () => {
149+
const outputDir = path.join(testDir, 'output');
150+
const messages: Array<{ timestamp: Date; role: string; content: string }> = [];
151+
const responses: AgentPreviewSendResponse[] = [];
152+
const traces: PlannerResponse[] = [trace1, trace2];
153+
154+
saveTranscriptsToFile(outputDir, messages, responses, traces);
155+
156+
const tracesPath = path.join(outputDir, 'traces.json');
157+
expect(fs.existsSync(tracesPath)).to.be.true;
158+
159+
const content = JSON.parse(fs.readFileSync(tracesPath, 'utf8')) as PlannerResponse[];
160+
expect(content).to.have.lengthOf(2);
161+
});
162+
});
163+
164+
describe('AgentPreviewReact getTraces', () => {
165+
let mockAgent: SinonStubbedInstance<AgentPreviewBase>;
166+
let mockLogger: SinonStubbedInstance<Logger>;
167+
const sessionId = 'session-123';
168+
const messageIds = ['msg-1', 'msg-2'];
169+
170+
beforeEach(() => {
171+
mockAgent = {
172+
traces: sinon.stub(),
173+
} as SinonStubbedInstance<AgentPreviewBase>;
174+
175+
mockLogger = {
176+
info: sinon.stub(),
177+
} as SinonStubbedInstance<Logger>;
178+
});
179+
180+
afterEach(() => {
181+
sinon.restore();
182+
});
183+
184+
it('should return traces when agent.traces succeeds', async () => {
185+
const expectedTraces: PlannerResponse[] = [trace1];
186+
187+
mockAgent.traces.resolves(expectedTraces);
188+
189+
const result = await getTraces(mockAgent, sessionId, messageIds, mockLogger);
190+
191+
expect(result).to.deep.equal(expectedTraces);
192+
expect(mockAgent.traces.calledWith(sessionId, messageIds)).to.be.true;
193+
expect(mockLogger.info.called).to.be.false;
194+
});
195+
196+
it('should return empty array when agent.traces throws an error', async () => {
197+
const error = new Error('Failed to get traces');
198+
mockAgent.traces.rejects(error);
199+
200+
const result = await getTraces(mockAgent, sessionId, messageIds, mockLogger);
201+
202+
expect(result).to.deep.equal([]);
203+
expect(mockAgent.traces.calledWith(sessionId, messageIds)).to.be.true;
204+
expect(
205+
mockLogger.info.calledWith('Error obtaining traces: Error - Failed to get traces', { sessionId, messageIds })
206+
).to.be.true;
207+
});
208+
209+
it('should handle empty messageIds array', async () => {
210+
const expectedTraces: PlannerResponse[] = [];
211+
mockAgent.traces.resolves(expectedTraces);
212+
213+
const result = await getTraces(mockAgent, sessionId, [], mockLogger);
214+
215+
expect(result).to.deep.equal(expectedTraces);
216+
expect(mockAgent.traces.notCalled).to.be.true;
217+
expect(mockLogger.info.called).to.be.false;
218+
});
142219
});

test/testData.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025, 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+
import { PlannerResponse } from '@salesforce/agents/lib/types.js';
17+
18+
export const trace1: PlannerResponse = {
19+
type: 'PlanSuccessResponse',
20+
planId: 'plan-1',
21+
sessionId: 'session-123',
22+
intent: 'get_weather',
23+
topic: 'weather',
24+
plan: [
25+
{
26+
type: 'FunctionStep',
27+
function: {
28+
name: 'get_weather',
29+
input: { location: 'Madrid' },
30+
output: { temperature: 25, condition: 'sunny' },
31+
},
32+
executionLatency: 100,
33+
startExecutionTime: Date.now(),
34+
endExecutionTime: Date.now() + 100,
35+
},
36+
],
37+
};
38+
39+
export const trace2: PlannerResponse = {
40+
type: 'PlanSuccessResponse',
41+
planId: 'plan-4',
42+
sessionId: 'session-456',
43+
intent: 'send_message',
44+
topic: 'communication',
45+
plan: [
46+
{
47+
type: 'PlannerResponseStep',
48+
message: 'Hello world',
49+
responseType: 'text',
50+
isContentSafe: true,
51+
safetyScore: {
52+
// eslint-disable-next-line camelcase
53+
safety_score: 0.9,
54+
// eslint-disable-next-line camelcase
55+
category_scores: {
56+
toxicity: 0.1,
57+
hate: 0.0,
58+
identity: 0.0,
59+
violence: 0.0,
60+
physical: 0.0,
61+
sexual: 0.0,
62+
profanity: 0.0,
63+
biased: 0.0,
64+
},
65+
},
66+
},
67+
],
68+
};

0 commit comments

Comments
 (0)