# LiquidCode Tester

Система автоматической проверки решений задач по программированию (Online Judge) для платформы LiquidCode.

## Архитектура

Проект состоит из трёх основных компонентов:

### 1. Gateway (Шлюз)
- **Технология**: ASP.NET Core Web API
- **Функции**:
  - Приём запросов на тестирование через REST API
  - Скачивание пакетов задач из Polygon
  - Маршрутизация запросов к соответствующим Worker'ам по языку программирования

### 2. Worker (Универсальный тестировщик)
- **Технология**: ASP.NET Core Web API + компиляторы всех поддерживаемых языков
- **Функции**:
  - Динамический выбор компилятора/интерпретатора на основе языка
  - Компиляция пользовательского кода (C++, Java, Kotlin, C#) или подготовка к исполнению (Python)
  - Запуск в изолированной среде
  - Тестирование на наборе тестов
  - Контроль ограничений по времени и памяти
  - Отправка статусов на callback URL

### 3. Common (Общая библиотека)
- **Технология**: .NET Class Library
- **Функции**: Общие модели данных для Gateway и Worker

## Поддерживаемые языки

- **C++** (реализовано)
- **Java** (реализовано)
- **Kotlin** (реализовано)
- **C#** (реализовано)
- **Python** (реализовано)

## Модель данных

### SubmitForTesterModel (запрос на тестирование)
```csharp
public record SubmitForTesterModel(
    long Id,
    long MissionId,
    string Language,
    string LanguageVersion,
    string SourceCode,
    string PackageUrl,
    string CallbackUrl
);
```

### TesterResponseModel (результат тестирования)
```csharp
public record TesterResponseModel(
    long SubmitId,
    State State,          // Waiting, Compiling, Testing, Done
    ErrorCode ErrorCode,  // None, CompileError, RuntimeError, etc.
    string Message,
    int CurrentTest,
    int AmountOfTests
);
```

## Быстрый старт

### Локальная разработка (Docker Compose)

```bash
# Сборка и запуск всех сервисов
docker-compose up --build

# Gateway доступен на http://localhost:8080
# Worker доступен на http://localhost:8081
```

### Разработка без Docker

```bash
# Сборка всего решения
dotnet build

# Запуск Gateway
cd src/LiquidCode.Tester.Gateway
dotnet run

# Запуск Worker (в другом терминале)
cd src/LiquidCode.Tester.Worker
dotnet run
```

## Локальное тестирование

Для тестирования системы без внешних зависимостей используйте endpoint `/api/Tester/submit-local`:

### 1. Подготовка тестового пакета

Создайте ZIP файл с тестами в формате:
```
test-package.zip
├── 1.in      # Входные данные для теста 1
├── 1.out     # Ожидаемый результат для теста 1
├── 2.in      # Входные данные для теста 2
├── 2.out     # Ожидаемый результат для теста 2
└── ...
```

### 2. Отправка запроса

```bash
curl -X 'POST' \
  'http://localhost:8080/api/Tester/submit-local' \
  -F 'id=1' \
  -F 'missionId=1' \
  -F 'language=python' \
  -F 'languageVersion=3.11' \
  -F 'sourceCode=n = int(input())
print(n * 2)' \
  -F 'callbackUrl=log' \
  -F 'package=@./test-package.zip'
```

### 3. Просмотр результатов

При использовании `callbackUrl=log` результаты тестирования выводятся в консоль Worker:

```
╔═══════════════════════════════════════════════════════════════╗
║                    CALLBACK RESULT                            ║
╠═══════════════════════════════════════════════════════════════╣
{
  "submitId": 1,
  "state": "Done",
  "errorCode": "None",
  "message": "All tests passed",
  "currentTest": 3,
  "amountOfTests": 3
}
╚═══════════════════════════════════════════════════════════════╝
```

### Специальные значения callbackUrl

- `"log"` - Вывод результатов в логи Worker
- `"console"` - Аналогично "log"
- `"log://"` - Аналогично "log"
- Любой HTTP URL - Отправка результатов на указанный endpoint

## API Endpoints

### Gateway

**POST /api/tester/submit**

Отправка решения с удаленным пакетом тестов:

```json
{
  "id": 123,
  "missionId": 456,
  "language": "C++",
  "languageVersion": "17",
  "sourceCode": "#include <iostream>\nint main() { ... }",
  "packageUrl": "https://example.com/package.zip",
  "callbackUrl": "https://example.com/api/submit/callback"
}
```

**POST /api/tester/submit-local**

Отправка решения с локальным пакетом тестов (для отладки):

```bash
curl -X 'POST' \
  'http://localhost:8080/api/Tester/submit-local' \
  -F 'id=1' \
  -F 'missionId=1' \
  -F 'language=python' \
  -F 'languageVersion=3.11' \
  -F 'sourceCode=print("Hello, World!")' \
  -F 'callbackUrl=log' \
  -F 'package=@/path/to/test-package.zip'
```

**Параметры:**
- `id` - ID сабмита
- `missionId` - ID задачи
- `language` - Язык программирования (C++, Java, Kotlin, C#, Python)
- `languageVersion` - Версия языка (опционально, используется "latest" по умолчанию)
- `sourceCode` - Исходный код решения
- `callbackUrl` - URL для отправки результатов или `"log"` для вывода в консоль
- `package` - ZIP файл с тестами

**GET /api/tester/health**
- Проверка состояния Gateway

### Worker

**POST /api/test**
- Принимает multipart/form-data с метаданными и ZIP файлом пакета

**GET /api/test/health**
- Проверка состояния Worker

## Процесс тестирования

1. **Приём запроса**: Gateway получает запрос с кодом и URL пакета
2. **Скачивание пакета**: Gateway скачивает Polygon пакет
3. **Маршрутизация**: Gateway отправляет запрос в Worker для нужного языка
4. **Парсинг**: Worker распаковывает и парсит пакет (тесты, лимиты)
5. **Компиляция**: Worker выбирает нужный компилятор на основе языка и компилирует код
6. **Тестирование**: Worker последовательно запускает все тесты
7. **Callback**: Worker отправляет статусы на callback URL на каждом этапе
8. **Cleanup**: Worker удаляет временные файлы

## Изоляция и безопасность

### Текущая реализация
- Процессы запускаются с ограничением по времени
- Контроль памяти через PeakWorkingSet64
- Процессы убиваются при превышении лимитов

### Production рекомендации
- Linux namespaces (PID, network, mount)
- cgroups для контроля CPU и памяти
- seccomp для ограничения системных вызовов
- AppArmor/SELinux профили
- chroot окружение
- Отключение доступа к сети для тестируемого кода


## Конфигурация

### Gateway (appsettings.json)
```json
{
  "PackageDownloadDirectory": "/tmp/packages",
  "Workers": {
    "Cpp": "http://localhost:8081",
    "Java": "http://localhost:8081",
    "Kotlin": "http://localhost:8081",
    "CSharp": "http://localhost:8081",
    "Python": "http://localhost:8081"
  }
}
```

### Worker (appsettings.json)
```json
{
  "Cpp": {
    "Compiler": "g++",
    "CompilerFlags": "-O2 -std=c++17 -Wall",
    "Versions": {
      "14": {
        "Compiler": "g++",
        "CompilerFlags": "-O2 -std=c++14 -Wall"
      },
      "17": {
        "Compiler": "g++",
        "CompilerFlags": "-O2 -std=c++17 -Wall"
      },
      "20": {
        "Compiler": "g++",
        "CompilerFlags": "-O2 -std=c++20 -Wall"
      }
    }
  },
  "Java": {
    "Compiler": "javac",
    "CompilerFlags": "",
    "Versions": {
      "8": {
        "Compiler": "javac",
        "CompilerFlags": "-source 8 -target 8"
      },
      "11": {
        "Compiler": "javac",
        "CompilerFlags": "-source 11 -target 11"
      },
      "17": {
        "Compiler": "javac",
        "CompilerFlags": ""
      }
    }
  },
  "Kotlin": {
    "Compiler": "kotlinc",
    "CompilerFlags": "",
    "Versions": {
      "1.9": {
        "Compiler": "kotlinc",
        "CompilerFlags": ""
      }
    }
  },
  "CSharp": {
    "Compiler": "csc",
    "CompilerFlags": "/optimize+",
    "Versions": {
      "7": {
        "Compiler": "csc",
        "CompilerFlags": "/optimize+ /langversion:7"
      },
      "8": {
        "Compiler": "csc",
        "CompilerFlags": "/optimize+ /langversion:8"
      },
      "9": {
        "Compiler": "csc",
        "CompilerFlags": "/optimize+ /langversion:9"
      }
    }
  },
  "Python": {
    "Executable": "python3",
    "Versions": {
      "3.8": {
        "Executable": "python3.8"
      },
      "3.9": {
        "Executable": "python3.9"
      },
      "3.10": {
        "Executable": "python3.10"
      },
      "3.11": {
        "Executable": "python3.11"
      }
    }
  }
}
```

#### Поддержка версий языков

Система поддерживает указание версии языка программирования через параметр `languageVersion`:

- **"latest"** или **null** - использует версию по умолчанию из основной конфигурации
- **Конкретная версия** (например, "17", "3.11") - использует конфигурацию из секции `Versions`
- **Несуществующая версия** - логируется предупреждение и используется версия по умолчанию

Каждый язык имеет свою конфигурацию с возможностью указания:
- Для компилируемых языков (C++, Java, Kotlin, C#): путь к компилятору и флаги компиляции
- Для интерпретируемых языков (Python): путь к интерпретатору

Пример использования:
```bash
# Использование Python 3.11
curl -X POST 'http://localhost:8080/api/Tester/submit-local' \
  -F 'languageVersion=3.11' \
  -F 'language=python' \
  ...

# Использование C++20
curl -X POST 'http://localhost:8080/api/Tester/submit-local' \
  -F 'languageVersion=20' \
  -F 'language=C++' \
  ...
```

## Структура проекта

```
LiquidCode.Tester/
├── src/
│   ├── LiquidCode.Tester.Common/        # Общие модели
│   │   └── Models/
│   ├── LiquidCode.Tester.Gateway/       # API Gateway
│   │   ├── Controllers/
│   │   ├── Services/
│   │   └── Dockerfile
│   └── LiquidCode.Tester.Worker/        # Универсальный Worker (все языки)
│       ├── Controllers/
│       ├── Services/
│       │   ├── Compilation/             # Компиляторы
│       │   ├── Execution/               # Исполнители
│       │   └── Factories/               # Фабрики
│       └── Dockerfile
├── compose.yaml                          # Docker Compose
└── README.md
```

## Требования

- .NET 9.0 SDK
- Docker & Docker Compose (для контейнеризации)
- Компиляторы языков программирования (устанавливаются автоматически в Docker):
  - g++ (C++)
  - OpenJDK 17 (Java)
  - Kotlin compiler (Kotlin)
  - Mono (C#)
  - Python 3

## Расширение на другие языки

Для добавления поддержки нового языка:

1. Создайте новую реализацию `ICompilationService` для языка (например, `GoCompilationService.cs`)
2. Создайте новую реализацию `IExecutionService` для языка (например, `GoExecutionService.cs`)
3. Зарегистрируйте новые сервисы в `Program.cs`:
   ```csharp
   builder.Services.AddSingleton<GoCompilationService>();
   builder.Services.AddSingleton<GoExecutionService>();
   ```
4. Обновите фабрики (`CompilationServiceFactory` и `ExecutionServiceFactory`), добавив поддержку нового языка
5. Добавьте конфигурацию языка в `appsettings.json` Worker
6. Обновите `Dockerfile` Worker, добавив установку компилятора/runtime
7. Добавьте конфигурацию воркера для нового языка в `appsettings.json` Gateway
8. Обновите `WorkerClientService` Gateway, добавив маппинг языка на URL воркера

## Разработка

### Структура кода
- **Controllers**: REST API endpoints
- **Services**: Бизнес-логика (компиляция, тестирование, callback)
- **Models**: Модели данных

### Логирование
Используется встроенный ILogger ASP.NET Core.

### Тестирование
```bash
dotnet test
```

## Лицензия

[Укажите лицензию]

## Авторы

LiquidCode Team
