Skip to content

Latest commit

 

History

History
84 lines (57 loc) · 13 KB

File metadata and controls

84 lines (57 loc) · 13 KB

Тестовое задание для стажёра Backend

Бизнес контекст

В компании нужен единый сервис бронирования переговорок: администраторы создают переговорки и настраивают расписание их доступности (например, по дням недели и времени), система сама формирует слоты для бронирования на основе расписания. Сотрудники просматривают свободные слоты и создают или отменяют брони.

Описание задачи

Необходимо реализовать сервис, который позволит:

Администратору:

  • Создавать переговорки (только admin)
  • Создавать расписание доступности переговорки один раз (дни недели, время начала и окончания). После создания расписание изменить нельзя. Длительность одного слота фиксирована — 30 минут. Слоты система формирует сама на основе расписания (например, на скользящее окно дат или при запросе списка доступных слотов — необходимо самостоятельно выбрать подход и обосновать в README). (только admin)
  • Просматривать список всех броней с поддержкой пагинации (только admin)

Пользователю:

  • Просматривать список переговорок (admin и user)
  • Просматривать доступные для бронирования слоты по переговорке и дате (admin и user)
  • Создавать бронь на слот от своего имени (только user)
  • Отменять свою бронь (только user)
  • Просматривать список своих броней (только user)

Основные бизнес-ограничения:

  • Один слот может быть занят только одной активной бронью.
  • Слоты одной переговорки не должны пересекаться по времени.
  • Если для переговорки нет расписания, слоты для неё не создаются — такая переговорка считается всегда недоступной для бронирования.
  • Все даты и время хранятся и передаются в UTC.
  • Администратор не может создавать брони — бронирование доступно только роли user.
  • Отмена брони является идемпотентной операцией: повторный вызов на уже отменённой брони не является ошибкой и возвращает 200 с актуальным состоянием брони.
  • В 99.9% случаев пользователи запрашивают для бронирования доступные слоты в пределах ближайших 7 дней от текущей даты.
  • Нельзя создать бронь на слот, временной интервал которого находится в прошлом — такой запрос возвращает ошибку 400.
  • Список броней текущего пользователя (/bookings/my) возвращает только брони на будущие слоты; брони на уже прошедшие слоты в ответ не включаются.

Общие вводные

Пользователь — участник системы с уникальным идентификатором, email, ролью (admin / user) и датой регистрации.

Переговорка (Room) — ресурс с уникальным идентификатором, названием, опционально — описанием и вместимостью. Управление переговорками доступно только роли admin.

Расписание (Schedule) — правило доступности переговорки: к какой переговорке относится, в какие дни недели и в каком временном диапазоне (начало и конец) она доступна для бронирования. Длительность одного слота фиксирована — 30 минут.

Слот (Slot) — временной интервал в рамках одной переговорки (начало и конец, date-time в UTC), сформированный системой по расписанию. В один момент времени слот может быть либо свободен, либо занят одной активной бронью. Слоты должны иметь стабильные UUID-идентификаторы, сохранённые в базе данных — иначе бронирование по slotId невозможно.

Бронь (Booking) — связь пользователя со слотом. Имеет статус active или cancelled. Только одна активная бронь на один слот. При отмене статус меняется на cancelled, слот становится свободным.

Авторизация: для получения токена доступа к API используется эндпоинт /dummyLogin, который выдаёт тестовый JWT по указанной роли (admin / user). Эндпоинт возвращает фиксированный UUID пользователя для каждой роли: один и тот же UUID для всех запросов с ролью admin и один и тот же UUID для всех запросов с ролью user. Это позволяет стабильно тестировать сценарии, требующие проверки владельца брони. Полноценные эндпоинты регистрации и авторизации по email/паролю являются дополнительным заданием (см. ниже).

Условия

  • Используйте API из файла api.yaml. Реализация должна соответствовать описанной в нём спецификации.
  • Объём данных: до 50 переговорок, до 1k слотов в день, до 10k пользователей, до 100k броней. RPS — 100, SLI успешности ответа — 99.9%. Самый высоконагруженный эндпоинт — получение списка доступных для бронирования слотов по переговорке и дате; требования по времени ответа в первую очередь должны учитывать именно этот эндпоинт (ориентир SLI времени ответа — 200 мс для данного эндпоинта).
  • Для авторизации должен использоваться JWT. Тестовый токен доступа выдаётся через /dummyLogin по указанной роли (admin / user). JWT должен содержать как минимум user_id (UUID) и role. При создании брони user_id берётся из токена, а не из тела запроса.
  • Реализуйте покрытие бизнес-сценариев юнит-тестами. Общее тестовое покрытие проекта должно превышать 40%.
  • Реализуйте интеграционный или E2E-тест на сценарий: создание переговорки → создание расписания → создание брони пользователем.
  • Реализуйте интеграционный или E2E-тест на сценарий отмены брони пользователем.
  • Реализуйте GET API метод /_info, который должен возвращать на любое запрос 200.

Дополнительные задания

Эти задания не являются обязательными, но выполнение всех или части из них даст вам преимущество перед другими кандидатами.

  • Регистрация и авторизация по email/паролю. Реализовать эндпоинты /register и /login с выдачей JWT. При регистрации создаётся пользователь в БД; при входе проверяется email и хеш пароля. Эндпоинт /dummyLogin является обязательным в любом случае и должен быть реализован независимо от этого задания.
  • Опциональное создание ссылки на конференцию при бронировании. В эндпоинт создания брони добавить опциональный параметр (например, createConferenceLink: true). При его передаче необходимо выполнить запрос к внешнему сервису («Conference Service»), получить ссылку на конференцию и сохранить её в связи с созданной бронью. Реализовывать реальный внешний сервис не требуется — достаточно создать мок, имитирующий его поведение. Необходимо продумать поведение системы при сбоях: самостоятельно смоделируйте возможные случаи (недоступность внешнего сервиса, ошибка после успешного ответа и т.п.) и опишите принятые решения в README.
  • Makefile. Написать Makefile с командами: запуск проекта со всеми зависимостями (make up) и наполнение БД тестовыми данными (make seed).
  • Swagger-документация. Настроить кодогенерацию Swagger-документации на основе аннотаций в коде (например, swaggo/swag для Go или аналог для выбранного языка).
  • CI в GitHub Actions. Настроить пайплайн: сборка приложения, запуск тестов, подсчёт тестового покрытия. Статусы джобов должны отображаться в заголовке README.md.
  • Нагрузочное тестирование. Провести нагрузочное тестирование полученного решения и приложить краткие результаты к решению.
  • Конфигурация линтера. Описать конфигурацию линтера (.golangci.yaml в корне проекта для Go или аналог для выбранного вами языка).

Требования по стеку

Язык сервиса: предпочтительным является Go, но также допустимы следующие языки: PHP, Java, Python, C#.

База данных: рекомендуется использовать PostgreSQL, но также допустимо использовать MySQL.

Для деплоя зависимостей и самого сервиса используйте Docker Compose. Порт доступа к сервису должен быть 8080 и быть доступен снаружи как localhost:8080.

Ход решения

Если у вас возникнут вопросы по заданию, ответы на которые вы не найдёте в описанных «Условиях», вы можете принимать решения самостоятельно. В таком случае приложите к проекту README-файл, в котором будет список вопросов и пояснения, как вы решили проблему и почему именно выбранным способом.

Оформление решения

Необходимо предоставить ссылку на свой репозиторий в форму для сбора заданий.