From b9a76548305767560591408031411537a4094109 Mon Sep 17 00:00:00 2001 From: "cu-workflow-launcher[bot]" <154891279+cu-workflow-launcher[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:53:32 +0000 Subject: [PATCH] Update protected files --- README.md | 24 +++++-- tests/openapi.yml | 58 +++++++++++++++-- tests/public-tests.json | 137 ++++++++++++++++++++-------------------- 3 files changed, 142 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index abc71fd..d4aa24c 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Для получения баллов за группу тестов решение должно пройти все тесты из данной группы. -Группы тестов могут зависеть друг от друга. Если группа B зависит от группы B, при тестировании группы B могут использоваться эндпоинты, участвовавшие в тестировании группы A. Это свойство транзитивно! +Группы тестов могут зависеть друг от друга. Если группа B зависит от группы A, при тестировании группы B могут использоваться эндпоинты, участвовавшие в тестировании группы A. Это свойство транзитивно! | Название группы | Описание | Баллы | От каких групп зависит | |------------------|------------------------------------|-------|------------------------| @@ -58,7 +58,7 @@ | 05/me | Получение и редактирование собственного профиля. | 8 | - 04/auth/sign-in | | 06/profiles | Получение профиля по логину. | 5 | - 04/auth/sign-in | | 07/password | Изменение пароля. | 7 | - 05/me | -| 08/friends | Друзья! | 12 | - 04/auth/sign-in | +| 08/friends | Друзья! | 12 | - 04/auth/sign-in
- 06/profiles | | 09/posts/publish | Публикация поста и получение по ID. | 12 | - 05/me
- 08/friends | | 10/posts/feed | Получение новостной ленты. | 16 | - 09/posts/publish | | 11/posts/likes | Лайки и дизлайки. | 20 | - 10/posts/feed | @@ -91,7 +91,7 @@ ```json { "name": "полное название", - "alpha2": "двухбуквенный код страны", + "alpha2": "двухбуквенный код страны (в верхнем регистре)", "alpha3": "трехбуквенный код страны", "region": "географический регион" } @@ -125,6 +125,8 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES Приложение вправе менять содержимое СУБД. Если вам требуются дополнительные таблицы, создавайте их самостоятельно при старте приложения (не забудьте про `IF NOT EXISTS`). +При поиске страны по двухбуквенному коду можно реализовать регистрозависимый поиск, то есть пользователь всегда будет указывать значения в нужном регистре. + ### 03/auth/register Эндпоинт `/auth/register` используется для первичной регистрации пользователей. @@ -158,6 +160,8 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES Обратите внимание, в некоторых ситуациях профиль пользователя получить нельзя (в зависимости от значения параметра `isPublic`). Для получения дополнительных деталей ознакомьтесь со спецификацией API. +В данной группе тестов не будет проверяться логика с друзьями пользователя. + ### 07/password С помощью `/me/updatePassword` у пользователя появляется возможность изменить пароль от своего аккаунта. @@ -175,6 +179,8 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES В приложении появляется возможность добавлять и удалять других пользователей из списка своих друзей. И конечно же можно посмотреть список своих друзей. +Свойство быть другом — одностороннее. Если Петя добавит Машу в друзья, то профиль Пети становится доступным для Маши, даже если у Пети закрытый профиль. + Чтобы не нагружать сервера и клиенты слишком сильно, в запросах на получение списка друзей используется пагинация. С помощью параметров `offset` и `limit` можно "постранично" получить весь список друзей, запрашивая данные порционно. @@ -250,7 +256,7 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES Для локального тестирования вы можете пользоваться [Postman](https://www.postman.com/). В директории проекта кто-то из коллег оставил [Postman коллекцию](./tests/public-tests.json) с публичными тестами для API. Не забудьте переопределить `base_url` в переменных коллекции. -Для инициализации СУБД PostgreSQL можно использовать [заранее подготовленный скрипт](./tests/init-database.sh), из которого можно выудить SQL запросы. +Для инициализации СУБД PostgreSQL можно использовать [заранее подготовленный скрипт](./tests/init-database.sh), из которого можно выудить SQL запросы. Обратите внимание, данный файл предназначен для локального тестирования. Тестирующая система не использует данный файл. Чтобы локальное тестирование было максимально приближенным к тестированию в CI, мы рекомендуем запускать PostgreSQL и ваше приложение в Docker контейнерах (связанных одной сетью). @@ -263,6 +269,16 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES Не забывайте делать `git pull --rebase`, чтобы загрузить актуальные требования в локальную версию репозитория. +### 28.02.2023 + +Коллеги передали, что связь "друзья" является односторонней. + +Если профиль пользователя закрыт, доступ к его профилю и его публикациям появляется у пользователей, кого данный пользователь добавил в друзья. + +При если это Маша добавила Петю в друзья, не значит, что Петя добавил Машу в друзья. Можно расценивать добавление в друзья как подписку. + +Группа `08/friends` зависит от группы `06/profiles`. + ### 27.02.2023 Коллеги, привет! Ничего критичного... Уговорили нашего Devops-инженера расширить список переменных с информацией для подключения к PostgreSQL. Смотрите секцию с описанием ENV переменных. Надеемся, теперь станет проще! diff --git a/tests/openapi.yml b/tests/openapi.yml index f4c4c0f..0fcad52 100644 --- a/tests/openapi.yml +++ b/tests/openapi.yml @@ -47,6 +47,9 @@ paths: type: array items: $ref: "#/components/schemas/countryRegion" + example: + - Europe + - Asia responses: "200": description: Список стран, соответствующих указанному фильтру. Страны должны быть отсортированы лексикографически по двухбуквенному коду. @@ -56,6 +59,12 @@ paths: type: array items: $ref: "#/components/schemas/country" + "400": + description: Формат входного запроса не соответствует формату либо переданы неверные значения. + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" /countries/{alpha2}: get: summary: Получить страну по alpha2 коду @@ -161,12 +170,27 @@ paths: description: | Процедура аутентификации по логину и паролю позволяет получить токен, который в дальнейшем будет использоваться пользователем для выполнения операций, требующих авторизацию. - Сервер должен генерировать уникальные токены, имеющие время жизни (в рамках задачи это будет 24 часа). После истечения времени действия токен должен быть недействительным и не может использоваться для аутентификации. + Сервер должен генерировать уникальные токены, имеющие время жизни (на усмотрение разработчика, от 1 до 24 часов). После истечения времени действия токен должен быть недействительным и не может использоваться для аутентификации. Токен является уникальным строковым значением с высокой энтропией (злоумышленник не сможет его "подобрать" перебором). При каждой новой аутентификации генерируется новый уникальный токен, который ранее не был использован. Можно использовать JWT. В дальнейшем полученный токен будет использоваться для авторизации пользовательских запросов. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. Следовательно, сервер должен уметь идентифицировать пользователя по токену. operationId: authSignIn + requestBody: + description: Данные для аутентификации пользователя. + required: true + content: + application/json: + schema: + type: object + properties: + login: + $ref: "#/components/schemas/userLogin" + password: + $ref: "#/components/schemas/userPassword" + required: + - login + - password responses: "200": description: Успешная аутентификация @@ -253,13 +277,36 @@ paths: application/json: schema: $ref: "#/components/schemas/errorResponse" + "400": + description: | + Данные не соответствуют ожидаемому формату и требованиям. + + Например, данную ошибку необходимо возвращать в следующих ситуациях (это не полный список): + + - Страна с указанным кодом не найдена. + - Длина ссылки на аватар пользователя превышает допустимый лимит. + + Для ознакомления с форматом и требованиями к регистрационным данным обратите внимание на описание моделей в Open API спецификации. + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" + "409": + description: | + Нарушено требование на уникальность авторизационных данных пользователей. + + Данный код ответа должен использоваться, если указанный номер телефона занят другим пользователем. + content: + application/json: + schema: + $ref: "#/components/schemas/errorResponse" /profiles/{login}: get: summary: Получение профиля пользователя по логину description: | Используется для получения профиля другого пользователя по логину. - Если профиль пользователя публичен (`isPublic: true`), его может получить любой другой пользователь. Иначе профиль пользователя получить нельзя. + Если профиль пользователя публичен (`isPublic: true`), его может получить любой другой пользователь. Если профиль пользователя закрыт, его могут получить пользователи, которых данный пользователь добавил в друзья. При этом собственный профиль пользователь может получить всегда. Сервер должен идентифицировать пользователя по переданному токену в заголовке `Authorization`. security: @@ -535,13 +582,13 @@ paths: $ref: "#/components/schemas/errorResponse" /posts/{postId}: get: - summary: Получить ленту со своими постами + summary: Получить публикацию по ID description: | Используется для получения публикации по её идентификатору. Если публикация принадлежит пользователю с публичным профилем, её может получить любой другой аутентифицированный пользователь. - Если профиль автора закрыт, только автор может получить публикацию по ID. + Если профиль автора закрыт, она доступна автору и пользователям, кого автор добавил в друзья. Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. security: @@ -611,7 +658,7 @@ paths: Используется для получения списка публикаций другого пользователя. Если профиль пользователя открыт, его посты доступны всем. - Если профиль пользователя закрыт, его посты доступны самому пользователю и пользователям, на кого он подписан. + Если профиль пользователя закрыт, его посты доступны самому пользователю и пользователям, кого он добавил в друзья. Для плавной работы приложения используется пагинация. @@ -800,6 +847,7 @@ components: description: Номер телефона пользователя в формате +123456789 pattern: \+[\d]+ example: "+74951239922" + maxLength: 20 userImage: type: string description: Ссылка на фото для аватара пользователя diff --git a/tests/public-tests.json b/tests/public-tests.json index 4ce20fe..ffde4a2 100644 --- a/tests/public-tests.json +++ b/tests/public-tests.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "c30d4269-9aa0-4d39-a5de-37901602b269", + "_postman_id": "a5dbb75a-d7a8-4e8c-8660-9925345a3a86", "name": "PROD round 2: public tests [1]", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -451,41 +451,40 @@ " var resp = response.json();", " pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");", " });", - " });", - "});", "", - "pm.test(\"Sign in\", function () {", - " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", - " const options = {", - " url: url,", - " method: 'POST',", - " header: {", - " 'Content-Type': 'application/json',", - " },", - " body: {", - " mode: 'raw',", - " raw: JSON.stringify({", - " 'login': 'yellowMonkey11000',", - " 'password': '$aba4821FWfew01#.fewA$',", - " })", - " }", - " };", + " pm.test(\"Sign in\", function () {", + " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", + " const options = {", + " url: url,", + " method: 'POST',", + " header: {", + " 'Content-Type': 'application/json',", + " },", + " body: {", + " mode: 'raw',", + " raw: JSON.stringify({", + " 'login': 'yellowMonkey11000',", + " 'password': '$aba4821FWfew01#.fewA$',", + " })", + " }", + " };", "", - " pm.sendRequest(options, function (err, response) {", - " pm.test(\"Validate sign-in response\", () => {", - " var resp = response.json();", + " pm.sendRequest(options, function (err, response) {", + " pm.test(\"Validate sign-in response\", () => {", + " var resp = response.json();", "", - " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", - " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", + " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", + " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", "", - " console.log('Token', resp.token);", + " console.log('Token', resp.token);", + " });", + " });", " });", " });", "});", "", "", "", - "", "" ], "type": "text/javascript" @@ -561,56 +560,56 @@ " var resp = response.json();", " pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");", " });", - " });", - "});", "", - "pm.test(\"Sign in\", function () {", - " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", - " const options = {", - " url: url,", - " method: 'POST',", - " header: {", - " 'Content-Type': 'application/json',", - " },", - " body: {", - " mode: 'raw',", - " raw: JSON.stringify({", - " 'login': 'yellowMonkey11000',", - " 'password': '$aba4821FWfew01#.fewA$',", - " })", - " }", - " };", + " pm.test(\"Sign in\", function () {", + " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", + " const options = {", + " url: url,", + " method: 'POST',", + " header: {", + " 'Content-Type': 'application/json',", + " },", + " body: {", + " mode: 'raw',", + " raw: JSON.stringify({", + " 'login': 'yellowMonkey11000',", + " 'password': '$aba4821FWfew01#.fewA$',", + " })", + " }", + " };", "", - " pm.sendRequest(options, function (err, response) {", - " pm.test(\"Validate sign-in response\", () => {", - " var resp = response.json();", + " pm.sendRequest(options, function (err, response) {", + " pm.test(\"Validate sign-in response\", () => {", + " var resp = response.json();", "", - " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", - " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", + " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", + " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;", "", - " pm.environment.set(\"05_profile_token\", resp.token);", - " console.log(\"Token has been saved\")", + " pm.environment.set(\"05_profile_token\", resp.token);", + " console.log(\"Token has been saved\")", "", - " pm.test(\"Get profile\", function () {", - " const url = pm.variables.get(\"base_url\") + \"/me/profile\";", - " const token = pm.environment.get(\"05_profile_token\");", - " const options = {", - " url: url,", - " method: 'GET',", - " header: {", - " 'Content-Type': 'application/json',", - " 'Authorization': `Bearer ${token}`,", - " },", - " };", + " pm.test(\"Get profile\", function () {", + " const url = pm.variables.get(\"base_url\") + \"/me/profile\";", + " const token = pm.environment.get(\"05_profile_token\");", + " const options = {", + " url: url,", + " method: 'GET',", + " header: {", + " 'Content-Type': 'application/json',", + " 'Authorization': `Bearer ${token}`,", + " },", + " };", "", - " pm.sendRequest(options, function (err, response) {", - " pm.test(\"Validate profile\", () => {", - " var resp = response.json();", + " pm.sendRequest(options, function (err, response) {", + " pm.test(\"Validate profile\", () => {", + " var resp = response.json();", "", - " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", + " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");", "", - " console.log(\"Got profile\", resp);", - " pm.expect(resp.login).to.be.eq(\"yellowMonkey11000\", \"Invalid login\");", + " console.log(\"Got profile\", resp);", + " pm.expect(resp.login).to.be.eq(\"yellowMonkey11000\", \"Invalid login\");", + " });", + " });", " });", " });", " });", @@ -622,6 +621,8 @@ "", "", "", + "", + "", "" ], "type": "text/javascript"