mengw15 commented on code in PR #7911:
URL: https://github.com/apache/texera/pull/7911#discussion_r3842311270


##########
frontend/src/app/workspace/service/agent/agent.service.spec.ts:
##########
@@ -493,6 +494,135 @@ describe("AgentService", () => {
         expect(service.isAgentActivelyConnected("agent-1")).toBe(true);
         expect(service.getActivelyConnectedAgentIds()).toEqual(["agent-1"]);
       });
+
+      it("keeps an already-open socket on re-activation and replaces a 
non-open one", () => {
+        seedAgent("agent-1");
+        service.activateAgent("agent-1");
+        const tracking = (service as any).agentStateTracking.get("agent-1");
+        const ws = FakeWebSocket.latest();
+
+        // deactivateAgent always drops the socket, so the "inactive but still
+        // holding a socket" shape that activateAgent's readyState guard 
defends
+        // against is set up directly.
+        ws.readyState = FakeWebSocket.OPEN;
+        tracking.isActive = false;
+        expect(service.activateAgent("agent-1")).toBe(true);
+        expect(FakeWebSocket.instances.length).toBe(1);
+        expect(tracking.websocket).toBe(ws);
+
+        // The same shape with a socket that is no longer OPEN reconnects 
instead.
+        ws.readyState = FakeWebSocket.CLOSED;
+        tracking.isActive = false;
+        expect(service.activateAgent("agent-1")).toBe(true);
+        expect(FakeWebSocket.instances.length).toBe(2);
+        expect(tracking.websocket).toBe(FakeWebSocket.latest());
+      });
+
+      it("ignores deactivation of an unknown agent and of an already-inactive 
one", () => {
+        service.deactivateAgent("nope");
+        expect((service as any).agentStateTracking.has("nope")).toBe(false);
+
+        seedAgent("agent-1");
+        service.activateAgent("agent-1");
+        const ws = FakeWebSocket.latest();
+        service.deactivateAgent("agent-1");
+        expect(ws.close).toHaveBeenCalledTimes(1);
+
+        const tracking = (service as any).agentStateTracking.get("agent-1");
+        const stopPolling = tracking.stopPolling$;
+        service.deactivateAgent("agent-1");
+
+        // The second call returns at the isActive guard: nothing is torn down 
twice.
+        expect(ws.close).toHaveBeenCalledTimes(1);
+        expect(tracking.stopPolling$).toBe(stopPolling);
+      });
+
+      it("deactivates cleanly when the socket was already dropped by a close 
event", () => {
+        seedAgent("agent-1");
+        service.activateAgent("agent-1");
+        const ws = FakeWebSocket.latest();
+        const tracking = (service as any).agentStateTracking.get("agent-1");
+
+        // A normal close clears tracking.websocket but leaves the agent 
active.
+        ws.onclose!({ code: 1000 });
+        expect(tracking.websocket).toBeUndefined();
+        expect(tracking.isActive).toBe(true);
+
+        const stopPolling = tracking.stopPolling$;
+        service.deactivateAgent("agent-1");
+
+        expect(ws.close).not.toHaveBeenCalled();
+        expect(tracking.isActive).toBe(false);
+        expect(tracking.stopPolling$).not.toBe(stopPolling);
+      });
+    });
+
+    describe("connection setup", () => {
+      /** Swap window.location for the duration of fn; jsdom's own is not 
writable. */
+      const withLocation = <T>(overrides: Partial<Location>, fn: () => T): T 
=> {
+        const original = window.location;
+        Object.defineProperty(window, "location", {
+          configurable: true,
+          value: { ...original, ...overrides },
+        });
+        try {
+          return fn();
+        } finally {
+          Object.defineProperty(window, "location", { configurable: true, 
value: original });
+        }
+      };
+
+      it("switches to the wss scheme when the page is served over https", () 
=> {
+        seedAgent("agent-1");
+
+        withLocation({ protocol: "https:", host: "texera.example.org" }, () => 
service.activateAgent("agent-1"));
+
+        
expect(FakeWebSocket.latest().url).toBe("wss://texera.example.org/api/agents/agent-1/react");
+      });
+
+      it("logs a payload that is not valid JSON instead of throwing out of the 
handler", () => {
+        const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+        seedAgent("agent-1");
+        service.activateAgent("agent-1");
+        const ws = FakeWebSocket.latest();
+
+        expect(() => ws.onmessage!({ data: "<html>not json</html>" 
})).not.toThrow();
+
+        expect(errSpy).toHaveBeenCalledWith("Failed to parse agent WebSocket 
message:", expect.any(SyntaxError));
+        // The connection is left intact.
+        expect((service as 
any).agentStateTracking.get("agent-1").websocket).toBe(ws);
+      });
+
+      it("logs socket transport errors", () => {
+        const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+        seedAgent("agent-1");
+        service.activateAgent("agent-1");
+        const event = { type: "error" };
+
+        FakeWebSocket.latest().onerror!(event);
+
+        expect(errSpy).toHaveBeenCalledWith("Agent agent-1 WebSocket error:", 
event);
+      });
+
+      it("ignores a close event from a socket that has already been replaced", 
() => {
+        seedAgent("agent-1");
+        service.activateAgent("agent-1");
+        const stale = FakeWebSocket.latest();
+        const states: AgentState[] = [];
+        service.getAgentStateObservable("agent-1").subscribe(s => 
states.push(s));

Review Comment:
   Each test gets a fresh `AgentService` from a fresh `TestBed`, so a later 
test cannot emit into an earlier test's captured array. Probed: the injected 
instance differs between two tests in one describe, and the first test's array 
stays at length 1 while the second subscribes and emits on its own instance. 
Same as the 47 pre-existing tests in this file. (Applies to the four identical 
comments below.)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to