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


##########
frontend/src/app/workspace/component/power-button/computing-unit-selection.component.spec.ts:
##########
@@ -839,6 +922,160 @@ describe("PowerButtonComponent", () => {
       expect(deleteSpy).toHaveBeenCalled();
       expect(installSpy).toHaveBeenCalled();
     });
+
+    it("refuses to create a second environment under a name another card 
already uses", () => {
+      setPve();
+      // The nameless card is scanned first, so the duplicate check has to 
survive it before it
+      // reaches the card that actually clashes.
+      const card = component.pves[0];
+      component.pves = [{ ...card, name: undefined }, { ...card }, { ...card, 
name: " envone " }] as any;
+
+      component.createVirtualEnvironment(2);
+
+      expect(errorSpy).toHaveBeenCalledWith("An environment with this name 
already exists.");
+      expect(sockets).toHaveLength(0);
+      expect(component.pves[2].isInstalling).toBeFalsy();
+    });
+
+    it("starts the log from empty when the card has produced no output yet", 
() => {
+      setPve();
+      component.createVirtualEnvironment(0);
+      // runPveWebSocket seeds the card with its banner; clearing it models a 
card whose log
+      // was reset while the socket was still attached, and exercises the 
handler's fallback.
+      (component.pves[0] as { pipOutput?: string }).pipOutput = undefined;
+
+      sockets[0].say("collecting numpy");
+
+      expect(component.pves[0].pipOutput).toBe("collecting numpy\n");
+    });
+
+    it("records a dropped connection even when the card has produced no output 
yet", () => {
+      setPve();
+      component.createVirtualEnvironment(0);
+      (component.pves[0] as { pipOutput?: string }).pipOutput = undefined;
+
+      sockets[0].onerror?.();
+
+      expect(component.pves[0].pipOutput).toBe("\n[WebSocket error]\n");
+      expect(component.pves[0].isInstalling).toBe(false);
+    });
+
+    describe("installUserPackages", () => {
+      it("refuses a package that is missing its operator or its version", () 
=> {
+        setPve({ newPackages: [{ name: "numpy", versionOp: undefined, version: 
"1.26.0" }] });
+        (component as any).installUserPackages(0);
+        expect(errorSpy).toHaveBeenLastCalledWith("Please specify an operator 
and version for each package.");
+
+        setPve({ newPackages: [{ name: "numpy", versionOp: "==", version: "   
" }] });
+        (component as any).installUserPackages(0);
+        expect(errorSpy).toHaveBeenLastCalledWith("Please specify an operator 
and version for each package.");
+
+        expect(sockets).toHaveLength(0);
+      });
+
+      it("skips a package already present as a system package or in the 
environment", () => {
+        vi.spyOn(TestBed.inject(WorkflowPveService), 
"getUserPackages").mockReturnValue(of([]));
+        // The system list is matched case-insensitively, so "NumPy" must 
block "numpy".
+        component.systemPackages = [{ name: "NumPy", version: "1.26.0" }];
+        setPve({
+          userPackages: [{ name: "pandas", versionOp: "==", version: "2.0.0" 
}],
+          newPackages: [
+            { name: "   ", versionOp: "==", version: "1.0.0" },
+            { name: "numpy", versionOp: "==", version: "1.26.0" },
+            { name: "Pandas", versionOp: "==", version: "2.0.0" },
+          ],
+        });
+
+        (component as any).installUserPackages(0);
+
+        expect(errorSpy).toHaveBeenCalledWith("Skipped numpy: already 
installed as a system package.");
+        expect(errorSpy).toHaveBeenCalledWith("Skipped Pandas: already 
installed in this environment.");
+        // Nothing survives the filters, so no socket is opened and the draft 
rows are cleared.
+        expect(sockets).toHaveLength(0);
+        expect(component.pves[0].newPackages).toEqual([]);
+        expect(component.pves[0].isInstalling).toBe(false);
+      });
+
+      it("has nothing to install when the card carries no draft rows at all", 
() => {
+        const refreshSpy = vi.spyOn(TestBed.inject(WorkflowPveService), 
"getUserPackages").mockReturnValue(of([]));
+        setPve({ newPackages: undefined });
+
+        (component as any).installUserPackages(0);
+
+        expect(sockets).toHaveLength(0);
+        expect(refreshSpy).toHaveBeenCalledWith(1, "envone");
+      });
+
+      it("opens an install socket for the surviving packages and reloads the 
list when it finishes", () => {
+        const urlSpy = vi.spyOn(TestBed.inject(WorkflowPveService), 
"getPveWebSocketUrl");
+        vi.spyOn(TestBed.inject(WorkflowPveService), 
"getUserPackages").mockReturnValue(of(["numpy==1.26.0"]));
+        component.systemPackages = [];
+        setPve({ newPackages: [{ name: "  numpy  ", versionOp: "==", version: 
"  1.26.0  " }] });
+
+        (component as any).installUserPackages(0);
+
+        expect(urlSpy).toHaveBeenCalledWith(1, "envone", "install", 
["numpy==1.26.0"]);
+        expect(sockets).toHaveLength(1);
+
+        sockets[0].say("__DONE__");
+
+        expect(component.pves[0].newPackages).toEqual([]);
+        expect(component.pves[0].userPackages).toEqual([{ name: "numpy", 
versionOp: "==", version: "1.26.0" }]);
+      });
+
+      it("logs and keeps the current list when reloading the packages fails", 
() => {
+        const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => 
{});
+        vi.spyOn(TestBed.inject(WorkflowPveService), 
"getUserPackages").mockReturnValue(
+          throwError(() => new Error("nope"))
+        );
+        setPve({ userPackages: [{ name: "numpy", versionOp: "==", version: 
"1.26.0" }] });
+
+        (component as any).refreshUserPackages(0);
+
+        expect(consoleSpy).toHaveBeenCalledWith("Failed to refresh user 
packages", expect.any(Error));
+        expect(component.pves[0].userPackages).toEqual([{ name: "numpy", 
versionOp: "==", version: "1.26.0" }]);
+      });

Review Comment:
   Confirmed and fixed — the block's `afterEach` now calls 
`vi.restoreAllMocks()`. Probed both ways: a test placed after this one saw 
`vi.isMockFunction(console.error) === true` before the change and `false` 
after. The five pre-existing `console.error` spies elsewhere in this file leak 
the same way; left alone here to keep the diff additive.



-- 
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