You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/12 01:29:07 UTC
(texera) 01/01: test(agent-service): add unit test coverage for assembleContext (#6354)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-6354-c7be51cca053faec17eaa786fd18b1d7253047ba
in repository https://gitbox.apache.org/repos/asf/texera.git
commit 85a56cbea19b2ad4a126bb61210e4f9d637bd61e
Author: Meng Wang <me...@uci.edu>
AuthorDate: Sat Jul 11 18:28:43 2026 -0700
test(agent-service): add unit test coverage for assembleContext (#6354)
### What changes were proposed in this PR?
Add unit test coverage for `assembleContext` in
`agent-service/src/agent/util/context-utils.ts` — a pure function
(type-only imports, no network) that groups `ReActStep`s by task,
serializes completed vs ongoing tasks into markdown sections, appends a
dataflow section, and returns `[{ role: "user", content }]`. A `bun
test` `.spec.ts` sits next to the source. No production-code changes.
### Any related issues, documentation, discussions?
Closes #6333.
### How was this PR tested?
```
bun test src/agent/util/context-utils.spec.ts
```
5 pass. `bun run typecheck` and `bun run format:check` are clean. The
spec is pure — it builds `ReActStep` / `WorkflowState` /
`OperatorPredicate` fixtures in-process and covers: empty steps → a
single user message with no task sections; a completed task under `#
Completed Tasks` (`## Task (completed)` with the request + turns); an
unfinished task under `# Ongoing Task`; a `# Current Dataflow` section
when the workflow has operators; and steps sharing a `messageId` grouped
into one task.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---------
Signed-off-by: Meng Wang <me...@uci.edu>
Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
agent-service/src/agent/util/context-utils.spec.ts | 103 +++++++++++++++++++++
1 file changed, 103 insertions(+)
diff --git a/agent-service/src/agent/util/context-utils.spec.ts b/agent-service/src/agent/util/context-utils.spec.ts
new file mode 100644
index 0000000000..84af9df3bb
--- /dev/null
+++ b/agent-service/src/agent/util/context-utils.spec.ts
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { describe, expect, test } from "bun:test";
+import type { ModelMessage } from "ai";
+import { assembleContext } from "./context-utils";
+import { WorkflowState } from "../workflow-state";
+import type { ReActStep } from "../../types/agent";
+import type { OperatorPredicate } from "../../types/workflow";
+
+function step(messageId: string, role: "user" | "agent", stepId: number, content: string, isEnd: boolean): ReActStep {
+ return {
+ id: `${messageId}-${stepId}`,
+ messageId,
+ stepId,
+ timestamp: 0,
+ role,
+ content,
+ isBegin: true,
+ isEnd,
+ };
+}
+
+function makeOperator(id: string): OperatorPredicate {
+ return {
+ operatorID: id,
+ operatorType: "TestOp",
+ operatorVersion: "1.0",
+ operatorProperties: {},
+ inputPorts: [],
+ outputPorts: [],
+ showAdvanced: false,
+ };
+}
+
+// assembleContext always returns a single user message whose content is a joined string.
+function contentOf(result: ModelMessage[]): string {
+ expect(result).toHaveLength(1);
+ expect(result[0].role).toBe("user");
+ expect(typeof result[0].content).toBe("string");
+ return result[0].content as string;
+}
+
+describe("assembleContext", () => {
+ test("returns a single user message with no task sections for empty steps", () => {
+ const result = assembleContext([], new WorkflowState(), new Map());
+ expect(result).toHaveLength(1);
+ expect(result[0].role).toBe("user");
+ expect(contentOf(result)).toBe("");
+ });
+
+ test("renders a completed task under a Completed Tasks heading", () => {
+ const steps = [step("m1", "user", 0, "do X", true), step("m1", "agent", 1, "thinking", true)];
+ const content = contentOf(assembleContext(steps, new WorkflowState(), new Map()));
+ expect(content).toContain("# Completed Tasks");
+ expect(content).toContain("## Task (completed)");
+ expect(content).toContain("do X"); // the user request
+ expect(content).toContain("### Turn 1"); // the agent step
+ });
+
+ test("renders an unfinished task under an Ongoing Task heading", () => {
+ const steps = [step("m1", "user", 0, "do Y", true), step("m1", "agent", 1, "working", false)];
+ const content = contentOf(assembleContext(steps, new WorkflowState(), new Map()));
+ expect(content).toContain("# Ongoing Task");
+ expect(content).toContain("## Task (ongoing)");
+ });
+
+ test("appends a Current Dataflow section when the workflow has operators", () => {
+ const workflowState = new WorkflowState();
+ workflowState.addOperator(makeOperator("op1"));
+ const content = contentOf(assembleContext([], workflowState, new Map()));
+ expect(content).toContain("# Current Dataflow");
+ expect(content).toContain("## Operators");
+ });
+
+ test("groups steps sharing a messageId into a single task", () => {
+ const steps = [
+ step("m1", "user", 0, "req", true),
+ step("m1", "agent", 1, "turn one", false),
+ step("m1", "agent", 2, "turn two", true),
+ ];
+ const content = contentOf(assembleContext(steps, new WorkflowState(), new Map()));
+ expect((content.match(/## Task/g) ?? []).length).toBe(1);
+ expect(content).toContain("### Turn 1");
+ expect(content).toContain("### Turn 2");
+ });
+});