Skip to content

Commit 6227301

Browse files
committed
test: add smoke tests for piping between .md agent files
Tests stdin/stdout piping mechanism using MA_COMMAND=echo to simulate LLM responses without actual API calls. Covers: - Single agent stdin handling with tag wrapping - Two-stage pipeline (agent1 | agent2) - Three-stage pipeline (agent1 | agent2 | agent3) - Template vars in piped context - Frontmatter flags passed correctly - Empty stdin handling - Multiline stdin preservation
1 parent dc32aab commit 6227301

1 file changed

Lines changed: 217 additions & 0 deletions

File tree

src/smoke-pipe.test.ts

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { expect, test, describe, beforeAll, afterAll } from "bun:test";
2+
import { spawn } from "bun";
3+
import { join } from "path";
4+
import { tmpdir } from "os";
5+
import { mkdir, writeFile, rm } from "fs/promises";
6+
7+
/**
8+
* Smoke tests for piping between .md agent files.
9+
* Uses MA_COMMAND=echo to simulate LLM responses without actual API calls.
10+
* These tests verify the stdin/stdout piping mechanism works correctly.
11+
*/
12+
13+
describe("smoke: pipe between agents", () => {
14+
const testDir = join(tmpdir(), `ma-smoke-pipe-${Date.now()}`);
15+
const indexPath = join(process.cwd(), "src/index.ts");
16+
17+
beforeAll(async () => {
18+
await mkdir(testDir, { recursive: true });
19+
});
20+
21+
afterAll(async () => {
22+
await rm(testDir, { recursive: true, force: true });
23+
});
24+
25+
test("stdin is passed to agent and wrapped in tags", async () => {
26+
// Agent that just echoes its body (which includes stdin)
27+
const agentFile = join(testDir, "echo-stdin.echo.md");
28+
await writeFile(agentFile, `---
29+
---
30+
Process this input:
31+
`);
32+
33+
const proc = spawn({
34+
cmd: ["bash", "-c", `echo "hello world" | bun run ${indexPath} ${agentFile}`],
35+
stdout: "pipe",
36+
stderr: "pipe",
37+
env: { ...process.env, MA_COMMAND: "echo" },
38+
});
39+
40+
const output = await new Response(proc.stdout).text();
41+
const exitCode = await proc.exited;
42+
43+
expect(exitCode).toBe(0);
44+
expect(output).toContain("Process this input:");
45+
expect(output).toContain("<stdin>");
46+
expect(output).toContain("hello world");
47+
expect(output).toContain("</stdin>");
48+
});
49+
50+
test("pipe: agent1 | agent2 (two-stage pipeline)", async () => {
51+
// Stage 1: Transforms input to structured output
52+
const agent1 = join(testDir, "stage1.echo.md");
53+
await writeFile(agent1, `---
54+
---
55+
STAGE1_OUTPUT: processed
56+
`);
57+
58+
// Stage 2: Receives stage 1 output
59+
const agent2 = join(testDir, "stage2.echo.md");
60+
await writeFile(agent2, `---
61+
---
62+
STAGE2_RECEIVED:
63+
`);
64+
65+
const proc = spawn({
66+
cmd: ["bash", "-c", `echo "initial" | bun run ${indexPath} ${agent1} | bun run ${indexPath} ${agent2}`],
67+
stdout: "pipe",
68+
stderr: "pipe",
69+
env: { ...process.env, MA_COMMAND: "echo" },
70+
});
71+
72+
const output = await new Response(proc.stdout).text();
73+
const exitCode = await proc.exited;
74+
75+
expect(exitCode).toBe(0);
76+
// Stage 2 output should contain its body
77+
expect(output).toContain("STAGE2_RECEIVED:");
78+
// Stage 2 should have received Stage 1's output in stdin
79+
expect(output).toContain("<stdin>");
80+
expect(output).toContain("STAGE1_OUTPUT: processed");
81+
});
82+
83+
test("pipe: agent1 | agent2 | agent3 (three-stage pipeline)", async () => {
84+
const agent1 = join(testDir, "three-stage1.echo.md");
85+
await writeFile(agent1, `---
86+
---
87+
[STEP1]
88+
`);
89+
90+
const agent2 = join(testDir, "three-stage2.echo.md");
91+
await writeFile(agent2, `---
92+
---
93+
[STEP2]
94+
`);
95+
96+
const agent3 = join(testDir, "three-stage3.echo.md");
97+
await writeFile(agent3, `---
98+
---
99+
[STEP3_FINAL]
100+
`);
101+
102+
const proc = spawn({
103+
cmd: ["bash", "-c", `echo "start" | bun run ${indexPath} ${agent1} | bun run ${indexPath} ${agent2} | bun run ${indexPath} ${agent3}`],
104+
stdout: "pipe",
105+
stderr: "pipe",
106+
env: { ...process.env, MA_COMMAND: "echo" },
107+
});
108+
109+
const output = await new Response(proc.stdout).text();
110+
const exitCode = await proc.exited;
111+
112+
expect(exitCode).toBe(0);
113+
// Final output is from stage 3
114+
expect(output).toContain("[STEP3_FINAL]");
115+
// Stage 3 received stage 2's output (which included stage 1's output)
116+
expect(output).toContain("[STEP2]");
117+
// The chain preserved earlier outputs in stdin
118+
expect(output).toContain("[STEP1]");
119+
});
120+
121+
test("template vars work in piped context", async () => {
122+
const agent = join(testDir, "template-pipe.echo.md");
123+
await writeFile(agent, `---
124+
args: [name]
125+
---
126+
Hello {{ name }}!
127+
`);
128+
129+
const proc = spawn({
130+
cmd: ["bash", "-c", `echo "context" | bun run ${indexPath} ${agent} "World"`],
131+
stdout: "pipe",
132+
stderr: "pipe",
133+
env: { ...process.env, MA_COMMAND: "echo" },
134+
});
135+
136+
const output = await new Response(proc.stdout).text();
137+
const exitCode = await proc.exited;
138+
139+
expect(exitCode).toBe(0);
140+
expect(output).toContain("Hello World!");
141+
expect(output).toContain("<stdin>");
142+
});
143+
144+
test("frontmatter flags are passed correctly in pipe", async () => {
145+
const agent = join(testDir, "flags-pipe.echo.md");
146+
await writeFile(agent, `---
147+
model: test-model
148+
verbose: true
149+
---
150+
Body content
151+
`);
152+
153+
// Use a wrapper to capture what args echo receives
154+
const proc = spawn({
155+
cmd: ["bash", "-c", `echo "input" | bun run ${indexPath} ${agent} --dry-run 2>&1`],
156+
stdout: "pipe",
157+
stderr: "pipe",
158+
env: { ...process.env },
159+
});
160+
161+
const output = await new Response(proc.stdout).text();
162+
const exitCode = await proc.exited;
163+
164+
expect(exitCode).toBe(0);
165+
// Dry run shows the command that would be executed
166+
expect(output).toContain("--model");
167+
expect(output).toContain("test-model");
168+
});
169+
170+
test("empty stdin is handled gracefully", async () => {
171+
const agent = join(testDir, "empty-stdin.echo.md");
172+
await writeFile(agent, `---
173+
---
174+
No stdin expected
175+
`);
176+
177+
const proc = spawn({
178+
cmd: ["bash", "-c", `bun run ${indexPath} ${agent}`],
179+
stdout: "pipe",
180+
stderr: "pipe",
181+
env: { ...process.env, MA_COMMAND: "echo" },
182+
});
183+
184+
const output = await new Response(proc.stdout).text();
185+
const exitCode = await proc.exited;
186+
187+
expect(exitCode).toBe(0);
188+
expect(output).toContain("No stdin expected");
189+
// No stdin tags when there's no stdin
190+
expect(output).not.toContain("<stdin>");
191+
});
192+
193+
test("multiline stdin is preserved through pipe", async () => {
194+
const agent = join(testDir, "multiline.echo.md");
195+
await writeFile(agent, `---
196+
---
197+
Received:
198+
`);
199+
200+
const multilineInput = "line1\nline2\nline3";
201+
202+
const proc = spawn({
203+
cmd: ["bash", "-c", `printf "${multilineInput}" | bun run ${indexPath} ${agent}`],
204+
stdout: "pipe",
205+
stderr: "pipe",
206+
env: { ...process.env, MA_COMMAND: "echo" },
207+
});
208+
209+
const output = await new Response(proc.stdout).text();
210+
const exitCode = await proc.exited;
211+
212+
expect(exitCode).toBe(0);
213+
expect(output).toContain("line1");
214+
expect(output).toContain("line2");
215+
expect(output).toContain("line3");
216+
});
217+
});

0 commit comments

Comments
 (0)