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-6432-297e550068a9f06811b994cde2755e5d98e4a7fd
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 181e38b4a0b8bfa49fdef5b4fa2d43eed2d79e1d
Author: Meng Wang <[email protected]>
AuthorDate: Tue Jul 14 18:47:10 2026 -0700

    test(frontend): add unit test coverage for AIAssistantService (#6432)
    
    ### What changes were proposed in this PR?
    
    Adds a Vitest unit-test suite for `AIAssistantService`
    
    (`frontend/src/app/workspace/service/ai-assistant/ai-assistant.service.ts`),
    previously without a spec (~19% coverage). Uses
    `HttpClientTestingModule` +
    `HttpTestingController` to assert request shape (method / URL /
    responseType /
    body) and response mapping — no backend.
    
    8 tests cover:
    
    - `checkAIAssistantEnabled` — GET `/isenabled` with `responseType:
    "text"`; emits `"OpenAI"` for an `OpenAI` response, `"NoAiAssistant"`
    for any other response, and falls back to `"NoAiAssistant"` when the
    request errors.
    - `getTypeAnnotations` — POST `/annotationresult` with the `{ code,
    lineNumber, allcode }` body; passes the `TypeAnnotationResponse`
    through.
    - `locateUnannotated` — POST `/annotate-argument` with the `{
    selectedCode, startLine }` body; flattens the nested
    `underlying.result.value[]` payload into `UnannotatedArgument[]`, emits
    `[]` on a null body, and errors with `"Request to backend failed"` when
    the request fails.
    
    No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6429
    
    ### 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-assistant/ai-assistant.service.spec.ts
    # Test Files 1 passed (1) | Tests 8 passed (8)
    prettier --write <spec>   # unchanged
    eslint  <spec>            # clean
    ```
    
    `HttpClientTestingModule` / `HttpTestingController` back the suite, 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])
---
 .../ai-assistant/ai-assistant.service.spec.ts      | 151 +++++++++++++++++++++
 1 file changed, 151 insertions(+)

diff --git 
a/frontend/src/app/workspace/service/ai-assistant/ai-assistant.service.spec.ts 
b/frontend/src/app/workspace/service/ai-assistant/ai-assistant.service.spec.ts
new file mode 100644
index 0000000000..1a37e8f8c1
--- /dev/null
+++ 
b/frontend/src/app/workspace/service/ai-assistant/ai-assistant.service.spec.ts
@@ -0,0 +1,151 @@
+/**
+ * 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 {
+  AIAssistantService,
+  AI_ASSISTANT_API_BASE_URL,
+  TypeAnnotationResponse,
+  UnannotatedArgument,
+} from "./ai-assistant.service";
+
+describe("AIAssistantService", () => {
+  let service: AIAssistantService;
+  let httpMock: HttpTestingController;
+
+  beforeEach(() => {
+    // The service logs to the console on every call; keep the test output 
quiet.
+    vi.spyOn(console, "log").mockImplementation(() => {});
+    vi.spyOn(console, "error").mockImplementation(() => {});
+
+    TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule],
+      providers: [AIAssistantService],
+    });
+    service = TestBed.inject(AIAssistantService);
+    httpMock = TestBed.inject(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpMock.verify();
+    vi.restoreAllMocks();
+  });
+
+  it("should be created", () => {
+    expect(service).toBeTruthy();
+  });
+
+  describe("checkAIAssistantEnabled", () => {
+    it("GETs /isenabled as text and emits OpenAI when the backend returns 
OpenAI", () => {
+      let result: string | undefined;
+      service.checkAIAssistantEnabled().subscribe(r => (result = r));
+
+      const req = httpMock.expectOne(`${AI_ASSISTANT_API_BASE_URL}/isenabled`);
+      expect(req.request.method).toBe("GET");
+      expect(req.request.responseType).toBe("text");
+
+      req.flush("OpenAI");
+      expect(result).toBe("OpenAI");
+    });
+
+    it("emits NoAiAssistant for any non-OpenAI response", () => {
+      let result: string | undefined;
+      service.checkAIAssistantEnabled().subscribe(r => (result = r));
+
+      
httpMock.expectOne(`${AI_ASSISTANT_API_BASE_URL}/isenabled`).flush("Gemini");
+      expect(result).toBe("NoAiAssistant");
+    });
+
+    it("falls back to NoAiAssistant when the request errors", () => {
+      let result: string | undefined;
+      service.checkAIAssistantEnabled().subscribe(r => (result = r));
+
+      httpMock
+        .expectOne(`${AI_ASSISTANT_API_BASE_URL}/isenabled`)
+        .flush("boom", { status: 500, statusText: "Server Error" });
+      expect(result).toBe("NoAiAssistant");
+    });
+  });
+
+  describe("getTypeAnnotations", () => {
+    it("POSTs /annotationresult with the code payload and passes the response 
through", () => {
+      const response: TypeAnnotationResponse = { choices: [{ message: { 
content: "def f(x: int) -> int" } }] };
+      let result: TypeAnnotationResponse | undefined;
+      service.getTypeAnnotations("x + 1", 3, "def f(x):\n    return x + 
1").subscribe(r => (result = r));
+
+      const req = 
httpMock.expectOne(`${AI_ASSISTANT_API_BASE_URL}/annotationresult`);
+      expect(req.request.method).toBe("POST");
+      expect(req.request.body).toEqual({ code: "x + 1", lineNumber: 3, 
allcode: "def f(x):\n    return x + 1" });
+
+      req.flush(response);
+      expect(result).toEqual(response);
+    });
+  });
+
+  describe("locateUnannotated", () => {
+    it("POSTs /annotate-argument and flattens the nested response into 
UnannotatedArgument[]", () => {
+      const backendResponse = {
+        underlying: {
+          result: {
+            value: [
+              {
+                underlying: {
+                  name: { value: "x" },
+                  startLine: { value: 1 },
+                  startColumn: { value: 4 },
+                  endLine: { value: 1 },
+                  endColumn: { value: 5 },
+                },
+              },
+            ],
+          },
+        },
+      };
+      let result: UnannotatedArgument[] | undefined;
+      service.locateUnannotated("def f(x):", 1).subscribe(r => (result = r));
+
+      const req = 
httpMock.expectOne(`${AI_ASSISTANT_API_BASE_URL}/annotate-argument`);
+      expect(req.request.method).toBe("POST");
+      expect(req.request.body).toEqual({ selectedCode: "def f(x):", startLine: 
1 });
+
+      req.flush(backendResponse);
+      expect(result).toEqual([{ name: "x", startLine: 1, startColumn: 4, 
endLine: 1, endColumn: 5 }]);
+    });
+
+    it("emits an empty array when the response body is null", () => {
+      let result: UnannotatedArgument[] | undefined;
+      service.locateUnannotated("code", 1).subscribe(r => (result = r));
+
+      
httpMock.expectOne(`${AI_ASSISTANT_API_BASE_URL}/annotate-argument`).flush(null);
+      expect(result).toEqual([]);
+    });
+
+    it("errors with a wrapped message when the request fails", () => {
+      let caught: unknown;
+      service.locateUnannotated("code", 1).subscribe({ error: (e: unknown) => 
(caught = e) });
+
+      httpMock
+        .expectOne(`${AI_ASSISTANT_API_BASE_URL}/annotate-argument`)
+        .flush("boom", { status: 500, statusText: "Server Error" });
+      expect(caught).toBeInstanceOf(Error);
+      expect((caught as Error).message).toBe("Request to backend failed");
+    });
+  });
+});

Reply via email to