Skip to content

Commit

Permalink
111
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksei Shashev committed Aug 8, 2023
1 parent 8087f54 commit 780edee
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 93 deletions.
2 changes: 1 addition & 1 deletion backend/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ val examples = (project in file("examples"))
),
)
.settings(
Compile / doc / sources := (file("examples/src") ** "*.scala").get,
Compile / doc / sources := (file("examples/src/main") ** "*.scala").get,
Compile / doc / scalacOptions ++= Seq("-groups", "-skip-packages", "sttp")
)
.settings(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import ru.tinkoff.tcb.mockingbird.edsl.model.*
* Метод `example` позволяет добавить пример к набору. Вначале указывается название примера, как первый набор
* аргументов. При генерации тестов это будет именем теста, а при генерации Markdown будет добавлено как заголовок
* второго уровня, затем описывается сам пример. Последовательность действий описывается при помощи монады
* [[ru.tinkoff.tcb.mockingbird.edsl.Example Example]].
* [[ru.tinkoff.tcb.mockingbird.edsl.model.Example Example]].
*
* `ExampleSet` предоставляет следующие действия:
* - [[describe]] - добавить текстовое описание.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru.tinkoff.tcb.mockingbird.dal2

import eu.timepit.refined.auto.*
import eu.timepit.refined.types.string.NonEmptyString
import org.scalatest.EitherValues.*
import org.scalatest.FutureOutcome
import org.scalatest.funsuite.AsyncFunSuiteLike

/**
* Данный трейт добавляет возожность пропустить исполнение тестов по их имени. Актуально для случая когда тесты описаны
* в трейте и в зависимости от имплементации некоторые тесты можно пропустить. Пример использования:
* {{{
* import eu.timepit.refined.auto.*
* import eu.timepit.refined.types.string.NonEmptyString
*
* class ConcreateBehaviorsImplSuite
* extends AsyncFunSuite
* with BehavioursSuite
* with AsyncCancelableTests {
*
* override val caceledTests: Map[TestName, CacnelTestReason] = Map(
* NonEmptyString("Test #3") -> "It is canceled, because the concrete implementation doesn't support this case."
* )
*
* }
* }}}
*/
trait AsyncCancelableTests { self: AsyncFunSuiteLike =>
type TestName = NonEmptyString
type CancelTestReason = NonEmptyString

/**
* canceledTests возвращает словарь содержащий имена тестов, которые необходимо отменить и причину отмены. Это может
* быть связано с тем, что конкретная реализация не поддерживает требуемую функциональность или это работает иначе, но
* без ущерба для конечного пользователя mockingbird.
*/
def canceledTests: Map[TestName, CancelTestReason] = Map.empty

override def withFixture(test: NoArgAsyncTest): FutureOutcome =
canceledTests.get(NonEmptyString.from(test.name).value).map(FutureOutcome.canceled(_)).getOrElse(test())
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import scala.util.Random
import scala.util.matching.Regex

import cats.Monad
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto.*
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.numeric.Positive
Expand All @@ -22,7 +21,6 @@ import org.scalacheck.Arbitrary
import org.scalacheck.Gen
import org.scalacheck.rng.Seed
import org.scalatest.EitherValues.*
import org.scalatest.FutureOutcome
import org.scalatest.OptionValues.*
import org.scalatest.funsuite.AsyncFunSuiteLike
import org.scalatest.matchers.should.Matchers
Expand All @@ -40,7 +38,11 @@ import ru.tinkoff.tcb.utils.id.SID
"scalafix:DisableSyntax.mapAs"
)
)
trait HttpStubDAOSpecBehaviors[F[_]] extends AsyncFunSuiteLike with Matchers with ScalaCheckDrivenPropertyChecks {
trait HttpStubDAOSpecBehaviors[F[_]]
extends AsyncFunSuiteLike
with Matchers
with ScalaCheckDrivenPropertyChecks
with AsyncCancelableTests {

// В некоторых случаях, можно встретить, что заглушки сравниваются как Json,
// после вызова asJson. Это связано с тем, что поле pathPattern имеет тип Option[Regex],
Expand All @@ -53,19 +55,6 @@ trait HttpStubDAOSpecBehaviors[F[_]] extends AsyncFunSuiteLike with Matchers wit
implicit def M: Monad[F]
implicit def fToFuture[T](fwh: F[T]): Future[T]

type TestName = String Refined NonEmpty
type CancelTestReason = String Refined NonEmpty

/**
* canceledTests возвращает словарь содержащий имена тестов, которые необходимо отменить и причину отмены. Это может
* быть связано с тем, что конкретная реализация не поддерживает требуемую функциональность или это работает иначе, но
* без ущерба для конечного пользователя mockingbird.
*/
def canceledTests: Map[TestName, CancelTestReason] = Map.empty

override def withFixture(test: NoArgAsyncTest): FutureOutcome =
canceledTests.get(refineV[NonEmpty](test.name).value).map(FutureOutcome.canceled(_)).getOrElse(test())

def dao: HttpStubDAO[F]

import HttpStubDAOSpecBehaviors.*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.tinkoff.tcb.mockingbird.dal2

import scala.concurrent.Future
import org.scalatest.funsuite.AsyncFunSuiteLike
import org.scalatest.matchers.should.Matchers

trait PersistentStateDAOBehaviors[F[_]] extends AsyncFunSuiteLike with Matchers with AsyncCancelableTests {

implicit def M: Monad[F]
implicit def fToFuture[T](fwh: F[T]): Future[T]

def dao: PersistentStateDAO[F]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import com.dimafeng.testcontainers.ContainerDef
import com.dimafeng.testcontainers.GenericContainer
import com.dimafeng.testcontainers.scalatest.TestContainerForAll
import eu.timepit.refined.auto.*
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.refineMV
import eu.timepit.refined.types.string.NonEmptyString
import org.mongodb.scala.MongoClient
import org.mongodb.scala.MongoCollection
import org.mongodb.scala.MongoDatabase
Expand Down Expand Up @@ -68,7 +67,7 @@ class HttpStubDAOSpec
super.afterEach()

override val canceledTests: Map[TestName, CancelTestReason] = Map(
refineMV[NonEmpty]("Получение списка всех заглушек (fetch): фильтр query по pathPattern") ->
NonEmptyString("Получение списка всех заглушек (fetch): фильтр query по pathPattern") ->
"""MongoDB не позволяет искать по вхождению подстроки в регулярное выражение, нет
возможности преобразовать регулярное выражение в обычную строку над которой
возможна операция regexMatch."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import ru.tinkoff.tcb.mockingbird.dal.ScenarioDAO
import ru.tinkoff.tcb.mockingbird.dal.ServiceDAO
import ru.tinkoff.tcb.mockingbird.dal.SourceConfigurationDAO
import ru.tinkoff.tcb.mockingbird.dal2.HttpStubDAO
import ru.tinkoff.tcb.mockingbird.dal2.StubExactlyPath
import ru.tinkoff.tcb.mockingbird.dal2.StubFetchParams
import ru.tinkoff.tcb.mockingbird.dal2.StubFindParams
import ru.tinkoff.tcb.mockingbird.dal2.StubPathPattern
import ru.tinkoff.tcb.mockingbird.dal2.model.StubExactlyPath
import ru.tinkoff.tcb.mockingbird.dal2.model.StubFetchParams
import ru.tinkoff.tcb.mockingbird.dal2.model.StubFindParams
import ru.tinkoff.tcb.mockingbird.dal2.model.StubPathPattern
import ru.tinkoff.tcb.mockingbird.error.*
import ru.tinkoff.tcb.mockingbird.error.DuplicationError
import ru.tinkoff.tcb.mockingbird.error.ValidationError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import zio.interop.catz.core.*
import ru.tinkoff.tcb.logging.MDCLogging
import ru.tinkoff.tcb.mockingbird.dal.PersistentStateDAO
import ru.tinkoff.tcb.mockingbird.dal2.HttpStubDAO
import ru.tinkoff.tcb.mockingbird.dal2.StubMatchParams
import ru.tinkoff.tcb.mockingbird.dal2.model.StubMatchParams
import ru.tinkoff.tcb.mockingbird.error.*
import ru.tinkoff.tcb.mockingbird.misc.Renderable.ops.*
import ru.tinkoff.tcb.mockingbird.model.HttpMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,18 @@ package ru.tinkoff.tcb.mockingbird.dal2

import java.time.Instant
import scala.annotation.implicitNotFound
import scala.util.matching.Regex

import cats.tagless.autoFunctorK
import eu.timepit.refined.api.Refined
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.numeric.NonNegative
import eu.timepit.refined.numeric.Positive
import simulacrum.typeclass

import ru.tinkoff.tcb.dataaccess.UpdateResult
import ru.tinkoff.tcb.mockingbird.api.request.StubPatch
import ru.tinkoff.tcb.mockingbird.model.HttpMethod
import ru.tinkoff.tcb.mockingbird.dal2.model.StubFetchParams
import ru.tinkoff.tcb.mockingbird.dal2.model.StubFindParams
import ru.tinkoff.tcb.mockingbird.dal2.model.StubMatchParams
import ru.tinkoff.tcb.mockingbird.model.HttpStub
import ru.tinkoff.tcb.mockingbird.model.Scope
import ru.tinkoff.tcb.utils.id.SID

/**
* Потенциально заглушка может сопоставляться с мокируемым путям или как точное соответствие или регулярное выражения,
* которому удовлетворяет путь на который пришел запрос.
*/
sealed trait StubPath extends Serializable with Product
final case class StubExactlyPath(value: String Refined NonEmpty) extends StubPath
final case class StubPathPattern(value: Regex) extends StubPath

/**
* Параметры для поиска заглушек.
*
* @param scope
* @param pathPattern
* представляет собой или строку, которая соответствует точному пути, или регулярное выражение. Заглушка может
* содержать или одно, или другое.
* @param method
*/
final case class StubFindParams(scope: Scope, path: StubPath, method: HttpMethod)

/**
* Параметры для подбора заглушек подходящих под указанный путь.
*
* @param scope
* @param path
* Путь который был передан в mockingbird при вызове заглушки. Подходящая у подходящей заглушки поле path будет в
* точности равно этому пути или переданный путь будет соотвествовать регулярному выражению хранимому в поле
* pathPattern.
* @param method
*/
final case class StubMatchParams(scope: Scope, path: String, method: HttpMethod)

/**
* Параметры для отбора заглушек для отображения их списка в UI.
*
* @param page
* номер страницы для которой формируется список заглушек
* @param query
* строка запроса, рассматривается как точный ID заглушки или используется как регулярное выражения и сопоставляется с
* полями name, path, pathPattern
* @param service
* имя сервиса к которому относится заглушка (поле serviceSuffix)
* @param labels
* список лейблов, которыми должна быть отмечена заглушка, все перечисленные лейблы должны содержаться в поле labels
* заглушки, хранящейся в хранилище
* @param count
* количество заглушек, отображаемых на странице
*/
final case class StubFetchParams(
page: Int Refined NonNegative,
query: Option[String],
service: Option[String],
labels: Seq[String],
count: Int Refined Positive
)

@implicitNotFound("Could not find an instance of HttpStubDAO for ${F}")
@typeclass @autoFunctorK
trait HttpStubDAO[F[_]] extends Serializable {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package ru.tinkoff.tcb.mockingbird.dal2

import scala.annotation.implicitNotFound

import cats.tagless.autoFunctorK
import eu.timepit.refined.types.string.NonEmptyString
import io.circe.Json
import simulacrum.typeclass

import ru.tinkoff.tcb.dataaccess.UpdateResult
import ru.tinkoff.tcb.mockingbird.model.PersistentState
import ru.tinkoff.tcb.predicatedsl.Keyword
import ru.tinkoff.tcb.utils.circe.optics.JsonOptic
import ru.tinkoff.tcb.utils.id.SID

object PersistentStateDAO {
type Predicate = Map[JsonOptic, Map[Keyword.Json, Json]]

/* ======================================================================== */
/* THE FOLLOWING CODE IS MANAGED BY SIMULACRUM; PLEASE DO NOT EDIT!!!! */
/* ======================================================================== */

/**
* Summon an instance of [[PersistentStateDAO]] for `F`.
*/
@inline def apply[F[_]](implicit instance: PersistentStateDAO[F]): PersistentStateDAO[F] = instance

object ops {
implicit def toAllPersistentStateDAOOps[F[_], A](target: F[A])(implicit tc: PersistentStateDAO[F]): AllOps[F, A] {
type TypeClassType = PersistentStateDAO[F]
} = new AllOps[F, A] {
type TypeClassType = PersistentStateDAO[F]
val self: F[A] = target
val typeClassInstance: TypeClassType = tc
}
}
trait Ops[F[_], A] extends Serializable {
type TypeClassType <: PersistentStateDAO[F]
def self: F[A]
val typeClassInstance: TypeClassType
}
trait AllOps[F[_], A] extends Ops[F, A]
trait ToPersistentStateDAOOps extends Serializable {
implicit def toPersistentStateDAOOps[F[_], A](target: F[A])(implicit tc: PersistentStateDAO[F]): Ops[F, A] {
type TypeClassType = PersistentStateDAO[F]
} = new Ops[F, A] {
type TypeClassType = PersistentStateDAO[F]
val self: F[A] = target
val typeClassInstance: TypeClassType = tc
}
}
object nonInheritedOps extends ToPersistentStateDAOOps

/* ======================================================================== */
/* END OF SIMULACRUM-MANAGED CODE */
/* ======================================================================== */

}

@implicitNotFound("Could not find an instance of PersistentStateDAO for ${F}")
@typeclass @autoFunctorK
trait PersistentStateDAO[F[_]] extends Serializable {
import PersistentStateDAO.Predicate

/**
* Поиск состояния по предикату. (В dal.PersistentStateDAO называется findBySpec)
*
* @param p
* Предикат
* @return
* Список найденных состояний удовлетворяющих заданному предикату.
*/
def find(p: Predicate): F[Vector[PersistentState]]

/**
* Создать или перезаписать хранимое состояние в БД. (В dal.PersistentStateDAO называется upsertBySpec)
*
* @param id
* Идентификатор хранимого состояния
* @param s
* Само состояние
* @return
*/
def upsert(id: SID[PersistentState], s: Json): F[UpdateResult]

/**
* Создать индекс для поиска хранимых состояний по указанному полю.
*
* @param field
* Поле по которому будет строится индекс. В общем случае, это может быть путь в JSON, а не просто поле первого
* уровня.
* @return
*/
def createIndexForDataField(field: NonEmptyString): F[Unit]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ru.tinkoff.tcb.mockingbird.dal2.model

import eu.timepit.refined.types.numeric.NonNegInt
import eu.timepit.refined.types.numeric.PosInt

/**
* Параметры для отбора заглушек для отображения их списка в UI.
*
* @param page
* номер страницы для которой формируется список заглушек
* @param query
* строка запроса, рассматривается как точный ID заглушки или используется как регулярное выражения и сопоставляется с
* полями name, path, pathPattern
* @param service
* имя сервиса к которому относится заглушка (поле serviceSuffix)
* @param labels
* список лейблов, которыми должна быть отмечена заглушка, все перечисленные лейблы должны содержаться в поле labels
* заглушки, хранящейся в хранилище
* @param count
* количество заглушек, отображаемых на странице
*/
final case class StubFetchParams(
page: NonNegInt,
query: Option[String],
service: Option[String],
labels: Seq[String],
count: PosInt
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.tinkoff.tcb.mockingbird.dal2.model

import ru.tinkoff.tcb.mockingbird.model.HttpMethod
import ru.tinkoff.tcb.mockingbird.model.Scope

/**
* Параметры для поиска заглушек.
*
* @param scope
* @param pathPattern
* представляет собой или строку, которая соответствует точному пути, или регулярное выражение. Заглушка может
* содержать или одно, или другое.
* @param method
*/
final case class StubFindParams(scope: Scope, path: StubPath, method: HttpMethod)

0 comments on commit 780edee

Please sign in to comment.