Как проверить эндпоинты в коде
Перейти к содержимому

Как проверить эндпоинты в коде

  • автор:

API Exploration

If the root structure of an API is know, let’s imaginatively say http://stackoverflow.com/api/service/ and we can successfully retrieve results from a know endpoint, let’s say http://stackoverflow.com/api/service/answers/?id=39234 , are there any methods, programmatic or otherwise, to identify other available endpoints?

Example

As an example, that’s likely very source specific, googling the root url has revealed methods used in tags in pages from the source. I’m interested in similar broadly applicable techniques that may work.

Тестирование API

Тестирование API изображение с сайта www.andreyolegovich.ru

В широком смысле слова API (Application Programming Interface) это метод который приложение предоставляет внешним пользователям для коммуникации с ним. Обычно через Интернет.

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

API применяются там где невозможна или нежелательна непосредственная интеграция с исходным приложением, то есть практически везде.

Крупные интернет-компании обычно предоставляют (платно или бесплатно) доступ к API своих сервисов.

  • Список API от Google
  • Работа с API GitHub

Одним из самых распространённых способов тестирования API является написание скриптов на Python .

Где применяют API

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

Конкретные примеры работы с API я разбираю в учебнике

Пример №1:

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

Это возможно потому, что программисты Яндекса разработали специальный набор запросов — API которые можно присылать к ним на сервер чтобы получить в ответ карту.

Пример №2:

Предположим, что Вы создали сайт vk2.com. Вы хотите, чтобы вебмастера могли добавить на свои сайты возможность комментировать записи используя учётную запись vk2, но раскрывать или раздавать свой код не хотите.

Чтобы обойти эту проблему Вы выкладываете в публичном доступе правила, по которым вебмастера могут обращаться к vk2, чтобы получить комментарии.

Формат этих сообщений это обычно либо JSON либо XML. О них мы поговорим позже.

Повторим для закрепления сути: Смысл в том, что сайт написанный на любом языке, поддерживающем HTTP запросы, не посылает на сервер никаких PHP/C/Python команд, а общается ним с помощью запросов, описанных в API.

Если вам интересен реальный пример работы с API рекомендую статью Работа с API GitHub

Endpoint

Адрес, на который посылаются сообщения называется Endpoint.

Обычно это URL (например, название сайта) и порт. Если я хочу создать веб сервис на порту 8080 Endpoint будет выглядеть так:

Слово адрес нужно понимать в общем смысле — как при отправке бумажного письма нужно написать на конверте физический адрес, так и при обращению к сервису у которого больше одного интерфейса нужно указать нужный.

Если моему Web сервису нужно будет отвечать на различные сообщения я создам сразу несколько URL (interfaces) по которым к сервису можно будет обратиться. Например

https://andreyolegovich.ru:8080 /resource1/status
https://andreyolegovich.ru:8080 /resource1/getserviceInfo
https://andreyolegovich.ru:8080 /resource1/putID
http://andreyolegovich.ru:8080 /resource1/eventslist
https://andreyolegovich.ru:8080 /resource2/putID

Как видите у моих эндпойнтов (Enpoints) различные окончания. Такое окончание в Endpoint называются Resource, а начало Base URL.

Такое определение Endpoint и Resource используется, например, в SOAP UI для RESTful интерфейсов

https://andreyolegovich.ru:8080 — это Base URL

/resource1/status — это Resource

Endpoint = Base URL + Resource

Понятие Endpoint может использоваться в более широком смысле. Можно сказать, что какой-то определённый роутер или компьютер является Endpoint. Например, в понятии Endpoint Management под Endpoint имеют в виду конечное устройство. Обычно это понятно из контекста.

Также следует обратить внимание на то, что понятие Endpoint выходит за рамки RESTful и может использовать как в SOAP так и в других протоколах.

Термин Resource также связан с RESTful, но в более широком смысле может означать что-то другое.

На программистском сленге Endpoint иногда называют ручкой.

Сделать какой-то запрос, например HTTP, на сленге будет — дёрнуть ручку

Спецификация

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

  1. Какие методы можно использовать, посылая запросы на каждый Endpoint
  2. Должны ли передаваться какие-то данные
  3. Если нужно передавать данные в теле запроса, то какие
  4. Какие ответы мы ожидаем в случае успешного запроса
  5. Какие ответы мы ожидаем когда с запросом или его обработкой на сервере что-то не так

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

Изображение баннера

HTTP методы

Вернёмся к первому пункту списка, а именно к тому, что такое методы.

В протоколе HTTP предусмотрено несколько способов отправить запрос на один и тот же Endpoint.

  • CONNECT
  • DELETE
  • GET
  • HEAD
  • OPTIONS
  • PATCH
  • POST
  • PUT
  • TRACE

Про их свойства можно почитать здесь.

Когда мы знаем какие методы с какими Enpoint можно использовать составить запросы не составит труда.

GET http://andreyolegovich.ru:8080 /resource1/status
GET http://andreyolegovich.ru:8080 /resource1/getserviceInfo
PUT http://andreyolegovich.ru:8080 /resource1/putID
GET http://andreyolegovich.ru:8080 /resource1/eventslist
POST http://andreyolegovich.ru:8080 /resource1/eventslist
PUT http://andreyolegovich.ru:8080 /resource2/putID

Таким образом простейший запрос состоит из метода и Enpoint

Request = Method + Endpoint

Пример API

Я заказал бесплатный месяц хостинга у Beget и создал сайт topbicycle.ru , который сообщает всем желающим количество велосипедистов в любом городе.

Чтобы узнать количество велосипедистов в городе нужно отправить GET запрос на https://topbicycle.ru:/bicyclists/$город

GET https://topbicycle.ru/bicyclists/helsinki

Получив такой запрос сайт вернёт число велосипедистов в Хельсинки.

Попробуйте вставить эту строку в браузер.

Доступные города: benalmadena , cordoba , fuengirola , helsinki , malaga ,moscow, spb.

Если хотите немного потренироваться — оцените мои практические уроки по тестированию API — «Уроки тестирование API»

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

Тестирование API без документации

Если Вам по какой-то причине предстоит проделать эту неблагодарную работу, определетесь, насколько всё плохо. Какая у Вас есть информация об объекте тестирования.

Известно ли какие порты для Вас открыты? Знаете ли Вы нужные endpoints?

Сканирование портов

Если дело совсем труба — просканируйте порты, например, с помощью netcat. Открытые порты сохраните в файл openports.txt

nc -z -v answerit.ru 1-10000 2>&1 | grep succeeded > openports.txt

Эта операция займёт довольно много времени. Можете почитать советы по работе с Nmap и Netcat , например, следующие:

  • Сканирование портов с помощью Netcat
  • Как записать вывод Netcat в файл

Перебор запросов

Если Вам известен нужный порт и соответствующий endopoint переберите все возможные HTTP методы. Начните с наиболее очевидных:

  • POST
  • PUT
  • GET

Для ускорения процесса напишите скрипт на Bash или Python .

В худшем случае, когда ни порт ни endpoints неизвестны Вам, скорее всего придётся перебирать все открытые порты и генерировать endpoints, которые подходят по смыслу.

Разработчики обычно не особо заморачиваются и закладывают минимально-необходиму информацию. Так что включите воображение и попробуйте придумать endpoints опираясь на бизнес логику и принятые в Вашей компании стандарты.

Если ни endpoints ни бизнес логика Вам неизвестны, то у меня есть подозрение, что Вы тестируете API с не самыми хорошими намерениями.

Инструменты для тестирования

Существует множество инструментов для тестирования. Здесь Вы можете познакомиться с одними из самых популярных: Python и SOAP UI.

О работе с REST API на Python вы можете прочитать в статье «REST API с Python»

А про то как сделать своё REST API — в статье «Flask»

Web проекты часто тестируются с применением Selenium Webdriver если Вам интересно — посмотрите статью Python + Selenium

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Шаг 2 «Конечные точки и методы (Описание API)»

Конечные точки указывают, как получить доступ к ресурсу, а метод указывает разрешенные взаимодействия (такие как GET, POST или DELETE) с ресурсом.

Один и тот же ресурс обычно имеет множество связанных конечных точек, каждая из которых имеет разные пути и методы, но возвращает различную информацию об одном и том же ресурсе. Конечные точки обычно имеют краткие описания, похожие на общее описание ресурса, только еще короче. Кроме того, конечная точка показывает только конечный путь URL ресурса, а не базовый, общий для всех конечных точек, путь.

Примеры конечных точек

Вот пример конечной точки ресурса User API Instagram

instagram

Конечная точка обычно выделяется стилизованным образом для придания ей более визуального внимания. Большая часть документации строится вокруг конечной точки, поэтому, может, имеет смысл визуально выделить конечную точку в нашей документации.

Конечная точка, возможно, является наиболее важным аспектом документации API, потому что она является тем, что разработчики будут реализовывать для выполнения своих запросов.

Представление параметра path при помощи фигурных скобок

Параметры path в конечных точках, представляют в фигурных скобках. Например, вот пример из API Mailchimp:

По возможности, параметр path выделяют другим цветом:

Фигурные скобки для параметров path являются условием, понятным пользователям. В приведенном выше примере почти ни одна конечная точка не использует фигурные скобки в синтаксисе фактического пути, поэтому является очевидным плэйсхолдером.

Вот пример из API Facebook, где параметр path выделен цветом для его легкой идентификации:

facebook

Для выделения параметров, при их описании в документации Facebook, используется зеленый цвет, который помогает пользователям понять их значение.

Параметры path не всегда выделяются уникальным цветом (например, некоторым может предшествовать двоеточие), но, как бы то ни было, нужно убедиться, что параметр path легко идентифицируется.

Перечисляем методы конечной точки

Для конечной точки обычно перечисляют методы (GET, POST и т. Д.). Метод определяет работу с ресурсом. Вкратце, каждый метод выглядит следующим образом:

  • GET: получает ресурс;
  • POST: создает ресурс;
  • PUT: обновляет или создает в существующем ресурсе;
  • PATCH: частично изменяет существующий ресурс;
  • DELETE: Удаляет ресурс.

См. Request methods в статье Wikipedia по HTTP для получения дополнительной информации. (Существуют дополнительные методы, но они редко используются.)

Поскольку о самом методе особо говорить нечего, имеет смысл сгруппировать метод с конечной точкой. Вот пример из Box API:

boxAPI

А вот пример API LinkedIn:

linkedIn

Конечная точка показывает только конечный путь

Когда мы описываем конечную точку, мы указываем только конечный путь (отсюда и термин «конечная точка»). Полный путь, который содержит как базовый путь, так и конечную точку, часто называют URL-адресом ресурса.

Как сгруппировать несколько конечных точек одного ресурса

Еще стоит обращать внимание при документировании конечных точек и методов, как группировать и перечислять конечные точки, особенно если у нас много конечных точек для одного и того же ресурса. В Примерах описания ресурсов мы рассмотрели различные API. Многие сайты документации используют различные схемы группировки или перечисления каждой конечной точки ресурса, поэтому не будем возвращаться к тем же примерам. Группируйте конечные точки таким образом, осмысленно, например, по методу или по типу возвращаемой информации.

Предположим, у нас есть три конечных точки GET и одна конечная точка POST, причем все они относятся к одному и тому же ресурсу. На некоторых сайтах документации все конечные точки для одного и того же ресурса могут перечисляться на одной странице. На других сайтах может встречаться разбивка методов на отдельных страницах. На третьих могут быть созданы одна группа для конечных точек GET, а другая — для конечных точек POST. Это зависит от того, что и сколько должно быть сказано о каждой конечной точке.

Если конечные точки в основном совпадают, объединение их на одной странице может иметь смысл. Но если они в значительной степени уникальны (с разными ответами, параметрами и сообщениями об ошибках), разделение их на разные страницы, вероятно, лучше (и проще в управлении). Опять же, создав более сложный дизайн сайта, мы можем сделать большую информацию доступной для навигации на той же странице.

Как ссылаться к конечным точкам в инструкциях

Как ссылаться к конечным точкам в разделе API в руководствах и другом безадресном контенте? Ссылка на конечную точку /aqi или на конечную точку /weatherdata не имеет большого значения. Но для более сложных API-интерфейсов использование конечной точки для описания ресурса может оказаться непростым делом.

В одной компании URL-адреса конечных точек ресурса Rewards выглядели так:

А Rewards в контексте Missions выглядели вот так:

Сказать, что можно использовать ресурс Rewards, не всегда было достаточно конкретно, потому что было несколько Rewards и конечных точек Missions.

В этом случае, возможно, неудобно обращаться к конечной точке. Например, может быть такое предложение: «При вызове /users//rewards/ , вы получаете список всех наград. Чтобы получить конкретное вознаграждение за определенную миссию для конкретного пользователя, конечная точка /users//rewards/ принимает несколько параметров… »

Чем длиннее конечная точка, тем более громоздкой становится ссылка. Эти виды описаний будут чаще встречаться в концептуальных разделах вашей документации. Как правило, нет четкого правила, как ссылаться на громоздкие конечные точки. Смысловой подход нашего API определим самостоятельно.

Конечная точка API surfReport

Давайте создадим описание «Конечные точки и методы» для нашего вымышленного API Surfrefport. Вот пример:

Следующие шаги

Теперь, когда мы описали ресурс и перечислили конечные точки и методы, пришло время заняться одной из самых важных частей API: раздел “Параметры”.

A step-by-step intro to end-point testing

A step-by-step intro to end-point testing

I’ve been playing around with testing lately. One thing I tried to do was to test the endpoints of my Express application.

Setting up the test was the hard part. People who write about tests don’t actually teach you how they set it up. I could not find any useful information about this, and I had to try and figure it out.

So today, I want to share the setup I created for myself. Hopefully, this can help you when you create your own tests.

Table of Contents

Setting up Jest and Supertest

First, let’s talk about the stack.

The Stack

  • I created my app with Express.
  • I used Mongoose to connect to MongoDB
  • I used Jest as my test framework.

You might have expected Express and Mongoose because everyone else seems to use those two frameworks. I used them too.

But why Jest and not other test frameworks?

Why Jest

I don’t like Facebook, so I didn’t want to try anything that was created by Facebook’s team. I know it sounds silly, but that was the truth.

Before Jest, I tried out all sorts of test frameworks. I tried Tap, Tape, Mocha, Jasmine, and AVA. Each test framework has its own pros and cons. I almost ended up with AVA, but I didn’t go with AVA because I found it hard to set up. Eventually, I tried Jest out because Kent C. Dodds recommended it.

I fell in love with Jest after trying it out. I love it because:

  1. It’s easy to setup
  2. The watch-mode is amazing
  3. When you console.log something, it actually shows up without any difficulty (this was a bitch with AVA).

Setting up Jest

First, you need to install Jest.

Next, you want to add tests scripts to your package.json file. It helps to add the test and test:watch scripts (for one-off testing and watch-mode respectively).

You can choose to write your test files in one of the following formats. Jest picks them up for you automatically.

  1. js files in the __tests__ folder
  2. files named with test.js (like user.test.js )
  3. files named with spec.js (like user.spec.js )

You can place your files however you like. When I tested endpoints, I put the test files together with my endpoints. I found this easier to manage.

Writing your first test

Jest includes describe , it and expect for you in every test file. You don’t have to require them.

  • describe lets you wrap many tests together under one umbrella. (It is used for organizing your tests).
  • it lets you run a test.
  • expect lets you perform assertions. The test passes if all assertions passes.

Here’s an example of a test that fails. In this example, I expect that 1 should be strictly equal to 2 . Since 1 !== 2 , the test fails.

You’ll see a failing message from Jest if you run Jest.

Output from Terminal. Test fails.

You can make the test pass by expecting 1 === 1 .

Output from Terminal. Test successful.

This is the most basic of tests. It’s not useful at all because we haven’t testing anything real yet.

Asynchronous tests

You need to send a request to test an endpoint. Requests are asynchronous, which means you must be able to conduct asynchronous tests.

This is easy with Jest. There are two steps:

  1. Add the async keyword
  2. Call done when you’re done with your tests

Here’s what it can look like:

Note: Here’s an article on Async/await in JavaScript if you don’t know how to use it.

Testing Endpoints

You can use Supertest to test endpoints. First, you need to install Supertest.

Before you can test endpoints, you need to setup the server so Supertest can use it in your tests.

Most tutorials teach you to listen to the Express app in the server file, like this:

This doesn’t work because it starts listening to one port. If you try to write many test files, you’ll get an error that says «port in use».

You want to allow each test file to start a server on their own. To do this, you need to export app without listening to it.

For development or production purposes, you can listen to your app like normal in a different file like start.js .

Using Supertest

To use Supertest, you require your app and supertest in the test file.

Once you do this, you get the ability to send GET, POST, PUT, PATCH and DELETE requests. Before we send a request, we need to have an endpoint. Let’s say we have a /test endpoint.

To send a GET request to /test , you use the .get method from Supertest.

Supertest gives you a response from the endpoint. You can test both HTTP status and the body (whatever you send through res.json ) like this:

First endpoint test passes.

Connecting Jest and Mongoose

The hard part about testing a backend application is setting up a test database. It can be complicated.

Today, I want to share how I setup Jest and Mongoose.

Setting up Mongoose with Jest

Jest gives you a warning if you try to use Mongoose with Jest.

Warning if you try to use Mongoose with Jest

If you don’t want to see this error, you need to set testEnvironment to node in your package.json file.

Setting up Mongoose in a test file

You want to connect to a database before you begin any tests. You can use the beforeAll hook to do so.

To connect to a MongoDB, you can use Mongoose’s connect command.

This creates a connection to the database named test . You can name your database anything. You’ll learn how to clean them up later.

Note: Make sure you have an active local MongoDB Connection before you test. Your tests will fail if you don’t have an active local MongoDB Connection. Read this to learn how to create a local MongoDB connection.

Creating databases for each test file

When you test, you want to connect to a different database for each test file, because:

  1. Jest runs each test file asynchronously. You won’t know which file comes first.
  2. You don’t want tests to share the same database. You don’t want data from one test file to spill over to the next test file.

To connect to a different database, you change the name of the database.

Sending a POST request

Let’s say you want to create a user for your app. The user has a name and an email address. Your Mongoose Schema might look like this:

To create a user, you need to save the name and email into MongoDB. Your route and controller might look like this:

To save the user into the database, you can send a POST request to signup . To send a post request, you use the post method. To send data along with the POST request, you use the send method. In your tests, it’ll look like this.

Note: If you run this code two times, you’ll get an E1100 duplicate key error . This error occurred because:

  1. We said the email should be unique in the Schema above.
  2. We tried to create another user with testing@gmail.com . even though one already exists in the database. (The first one was created when you sent the first request).

Cleaning up the database between tests

You want to remove entries from the database between each test. This ensures you always start with an empty database.

You can do this with the afterEach hook.

In this code above, we only cleared the User collection in the database. In a real scenario, you want to clear all collections. You can use the following code to do so:

Testing the Endpoint

Let’s begin our tests. In this test, we will send a POST request to the /signup endpoint. We want to make sure:

  1. The user gets saved to the database
  2. The returned object contains information about the user

Checking if the user was saved to the database

To check whether the user gets saved into the database, you search the database for the user.

If you console.log user, you should see something like this:

User object from MongoDB.

This means our user got saved to the database. If we want to confirm the user has a name and an email, we can do expect them to be true.

Checking if the returned object contains the information about the user

We want to make sure the returned object contains the user’s name and email address. To do this, we check the response from the post request.

We’re done with our tests now. We want to delete the database from MongoDB.

Deleting the database

To delete the database, you need to ensure there are 0 collections in the database. We can do this by dropping each collection we used.

We’ll do after all our tests have run, in the afterAll hook.

To drop all your collections you can use this:

Finally, you want to close the Mongoose connection to end the test. Here’s how you can do it:

That’s everything you need to do to setup Mongoose with Jest!

Refactoring

There’s a lot of code that goes into beforeEach , afterEach , and afterAll hooks. We will be using them for every test file. It makes sense to create a setup file for these hooks.

You can import the setup file for each test like this:

There’s one more thing I want to show you.

When you create tests, you want to seed the database with fake data.

Seeding a database

When you write tests for the backend, you need to test for four different kinds of operations:

  1. Create (for adding things to the database)
  2. Read (for getting things from the database)
  3. Update (for changing the database)
  4. Delete (for deleting things from the database)

The easiest type to test for is create operations. You put something into the database and test whether it’s there.

For the other three types of operations, you need to put something into the database before you write the test.

Putting things into the database

The process where you add things to a database is called seeding a database.

Let’s say you want to add three users to the database. These users contain a name and an email address.

You can use your models to seed the database at the start of the test.

If you need these users for every test, the best way is to add them through the beforeEach hook. The beforeEach hook runs before every it declaration.

You can also use Mongoose’s create function to do the same thing. It runs new Model() and save() , so the code below and the one above does the same thing.

create vs insertMany

Mongoose has a second method to help you seed the database. This method is called insertMany . insertMany is faster than create , because:

  • insertMany sends one operation to the server
  • create sends one operation for each document

However, insertMany does not run the save middleware.

Is triggering the save middleware important?

This depends on your seed data. If your seed data needs to go through the save middleware, you need to use create . For example, let’s say you want to save a user’s password into the database. You have this data:

When we save a user’s password into the database, we want to hash the password for security reasons. We usually hash the password through the save middleware.

If you use create , you’ll get users with hashed passwords:

Create runs the save middleware.

If you use insertMany , you’ll get users without hashed passwords:

InsertMany does not run the save middleware.

When to use create, when to use insertMany

Since insertMany is faster than create , you want to use insertMany whenever you can.

Here’s how I do it:

  1. If seed data does not require the save middleware, use insertMany .
  2. If seed data requires save middleware, use create . Then, overwrite seed data so it no longer requires the save middleware.

For the password example above, I would run create first. Then, I copy-paste the hashed password seed data. Then, I’ll run insertMany from this point onwards.

If you want to overwrite complicated seed data, you might want to get JSON straight from MongoDB. To do this, you can use mongoexport :

  1. Export <collection> from <databaseName>
  2. Creates output as a JSON Array, prettified, in a file called output.json . This file will be placed in the folder where you run the command.

Seeding multiple test files and collections

You want a place to store your seed data so you can use them across all your tests and collections. Here’s a system I use:

  1. I name my seed files according to their models. I seed a User model with the user.seed.js file.
  2. I put my seed files in the seeds folder
  3. I loop through each seed file to seed the database.

To loop through each seed file, you need to use the fs module. fs stands for filesystem.

The easiest way to loop through the files is to create an index.js file in the same seeds folder. Once you have the index.js file, you can use the following code to look for all files with *.seed.js

Once you have a list of seed files, you can loop through each seed file to seed the database. Here, I use a for. of loop to keep things simple.

To seed the database, we need to find the correct Mongoose model from the name of the seed file. A file called user.seed.js should seed the User model. This means:

  1. We must find user from user.seed.js
  2. We must capitalize user into User

Here’s a crude version that does what’s required. (If you want to, you can make the code more robust with regex instead of split ).

Next, we want to make sure each file has a Model that corresponds to it. If the model cannot be found, we want to throw an error.

If there’s a corresponding model, we want to seed the database with the contents in the seed file. To do this, we need to read the seed file first. Here, since I used the .js extension, I can simply require the file.

For this to work, my seed files must export an array of data.

Once I have the contents of the seed file, I can run create or insertMany .

Here’s the whole seedDatabase code:

Why JS, not JSON?

It’s the industry norm to use JSON to store data. In this case, I find it easier to use JavaScript objects because:

  1. I don’t have to write opening and closing double-quotes for each property.
  2. I don’t have to use double-quotes at all! (It’s easier to write single-quotes because there’s no need to press the shift key).

If you want to use JSON, make sure you change seedDatabase to work with JSON. (I’ll let you work through the code yourself).

Adjusting the setupDB function

Earlier, I created a setupDB function to help set up databases for my tests. seedDatabase goes into the setupDB function since seeding is part of the setting up process.

A Github Repository

I created a Github repository to go with this article. I hope this demo code helps you start testing your applications.

Thanks for reading. This article was originally posted on my blog. Sign up for my newsletter if you want more articles to help you become a better frontend developer.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *