Go: Контрактный тест обработчика и клиента питания — задача с проверкой

Практическая задача на Go по теме «Контрактный тест обработчика и клиента». Условие содержит контракт функций, предоставленные типы и требования к результату. Используйте выданный шаблон и сохраняйте его публичные объявления. Перед проверкой разберите успешный путь, обработку ошибок и отмену операции, когда она предусмотрена контрактом. Здесь оценивается конкретный компонент сервиса; в полном курсе микросервисов на Go эти компоненты связываются с HTTP, PostgreSQL, gRPC и Kafka.

Контрактный тест обработчика и клиента питания

Реализуйте тело функции VerifyNutritionContract(handler http.Handler, call CalculateFunc) error, которая проверяет, что переданные handler и call соблюдают единый HTTP-контракт сервиса расчёта калорий.

Объявления типов, которые уже даны:


type IngredientInput struct {
    IngredientID *string  `json:"ingredient_id"`
    Grams        *float64 `json:"grams"`
}
type CalculationRequest  struct { Ingredients []IngredientInput `json:"ingredients"` }
type CalculationResponse struct { Calories float64 `json:"calories"` }
type CalculateFunc func(context.Context, *http.Client, string, CalculationRequest) (CalculationResponse, error)
type transportFunc func(*http.Request) (*http.Response, error)
func (f transportFunc) RoundTrip(r *http.Request) (*http.Response, error)

Каркас готовит состав из двух ингредиентов — oats 50 г и banana 100 г — и выполняет все HTTP-вызовы и cleanup за вас. Ваша задача — заполнить условия проверок в предоставленных блоках TODO, не добавляя новых функций, серверов или клиентов.

Что должна проверять функция:

  1. Успешный ответ handler. Вызов POST /calculations должен вернуть статус 200 и Content-Type: application/json (charset допустим). Тело — ровно один JSON-объект с числовым полем calories равным

  2. Любое переименование поля, нулевое значение вместо числа, лишний текст после объекта или отсутствие поля — нарушение контракта.

  3. Ответ handler на неверный запрос. Запрос без поля grams обязан вернуть статус 400, Content-Type: application/json, и тело должно содержать только поле error со значением "invalid_request" — без поля calories.

  4. Корректность исходящего запроса клиента. Когда call обращается к fake-серверу, он обязан использовать метод POST, путь /calculations и заголовок Content-Type: application/json. Декодированное тело запроса должно содержать оба ингредиента с правильными ID и граммами.

  5. Разбор ответов fake-сервера клиентом. Четыре варианта fake-ответа проверяются отдельно:

    • {"calories":280} → успех, значение 280;
    • {"calories":280,"units":"kcal"} → успех, значение 280 (лишнее поле допустимо);
    • {"calories":73.5} → успех, значение 73.5;
    • {} → ошибка (поле calories обязательно, ноль не засчитывается).
  6. Отмена контекста. Если контекст уже отменён до вызова call, функция должна вернуть ошибку, удовлетворяющую errors.Is(err, context.Canceled), а не просто любую ошибку.

Дополните TODO в редакторе и отправьте весь код, сохранив package main, импорты, предоставленные типы и публичную функцию RunScenario. Не добавляйте свою функцию main: JSON-адаптер запуска платформа добавит сама. Скрытые входы и ожидаемые результаты находятся на стороне проверки.

Подсказки

  • Для проверки media type используйте mime.ParseMediaType — он отделяет тип от параметров вроде charset, поэтому сравнивать достаточно только базовую часть.
  • Чтобы убедиться, что в теле ровно один JSON-объект без хвоста, декодируйте его через json.Decoder и после успешного Decode вызовите Decode повторно — он должен вернуть io.EOF.
РешениеGo

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи