moadim 3.3.1

Loop engine for AI agents — routines over REST, MCP, and a built-in web UI
import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { describe, expect, it } from "vitest";
import type { FleetRunSummary } from "../../api/hooks";
import { computeFailureCauses, exitCodeMeaning } from "./exitCodeMath";
import { FailureCauses } from "./FailureCauses";

function run(
  routine_id: string,
  started_at: number,
  status: FleetRunSummary["status"],
  exit_code: number | null,
  finished_at: number | null = started_at + 60,
): FleetRunSummary {
  return {
    routine_id,
    routine_title: routine_id.toUpperCase(),
    workbench: `wb-${started_at}`,
    started_at,
    started_at_local: "",
    finished_at,
    finished_at_local: "",
    status,
    exit_code,
  };
}

describe("exitCodeMeaning", () => {
  it("names known codes, signals, and unknowns", () => {
    expect(exitCodeMeaning(null)).toBe("no exit code recorded");
    expect(exitCodeMeaning(137)).toBe("killed (SIGKILL / OOM)");
    expect(exitCodeMeaning(129)).toBe("signal 1");
    expect(exitCodeMeaning(3)).toBe("routine-specific");
    expect(exitCodeMeaning(200)).toBe("routine-specific");
  });
});

describe("computeFailureCauses", () => {
  it("is empty without failures", () => {
    expect(computeFailureCauses([run("a", 1, "success", 0)])).toEqual({ causes: [], totalFailed: 0 });
  });

  it("groups by code, most frequent first, ties newest first, tracks routines and last run", () => {
    const r = computeFailureCauses([
      run("a", 100, "failed", 137),
      run("b", 300, "failed", 137),
      run("a", 200, "failed", 137),
      run("c", 500, "failed", 1),
      run("c", 50, "failed", null, null),
      run("a", 900, "success", 0),
    ]);
    expect(r.totalFailed).toBe(5);
    expect(r.causes.map((c) => c.code)).toEqual([137, 1, null]);
    expect(r.causes[0]).toEqual({
      code: 137,
      count: 3,
      routines: [
        { id: "a", title: "A", count: 2 },
        { id: "b", title: "B", count: 1 },
      ],
      last: { routineId: "b", workbench: "wb-300", at: 360 },
    });
    expect(r.causes[2]?.last.at).toBe(50);
  });
});

describe("FailureCauses", () => {
  it("renders nothing without failures", () => {
    const { container } = render(<FailureCauses report={{ causes: [], totalFailed: 0 }} />);
    expect(container.innerHTML).toBe("");
  });

  it("renders rows with meanings, shares, routine overflow and links", () => {
    const report = computeFailureCauses([
      run("a", 1, "failed", 137),
      run("a", 2, "failed", 137),
      run("b", 3, "failed", 137),
      run("c", 4, "failed", 137),
      run("d", 5, "failed", 137),
      run("e", 6, "failed", null),
    ]);
    render(
      <MemoryRouter>
        <FailureCauses report={report} />
      </MemoryRouter>,
    );
    expect(screen.getByText("FAILURE CAUSES")).toBeInTheDocument();
    expect(screen.getByText(/6 failed runs · 2 distinct exit codes/)).toBeInTheDocument();
    expect(screen.getByText("killed (SIGKILL / OOM)")).toBeInTheDocument();
    expect(screen.getByText("5 (83%)")).toBeInTheDocument();
    expect(screen.getByText("?")).toBeInTheDocument();
    expect(screen.getByText("×2")).toBeInTheDocument();
    expect(screen.getByText("+1 more")).toBeInTheDocument();
    expect(screen.getByText("A").closest("a")).toHaveAttribute("href", "/routines?history=a");
    const last = screen.getAllByTitle("Open the newest failed run with this exit code");
    expect(last[0]).toHaveAttribute("href", "/runs/d/wb-5");
  });

  it("uses singular wording for one code", () => {
    render(
      <MemoryRouter>
        <FailureCauses report={computeFailureCauses([run("a", 1, "failed", 1)])} />
      </MemoryRouter>,
    );
    expect(screen.getByText(/1 distinct exit code$/)).toBeInTheDocument();
  });
});