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-6784-31a28b9e567a7f30dc543dc95fda03986893b906
in repository https://gitbox.apache.org/repos/asf/texera.git

commit e345e4559560d4d3daad316d88c9626f5e107b21
Author: Xinyuan Lin <[email protected]>
AuthorDate: Wed Jul 22 17:02:16 2026 -0700

    test(frontend): extend VirtualEnvironmentService unit test coverage (#6784)
    
    ### What changes were proposed in this PR?
    
    Extends `virtual-environment.service.spec.ts` with 15 tests (5 -> 20),
    covering `getAccessToken` (all three branches), the PVE list/save/delete
    and package endpoints (both the token and no-token `buildBaseParams`
    branches), `getUserPackages` match/no-match, and `getPveWebSocketUrl`
    (ws/wss, token presence, package/name encoding). Coverage 30% -> 100%.
    No existing tests modified.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6778.
    
    ### How was this PR tested?
    
    `ng test --include='**/virtual-environment.service.spec.ts'` -> 20/20
    passing. `yarn format:ci` passes.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../virtual-environment.service.spec.ts            | 190 ++++++++++++++++++++-
 1 file changed, 189 insertions(+), 1 deletion(-)

diff --git 
a/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
 
b/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
index c124972baf..51721948da 100644
--- 
a/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
+++ 
b/frontend/src/app/workspace/service/virtual-environment/virtual-environment.service.spec.ts
@@ -20,8 +20,9 @@
 import { TestBed } from "@angular/core/testing";
 import { AppSettings } from "../../../common/app-setting";
 import { HttpClientTestingModule, HttpTestingController } from 
"@angular/common/http/testing";
-import { UserPveRecord, WorkflowPveService } from 
"./virtual-environment.service";
+import { PvePackageResponse, UserPveRecord, WorkflowPveService } from 
"./virtual-environment.service";
 import { commonTestProviders } from "../../../common/testing/test-utils";
+import { AuthService } from "../../../common/service/user/auth.service";
 
 describe("WorkflowPveService", () => {
   let service: WorkflowPveService;
@@ -82,4 +83,191 @@ describe("WorkflowPveService", () => {
     expect(req.request.method).toBe("DELETE");
     req.flush(null);
   });
+
+  describe("token-parameterized endpoints", () => {
+    const endpoint = AppSettings.getApiEndpoint();
+
+    afterEach(() => {
+      httpTestingController.verify();
+      vi.restoreAllMocks();
+    });
+
+    describe("getAccessToken()", () => {
+      it("returns the token when AuthService yields a non-empty value", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue("jwt-123");
+        expect(service.getAccessToken()).toBe("jwt-123");
+      });
+
+      it("returns null when AuthService yields null", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+        expect(service.getAccessToken()).toBeNull();
+      });
+
+      it("returns null when the token is whitespace-only", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue("   ");
+        expect(service.getAccessToken()).toBeNull();
+      });
+    });
+
+    describe("getSystemPackages()", () => {
+      it("GETs /pve/system with cuid and the access-token param when 
authenticated", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue("jwt-123");
+        const response = { system: ["numpy", "pandas"] };
+        service.getSystemPackages(5).subscribe(resp => {
+          expect(resp).toEqual(response);
+        });
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/system`);
+        expect(req.request.method).toBe("GET");
+        expect(req.request.params.get("cuid")).toBe("5");
+        expect(req.request.params.get("access-token")).toBe("jwt-123");
+        req.flush(response);
+      });
+
+      it("omits the access-token param when unauthenticated", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+        service.getSystemPackages(8).subscribe();
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/system`);
+        expect(req.request.params.get("cuid")).toBe("8");
+        expect(req.request.params.has("access-token")).toBe(false);
+        req.flush({ system: [] });
+      });
+    });
+
+    describe("fetchPVEs()", () => {
+      it("GETs /pve/pves with the cuid param and returns the records", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue("jwt-123");
+        const pves: PvePackageResponse[] = [
+          { pveName: "env-a", userPackages: ["numpy"] },
+          { pveName: "env-b", userPackages: ["scipy"] },
+        ];
+        service.fetchPVEs(3).subscribe(resp => {
+          expect(resp).toEqual(pves);
+        });
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/pves`);
+        expect(req.request.method).toBe("GET");
+        expect(req.request.params.get("cuid")).toBe("3");
+        expect(req.request.params.get("access-token")).toBe("jwt-123");
+        req.flush(pves);
+      });
+    });
+
+    describe("getUserPackages()", () => {
+      it("returns the userPackages of the matching PVE", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+        const pves: PvePackageResponse[] = [
+          { pveName: "env-a", userPackages: ["numpy"] },
+          { pveName: "env-b", userPackages: ["scipy", "torch"] },
+        ];
+        let received: string[] | undefined;
+        service.getUserPackages(4, "env-b").subscribe(resp => {
+          received = resp;
+        });
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/pves`);
+        req.flush(pves);
+        expect(received).toEqual(["scipy", "torch"]);
+      });
+
+      it("returns an empty array when no PVE matches the given name", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+        const pves: PvePackageResponse[] = [{ pveName: "env-a", userPackages: 
["numpy"] }];
+        let received: string[] | undefined;
+        service.getUserPackages(4, "does-not-exist").subscribe(resp => {
+          received = resp;
+        });
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/pves`);
+        req.flush(pves);
+        expect(received).toEqual([]);
+      });
+    });
+
+    describe("deleteEnvironments()", () => {
+      it("DELETEs /pve/pves/{cuid}", () => {
+        service.deleteEnvironments(11).subscribe();
+
+        const req = httpTestingController.expectOne(`${endpoint}/pve/pves/11`);
+        expect(req.request.method).toBe("DELETE");
+        req.flush(null);
+      });
+    });
+
+    describe("deletePackage()", () => {
+      it("DELETEs a URL-encoded package path with the access-token param when 
authenticated", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue("jwt-123");
+        service.deletePackage(6, "env a", "pkg+x").subscribe();
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/6/env%20a/packages/pkg%2Bx`);
+        expect(req.request.method).toBe("DELETE");
+        expect(req.request.params.get("access-token")).toBe("jwt-123");
+        req.flush(["remaining"]);
+      });
+
+      it("omits the access-token param when unauthenticated", () => {
+        vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+        service.deletePackage(6, "env-x", "numpy").subscribe();
+
+        const req = httpTestingController.expectOne(r => r.url === 
`${endpoint}/pve/6/env-x/packages/numpy`);
+        expect(req.request.params.has("access-token")).toBe(false);
+        req.flush([]);
+      });
+    });
+  });
+
+  describe("getPveWebSocketUrl()", () => {
+    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 });
+      }
+    };
+
+    afterEach(() => {
+      vi.restoreAllMocks();
+    });
+
+    it("uses the ws:// scheme over http and encodes an empty package list with 
no token param", () => {
+      vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+      const url = withLocation({ protocol: "http:", host: "localhost:9000" }, 
() =>
+        service.getPveWebSocketUrl(2, "env-a", "install")
+      );
+      
expect(url).toBe("ws://localhost:9000/wsapi/pve?packages=%5B%5D&cuid=2&pveName=env-a&action=install");
+    });
+
+    it("uses the wss:// scheme over https", () => {
+      vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+      const url = withLocation({ protocol: "https:", host: "example.com" }, () 
=>
+        service.getPveWebSocketUrl(2, "env-a", "install")
+      );
+      expect(url.startsWith("wss://example.com/wsapi/pve")).toBe(true);
+    });
+
+    it("appends the access-token param when authenticated and URL-encodes it", 
() => {
+      vi.spyOn(AuthService, "getAccessToken").mockReturnValue("a b/c");
+      const url = withLocation({ protocol: "http:", host: "localhost:9000" }, 
() =>
+        service.getPveWebSocketUrl(2, "env-a", "install")
+      );
+      expect(url.endsWith("&access-token=a%20b%2Fc")).toBe(true);
+    });
+
+    it("JSON-encodes the packages list and URL-encodes the pveName", () => {
+      vi.spyOn(AuthService, "getAccessToken").mockReturnValue(null);
+      const url = withLocation({ protocol: "http:", host: "localhost:9000" }, 
() =>
+        service.getPveWebSocketUrl(7, "my env", "uninstall", ["numpy==1.26.0", 
"scipy"])
+      );
+      const expectedPackages = 
encodeURIComponent(JSON.stringify(["numpy==1.26.0", "scipy"]));
+      expect(url).toBe(
+        
`ws://localhost:9000/wsapi/pve?packages=${expectedPackages}&cuid=7&pveName=my%20env&action=uninstall`
+      );
+    });
+  });
 });

Reply via email to