@@ -298,6 +298,49 @@ describe("plugin-sdk qa-runtime", () => {
|
298 | 298 | expect(sleepImpl).toHaveBeenCalledTimes(1); |
299 | 299 | }); |
300 | 300 | |
| 301 | +it("normalizes multiline Docker compose service lookup output", async () => { |
| 302 | +const module = await import("./qa-runtime.js"); |
| 303 | +const runtime = module.createQaDockerRuntime({ auditContext: "qa-test" }); |
| 304 | +const runCommand = vi.fn(async (command: string, args: string[], cwd: string) => { |
| 305 | +expect(command).toBe("docker"); |
| 306 | +expect(cwd).toBe("/repo"); |
| 307 | + |
| 308 | +if (args.includes("ps") && args.includes("-q")) { |
| 309 | +return { |
| 310 | +stdout: "\nqa-gateway-one\nqa-gateway-two\n", |
| 311 | +stderr: "", |
| 312 | +}; |
| 313 | +} |
| 314 | + |
| 315 | +if (args[0] === "inspect") { |
| 316 | +expect(args.at(-1)).toBe("qa-gateway-one"); |
| 317 | +return { |
| 318 | +stdout: "\n172.18.0.4\n172.19.0.4\n", |
| 319 | +stderr: "", |
| 320 | +}; |
| 321 | +} |
| 322 | + |
| 323 | +throw new Error(`unexpected docker args: ${args.join(" ")}`); |
| 324 | +}); |
| 325 | +const fetchImpl = vi.fn(async (url: string) => ({ |
| 326 | +ok: url === "http://172.18.0.4:18789/healthz", |
| 327 | +})); |
| 328 | + |
| 329 | +await expect( |
| 330 | +runtime.resolveComposeServiceUrl( |
| 331 | +"gateway", |
| 332 | +18789, |
| 333 | +"/tmp/docker-compose.yml", |
| 334 | +"/repo", |
| 335 | +runCommand, |
| 336 | +fetchImpl, |
| 337 | +), |
| 338 | +).resolves.toBe("http://172.18.0.4:18789/"); |
| 339 | + |
| 340 | +expect(runCommand).toHaveBeenCalledTimes(2); |
| 341 | +expect(fetchImpl).toHaveBeenCalledWith("http://172.18.0.4:18789/healthz"); |
| 342 | +}); |
| 343 | + |
301 | 344 | it("resolves an unpinned QA Docker host port away from an occupied loopback default", async () => { |
302 | 345 | const module = await import("./qa-runtime.js"); |
303 | 346 | const reservation = await occupyLoopbackPort(); |
|