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;
+ }
+ });
});