@langwatch/mcp-server 1.0.0 → 2.1.0
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.
- package/.python-version +1 -0
- package/CHANGELOG.md +110 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +86 -4
- package/dist/{archive-project-MK3673A7.js → archive-project-W3IKI7UL.js} +5 -5
- package/dist/archive-run-plan-474OKHHU.js +16 -0
- package/dist/{archive-scenario-KATSXRIN.js → archive-scenario-V5UNYK26.js} +5 -5
- package/dist/archive-test-suite-O7FLDMXK.js +16 -0
- package/dist/{assign-prompt-tag-E3C5W6PI.js → assign-prompt-tag-Q46WRB6P.js} +4 -4
- package/dist/chunk-56OGTS24.js +44 -0
- package/dist/chunk-6DJDEXET.js +63 -0
- package/dist/chunk-7E4FZIYJ.js +15 -0
- package/dist/{chunk-J45SVWPR.js → chunk-AN4OUA4D.js} +2 -2
- package/dist/chunk-CKNAH5TG.js +74 -0
- package/dist/{chunk-CE6QMZOP.js → chunk-EYXMLE5X.js} +86 -11
- package/dist/{chunk-XY4STOBE.js → chunk-G4ZAZTZW.js} +2 -2
- package/dist/{chunk-5UWY24RP.js → chunk-GELJ2MQR.js} +12 -3
- package/dist/chunk-HV4DWKBI.js +46 -0
- package/dist/chunk-JR6DJZOU.js +49 -0
- package/dist/{chunk-DAIQKS27.js → chunk-JTQJRNFJ.js} +2 -2
- package/dist/{chunk-WWMDHZZV.js → chunk-K37ZNPXK.js} +2 -2
- package/dist/{chunk-EU22L52K.js → chunk-OJLYYJWI.js} +2 -2
- package/dist/{chunk-Y3HDZ6OW.js → chunk-QAMP3CGR.js} +2 -2
- package/dist/{chunk-VYPSKRPN.js → chunk-QRO35SOM.js} +347 -146
- package/dist/{chunk-O3LWVN67.js → chunk-RVAE2MEA.js} +2 -2
- package/dist/{chunk-UXXA4D6K.js → chunk-S4GVX36W.js} +2 -2
- package/dist/{chunk-BJ7B4R26.js → chunk-SAS5OB7F.js} +18 -2
- package/dist/{chunk-RYRB2YVJ.js → chunk-SR6QPRIE.js} +2 -2
- package/dist/chunk-UF6XIGYR.js +58 -0
- package/dist/{chunk-T7WJVS2N.js → chunk-UFOYFYI7.js} +2 -2
- package/dist/chunk-UJPXFA26.js +267 -0
- package/dist/{chunk-KOO5733S.js → chunk-WT4PO4A3.js} +8 -2
- package/dist/config.js +2 -2
- package/dist/{create-agent-7W57J7MT.js → create-agent-JFJ2K6X3.js} +5 -5
- package/dist/{create-annotation-W2XELBCQ.js → create-annotation-EUXXQ6NS.js} +5 -5
- package/dist/{create-api-key-MKRXKFJM.js → create-api-key-BGBB76JX.js} +5 -5
- package/dist/{create-dashboard-QGL7IODC.js → create-dashboard-Z76AIPLS.js} +5 -5
- package/dist/{create-dataset-3PH7A4HG.js → create-dataset-FD5VAXU4.js} +5 -5
- package/dist/{create-dataset-records-4DZS3VRL.js → create-dataset-records-WQUXXOHI.js} +5 -5
- package/dist/{create-evaluator-YWH6NC4M.js → create-evaluator-6NV33Z52.js} +5 -5
- package/dist/create-mcp-server.js +9 -4
- package/dist/{create-project-BOBQ32C5.js → create-project-ZQ7TFIO2.js} +5 -5
- package/dist/{create-prompt-FLCFGITE.js → create-prompt-BS6QRCUU.js} +4 -4
- package/dist/{create-prompt-tag-EMPNIKEA.js → create-prompt-tag-FCQ5ELSQ.js} +4 -4
- package/dist/{create-scenario-BSBIHUL6.js → create-scenario-DRPTPXU3.js} +12 -5
- package/dist/create-test-suite-ZIZCCFJ4.js +37 -0
- package/dist/{delete-agent-7MSYNY5E.js → delete-agent-SA4JMUZF.js} +5 -5
- package/dist/{delete-annotation-ZT5IGN24.js → delete-annotation-X3KKK7QY.js} +5 -5
- package/dist/{delete-dashboard-BTIY5S3L.js → delete-dashboard-LYMRPF27.js} +5 -5
- package/dist/{delete-dataset-U45HQHPR.js → delete-dataset-W6EZ44HY.js} +5 -5
- package/dist/{delete-dataset-records-R43IMAWB.js → delete-dataset-records-7CNOTALB.js} +5 -5
- package/dist/{delete-evaluator-U6JMQ6C7.js → delete-evaluator-RKYC4TNB.js} +5 -5
- package/dist/{delete-prompt-tag-4EV4TDHB.js → delete-prompt-tag-QB3OCZUV.js} +4 -4
- package/dist/{delete-workflow-5QYALXEL.js → delete-workflow-NG2VXYYM.js} +5 -5
- package/dist/{discover-evaluator-schema-MRGHVNTT.js → discover-evaluator-schema-ESSHQGPY.js} +58 -321
- package/dist/{discover-scenario-schema-UPBTCWPG.js → discover-scenario-schema-W7KBXA4J.js} +54 -2
- package/dist/{discover-schema-ICGXPHUW.js → discover-schema-L6QDPON3.js} +2 -2
- package/dist/get-agent-7THF7HCU.js +66 -0
- package/dist/{get-analytics-PVERWOA5.js → get-analytics-R6AZD3AW.js} +4 -4
- package/dist/{get-dashboard-ZZPC3WMD.js → get-dashboard-P4WWVHFU.js} +5 -5
- package/dist/{get-dataset-25TB2LQH.js → get-dataset-AKTYSQDI.js} +5 -5
- package/dist/{get-evaluator-3FTRQKEC.js → get-evaluator-RZ44UIN6.js} +5 -5
- package/dist/{get-project-5OAXYC57.js → get-project-QSQEAB33.js} +5 -5
- package/dist/get-prompt-GVKXF5NL.js +100 -0
- package/dist/get-run-plan-NY7J5GRE.js +61 -0
- package/dist/{get-scenario-DSSTR6FI.js → get-scenario-Q3LLQE4T.js} +9 -5
- package/dist/{get-simulation-run-RQPMS2MC.js → get-simulation-run-WINEYDMK.js} +14 -5
- package/dist/get-test-suite-4HKA7OKS.js +49 -0
- package/dist/{get-trace-O5VT3NAC.js → get-trace-RCUV7N4O.js} +4 -4
- package/dist/{get-workflow-3EZDOEIF.js → get-workflow-EEOC7Y45.js} +5 -5
- package/dist/http-security-EU3CHTY7.js +26 -0
- package/dist/{http-server-2ACJ3CXW.js → http-server-WCDUARTH.js} +1038 -693
- package/dist/index.js +6437 -4020
- package/dist/{langwatch-api-annotations-E65MI3WZ.js → langwatch-api-annotations-6DHVZWUK.js} +5 -5
- package/dist/{langwatch-api-dashboards-QCDECRCY.js → langwatch-api-dashboards-2KJL2M42.js} +5 -5
- package/dist/{langwatch-api-monitors-UIO2KTDV.js → langwatch-api-monitors-6ISFJ7WX.js} +4 -4
- package/dist/{langwatch-api-secrets-4T2EKSJ7.js → langwatch-api-secrets-OEBD6GBU.js} +4 -4
- package/dist/{langwatch-api-triggers-MPOEKPUY.js → langwatch-api-triggers-5E6DDGX6.js} +4 -4
- package/dist/langwatch-api-workflows-6UJL5U4Z.js +16 -0
- package/dist/list-agents-AMVDQPQL.js +43 -0
- package/dist/{list-annotations-CGX5BT7E.js → list-annotations-6PR5NLIC.js} +5 -5
- package/dist/{list-api-keys-JLWJPFRX.js → list-api-keys-GVPNBIHQ.js} +5 -5
- package/dist/{list-dashboards-RNIWX3TX.js → list-dashboards-Z3BZKDGF.js} +5 -5
- package/dist/{list-dataset-records-B5MHZR66.js → list-dataset-records-O67HZ35Y.js} +5 -5
- package/dist/{list-datasets-BVCAMQCP.js → list-datasets-WXZL6UMP.js} +5 -5
- package/dist/{list-evaluators-65FBTL2U.js → list-evaluators-PAX72WCN.js} +5 -5
- package/dist/{list-model-providers-GRK3I3VV.js → list-model-providers-VVLZMLCX.js} +5 -5
- package/dist/{list-projects-JLMBFMW3.js → list-projects-AQVI6SUK.js} +5 -5
- package/dist/{list-prompt-tags-UHNWDGQI.js → list-prompt-tags-WZFBJOHG.js} +4 -4
- package/dist/{list-prompts-7254B3U7.js → list-prompts-BQOHRHYT.js} +4 -4
- package/dist/list-run-plans-UBAU2DGH.js +46 -0
- package/dist/{list-scenarios-46YNICKM.js → list-scenarios-MS4IUIVM.js} +10 -7
- package/dist/{list-simulation-runs-LGNEFNPN.js → list-simulation-runs-D6YF4ZOU.js} +13 -7
- package/dist/list-test-suites-L7R2O6DR.js +35 -0
- package/dist/{list-workflows-MLQDM6CP.js → list-workflows-6MV4XJHF.js} +5 -5
- package/dist/{rename-prompt-tag-RHQMNEQL.js → rename-prompt-tag-K5LQ4CY6.js} +4 -4
- package/dist/rename-test-suite-NTYHAO5X.js +21 -0
- package/dist/report-issue-RHZZ33IS.js +885 -0
- package/dist/rerun-run-plan-MUDWJBE4.js +22 -0
- package/dist/{revoke-api-key-3Y35EMBT.js → revoke-api-key-6LAZ3QZZ.js} +5 -5
- package/dist/run-agent-CAGWGTYT.js +72 -0
- package/dist/run-plan-T4ZYOZGA.js +39 -0
- package/dist/run-test-suite-L4MZ7G4D.js +26 -0
- package/dist/{run-workflow-5LVBZYLS.js → run-workflow-DFIS23AP.js} +5 -5
- package/dist/{search-traces-U4HLOCZ2.js → search-traces-4XTJYXIK.js} +4 -4
- package/dist/{set-model-provider-V3M5CDIY.js → set-model-provider-XRNMFOVR.js} +5 -5
- package/dist/{update-agent-47RZIHS7.js → update-agent-DUWNYBNA.js} +5 -5
- package/dist/{update-dataset-G6QJJGS5.js → update-dataset-YSOMDCOB.js} +5 -5
- package/dist/{update-dataset-record-RMWLSWCS.js → update-dataset-record-DEA2ITYR.js} +5 -5
- package/dist/{update-evaluator-SFRRI5Q3.js → update-evaluator-NH7Q5GWV.js} +5 -5
- package/dist/{update-project-PI5DWBVK.js → update-project-Q26FBSYH.js} +5 -5
- package/dist/update-prompt-ZQQ2C3KU.js +143 -0
- package/dist/{update-scenario-G3IU2UIB.js → update-scenario-WUX34JAR.js} +12 -5
- package/package.json +8 -9
- package/pyproject.toml +5 -5
- package/src/__tests__/agent-tools.unit.test.ts +257 -0
- package/src/__tests__/all-tools.integration.test.ts +321 -75
- package/src/__tests__/config.unit.test.ts +33 -0
- package/src/__tests__/docker.integration.test.ts +91 -7
- package/src/__tests__/documentation-fetch-security.integration.test.ts +8 -2
- package/src/__tests__/feature-map-tool-drift.unit.test.ts +93 -0
- package/src/__tests__/http-security-origin.unit.test.ts +97 -0
- package/src/__tests__/http-security-sessions.unit.test.ts +209 -0
- package/src/__tests__/http-security-verifier.unit.test.ts +248 -0
- package/src/__tests__/http-server-allocation.integration.test.ts +279 -0
- package/src/__tests__/http-server-origin.integration.test.ts +169 -0
- package/src/__tests__/http-server-session-auth.integration.test.ts +341 -0
- package/src/__tests__/http-transport.integration.test.ts +85 -87
- package/src/__tests__/integration.integration.test.ts +7 -11
- package/src/__tests__/prompt-response-types.unit.test.ts +51 -0
- package/src/__tests__/prompts.integration.test.ts +736 -0
- package/src/__tests__/run-plan-tools.unit.test.ts +565 -0
- package/src/__tests__/scenario-tools.unit.test.ts +19 -0
- package/src/__tests__/simulation-run-tools.unit.test.ts +116 -0
- package/src/__tests__/support/http-server-harness.ts +138 -0
- package/src/__tests__/test-suite-tools.unit.test.ts +615 -0
- package/src/__tests__/{tools.unit.test.ts → tools.integration.test.ts} +31 -59
- package/src/config.ts +17 -2
- package/src/create-mcp-server.ts +334 -55
- package/src/http-security.ts +492 -0
- package/src/http-server.ts +536 -152
- package/src/index.ts +49 -2
- package/src/langwatch-api-agents.ts +196 -12
- package/src/langwatch-api-run-plans.ts +157 -0
- package/src/langwatch-api-scenarios.ts +9 -0
- package/src/langwatch-api-simulation-runs.ts +5 -0
- package/src/langwatch-api-test-suites.ts +135 -0
- package/src/langwatch-api.ts +62 -12
- package/src/schemas/index.ts +5 -0
- package/src/schemas/run-plan.ts +92 -0
- package/src/schemas/suite-fields.ts +149 -0
- package/src/tools/archive-run-plan.ts +12 -0
- package/src/tools/archive-test-suite.ts +12 -0
- package/src/tools/create-scenario.ts +8 -0
- package/src/tools/create-test-suite.ts +43 -0
- package/src/tools/discover-evaluator-schema.ts +2 -2
- package/src/tools/discover-scenario-schema.ts +54 -0
- package/src/tools/format-run-plan.ts +68 -0
- package/src/tools/format-scenario.ts +14 -0
- package/src/tools/format-suite-details.ts +96 -0
- package/src/tools/get-agent.ts +45 -3
- package/src/tools/get-prompt.ts +108 -24
- package/src/tools/get-run-plan.ts +62 -0
- package/src/tools/get-scenario.ts +3 -0
- package/src/tools/get-simulation-run.ts +9 -0
- package/src/tools/get-test-suite.ts +49 -0
- package/src/tools/list-agents.ts +20 -3
- package/src/tools/list-run-plans.ts +47 -0
- package/src/tools/list-scenarios.ts +12 -2
- package/src/tools/list-simulation-runs.ts +6 -2
- package/src/tools/list-test-suites.ts +35 -0
- package/src/tools/rename-test-suite.ts +18 -0
- package/src/tools/rerun-run-plan.ts +21 -0
- package/src/tools/run-agent.ts +54 -6
- package/src/tools/run-plan.ts +49 -0
- package/src/tools/run-test-suite.ts +30 -0
- package/src/tools/test-agent.ts +18 -0
- package/src/tools/update-prompt.ts +195 -10
- package/src/tools/update-scenario.ts +8 -0
- package/src/tools/update-test-suite.ts +45 -0
- package/tsconfig.json +7 -2
- package/uv.lock +418 -298
- package/dist/archive-suite-C3KX6IIZ.js +0 -16
- package/dist/chunk-WDH5P5RF.js +0 -48
- package/dist/create-suite-TWF6BBR4.js +0 -39
- package/dist/get-agent-7QJVPH72.js +0 -29
- package/dist/get-prompt-I753LZKZ.js +0 -46
- package/dist/get-suite-GTOCY5PL.js +0 -45
- package/dist/langwatch-api-workflows-5HRZ7HR4.js +0 -16
- package/dist/list-agents-TIJTP566.js +0 -33
- package/dist/list-suites-5VK4HXEK.js +0 -43
- package/dist/report-issue-DXJ6TPTC.js +0 -412
- package/dist/run-agent-RBSIFKRT.js +0 -37
- package/dist/run-suite-K3DNQ3SE.js +0 -30
- package/dist/update-prompt-RQFTEO7Y.js +0 -25
- package/dist/update-suite-TRGGLNR7.js +0 -40
- package/pnpm-workspace.yaml +0 -34
- package/src/langwatch-api-suites.ts +0 -118
- package/src/tools/archive-suite.ts +0 -12
- package/src/tools/create-suite.ts +0 -42
- package/src/tools/get-suite.ts +0 -47
- package/src/tools/list-suites.ts +0 -43
- package/src/tools/run-suite.ts +0 -28
- package/src/tools/update-suite.ts +0 -44
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digests of the test suite tools, and the test suite fields the scenario
|
|
3
|
+
* tools carry so an agent can file a scenario in a suite and read a suite
|
|
4
|
+
* back.
|
|
5
|
+
*
|
|
6
|
+
* @see specs/mcp-server/test-suite-tools.feature
|
|
7
|
+
*/
|
|
8
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
|
|
10
|
+
vi.mock("../langwatch-api-test-suites.js", () => ({
|
|
11
|
+
listTestSuites: vi.fn(),
|
|
12
|
+
createTestSuite: vi.fn(),
|
|
13
|
+
getTestSuite: vi.fn(),
|
|
14
|
+
renameTestSuite: vi.fn(),
|
|
15
|
+
updateTestSuite: vi.fn(),
|
|
16
|
+
archiveTestSuite: vi.fn(),
|
|
17
|
+
runTestSuite: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock("../langwatch-api-scenarios.js", () => ({
|
|
21
|
+
listScenarios: vi.fn(),
|
|
22
|
+
createScenario: vi.fn(),
|
|
23
|
+
updateScenario: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
createScenario,
|
|
28
|
+
listScenarios,
|
|
29
|
+
updateScenario,
|
|
30
|
+
type ScenarioSummary,
|
|
31
|
+
} from "../langwatch-api-scenarios.js";
|
|
32
|
+
import type { RunPlanRunResult } from "../langwatch-api-run-plans.js";
|
|
33
|
+
import {
|
|
34
|
+
archiveTestSuite,
|
|
35
|
+
createTestSuite,
|
|
36
|
+
getTestSuite,
|
|
37
|
+
listTestSuites,
|
|
38
|
+
renameTestSuite,
|
|
39
|
+
runTestSuite,
|
|
40
|
+
updateTestSuite,
|
|
41
|
+
type TestSuite,
|
|
42
|
+
} from "../langwatch-api-test-suites.js";
|
|
43
|
+
|
|
44
|
+
import { handleArchiveTestSuite } from "../tools/archive-test-suite.js";
|
|
45
|
+
import { handleCreateScenario } from "../tools/create-scenario.js";
|
|
46
|
+
import { handleCreateTestSuite } from "../tools/create-test-suite.js";
|
|
47
|
+
import { handleGetTestSuite } from "../tools/get-test-suite.js";
|
|
48
|
+
import { handleListScenarios } from "../tools/list-scenarios.js";
|
|
49
|
+
import { handleListTestSuites } from "../tools/list-test-suites.js";
|
|
50
|
+
import { handleRenameTestSuite } from "../tools/rename-test-suite.js";
|
|
51
|
+
import { handleRunTestSuite } from "../tools/run-test-suite.js";
|
|
52
|
+
import { handleUpdateScenario } from "../tools/update-scenario.js";
|
|
53
|
+
import { handleUpdateTestSuite } from "../tools/update-test-suite.js";
|
|
54
|
+
|
|
55
|
+
const mockListTestSuites = vi.mocked(listTestSuites);
|
|
56
|
+
const mockCreateTestSuite = vi.mocked(createTestSuite);
|
|
57
|
+
const mockGetTestSuite = vi.mocked(getTestSuite);
|
|
58
|
+
const mockRenameTestSuite = vi.mocked(renameTestSuite);
|
|
59
|
+
const mockUpdateTestSuite = vi.mocked(updateTestSuite);
|
|
60
|
+
const mockArchiveTestSuite = vi.mocked(archiveTestSuite);
|
|
61
|
+
const mockRunTestSuite = vi.mocked(runTestSuite);
|
|
62
|
+
const mockListScenarios = vi.mocked(listScenarios);
|
|
63
|
+
const mockCreateScenario = vi.mocked(createScenario);
|
|
64
|
+
const mockUpdateScenario = vi.mocked(updateScenario);
|
|
65
|
+
|
|
66
|
+
const sampleSuite: TestSuite = {
|
|
67
|
+
id: "suite_abc123",
|
|
68
|
+
name: "Checkout",
|
|
69
|
+
slug: "checkout",
|
|
70
|
+
scenarioIds: ["scen_abc123", "scen_def456"],
|
|
71
|
+
scenarioCount: 2,
|
|
72
|
+
archivedAt: null,
|
|
73
|
+
createdAt: "2024-01-01T00:00:00Z",
|
|
74
|
+
updatedAt: "2024-01-02T00:00:00Z",
|
|
75
|
+
platformUrl:
|
|
76
|
+
"https://app.langwatch.ai/proj/agent-testing/suites/checkout",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const sampleScenario: ScenarioSummary = {
|
|
80
|
+
id: "scen_abc123",
|
|
81
|
+
name: "Card declined",
|
|
82
|
+
situation: "The card issuer declines the payment",
|
|
83
|
+
criteria: ["Offers another payment method"],
|
|
84
|
+
labels: ["checkout"],
|
|
85
|
+
testSuiteId: "suite_abc123",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
vi.clearAllMocks();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("handleListTestSuites()", () => {
|
|
93
|
+
describe("when test suites exist", () => {
|
|
94
|
+
let result: string;
|
|
95
|
+
|
|
96
|
+
beforeEach(async () => {
|
|
97
|
+
mockListTestSuites.mockResolvedValue([sampleSuite]);
|
|
98
|
+
result = await handleListTestSuites({});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/** @scenario "Agent lists the test suites of a project" */
|
|
102
|
+
it("lists each suite with the count of scenarios in it", () => {
|
|
103
|
+
expect(result).toContain("# Test Suites (1 total)");
|
|
104
|
+
expect(result).toContain("## Checkout");
|
|
105
|
+
expect(result).toContain("**Scenarios**: 2");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("includes the suite id", () => {
|
|
109
|
+
expect(result).toContain("**ID**: suite_abc123");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("when no test suites exist", () => {
|
|
114
|
+
let result: string;
|
|
115
|
+
|
|
116
|
+
beforeEach(async () => {
|
|
117
|
+
mockListTestSuites.mockResolvedValue([]);
|
|
118
|
+
result = await handleListTestSuites({});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/** @scenario "Agent lists test suites when none exist" */
|
|
122
|
+
it("returns a no-suites message", () => {
|
|
123
|
+
expect(result).toContain("No test suites found");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("includes a tip to use platform_create_test_suite", () => {
|
|
127
|
+
expect(result).toContain("platform_create_test_suite");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("when format is json", () => {
|
|
132
|
+
it("returns valid parseable JSON matching the suite structure", async () => {
|
|
133
|
+
mockListTestSuites.mockResolvedValue([sampleSuite]);
|
|
134
|
+
|
|
135
|
+
const result = await handleListTestSuites({ format: "json" });
|
|
136
|
+
|
|
137
|
+
expect(JSON.parse(result)).toEqual([sampleSuite]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("handleCreateTestSuite()", () => {
|
|
143
|
+
describe("when the suite is created", () => {
|
|
144
|
+
let result: string;
|
|
145
|
+
|
|
146
|
+
beforeEach(async () => {
|
|
147
|
+
mockCreateTestSuite.mockResolvedValue({
|
|
148
|
+
...sampleSuite,
|
|
149
|
+
scenarioIds: [],
|
|
150
|
+
scenarioCount: 0,
|
|
151
|
+
});
|
|
152
|
+
result = await handleCreateTestSuite({ name: "Checkout" });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
/** @scenario "Agent creates a test suite" */
|
|
156
|
+
it("confirms the suite was created and names its id", () => {
|
|
157
|
+
expect(result).toContain('Test suite "Checkout" created.');
|
|
158
|
+
expect(result).toContain("**ID**: suite_abc123");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("says to file scenarios in it with testSuiteId", () => {
|
|
162
|
+
expect(result).toContain("testSuiteId `suite_abc123`");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("handleGetTestSuite()", () => {
|
|
168
|
+
describe("when scenarios are filed in the suite", () => {
|
|
169
|
+
/** @scenario "Agent reads a test suite with the scenarios filed in it" */
|
|
170
|
+
it("lists the name and the id of each scenario", async () => {
|
|
171
|
+
mockGetTestSuite.mockResolvedValue({
|
|
172
|
+
...sampleSuite,
|
|
173
|
+
scenarios: [
|
|
174
|
+
{ id: "scen_abc123", name: "Card declined" },
|
|
175
|
+
{ id: "scen_def456", name: "Coupon expired" },
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = await handleGetTestSuite({ id: "suite_abc123" });
|
|
180
|
+
|
|
181
|
+
expect(result).toContain("- Card declined (scen_abc123)");
|
|
182
|
+
expect(result).toContain("- Coupon expired (scen_def456)");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("when no scenario is filed in the suite", () => {
|
|
187
|
+
/** @scenario "Agent reads a test suite with no scenarios filed in it" */
|
|
188
|
+
it("says none are filed yet", async () => {
|
|
189
|
+
mockGetTestSuite.mockResolvedValue({
|
|
190
|
+
...sampleSuite,
|
|
191
|
+
scenarioIds: [],
|
|
192
|
+
scenarioCount: 0,
|
|
193
|
+
scenarios: [],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const result = await handleGetTestSuite({ id: "suite_abc123" });
|
|
197
|
+
|
|
198
|
+
expect(result).toContain("None filed yet.");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("when format is json", () => {
|
|
203
|
+
it("returns valid parseable JSON matching the suite structure", async () => {
|
|
204
|
+
const detail = { ...sampleSuite, scenarios: [] };
|
|
205
|
+
mockGetTestSuite.mockResolvedValue(detail);
|
|
206
|
+
|
|
207
|
+
const result = await handleGetTestSuite({
|
|
208
|
+
id: "suite_abc123",
|
|
209
|
+
format: "json",
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(JSON.parse(result)).toEqual(detail);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("handleRenameTestSuite()", () => {
|
|
218
|
+
describe("when the suite is renamed", () => {
|
|
219
|
+
/** @scenario "Agent renames a test suite" */
|
|
220
|
+
it("confirms the new name", async () => {
|
|
221
|
+
mockRenameTestSuite.mockResolvedValue({
|
|
222
|
+
...sampleSuite,
|
|
223
|
+
name: "Checkout v2",
|
|
224
|
+
slug: "checkout-v2",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result = await handleRenameTestSuite({
|
|
228
|
+
id: "suite_abc123",
|
|
229
|
+
name: "Checkout v2",
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(result).toContain(
|
|
233
|
+
'Test suite suite_abc123 is now named "Checkout v2".',
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("handleArchiveTestSuite()", () => {
|
|
240
|
+
describe("when the suite is archived", () => {
|
|
241
|
+
let result: string;
|
|
242
|
+
|
|
243
|
+
beforeEach(async () => {
|
|
244
|
+
mockArchiveTestSuite.mockResolvedValue({
|
|
245
|
+
id: "suite_abc123",
|
|
246
|
+
archived: true,
|
|
247
|
+
});
|
|
248
|
+
result = await handleArchiveTestSuite({ id: "suite_abc123" });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
/** @scenario "Agent archives a test suite" */
|
|
252
|
+
it("confirms the suite is archived", () => {
|
|
253
|
+
expect(result).toContain("Test suite suite_abc123 is archived");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("says the scenarios filed in it are archived with it", () => {
|
|
257
|
+
expect(result).toContain(
|
|
258
|
+
"the scenarios filed in it are archived with it",
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("handleRunTestSuite()", () => {
|
|
265
|
+
describe("when the suite runs against one target", () => {
|
|
266
|
+
const run: RunPlanRunResult = {
|
|
267
|
+
scheduled: true,
|
|
268
|
+
batchRunId: "batch_123",
|
|
269
|
+
setId: "set_456",
|
|
270
|
+
jobCount: 2,
|
|
271
|
+
skippedArchived: { scenarios: [], targets: [] },
|
|
272
|
+
items: [],
|
|
273
|
+
runPlanId: "plan_abc123",
|
|
274
|
+
planName: "Checkout Support Bot",
|
|
275
|
+
created: true,
|
|
276
|
+
platformUrl:
|
|
277
|
+
"https://app.langwatch.ai/proj/agent-testing/results/regression-plan",
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
let result: string;
|
|
281
|
+
|
|
282
|
+
beforeEach(async () => {
|
|
283
|
+
mockRunTestSuite.mockResolvedValue(run);
|
|
284
|
+
result = await handleRunTestSuite({
|
|
285
|
+
id: "suite_abc123",
|
|
286
|
+
targets: [{ type: "http", referenceId: "agent_abc" }],
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
/** @scenario "Agent runs a test suite against a target" */
|
|
291
|
+
it("names the run plan the run created or joined", () => {
|
|
292
|
+
expect(result).toContain(
|
|
293
|
+
'Run plan "Checkout Support Bot" created and started.',
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("includes the batch run id and the job count", () => {
|
|
298
|
+
expect(result).toContain("**Batch Run ID**: batch_123");
|
|
299
|
+
expect(result).toContain("**Jobs**: 2");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("sends the suite id apart from the run body", () => {
|
|
303
|
+
expect(mockRunTestSuite).toHaveBeenCalledWith({
|
|
304
|
+
id: "suite_abc123",
|
|
305
|
+
targets: [{ type: "http", referenceId: "agent_abc" }],
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe("when two targets name the same agent with different parameters", () => {
|
|
311
|
+
/** @scenario "Agent runs a test suite against one agent on two models" */
|
|
312
|
+
it("sends both targets, each carrying its own runParameters", async () => {
|
|
313
|
+
await handleRunTestSuite({
|
|
314
|
+
id: "suite_abc123",
|
|
315
|
+
targets: [
|
|
316
|
+
{
|
|
317
|
+
type: "http",
|
|
318
|
+
referenceId: "agent_abc",
|
|
319
|
+
parameters: { model: "gpt-5" },
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
type: "http",
|
|
323
|
+
referenceId: "agent_abc",
|
|
324
|
+
parameters: { model: "gpt-5-mini" },
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(mockRunTestSuite).toHaveBeenLastCalledWith({
|
|
330
|
+
id: "suite_abc123",
|
|
331
|
+
targets: [
|
|
332
|
+
{
|
|
333
|
+
type: "http",
|
|
334
|
+
referenceId: "agent_abc",
|
|
335
|
+
runParameters: { model: "gpt-5" },
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
type: "http",
|
|
339
|
+
referenceId: "agent_abc",
|
|
340
|
+
runParameters: { model: "gpt-5-mini" },
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe("handleCreateScenario() with a testSuiteId", () => {
|
|
349
|
+
describe("when the scenario is filed in a test suite", () => {
|
|
350
|
+
/** @scenario "Agent files a new scenario in a test suite" */
|
|
351
|
+
it("says which test suite the scenario is filed in", async () => {
|
|
352
|
+
mockCreateScenario.mockResolvedValue(sampleScenario);
|
|
353
|
+
|
|
354
|
+
const result = await handleCreateScenario({
|
|
355
|
+
name: "Card declined",
|
|
356
|
+
situation: "The card issuer declines the payment",
|
|
357
|
+
testSuiteId: "suite_abc123",
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
expect(result).toContain("**Test suite**: suite_abc123");
|
|
361
|
+
expect(mockCreateScenario).toHaveBeenCalledWith(
|
|
362
|
+
expect.objectContaining({ testSuiteId: "suite_abc123" }),
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe("handleUpdateScenario() with a testSuiteId", () => {
|
|
369
|
+
describe("when an unfiled scenario is filed in a test suite", () => {
|
|
370
|
+
/** @scenario "Agent files an existing scenario in a test suite" */
|
|
371
|
+
it("says which test suite the scenario is filed in", async () => {
|
|
372
|
+
mockUpdateScenario.mockResolvedValue(sampleScenario);
|
|
373
|
+
|
|
374
|
+
const result = await handleUpdateScenario({
|
|
375
|
+
scenarioId: "scen_abc123",
|
|
376
|
+
testSuiteId: "suite_abc123",
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(result).toContain("**Test suite**: suite_abc123");
|
|
380
|
+
expect(mockUpdateScenario).toHaveBeenCalledWith({
|
|
381
|
+
id: "scen_abc123",
|
|
382
|
+
testSuiteId: "suite_abc123",
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe("handleListScenarios() with a testSuiteId", () => {
|
|
389
|
+
const otherScenario: ScenarioSummary = {
|
|
390
|
+
...sampleScenario,
|
|
391
|
+
id: "scen_zzz999",
|
|
392
|
+
name: "Login timeout",
|
|
393
|
+
testSuiteId: "suite_other",
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
describe("when the project has scenarios in two test suites", () => {
|
|
397
|
+
/** @scenario "Agent lists only the scenarios filed in a test suite" */
|
|
398
|
+
it("returns only the scenarios filed in that test suite", async () => {
|
|
399
|
+
mockListScenarios.mockResolvedValue([sampleScenario, otherScenario]);
|
|
400
|
+
|
|
401
|
+
const result = await handleListScenarios({ testSuiteId: "suite_abc123" });
|
|
402
|
+
|
|
403
|
+
expect(result).toContain("Card declined");
|
|
404
|
+
expect(result).not.toContain("Login timeout");
|
|
405
|
+
expect(result).toContain("# Scenarios (1 total)");
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe("when no scenario is filed in the requested test suite", () => {
|
|
410
|
+
/** @scenario "Agent lists the scenarios of an empty test suite" */
|
|
411
|
+
it("says no scenarios are filed in that test suite", async () => {
|
|
412
|
+
mockListScenarios.mockResolvedValue([otherScenario]);
|
|
413
|
+
|
|
414
|
+
const result = await handleListScenarios({ testSuiteId: "suite_abc123" });
|
|
415
|
+
|
|
416
|
+
expect(result).toContain(
|
|
417
|
+
"No scenarios found in test suite suite_abc123.",
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe("when no testSuiteId is given", () => {
|
|
423
|
+
it("lists every scenario of the project", async () => {
|
|
424
|
+
mockListScenarios.mockResolvedValue([sampleScenario, otherScenario]);
|
|
425
|
+
|
|
426
|
+
const result = await handleListScenarios({});
|
|
427
|
+
|
|
428
|
+
expect(result).toContain("# Scenarios (2 total)");
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const goldenSqlField = { identifier: "golden_sql", type: "text" as const };
|
|
434
|
+
|
|
435
|
+
const sqlAttachment = {
|
|
436
|
+
id: "att_sql",
|
|
437
|
+
evaluatorId: "evaluator_sql",
|
|
438
|
+
required: true,
|
|
439
|
+
mappings: {
|
|
440
|
+
output: {
|
|
441
|
+
type: "source" as const,
|
|
442
|
+
sourceId: "trace" as const,
|
|
443
|
+
path: ["tool_calls", "run_sql", "input"],
|
|
444
|
+
},
|
|
445
|
+
expected_output: {
|
|
446
|
+
type: "source" as const,
|
|
447
|
+
sourceId: "scenario" as const,
|
|
448
|
+
path: ["fields", "golden_sql"],
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
describe("handleCreateTestSuite()", () => {
|
|
454
|
+
beforeEach(() => {
|
|
455
|
+
vi.clearAllMocks();
|
|
456
|
+
mockCreateTestSuite.mockResolvedValue({
|
|
457
|
+
...sampleSuite,
|
|
458
|
+
fields: [goldenSqlField],
|
|
459
|
+
evaluators: [sqlAttachment],
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe("when fields and evaluators are given", () => {
|
|
464
|
+
/** @scenario "Agent creates a test suite with fields and evaluators" */
|
|
465
|
+
it("sends the fields as given and the attachments with an id and a gate settled", async () => {
|
|
466
|
+
await handleCreateTestSuite({
|
|
467
|
+
name: "Case lookups",
|
|
468
|
+
fields: [goldenSqlField],
|
|
469
|
+
evaluators: [{ evaluatorId: "evaluator_sql", mappings: sqlAttachment.mappings }],
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(mockCreateTestSuite).toHaveBeenCalledWith({
|
|
473
|
+
name: "Case lookups",
|
|
474
|
+
fields: [goldenSqlField],
|
|
475
|
+
evaluators: [
|
|
476
|
+
{
|
|
477
|
+
id: expect.stringMatching(/^att_/),
|
|
478
|
+
evaluatorId: "evaluator_sql",
|
|
479
|
+
required: true,
|
|
480
|
+
mappings: sqlAttachment.mappings,
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
/** @scenario "Agent creates a test suite with fields and evaluators" */
|
|
487
|
+
it("lists the fields and the evaluators with their mappings in the digest", async () => {
|
|
488
|
+
const result = await handleCreateTestSuite({
|
|
489
|
+
name: "Case lookups",
|
|
490
|
+
fields: [goldenSqlField],
|
|
491
|
+
evaluators: [
|
|
492
|
+
{ evaluatorId: "evaluator_sql", mappings: sqlAttachment.mappings },
|
|
493
|
+
],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
expect(result).toContain("- golden_sql (text)");
|
|
497
|
+
expect(result).toContain("evaluator_sql (required, attachment att_sql)");
|
|
498
|
+
expect(result).toContain("expected_output: scenario.fields.golden_sql");
|
|
499
|
+
expect(result).toContain("output: trace.tool_calls.run_sql.input");
|
|
500
|
+
expect(result).toContain("a value per field under `fields`");
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe("when neither fields nor evaluators are given", () => {
|
|
505
|
+
it("sends neither key when the agent gave neither", async () => {
|
|
506
|
+
await handleCreateTestSuite({ name: "Checkout" });
|
|
507
|
+
|
|
508
|
+
expect(mockCreateTestSuite).toHaveBeenCalledWith({ name: "Checkout" });
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
describe("handleUpdateTestSuite()", () => {
|
|
514
|
+
beforeEach(() => {
|
|
515
|
+
vi.clearAllMocks();
|
|
516
|
+
mockUpdateTestSuite.mockResolvedValue({
|
|
517
|
+
...sampleSuite,
|
|
518
|
+
name: "Case lookups v2",
|
|
519
|
+
fields: [goldenSqlField],
|
|
520
|
+
evaluators: [{ ...sqlAttachment, required: false }],
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
describe("when only fields are given", () => {
|
|
525
|
+
/** @scenario "Agent updates the fields of a test suite" */
|
|
526
|
+
it("sends the field list alone when only fields are given", async () => {
|
|
527
|
+
await handleUpdateTestSuite({ id: "suite_abc123", fields: [goldenSqlField] });
|
|
528
|
+
|
|
529
|
+
expect(mockUpdateTestSuite).toHaveBeenCalledWith({
|
|
530
|
+
id: "suite_abc123",
|
|
531
|
+
fields: [goldenSqlField],
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe("when evaluators are given", () => {
|
|
537
|
+
/** @scenario "Agent updates the evaluators of a test suite" */
|
|
538
|
+
it("sends the attachments with the gate the agent set", async () => {
|
|
539
|
+
await handleUpdateTestSuite({
|
|
540
|
+
id: "suite_abc123",
|
|
541
|
+
evaluators: [
|
|
542
|
+
{ id: "att_sql", evaluatorId: "evaluator_sql", required: false, mappings: sqlAttachment.mappings },
|
|
543
|
+
],
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
expect(mockUpdateTestSuite).toHaveBeenCalledWith({
|
|
547
|
+
id: "suite_abc123",
|
|
548
|
+
evaluators: [{ ...sqlAttachment, required: false }],
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe("when the suite already carries fields and evaluators", () => {
|
|
554
|
+
/** @scenario "Agent updates the evaluators of a test suite" */
|
|
555
|
+
it("confirms the new state, with each evaluator's gate and mappings", async () => {
|
|
556
|
+
const result = await handleUpdateTestSuite({ id: "suite_abc123", name: "Case lookups v2" });
|
|
557
|
+
|
|
558
|
+
expect(mockUpdateTestSuite).toHaveBeenCalledWith({ id: "suite_abc123", name: "Case lookups v2" });
|
|
559
|
+
expect(result).toContain('Test suite "Case lookups v2" updated.');
|
|
560
|
+
expect(result).toContain("- golden_sql (text)");
|
|
561
|
+
expect(result).toContain("evaluator_sql (reports only, attachment att_sql)");
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
describe("handleGetTestSuite()", () => {
|
|
567
|
+
describe("when the suite declares fields and evaluators", () => {
|
|
568
|
+
/** @scenario "Agent reads a test suite that declares fields and evaluators" */
|
|
569
|
+
it("lists the fields and the evaluators before the scenarios", async () => {
|
|
570
|
+
mockGetTestSuite.mockResolvedValue({
|
|
571
|
+
...sampleSuite,
|
|
572
|
+
scenarios: [],
|
|
573
|
+
fields: [goldenSqlField],
|
|
574
|
+
evaluators: [sqlAttachment],
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const result = await handleGetTestSuite({ id: "suite_abc123" });
|
|
578
|
+
|
|
579
|
+
expect(result).toContain("## Fields");
|
|
580
|
+
expect(result).toContain("- golden_sql (text)");
|
|
581
|
+
expect(result).toContain("## Evaluators");
|
|
582
|
+
expect(result).toContain("expected_output: scenario.fields.golden_sql");
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
describe("handleCreateScenario()", () => {
|
|
588
|
+
describe("when field values are given", () => {
|
|
589
|
+
/** @scenario "Agent files a scenario with values for the suite's fields" */
|
|
590
|
+
it("sends the values under fields and echoes them", async () => {
|
|
591
|
+
mockCreateScenario.mockResolvedValue({
|
|
592
|
+
id: "scen_new",
|
|
593
|
+
name: "Chargebacks by quarter",
|
|
594
|
+
situation: "A fraud analyst asks for chargebacks per quarter",
|
|
595
|
+
criteria: [],
|
|
596
|
+
labels: [],
|
|
597
|
+
testSuiteId: "suite_abc123",
|
|
598
|
+
fields: { golden_sql: "SELECT 1", row_limit: 10 },
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const result = await handleCreateScenario({
|
|
602
|
+
name: "Chargebacks by quarter",
|
|
603
|
+
situation: "A fraud analyst asks for chargebacks per quarter",
|
|
604
|
+
testSuiteId: "suite_abc123",
|
|
605
|
+
fields: { golden_sql: "SELECT 1", row_limit: 10 },
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
expect(mockCreateScenario).toHaveBeenCalledWith(
|
|
609
|
+
expect.objectContaining({ fields: { golden_sql: "SELECT 1", row_limit: 10 } }),
|
|
610
|
+
);
|
|
611
|
+
expect(result).toContain('- golden_sql: "SELECT 1"');
|
|
612
|
+
expect(result).toContain("- row_limit: 10");
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
});
|