Files
bambuddy/frontend/src/__tests__/components/EditPrinterPreflight.test.tsx
T
maziggy e222a0ef0e feat(system): log-health scanner + Add/Edit-Printer setup pre-flight
Adds a passive log-health check that complements the active Connection
  Diagnostic. Scans Bambuddy's recent app log against a curated allowlist
  catalog of known failure signatures (rejected access code, FTPS :990
  timeout, FTPS TLS failure, flapping MQTT, unreachable camera, SQLite
  "database is locked" contention), dedupes and classifies each finding
  as layer8/environment/bug, and deep-links to the troubleshooting wiki.
  Sample log lines are sanitized before they leave the process. Exposed
  via GET /system/health and surfaced on two surfaces sharing one
  SystemHealthPanel component: a System Health section on the System
  page, and inline in the bug reporter when the form opens.

  The Add-Printer and Edit-Printer dialogs gained a setup-time pre-flight:
  saving runs the connection diagnostic and, on a failed check, warns with
  a "save anyway" escape hatch instead of silently saving a printer that
  will immediately show offline.

  Log read/parse/sanitize primitives extracted from routes/support.py into
  a shared services/log_reader.py (behaviour-preserving); affected support
  tests repointed accordingly.

  Tests: test_log_health.py (11), test_system_api.py (2 new),
  SystemHealthPanel + BugReportBubble + AddPrinterPreflight +
  EditPrinterPreflight (8 frontend). All strings translated across the 9
  locales. Backend ruff clean, full unit suite green, frontend build +
  eslint clean, i18n parity green.
2026-05-22 16:31:16 +02:00

110 lines
3.4 KiB
TypeScript

/**
* Tests for the Edit-Printer setup-time pre-flight.
*
* Editing a printer runs the same connection diagnostic on save as the
* Add-Printer dialog, and warns (rather than blocks) when a check fails.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '../utils';
import { PrintersPage } from '../../pages/PrintersPage';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
const mockPrinter = {
id: 1,
name: 'X1 Carbon',
ip_address: '192.168.1.100',
serial_number: '00M09A350100001',
access_code: '12345678',
model: 'X1C',
enabled: true,
nozzle_diameter: 0.4,
nozzle_type: 'hardened_steel',
location: null,
auto_archive: true,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
};
const mockStatus = {
connected: true,
state: 'IDLE',
progress: 0,
layer_num: 0,
total_layers: 0,
temperatures: { nozzle: 25, bed: 25, chamber: 25 },
remaining_time: 0,
filename: null,
wifi_signal: -50,
vt_tray: [],
};
async function openEditModal() {
render(<PrintersPage />);
await waitFor(() => expect(screen.getByText('X1 Carbon')).toBeInTheDocument());
// Open the per-printer actions menu (kebab button), then click Edit.
const menuBtn = [...document.querySelectorAll('button')].find((b) =>
b.querySelector('.lucide-ellipsis-vertical'),
)!;
await userEvent.click(menuBtn);
await userEvent.click(await screen.findByRole('button', { name: /^edit$/i }));
await screen.findByText('Edit Printer');
}
describe('EditPrinterModal pre-flight', () => {
beforeEach(() => {
server.use(
http.get('/api/v1/printers/', () => HttpResponse.json([mockPrinter])),
http.get('/api/v1/printers/:id/status', () => HttpResponse.json(mockStatus)),
http.get('/api/v1/queue/', () => HttpResponse.json([])),
);
});
it('warns instead of saving when a connection check fails', async () => {
server.use(
http.post('/api/v1/printers/diagnostic', () =>
HttpResponse.json({
printer_id: null,
ip_address: '192.168.1.100',
overall: 'problems',
checks: [{ id: 'developer_mode', status: 'fail', params: {} }],
}),
),
);
await openEditModal();
await userEvent.click(screen.getByRole('button', { name: /save changes/i }));
expect(await screen.findByText(/Some connection checks failed/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /save anyway/i })).toBeInTheDocument();
});
it('saves directly when all connection checks pass', async () => {
let updated = false;
server.use(
http.post('/api/v1/printers/diagnostic', () =>
HttpResponse.json({
printer_id: null,
ip_address: '192.168.1.100',
overall: 'ok',
checks: [{ id: 'developer_mode', status: 'pass', params: {} }],
}),
),
http.patch('/api/v1/printers/:id', async () => {
updated = true;
return HttpResponse.json({ ...mockPrinter, name: 'X1 Carbon' });
}),
);
await openEditModal();
await userEvent.click(screen.getByRole('button', { name: /save changes/i }));
await waitFor(() => expect(updated).toBe(true));
expect(screen.queryByText(/Some connection checks failed/i)).not.toBeInTheDocument();
});
});