fix: reduce false container-failed notifications, add error details to UI
Backend (health_monitor.py): - Skip health checks for instances with no container_id - Treat 'not_found' as error only when container was previously running - Skip duplicate error notifications when already in error state - Skip 'not_found' notifications for containers that never ran Frontend (notification-item.tsx): - Display notification.message (detailed error text) - Add expandable Details section showing metadata (exit_code, previous_status, etc.) - New CSS styles for message and metadata display Quality gates: py_compile, tsc --noEmit, 80/80 tests pass
This commit is contained in:
@@ -10,72 +10,79 @@ const mockProjects = vi.fn();
|
||||
const mockRepos = vi.fn();
|
||||
|
||||
vi.mock("../api/dashboard", () => ({
|
||||
getDashboardSummary: (...args: unknown[]) => mockDashboard(...args)
|
||||
getDashboardSummary: (...args: unknown[]) => mockDashboard(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../api/sessions", () => ({
|
||||
getUserSessions: (...args: unknown[]) => mockSessions(...args),
|
||||
createInstance: vi.fn(),
|
||||
startInstance: vi.fn(),
|
||||
stopInstance: vi.fn(),
|
||||
deleteInstance: vi.fn(),
|
||||
recreateInstanceTunnel: vi.fn()
|
||||
getUserSessions: (...args: unknown[]) => mockSessions(...args),
|
||||
createInstance: vi.fn(),
|
||||
startInstance: vi.fn(),
|
||||
stopInstance: vi.fn(),
|
||||
deleteInstance: vi.fn(),
|
||||
recreateInstanceTunnel: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../api/projects", () => ({
|
||||
listProjects: (...args: unknown[]) => mockProjects(...args)
|
||||
listProjects: (...args: unknown[]) => mockProjects(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../api/git-repositories", () => ({
|
||||
listRepositories: (...args: unknown[]) => mockRepos(...args)
|
||||
listRepositories: (...args: unknown[]) => mockRepos(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../api/tool-types", () => ({
|
||||
listToolTypes: vi.fn().mockResolvedValue([])
|
||||
listToolTypes: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
describe("HomePage", () => {
|
||||
beforeEach(() => {
|
||||
mockDashboard.mockReset();
|
||||
mockSessions.mockReset();
|
||||
mockProjects.mockReset();
|
||||
mockRepos.mockReset();
|
||||
});
|
||||
beforeEach(() => {
|
||||
mockDashboard.mockReset();
|
||||
mockSessions.mockReset();
|
||||
mockProjects.mockReset();
|
||||
mockRepos.mockReset();
|
||||
});
|
||||
|
||||
it("shows overview sections", async () => {
|
||||
mockDashboard.mockResolvedValue({ projects: 1, repositories: 2, sshKeys: 3, recentActivity: [] });
|
||||
mockSessions.mockResolvedValue([]);
|
||||
mockProjects.mockResolvedValue([]);
|
||||
mockRepos.mockResolvedValue([]);
|
||||
it("shows overview sections", async () => {
|
||||
mockDashboard.mockResolvedValue({
|
||||
projects: 1,
|
||||
repositories: 2,
|
||||
sshKeys: 3,
|
||||
recentActivity: [],
|
||||
});
|
||||
mockSessions.mockResolvedValue([]);
|
||||
mockProjects.mockResolvedValue([]);
|
||||
mockRepos.mockResolvedValue([]);
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<HomePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<HomePage />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Loading overview...")).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText("Open sessions").length).toBeGreaterThan(0);
|
||||
expect(screen.getByText("Workspaces")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
expect(screen.getByText("Loading overview...")).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText("Open sessions").length).toBeGreaterThan(0);
|
||||
expect(screen.getByText("Workspaces")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows retry action when home load fails", async () => {
|
||||
mockDashboard.mockRejectedValueOnce(new Error("failed"));
|
||||
mockSessions.mockRejectedValueOnce(new Error("failed"));
|
||||
mockProjects.mockRejectedValueOnce(new Error("failed"));
|
||||
it("shows retry action when home load fails", async () => {
|
||||
mockDashboard.mockRejectedValueOnce(new Error("failed"));
|
||||
mockSessions.mockRejectedValueOnce(new Error("failed"));
|
||||
mockProjects.mockRejectedValueOnce(new Error("failed"));
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<HomePage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<HomePage />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Unable to load your workspace overview.")).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("Unable to load your workspace overview."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getAllByRole("button", { name: "Retry" })[0]);
|
||||
});
|
||||
fireEvent.click(screen.getAllByRole("button", { name: "Retry" })[0]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user