adk-gateway 1.0.0

Multi-channel AI gateway for adk-rust agents — Telegram, Slack, WhatsApp, Discord, Matrix + control panel
/**
 * UI Tests for AgentModel page — Task 7.6
 *
 * Tests verify:
 * - Cloud provider form persistence round-trip (Task 7.6)
 * - Cloud provider validation behavior (Task 7.6)
 * - Cloud config included in save payload (Task 7.6)
 *
 * @vitest-environment jsdom
 *
 * Prerequisites:
 *   npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import AgentModel from '../AgentModel';

// ── Mocks ──────────────────────────────────────────────────────────

const mockSaveAgent = vi.fn();

vi.mock('../../api/client', () => ({
  api: {
    getAgent: vi.fn().mockResolvedValue({
      ok: true,
      data: {
        primary: 'gpt-4o',
        cloud_provider: {
          type: 'azure',
          endpoint_url: 'https://my-resource.openai.azure.com/',
          api_version: '2024-02-01',
          deployment_name: 'gpt-4o-deploy',
        },
      },
    }),
    saveAgent: (...args: unknown[]) => mockSaveAgent(...args),
  },
}));

vi.mock('../../api/modelProviders', () => ({
  extractProvider: (modelId: string) => {
    const parts = modelId.split('/');
    return parts.length > 1 ? parts[0] : null;
  },
}));

vi.mock('../../components/ModelSelector', () => ({
  default: () => <div data-testid="model-selector" />,
}));

// ── Task 7.6: Cloud Provider Form Persistence ──────────────────────

describe('Cloud Provider Form Persistence', () => {
  beforeEach(() => {
    mockSaveAgent.mockClear();
    mockSaveAgent.mockResolvedValue({ ok: true, message: 'Saved.' });
  });

  it('populates cloud config fields from API response on load', async () => {
    render(<AgentModel />);

    await waitFor(() => {
      // Should show the enterprise cloud provider section with pre-filled values
      const endpointInput = screen.getByDisplayValue('https://my-resource.openai.azure.com/');
      expect(endpointInput).toBeInTheDocument();
    });

    expect(screen.getByDisplayValue('2024-02-01')).toBeInTheDocument();
    expect(screen.getByDisplayValue('gpt-4o-deploy')).toBeInTheDocument();
  });

  it('includes cloud_provider in save payload', async () => {
    render(<AgentModel />);

    await waitFor(() => {
      expect(screen.getByDisplayValue('https://my-resource.openai.azure.com/')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText('Save Configuration'));

    await waitFor(() => {
      expect(mockSaveAgent).toHaveBeenCalledWith(
        expect.objectContaining({
          cloud_provider: expect.objectContaining({
            type: 'azure',
            endpoint_url: 'https://my-resource.openai.azure.com/',
            api_version: '2024-02-01',
            deployment_name: 'gpt-4o-deploy',
          }),
        }),
      );
    });
  });

  it('shows validation errors when required fields are empty', async () => {
    const { api } = await import('../../api/client');
    (api.getAgent as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      ok: true,
      data: {
        primary: null,
        cloud_provider: { type: 'azure' },
      },
    });

    render(<AgentModel />);

    await waitFor(() => {
      expect(screen.getByText('Save Configuration')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText('Save Configuration'));

    await waitFor(() => {
      expect(screen.getByText('Endpoint URL is required')).toBeInTheDocument();
      expect(screen.getByText('API Version is required')).toBeInTheDocument();
      expect(screen.getByText('Deployment Name is required')).toBeInTheDocument();
    });

    // Save should not have been called
    expect(mockSaveAgent).not.toHaveBeenCalled();
  });

  it('switches cloud provider and shows correct fields', async () => {
    render(<AgentModel />);

    await waitFor(() => {
      expect(screen.getByText('Google Vertex AI')).toBeInTheDocument();
    });

    // Click GCP provider
    fireEvent.click(screen.getByText('Google Vertex AI'));

    await waitFor(() => {
      expect(screen.getByText('Project ID')).toBeInTheDocument();
      expect(screen.getByText('Region')).toBeInTheDocument();
      expect(screen.getByText('Model Name')).toBeInTheDocument();
    });
  });

  it('validates GCP fields when GCP is selected', async () => {
    const { api } = await import('../../api/client');
    (api.getAgent as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      ok: true,
      data: {
        primary: null,
        cloud_provider: { type: 'gcp' },
      },
    });

    render(<AgentModel />);

    await waitFor(() => {
      expect(screen.getByText('Save Configuration')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText('Save Configuration'));

    await waitFor(() => {
      expect(screen.getByText('Project ID is required')).toBeInTheDocument();
      expect(screen.getByText('Region is required')).toBeInTheDocument();
      expect(screen.getByText('Model Name is required')).toBeInTheDocument();
    });
  });

  it('validates Bedrock fields when Bedrock is selected', async () => {
    const { api } = await import('../../api/client');
    (api.getAgent as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      ok: true,
      data: {
        primary: null,
        cloud_provider: { type: 'bedrock' },
      },
    });

    render(<AgentModel />);

    await waitFor(() => {
      expect(screen.getByText('Save Configuration')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText('Save Configuration'));

    await waitFor(() => {
      expect(screen.getByText('Region is required')).toBeInTheDocument();
      expect(screen.getByText('AWS Profile is required')).toBeInTheDocument();
      expect(screen.getByText('Model ARN is required')).toBeInTheDocument();
    });
  });
});