Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

English translation #63

Merged
merged 12 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions backend/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ val mockingbird = (project in file("mockingbird"))
)

/*
Отдельный подпроект был создан ради того, чтобы не отключать coursier во всём проекте.
См. https://github.com/coursier/coursier/issues/2016
Так как netty-tranport-epoll больше не используется можно перенести код отсюда в mockingbird
A separate subproject was created to avoid disabling coursier throughout the entire project.
See https://github.com/coursier/coursier/issues/2016.
Since netty-transport-epoll is no longer used, the code from here can be moved to mockingbird.
*/
lazy val `mockingbird-api` = (project in file("mockingbird-api"))
.enablePlugins(BuildInfoPlugin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final case class JsonOptic private[optics] (private val jsonPath: Seq[PathPart])
def set(v: Json): Json => Json = jsonPath.foldRight[Json => Json](_ => v)((part, f) => modifyPart(v)(part)(f))

/**
* Если передан Some(..) - обновляет поддерево, если передан None - удаляет существующее
* Updates subtress if arg is Some(..), removes subtree otherwise
*/
def setOpt(vo: Option[Json]): Json => Json = vo match {
case Some(j) => set(j)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,20 @@ package object circe {
transformKeys(in => "_([a-z\\d])".r.replaceAllIn(in, _.group(1).toUpperCase)).result

/**
* Производит слияние двух json объектов
* Merges two Json objects
*
* json1 :+ json2
*
* В случае совпадения значений по определённому ключу приоритетными являются значения из json1
* In case of conflicts values from json1 take precedence
*/
@inline def :+(other: Json): Json = merge(other, json, false)

/**
* Производит слияние двух json объектов
* Merges two Json objects
*
* json1 +: json2
*
* В случае совпадения значений по определённому ключу приоритетными являются значения из json2
* In case of conflicts values from json2 take precedence
*/
@inline def +:(other: Json): Json = merge(other, json, false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ package object bson {
private type PartialEndo2[A, B] = PartialFunction[(A, B), (A, B)]

/*
Экстракторы
Extractors
*/

object BUndef {
Expand Down Expand Up @@ -108,7 +108,7 @@ package object bson {
}

/*
Расширения
Extensions
*/

implicit final class BsonDocumentObjExt(private val doc: BsonDocument.type) extends AnyVal {
Expand All @@ -123,20 +123,20 @@ package object bson {
@inline def decodeOpt[T: BsonDecoder]: Option[T] = decodeAs[T].toOption

/**
* Производит слияние двух bson значений
* Merges two bson values
*
* bson1 :+ bson2
*
* В случае совпадения значений по определённому ключу приоритетными являются значения из bson1
* In case of conflicts values from bson1 take precedence
*/
@inline def :+(other: BsonValue): BsonValue = merge(other, bv, false)

/**
* Производит слияние двух bson значений
* Merges two bson values
*
* bson1 :+ bson2
*
* В случае совпадения значений по определённому ключу приоритетными являются значения из bson2
* In case of conflicts values from bson2 take precedence
*/
@inline def +:(other: BsonValue): BsonValue = merge(other, bv, false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,187 +5,44 @@ import org.scalactic.source

import ru.tinkoff.tcb.mockingbird.edsl.model.*

/**
* ==Описание набора примеров==
*
* `ExampleSet` предоставляет DSL для описания примеров взаимодействия с Mockingbird со стороны внешнего
* приложения/пользователя через его API. Описанные примеры потом можно в Markdown описание последовательности действий
* с примерами HTTP запросов и ответов на них или сгенерировать тесты для scalatest. За это отвечают интерпретаторы DSL
* [[ru.tinkoff.tcb.mockingbird.edsl.interpreter.MarkdownGenerator MarkdownGenerator]] и
* [[ru.tinkoff.tcb.mockingbird.edsl.interpreter.AsyncScalaTestSuite AsyncScalaTestSuite]] соответственно.
*
* Описание набора примеров может выглядеть так:
*
* {{{
* package ru.tinkoff.tcb.mockingbird.examples
*
* import ru.tinkoff.tcb.mockingbird.edsl.ExampleSet
* import ru.tinkoff.tcb.mockingbird.edsl.model.*
* import ru.tinkoff.tcb.mockingbird.edsl.model.Check.*
* import ru.tinkoff.tcb.mockingbird.edsl.model.HttpMethod.*
* import ru.tinkoff.tcb.mockingbird.edsl.model.ValueMatcher.syntax.*
*
* class CatsFacts[HttpResponseR] extends ExampleSet[HttpResponseR] {
*
* override val name = "Примеры использования ExampleSet"
*
* example("Получение случайного факта о котиках")(
* for {
* _ <- describe("Отправить GET запрос")
* resp <- sendHttp(
* method = Get,
* path = "/fact",
* headers = Seq("X-CSRF-TOKEN" -> "unEENxJqSLS02rji2GjcKzNLc0C0ySlWih9hSxwn")
* )
* _ <- describe("Ответ содержит случайный факт полученный с сервера")
* _ <- checkHttp(
* resp,
* HttpResponseExpected(
* code = Some(CheckInteger(200)),
* body = Some(
* CheckJsonObject(
* "fact" -> CheckJsonString("There are approximately 100 breeds of cat.".sample),
* "length" -> CheckJsonNumber(42.sample)
* )
* ),
* headers = Seq("Content-Type" -> CheckString("application/json"))
* )
* )
* } yield ()
* )
* }
* }}}
*
* Дженерик параметр `HttpResponseR` нужен так результат выполнения HTTP запроса зависит от интерпретатора DSL.
*
* Переменная `name` - общий заголовок для примеров внутри набора, при генерации Markdown файла будет добавлен в самое
* начало как заголовок первого уровня.
*
* Метод `example` позволяет добавить пример к набору. Вначале указывается название примера, как первый набор
* аргументов. При генерации тестов это будет именем теста, а при генерации Markdown будет добавлено как заголовок
* второго уровня, затем описывается сам пример. Последовательность действий описывается при помощи монады
* [[ru.tinkoff.tcb.mockingbird.edsl.model.Example Example]].
*
* `ExampleSet` предоставляет следующие действия:
* - [[describe]] - добавить текстовое описание.
* - [[sendHttp]] - исполнить HTTP запрос с указанными параметрами, возвращает результат запроса.
* - [[checkHttp]] - проверить, что результат запроса отвечает указанным ожиданиям, возвращает извлеченные из ответа
* данные на основании проверок. ''Если предполагается использовать какие-то части ответа по ходу описания примера,
* то необходимо для них задать ожидания, иначе они будут отсутствовать в возвращаемом объекте.''
*
* Для описания ожиданий используются проверки [[model.Check$]]. Некоторые проверки принимают как параметр
* [[model.ValueMatcher ValueMatcher]]. Данный трейт тип представлен двумя реализациями
* [[model.ValueMatcher.AnyValue AnyValue]] и [[model.ValueMatcher.FixedValue FixedValue]]. Первая описывает
* произвольное значение определенного типа, т.е. проверки значения не производится. Вторая задает конкретное ожидаемое
* значение.
*
* Для упрощения создания значений типа [[model.ValueMatcher ValueMatcher]] добавлены имплиситы в объекте
* [[model.ValueMatcher.syntax ValueMatcher.syntax]]. Они добавляют неявную конвертацию значений в тип
* [[model.ValueMatcher.FixedValue FixedValue]], а так же методы `sample` и `fixed` для создания
* [[model.ValueMatcher.AnyValue AnyValue]] и [[model.ValueMatcher.FixedValue FixedValue]] соответственно. Благодаря
* этому можно писать:
* {{{
* CheckString("some sample".sample) // вместо CheckString(AnyValue("some sample"))
* CheckString("some fixed string") // вместо CheckString(FixedValue("some fixed string"))
* }}}
*
* ==Генерации markdown документа из набора примеров==
*
* {{{
* package ru.tinkoff.tcb.mockingbird.examples
*
* import sttp.client3.*
*
* import ru.tinkoff.tcb.mockingbird.edsl.interpreter.MarkdownGenerator
*
* object CatsFactsMd {
* def main(args: Array[String]): Unit = {
* val mdg = MarkdownGenerator(baseUri = uri"https://catfact.ninja")
* val set = new CatsFacts[MarkdownGenerator.HttpResponseR]()
* println(mdg.generate(set))
* }
* }
* }}}
*
* Здесь создается интерпретатор [[ru.tinkoff.tcb.mockingbird.edsl.interpreter.MarkdownGenerator MarkdownGenerator]] для
* генерации markdown документа из инстанса `ExampleSet`. Как параметр, конструктору передается хост со схемой который
* будет подставлен в качестве примера в документ.
*
* Как упоминалось ранее, тип ответа от HTTP сервера зависит от интерпретатора DSL, поэтому при создании `CatsFacts`
* параметром передается тип `MarkdownGenerator.HttpResponseR`.
*
* ==Генерация тестов из набора примеров==
* {{{
* package ru.tinkoff.tcb.mockingbird.examples
*
* import sttp.client3.*
*
* import ru.tinkoff.tcb.mockingbird.edsl.interpreter.AsyncScalaTestSuite
*
* class CatsFactsSuite extends AsyncScalaTestSuite {
* override val baseUri = uri"https://catfact.ninja"
* val set = new CatsFacts[HttpResponseR]()
* generateTests(set)
* }
* }}}
*
* Для генерации тестов нужно создать класс и унаследовать его от
* [[ru.tinkoff.tcb.mockingbird.edsl.interpreter.AsyncScalaTestSuite AsyncScalaTestSuite]]. После чего в переопределить
* значение `baseUri` и в конструкторе вызвать метод `generateTests` передав в него набор примеров. В качестве дженерик
* параметра для типа HTTP ответа, в создаваемый инстанс набора примеров надо передать тип
* [[ru.tinkoff.tcb.mockingbird.edsl.interpreter.AsyncScalaTestSuite.HttpResponseR AsyncScalaTestSuite.HttpResponseR]]
*
* Пример запуска тестов:
* {{{
* [info] CatsFactsSuite:
* [info] - Получение случайного факта о котиках
* [info] + Отправить GET запрос
* [info] + Ответ содержит случайный факт полученный с сервера
* [info] Run completed in 563 milliseconds.
* [info] Total number of tests run: 1
* [info] Suites: completed 1, aborted 0
* [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
* [info] All tests passed.
* }}}
*/
trait ExampleSet[HttpResponseR] {
private var examples_ : Vector[ExampleDescription] = Vector.empty

final private[edsl] def examples: Vector[ExampleDescription] = examples_

/**
* Заглавие набора примеров.
* Title of the example set.
*/
def name: String

final protected def example(name: String)(body: Example[Any])(implicit pos: source.Position): Unit =
examples_ = examples_ :+ ExampleDescription(name, body, pos)

/**
* Выводит сообщение при помощи `info` при генерации тестов или добавляет текстовый блок при генерации Markdown.
* Prints a message using info during test generation or adds a text block during Markdown generation.
* @param text
* текст сообщения
* The message text
*/
final def describe(text: String)(implicit pos: source.Position): Example[Unit] =
liftF[Step, Unit](Describe(text, pos))

/**
* В тестах, выполняет HTTP запрос с указанными параметрами или добавляет в Markdown пример запроса, который можно
* исполнить командой `curl`.
* In tests, makes an HTTP request with the specified parameters or adds a sample request to the Markdown, which can
* be executed with the curl command.
*
* @param method
* используемый HTTP метод.
* HTTP method used.
* @param path
* путь до ресурса без схемы и хоста.
* path to the resource without the scheme and host.
* @param body
* тело запроса как текст.
* request body as text..
* @param headers
* заголовки, который будут переданы вместе с запросом.
* request headers to send.
* @param query
* URL параметры запроса
* URL query parameters.
* @return
* возвращает объект представляющий собой результат исполнения запроса, конкретный тип зависит от интерпретатора
* DSL. Использовать возвращаемое значение можно только передав в метод [[checkHttp]].
* Returns an object representing the result of the request execution; the specific type depends on the DSL
* interpreter. The return value can only be used by passing it to the [[checkHttp]] method.
*/
final def sendHttp(
method: HttpMethod,
Expand All @@ -199,18 +56,18 @@ trait ExampleSet[HttpResponseR] {
liftF[Step, HttpResponseR](SendHttp[HttpResponseR](HttpRequest(method, path, body, headers, query), pos))

/**
* В тестах, проверяет, что полученный HTTP ответ соответствует ожиданиям. При генерации Markdown вставляет ожидаемый
* ответ опираясь на указанные ожидания. Если никакие ожидания не указана, то ничего добавлено не будет.
* In tests, verifies that the received HTTP response matches the expectations. When generating Markdown, inserts the
* expected response based on the specified expectations. If no expectations are specified, nothing will be added.
*
* @param response
* результат исполнения [[sendHttp]], тип зависит от интерпретатора DSL.
* the result of executing [[sendHttp]], the type depends on the DSL interpreter.
* @param expects
* ожидания предъявляемые к результату HTTP запроса. Ожидания касаются кода ответа, тела запроса и заголовков
* полеченных от сервера.
* expectations placed on the result of the HTTP request. Expectations concern the response code, request body, and
* headers received from the server.
* @return
* возвращает разобранный ответ от сервера. При генерации Markdown, так как реального ответа от сервера нет, то
* формирует ответ на основании переданных ожиданий от ответа. В Markdown добавляется информация только от том, для
* чего была указана проверка.
* returns the parsed response from the server. When generating Markdown, since there is no actual response from the
* server, it constructs a response based on the provided response expectations. Only information relevant to the
* specified checks is added to the Markdown.
*/
final def checkHttp(response: HttpResponseR, expects: HttpResponseExpected)(implicit
pos: source.Position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ import ru.tinkoff.tcb.mockingbird.edsl.model.Check.*
import ru.tinkoff.tcb.mockingbird.edsl.model.ValueMatcher.*

/**
* Базовый трейт для генерации набора тестов по набору примеров
* [[ru.tinkoff.tcb.mockingbird.edsl.ExampleSet ExampleSet]].
* Base trait for generating a set of tests from an [[ru.tinkoff.tcb.mockingbird.edsl.ExampleSet ExampleSet]].
*
* Трейт наследуется от `AsyncFunSuiteLike` из фреймоврка [[https://www.scalatest.org/ ScalaTest]], поэтому внутри можно
* как дописать дополнительные тесты, так и использовать
* [[https://www.scalatest.org/user_guide/sharing_fixtures#beforeAndAfter BeforeAndAfter]] и/или
* [[https://www.scalatest.org/user_guide/sharing_fixtures#composingFixtures BeforeAndAfterEach]] для управления
* поднятием необходимого для исполнения тестов окружения, в том числе используя
* This trait inherits from AsyncFunSuiteLike in the [[https://www.scalatest.org/ ScalaTest]] framework, so you can add
* additional tests inside it or use
* [[https://www.scalatest.org/user_guide/sharing_fixtures#beforeAndAfter BeforeAndAfter]] and/or
* [[https://www.scalatest.org/user_guide/sharing_fixtures#composingFixtures BeforeAndAfterEach]] to manage the setup of
* the necessary environment for executing tests, including using
* [[https://github.com/testcontainers/testcontainers-scala testcontainers-scala]].
*/
trait AsyncScalaTestSuite extends AsyncFunSuiteLike {
Expand All @@ -40,12 +39,12 @@ trait AsyncScalaTestSuite extends AsyncFunSuiteLike {
private[interpreter] def sttpbackend: SttpBackend[Future] = sttpbackend_

/**
* URI относительно которого будут разрешаться пути используемые в примерах
* URI relative to which paths used in examples will be resolved
*/
def baseUri: Uri

/**
* Сгенерировать тесты из набора примеров.
* Generate tests from the set of examples.
*/
protected def generateTests(es: ExampleSet[HttpResponseR]): Unit =
es.examples.foreach { desc =>
Expand Down
Loading