tftsr-devops_investigation/tests/unit/issueActions.test.ts
Shaun Arman 47af97b68e
Some checks failed
Test / frontend-typecheck (push) Waiting to run
Test / frontend-tests (push) Waiting to run
Auto Tag / auto-tag (push) Successful in 4s
Test / rust-fmt-check (push) Successful in 1m2s
Release / build-linux-arm64 (push) Failing after 1m11s
Release / build-macos-arm64 (push) Successful in 4m31s
Test / rust-clippy (push) Successful in 7m44s
Test / rust-tests (push) Has been cancelled
Release / build-linux-amd64 (push) Successful in 16m6s
Release / build-windows-amd64 (push) Successful in 12m38s
feat: close issues, restore history, auto-save resolution steps
- db.rs: add get_issue_messages command (joins ai_conversations + ai_messages)
- tauriCommands.ts: fix updateIssueCmd to pass updates as nested object
  (was spreading inline — Rust expects {issueId, updates}); fix addFiveWhyCmd
  parameter names to match Rust (stepOrder, whyQuestion, answer, evidence);
  add getIssueMessagesCmd and IssueMessage interface
- Dashboard: X button on each open issue row to close (mark resolved) inline
- Triage: restore conversation history from DB when revisiting existing issues;
  detect close intent patterns and mark issue resolved + navigate home;
  auto-save resolution step via addFiveWhyCmd when AI advances why level
- tests: add issueActions.test.ts covering IPC arg structure and close intent
2026-03-31 12:50:39 -05:00

99 lines
3.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { invoke } from "@tauri-apps/api/core";
const mockInvoke = vi.mocked(invoke);
// Helper to capture the args passed to invoke
function lastInvokeArgs() {
const calls = mockInvoke.mock.calls;
return calls[calls.length - 1];
}
describe("updateIssueCmd IPC args", () => {
beforeEach(() => mockInvoke.mockReset());
it("passes updates as a nested object (not spread)", async () => {
mockInvoke.mockResolvedValueOnce({} as never);
const { updateIssueCmd } = await import("@/lib/tauriCommands");
await updateIssueCmd("issue-1", { status: "resolved" });
const [cmd, args] = lastInvokeArgs();
expect(cmd).toBe("update_issue");
// args must have 'issueId' and 'updates' as separate keys
expect((args as Record<string, unknown>).issueId).toBe("issue-1");
expect((args as Record<string, unknown>).updates).toEqual({ status: "resolved" });
// must NOT have status at the top level
expect((args as Record<string, unknown>).status).toBeUndefined();
});
});
describe("addFiveWhyCmd IPC args", () => {
beforeEach(() => mockInvoke.mockReset());
it("passes correct Rust parameter names", async () => {
mockInvoke.mockResolvedValueOnce({} as never);
const { addFiveWhyCmd } = await import("@/lib/tauriCommands");
await addFiveWhyCmd("issue-1", 1, "Why did it fail?", "Network timeout", "");
const [cmd, args] = lastInvokeArgs();
expect(cmd).toBe("add_five_why");
const a = args as Record<string, unknown>;
expect(a.issueId).toBe("issue-1");
expect(a.stepOrder).toBe(1);
expect(a.whyQuestion).toBe("Why did it fail?");
expect(a.answer).toBe("Network timeout");
expect(a.evidence).toBe("");
});
});
describe("getIssueMessagesCmd IPC args", () => {
beforeEach(() => mockInvoke.mockReset());
it("calls get_issue_messages with issueId", async () => {
mockInvoke.mockResolvedValueOnce([]);
const { getIssueMessagesCmd } = await import("@/lib/tauriCommands");
await getIssueMessagesCmd("issue-1");
const [cmd, args] = lastInvokeArgs();
expect(cmd).toBe("get_issue_messages");
expect((args as Record<string, unknown>).issueId).toBe("issue-1");
});
});
describe("close intent detection", () => {
const closePatterns = [
"close this issue",
"please close",
"mark as resolved",
"mark resolved",
"issue is fixed",
"issue is resolved",
"resolve this",
"this is resolved",
];
const notCloseMessages = [
"why did the server close the connection",
"the issue resolves around DNS",
"how do I fix this",
];
function isCloseIntent(message: string): boolean {
const lower = message.toLowerCase();
return closePatterns.some((p) => lower.includes(p));
}
it("detects close intent patterns", () => {
expect(isCloseIntent("Please close this issue as it was a test")).toBe(true);
expect(isCloseIntent("Mark as resolved")).toBe(true);
expect(isCloseIntent("This issue is fixed now")).toBe(true);
expect(isCloseIntent("issue is resolved")).toBe(true);
});
it("does not flag non-close messages as close intent", () => {
for (const msg of notCloseMessages) {
expect(isCloseIntent(msg)).toBe(false);
}
});
});