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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new d561da8f3c test(frontend): add unit test coverage for AiAnalystService 
(#6433)
d561da8f3c is described below

commit d561da8f3ca8b3ee6ce39b9a66320372d295f186
Author: Meng Wang <[email protected]>
AuthorDate: Tue Jul 14 16:29:31 2026 -0700

    test(frontend): add unit test coverage for AiAnalystService (#6433)
    
    ### What changes were proposed in this PR?
    
    Adds a Vitest unit-test suite for `AiAnalystService`
    (`frontend/src/app/workspace/service/ai-analyst/ai-analyst.service.ts`),
    previously without a spec (~32% coverage). Uses
    `HttpClientTestingModule` +
    `HttpTestingController`; `WorkflowActionService` (an unused constructor
    dependency) is stubbed. No backend.
    
    9 tests cover:
    
    - `isOpenAIEnabled` — GET `/aiassistant/isenabled` with `responseType:
    "text"`; emits `true` for an `OpenAI` response, `false` for anything
    else, `false` when the request errors, and short-circuits to the cached
    flag without issuing a request.
    - `sendPromptToOpenAI` — the manual `Observable` contract: emits `""`
    (and skips the OpenAI call) when the assistant is disabled; POSTs `{
    prompt }` to `/aiassistant/openai` and emits the trimmed completion when
    enabled; emits `""` when the OpenAI request fails; and emits `""` when
    the enablement check itself errors.
    
    No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6430
    
    ### How was this PR tested?
    
    New unit tests, run locally in `frontend/` (all green; the failure path
    was
    verified by deliberately breaking an assertion to confirm the suite goes
    red):
    
    ```
    ng test --watch=false --include 
src/app/workspace/service/ai-analyst/ai-analyst.service.spec.ts
    # Test Files 1 passed (1) | Tests 9 passed (9)
    prettier --write <spec>   # unchanged
    eslint  <spec>            # clean
    ```
    
    The suite is backed by `HttpClientTestingModule` /
    `HttpTestingController`, so
    it makes no real network calls (see `frontend/TESTING.md`).
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../service/ai-analyst/ai-analyst.service.spec.ts  | 134 +++++++++++++++++++++
 1 file changed, 134 insertions(+)

diff --git 
a/frontend/src/app/workspace/service/ai-analyst/ai-analyst.service.spec.ts 
b/frontend/src/app/workspace/service/ai-analyst/ai-analyst.service.spec.ts
new file mode 100644
index 0000000000..174020402f
--- /dev/null
+++ b/frontend/src/app/workspace/service/ai-analyst/ai-analyst.service.spec.ts
@@ -0,0 +1,134 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TestBed } from "@angular/core/testing";
+import { HttpClientTestingModule, HttpTestingController } from 
"@angular/common/http/testing";
+import { throwError } from "rxjs";
+import { AiAnalystService } from "./ai-analyst.service";
+import { WorkflowActionService } from 
"../workflow-graph/model/workflow-action.service";
+import { AppSettings } from "../../../common/app-setting";
+
+describe("AiAnalystService", () => {
+  let service: AiAnalystService;
+  let httpMock: HttpTestingController;
+  const isEnabledUrl = `${AppSettings.getApiEndpoint()}/aiassistant/isenabled`;
+  const openaiUrl = `${AppSettings.getApiEndpoint()}/aiassistant/openai`;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule],
+      // WorkflowActionService is a constructor dependency but unused by the 
tested
+      // methods, so an empty stub avoids pulling in the workflow-graph module.
+      providers: [AiAnalystService, { provide: WorkflowActionService, 
useValue: {} }],
+    });
+    service = TestBed.inject(AiAnalystService);
+    httpMock = TestBed.inject(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpMock.verify();
+    vi.restoreAllMocks();
+  });
+
+  it("should be created", () => {
+    expect(service).toBeTruthy();
+  });
+
+  describe("isOpenAIEnabled", () => {
+    it("GETs /aiassistant/isenabled as text and emits true when the backend 
returns OpenAI", () => {
+      let result: boolean | undefined;
+      service.isOpenAIEnabled().subscribe(v => (result = v));
+
+      const req = httpMock.expectOne(isEnabledUrl);
+      expect(req.request.method).toBe("GET");
+      expect(req.request.responseType).toBe("text");
+
+      req.flush("OpenAI");
+      expect(result).toBe(true);
+    });
+
+    it("emits false for any non-OpenAI response", () => {
+      let result: boolean | undefined;
+      service.isOpenAIEnabled().subscribe(v => (result = v));
+
+      httpMock.expectOne(isEnabledUrl).flush("NoAiAssistant");
+      expect(result).toBe(false);
+    });
+
+    it("emits false when the request errors", () => {
+      let result: boolean | undefined;
+      service.isOpenAIEnabled().subscribe(v => (result = v));
+
+      httpMock.expectOne(isEnabledUrl).flush("boom", { status: 500, 
statusText: "Server Error" });
+      expect(result).toBe(false);
+    });
+
+    it("returns the cached flag without issuing a request", () => {
+      (service as any).isAIAssistantEnabled = true;
+      let result: boolean | undefined;
+      service.isOpenAIEnabled().subscribe(v => (result = v));
+
+      expect(result).toBe(true);
+      httpMock.expectNone(isEnabledUrl);
+    });
+  });
+
+  describe("sendPromptToOpenAI", () => {
+    it("emits an empty string without calling OpenAI when the assistant is 
disabled", () => {
+      let result: string | undefined;
+      service.sendPromptToOpenAI("hello").subscribe(v => (result = v));
+
+      httpMock.expectOne(isEnabledUrl).flush("NoAiAssistant");
+      httpMock.expectNone(openaiUrl);
+      expect(result).toBe("");
+    });
+
+    it("POSTs the prompt and emits the trimmed completion when enabled", () => 
{
+      let result: string | undefined;
+      service.sendPromptToOpenAI("analyze this").subscribe(v => (result = v));
+
+      httpMock.expectOne(isEnabledUrl).flush("OpenAI");
+
+      const req = httpMock.expectOne(openaiUrl);
+      expect(req.request.method).toBe("POST");
+      expect(req.request.body).toEqual({ prompt: "analyze this" });
+      req.flush({ choices: [{ message: { content: "  the answer  " } }] });
+
+      expect(result).toBe("the answer");
+    });
+
+    it("emits an empty string when the OpenAI request fails", () => {
+      let result: string | undefined;
+      service.sendPromptToOpenAI("x").subscribe(v => (result = v));
+
+      httpMock.expectOne(isEnabledUrl).flush("OpenAI");
+      httpMock.expectOne(openaiUrl).flush("boom", { status: 500, statusText: 
"Server Error" });
+
+      expect(result).toBe("");
+    });
+
+    it("emits an empty string when the enablement check errors", () => {
+      vi.spyOn(service, "isOpenAIEnabled").mockReturnValue(throwError(() => 
new Error("check failed")));
+      let result: string | undefined;
+      service.sendPromptToOpenAI("x").subscribe(v => (result = v));
+
+      expect(result).toBe("");
+    });
+  });
+});

Reply via email to