XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { App } from '../app';
import { ServerProvider } from '../context/server-context';
import { ApiClient } from '../lib/api';
import { I18nProvider } from '../lib/i18n';
import type { StreamEvent } from '../types';

describe('policy draft editor', () => {
  afterEach(() => {
    location.hash = '';
  });

  it('builds multiple visual groups and atomically applies the validated draft', async () => {
    localStorage.setItem('xfesm.locale', 'en-US');
    location.hash = '/policies';
    const client = new ApiClient();
    vi.spyOn(client, 'session').mockResolvedValue({
      authenticated: true,
      csrfToken: 'csrf',
      actor: { id: 'owner', displayName: 'owner', roles: ['owner'], permissions: ['policy_publish'], totpVerified: true },
    });
    vi.spyOn(client, 'status').mockResolvedValue({ sampledAt: '2026-01-01T00:00:00Z', uptimeSeconds: 1, playersOnline: 0 });
    vi.spyOn(client, 'features').mockResolvedValue({ consoleEnabled: false, moderationEnabled: false, claimsEnabled: false, worldTrackingEnabled: false, rollbackEnabled: false });
    vi.spyOn(client, 'subscribe').mockReturnValue(() => undefined);
    vi.spyOn(client, 'policies').mockResolvedValue({
      items: [{ id: 'policy-3', version: 3, name: 'active', state: 'ACTIVE', ruleCount: 1, updatedAt: '2026-01-01T00:00:00Z' }],
    });
    vi.spyOn(client, 'policyCommands').mockResolvedValue({ commands: [] });
    vi.spyOn(client, 'policy').mockResolvedValue({
      id: 'policy-3',
      version: 3,
      name: 'active',
      state: 'ACTIVE',
      ruleCount: 1,
      updatedAt: '2026-01-01T00:00:00Z',
      checksum: 'sha256',
      rules: [{ id: 'deny-kill', enabled: true } as never],
      validation: { valid: true, errors: [], warnings: [] },
    });
    const stage = vi.spyOn(client, 'stagePolicy').mockResolvedValue({
      id: 'operation-1', kind: 'policy.draft.stage', state: 'SUCCEEDED',
      policy: { id: 'policy-4', version: 4, name: 'draft', state: 'DRAFT', groupCount: 2, ruleCount: 2, updatedAt: '2026-01-01T00:01:00Z' },
    });
    const publish = vi.spyOn(client, 'publishPolicy').mockResolvedValue({
      id: 'operation-2', kind: 'policy.publish', state: 'SUCCEEDED',
    });
    const user = userEvent.setup();

    render(<I18nProvider><ServerProvider client={client}><App /></ServerProvider></I18nProvider>);

    await user.click(await screen.findByRole('button', { name: 'Edit' }));
    const ruleId = await screen.findByLabelText('规则 ID');
    expect(ruleId).toHaveValue('deny-kill');
    fireEvent.change(ruleId, { target: { value: 'deny-give' } });
    await user.click(screen.getByRole('button', { name: '新增策略组 / Add group' }));
    expect(screen.getAllByLabelText('策略组 ID')).toHaveLength(2);
    fireEvent.change(screen.getByLabelText('Draft description and audit reason'), { target: { value: 'Restrict give' } });
    fireEvent.keyDown(window, { key: 's', ctrlKey: true });

    await waitFor(() => expect(stage).toHaveBeenCalled());
    expect(stage.mock.calls[0][0]).toBe(3);
    expect(stage.mock.calls[0][1]).toBe('Restrict give');
    expect(stage.mock.calls[0][2][0]).toMatchObject({
      id: 'deny-give', enabled: true, tier: 'DEFAULT', effect: 'DENY',
      groupId: 'default', groupEnabled: true,
      subject: { actorIds: [], roles: [], minimumOpLevel: null, maximumOpLevel: null },
      command: { commandIds: [], pathPrefix: [] },
    });
    expect(new Set(stage.mock.calls[0][2].map((rule) => rule.groupId)).size).toBe(2);
    await waitFor(() => expect(publish).toHaveBeenCalledWith('policy-4', 4, 'Restrict give'));
    expect(await screen.findByText(/Policy published atomically and applied immediately/)).toBeInTheDocument();
  });

  it('searches and adds live trigger commands without duplicating equivalent manual command ids', async () => {
    localStorage.setItem('xfesm.locale', 'zh-CN');
    location.hash = '/policies';
    const client = new ApiClient();
    let emit: ((event: StreamEvent) => void) | undefined;
    vi.spyOn(client, 'session').mockResolvedValue({
      authenticated: true,
      csrfToken: 'csrf',
      actor: { id: 'owner', displayName: 'owner', roles: ['owner'], permissions: ['policy_publish'], totpVerified: true },
    });
    vi.spyOn(client, 'status').mockResolvedValue({ sampledAt: '2026-01-01T00:00:00Z', uptimeSeconds: 1, playersOnline: 0 });
    vi.spyOn(client, 'features').mockResolvedValue({ consoleEnabled: false, moderationEnabled: false, claimsEnabled: false, worldTrackingEnabled: false, rollbackEnabled: false });
    vi.spyOn(client, 'subscribe').mockImplementation((listener) => {
      emit = listener;
      return () => undefined;
    });
    vi.spyOn(client, 'policies').mockResolvedValue({
      items: [{ id: 'policy-7', version: 7, name: 'active', state: 'ACTIVE', ruleCount: 1, updatedAt: '2026-01-01T00:00:00Z' }],
    });
    vi.spyOn(client, 'policy').mockResolvedValue({
      id: 'policy-7', version: 7, name: 'active', state: 'ACTIVE', ruleCount: 1,
      updatedAt: '2026-01-01T00:00:00Z', checksum: 'sha256',
      rules: [{ id: 'command-rule', enabled: true, command: { commandIds: ['/notice'], pathPrefix: [] } } as never],
      validation: { valid: true, errors: [], warnings: [] },
    });
    const catalog = vi.spyOn(client, 'policyCommands').mockResolvedValue({ commands: [
      {
        id: 'minecraft:notice', root: 'notice', aliases: ['announce'], source: 'server',
        usages: ['/notice <message>'],
      },
      {
        id: 'minecraft:welcome', root: 'welcome', source: 'trigger', triggerId: 'trigger-welcome',
        triggerName: '欢迎指令', arguments: ['target', 'message'],
        usages: ['/welcome <target> <message>'],
      },
    ] });
    const user = userEvent.setup();

    render(<I18nProvider><ServerProvider client={client}><App /></ServerProvider></I18nProvider>);

    await user.click(await screen.findByRole('button', { name: '编辑' }));
    await waitFor(() => expect(catalog).toHaveBeenCalledOnce());
    await user.click(screen.getByRole('button', { name: '添加命令 / Add command' }));
    const dialog = await screen.findByRole('dialog', { name: '命令目录 / Command catalog' });
    expect(dialog.closest('.page')).toBeNull();

    await user.type(screen.getByRole('searchbox', { name: '搜索命令 / Search commands' }), 'target');
    expect(screen.getByText('minecraft:welcome')).toBeInTheDocument();
    await user.click(screen.getByRole('button', { name: '添加命令 minecraft:welcome' }));
    expect(screen.getByLabelText('命令 ID(如 minecraft:kill)')).toHaveValue('/notice\nminecraft:welcome');
    expect(screen.getByRole('button', { name: '命令 minecraft:welcome 已添加' })).toBeDisabled();

    await user.clear(screen.getByRole('searchbox', { name: '搜索命令 / Search commands' }));
    await user.selectOptions(screen.getByLabelText('命令来源 / Command source'), 'server');
    await user.type(screen.getByRole('searchbox', { name: '搜索命令 / Search commands' }), 'minecraft:notice');
    expect(screen.getByRole('button', { name: '命令 minecraft:notice 已添加' })).toBeDisabled();
    expect(screen.getByLabelText('命令 ID(如 minecraft:kill)')).toHaveValue('/notice\nminecraft:welcome');

    await waitFor(() => expect(emit).toBeDefined());
    act(() => emit?.({ type: 'triggers-changed', data: {} }));
    await waitFor(() => expect(catalog).toHaveBeenCalledTimes(2));

    await user.keyboard('{Escape}');
    expect(screen.queryByRole('dialog', { name: '命令目录 / Command catalog' })).not.toBeInTheDocument();
  });
});