promptfoo 0.103.3 → 0.103.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +10 -1
  2. package/dist/package.json +13 -13
  3. package/dist/src/app/assets/index-BR1tgrAf.css +1 -0
  4. package/dist/src/app/assets/{index-XXoiz61D.js → index-CmPQAxfe.js} +276 -276
  5. package/dist/src/app/assets/{index.es-DTKpmNcZ.js → index.es-DfqJ7zdu.js} +1 -1
  6. package/dist/src/app/assets/{sync-ClbHj3jr.js → sync-C-aW1Mpw.js} +1 -1
  7. package/dist/src/app/index.html +2 -2
  8. package/dist/src/assertions/index.d.ts +3 -2
  9. package/dist/src/assertions/index.d.ts.map +1 -1
  10. package/dist/src/assertions/index.js +21 -6
  11. package/dist/src/assertions/index.js.map +1 -1
  12. package/dist/src/assertions/utils.d.ts +6 -2
  13. package/dist/src/assertions/utils.d.ts.map +1 -1
  14. package/dist/src/commands/eval/filterErrorTests.d.ts +5 -0
  15. package/dist/src/commands/eval/filterErrorTests.d.ts.map +1 -0
  16. package/dist/src/commands/eval/filterErrorTests.js +19 -0
  17. package/dist/src/commands/eval/filterErrorTests.js.map +1 -0
  18. package/dist/src/commands/eval/filterTests.d.ts +1 -0
  19. package/dist/src/commands/eval/filterTests.d.ts.map +1 -1
  20. package/dist/src/commands/eval/filterTests.js +4 -0
  21. package/dist/src/commands/eval/filterTests.js.map +1 -1
  22. package/dist/src/commands/eval.d.ts.map +1 -1
  23. package/dist/src/commands/eval.js +1 -0
  24. package/dist/src/commands/eval.js.map +1 -1
  25. package/dist/src/database/tables.d.ts +51 -12
  26. package/dist/src/database/tables.d.ts.map +1 -1
  27. package/dist/src/envars.d.ts +1 -0
  28. package/dist/src/envars.d.ts.map +1 -1
  29. package/dist/src/envars.js.map +1 -1
  30. package/dist/src/evaluator.d.ts.map +1 -1
  31. package/dist/src/evaluator.js +1 -0
  32. package/dist/src/evaluator.js.map +1 -1
  33. package/dist/src/fetch.d.ts.map +1 -1
  34. package/dist/src/fetch.js +20 -3
  35. package/dist/src/fetch.js.map +1 -1
  36. package/dist/src/models/evalResult.d.ts.map +1 -1
  37. package/dist/src/models/evalResult.js +9 -1
  38. package/dist/src/models/evalResult.js.map +1 -1
  39. package/dist/src/providers/browser.js +1 -1
  40. package/dist/src/providers/browser.js.map +1 -1
  41. package/dist/src/providers/defaults.d.ts +1 -0
  42. package/dist/src/providers/defaults.d.ts.map +1 -1
  43. package/dist/src/providers/defaults.js +11 -0
  44. package/dist/src/providers/defaults.js.map +1 -1
  45. package/dist/src/providers/http.d.ts.map +1 -1
  46. package/dist/src/providers/http.js +39 -63
  47. package/dist/src/providers/http.js.map +1 -1
  48. package/dist/src/providers/llama.d.ts.map +1 -1
  49. package/dist/src/providers/llama.js +8 -1
  50. package/dist/src/providers/llama.js.map +1 -1
  51. package/dist/src/providers/openai.d.ts.map +1 -1
  52. package/dist/src/providers/openai.js +6 -13
  53. package/dist/src/providers/openai.js.map +1 -1
  54. package/dist/src/providers/watsonx.d.ts.map +1 -1
  55. package/dist/src/providers/watsonx.js +9 -0
  56. package/dist/src/providers/watsonx.js.map +1 -1
  57. package/dist/src/providers.d.ts.map +1 -1
  58. package/dist/src/providers.js +15 -0
  59. package/dist/src/providers.js.map +1 -1
  60. package/dist/src/redteam/commands/generate.d.ts.map +1 -1
  61. package/dist/src/redteam/commands/generate.js +4 -0
  62. package/dist/src/redteam/commands/generate.js.map +1 -1
  63. package/dist/src/redteam/constants.d.ts +4 -2
  64. package/dist/src/redteam/constants.d.ts.map +1 -1
  65. package/dist/src/redteam/constants.js +11 -7
  66. package/dist/src/redteam/constants.js.map +1 -1
  67. package/dist/src/redteam/plugins/base.d.ts.map +1 -1
  68. package/dist/src/redteam/plugins/base.js +3 -0
  69. package/dist/src/redteam/plugins/base.js.map +1 -1
  70. package/dist/src/redteam/plugins/cyberseceval.d.ts.map +1 -1
  71. package/dist/src/redteam/plugins/cyberseceval.js +13 -3
  72. package/dist/src/redteam/plugins/cyberseceval.js.map +1 -1
  73. package/dist/src/redteam/providers/crescendo/index.d.ts +1 -0
  74. package/dist/src/redteam/providers/crescendo/index.d.ts.map +1 -1
  75. package/dist/src/redteam/providers/crescendo/index.js +58 -3
  76. package/dist/src/redteam/providers/crescendo/index.js.map +1 -1
  77. package/dist/src/redteam/providers/iterative.d.ts.map +1 -1
  78. package/dist/src/redteam/providers/iterative.js +59 -5
  79. package/dist/src/redteam/providers/iterative.js.map +1 -1
  80. package/dist/src/redteam/providers/iterativeImage.d.ts +6 -2
  81. package/dist/src/redteam/providers/iterativeImage.d.ts.map +1 -1
  82. package/dist/src/redteam/providers/iterativeImage.js +322 -131
  83. package/dist/src/redteam/providers/iterativeImage.js.map +1 -1
  84. package/dist/src/redteam/providers/iterativeTree.d.ts +37 -26
  85. package/dist/src/redteam/providers/iterativeTree.d.ts.map +1 -1
  86. package/dist/src/redteam/providers/iterativeTree.js +193 -85
  87. package/dist/src/redteam/providers/iterativeTree.js.map +1 -1
  88. package/dist/src/redteam/shared.d.ts.map +1 -1
  89. package/dist/src/redteam/shared.js +4 -1
  90. package/dist/src/redteam/shared.js.map +1 -1
  91. package/dist/src/server/routes/providers.js +11 -6
  92. package/dist/src/server/routes/providers.js.map +1 -1
  93. package/dist/src/types/env.d.ts +3 -0
  94. package/dist/src/types/env.d.ts.map +1 -1
  95. package/dist/src/types/index.d.ts +1376 -351
  96. package/dist/src/types/index.d.ts.map +1 -1
  97. package/dist/src/types/index.js +4 -1
  98. package/dist/src/types/index.js.map +1 -1
  99. package/dist/src/types/providers.d.ts +22 -0
  100. package/dist/src/types/providers.d.ts.map +1 -1
  101. package/dist/src/types/providers.js.map +1 -1
  102. package/dist/src/util/config/manage.d.ts +1 -1
  103. package/dist/src/util/config/manage.d.ts.map +1 -1
  104. package/dist/src/util/config/manage.js.map +1 -1
  105. package/dist/src/util/convertEvalResultsToTable.d.ts.map +1 -1
  106. package/dist/src/util/convertEvalResultsToTable.js +14 -0
  107. package/dist/src/util/convertEvalResultsToTable.js.map +1 -1
  108. package/dist/src/util/index.d.ts +12 -4
  109. package/dist/src/util/index.d.ts.map +1 -1
  110. package/dist/src/validators/providers.d.ts +71 -2
  111. package/dist/src/validators/providers.d.ts.map +1 -1
  112. package/dist/src/validators/providers.js +3 -0
  113. package/dist/src/validators/providers.js.map +1 -1
  114. package/dist/src/validators/redteam.d.ts +24 -0
  115. package/dist/src/validators/redteam.d.ts.map +1 -1
  116. package/dist/test/assertions/index.test.js +26 -475
  117. package/dist/test/assertions/index.test.js.map +1 -1
  118. package/dist/test/assertions/javascript.test.d.ts +2 -0
  119. package/dist/test/assertions/javascript.test.d.ts.map +1 -0
  120. package/dist/test/assertions/javascript.test.js +679 -0
  121. package/dist/test/assertions/javascript.test.js.map +1 -0
  122. package/dist/test/assertions/python.test.d.ts +2 -0
  123. package/dist/test/assertions/python.test.d.ts.map +1 -0
  124. package/dist/test/assertions/python.test.js +377 -0
  125. package/dist/test/assertions/python.test.js.map +1 -0
  126. package/dist/test/cache.test.js +297 -100
  127. package/dist/test/cache.test.js.map +1 -1
  128. package/dist/test/commands/eval/filterErrorTests.test.d.ts +2 -0
  129. package/dist/test/commands/eval/filterErrorTests.test.d.ts.map +1 -0
  130. package/dist/test/commands/eval/filterErrorTests.test.js +110 -0
  131. package/dist/test/commands/eval/filterErrorTests.test.js.map +1 -0
  132. package/dist/test/evaluator.test.js +10 -0
  133. package/dist/test/evaluator.test.js.map +1 -1
  134. package/dist/test/factories/evalFactory.d.ts +39 -8
  135. package/dist/test/factories/evalFactory.d.ts.map +1 -1
  136. package/dist/test/fetch.test.js +147 -19
  137. package/dist/test/fetch.test.js.map +1 -1
  138. package/dist/test/models/eval.test.js +12 -0
  139. package/dist/test/models/eval.test.js.map +1 -1
  140. package/dist/test/providers/defaults.test.d.ts +2 -0
  141. package/dist/test/providers/defaults.test.d.ts.map +1 -0
  142. package/dist/test/providers/defaults.test.js +77 -0
  143. package/dist/test/providers/defaults.test.js.map +1 -0
  144. package/dist/test/providers/http.test.js +65 -9
  145. package/dist/test/providers/http.test.js.map +1 -1
  146. package/dist/test/providers/index.test.js +6 -3
  147. package/dist/test/providers/index.test.js.map +1 -1
  148. package/dist/test/providers/mistral.test.js +28 -19
  149. package/dist/test/providers/mistral.test.js.map +1 -1
  150. package/dist/test/providers/watsonx.test.js +58 -0
  151. package/dist/test/providers/watsonx.test.js.map +1 -1
  152. package/dist/test/redteam/providers/iterativeTree.test.js +329 -98
  153. package/dist/test/redteam/providers/iterativeTree.test.js.map +1 -1
  154. package/dist/test/server/providers.test.js +4 -4
  155. package/dist/test/server/providers.test.js.map +1 -1
  156. package/dist/test/util/config/main.test.js +3 -0
  157. package/dist/test/util/config/main.test.js.map +1 -1
  158. package/dist/tsconfig.tsbuildinfo +1 -1
  159. package/package.json +13 -13
  160. package/dist/src/app/assets/index-DdUNCsxz.css +0 -1
@@ -0,0 +1,679 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const path = __importStar(require("path"));
37
+ const assertions_1 = require("../../src/assertions");
38
+ const esm_1 = require("../../src/esm");
39
+ const openai_1 = require("../../src/providers/openai");
40
+ const packageParser_1 = require("../../src/providers/packageParser");
41
+ jest.mock('../../src/redteam/remoteGeneration', () => ({
42
+ shouldGenerateRemote: jest.fn().mockReturnValue(false),
43
+ }));
44
+ jest.mock('proxy-agent', () => ({
45
+ ProxyAgent: jest.fn().mockImplementation(() => ({})),
46
+ }));
47
+ jest.mock('node:module', () => {
48
+ const mockRequire = {
49
+ resolve: jest.fn(),
50
+ };
51
+ return {
52
+ createRequire: jest.fn().mockReturnValue(mockRequire),
53
+ };
54
+ });
55
+ jest.mock('../../src/fetch', () => {
56
+ const actual = jest.requireActual('../../src/fetch');
57
+ return {
58
+ ...actual,
59
+ fetchWithRetries: jest.fn(actual.fetchWithRetries),
60
+ };
61
+ });
62
+ jest.mock('glob', () => ({
63
+ globSync: jest.fn(),
64
+ }));
65
+ jest.mock('fs', () => ({
66
+ readFileSync: jest.fn(),
67
+ promises: {
68
+ readFile: jest.fn(),
69
+ },
70
+ }));
71
+ jest.mock('../../src/esm', () => ({
72
+ importModule: jest.fn().mockImplementation((path, functionName) => {
73
+ // Make sure both parameters are captured in the mock call
74
+ return Promise.resolve();
75
+ }),
76
+ __esModule: true,
77
+ }));
78
+ jest.mock('../../src/database', () => ({
79
+ getDb: jest.fn(),
80
+ }));
81
+ jest.mock('path', () => {
82
+ const actualPath = jest.requireActual('path');
83
+ return {
84
+ ...actualPath,
85
+ resolve: jest.fn((basePath, filePath) => actualPath.join(basePath, filePath)),
86
+ extname: jest.fn((filePath) => actualPath.extname(filePath)),
87
+ join: actualPath.join,
88
+ };
89
+ });
90
+ jest.mock('../../src/cliState', () => ({
91
+ basePath: '/base/path',
92
+ }));
93
+ jest.mock('../../src/matchers', () => {
94
+ const actual = jest.requireActual('../../src/matchers');
95
+ return {
96
+ ...actual,
97
+ matchesContextRelevance: jest
98
+ .fn()
99
+ .mockResolvedValue({ pass: true, score: 1, reason: 'Mocked reason' }),
100
+ matchesContextFaithfulness: jest
101
+ .fn()
102
+ .mockResolvedValue({ pass: true, score: 1, reason: 'Mocked reason' }),
103
+ };
104
+ });
105
+ // Add this mock for packageParser
106
+ jest.mock('../../src/providers/packageParser', () => {
107
+ const mockIsPackagePath = jest.fn();
108
+ const mockLoadFromPackage = jest.fn();
109
+ return {
110
+ isPackagePath: mockIsPackagePath,
111
+ loadFromPackage: mockLoadFromPackage,
112
+ __esModule: true, // This is important for proper mocking
113
+ };
114
+ });
115
+ const javascriptStringAssertion = {
116
+ type: 'javascript',
117
+ value: 'output === "Expected output"',
118
+ };
119
+ const javascriptMultilineStringAssertion = {
120
+ type: 'javascript',
121
+ value: `
122
+ if (output === "Expected output") {
123
+ return {
124
+ pass: true,
125
+ score: 0.5,
126
+ reason: 'Assertion passed',
127
+ };
128
+ }
129
+ return {
130
+ pass: false,
131
+ score: 0,
132
+ reason: 'Assertion failed',
133
+ };`,
134
+ };
135
+ const javascriptStringAssertionWithNumber = {
136
+ type: 'javascript',
137
+ value: 'output.length * 10',
138
+ };
139
+ const javascriptBooleanAssertionWithConfig = {
140
+ type: 'javascript',
141
+ value: 'output.length <= context.config.maximumOutputSize',
142
+ config: {
143
+ maximumOutputSize: 20,
144
+ },
145
+ };
146
+ const javascriptStringAssertionWithNumberAndThreshold = {
147
+ type: 'javascript',
148
+ value: 'output.length * 10',
149
+ threshold: 0.5,
150
+ };
151
+ const javascriptFunctionAssertion = {
152
+ type: 'javascript',
153
+ value: async (output) => ({
154
+ pass: true,
155
+ score: 0.5,
156
+ reason: 'Assertion passed',
157
+ assertion: null,
158
+ }),
159
+ };
160
+ const javascriptFunctionFailAssertion = {
161
+ type: 'javascript',
162
+ value: async (output) => ({
163
+ pass: false,
164
+ score: 0.5,
165
+ reason: 'Assertion failed',
166
+ assertion: null,
167
+ }),
168
+ };
169
+ describe('JavaScript file references', () => {
170
+ beforeEach(() => {
171
+ jest.clearAllMocks();
172
+ // Reset all mocks before each test
173
+ jest.mocked(esm_1.importModule).mockReset();
174
+ jest.mocked(path.resolve).mockReset();
175
+ jest.mocked(packageParser_1.isPackagePath).mockReset();
176
+ jest.mocked(packageParser_1.loadFromPackage).mockReset();
177
+ });
178
+ it('should handle JavaScript file reference with function name', async () => {
179
+ const assertion = {
180
+ type: 'javascript',
181
+ value: 'file:///path/to/assert.js:customFunction',
182
+ };
183
+ const mockFn = jest.fn((output) => true);
184
+ jest.mocked(path.resolve).mockReturnValue('/path/to/assert.js');
185
+ jest.mocked(path.extname).mockReturnValue('.js');
186
+ jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
187
+ // Mock importModule to return the mock function
188
+ jest.mocked(esm_1.importModule).mockImplementationOnce((path, functionName) => {
189
+ return Promise.resolve({
190
+ customFunction: mockFn,
191
+ });
192
+ });
193
+ const output = 'Expected output';
194
+ const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
195
+ const providerResponse = { output };
196
+ const result = await (0, assertions_1.runAssertion)({
197
+ prompt: 'Some prompt',
198
+ provider,
199
+ assertion,
200
+ test: {},
201
+ providerResponse,
202
+ });
203
+ // Verify the mock was called with both parameters
204
+ expect(esm_1.importModule).toHaveBeenCalledWith('/path/to/assert.js', 'customFunction');
205
+ expect(mockFn).toHaveBeenCalledWith(output, {
206
+ prompt: 'Some prompt',
207
+ vars: {},
208
+ test: {},
209
+ provider,
210
+ providerResponse,
211
+ });
212
+ expect(result).toMatchObject({
213
+ pass: true,
214
+ reason: 'Assertion passed',
215
+ });
216
+ });
217
+ it('should handle default export when no function name specified', async () => {
218
+ const assertion = {
219
+ type: 'javascript',
220
+ value: 'file:///path/to/assert.js',
221
+ };
222
+ const mockFn = jest.fn((output) => true);
223
+ jest.mocked(path.resolve).mockReturnValue('/path/to/assert.js');
224
+ jest.mocked(path.extname).mockReturnValue('.js');
225
+ jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
226
+ // Mock importModule to return the mock function
227
+ jest.mocked(esm_1.importModule).mockImplementationOnce((path, functionName) => {
228
+ return Promise.resolve(mockFn);
229
+ });
230
+ const output = 'Expected output';
231
+ const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
232
+ const providerResponse = { output };
233
+ const result = await (0, assertions_1.runAssertion)({
234
+ prompt: 'Some prompt',
235
+ provider,
236
+ assertion,
237
+ test: {},
238
+ providerResponse,
239
+ });
240
+ expect(esm_1.importModule).toHaveBeenCalledWith('/path/to/assert.js', undefined);
241
+ expect(mockFn).toHaveBeenCalledWith(output, {
242
+ prompt: 'Some prompt',
243
+ vars: {},
244
+ test: {},
245
+ provider,
246
+ providerResponse,
247
+ });
248
+ expect(result).toMatchObject({
249
+ pass: true,
250
+ reason: 'Assertion passed',
251
+ });
252
+ });
253
+ it('should handle default export object with function', async () => {
254
+ const assertion = {
255
+ type: 'javascript',
256
+ value: 'file:///path/to/assert.js',
257
+ };
258
+ const mockFn = jest.fn((output) => true);
259
+ jest.mocked(path.resolve).mockReturnValue('/path/to/assert.js');
260
+ jest.mocked(path.extname).mockReturnValue('.js');
261
+ jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
262
+ // Mock importModule to handle both parameters
263
+ const mockImportModule = jest.mocked(esm_1.importModule);
264
+ mockImportModule.mockImplementationOnce((path, functionName) => {
265
+ // Return the mock function in a default export object
266
+ return Promise.resolve({ default: mockFn });
267
+ });
268
+ const output = 'Expected output';
269
+ const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
270
+ const providerResponse = { output };
271
+ const result = await (0, assertions_1.runAssertion)({
272
+ prompt: 'Some prompt',
273
+ provider,
274
+ assertion,
275
+ test: {},
276
+ providerResponse,
277
+ });
278
+ expect(esm_1.importModule).toHaveBeenCalledWith('/path/to/assert.js', undefined);
279
+ expect(mockFn).toHaveBeenCalledWith(output, {
280
+ prompt: 'Some prompt',
281
+ vars: {},
282
+ test: {},
283
+ provider,
284
+ providerResponse,
285
+ });
286
+ expect(result).toMatchObject({
287
+ pass: true,
288
+ reason: 'Assertion passed',
289
+ });
290
+ });
291
+ it('should pass when the javascript assertion passes', async () => {
292
+ const output = 'Expected output';
293
+ const result = await (0, assertions_1.runAssertion)({
294
+ prompt: 'Some prompt',
295
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
296
+ assertion: javascriptStringAssertion,
297
+ test: {},
298
+ providerResponse: { output },
299
+ });
300
+ expect(result).toMatchObject({
301
+ pass: true,
302
+ reason: 'Assertion passed',
303
+ });
304
+ });
305
+ it('should pass a score through when the javascript returns a number', async () => {
306
+ const output = 'Expected output';
307
+ const result = await (0, assertions_1.runAssertion)({
308
+ prompt: 'Some prompt',
309
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
310
+ assertion: javascriptStringAssertionWithNumber,
311
+ test: {},
312
+ providerResponse: { output },
313
+ });
314
+ expect(result).toMatchObject({
315
+ pass: true,
316
+ score: output.length * 10,
317
+ reason: 'Assertion passed',
318
+ });
319
+ });
320
+ it('should pass when javascript returns an output string that is smaller than the maximum size threshold', async () => {
321
+ const output = 'Expected output';
322
+ const result = await (0, assertions_1.runAssertion)({
323
+ prompt: 'Some prompt',
324
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
325
+ assertion: javascriptBooleanAssertionWithConfig,
326
+ test: {},
327
+ providerResponse: { output },
328
+ });
329
+ expect(result).toMatchObject({
330
+ pass: true,
331
+ score: 1.0,
332
+ reason: 'Assertion passed',
333
+ });
334
+ });
335
+ it('should fail when javascript returns an output string that is larger than the maximum size threshold', async () => {
336
+ const output = 'Expected output with some extra characters';
337
+ const result = await (0, assertions_1.runAssertion)({
338
+ prompt: 'Some prompt',
339
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
340
+ assertion: javascriptBooleanAssertionWithConfig,
341
+ test: {},
342
+ providerResponse: { output },
343
+ });
344
+ expect(result).toMatchObject({
345
+ pass: false,
346
+ score: 0,
347
+ reason: expect.stringContaining('Custom function returned false'),
348
+ });
349
+ });
350
+ it('should pass when javascript returns a number above threshold', async () => {
351
+ const output = 'Expected output';
352
+ const result = await (0, assertions_1.runAssertion)({
353
+ prompt: 'Some prompt',
354
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
355
+ assertion: javascriptStringAssertionWithNumberAndThreshold,
356
+ test: {},
357
+ providerResponse: { output },
358
+ });
359
+ expect(result).toMatchObject({
360
+ pass: true,
361
+ score: output.length * 10,
362
+ reason: 'Assertion passed',
363
+ });
364
+ });
365
+ it('should fail when javascript returns a number below threshold', async () => {
366
+ const output = '';
367
+ const result = await (0, assertions_1.runAssertion)({
368
+ prompt: 'Some prompt',
369
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
370
+ assertion: javascriptStringAssertionWithNumberAndThreshold,
371
+ test: {},
372
+ providerResponse: { output },
373
+ });
374
+ expect(result).toMatchObject({
375
+ pass: false,
376
+ score: output.length * 10,
377
+ reason: expect.stringContaining('Custom function returned false'),
378
+ });
379
+ });
380
+ it('should set score when javascript returns false', async () => {
381
+ const output = 'Test output';
382
+ const assertion = {
383
+ type: 'javascript',
384
+ value: 'output.length < 1',
385
+ };
386
+ const result = await (0, assertions_1.runAssertion)({
387
+ prompt: 'Some prompt',
388
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
389
+ assertion,
390
+ test: {},
391
+ providerResponse: { output },
392
+ });
393
+ expect(result).toMatchObject({
394
+ pass: false,
395
+ score: 0,
396
+ reason: expect.stringContaining('Custom function returned false'),
397
+ });
398
+ });
399
+ it('should fail when the javascript assertion fails', async () => {
400
+ const output = 'Different output';
401
+ const result = await (0, assertions_1.runAssertion)({
402
+ prompt: 'Some prompt',
403
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
404
+ assertion: javascriptStringAssertion,
405
+ test: {},
406
+ providerResponse: { output },
407
+ });
408
+ expect(result).toMatchObject({
409
+ pass: false,
410
+ reason: 'Custom function returned false\noutput === "Expected output"',
411
+ });
412
+ });
413
+ it('should pass when javascript function assertion passes - with vars', async () => {
414
+ const output = 'Expected output';
415
+ const javascriptStringAssertionWithVars = {
416
+ type: 'javascript',
417
+ value: 'output === "Expected output" && context.vars.foo === "bar"',
418
+ };
419
+ const result = await (0, assertions_1.runAssertion)({
420
+ prompt: 'Some prompt',
421
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
422
+ assertion: javascriptStringAssertionWithVars,
423
+ test: { vars: { foo: 'bar' } },
424
+ providerResponse: { output },
425
+ });
426
+ expect(result).toMatchObject({
427
+ pass: true,
428
+ reason: 'Assertion passed',
429
+ });
430
+ });
431
+ it('should fail when the javascript does not match vars', async () => {
432
+ const output = 'Expected output';
433
+ const javascriptStringAssertionWithVars = {
434
+ type: 'javascript',
435
+ value: 'output === "Expected output" && context.vars.foo === "something else"',
436
+ };
437
+ const result = await (0, assertions_1.runAssertion)({
438
+ prompt: 'Some prompt',
439
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
440
+ assertion: javascriptStringAssertionWithVars,
441
+ test: { vars: { foo: 'bar' } },
442
+ providerResponse: { output },
443
+ });
444
+ expect(result).toMatchObject({
445
+ pass: false,
446
+ reason: 'Custom function returned false\noutput === "Expected output" && context.vars.foo === "something else"',
447
+ });
448
+ });
449
+ it('should pass when the function returns pass', async () => {
450
+ const output = 'Expected output';
451
+ const result = await (0, assertions_1.runAssertion)({
452
+ prompt: 'Some prompt',
453
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
454
+ assertion: javascriptFunctionAssertion,
455
+ test: {},
456
+ providerResponse: { output },
457
+ });
458
+ expect(result).toMatchObject({
459
+ pass: true,
460
+ score: 0.5,
461
+ reason: 'Assertion passed',
462
+ });
463
+ });
464
+ it('should fail when the function returns fail', async () => {
465
+ const output = 'Expected output';
466
+ const result = await (0, assertions_1.runAssertion)({
467
+ prompt: 'Some prompt',
468
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
469
+ assertion: javascriptFunctionFailAssertion,
470
+ test: {},
471
+ providerResponse: { output },
472
+ });
473
+ expect(result).toMatchObject({
474
+ pass: false,
475
+ score: 0.5,
476
+ reason: 'Assertion failed',
477
+ });
478
+ });
479
+ it('should pass when the multiline javascript assertion passes', async () => {
480
+ const output = 'Expected output';
481
+ const result = await (0, assertions_1.runAssertion)({
482
+ prompt: 'Some prompt',
483
+ assertion: javascriptMultilineStringAssertion,
484
+ test: {},
485
+ providerResponse: { output },
486
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
487
+ });
488
+ expect(result).toMatchObject({
489
+ pass: true,
490
+ reason: 'Assertion passed',
491
+ });
492
+ });
493
+ it('should pass when the multiline javascript assertion fails', async () => {
494
+ const output = 'Not the expected output';
495
+ const result = await (0, assertions_1.runAssertion)({
496
+ prompt: 'Some prompt',
497
+ assertion: javascriptMultilineStringAssertion,
498
+ test: {},
499
+ providerResponse: { output },
500
+ provider: new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini'),
501
+ });
502
+ expect(result).toMatchObject({
503
+ pass: false,
504
+ reason: 'Assertion failed',
505
+ });
506
+ });
507
+ it.each([
508
+ [
509
+ 'boolean',
510
+ jest.fn((output) => output === 'Expected output'),
511
+ true,
512
+ 'Assertion passed',
513
+ ],
514
+ ['number', jest.fn((output) => output.length), true, 'Assertion passed'],
515
+ [
516
+ 'GradingResult',
517
+ jest.fn((output) => ({ pass: true, score: 1, reason: 'Custom reason' })),
518
+ true,
519
+ 'Custom reason',
520
+ ],
521
+ [
522
+ 'boolean',
523
+ jest.fn((output) => output !== 'Expected output'),
524
+ false,
525
+ 'Custom function returned false',
526
+ ],
527
+ ['number', jest.fn((output) => 0), false, 'Custom function returned false'],
528
+ [
529
+ 'GradingResult',
530
+ jest.fn((output) => ({ pass: false, score: 0.1, reason: 'Custom reason' })),
531
+ false,
532
+ 'Custom reason',
533
+ ],
534
+ [
535
+ 'boolean Promise',
536
+ jest.fn((output) => Promise.resolve(true)),
537
+ true,
538
+ 'Assertion passed',
539
+ ],
540
+ ])('should pass when the file:// assertion with .js file returns a %s', async (type, mockFn, expectedPass, expectedReason) => {
541
+ const output = 'Expected output';
542
+ // Mock path.resolve to return a valid path
543
+ jest.mocked(path.resolve).mockReturnValue('/mocked/path/to/assert.js');
544
+ jest.mocked(path.extname).mockReturnValue('.js');
545
+ // Mock isPackagePath to return false for file:// paths
546
+ jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
547
+ // Mock importModule to handle both path and functionName
548
+ const mockImportModule = jest.mocked(esm_1.importModule);
549
+ mockImportModule.mockImplementation((path, functionName) => {
550
+ // Make sure both parameters are captured in the mock
551
+ mockImportModule.mock.calls.push([path, functionName]);
552
+ return Promise.resolve(mockFn);
553
+ });
554
+ const fileAssertion = {
555
+ type: 'javascript',
556
+ value: 'file:///path/to/assert.js',
557
+ };
558
+ const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
559
+ const providerResponse = { output };
560
+ const result = await (0, assertions_1.runAssertion)({
561
+ prompt: 'Some prompt',
562
+ provider,
563
+ assertion: fileAssertion,
564
+ test: {},
565
+ providerResponse,
566
+ });
567
+ expect(mockFn).toHaveBeenCalledWith(output, {
568
+ prompt: 'Some prompt',
569
+ vars: {},
570
+ test: {},
571
+ provider,
572
+ providerResponse,
573
+ });
574
+ expect(result).toMatchObject({
575
+ pass: expectedPass,
576
+ reason: expect.stringContaining(expectedReason),
577
+ });
578
+ });
579
+ it.each([
580
+ [
581
+ 'boolean',
582
+ jest.fn((output) => output === 'Expected output'),
583
+ true,
584
+ 'Assertion passed',
585
+ ],
586
+ ['number', jest.fn((output) => output.length), true, 'Assertion passed'],
587
+ [
588
+ 'GradingResult',
589
+ jest.fn((output) => ({ pass: true, score: 1, reason: 'Custom reason' })),
590
+ true,
591
+ 'Custom reason',
592
+ ],
593
+ [
594
+ 'boolean',
595
+ jest.fn((output) => output !== 'Expected output'),
596
+ false,
597
+ 'Custom function returned false',
598
+ ],
599
+ ['number', jest.fn((output) => 0), false, 'Custom function returned false'],
600
+ [
601
+ 'GradingResult',
602
+ jest.fn((output) => ({ pass: false, score: 0.1, reason: 'Custom reason' })),
603
+ false,
604
+ 'Custom reason',
605
+ ],
606
+ [
607
+ 'boolean Promise',
608
+ jest.fn((output) => Promise.resolve(true)),
609
+ true,
610
+ 'Assertion passed',
611
+ ],
612
+ ])('should pass when assertion is a package path', async (type, mockFn, expectedPass, expectedReason) => {
613
+ const output = 'Expected output';
614
+ // Mock isPackagePath to return true for package paths
615
+ jest.mocked(packageParser_1.isPackagePath).mockReturnValue(true);
616
+ // Mock loadFromPackage to return the mockFn
617
+ jest.mocked(packageParser_1.loadFromPackage).mockResolvedValue(mockFn);
618
+ const packageAssertion = {
619
+ type: 'javascript',
620
+ value: 'package:@promptfoo/fake:assertionFunction',
621
+ };
622
+ const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
623
+ const providerResponse = { output };
624
+ const result = await (0, assertions_1.runAssertion)({
625
+ prompt: 'Some prompt',
626
+ provider,
627
+ assertion: packageAssertion,
628
+ test: {},
629
+ providerResponse,
630
+ });
631
+ expect(mockFn).toHaveBeenCalledWith(output, {
632
+ prompt: 'Some prompt',
633
+ vars: {},
634
+ test: {},
635
+ provider,
636
+ providerResponse,
637
+ });
638
+ expect(result).toMatchObject({
639
+ pass: expectedPass,
640
+ reason: expect.stringContaining(expectedReason),
641
+ });
642
+ });
643
+ it('should resolve js paths relative to the configuration file', async () => {
644
+ const output = 'Expected output';
645
+ const mockFn = jest.fn((output) => output === 'Expected output');
646
+ // Mock path.resolve to return a valid path
647
+ jest.mocked(path.resolve).mockReturnValue('/base/path/path/to/assert.js');
648
+ jest.mocked(path.extname).mockReturnValue('.js');
649
+ // Mock isPackagePath to return false
650
+ jest.mocked(packageParser_1.isPackagePath).mockReturnValue(false);
651
+ // Mock importModule to return the mockFn
652
+ jest.mocked(esm_1.importModule).mockResolvedValue(mockFn);
653
+ const fileAssertion = {
654
+ type: 'javascript',
655
+ value: 'file://./path/to/assert.js',
656
+ };
657
+ const provider = new openai_1.OpenAiChatCompletionProvider('gpt-4o-mini');
658
+ const providerResponse = { output };
659
+ const result = await (0, assertions_1.runAssertion)({
660
+ prompt: 'Some prompt',
661
+ provider,
662
+ assertion: fileAssertion,
663
+ test: {},
664
+ providerResponse,
665
+ });
666
+ expect(mockFn).toHaveBeenCalledWith(output, {
667
+ prompt: 'Some prompt',
668
+ vars: {},
669
+ test: {},
670
+ provider,
671
+ providerResponse,
672
+ });
673
+ expect(result).toMatchObject({
674
+ pass: true,
675
+ reason: 'Assertion passed',
676
+ });
677
+ });
678
+ });
679
+ //# sourceMappingURL=javascript.test.js.map