/** * Tests for billing-related PrintModal request payloads. */ import React from 'react'; import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { server } from '../mocks/server'; import { ThemeProvider } from '../../contexts/ThemeContext'; const mockShowToast = vi.fn(); const mockUseAuth = { user: { id: 1, username: 'finance-user', permissions: ['cost_centers:read_own', 'printers:control'] }, authEnabled: true, requiresSetup: false, loading: false, isAdmin: false, login: vi.fn(), loginWithToken: vi.fn(), logout: vi.fn(), refreshUser: vi.fn(), refreshAuth: vi.fn(), hasPermission: vi.fn((permission: string) => permission === 'cost_centers:read_own' || permission === 'printers:control'), hasAnyPermission: vi.fn(() => true), hasAllPermissions: vi.fn(() => true), canModify: vi.fn(() => true), }; vi.mock('../../contexts/AuthContext', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useAuth: () => mockUseAuth, }; }); vi.mock('../../contexts/ToastContext', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useToast: () => ({ showToast: mockShowToast }), }; }); import { PrintModal } from '../../components/PrintModal'; function createTestQueryClient() { return new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 }, mutations: { retry: false }, }, }); } function renderWithProviders(ui: React.ReactElement) { const queryClient = createTestQueryClient(); return render( {ui} ); } const mockPrinters = [ { id: 1, name: 'X1 Carbon', model: 'X1C', ip_address: '192.168.1.100', enabled: true, is_active: true }, ]; const mockQueueItem = { id: 9, printer_id: 1, archive_id: 1, position: 1, scheduled_time: null, require_previous_success: false, auto_off_after: false, gcode_injection: false, manual_start: false, ams_mapping: null, plate_id: null, bed_levelling: true, flow_cali: false, vibration_cali: true, layer_inspect: false, timelapse: false, use_ams: true, status: 'pending', started_at: null, completed_at: null, error_message: null, created_at: '2024-01-01T00:00:00Z', archive_name: 'Billing Print', archive_thumbnail: null, printer_name: 'X1 Carbon', print_time_seconds: 3600, batch_id: null, batch_name: null, cost_center_id: 42, estimated_cost: 12.34, }; describe('PrintModal billing payloads', () => { beforeEach(() => { vi.clearAllMocks(); server.use( http.get('/api/v1/settings/', () => { return HttpResponse.json({ currency: 'USD', default_filament_cost: 25, billing_enabled: true, default_bed_levelling: true, default_flow_cali: false, default_vibration_cali: true, default_layer_inspect: false, default_timelapse: false, stagger_group_size: 2, stagger_interval_minutes: 5, per_printer_mapping_expanded: false, date_format: 'system', time_format: 'system', }); }), http.get('/api/v1/printers/', () => HttpResponse.json(mockPrinters)), http.get('/api/v1/printers/:id/status', () => { return HttpResponse.json({ connected: true, state: 'IDLE', ams: [], vt_tray: [] }); }), http.get('/api/v1/archives/:id', () => { return HttpResponse.json({ id: 1, sliced_for_model: null }); }), http.get('/api/v1/archives/:id/plates', () => { return HttpResponse.json({ is_multi_plate: false, plates: [] }); }), http.get('/api/v1/archives/:id/filament-requirements', () => { return HttpResponse.json({ filaments: [] }); }), http.get('/api/v1/finance/cost-centers/mine', () => { return HttpResponse.json([ { id: 42, name: 'Lab', is_private: false, owner_user_id: null, is_active: true, total_balance: 0, total_budget: 100, monthly_budget: 100, budget_mode: 'monthly', budget_limit: 100, budget_used: 0, budget_available: 88, can_print: true, }, ]); }), http.patch('/api/v1/queue/:id', async ({ request }) => { const body = await request.json() as Record; expect(body.cost_center_id).toBe(42); expect(body.estimated_cost).toBe(12.34); return HttpResponse.json({ id: 9, status: 'pending' }); }) ); }); it('includes the selected cost center and estimate when saving an edit-queue item', async () => { const user = userEvent.setup(); renderWithProviders( ); await waitFor(() => { expect(screen.getByText('Lab')).not.toBeNull(); }); await user.click(screen.getByRole('button', { name: /save/i })); await waitFor(() => { expect(mockShowToast).toHaveBeenCalled(); }); }); it('labels a cost center without a budget as unlimited', async () => { server.use( http.get('/api/v1/finance/cost-centers/mine', () => { return HttpResponse.json([ { id: 42, name: 'Unlimited Lab', is_private: false, owner_user_id: null, is_active: true, total_balance: -25, total_budget: null, monthly_budget: null, budget_mode: 'none', budget_limit: null, budget_used: null, budget_available: null, can_print: true, }, ]); }), ); renderWithProviders( , ); expect(await screen.findByText('Unlimited – no budget limit is set.')).not.toBeNull(); }); });