Update protected files

This commit is contained in:
cu-workflow-launcher[bot]
2024-03-02 17:23:28 +00:00
committed by GitHub
parent ed687650ba
commit 3c335f9efe
3 changed files with 365 additions and 125 deletions
+29 -3
View File
@@ -30,6 +30,8 @@
- `POSTGRES_DATABASE` — имя базы данных PostgreSQL, с которой должно работать приложение. - `POSTGRES_DATABASE` — имя базы данных PostgreSQL, с которой должно работать приложение.
- `RANDOM_SECRET` — псевдо-случайная последовательность из 128 символов (a-z, A-Z, 0-9), сгенерированная тестирующей системой. Можете использовать её, если вашему приложению необходим секретный ключ (например, для JWT). Если вам не требуется данное значение, можете его не использовать.
Автор приложения сам выбирает, с какими из переменных окружения ему комфортно работать. Автор приложения сам выбирает, с какими из переменных окружения ему комфортно работать.
Учитывая современные реалии, приложение будет запускаться через Docker контейнер. В репозитории присутствует Dockerfile, с помощью которого будет собираться образ приложения. Учитывая современные реалии, приложение будет запускаться через Docker контейнер. В репозитории присутствует Dockerfile, с помощью которого будет собираться образ приложения.
@@ -80,6 +82,8 @@
Если структура запроса не соответствует требованиям и описанному формату, по умолчанию возвращается код ответа 400. Если структура запроса не соответствует требованиям и описанному формату, по умолчанию возвращается код ответа 400.
Если указан более специфичный код ответа, используйте его. Если указан более специфичный код ответа, используйте его.
Если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным.
### 01/ping ### 01/ping
Достаточно реализовать возврат успешного ответа (с кодом `200`) на запрос `GET /api/ping`. Содержимое тела ответа при этом не валидируется, можно возвращать `"ok"`. Достаточно реализовать возврат успешного ответа (с кодом `200`) на запрос `GET /api/ping`. Содержимое тела ответа при этом не валидируется, можно возвращать `"ok"`.
@@ -217,7 +221,7 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES
Всегда запоминается последняя реакция пользователя. Если пользователь поставил лайк два раза подряд, эффект лайка остается. Всегда запоминается последняя реакция пользователя. Если пользователь поставил лайк два раза подряд, эффект лайка остается.
Если пользователь поставил лайк, а потом дизлайк, остается реакция дизлайка. Если пользователь поставил лайк, а потом дизлайк, остается реакция дизлайка.
В полях `likesCount` и `dislikesCount` необходимо отразить уникальное число лайков и дизлайков. В полях `likesCount` и `dislikesCount` необходимо отразить число лайков и дизлайков публикации, при этом от каждого пользователя учитывается только его самая последняя реакция.
## Тестирование ## Тестирование
@@ -272,6 +276,28 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES
Не забывайте делать `git pull --rebase`, чтобы загрузить актуальные требования в локальную версию репозитория. Не забывайте делать `git pull --rebase`, чтобы загрузить актуальные требования в локальную версию репозитория.
### 02.03.2024
Коллеги, привет! Ваш Project Manager передал все опасения касательно сроков, поэтому мы договорились,
что финальное тестирование будет проходить, опираясь на версию спецификации, опубликованную 3 марта 15:00 (МСК).
Напоминаем! В тестах будет проверяться только то поведение, которое было описано в README либо спецификации.
Обращаем внимание: при работе с публичным набором тестов в Postman обращайте внимание на содержимое вкладки Tests, именно там заключена логика тестирования.
Request-path в Postman изменены на `GET /api/ping`, чтобы нерелевантная информация в логах не смущала вас.
И еще немного полезных замечаний:
- Если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным.
- Если вам нужен секретный ключ, можете (необязательно!) использовать `RANDOM_SECRET`.
- Timezone при передаче времени не так важна. Важно, чтобы счетчик времени монотонно рос и был одного формата во всех ответах backend'а.
- Чтобы отобразить число лайков и дизлайков поста, учитывайте только последнюю реакцию от каждого пользователя.
- Если структура ответа предполагает опциональность поля, сервер не должен возвращать данное поле при его отсутствии.
### 01.03.2024 ### 01.03.2024
Коллеги, с первым днем весны! Коллеги, с первым днем весны!
@@ -279,7 +305,7 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES
Напоминаем вам, что корректные логин, номер телефона, e-mail и другая подобная информация должны состоять минимум из одного символа! Напоминаем вам, что корректные логин, номер телефона, e-mail и другая подобная информация должны состоять минимум из одного символа!
А длина уникального идентификатора публикации не превышает разумных значений... А длина уникального идентификатора публикации не превышает разумных значений...
Также добавим, что в эндпоинте `/countries` если хотя бы один переданный регион является некорректным, весь запрос считается некорректным. Также добавим, что в эндпоинте `/countries` если хотя бы один переданный регион является некорректным, весь запрос считается некорректным. Это общее правило: если запрос некорректен хотя бы в одном параметре, весь запрос отвергается и признается некорректным.
### 28.02.2024 ### 28.02.2024
@@ -287,7 +313,7 @@ INSERT INTO countries (name, alpha2, alpha3, region) VALUES
Если профиль пользователя закрыт, доступ к его профилю и его публикациям появляется у пользователей, кого данный пользователь добавил в друзья. Если профиль пользователя закрыт, доступ к его профилю и его публикациям появляется у пользователей, кого данный пользователь добавил в друзья.
При если это Маша добавила Петю в друзья, не значит, что Петя добавил Машу в друзья. Можно расценивать добавление в друзья как подписку. При этом если Маша добавила Петю в друзья, не значит, что Петя добавил Машу в друзья. Можно расценивать добавление в друзья как подписку.
Группа `08/friends` зависит от группы `06/profiles`. Группа `08/friends` зависит от группы `06/profiles`.
+5 -2
View File
@@ -251,6 +251,7 @@ paths:
Если значение передано, данное изменение должно быть отражено в профиле пользователя. Если значение передано, данное изменение должно быть отражено в профиле пользователя.
Если значение не передано, необходимо оставить прежнее значение параметра. Если значение не передано, необходимо оставить прежнее значение параметра.
Если передана пустая структура, ничего изменять не требуется, возвращается успешный ответ.
required: true required: true
content: content:
application/json: application/json:
@@ -630,6 +631,8 @@ paths:
Для плавной работы приложения используется пагинация. Для плавной работы приложения используется пагинация.
Можете считать, что пользователей с логином `my` не будет.
Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`. Сервер должен идентифицировать пользователя по переданному токену. Значение токена будет подставляться в заголовок `Authorization` в формате `Bearer {token}`.
security: security:
- bearerAuth: [] - bearerAuth: []
@@ -722,7 +725,7 @@ paths:
$ref: "#/components/schemas/postId" $ref: "#/components/schemas/postId"
responses: responses:
"200": "200":
description: Лайк засчитан. description: Реакция засчитана, возвращайте пубикацию с актуальным числом лайков и дизлайков.
content: content:
application/json: application/json:
schema: schema:
@@ -758,7 +761,7 @@ paths:
$ref: "#/components/schemas/postId" $ref: "#/components/schemas/postId"
responses: responses:
"200": "200":
description: Дизлайк засчитан. description: Реакция засчитана, возвращайте пубикацию с актуальным числом лайков и дизлайков.
content: content:
application/json: application/json:
schema: schema:
+331 -120
View File
@@ -118,7 +118,7 @@
" var url = pm.variables.get(\"base_url\") + `/countries?region=${country.region}`;", " var url = pm.variables.get(\"base_url\") + `/countries?region=${country.region}`;",
"", "",
" pm.sendRequest(url, function (err, response) {", " pm.sendRequest(url, function (err, response) {",
" pm.test(\"Validate response\", () => {", " pm.test(`Validate response for region ${country.region}`, () => {",
" var resp = response.json();", " 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\");",
@@ -383,16 +383,15 @@
} }
], ],
"request": { "request": {
"method": "POST", "method": "GET",
"header": [], "header": [],
"url": { "url": {
"raw": "{{base_url}}/auth/register", "raw": "{{base_url}}/ping",
"host": [ "host": [
"{{base_url}}" "{{base_url}}"
], ],
"path": [ "path": [
"auth", "ping"
"register"
] ]
} }
}, },
@@ -403,6 +402,133 @@
{ {
"name": "04/auth/sign-in", "name": "04/auth/sign-in",
"item": [ "item": [
{
"name": "Register a user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var schema = {",
" \"type\": \"object\",",
" \"properties\": {",
" \"profile\": {",
" \"type\": \"object\",",
" \"description\": \"Информация о профиле пользователя\",",
" \"properties\": {",
" \"login\": {",
" \"type\": \"string\",",
" \"description\": \"Логин пользователя\",",
" \"maxLength\": 30,",
" \"pattern\": \"[a-zA-Z0-9-]+\"",
" },",
" \"email\": {",
" \"type\": \"string\",",
" \"description\": \"E-mail пользователя\",",
" \"maxLength\": 50",
" },",
" \"countryCode\": {",
" \"type\": \"string\",",
" \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",",
" \"maxLength\": 2,",
" \"pattern\": \"[a-zA-Z]{2}\"",
" },",
" \"isPublic\": {",
" \"type\": \"boolean\",",
" \"description\": \"Является ли данный профиль публичным. \\n\\nПубличные профили доступны другим пользователям: если профиль публичный, любой пользователь платформы сможет получить информацию о пользователе.\\n\"",
" },",
" \"phone\": {",
" \"type\": \"string\",",
" \"description\": \"Номер телефона пользователя в формате +123456789\",",
" \"pattern\": \"\\\\+[\\\\d]+\"",
" },",
" \"image\": {",
" \"type\": \"string\",",
" \"description\": \"Ссылка на фото для аватара пользователя\",",
" \"maxLength\": 200",
" }",
" },",
" \"required\": [",
" \"login\",",
" \"email\",",
" \"countryCode\",",
" \"isPublic\"",
" ]",
" }",
" },",
" \"required\": [",
" \"profile\"",
" ],",
" \"$schema\": \"http://json-schema.org/draft-04/schema#\"",
"};",
"",
"pm.test(\"Register a user\", function () {",
" var url = pm.variables.get(\"base_url\") + \"/auth/register\";",
" const options = {",
" url: url,",
" method: 'POST',",
" header: {",
" 'Content-Type': 'application/json',",
" },",
" body: {",
" mode: 'raw',",
" raw: JSON.stringify({",
" 'login': 'yellowMonkey2',",
" 'email': 'yellowstone1980@you.ru',",
" 'password': '$aba4821FWfew01#.fewA$',",
" 'countryCode': 'RU',",
" 'isPublic': true,",
" 'phone': '+74951239922',",
" })",
" }",
" };",
"",
" const profile = {",
" 'profile': {",
" 'login': 'yellowMonkey2',",
" 'email': 'yellowstone1980@you.ru',",
" 'countryCode': 'RU',",
" 'isPublic': true,",
" 'phone': '+74951239922',",
" }",
" }",
"",
" pm.sendRequest(options, function (err, response) {",
" pm.test(\"Validate response\", () => {",
" var resp = response.json();",
" ",
" pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");",
" pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;",
"",
" console.log(\"got\", resp, \"expected\", profile);",
" pm.expect(resp).to.deep.eq(profile, `Got invalid object`);",
" });",
" });",
"});",
"",
"",
"",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/ping",
"host": [
"{{base_url}}"
],
"path": [
"ping"
]
}
},
"response": []
},
{ {
"name": "Sign in", "name": "Sign in",
"event": [ "event": [
@@ -425,6 +551,122 @@
" \"$schema\": \"http://json-schema.org/draft-04/schema#\"", " \"$schema\": \"http://json-schema.org/draft-04/schema#\"",
"};", "};",
"", "",
"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': 'yellowMonkey2',",
" 'password': '$aba4821FWfew01#.fewA$',",
" })",
" }",
" };",
"",
" 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;",
"",
" console.log('Token', resp.token);",
" });",
" });",
"});",
"",
"",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/ping",
"host": [
"{{base_url}}"
],
"path": [
"ping"
]
}
},
"response": []
}
]
},
{
"name": "05/me",
"item": [
{
"name": "Register a user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var schema = {",
" \"type\": \"object\",",
" \"properties\": {",
" \"profile\": {",
" \"type\": \"object\",",
" \"description\": \"Информация о профиле пользователя\",",
" \"properties\": {",
" \"login\": {",
" \"type\": \"string\",",
" \"description\": \"Логин пользователя\",",
" \"maxLength\": 30,",
" \"pattern\": \"[a-zA-Z0-9-]+\"",
" },",
" \"email\": {",
" \"type\": \"string\",",
" \"description\": \"E-mail пользователя\",",
" \"maxLength\": 50",
" },",
" \"countryCode\": {",
" \"type\": \"string\",",
" \"description\": \"Двухбуквенный код, уникально идентифицирующий страну\",",
" \"maxLength\": 2,",
" \"pattern\": \"[a-zA-Z]{2}\"",
" },",
" \"isPublic\": {",
" \"type\": \"boolean\",",
" \"description\": \"Является ли данный профиль публичным. \\n\\nПубличные профили доступны другим пользователям: если профиль публичный, любой пользователь платформы сможет получить информацию о пользователе.\\n\"",
" },",
" \"phone\": {",
" \"type\": \"string\",",
" \"description\": \"Номер телефона пользователя в формате +123456789\",",
" \"pattern\": \"\\\\+[\\\\d]+\"",
" },",
" \"image\": {",
" \"type\": \"string\",",
" \"description\": \"Ссылка на фото для аватара пользователя\",",
" \"maxLength\": 200",
" }",
" },",
" \"required\": [",
" \"login\",",
" \"email\",",
" \"countryCode\",",
" \"isPublic\"",
" ]",
" }",
" },",
" \"required\": [",
" \"profile\"",
" ],",
" \"$schema\": \"http://json-schema.org/draft-04/schema#\"",
"};",
"",
"pm.test(\"Register a user\", function () {", "pm.test(\"Register a user\", function () {",
" var url = pm.variables.get(\"base_url\") + \"/auth/register\";", " var url = pm.variables.get(\"base_url\") + \"/auth/register\";",
" const options = {", " const options = {",
@@ -436,50 +678,32 @@
" body: {", " body: {",
" mode: 'raw',", " mode: 'raw',",
" raw: JSON.stringify({", " raw: JSON.stringify({",
" 'login': 'yellowMonkey11000',", " 'login': 'yellowMonkey10000',",
" 'email': 'yellowstone1983@you.ru',", " 'email': 'yellowstone1980@you.ru',",
" 'password': '$aba4821FWfew01#.fewA$',", " 'password': '$aba4821FWfew01#.fewA$',",
" 'countryCode': 'RU',", " 'countryCode': 'RU',",
" 'isPublic': true,", " 'isPublic': true,",
" 'phone': '+74951239912',", " 'phone': '+74951239922',",
" })", " })",
" }", " }",
" };", " };",
"", "",
" const profile = {",
" 'profile': {",
" 'login': 'yellowMonkey10000',",
" 'email': 'yellowstone1980@you.ru',",
" 'countryCode': 'RU',",
" 'isPublic': true,",
" 'phone': '+74951239922',",
" }",
" }",
"",
" pm.sendRequest(options, function (err, response) {", " pm.sendRequest(options, function (err, response) {",
" pm.test(\"Validate register response\", () => {", " pm.test(\"Validate response\", () => {",
" var resp = response.json();", " var resp = response.json();",
" ",
" pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");", " 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.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;",
"",
" console.log('Token', resp.token);",
" });",
" });",
" });",
" });", " });",
"});", "});",
"", "",
@@ -492,28 +716,22 @@
} }
], ],
"request": { "request": {
"method": "POST", "method": "GET",
"header": [], "header": [],
"url": { "url": {
"raw": "{{base_url}}/auth/sign-in", "raw": "{{base_url}}/ping",
"host": [ "host": [
"{{base_url}}" "{{base_url}}"
], ],
"path": [ "path": [
"auth", "ping"
"sign-in"
] ]
} }
}, },
"response": [] "response": []
} },
]
},
{
"name": "05/me",
"item": [
{ {
"name": "Get my profile", "name": "Sign in",
"event": [ "event": [
{ {
"listen": "test", "listen": "test",
@@ -534,9 +752,9 @@
" \"$schema\": \"http://json-schema.org/draft-04/schema#\"", " \"$schema\": \"http://json-schema.org/draft-04/schema#\"",
"};", "};",
"", "",
"pm.test(\"Register a user\", function () {", "pm.test(\"Sign in\", function () {",
" var url = pm.variables.get(\"base_url\") + \"/auth/register\";", " var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";",
" const options = {", " var options = {",
" url: url,", " url: url,",
" method: 'POST',", " method: 'POST',",
" header: {", " header: {",
@@ -545,84 +763,26 @@
" body: {", " body: {",
" mode: 'raw',", " mode: 'raw',",
" raw: JSON.stringify({", " raw: JSON.stringify({",
" 'login': 'yellowMonkey11000',", " 'login': 'yellowMonkey10000',",
" 'email': 'yellowstone1983@you.ru',",
" 'password': '$aba4821FWfew01#.fewA$',", " 'password': '$aba4821FWfew01#.fewA$',",
" 'countryCode': 'RU',",
" 'isPublic': true,",
" 'phone': '+74951239912',",
" })", " })",
" }", " }",
" };", " };",
"", " ",
" pm.sendRequest(options, function (err, response) {", " pm.sendRequest(options, function (err, response) {",
" pm.test(\"Validate register response\", () => {", " pm.test(\"Validate sign-in response\", () => {",
" var resp = response.json();", " var resp = response.json();",
" pm.expect(response.code).to.be.oneOf([201, 409], \"Invalid response code status\");",
" });",
"", "",
" pm.test(\"Sign in\", function () {", " pm.expect(response.code).to.be.eq(200, \"Invalid response code status\");",
" var url = pm.variables.get(\"base_url\") + \"/auth/sign-in\";", " pm.expect(tv4.validate(resp, schema), \"Invalid JSON schema\").to.be.true;",
" 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.environment.set(\"05_profile_token\", resp.token);",
" pm.test(\"Validate sign-in response\", () => {", " console.log(\"Token has been saved\")",
" 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.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.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\");",
"",
" console.log(\"Got profile\", resp);",
" pm.expect(resp.login).to.be.eq(\"yellowMonkey11000\", \"Invalid login\");",
" });",
" });",
" });",
" });",
" });",
" });", " });",
" });", " });",
"});", "});",
"", "",
"", "",
"",
"",
"",
"",
"",
"" ""
], ],
"type": "text/javascript" "type": "text/javascript"
@@ -633,13 +793,64 @@
"method": "GET", "method": "GET",
"header": [], "header": [],
"url": { "url": {
"raw": "{{base_url}}/me/profile", "raw": "{{base_url}}/ping",
"host": [ "host": [
"{{base_url}}" "{{base_url}}"
], ],
"path": [ "path": [
"me", "ping"
"profile" ]
}
},
"response": []
},
{
"name": "Get my profile",
"event": [
{
"listen": "test",
"script": {
"exec": [
"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.expect(response.code).to.be.eq(200, \"Invalid response code status\");",
"",
" console.log(\"Got profile\", resp);",
" pm.expect(resp.login).to.be.eq(\"yellowMonkey10000\", \"Invalid login\");",
" });",
" });",
"});",
"",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/ping",
"host": [
"{{base_url}}"
],
"path": [
"ping"
] ]
} }
}, },