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/22 09:32:32 UTC

(texera) branch gh-readonly-queue/main/pr-6765-a26ccb47d847a7a3a2d7336addf7811be14b3e62 created (now 28ec924c2b)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-6765-a26ccb47d847a7a3a2d7336addf7811be14b3e62
in repository https://gitbox.apache.org/repos/asf/texera.git


      at 28ec924c2b test(frontend): cover ComputingUnitStatusService polling/state and WorkflowWebsocketService events (#6765)

This branch includes the following new commits:

     new 28ec924c2b test(frontend): cover ComputingUnitStatusService polling/state and WorkflowWebsocketService events (#6765)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(texera) 01/01: test(frontend): cover ComputingUnitStatusService polling/state and WorkflowWebsocketService events (#6765)

Posted by gi...@apache.org.
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-6765-a26ccb47d847a7a3a2d7336addf7811be14b3e62
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 28ec924c2bbf3c2176fb31d47fc8d86e165e1ca2
Author: Meng Wang <me...@uci.edu>
AuthorDate: Wed Jul 22 01:15:54 2026 -0700

    test(frontend): cover ComputingUnitStatusService polling/state and WorkflowWebsocketService events (#6765)
    
    ### What changes were proposed in this PR?
    
    Extends two related workspace service specs
    
    (`frontend/src/app/common/service/computing-unit/computing-unit-status/computing-unit-status.service.ts`,
    
    `frontend/src/app/workspace/service/workflow-websocket/workflow-websocket.service.ts`)
    to cover the state-management, polling and connection logic they had
    left
    untested. No production code was changed.
    
    **`ComputingUnitStatusService`** (+5 tests)
    
    - `updateUnitInList` — replaces the matching unit and leaves the others
    untouched;
    - `setComputingUnitsState` — refreshes the selected unit when it is
    still present,
    and clears the selection + stops polling when the selected unit
    disappears;
    - `startPollingSelectedUnit` — with `vi.useFakeTimers()`, re-fetches the
    unit on
    each interval tick and merges the result; `stopPollingSelectedUnit`
    halts further
      polling (real timers restored in a `finally`).
    
    **`WorkflowWebsocketService`** (+3 tests)
    
    - `websocketEvent` — surfaces events pushed onto the response stream;
    - `getConnectionStatusStream` / `updateConnectionStatus` — reflect
    connect/disconnect
      transitions and guard duplicate values;
    - `openWebsocket` — routes an incoming socket message to
    `websocketEvent` and marks
    the connection up, driven through a fake `window.WebSocket` (the spec's
    existing
      test double), so no real socket is opened.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6754
    
    ### How was this PR tested?
    
    Extended unit tests, run locally in `frontend/` (all green; the failure
    path was
    verified by breaking the polling and connection assertions to confirm
    the suite
    goes red):
    
    ```
    ng test --watch=false --include src/app/common/service/computing-unit/computing-unit-status/computing-unit-status.service.spec.ts   # 18 passed
    ng test --watch=false --include src/app/workspace/service/workflow-websocket/workflow-websocket.service.spec.ts                     # 6 passed
    prettier --write <specs>   # clean
    eslint  <specs>            # clean
    ```
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../computing-unit-status.service.spec.ts          | 71 ++++++++++++++++++++++
 .../workflow-websocket.service.spec.ts             | 62 +++++++++++++++++++
 2 files changed, 133 insertions(+)

diff --git a/frontend/src/app/common/service/computing-unit/computing-unit-status/computing-unit-status.service.spec.ts b/frontend/src/app/common/service/computing-unit/computing-unit-status/computing-unit-status.service.spec.ts
index 4889a43270..bae16d79ec 100644
--- a/frontend/src/app/common/service/computing-unit/computing-unit-status/computing-unit-status.service.spec.ts
+++ b/frontend/src/app/common/service/computing-unit/computing-unit-status/computing-unit-status.service.spec.ts
@@ -223,4 +223,75 @@ describe("ComputingUnitStatusService", () => {
     expect(listSpy).toHaveBeenCalled();
     expect(latest).toEqual(newUnits);
   });
+
+  it("updateUnitInList replaces the matching unit and leaves the others untouched", () => {
+    const unitA = mockUnit(1);
+    const unitB = mockUnit(2);
+    (service as any).allComputingUnitsSubject.next([unitA, unitB]);
+
+    const updatedA = { computingUnit: { cuid: 1 }, status: "Running" } as unknown as DashboardWorkflowComputingUnit;
+    (service as any).updateUnitInList(updatedA);
+
+    expect((service as any).allComputingUnitsSubject.value).toEqual([updatedA, unitB]);
+  });
+
+  it("setComputingUnitsState refreshes the selected unit when it is still present in the new list", () => {
+    (service as any).selectedUnitSubject.next(mockUnit(7));
+
+    const updated = { computingUnit: { cuid: 7 }, status: "Running" } as unknown as DashboardWorkflowComputingUnit;
+    (service as any).setComputingUnitsState([updated]);
+
+    expect(service.getSelectedComputingUnitValue()).toBe(updated);
+  });
+
+  it("setComputingUnitsState clears the selection and stops polling when the selected unit disappears", () => {
+    (service as any).selectedUnitSubject.next(mockUnit(7));
+    const stopSpy = vi.spyOn(service as any, "stopPollingSelectedUnit");
+
+    (service as any).setComputingUnitsState([mockUnit(8)]);
+
+    expect(service.getSelectedComputingUnitValue()).toBeNull();
+    expect(stopSpy).toHaveBeenCalled();
+  });
+
+  it("startPollingSelectedUnit polls the unit on each interval tick and merges the result", () => {
+    vi.useFakeTimers();
+    try {
+      const managing = TestBed.inject(WorkflowComputingUnitManagingService);
+      const polled = { computingUnit: { cuid: 3 }, status: "Running" } as unknown as DashboardWorkflowComputingUnit;
+      const getSpy = vi.spyOn(managing, "getComputingUnit").mockReturnValue(of(polled));
+      (service as any).allComputingUnitsSubject.next([mockUnit(3)]);
+
+      (service as any).startPollingSelectedUnit(3);
+      // interval() fires only after the first period elapses
+      expect(getSpy).not.toHaveBeenCalled();
+
+      vi.advanceTimersByTime((service as any).REFRESH_INTERVAL_MS);
+
+      expect(getSpy).toHaveBeenCalledWith(3);
+      expect((service as any).allComputingUnitsSubject.value).toEqual([polled]);
+    } finally {
+      // Stop the interval poll here so the test is self-contained rather than
+      // relying on afterEach's ngOnDestroy to tear it down.
+      (service as any).stopPollingSelectedUnit();
+      vi.useRealTimers();
+    }
+  });
+
+  it("stopPollingSelectedUnit halts further polling", () => {
+    vi.useFakeTimers();
+    try {
+      const managing = TestBed.inject(WorkflowComputingUnitManagingService);
+      const getSpy = vi.spyOn(managing, "getComputingUnit").mockReturnValue(of(mockUnit(3)));
+
+      (service as any).startPollingSelectedUnit(3);
+      (service as any).stopPollingSelectedUnit();
+
+      vi.advanceTimersByTime(10000);
+
+      expect(getSpy).not.toHaveBeenCalled();
+    } finally {
+      vi.useRealTimers();
+    }
+  });
 });
diff --git a/frontend/src/app/workspace/service/workflow-websocket/workflow-websocket.service.spec.ts b/frontend/src/app/workspace/service/workflow-websocket/workflow-websocket.service.spec.ts
index 4fb0fe1d10..db6ecc6aad 100644
--- a/frontend/src/app/workspace/service/workflow-websocket/workflow-websocket.service.spec.ts
+++ b/frontend/src/app/workspace/service/workflow-websocket/workflow-websocket.service.spec.ts
@@ -18,6 +18,7 @@
  */
 
 import { TestBed } from "@angular/core/testing";
+import { Subscription } from "rxjs";
 import { WorkflowWebsocketService } from "./workflow-websocket.service";
 import { commonTestProviders } from "../../../common/testing/test-utils";
 
@@ -102,4 +103,65 @@ describe("WorkflowWebsocketService", () => {
     service.closeWebsocket();
     expect(service.numWorkers).toBe(-1);
   });
+
+  it("websocketEvent surfaces events pushed onto the response stream", () => {
+    const received: unknown[] = [];
+    const sub = service.websocketEvent().subscribe(event => received.push(event));
+
+    const event = { type: "WorkflowStateEvent", state: "RUNNING" };
+    (service as any).webSocketResponseSubject.next(event);
+    sub.unsubscribe();
+
+    expect(received).toEqual([event]);
+  });
+
+  it("getConnectionStatusStream reflects updateConnectionStatus transitions and guards duplicates", () => {
+    const emissions: boolean[] = [];
+    const sub = service.getConnectionStatusStream().subscribe(value => emissions.push(value));
+
+    // BehaviorSubject seeds `false`; a repeated value is guarded and does not re-emit.
+    (service as any).updateConnectionStatus(true);
+    (service as any).updateConnectionStatus(true);
+    (service as any).updateConnectionStatus(false);
+    sub.unsubscribe();
+
+    expect(emissions).toEqual([false, true, false]);
+    expect(service.isConnected).toBe(false);
+  });
+
+  it("openWebsocket routes an incoming socket message to websocketEvent and marks the connection up", async () => {
+    const originalWebSocket = window.WebSocket;
+    const sockets: FakeWebSocket[] = [];
+    class CapturingWebSocket extends FakeWebSocket {
+      constructor(url: string) {
+        super(url);
+        sockets.push(this);
+      }
+    }
+    window.WebSocket = CapturingWebSocket as unknown as typeof WebSocket;
+
+    const subscriptions: Subscription[] = [];
+    try {
+      const events: unknown[] = [];
+      subscriptions.push(service.websocketEvent().subscribe(event => events.push(event)));
+      let connected: boolean | undefined;
+      subscriptions.push(service.getConnectionStatusStream().subscribe(value => (connected = value)));
+
+      service.openWebsocket(1, 1, 1);
+      await Promise.resolve(); // let the fake socket transition to OPEN
+
+      const socket = sockets[sockets.length - 1];
+      const event = { type: "WorkflowStateEvent", state: "RUNNING" };
+      socket.onmessage?.(new MessageEvent("message", { data: JSON.stringify(event) }));
+
+      expect(events).toContainEqual(event);
+      expect(connected).toBe(true);
+    } finally {
+      // The service's subjects never complete, so unsubscribe our observers
+      // explicitly rather than leaving them attached past the test.
+      subscriptions.forEach(subscription => subscription.unsubscribe());
+      service.closeWebsocket();
+      window.WebSocket = originalWebSocket;
+    }
+  });
 });