├── .github └── workflows │ └── test.yaml ├── README.md ├── solution ├── .dockerignore ├── .gitignore ├── Dockerfile ├── app.py └── requirements.txt └── tests ├── init-database.sh ├── openapi.yml └── public-tests.json /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: deployment 4 | 5 | permissions: 6 | contents: read 7 | packages: write 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: cu-backend 13 | timeout-minutes: 10 14 | if: github.actor != 'github-classroom[bot]' 15 | container: 16 | image: gcr.io/kaniko-project/executor:debug 17 | steps: 18 | - name: Login to ghcr.io 19 | run: > 20 | echo "{\"auths\": {\"ghcr.io\": {\"auth\": \"$(echo -n "$AUTH" | base64 -w 0)\"}}}" 21 | > /kaniko/.docker/config.json 22 | env: 23 | AUTH: "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" 24 | 25 | - name: Build solution image 26 | run: > 27 | /kaniko/executor 28 | --context="${{ github.repositoryUrl }}#refs/heads/main#${{ github.sha }}" 29 | --context-sub-path=solution 30 | --destination="$(echo -n "$REPO:run-${{ github.run_id }}" | tr '[:upper:]' '[:lower:]')" 31 | --destination="$(echo -n "$REPO:latest" | tr '[:upper:]' '[:lower:]')" 32 | --label org.opencontainers.image.source=https://github.com/${{ github.repository }} 33 | env: 34 | GIT_USERNAME: kaniko 35 | GIT_PASSWORD: "${{ secrets.GITHUB_TOKEN }}" 36 | REPO: "ghcr.io/${{ github.repository }}" 37 | 38 | tests: 39 | name: Tests 40 | runs-on: ubuntu-22.04 41 | needs: build 42 | timeout-minutes: 10 43 | if: github.actor != 'github-classroom[bot]' 44 | steps: 45 | - uses: Central-University-IT/setup-test-backend@v1 46 | 47 | - uses: docker/login-action@v3.0.0 48 | with: 49 | registry: ghcr.io 50 | username: ${{ github.actor }} 51 | password: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | - run: > 54 | IMAGE_SOLUTION="$(echo "ghcr.io/${{ github.repository }}:run-${{ github.run_id }}" | tr '[:upper:]' '[:lower:]')" 55 | IMAGE_POSTGRES="$(echo "ghcr.io/${{ github.repository_owner }}/postgres:16.1-alpine3.19" | tr '[:upper:]' '[:lower:]')" 56 | /usr/local/bin/checker 57 | continue-on-error: true 58 | 59 | - uses: actions/upload-artifact@v4.0.0 60 | with: 61 | name: result 62 | path: ./result.json 63 | if-no-files-found: error 64 | compression-level: 0 65 | 66 | - uses: bots-house/ghcr-delete-image-action@v1.1.0 67 | continue-on-error: true 68 | with: 69 | owner: ${{ github.repository_owner }} 70 | name: ${{ github.event.repository.name }} 71 | token: ${{ secrets.GITHUB_TOKEN }} 72 | tag: run-${{ github.run_id }} 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PROD v2: Pulse Backend 2 | 3 | Ваши коллеги разрабатывают социальную сеть для инвесторов. Уже совсем скоро им нужно сдавать проект, а вся команда бэкенд-разработчиков ушла в отпуск. 4 | 5 | Кто-то проболтался о том, что вы знакомы с Git, HTTP, Docker, PostgreSQL и e2e-тестами. Это именно то, что нужно ребятам (а если с чем-то не знакомы, они рассчитывают на ваши навыки поиска информации)! Помогите коллегам успеть завершить проект до дедлайна и реализуйте новое HTTP API :) 6 | 7 | Результатом выполнения данного задания является Github репозиторий с исходным кодом приложения (директория `solution`). 8 | 9 | ## Про приложение 10 | 11 | Приложение должно представлять из себя HTTP сервер, реализующий необходимое [API](./tests/openapi.yml). В наследие от предыдущей команды вам достался инстанс PostgreSQL, который необходимо использовать для хранения данных. 12 | 13 | Приложение конфигурируется через переменные окружения: 14 | 15 | - `SERVER_ADDRESS` — хост и порт, которые будет _слушать_ запущенный HTTP сервер. Например, `0.0.0.0:8080`. 16 | 17 | - `SERVER_PORT` — содержит порт; запущенный сервер должен слушать IP `0.0.0.0` и указанный порт. Используйте эту переменную, если вам не подошел формат данных в переменной `SERVER_ADDRESS` (переданные параметры равнозначны). 18 | 19 | - `POSTGRES_CONN` — URL-строка для подключения к PostgreSQL в формате `postgres://{username}:{password}@{host}:{5432}/{dbname}`. 20 | 21 | - `POSTGRES_JDBC_URL` — JDBC-строка для подключения к PostgreSQL в формате `jdbc:postgresql://{host}:{port}/{dbname}`. 22 | 23 | - `POSTGRES_USERNAME` — имя пользователя для подключения к PostgreSQL. 24 | 25 | - `POSTGRES_PASSWORD` — пароль для подключения к PostgreSQL. 26 | 27 | - `POSTGRES_HOST` — хост для подключения к PostgreSQL (например, `localhost`). 28 | 29 | - `POSTGRES_PORT` — порт для подключения к PostgreSQL (например, `5432`). 30 | 31 | - `POSTGRES_DATABASE` — имя базы данных PostgreSQL, с которой должно работать приложение. 32 | 33 | - `RANDOM_SECRET` — псевдо-случайная последовательность из 128 символов (a-z, A-Z, 0-9), сгенерированная тестирующей системой. Можете использовать её, если вашему приложению необходим секретный ключ (например, для JWT). Если вам не требуется данное значение, можете его не использовать. 34 | 35 | Автор приложения сам выбирает, с какими из переменных окружения ему комфортно работать. 36 | 37 | Учитывая современные реалии, приложение будет запускаться через Docker контейнер. В репозитории присутствует Dockerfile, с помощью которого будет собираться образ приложения. 38 | Так как приложение совсем небольшое, мы обойдемся одним Docker контейнером, docker-compose определить не получится. 39 | 40 | **Список используемых зависимостей (и фреймворков) не ограничен** (любая версия языка программирования, без ограничений на библиотеки), однако вы должны убедиться, что необходимые зависимости загружаются и подключаются в Dockerfile. Вы сами в праве выбирать стек вашего приложения, от вас зависит успех всего проекта! 41 | 42 | Описание API находится ниже, но если вы хотите ознакомиться с точными требованиями, не стесняйтесь использовать Swagger и предоставленную [Open API спецификацию](./tests/openapi.yml). 43 | 44 | Тестирование решения происходит с помощью Github CI. Для отправки решения на тестирование необходимо обновить исходный код вашего репозитория на Github (git commit & git push). 45 | 46 | **Вы можете редактировать файлы в директории `solution` (и `.gitignore` в корне). Если в репозитории содержатся изменения в других файлах, решение не будет принято.** 47 | 48 | ## Оценивание 49 | 50 | Для получения баллов за группу тестов решение должно пройти все тесты из данной группы. 51 | 52 | Группы тестов могут зависеть друг от друга. Если группа B зависит от группы A, при тестировании группы B могут использоваться эндпоинты, участвовавшие в тестировании группы A. Это свойство транзитивно! 53 | 54 | | Название группы | Описание | Баллы | От каких групп зависит | 55 | |------------------|------------------------------------|-------|------------------------| 56 | | 01/ping | Успешный ответ на `/api/ping`. | 1 | | 57 | | 02/countries | Получение и фильтрация стран. | 6 | | 58 | | 03/auth/register | Регистрация пользователей. | 6 | - 02/countries | 59 | | 04/auth/sign-in | Аутентификация и получение токена. | 7 | - 03/auth/register | 60 | | 05/me | Получение и редактирование собственного профиля. | 8 | - 04/auth/sign-in | 61 | | 06/profiles | Получение профиля по логину. | 5 | - 04/auth/sign-in | 62 | | 07/password | Изменение пароля. | 7 | - 05/me | 63 | | 08/friends | Друзья! | 12 | - 04/auth/sign-in
- 06/profiles | 64 | | 09/posts/publish | Публикация поста и получение по ID. | 12 | - 05/me
- 08/friends | 65 | | 10/posts/feed | Получение новостной ленты. | 16 | - 09/posts/publish | 66 | | 11/posts/likes | Лайки и дизлайки. | 20 | - 10/posts/feed | 67 | 68 | В спорных ситуациях будет оцениваться качество кода. 69 | 70 | На данный момент в Github CI тестирование производится на публичном наборе тестов. Данные тесты помогают провалидировать минимальную логику приложения, **но не гарантируют прохождения финальных тестов**. 71 | 72 | ## Группы тестов 73 | 74 | ### Общие требования 75 | 76 | **У всех эндпоинтов есть префикс `/api`.** 77 | 78 | Обратите внимание, возврат успешного ответа на `GET /api/ping` является **обязательным условием для начала тестирования приложения**. 79 | 80 | Поступающие запросы и возвращаемые ответы должны соответствовать структуре и требованиям, описанным в [Open API](./tests/openapi.yml) спецификации. Обращайте внимание на ожидаемые status code, ограничения по длине и разрешенные символы в строках. 81 | 82 | Если структура запроса не соответствует требованиям и описанному формату, по умолчанию возвращается код ответа 400. 83 | Если указан более специфичный код ответа, используйте его. 84 | 85 | Если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным. 86 | 87 | ### 01/ping 88 | 89 | Достаточно реализовать возврат успешного ответа (с кодом `200`) на запрос `GET /api/ping`. Содержимое тела ответа при этом не валидируется, можно возвращать `"ok"`. 90 | 91 | Данная логика является блокирующей для всех остальных групп тестов. 92 | 93 | ### 02/countries 94 | 95 | Как и в любом большом проекте у нас есть собственный словарь стран, который используется при регистрации пользователей и может учитываться рекомендательными системами и системой локализации контента. 96 | 97 | Про каждую страну известны следующие данные: 98 | ```json 99 | { 100 | "name": "полное название", 101 | "alpha2": "двухбуквенный код страны (в верхнем регистре)", 102 | "alpha3": "трехбуквенный код страны", 103 | "region": "географический регион" 104 | } 105 | ``` 106 | 107 | Необходимо реализовать следующие эндпоинты: 108 | 109 | - `GET /countries` — получить список доступных стран, доступна фильтрация по регионам. 110 | 111 | - `GET /countries/{alpha2}` — получить страну по её уникальному двухбуквенному коду. 112 | 113 | Самое интересное: **для получения списка стран необходимо использовать предоставленную СУБД PostgreSQL**. 114 | 115 | Данные находятся в таблице `countries`, которая имеет следующее определение: 116 | ```sql 117 | CREATE TABLE countries ( 118 | id SERIAL PRIMARY KEY, 119 | name TEXT, 120 | alpha2 TEXT, 121 | alpha3 TEXT, 122 | region TEXT 123 | ); 124 | 125 | INSERT INTO countries (name, alpha2, alpha3, region) VALUES 126 | ('Åland Islands','AX','ALA','Europe'), 127 | ('Albania','AL','ALB','Europe'), 128 | ...; 129 | ``` 130 | 131 | При тестировании в Github CI база данных уже будет содержать нужный набор данных. Обратите внимание, данные в публичном и закрытом наборе тестов могут отличаться. **Приложение должно опираться на данные в СУБД, чтобы успешно пройти закрытые тесты.** 132 | 133 | Приложение вправе менять содержимое СУБД. Если вам требуются дополнительные таблицы, создавайте их самостоятельно при старте приложения (не забудьте про `IF NOT EXISTS`). 134 | 135 | При поиске страны по двухбуквенному коду можно реализовать регистрозависимый поиск, то есть пользователь всегда будет указывать значения в нужном регистре. 136 | 137 | ### 03/auth/register 138 | 139 | Эндпоинт `/auth/register` используется для первичной регистрации пользователей. 140 | 141 | Сервер должен поддерживать базу данных пользователей, валидировать запросы и не допускать наличия пользователей с эквивалентными регистрационными данными. 142 | 143 | Не храните пароль пользователей в [открытом виде](https://security.stackexchange.com/questions/36833/why-should-i-hash-passwords), используется хеширование (например, bcrypt). 144 | 145 | ### 04/auth/sign-in 146 | 147 | Эндпоинт `/auth/sign-in` предназначен для аутентификации пользователя по логину и паролю и генерации сессионного токена, 148 | который в дальнейшем будет использоваться для генерации запросов. 149 | 150 | Генерируемый токен должен уникально идентифицировать пользователя и быть сложным для подбора (можно использовать JWT). 151 | 152 | Данный токен в дальнейшем будет передаваться пользователем в заголовке `Authorization: Bearer {token}`, и приложение должно уметь понять, какой пользователь хочет сделать запрос. 153 | 154 | Временно будем считать, что время действия токена (TTL) должно составлять от 1 до 24 часов (на усмотрение разработчика). 155 | 156 | ### 05/me 157 | 158 | Эндпоинт `/me/profile` используется для получения и редактирования параметров собственного профиля пользователя. Действие зависит от указанного метода (`GET` и `PATCH`). 159 | 160 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. Например, `Authorization: Bearer $deddz$@pp...`. 161 | 162 | В запросе на редактирование профиля передаются значения только тех полей, которые необходимо обновить. 163 | 164 | ### 06/profiles 165 | 166 | Эндпоинт `/profiles/{login}` позволяет получить профиль другого пользователя по логину. 167 | 168 | Обратите внимание, в некоторых ситуациях профиль пользователя получить нельзя (в зависимости от значения параметра `isPublic`). Для получения дополнительных деталей ознакомьтесь со спецификацией API. 169 | 170 | В данной группе тестов не будет проверяться логика с друзьями пользователя. 171 | 172 | ### 07/password 173 | 174 | С помощью `/me/updatePassword` у пользователя появляется возможность изменить пароль от своего аккаунта. 175 | 176 | После изменения пароля: 177 | 178 | - Аутентификация со старым паролем становится невозможной. 179 | 180 | - Все ранее выпущенные токены должны быть отозваны. Использование старых токенов становится равнозначным использованию некорректных токенов. 181 | 182 | После успешной смены пароля при попытке получить свой профиль со старым токеном пользователь должен получать ошибку. 183 | 184 | ### 08/friends 185 | 186 | В приложении появляется возможность добавлять и удалять других пользователей из списка своих друзей. 187 | И конечно же можно посмотреть список своих друзей. 188 | 189 | Свойство быть другом — одностороннее. Если Петя добавит Машу в друзья, то профиль Пети становится доступным для Маши, даже если у Пети закрытый профиль. 190 | 191 | Чтобы не нагружать сервера и клиенты слишком сильно, в запросах на получение списка друзей используется пагинация. 192 | С помощью параметров `offset` и `limit` можно "постранично" получить весь список друзей, запрашивая данные порционно. 193 | 194 | Вам потребуется запоминать дату и время последнего добавления в друзья для корректно сортировки и реализации пагинации. 195 | 196 | ### 09/posts/publish 197 | 198 | В данной группе проверяется возможность создавать публикации со стороны пользователей. 199 | Затрагиваемые эндпоинты: 200 | - `/posts/new` 201 | - `/posts/{postId}` 202 | 203 | Сервер должен генерировать уникальные идентификаторы и запоминать время создания публикаций. 204 | 205 | У пользователя есть доступ к своим постам, постам пользователей с публичным профилем и постам других пользователей, которые добавили данного пользователя в друзья. 206 | 207 | В данной группе не проверяются поля с лайками и дизлайками. 208 | 209 | ### 10/posts/feed 210 | 211 | У пользователей появилась возможность смотреть новостную ленту со своими и чужими постами. Используя пагинацию :) 212 | 213 | Появляются запросы на `/posts/feed/my` и `/posts/feed/{login}` (значение `my` не может являться логином). 214 | 215 | В данной группе не проверяются поля с лайками и дизлайками. 216 | 217 | ### 11/posts/likes 218 | 219 | Самое интересное: пользователи могут поставить лайк и дизлайк публикации, к которой у них есть доступ. 220 | 221 | Всегда запоминается последняя реакция пользователя. Если пользователь поставил лайк два раза подряд, эффект лайка остается. 222 | Если пользователь поставил лайк, а потом дизлайк, остается реакция дизлайка. 223 | 224 | В полях `likesCount` и `dislikesCount` необходимо отразить число лайков и дизлайков публикации, при этом от каждого пользователя учитывается только его самая последняя реакция. 225 | 226 | ## Тестирование 227 | 228 | Для тестирования решения отразите ваши изменения в Github репозитории. Разрешено изменять только директорию `solution` и `.gitignore`, иначе тесты не будут запущены. 229 | 230 | ### Тестирование в CI 231 | 232 | Для тестирования решений используется [Github CI](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration). При отправке новых изменений в репозиторий на Github активируется тестирующий пайплайн. 233 | 234 | Пайплайн состоит из двух этапов: 235 | - Сборка Docker образа с вашим приложением (на основании исходного кода репозитория и Dockerfile). 236 | 237 | - Запуск тестов. Для каждой группы тестов 238 | - запускаются Docker контейнеры с вашим приложением и PostgreSQL; 239 | 240 | - тестирующая система применяет нужные миграции к запущенному PostgreSQL (создается и заполняется только таблица `countries`, остальное должно делать ваше приложение); 241 | 242 | - тестирующая система дожидается успешного (`200`) ответа на `GET /api/ping`, на это дается не более 10 секунд; 243 | 244 | - приложение считается запущенным и начинается запуск HTTP тестов из тестируемой группы. 245 | 246 | Проверьте, что ваше приложение готово запускать HTTP сервер на адресе, переданном в переменной окружения `SERVER_ADDRESS`. **В качестве хоста (IP) передается `0.0.0.0`, а не localhost или 127.0.0.1. Это важно!** 247 | 248 | Также проверьте локально, что Docker образ с вашим приложением собирается (выполните `docker build .` в директории `solution`). 249 | 250 | Существующие ограничения: 251 | 252 | - Решению выделяется 3 vCPU, 6 GB RAM и до 1 GB дискового пространства (не учитывая PostgreSQL). 253 | 254 | - В рамках тестирования ваше приложение не должно завершать работу (помните о защите от Exception, panic и прочих причинах аварийного завершения). 255 | 256 | - Сетевое взаимодействие разрешено только с PostgreSQL и тестирующей системой. Обращаться к сторонним ресурсам по сети нельзя. 257 | 258 | Во вкладке Actions можно найти лог тестирования, в котором будут отражены результаты запуска тестов на публичном наборе тестов. 259 | 260 | Прохождение публичного набора тестов не дает гарантию прохождения финальных тестов. 261 | 262 | ### Локальное тестирование 263 | 264 | Для локального тестирования вы можете пользоваться [Postman](https://www.postman.com/). В директории проекта кто-то из коллег оставил [Postman коллекцию](./tests/public-tests.json) с публичными тестами для API. Не забудьте переопределить `base_url` в переменных коллекции. 265 | 266 | Для инициализации СУБД PostgreSQL можно использовать [заранее подготовленный скрипт](./tests/init-database.sh), из которого можно выудить SQL запросы. Обратите внимание, данный файл предназначен для локального тестирования. Тестирующая система не использует данный файл. 267 | 268 | Чтобы локальное тестирование было максимально приближенным к тестированию в CI, мы рекомендуем запускать PostgreSQL и ваше приложение в Docker контейнерах (связанных одной сетью). 269 | 270 | ## Changelog 271 | 272 | Как это часто бывает, заказчики проекта вносят правки в требования! 273 | Ваших коллег ждала та же участь... Заказчики просили передать, что они будут стараться делать как можно меньше изменений. 274 | 275 | Но удача на нашей стороне! Коллеги будут фиксировать все правки в данном документе и вести ченджлог изменений. 276 | 277 | Не забывайте делать `git pull --rebase`, чтобы загрузить актуальные требования в локальную версию репозитория. 278 | 279 | ### 02.03.2024 280 | 281 | Коллеги, привет! Ваш Project Manager передал все опасения касательно сроков, поэтому мы договорились, 282 | что финальное тестирование будет проходить, опираясь на версию спецификации, опубликованную 3 марта 15:00 (МСК). 283 | 284 | Напоминаем! В тестах будет проверяться только то поведение, которое было описано в README либо спецификации. 285 | 286 | Обращаем внимание: при работе с публичным набором тестов в Postman обращайте внимание на содержимое вкладки Tests, именно там заключена логика тестирования. 287 | Request-path в Postman изменены на `GET /api/ping`, чтобы нерелевантная информация в логах не смущала вас. 288 | 289 | И еще немного полезных замечаний: 290 | 291 | - Если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным. 292 | 293 | - Если вам нужен секретный ключ, можете (необязательно!) использовать `RANDOM_SECRET`. 294 | 295 | - Timezone при передаче времени не так важна. Важно, чтобы счетчик времени монотонно рос и был одного формата во всех ответах backend'а. 296 | 297 | - Чтобы отобразить число лайков и дизлайков поста, учитывайте только последнюю реакцию от каждого пользователя. 298 | 299 | - Если структура ответа предполагает опциональность поля, сервер не должен возвращать данное поле при его отсутствии. 300 | 301 | ### 01.03.2024 302 | 303 | Коллеги, с первым днем весны! 304 | 305 | Напоминаем вам, что корректные логин, номер телефона, e-mail и другая подобная информация должны состоять минимум из одного символа! 306 | А длина уникального идентификатора публикации не превышает разумных значений... 307 | 308 | Также добавим, что в эндпоинте `/countries` если хотя бы один переданный регион является некорректным, весь запрос считается некорректным. Это общее правило: если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным. 309 | 310 | ### 28.02.2024 311 | 312 | Коллеги передали, что связь "друзья" является односторонней. 313 | 314 | Если профиль пользователя закрыт, доступ к его профилю и его публикациям появляется у пользователей, кого данный пользователь добавил в друзья. 315 | 316 | При этом если Маша добавила Петю в друзья, не значит, что Петя добавил Машу в друзья. Можно расценивать добавление в друзья как подписку. 317 | 318 | Группа `08/friends` зависит от группы `06/profiles`. 319 | 320 | ### 27.02.2024 321 | 322 | Коллеги, привет! Ничего критичного... Уговорили нашего Devops-инженера расширить список переменных с информацией для подключения к PostgreSQL. Смотрите секцию с описанием ENV переменных. Надеемся, теперь станет проще! 323 | 324 | Также подсветим, что приложение должно опираться на данные в СУБД, сохранить словарь в коде приложения не получится, так как список стран может меняться! Наши QA-специалисты любят проверять работу приложения в выдуманных странах... 325 | -------------------------------------------------------------------------------- /solution/.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | README.md 4 | .venv/ 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /solution/.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | __pycache__/ -------------------------------------------------------------------------------- /solution/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.1-alpine3.19 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt requirements.txt 6 | RUN pip3 install -r requirements.txt 7 | 8 | COPY . . 9 | 10 | ENV SERVER_PORT=8080 11 | 12 | CMD ["sh", "-c", "exec python3 -m flask run --host=0.0.0.0 --port=$SERVER_PORT"] -------------------------------------------------------------------------------- /solution/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/api/ping', methods=['GET']) 6 | def send(): 7 | return jsonify({"status": "ok"}), 200 8 | 9 | if __name__ == "__main__": 10 | app.run() 11 | -------------------------------------------------------------------------------- /solution/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.7.0 2 | click==8.1.7 3 | Flask==3.0.1 4 | itsdangerous==2.1.2 5 | Jinja2==3.1.3 6 | MarkupSafe==2.1.4 7 | Werkzeug==3.0.1 8 | -------------------------------------------------------------------------------- /tests/init-database.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL 5 | CREATE TABLE countries ( 6 | id SERIAL PRIMARY KEY, 7 | name TEXT, 8 | alpha2 TEXT, 9 | alpha3 TEXT, 10 | region TEXT 11 | ); 12 | 13 | -- https://github.com/lukes/ISO-3166-Countries-with-Regional-Codes/blob/master/all/all.json 14 | -- cat tests/countries.json| jq '.[] | [.name, ."alpha-2", ."alpha-3", .region] | @csv' -r | tr "'" " " | tr '"' "'" | awk -F "\n" '{print "("$1"),"}' 15 | INSERT INTO countries (name, alpha2, alpha3, region) VALUES 16 | ('Afghanistan','AF','AFG','Asia'), 17 | ('Åland Islands','AX','ALA','Europe'), 18 | ('Albania','AL','ALB','Europe'), 19 | ('Algeria','DZ','DZA','Africa'), 20 | ('American Samoa','AS','ASM','Oceania'), 21 | ('Andorra','AD','AND','Europe'), 22 | ('Angola','AO','AGO','Africa'), 23 | ('Anguilla','AI','AIA','Americas'), 24 | ('Antarctica','AQ','ATA',''), 25 | ('Antigua and Barbuda','AG','ATG','Americas'), 26 | ('Argentina','AR','ARG','Americas'), 27 | ('Armenia','AM','ARM','Asia'), 28 | ('Aruba','AW','ABW','Americas'), 29 | ('Australia','AU','AUS','Oceania'), 30 | ('Austria','AT','AUT','Europe'), 31 | ('Azerbaijan','AZ','AZE','Asia'), 32 | ('Bahamas','BS','BHS','Americas'), 33 | ('Bahrain','BH','BHR','Asia'), 34 | ('Bangladesh','BD','BGD','Asia'), 35 | ('Barbados','BB','BRB','Americas'), 36 | ('Belarus','BY','BLR','Europe'), 37 | ('Belgium','BE','BEL','Europe'), 38 | ('Belize','BZ','BLZ','Americas'), 39 | ('Benin','BJ','BEN','Africa'), 40 | ('Bermuda','BM','BMU','Americas'), 41 | ('Bhutan','BT','BTN','Asia'), 42 | ('Bolivia (Plurinational State of)','BO','BOL','Americas'), 43 | ('Bonaire, Sint Eustatius and Saba','BQ','BES','Americas'), 44 | ('Bosnia and Herzegovina','BA','BIH','Europe'), 45 | ('Botswana','BW','BWA','Africa'), 46 | ('Bouvet Island','BV','BVT','Americas'), 47 | ('Brazil','BR','BRA','Americas'), 48 | ('British Indian Ocean Territory','IO','IOT','Africa'), 49 | ('Brunei Darussalam','BN','BRN','Asia'), 50 | ('Bulgaria','BG','BGR','Europe'), 51 | ('Burkina Faso','BF','BFA','Africa'), 52 | ('Burundi','BI','BDI','Africa'), 53 | ('Cabo Verde','CV','CPV','Africa'), 54 | ('Cambodia','KH','KHM','Asia'), 55 | ('Cameroon','CM','CMR','Africa'), 56 | ('Canada','CA','CAN','Americas'), 57 | ('Cayman Islands','KY','CYM','Americas'), 58 | ('Central African Republic','CF','CAF','Africa'), 59 | ('Chad','TD','TCD','Africa'), 60 | ('Chile','CL','CHL','Americas'), 61 | ('China','CN','CHN','Asia'), 62 | ('Christmas Island','CX','CXR','Oceania'), 63 | ('Cocos (Keeling) Islands','CC','CCK','Oceania'), 64 | ('Colombia','CO','COL','Americas'), 65 | ('Comoros','KM','COM','Africa'), 66 | ('Congo','CG','COG','Africa'), 67 | ('Congo, Democratic Republic of the','CD','COD','Africa'), 68 | ('Cook Islands','CK','COK','Oceania'), 69 | ('Costa Rica','CR','CRI','Americas'), 70 | ('Côte d Ivoire','CI','CIV','Africa'), 71 | ('Croatia','HR','HRV','Europe'), 72 | ('Cuba','CU','CUB','Americas'), 73 | ('Curaçao','CW','CUW','Americas'), 74 | ('Cyprus','CY','CYP','Asia'), 75 | ('Czechia','CZ','CZE','Europe'), 76 | ('Denmark','DK','DNK','Europe'), 77 | ('Djibouti','DJ','DJI','Africa'), 78 | ('Dominica','DM','DMA','Americas'), 79 | ('Dominican Republic','DO','DOM','Americas'), 80 | ('Ecuador','EC','ECU','Americas'), 81 | ('Egypt','EG','EGY','Africa'), 82 | ('El Salvador','SV','SLV','Americas'), 83 | ('Equatorial Guinea','GQ','GNQ','Africa'), 84 | ('Eritrea','ER','ERI','Africa'), 85 | ('Estonia','EE','EST','Europe'), 86 | ('Eswatini','SZ','SWZ','Africa'), 87 | ('Ethiopia','ET','ETH','Africa'), 88 | ('Falkland Islands (Malvinas)','FK','FLK','Americas'), 89 | ('Faroe Islands','FO','FRO','Europe'), 90 | ('Fiji','FJ','FJI','Oceania'), 91 | ('Finland','FI','FIN','Europe'), 92 | ('France','FR','FRA','Europe'), 93 | ('French Guiana','GF','GUF','Americas'), 94 | ('French Polynesia','PF','PYF','Oceania'), 95 | ('French Southern Territories','TF','ATF','Africa'), 96 | ('Gabon','GA','GAB','Africa'), 97 | ('Gambia','GM','GMB','Africa'), 98 | ('Georgia','GE','GEO','Asia'), 99 | ('Germany','DE','DEU','Europe'), 100 | ('Ghana','GH','GHA','Africa'), 101 | ('Gibraltar','GI','GIB','Europe'), 102 | ('Greece','GR','GRC','Europe'), 103 | ('Greenland','GL','GRL','Americas'), 104 | ('Grenada','GD','GRD','Americas'), 105 | ('Guadeloupe','GP','GLP','Americas'), 106 | ('Guam','GU','GUM','Oceania'), 107 | ('Guatemala','GT','GTM','Americas'), 108 | ('Guernsey','GG','GGY','Europe'), 109 | ('Guinea','GN','GIN','Africa'), 110 | ('Guinea-Bissau','GW','GNB','Africa'), 111 | ('Guyana','GY','GUY','Americas'), 112 | ('Haiti','HT','HTI','Americas'), 113 | ('Heard Island and McDonald Islands','HM','HMD','Oceania'), 114 | ('Holy See','VA','VAT','Europe'), 115 | ('Honduras','HN','HND','Americas'), 116 | ('Hong Kong','HK','HKG','Asia'), 117 | ('Hungary','HU','HUN','Europe'), 118 | ('Iceland','IS','ISL','Europe'), 119 | ('India','IN','IND','Asia'), 120 | ('Indonesia','ID','IDN','Asia'), 121 | ('Iran (Islamic Republic of)','IR','IRN','Asia'), 122 | ('Iraq','IQ','IRQ','Asia'), 123 | ('Ireland','IE','IRL','Europe'), 124 | ('Isle of Man','IM','IMN','Europe'), 125 | ('Israel','IL','ISR','Asia'), 126 | ('Italy','IT','ITA','Europe'), 127 | ('Jamaica','JM','JAM','Americas'), 128 | ('Japan','JP','JPN','Asia'), 129 | ('Jersey','JE','JEY','Europe'), 130 | ('Jordan','JO','JOR','Asia'), 131 | ('Kazakhstan','KZ','KAZ','Asia'), 132 | ('Kenya','KE','KEN','Africa'), 133 | ('Kiribati','KI','KIR','Oceania'), 134 | ('Korea (Democratic People s Republic of)','KP','PRK','Asia'), 135 | ('Korea, Republic of','KR','KOR','Asia'), 136 | ('Kuwait','KW','KWT','Asia'), 137 | ('Kyrgyzstan','KG','KGZ','Asia'), 138 | ('Lao People s Democratic Republic','LA','LAO','Asia'), 139 | ('Latvia','LV','LVA','Europe'), 140 | ('Lebanon','LB','LBN','Asia'), 141 | ('Lesotho','LS','LSO','Africa'), 142 | ('Liberia','LR','LBR','Africa'), 143 | ('Libya','LY','LBY','Africa'), 144 | ('Liechtenstein','LI','LIE','Europe'), 145 | ('Lithuania','LT','LTU','Europe'), 146 | ('Luxembourg','LU','LUX','Europe'), 147 | ('Macao','MO','MAC','Asia'), 148 | ('Madagascar','MG','MDG','Africa'), 149 | ('Malawi','MW','MWI','Africa'), 150 | ('Malaysia','MY','MYS','Asia'), 151 | ('Maldives','MV','MDV','Asia'), 152 | ('Mali','ML','MLI','Africa'), 153 | ('Malta','MT','MLT','Europe'), 154 | ('Marshall Islands','MH','MHL','Oceania'), 155 | ('Martinique','MQ','MTQ','Americas'), 156 | ('Mauritania','MR','MRT','Africa'), 157 | ('Mauritius','MU','MUS','Africa'), 158 | ('Mayotte','YT','MYT','Africa'), 159 | ('Mexico','MX','MEX','Americas'), 160 | ('Micronesia (Federated States of)','FM','FSM','Oceania'), 161 | ('Moldova, Republic of','MD','MDA','Europe'), 162 | ('Monaco','MC','MCO','Europe'), 163 | ('Mongolia','MN','MNG','Asia'), 164 | ('Montenegro','ME','MNE','Europe'), 165 | ('Montserrat','MS','MSR','Americas'), 166 | ('Morocco','MA','MAR','Africa'), 167 | ('Mozambique','MZ','MOZ','Africa'), 168 | ('Myanmar','MM','MMR','Asia'), 169 | ('Namibia','NA','NAM','Africa'), 170 | ('Nauru','NR','NRU','Oceania'), 171 | ('Nepal','NP','NPL','Asia'), 172 | ('Netherlands','NL','NLD','Europe'), 173 | ('New Caledonia','NC','NCL','Oceania'), 174 | ('New Zealand','NZ','NZL','Oceania'), 175 | ('Nicaragua','NI','NIC','Americas'), 176 | ('Niger','NE','NER','Africa'), 177 | ('Nigeria','NG','NGA','Africa'), 178 | ('Niue','NU','NIU','Oceania'), 179 | ('Norfolk Island','NF','NFK','Oceania'), 180 | ('North Macedonia','MK','MKD','Europe'), 181 | ('Northern Mariana Islands','MP','MNP','Oceania'), 182 | ('Norway','NO','NOR','Europe'), 183 | ('Oman','OM','OMN','Asia'), 184 | ('Pakistan','PK','PAK','Asia'), 185 | ('Palau','PW','PLW','Oceania'), 186 | ('Palestine, State of','PS','PSE','Asia'), 187 | ('Panama','PA','PAN','Americas'), 188 | ('Papua New Guinea','PG','PNG','Oceania'), 189 | ('Paraguay','PY','PRY','Americas'), 190 | ('Peru','PE','PER','Americas'), 191 | ('Philippines','PH','PHL','Asia'), 192 | ('Pitcairn','PN','PCN','Oceania'), 193 | ('Poland','PL','POL','Europe'), 194 | ('Portugal','PT','PRT','Europe'), 195 | ('Puerto Rico','PR','PRI','Americas'), 196 | ('Qatar','QA','QAT','Asia'), 197 | ('Réunion','RE','REU','Africa'), 198 | ('Romania','RO','ROU','Europe'), 199 | ('Russian Federation','RU','RUS','Europe'), 200 | ('Rwanda','RW','RWA','Africa'), 201 | ('Saint Barthélemy','BL','BLM','Americas'), 202 | ('Saint Helena, Ascension and Tristan da Cunha','SH','SHN','Africa'), 203 | ('Saint Kitts and Nevis','KN','KNA','Americas'), 204 | ('Saint Lucia','LC','LCA','Americas'), 205 | ('Saint Martin (French part)','MF','MAF','Americas'), 206 | ('Saint Pierre and Miquelon','PM','SPM','Americas'), 207 | ('Saint Vincent and the Grenadines','VC','VCT','Americas'), 208 | ('Samoa','WS','WSM','Oceania'), 209 | ('San Marino','SM','SMR','Europe'), 210 | ('Sao Tome and Principe','ST','STP','Africa'), 211 | ('Saudi Arabia','SA','SAU','Asia'), 212 | ('Senegal','SN','SEN','Africa'), 213 | ('Serbia','RS','SRB','Europe'), 214 | ('Seychelles','SC','SYC','Africa'), 215 | ('Sierra Leone','SL','SLE','Africa'), 216 | ('Singapore','SG','SGP','Asia'), 217 | ('Sint Maarten (Dutch part)','SX','SXM','Americas'), 218 | ('Slovakia','SK','SVK','Europe'), 219 | ('Slovenia','SI','SVN','Europe'), 220 | ('Solomon Islands','SB','SLB','Oceania'), 221 | ('Somalia','SO','SOM','Africa'), 222 | ('South Africa','ZA','ZAF','Africa'), 223 | ('South Georgia and the South Sandwich Islands','GS','SGS','Americas'), 224 | ('South Sudan','SS','SSD','Africa'), 225 | ('Spain','ES','ESP','Europe'), 226 | ('Sri Lanka','LK','LKA','Asia'), 227 | ('Sudan','SD','SDN','Africa'), 228 | ('Suriname','SR','SUR','Americas'), 229 | ('Svalbard and Jan Mayen','SJ','SJM','Europe'), 230 | ('Sweden','SE','SWE','Europe'), 231 | ('Switzerland','CH','CHE','Europe'), 232 | ('Syrian Arab Republic','SY','SYR','Asia'), 233 | ('Taiwan, Province of China','TW','TWN','Asia'), 234 | ('Tajikistan','TJ','TJK','Asia'), 235 | ('Tanzania, United Republic of','TZ','TZA','Africa'), 236 | ('Thailand','TH','THA','Asia'), 237 | ('Timor-Leste','TL','TLS','Asia'), 238 | ('Togo','TG','TGO','Africa'), 239 | ('Tokelau','TK','TKL','Oceania'), 240 | ('Tonga','TO','TON','Oceania'), 241 | ('Trinidad and Tobago','TT','TTO','Americas'), 242 | ('Tunisia','TN','TUN','Africa'), 243 | ('Turkey','TR','TUR','Asia'), 244 | ('Turkmenistan','TM','TKM','Asia'), 245 | ('Turks and Caicos Islands','TC','TCA','Americas'), 246 | ('Tuvalu','TV','TUV','Oceania'), 247 | ('Uganda','UG','UGA','Africa'), 248 | ('Ukraine','UA','UKR','Europe'), 249 | ('United Arab Emirates','AE','ARE','Asia'), 250 | ('United Kingdom of Great Britain and Northern Ireland','GB','GBR','Europe'), 251 | ('United States of America','US','USA','Americas'), 252 | ('United States Minor Outlying Islands','UM','UMI','Oceania'), 253 | ('Uruguay','UY','URY','Americas'), 254 | ('Uzbekistan','UZ','UZB','Asia'), 255 | ('Vanuatu','VU','VUT','Oceania'), 256 | ('Venezuela (Bolivarian Republic of)','VE','VEN','Americas'), 257 | ('Viet Nam','VN','VNM','Asia'), 258 | ('Virgin Islands (British)','VG','VGB','Americas'), 259 | ('Virgin Islands (U.S.)','VI','VIR','Americas'), 260 | ('Wallis and Futuna','WF','WLF','Oceania'), 261 | ('Western Sahara','EH','ESH','Africa'), 262 | ('Yemen','YE','YEM','Asia'), 263 | ('Zambia','ZM','ZMB','Africa'), 264 | ('Zimbabwe','ZW','ZWE','Africa'); 265 | EOSQL -------------------------------------------------------------------------------- /tests/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.1" 2 | info: 3 | title: Pulse API 4 | version: "1.0" 5 | servers: 6 | - url: http://localhost:8080/api 7 | paths: 8 | /ping: 9 | get: 10 | summary: Проверка сервера на готовность принимать запросы 11 | description: | 12 | Данный эндпоинт позволяет понять, что сервер готов принимать входящие запросы. 13 | 14 | Программа-чекер будет дожидаться первого успешного ответа от сервера на данный эндпоинт, после чего будет запускать проверку тестовый сценариев. 15 | operationId: ping 16 | responses: 17 | "200": 18 | description: | 19 | Если сервер успешно отвечает на данный запрос, считается, что он готов обрабатывать входящие запросы в API. 20 | 21 | Содержимое ответа при этом не валидируется, можно возвращать "ok". 22 | content: 23 | text/plain: 24 | schema: 25 | type: string 26 | example: ok 27 | "500": 28 | description: Если сервер отвечает любым отличным от 200 кодом ответа, считается, что он не готов принимать запросы. 29 | /countries: 30 | get: 31 | summary: Получить список стран 32 | description: | 33 | Получение списка стран с возможной фильтрацией. 34 | 35 | Используется на странице регистрации для предоставления возможности выбора страны, к которой относится пользователь. 36 | Если хотя бы один переданный регион является некорректным, весь запрос считается некорректным. 37 | 38 | Если никакие из фильтров не переданы, необходимо вернуть все страны. 39 | operationId: listCountries 40 | parameters: 41 | - name: region 42 | description: | 43 | Возвращаемые страны должны относиться только к тем регионам, которые переданы в данном списке. 44 | 45 | Если передан пустой список, считайте, что фильтр по региону отсутствует. 46 | in: query 47 | schema: 48 | type: array 49 | items: 50 | $ref: "#/components/schemas/countryRegion" 51 | example: 52 | - Europe 53 | - Asia 54 | responses: 55 | "200": 56 | description: Список стран, соответствующих указанному фильтру. Страны должны быть отсортированы лексикографически по двухбуквенному коду. 57 | content: 58 | application/json: 59 | schema: 60 | type: array 61 | items: 62 | $ref: "#/components/schemas/country" 63 | "400": 64 | description: Формат входного запроса не соответствует формату либо переданы неверные значения. 65 | content: 66 | application/json: 67 | schema: 68 | $ref: "#/components/schemas/errorResponse" 69 | /countries/{alpha2}: 70 | get: 71 | summary: Получить страну по alpha2 коду 72 | description: | 73 | Получение одной страны по её уникальному двухбуквенному коду. 74 | 75 | Используется для получения информации по определенной стране. 76 | operationId: getCountry 77 | parameters: 78 | - name: alpha2 79 | description: | 80 | Возвращаемая страна должна иметь указанный alpha2 код. 81 | required: true 82 | in: path 83 | schema: 84 | $ref: "#/components/schemas/countryAlpha2" 85 | responses: 86 | "200": 87 | description: Страна, найденная по указанному коду. 88 | content: 89 | application/json: 90 | schema: 91 | $ref: "#/components/schemas/country" 92 | "404": 93 | description: Страна с указанным кодом не найдена. 94 | content: 95 | application/json: 96 | schema: 97 | $ref: "#/components/schemas/errorResponse" 98 | /auth/register: 99 | post: 100 | summary: Регистрация нового пользователя 101 | description: | 102 | Используется для регистрации нового пользователя по логину и паролю. 103 | operationId: authRegister 104 | requestBody: 105 | description: Данные для регистрации пользователя. 106 | required: true 107 | content: 108 | application/json: 109 | schema: 110 | type: object 111 | properties: 112 | login: 113 | $ref: "#/components/schemas/userLogin" 114 | email: 115 | $ref: "#/components/schemas/userEmail" 116 | password: 117 | $ref: "#/components/schemas/userPassword" 118 | countryCode: 119 | $ref: "#/components/schemas/countryAlpha2" 120 | isPublic: 121 | $ref: "#/components/schemas/userIsPublic" 122 | phone: 123 | $ref: "#/components/schemas/userPhone" 124 | image: 125 | $ref: "#/components/schemas/userImage" 126 | required: 127 | - login 128 | - email 129 | - password 130 | - countryCode 131 | - isPublic 132 | responses: 133 | "201": 134 | description: В случае успеха возвращается профиль зарегистрированного пользователя 135 | content: 136 | application/json: 137 | schema: 138 | type: object 139 | properties: 140 | profile: 141 | $ref: "#/components/schemas/userProfile" 142 | required: 143 | - profile 144 | "400": 145 | description: | 146 | Регистрационные данные не соответствуют ожидаемому формату и требованиям. 147 | 148 | Например, данную ошибку необходимо возвращать в следующих ситуациях (это не полный список): 149 | 150 | - Недостаточно "надежный" пароль. 151 | - Страна с указанным кодом не найдена. 152 | - Длина ссылки на аватар пользователя превышает допустимый лимит. 153 | 154 | Для ознакомления с форматом и требованиями к регистрационным данным обратите внимание на описание моделей в Open API спецификации. 155 | content: 156 | application/json: 157 | schema: 158 | $ref: "#/components/schemas/errorResponse" 159 | "409": 160 | description: | 161 | Нарушено требование на уникальность авторизационных данных пользователей. 162 | 163 | Данный код ответа должен использоваться, если пользователь с таким e-mail, номером телефона или логином уже зарегистрирован. 164 | content: 165 | application/json: 166 | schema: 167 | $ref: "#/components/schemas/errorResponse" 168 | /auth/sign-in: 169 | post: 170 | summary: Аутентификация для получения токена 171 | description: | 172 | Процедура аутентификации по логину и паролю позволяет получить токен, который в дальнейшем будет использоваться пользователем для выполнения операций, требующих авторизацию. 173 | 174 | Сервер должен генерировать уникальные токены, имеющие время жизни (на усмотрение разработчика, от 1 до 24 часов). После истечения времени действия токен должен быть недействительным и не может использоваться для аутентификации. 175 | 176 | Токен является уникальным строковым значением с высокой энтропией (злоумышленник не сможет его "подобрать" перебором). При каждой новой аутентификации генерируется новый уникальный токен, который ранее не был использован. Можно использовать JWT. 177 | 178 | В дальнейшем полученный токен будет использоваться для авторизации пользовательских запросов. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. Следовательно, сервер должен уметь идентифицировать пользователя по токену. 179 | operationId: authSignIn 180 | requestBody: 181 | description: Данные для аутентификации пользователя. 182 | required: true 183 | content: 184 | application/json: 185 | schema: 186 | type: object 187 | properties: 188 | login: 189 | $ref: "#/components/schemas/userLogin" 190 | password: 191 | $ref: "#/components/schemas/userPassword" 192 | required: 193 | - login 194 | - password 195 | responses: 196 | "200": 197 | description: Успешная аутентификация 198 | content: 199 | application/json: 200 | schema: 201 | type: object 202 | properties: 203 | token: 204 | type: string 205 | description: Сгенерированный токен пользователя 206 | minLength: 20 207 | example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 208 | required: 209 | - token 210 | "401": 211 | description: Пользователь с указанным логином и паролем не найден 212 | content: 213 | application/json: 214 | schema: 215 | $ref: "#/components/schemas/errorResponse" 216 | /me/profile: 217 | get: 218 | summary: Получение собственного профиля 219 | description: | 220 | Используется для получения пользователем его собственного профиля. 221 | 222 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 223 | security: 224 | - bearerAuth: [] 225 | operationId: getMyProfile 226 | responses: 227 | "200": 228 | description: Передан действительный токен, в ответе возвращается профиль пользователя. 229 | content: 230 | application/json: 231 | schema: 232 | $ref: "#/components/schemas/userProfile" 233 | "401": 234 | description: Переданный токен не существует либо некорректен. 235 | content: 236 | application/json: 237 | schema: 238 | $ref: "#/components/schemas/errorResponse" 239 | patch: 240 | summary: Редактирование собственного профиля 241 | description: | 242 | Используется для редактирования параметров профиля пользователя. 243 | 244 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 245 | security: 246 | - bearerAuth: [] 247 | operationId: patchMyProfile 248 | requestBody: 249 | description: | 250 | В теле запроса перечисляются названия параметров, которые необходимо обновить, и новые значения. 251 | 252 | Если значение передано, данное изменение должно быть отражено в профиле пользователя. 253 | Если значение не передано, необходимо оставить прежнее значение параметра. 254 | Если передана пустая структура, ничего изменять не требуется, возвращается успешный ответ. 255 | required: true 256 | content: 257 | application/json: 258 | schema: 259 | type: object 260 | properties: 261 | countryCode: 262 | $ref: "#/components/schemas/countryAlpha2" 263 | isPublic: 264 | $ref: "#/components/schemas/userIsPublic" 265 | phone: 266 | $ref: "#/components/schemas/userPhone" 267 | image: 268 | $ref: "#/components/schemas/userImage" 269 | responses: 270 | "200": 271 | description: Передан действительный токен, в ответе возвращается профиль пользователя с примененными изменениями. 272 | content: 273 | application/json: 274 | schema: 275 | $ref: "#/components/schemas/userProfile" 276 | "401": 277 | description: Переданный токен не существует либо некорректен. 278 | content: 279 | application/json: 280 | schema: 281 | $ref: "#/components/schemas/errorResponse" 282 | "400": 283 | description: | 284 | Данные не соответствуют ожидаемому формату и требованиям. 285 | 286 | Например, данную ошибку необходимо возвращать в следующих ситуациях (это не полный список): 287 | 288 | - Страна с указанным кодом не найдена. 289 | - Длина ссылки на аватар пользователя превышает допустимый лимит. 290 | 291 | Для ознакомления с форматом и требованиями к регистрационным данным обратите внимание на описание моделей в Open API спецификации. 292 | content: 293 | application/json: 294 | schema: 295 | $ref: "#/components/schemas/errorResponse" 296 | "409": 297 | description: | 298 | Нарушено требование на уникальность авторизационных данных пользователей. 299 | 300 | Данный код ответа должен использоваться, если указанный номер телефона занят другим пользователем. 301 | content: 302 | application/json: 303 | schema: 304 | $ref: "#/components/schemas/errorResponse" 305 | /profiles/{login}: 306 | get: 307 | summary: Получение профиля пользователя по логину 308 | description: | 309 | Используется для получения профиля другого пользователя по логину. 310 | 311 | Если профиль пользователя публичен (`isPublic: true`), его может получить любой другой пользователь. Если профиль пользователя закрыт, его могут получить пользователи, которых данный пользователь добавил в друзья. 312 | 313 | При этом собственный профиль пользователь может получить всегда. Сервер должен идентифицировать пользователя по переданному токену в заголовке `Authorization`. 314 | security: 315 | - bearerAuth: [] 316 | parameters: 317 | - name: login 318 | description: Логин пользователя, чей профиль необходимо получить. 319 | required: true 320 | in: path 321 | schema: 322 | $ref: "#/components/schemas/userLogin" 323 | operationId: getProfile 324 | responses: 325 | "200": 326 | description: Пользователь с указанным логином существует и его профиль может быть получен пользователем, осуществившим запрос. 327 | content: 328 | application/json: 329 | schema: 330 | $ref: "#/components/schemas/userProfile" 331 | "401": 332 | description: Переданный токен не существует либо некорректен. 333 | content: 334 | application/json: 335 | schema: 336 | $ref: "#/components/schemas/errorResponse" 337 | "403": 338 | description: | 339 | Профиль не может быть получен: либо пользователь с указанным логином не существует, либо у отправителя запроса нет доступа к запрашиваемому профилю. 340 | content: 341 | application/json: 342 | schema: 343 | $ref: "#/components/schemas/errorResponse" 344 | /me/updatePassword: 345 | post: 346 | summary: Обновление пароля 347 | description: | 348 | Используется для обновления пароля. 349 | 350 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 351 | 352 | Важно: после успешного обновления пароля ранее выписанные токены должны быть деактивированы. Как только сервер вернет успешный ответ на данный запрос, пользователь не сможет совершить какие-либо операции с ранее созданными токенами (запросы со старыми токенами должны получать соответствующий ошибочный статус код). 353 | security: 354 | - bearerAuth: [] 355 | operationId: updatePassword 356 | requestBody: 357 | description: | 358 | В теле запроса передается старый и новый пароли. Пароль может быть обновлен только в случае передачи правильного значения старого пароля. 359 | required: true 360 | content: 361 | application/json: 362 | schema: 363 | type: object 364 | properties: 365 | oldPassword: 366 | $ref: "#/components/schemas/userPassword" 367 | newPassword: 368 | $ref: "#/components/schemas/userPassword" 369 | required: 370 | - oldPassword 371 | - newPassword 372 | responses: 373 | "200": 374 | description: Пароль успешно обновлен и ранее выпущенные токены отозваны. 375 | content: 376 | application/json: 377 | schema: 378 | type: object 379 | properties: 380 | status: 381 | type: string 382 | description: Должно принимать значение `ok`. 383 | example: ok 384 | required: 385 | - status 386 | "400": 387 | description: Новый пароль не соответствует требованиям безопасности. 388 | content: 389 | application/json: 390 | schema: 391 | $ref: "#/components/schemas/errorResponse" 392 | "401": 393 | description: Переданный токен не существует либо некорректен. 394 | content: 395 | application/json: 396 | schema: 397 | $ref: "#/components/schemas/errorResponse" 398 | "403": 399 | description: Указанный пароль не совпадает с действительным. 400 | content: 401 | application/json: 402 | schema: 403 | $ref: "#/components/schemas/errorResponse" 404 | /friends/add: 405 | post: 406 | summary: Добавить пользователя в друзья 407 | description: | 408 | Позволяет добавить другого пользователя к себе в друзья. 409 | 410 | Если указанный пользователь уже добавлен в друзья, верните успешный ответ. 411 | Если пользователь добавляет в друзья самого себя, верните успешный ответ (добавлять в друзья при этом не нужно). 412 | 413 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 414 | security: 415 | - bearerAuth: [] 416 | operationId: friendsAdd 417 | requestBody: 418 | description: | 419 | В теле запроса передается логин пользователя. 420 | required: true 421 | content: 422 | application/json: 423 | schema: 424 | type: object 425 | properties: 426 | login: 427 | $ref: "#/components/schemas/userLogin" 428 | required: 429 | - login 430 | responses: 431 | "200": 432 | description: Операция завершилась успешно. 433 | content: 434 | application/json: 435 | schema: 436 | type: object 437 | properties: 438 | status: 439 | type: string 440 | description: Должно принимать значение `ok`. 441 | example: ok 442 | required: 443 | - status 444 | "401": 445 | description: Переданный токен не существует либо некорректен. 446 | content: 447 | application/json: 448 | schema: 449 | $ref: "#/components/schemas/errorResponse" 450 | "404": 451 | description: Пользователь с указанным логином не найден. 452 | content: 453 | application/json: 454 | schema: 455 | $ref: "#/components/schemas/errorResponse" 456 | /friends/remove: 457 | post: 458 | summary: Удалить пользователя из друзей 459 | description: | 460 | Позволяет удалить пользователя из друзей. 461 | 462 | Если указанного пользователя нет в друзьях, верните успешный ответ. 463 | 464 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 465 | security: 466 | - bearerAuth: [] 467 | operationId: friendsRemove 468 | requestBody: 469 | description: | 470 | В теле запроса передается логин пользователя. 471 | required: true 472 | content: 473 | application/json: 474 | schema: 475 | type: object 476 | properties: 477 | login: 478 | $ref: "#/components/schemas/userLogin" 479 | required: 480 | - login 481 | responses: 482 | "200": 483 | description: Операция завершилась успешно. 484 | content: 485 | application/json: 486 | schema: 487 | type: object 488 | properties: 489 | status: 490 | type: string 491 | description: Должно принимать значение `ok`. 492 | example: ok 493 | required: 494 | - status 495 | "401": 496 | description: Переданный токен не существует либо некорректен. 497 | content: 498 | application/json: 499 | schema: 500 | $ref: "#/components/schemas/errorResponse" 501 | /friends: 502 | get: 503 | summary: Получение списка друзей 504 | description: | 505 | Используется для получения списка своих друзей (пользователей, кого инициатор запроса добавил в друзья). 506 | 507 | Для плавной работы приложения используется пагинация. 508 | 509 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 510 | security: 511 | - bearerAuth: [] 512 | operationId: friendsList 513 | parameters: 514 | - $ref: "#/components/parameters/paginationLimit" 515 | - $ref: "#/components/parameters/paginationOffset" 516 | responses: 517 | "200": 518 | description: | 519 | Список друзей пользователя, отсортированный по убыванию по дате последнего добавления в друзья. 520 | 521 | В начале идут друзья, которые были добавлены совсем недавно. 522 | content: 523 | application/json: 524 | schema: 525 | type: array 526 | items: 527 | type: object 528 | description: Описание друга. 529 | properties: 530 | login: 531 | $ref: "#/components/schemas/userLogin" 532 | addedAt: 533 | type: string 534 | description: | 535 | Время и дата, когда данный пользователь был добавлен в друзья в последний раз. 536 | 537 | Передается в формате RFC3339. 538 | example: 2006-01-02T15:04:05Z07:00 539 | required: 540 | - login 541 | - addedAt 542 | "401": 543 | description: Переданный токен не существует либо некорректен. 544 | content: 545 | application/json: 546 | schema: 547 | $ref: "#/components/schemas/errorResponse" 548 | /posts/new: 549 | post: 550 | summary: Отправить публикацию 551 | description: | 552 | Используется для отправки публикации в ленту. 553 | 554 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 555 | security: 556 | - bearerAuth: [] 557 | operationId: submitPost 558 | requestBody: 559 | description: Информация о публикации. 560 | required: true 561 | content: 562 | application/json: 563 | schema: 564 | type: object 565 | properties: 566 | content: 567 | $ref: "#/components/schemas/postContent" 568 | tags: 569 | $ref: "#/components/schemas/postTags" 570 | required: 571 | - content 572 | - tags 573 | responses: 574 | "200": 575 | description: Публикация сохранена. Сервер назначает уникальный идентификатор и время создания публикации. 576 | content: 577 | application/json: 578 | schema: 579 | $ref: "#/components/schemas/post" 580 | "401": 581 | description: Переданный токен не существует либо некорректен. 582 | content: 583 | application/json: 584 | schema: 585 | $ref: "#/components/schemas/errorResponse" 586 | /posts/{postId}: 587 | get: 588 | summary: Получить публикацию по ID 589 | description: | 590 | Используется для получения публикации по её идентификатору. 591 | 592 | Если публикация принадлежит пользователю с публичным профилем, её может получить любой другой аутентифицированный пользователь. 593 | 594 | Если профиль автора закрыт, она доступна автору и пользователям, кого автор добавил в друзья. 595 | 596 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 597 | security: 598 | - bearerAuth: [] 599 | operationId: getPostById 600 | parameters: 601 | - name: postId 602 | description: ID публикации. 603 | required: true 604 | in: path 605 | schema: 606 | $ref: "#/components/schemas/postId" 607 | responses: 608 | "200": 609 | description: Публикация найдена. 610 | content: 611 | application/json: 612 | schema: 613 | $ref: "#/components/schemas/post" 614 | "401": 615 | description: Переданный токен не существует либо некорректен. 616 | content: 617 | application/json: 618 | schema: 619 | $ref: "#/components/schemas/errorResponse" 620 | "404": 621 | description: Указанный пост не найден либо к нему нет доступа. 622 | content: 623 | application/json: 624 | schema: 625 | $ref: "#/components/schemas/errorResponse" 626 | /posts/feed/my: 627 | get: 628 | summary: Получить ленту со своими постами 629 | description: | 630 | Используется для получения списка своих постов. 631 | 632 | Для плавной работы приложения используется пагинация. 633 | 634 | Можете считать, что пользователей с логином `my` не будет. 635 | 636 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 637 | security: 638 | - bearerAuth: [] 639 | operationId: getMyFeed 640 | parameters: 641 | - $ref: "#/components/parameters/paginationLimit" 642 | - $ref: "#/components/parameters/paginationOffset" 643 | responses: 644 | "200": 645 | description: | 646 | Список публикаций пользователя, отсортированных по убыванию по дате публикации. 647 | 648 | В начале идут публикации, которые были добавлены совсем недавно. 649 | content: 650 | application/json: 651 | schema: 652 | type: array 653 | items: 654 | $ref: "#/components/schemas/post" 655 | "401": 656 | description: Переданный токен не существует либо некорректен. 657 | content: 658 | application/json: 659 | schema: 660 | $ref: "#/components/schemas/errorResponse" 661 | /posts/feed/{login}: 662 | get: 663 | summary: Получить ленту с постами другого пользователя 664 | description: | 665 | Используется для получения списка публикаций другого пользователя. 666 | 667 | Если профиль пользователя открыт, его посты доступны всем. 668 | Если профиль пользователя закрыт, его посты доступны самому пользователю и пользователям, кого он добавил в друзья. 669 | 670 | Для плавной работы приложения используется пагинация. 671 | 672 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 673 | security: 674 | - bearerAuth: [] 675 | operationId: getFeedByOthers 676 | parameters: 677 | - name: login 678 | description: Логин пользователя. 679 | required: true 680 | in: path 681 | schema: 682 | $ref: "#/components/schemas/userLogin" 683 | - $ref: "#/components/parameters/paginationLimit" 684 | - $ref: "#/components/parameters/paginationOffset" 685 | responses: 686 | "200": 687 | description: | 688 | Список публикаций пользователя, отсортированных по убыванию по дате публикации. 689 | 690 | В начале идут публикации, которые были добавлены совсем недавно. 691 | content: 692 | application/json: 693 | schema: 694 | type: array 695 | items: 696 | $ref: "#/components/schemas/post" 697 | "401": 698 | description: Переданный токен не существует либо некорректен. 699 | content: 700 | application/json: 701 | schema: 702 | $ref: "#/components/schemas/errorResponse" 703 | "404": 704 | description: Пользователь не найден либо к нему нет доступа. 705 | content: 706 | application/json: 707 | schema: 708 | $ref: "#/components/schemas/errorResponse" 709 | /posts/{postId}/like: 710 | post: 711 | summary: Лайк поста 712 | description: | 713 | Лайк поста. 714 | 715 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 716 | security: 717 | - bearerAuth: [] 718 | operationId: likstPost 719 | parameters: 720 | - name: postId 721 | description: ID публикации. 722 | required: true 723 | in: path 724 | schema: 725 | $ref: "#/components/schemas/postId" 726 | responses: 727 | "200": 728 | description: Реакция засчитана, возвращайте пубикацию с актуальным числом лайков и дизлайков. 729 | content: 730 | application/json: 731 | schema: 732 | $ref: "#/components/schemas/post" 733 | "401": 734 | description: Переданный токен не существует либо некорректен. 735 | content: 736 | application/json: 737 | schema: 738 | $ref: "#/components/schemas/errorResponse" 739 | "404": 740 | description: Указанный пост не найден либо к нему нет доступа. 741 | content: 742 | application/json: 743 | schema: 744 | $ref: "#/components/schemas/errorResponse" 745 | /posts/{postId}/dislike: 746 | post: 747 | summary: Дизлайк поста 748 | description: | 749 | Дизлайк поста. 750 | 751 | Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. 752 | security: 753 | - bearerAuth: [] 754 | operationId: dislikePost 755 | parameters: 756 | - name: postId 757 | description: ID публикации. 758 | required: true 759 | in: path 760 | schema: 761 | $ref: "#/components/schemas/postId" 762 | responses: 763 | "200": 764 | description: Реакция засчитана, возвращайте пубикацию с актуальным числом лайков и дизлайков. 765 | content: 766 | application/json: 767 | schema: 768 | $ref: "#/components/schemas/post" 769 | "401": 770 | description: Переданный токен не существует либо некорректен. 771 | content: 772 | application/json: 773 | schema: 774 | $ref: "#/components/schemas/errorResponse" 775 | "404": 776 | description: Указанный пост не найден либо к нему нет доступа. 777 | content: 778 | application/json: 779 | schema: 780 | $ref: "#/components/schemas/errorResponse" 781 | 782 | components: 783 | schemas: 784 | countryAlpha2: 785 | type: string 786 | description: Двухбуквенный код, уникально идентифицирующий страну 787 | maxLength: 2 788 | pattern: "[a-zA-Z]{2}" 789 | example: RU 790 | countryRegion: 791 | type: string 792 | description: Географический регион, к которому относится страна 793 | enum: 794 | - Europe 795 | - Africa 796 | - Americas 797 | - Oceania 798 | - Asia 799 | country: 800 | type: object 801 | description: Информация о стране из стандарта ISO 3166 802 | properties: 803 | name: 804 | type: string 805 | description: Полное название страны 806 | maxLength: 100 807 | alpha2: 808 | $ref: "#/components/schemas/countryAlpha2" 809 | alpha3: 810 | type: string 811 | description: Трехбуквенный код страны 812 | maxLength: 3 813 | pattern: "[a-zA-Z]{3}" 814 | region: 815 | $ref: "#/components/schemas/countryRegion" 816 | required: 817 | - name 818 | - alpha2 819 | - alpha3 820 | example: 821 | name: Burkina Faso 822 | alpha2: BF 823 | alpha3: BFA 824 | region: Africa 825 | userLogin: 826 | type: string 827 | description: Логин пользователя 828 | maxLength: 30 829 | pattern: "[a-zA-Z0-9-]+" 830 | example: yellowMonkey 831 | userEmail: 832 | type: string 833 | description: E-mail пользователя 834 | maxLength: 50 835 | minLength: 1 836 | example: yellowstone1980@you.ru 837 | userPassword: 838 | type: string 839 | description: | 840 | Пароль пользователя, к которому предъявляются следующие требования: 841 | 842 | - Длина пароля не менее 6 символов. 843 | - Присутствуют латинские символы в нижнем и верхнем регистре. 844 | - Присутствует минимум одна цифра. 845 | minLength: 6 846 | maxLength: 100 847 | example: $aba4821FWfew01#.fewA$ 848 | userIsPublic: 849 | type: boolean 850 | description: | 851 | Является ли данный профиль публичным. 852 | 853 | Публичные профили доступны другим пользователям: если профиль публичный, любой пользователь платформы сможет получить информацию о пользователе. 854 | example: true 855 | userPhone: 856 | type: string 857 | description: Номер телефона пользователя в формате +123456789 858 | pattern: \+[\d]+ 859 | example: "+74951239922" 860 | maxLength: 20 861 | userImage: 862 | type: string 863 | description: Ссылка на фото для аватара пользователя 864 | example: https://http.cat/images/100.jpg 865 | maxLength: 200 866 | minLength: 1 867 | userProfile: 868 | type: object 869 | description: Информация о профиле пользователя 870 | properties: 871 | login: 872 | $ref: "#/components/schemas/userLogin" 873 | email: 874 | $ref: "#/components/schemas/userEmail" 875 | countryCode: 876 | $ref: "#/components/schemas/countryAlpha2" 877 | isPublic: 878 | $ref: "#/components/schemas/userIsPublic" 879 | phone: 880 | $ref: "#/components/schemas/userPhone" 881 | image: 882 | $ref: "#/components/schemas/userImage" 883 | required: 884 | - login 885 | - email 886 | - countryCode 887 | - isPublic 888 | postId: 889 | type: string 890 | description: Уникальный идентификатор публикации, присвоенный сервером. 891 | example: 550e8400-e29b-41d4-a716-446655440000 892 | maxLength: 100 893 | postContent: 894 | type: string 895 | description: Текст публикации. 896 | example: Свеча на 400! Покупаем, докупаем и фиксируем прибыль. 897 | maxLength: 1000 898 | postTags: 899 | type: array 900 | description: Список тегов публикации. 901 | items: 902 | type: string 903 | description: Значение тега. 904 | example: тинькофф 905 | maxLength: 20 906 | example: 907 | - тинькофф 908 | - спббиржа 909 | - moex 910 | post: 911 | type: object 912 | description: Пользовательская публикация. 913 | properties: 914 | id: 915 | $ref: "#/components/schemas/postId" 916 | content: 917 | $ref: "#/components/schemas/postContent" 918 | author: 919 | $ref: "#/components/schemas/userLogin" 920 | tags: 921 | $ref: "#/components/schemas/postTags" 922 | createdAt: 923 | type: string 924 | description: | 925 | Серверная дата и время в момент, когда пользователь отправил данную публикацию. 926 | Передается в формате RFC3339. 927 | example: 2006-01-02T15:04:05Z07:00 928 | likesCount: 929 | type: integer 930 | minimum: 0 931 | default: 0 932 | description: Число лайков, набранное публикацией. 933 | dislikesCount: 934 | type: integer 935 | minimum: 0 936 | default: 0 937 | description: Число дизлайков, набранное публикацией. 938 | required: 939 | - id 940 | - content 941 | - author 942 | - tags 943 | - createdAt 944 | - likesCount 945 | - dislikesCount 946 | errorResponse: 947 | type: object 948 | description: Используется для возвращения ошибки пользователю 949 | properties: 950 | reason: 951 | type: string 952 | description: Описание ошибки в свободной форме 953 | minLength: 5 954 | required: 955 | - reason 956 | example: 957 | reason: <объяснение, почему запрос пользователя не может быть обработан> 958 | parameters: 959 | paginationLimit: 960 | in: query 961 | name: limit 962 | required: false 963 | description: | 964 | Максимальное число возвращаемых объектов. Используется для запросов с пагинацией. 965 | 966 | Сервер должен возвращать максимальное допустимое число объектов. 967 | schema: 968 | type: integer 969 | format: int32 970 | minimum: 0 971 | maximum: 50 972 | default: 5 973 | paginationOffset: 974 | in: query 975 | name: offset 976 | required: false 977 | description: | 978 | Какое количество объектов должно быть пропущено с начала. Используется для запросов с пагинацией. 979 | schema: 980 | type: integer 981 | format: int32 982 | default: 0 983 | minimum: 0 984 | securitySchemes: 985 | bearerAuth: 986 | type: http 987 | scheme: bearer 988 | -------------------------------------------------------------------------------- /tests/public-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "a5dbb75a-d7a8-4e8c-8660-9925345a3a86", 4 | "name": "PROD round 2: public tests [1]", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "01/ping", 10 | "item": [ 11 | { 12 | "name": "Ping", 13 | "event": [ 14 | { 15 | "listen": "test", 16 | "script": { 17 | "exec": [ 18 | "", 19 | "pm.test(\"PING server\", function () {", 20 | " pm.sendRequest(pm.variables.get(\"base_url\") + \"/ping\", function (err, response) {", 21 | " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", 22 | " });", 23 | "});", 24 | "" 25 | ], 26 | "type": "text/javascript" 27 | } 28 | } 29 | ], 30 | "request": { 31 | "method": "GET", 32 | "header": [], 33 | "url": { 34 | "raw": "{{base_url}}/ping", 35 | "host": [ 36 | "{{base_url}}" 37 | ], 38 | "path": [ 39 | "ping" 40 | ] 41 | } 42 | }, 43 | "response": [] 44 | } 45 | ] 46 | }, 47 | { 48 | "name": "02/countries", 49 | "item": [ 50 | { 51 | "name": "List countries", 52 | "event": [ 53 | { 54 | "listen": "test", 55 | "script": { 56 | "exec": [ 57 | "var schema = {", 58 | " \"type\": \"array\",", 59 | " \"items\": {", 60 | " \"type\": \"object\",", 61 | " \"description\": \"Информация о стране из стандарта ISO 3166\",", 62 | " \"properties\": {", 63 | " \"name\": {", 64 | " \"type\": \"string\",", 65 | " \"description\": \"Полное название страны\",", 66 | " \"maxLength\": 100", 67 | " },", 68 | " \"alpha2\": {", 69 | " \"type\": \"string\",", 70 | " \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",", 71 | " \"maxLength\": 2,", 72 | " \"pattern\": \"[a-zA-Z]{2}\"", 73 | " },", 74 | " \"alpha3\": {", 75 | " \"type\": \"string\",", 76 | " \"description\": \"Трехбуквенный код страны\",", 77 | " \"maxLength\": 3,", 78 | " \"pattern\": \"[a-zA-Z]{3}\"", 79 | " },", 80 | " \"region\": {", 81 | " \"type\": \"string\",", 82 | " \"description\": \"Географический регион, к которому относится страна\",", 83 | " \"enum\": [", 84 | " \"Europe\",", 85 | " \"Africa\",", 86 | " \"Americas\",", 87 | " \"Oceania\",", 88 | " \"Asia\"", 89 | " ]", 90 | " }", 91 | " },", 92 | " \"required\": [", 93 | " \"name\",", 94 | " \"alpha2\",", 95 | " \"alpha3\"", 96 | " ]", 97 | " },", 98 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 99 | "};", 100 | "", 101 | "var countries = [", 102 | " {", 103 | " \"name\": \"Algeria\",", 104 | " \"alpha2\": \"DZ\",", 105 | " \"alpha3\": \"DZA\",", 106 | " \"region\": \"Africa\"", 107 | " },", 108 | " {", 109 | " \"name\": \"Russian Federation\",", 110 | " \"alpha2\": \"RU\",", 111 | " \"alpha3\": \"RUS\",", 112 | " \"region\": \"Europe\"", 113 | " },", 114 | "];", 115 | "", 116 | "countries.forEach(function (country) {", 117 | " pm.test(`List countries from region \"${country.region}\"`, function () {", 118 | " var url = pm.variables.get(\"base_url\") + `/countries?region=${country.region}`;", 119 | "", 120 | " pm.sendRequest(url, function (err, response) {", 121 | " pm.test(`Validate response for region ${country.region}`, () => {", 122 | " var resp = response.json();", 123 | " ", 124 | " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", 125 | " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", 126 | "", 127 | " console.log(\"got\", resp);", 128 | " pm.expect(resp).to.deep.include(country, `Invalid countries list`);", 129 | " });", 130 | " });", 131 | " });", 132 | "});", 133 | "", 134 | "", 135 | "", 136 | "" 137 | ], 138 | "type": "text/javascript" 139 | } 140 | } 141 | ], 142 | "request": { 143 | "method": "GET", 144 | "header": [], 145 | "url": { 146 | "raw": "{{base_url}}/countries", 147 | "host": [ 148 | "{{base_url}}" 149 | ], 150 | "path": [ 151 | "countries" 152 | ] 153 | } 154 | }, 155 | "response": [] 156 | }, 157 | { 158 | "name": "Get country", 159 | "event": [ 160 | { 161 | "listen": "test", 162 | "script": { 163 | "exec": [ 164 | "var countrySchema = {", 165 | " \"type\": \"object\",", 166 | " \"description\": \"Информация о стране из стандарта ISO 3166\",", 167 | " \"properties\": {", 168 | " \"name\": {", 169 | " \"type\": \"string\",", 170 | " \"description\": \"Полное название страны\",", 171 | " \"maxLength\": 100", 172 | " },", 173 | " \"alpha2\": {", 174 | " \"type\": \"string\",", 175 | " \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",", 176 | " \"maxLength\": 2,", 177 | " \"pattern\": \"[a-zA-Z]{2}\"", 178 | " },", 179 | " \"alpha3\": {", 180 | " \"type\": \"string\",", 181 | " \"description\": \"Трехбуквенный код страны\",", 182 | " \"maxLength\": 3,", 183 | " \"pattern\": \"[a-zA-Z]{3}\"", 184 | " },", 185 | " \"region\": {", 186 | " \"type\": \"string\",", 187 | " \"description\": \"Географический регион, к которому относится страна\",", 188 | " \"enum\": [", 189 | " \"Europe\",", 190 | " \"Africa\",", 191 | " \"Americas\",", 192 | " \"Oceania\",", 193 | " \"Asia\"", 194 | " ]", 195 | " }", 196 | " },", 197 | " \"required\": [", 198 | " \"name\",", 199 | " \"alpha2\",", 200 | " \"alpha3\"", 201 | " ],", 202 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 203 | "};", 204 | "", 205 | "var countries = [", 206 | " {", 207 | " \"name\": \"Algeria\",", 208 | " \"alpha2\": \"DZ\",", 209 | " \"alpha3\": \"DZA\",", 210 | " \"region\": \"Africa\"", 211 | " },", 212 | " {", 213 | " \"name\": \"Russian Federation\",", 214 | " \"alpha2\": \"RU\",", 215 | " \"alpha3\": \"RUS\",", 216 | " \"region\": \"Europe\"", 217 | " },", 218 | " {", 219 | " \"name\": \"Kazakhstan\",", 220 | " \"alpha2\": \"KZ\",", 221 | " \"alpha3\": \"KAZ\",", 222 | " \"region\": \"Asia\"", 223 | " }", 224 | "];", 225 | "", 226 | "countries.forEach(function (country) {", 227 | " pm.test(`Get country \"${country.name}\" [existing]`, function () {", 228 | " var url = pm.variables.get(\"base_url\") + \"/countries/\" + country.alpha2;", 229 | "", 230 | " pm.sendRequest(url, function (err, response) {", 231 | " pm.test(\"Validate response\", () => {", 232 | " var resp = response.json();", 233 | "", 234 | " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", 235 | " pm.expect(tv4.validate(resp, countrySchema), \"Invalid JSON schema\").to.be.true;", 236 | "", 237 | " console.log(\"got\", resp, \"expected\", country);", 238 | " pm.expect(resp).to.deep.eq(country, `Got invalid object`);", 239 | " });", 240 | " });", 241 | " });", 242 | "});", 243 | "", 244 | "", 245 | "", 246 | "" 247 | ], 248 | "type": "text/javascript" 249 | } 250 | } 251 | ], 252 | "request": { 253 | "method": "GET", 254 | "header": [], 255 | "url": { 256 | "raw": "{{base_url}}/countries/RU", 257 | "host": [ 258 | "{{base_url}}" 259 | ], 260 | "path": [ 261 | "countries", 262 | "RU" 263 | ] 264 | } 265 | }, 266 | "response": [] 267 | } 268 | ] 269 | }, 270 | { 271 | "name": "03/auth/registration", 272 | "item": [ 273 | { 274 | "name": "Register a user", 275 | "event": [ 276 | { 277 | "listen": "test", 278 | "script": { 279 | "exec": [ 280 | "var schema = {", 281 | " \"type\": \"object\",", 282 | " \"properties\": {", 283 | " \"profile\": {", 284 | " \"type\": \"object\",", 285 | " \"description\": \"Информация о профиле пользователя\",", 286 | " \"properties\": {", 287 | " \"login\": {", 288 | " \"type\": \"string\",", 289 | " \"description\": \"Логин пользователя\",", 290 | " \"maxLength\": 30,", 291 | " \"pattern\": \"[a-zA-Z0-9-]+\"", 292 | " },", 293 | " \"email\": {", 294 | " \"type\": \"string\",", 295 | " \"description\": \"E-mail пользователя\",", 296 | " \"maxLength\": 50", 297 | " },", 298 | " \"countryCode\": {", 299 | " \"type\": \"string\",", 300 | " \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",", 301 | " \"maxLength\": 2,", 302 | " \"pattern\": \"[a-zA-Z]{2}\"", 303 | " },", 304 | " \"isPublic\": {", 305 | " \"type\": \"boolean\",", 306 | " \"description\": \"Является ли данный профиль публичным. \\n\\nПубличные профили доступны другим пользователям: если профиль публичный, любой пользователь платформы сможет получить информацию о пользователе.\\n\"", 307 | " },", 308 | " \"phone\": {", 309 | " \"type\": \"string\",", 310 | " \"description\": \"Номер телефона пользователя в формате +123456789\",", 311 | " \"pattern\": \"\\\\+[\\\\d]+\"", 312 | " },", 313 | " \"image\": {", 314 | " \"type\": \"string\",", 315 | " \"description\": \"Ссылка на фото для аватара пользователя\",", 316 | " \"maxLength\": 200", 317 | " }", 318 | " },", 319 | " \"required\": [", 320 | " \"login\",", 321 | " \"email\",", 322 | " \"countryCode\",", 323 | " \"isPublic\"", 324 | " ]", 325 | " }", 326 | " },", 327 | " \"required\": [", 328 | " \"profile\"", 329 | " ],", 330 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 331 | "};", 332 | "", 333 | "pm.test(\"Register a user\", function () {", 334 | " var url = pm.variables.get(\"base_url\") + \"/auth/register\";", 335 | " const options = {", 336 | " url: url,", 337 | " method: 'POST',", 338 | " header: {", 339 | " 'Content-Type': 'application/json',", 340 | " },", 341 | " body: {", 342 | " mode: 'raw',", 343 | " raw: JSON.stringify({", 344 | " 'login': 'yellowMonkey2',", 345 | " 'email': 'yellowstone1980@you.ru',", 346 | " 'password': '$aba4821FWfew01#.fewA$',", 347 | " 'countryCode': 'RU',", 348 | " 'isPublic': true,", 349 | " 'phone': '+74951239922',", 350 | " })", 351 | " }", 352 | " };", 353 | "", 354 | " const profile = {", 355 | " 'profile': {", 356 | " 'login': 'yellowMonkey2',", 357 | " 'email': 'yellowstone1980@you.ru',", 358 | " 'countryCode': 'RU',", 359 | " 'isPublic': true,", 360 | " 'phone': '+74951239922',", 361 | " }", 362 | " }", 363 | "", 364 | " pm.sendRequest(options, function (err, response) {", 365 | " pm.test(\"Validate response\", () => {", 366 | " var resp = response.json();", 367 | " ", 368 | " pm.expect(response.code).to.be.eq(201, \"Invalid response code status\");", 369 | " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", 370 | "", 371 | " console.log(\"got\", resp, \"expected\", profile);", 372 | " pm.expect(resp).to.deep.eq(profile, `Got invalid object`);", 373 | " });", 374 | " });", 375 | "});", 376 | "", 377 | "", 378 | "", 379 | "" 380 | ], 381 | "type": "text/javascript" 382 | } 383 | } 384 | ], 385 | "request": { 386 | "method": "GET", 387 | "header": [], 388 | "url": { 389 | "raw": "{{base_url}}/ping", 390 | "host": [ 391 | "{{base_url}}" 392 | ], 393 | "path": [ 394 | "ping" 395 | ] 396 | } 397 | }, 398 | "response": [] 399 | } 400 | ] 401 | }, 402 | { 403 | "name": "04/auth/sign-in", 404 | "item": [ 405 | { 406 | "name": "Register a user", 407 | "event": [ 408 | { 409 | "listen": "test", 410 | "script": { 411 | "exec": [ 412 | "var schema = {", 413 | " \"type\": \"object\",", 414 | " \"properties\": {", 415 | " \"profile\": {", 416 | " \"type\": \"object\",", 417 | " \"description\": \"Информация о профиле пользователя\",", 418 | " \"properties\": {", 419 | " \"login\": {", 420 | " \"type\": \"string\",", 421 | " \"description\": \"Логин пользователя\",", 422 | " \"maxLength\": 30,", 423 | " \"pattern\": \"[a-zA-Z0-9-]+\"", 424 | " },", 425 | " \"email\": {", 426 | " \"type\": \"string\",", 427 | " \"description\": \"E-mail пользователя\",", 428 | " \"maxLength\": 50", 429 | " },", 430 | " \"countryCode\": {", 431 | " \"type\": \"string\",", 432 | " \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",", 433 | " \"maxLength\": 2,", 434 | " \"pattern\": \"[a-zA-Z]{2}\"", 435 | " },", 436 | " \"isPublic\": {", 437 | " \"type\": \"boolean\",", 438 | " \"description\": \"Является ли данный профиль публичным. \\n\\nПубличные профили доступны другим пользователям: если профиль публичный, любой пользователь платформы сможет получить информацию о пользователе.\\n\"", 439 | " },", 440 | " \"phone\": {", 441 | " \"type\": \"string\",", 442 | " \"description\": \"Номер телефона пользователя в формате +123456789\",", 443 | " \"pattern\": \"\\\\+[\\\\d]+\"", 444 | " },", 445 | " \"image\": {", 446 | " \"type\": \"string\",", 447 | " \"description\": \"Ссылка на фото для аватара пользователя\",", 448 | " \"maxLength\": 200", 449 | " }", 450 | " },", 451 | " \"required\": [", 452 | " \"login\",", 453 | " \"email\",", 454 | " \"countryCode\",", 455 | " \"isPublic\"", 456 | " ]", 457 | " }", 458 | " },", 459 | " \"required\": [", 460 | " \"profile\"", 461 | " ],", 462 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 463 | "};", 464 | "", 465 | "pm.test(\"Register a user\", function () {", 466 | " var url = pm.variables.get(\"base_url\") + \"/auth/register\";", 467 | " const options = {", 468 | " url: url,", 469 | " method: 'POST',", 470 | " header: {", 471 | " 'Content-Type': 'application/json',", 472 | " },", 473 | " body: {", 474 | " mode: 'raw',", 475 | " raw: JSON.stringify({", 476 | " 'login': 'yellowMonkey2',", 477 | " 'email': 'yellowstone1980@you.ru',", 478 | " 'password': '$aba4821FWfew01#.fewA$',", 479 | " 'countryCode': 'RU',", 480 | " 'isPublic': true,", 481 | " 'phone': '+74951239922',", 482 | " })", 483 | " }", 484 | " };", 485 | "", 486 | " const profile = {", 487 | " 'profile': {", 488 | " 'login': 'yellowMonkey2',", 489 | " 'email': 'yellowstone1980@you.ru',", 490 | " 'countryCode': 'RU',", 491 | " 'isPublic': true,", 492 | " 'phone': '+74951239922',", 493 | " }", 494 | " }", 495 | "", 496 | " pm.sendRequest(options, function (err, response) {", 497 | " pm.test(\"Validate response\", () => {", 498 | " var resp = response.json();", 499 | " ", 500 | " pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");", 501 | " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", 502 | "", 503 | " console.log(\"got\", resp, \"expected\", profile);", 504 | " pm.expect(resp).to.deep.eq(profile, `Got invalid object`);", 505 | " });", 506 | " });", 507 | "});", 508 | "", 509 | "", 510 | "", 511 | "" 512 | ], 513 | "type": "text/javascript" 514 | } 515 | } 516 | ], 517 | "request": { 518 | "method": "GET", 519 | "header": [], 520 | "url": { 521 | "raw": "{{base_url}}/ping", 522 | "host": [ 523 | "{{base_url}}" 524 | ], 525 | "path": [ 526 | "ping" 527 | ] 528 | } 529 | }, 530 | "response": [] 531 | }, 532 | { 533 | "name": "Sign in", 534 | "event": [ 535 | { 536 | "listen": "test", 537 | "script": { 538 | "exec": [ 539 | "var schema = {", 540 | " \"type\": \"object\",", 541 | " \"properties\": {", 542 | " \"token\": {", 543 | " \"type\": \"string\",", 544 | " \"description\": \"Сгенерированный токен пользователя\",", 545 | " \"minLength\": 20,", 546 | " }", 547 | " },", 548 | " \"required\": [", 549 | " \"token\"", 550 | " ],", 551 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 552 | "};", 553 | "", 554 | "pm.test(\"Sign in\", function () {", 555 | " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", 556 | " const options = {", 557 | " url: url,", 558 | " method: 'POST',", 559 | " header: {", 560 | " 'Content-Type': 'application/json',", 561 | " },", 562 | " body: {", 563 | " mode: 'raw',", 564 | " raw: JSON.stringify({", 565 | " 'login': 'yellowMonkey2',", 566 | " 'password': '$aba4821FWfew01#.fewA$',", 567 | " })", 568 | " }", 569 | " };", 570 | "", 571 | " pm.sendRequest(options, function (err, response) {", 572 | " pm.test(\"Validate sign-in response\", () => {", 573 | " var resp = response.json();", 574 | "", 575 | " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", 576 | " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", 577 | "", 578 | " console.log('Token', resp.token);", 579 | " });", 580 | " });", 581 | "});", 582 | "", 583 | "", 584 | "" 585 | ], 586 | "type": "text/javascript" 587 | } 588 | } 589 | ], 590 | "request": { 591 | "method": "GET", 592 | "header": [], 593 | "url": { 594 | "raw": "{{base_url}}/ping", 595 | "host": [ 596 | "{{base_url}}" 597 | ], 598 | "path": [ 599 | "ping" 600 | ] 601 | } 602 | }, 603 | "response": [] 604 | } 605 | ] 606 | }, 607 | { 608 | "name": "05/me", 609 | "item": [ 610 | { 611 | "name": "Register a user", 612 | "event": [ 613 | { 614 | "listen": "test", 615 | "script": { 616 | "exec": [ 617 | "var schema = {", 618 | " \"type\": \"object\",", 619 | " \"properties\": {", 620 | " \"profile\": {", 621 | " \"type\": \"object\",", 622 | " \"description\": \"Информация о профиле пользователя\",", 623 | " \"properties\": {", 624 | " \"login\": {", 625 | " \"type\": \"string\",", 626 | " \"description\": \"Логин пользователя\",", 627 | " \"maxLength\": 30,", 628 | " \"pattern\": \"[a-zA-Z0-9-]+\"", 629 | " },", 630 | " \"email\": {", 631 | " \"type\": \"string\",", 632 | " \"description\": \"E-mail пользователя\",", 633 | " \"maxLength\": 50", 634 | " },", 635 | " \"countryCode\": {", 636 | " \"type\": \"string\",", 637 | " \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",", 638 | " \"maxLength\": 2,", 639 | " \"pattern\": \"[a-zA-Z]{2}\"", 640 | " },", 641 | " \"isPublic\": {", 642 | " \"type\": \"boolean\",", 643 | " \"description\": \"Является ли данный профиль публичным. \\n\\nПубличные профили доступны другим пользователям: если профиль публичный, любой пользователь платформы сможет получить информацию о пользователе.\\n\"", 644 | " },", 645 | " \"phone\": {", 646 | " \"type\": \"string\",", 647 | " \"description\": \"Номер телефона пользователя в формате +123456789\",", 648 | " \"pattern\": \"\\\\+[\\\\d]+\"", 649 | " },", 650 | " \"image\": {", 651 | " \"type\": \"string\",", 652 | " \"description\": \"Ссылка на фото для аватара пользователя\",", 653 | " \"maxLength\": 200", 654 | " }", 655 | " },", 656 | " \"required\": [", 657 | " \"login\",", 658 | " \"email\",", 659 | " \"countryCode\",", 660 | " \"isPublic\"", 661 | " ]", 662 | " }", 663 | " },", 664 | " \"required\": [", 665 | " \"profile\"", 666 | " ],", 667 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 668 | "};", 669 | "", 670 | "pm.test(\"Register a user\", function () {", 671 | " var url = pm.variables.get(\"base_url\") + \"/auth/register\";", 672 | " const options = {", 673 | " url: url,", 674 | " method: 'POST',", 675 | " header: {", 676 | " 'Content-Type': 'application/json',", 677 | " },", 678 | " body: {", 679 | " mode: 'raw',", 680 | " raw: JSON.stringify({", 681 | " 'login': 'yellowMonkey10000',", 682 | " 'email': 'yellowstone1980@you.ru',", 683 | " 'password': '$aba4821FWfew01#.fewA$',", 684 | " 'countryCode': 'RU',", 685 | " 'isPublic': true,", 686 | " 'phone': '+74951239922',", 687 | " })", 688 | " }", 689 | " };", 690 | "", 691 | " const profile = {", 692 | " 'profile': {", 693 | " 'login': 'yellowMonkey10000',", 694 | " 'email': 'yellowstone1980@you.ru',", 695 | " 'countryCode': 'RU',", 696 | " 'isPublic': true,", 697 | " 'phone': '+74951239922',", 698 | " }", 699 | " }", 700 | "", 701 | " pm.sendRequest(options, function (err, response) {", 702 | " pm.test(\"Validate response\", () => {", 703 | " var resp = response.json();", 704 | " ", 705 | " pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");", 706 | " });", 707 | " });", 708 | "});", 709 | "", 710 | "", 711 | "", 712 | "" 713 | ], 714 | "type": "text/javascript" 715 | } 716 | } 717 | ], 718 | "request": { 719 | "method": "GET", 720 | "header": [], 721 | "url": { 722 | "raw": "{{base_url}}/ping", 723 | "host": [ 724 | "{{base_url}}" 725 | ], 726 | "path": [ 727 | "ping" 728 | ] 729 | } 730 | }, 731 | "response": [] 732 | }, 733 | { 734 | "name": "Sign in", 735 | "event": [ 736 | { 737 | "listen": "test", 738 | "script": { 739 | "exec": [ 740 | "var schema = {", 741 | " \"type\": \"object\",", 742 | " \"properties\": {", 743 | " \"token\": {", 744 | " \"type\": \"string\",", 745 | " \"description\": \"Сгенерированный токен пользователя\",", 746 | " \"minLength\": 20,", 747 | " }", 748 | " },", 749 | " \"required\": [", 750 | " \"token\"", 751 | " ],", 752 | " \"$schema\": \"http://json-schema.org/draft-04/schema#\"", 753 | "};", 754 | "", 755 | "pm.test(\"Sign in\", function () {", 756 | " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", 757 | " var options = {", 758 | " url: url,", 759 | " method: 'POST',", 760 | " header: {", 761 | " 'Content-Type': 'application/json',", 762 | " },", 763 | " body: {", 764 | " mode: 'raw',", 765 | " raw: JSON.stringify({", 766 | " 'login': 'yellowMonkey10000',", 767 | " 'password': '$aba4821FWfew01#.fewA$',", 768 | " })", 769 | " }", 770 | " };", 771 | " ", 772 | " pm.sendRequest(options, function (err, response) {", 773 | " pm.test(\"Validate sign-in response\", () => {", 774 | " var resp = response.json();", 775 | "", 776 | " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", 777 | " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", 778 | "", 779 | " pm.environment.set(\"05_profile_token\", resp.token);", 780 | " console.log(\"Token has been saved\")", 781 | " });", 782 | " });", 783 | "});", 784 | "", 785 | "", 786 | "" 787 | ], 788 | "type": "text/javascript" 789 | } 790 | } 791 | ], 792 | "request": { 793 | "method": "GET", 794 | "header": [], 795 | "url": { 796 | "raw": "{{base_url}}/ping", 797 | "host": [ 798 | "{{base_url}}" 799 | ], 800 | "path": [ 801 | "ping" 802 | ] 803 | } 804 | }, 805 | "response": [] 806 | }, 807 | { 808 | "name": "Get my profile", 809 | "event": [ 810 | { 811 | "listen": "test", 812 | "script": { 813 | "exec": [ 814 | "pm.test(\"Get profile\", function () {", 815 | " const url = pm.variables.get(\"base_url\") + \"/me/profile\";", 816 | " const token = pm.environment.get(\"05_profile_token\");", 817 | " const options = {", 818 | " url: url,", 819 | " method: 'GET',", 820 | " header: {", 821 | " 'Content-Type': 'application/json',", 822 | " 'Authorization': `Bearer ${token}`,", 823 | " },", 824 | " };", 825 | "", 826 | " pm.sendRequest(options, function (err, response) {", 827 | " pm.test(\"Validate profile\", () => {", 828 | " var resp = response.json();", 829 | "", 830 | " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", 831 | "", 832 | " console.log(\"Got profile\", resp);", 833 | " pm.expect(resp.login).to.be.eq(\"yellowMonkey10000\", \"Invalid login\");", 834 | " });", 835 | " });", 836 | "});", 837 | "", 838 | "" 839 | ], 840 | "type": "text/javascript" 841 | } 842 | } 843 | ], 844 | "request": { 845 | "method": "GET", 846 | "header": [], 847 | "url": { 848 | "raw": "{{base_url}}/ping", 849 | "host": [ 850 | "{{base_url}}" 851 | ], 852 | "path": [ 853 | "ping" 854 | ] 855 | } 856 | }, 857 | "response": [] 858 | } 859 | ] 860 | } 861 | ], 862 | "event": [ 863 | { 864 | "listen": "prerequest", 865 | "script": { 866 | "type": "text/javascript", 867 | "exec": [ 868 | "" 869 | ] 870 | } 871 | }, 872 | { 873 | "listen": "test", 874 | "script": { 875 | "type": "text/javascript", 876 | "exec": [ 877 | "" 878 | ] 879 | } 880 | } 881 | ], 882 | "variable": [ 883 | { 884 | "key": "base_url", 885 | "value": "http://localhost:57424/api", 886 | "type": "default" 887 | } 888 | ] 889 | } --------------------------------------------------------------------------------