├── .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 | }
--------------------------------------------------------------------------------