Full test coverage for the baseline API.
This commit is contained in:
parent
c114da203e
commit
af109358b5
9 changed files with 269 additions and 32 deletions
|
|
@ -2,7 +2,6 @@ package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.predicate.v0.api.Predicate.Result.forall
|
|
||||||
import gs.uuid.v0.UUID
|
import gs.uuid.v0.UUID
|
||||||
|
|
||||||
/** Implements logical AND.
|
/** Implements logical AND.
|
||||||
|
|
@ -18,7 +17,7 @@ final class And[F[_]: Applicative, -A](
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def eval(input: A): F[Predicate.Result] =
|
override def eval(input: A): F[Predicate.Result] =
|
||||||
ps.map(_.eval(input)).sequence.map(_.forall())
|
ps.map(_.eval(input)).sequence.map(_.allMatch())
|
||||||
|
|
||||||
object And:
|
object And:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.predicate.v0.api.Predicate.Result.forany
|
|
||||||
import gs.uuid.v0.UUID
|
import gs.uuid.v0.UUID
|
||||||
|
|
||||||
/** Implements logical OR.
|
/** Implements logical OR.
|
||||||
|
|
@ -18,7 +17,7 @@ final class Or[F[_]: Applicative, -A](
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def eval(input: A): F[Predicate.Result] =
|
override def eval(input: A): F[Predicate.Result] =
|
||||||
ps.map(_.eval(input)).sequence.map(_.forany())
|
ps.map(_.eval(input)).sequence.map(_.anyMatch())
|
||||||
|
|
||||||
object Or:
|
object Or:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
|
import cats.Applicative
|
||||||
import gs.uuid.v0.UUID
|
import gs.uuid.v0.UUID
|
||||||
|
|
||||||
/** A _Predicate_ is some function that accepts any input and emits some
|
/** A _Predicate_ is some function that accepts any input and emits some
|
||||||
|
|
@ -48,6 +49,12 @@ trait Predicate[F[_], -A]:
|
||||||
|
|
||||||
object Predicate:
|
object Predicate:
|
||||||
|
|
||||||
|
given CanEqual[Predicate[?, ?], Predicate[?, ?]] = CanEqual.derived
|
||||||
|
|
||||||
|
def alwaysTrue[F[_]: Applicative]: Predicate[F, Any] = True[F]
|
||||||
|
|
||||||
|
def alwaysFalse[F[_]: Applicative]: Predicate[F, Any] = False[F]
|
||||||
|
|
||||||
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
/** The result of evaluating a [[Predicate]] is a Boolean value where:
|
||||||
*
|
*
|
||||||
* - `true`: The predicate matched the given input.
|
* - `true`: The predicate matched the given input.
|
||||||
|
|
@ -80,14 +87,15 @@ object Predicate:
|
||||||
|
|
||||||
extension (results: List[Result])
|
extension (results: List[Result])
|
||||||
/** @return
|
/** @return
|
||||||
* True if all results are true.
|
* True if all results are true. False if no results are given.
|
||||||
*/
|
*/
|
||||||
def forall(): Result = results.forall(x => x)
|
def allMatch(): Result = results.nonEmpty && results.forall(x => x)
|
||||||
|
|
||||||
/** @return
|
/** @return
|
||||||
* True if any results match.
|
* True if any results match. False if no results are given.
|
||||||
*/
|
*/
|
||||||
def forany(): Result = results.find(x => x).isDefined
|
def anyMatch(): Result =
|
||||||
|
results.nonEmpty && results.find(x => x).isDefined
|
||||||
|
|
||||||
extension (result: Result)
|
extension (result: Result)
|
||||||
/** @return
|
/** @return
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
|
import gs.uuid.v0.UUID
|
||||||
import support.IOSuite
|
import support.IOSuite
|
||||||
|
|
||||||
class AndTests extends IOSuite:
|
class AndTests extends IOSuite:
|
||||||
|
|
||||||
iotest("should return true if all are true") {
|
iotest("should return true if all are true") {
|
||||||
val and = And(True[IO], True[IO], True[IO])
|
val id = UUID.v7()
|
||||||
|
val and = And(True[IO], True[IO], True[IO])
|
||||||
|
val and2 = And(id, True[IO], True[IO], True[IO])
|
||||||
|
|
||||||
for result <- and.eval(())
|
for
|
||||||
yield assertEquals(result.unwrap(), true)
|
result <- and.eval(())
|
||||||
|
result2 <- and2.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), true)
|
||||||
|
assertEquals(result2.unwrap(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false if any are false") {
|
iotest("should return false if any are false") {
|
||||||
val and = And(True[IO], False[IO], True[IO])
|
val id = UUID.v7()
|
||||||
|
val and = And(True[IO], False[IO], True[IO])
|
||||||
for result <- and.eval(())
|
val and2 = And(id, True[IO], False[IO], True[IO])
|
||||||
yield assertEquals(result.unwrap(), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should return false for an empty list") {
|
|
||||||
val and = And.empty[IO]
|
|
||||||
val and2 = And[IO, Any]()
|
|
||||||
|
|
||||||
for
|
for
|
||||||
result <- and.eval(())
|
result <- and.eval(())
|
||||||
|
|
@ -30,3 +31,33 @@ class AndTests extends IOSuite:
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iotest("should return false for an empty list") {
|
||||||
|
val and = And.empty[IO]
|
||||||
|
val and2 = And[IO, Any]()
|
||||||
|
val and3 = And[IO, Any](UUID.v7())
|
||||||
|
|
||||||
|
for
|
||||||
|
result <- and.eval(())
|
||||||
|
result2 <- and2.eval(())
|
||||||
|
result3 <- and3.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), false)
|
||||||
|
assertEquals(result2.unwrap(), false)
|
||||||
|
assertEquals(result3.unwrap(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
iotest("should return the underlying predicate for a singular entry") {
|
||||||
|
val p = True[IO]
|
||||||
|
val and = And(p)
|
||||||
|
val and2 = And(UUID.v7(), p)
|
||||||
|
|
||||||
|
for
|
||||||
|
result <- and.eval(())
|
||||||
|
result2 <- and2.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), true)
|
||||||
|
assertEquals(result2.unwrap(), true)
|
||||||
|
assertEquals(p, and)
|
||||||
|
assertEquals(p, and2)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import gs.uuid.v0.UUID
|
||||||
|
import support.IOSuite
|
||||||
|
|
||||||
|
class FalseTests extends IOSuite:
|
||||||
|
|
||||||
|
iotest("should return false") {
|
||||||
|
val predicate = False[IO]
|
||||||
|
|
||||||
|
for result <- predicate.eval(())
|
||||||
|
yield assertEquals(result.unwrap(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
iotest("should provide a unique identifier") {
|
||||||
|
val id = UUID.v7()
|
||||||
|
val predicate = False[IO](id)
|
||||||
|
|
||||||
|
for result <- predicate.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), false)
|
||||||
|
assertEquals(predicate.id, id)
|
||||||
|
assertEquals(predicate.hashCode(), id.hashCode())
|
||||||
|
assertEquals(predicate.toString(), s"predicate:${id.withoutDashes()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support equality") {
|
||||||
|
val predicate = False[IO]
|
||||||
|
val p2 = False[IO](predicate.id)
|
||||||
|
val p3 = False[IO]
|
||||||
|
assertEquals(predicate, predicate)
|
||||||
|
assertEquals(predicate, p2)
|
||||||
|
assertNotEquals(predicate, p3)
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,28 @@
|
||||||
package gs.predicate.v0.api
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
|
import gs.uuid.v0.UUID
|
||||||
import support.IOSuite
|
import support.IOSuite
|
||||||
|
|
||||||
class OrTests extends IOSuite:
|
class OrTests extends IOSuite:
|
||||||
|
|
||||||
iotest("should return true if any are true") {
|
iotest("should return true if any are true") {
|
||||||
val or = Or(False[IO], True[IO], False[IO])
|
val id = UUID.v7()
|
||||||
|
val or = Or(False[IO], True[IO], False[IO])
|
||||||
|
val or2 = Or(id, False[IO], True[IO], False[IO])
|
||||||
|
|
||||||
for result <- or.eval(())
|
for
|
||||||
yield assertEquals(result.unwrap(), true)
|
result <- or.eval(())
|
||||||
|
result2 <- or2.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), true)
|
||||||
|
assertEquals(result2.unwrap(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
iotest("should return false if all are false") {
|
iotest("should return false if all are false") {
|
||||||
val or = Or(False[IO], False[IO], False[IO])
|
val id = UUID.v7()
|
||||||
|
val or = Or(False[IO], False[IO], False[IO])
|
||||||
for result <- or.eval(())
|
val or2 = Or(id, False[IO], False[IO], False[IO])
|
||||||
yield assertEquals(result.unwrap(), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
iotest("should return false for an empty list") {
|
|
||||||
val or = Or.empty[IO]
|
|
||||||
val or2 = Or[IO, Any]()
|
|
||||||
|
|
||||||
for
|
for
|
||||||
result <- or.eval(())
|
result <- or.eval(())
|
||||||
|
|
@ -30,3 +31,33 @@ class OrTests extends IOSuite:
|
||||||
assertEquals(result.unwrap(), false)
|
assertEquals(result.unwrap(), false)
|
||||||
assertEquals(result2.unwrap(), false)
|
assertEquals(result2.unwrap(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iotest("should return false for an empty list") {
|
||||||
|
val or = Or.empty[IO]
|
||||||
|
val or2 = Or[IO, Any]()
|
||||||
|
val or3 = Or[IO, Any](UUID.v7())
|
||||||
|
|
||||||
|
for
|
||||||
|
result <- or.eval(())
|
||||||
|
result2 <- or2.eval(())
|
||||||
|
result3 <- or3.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), false)
|
||||||
|
assertEquals(result2.unwrap(), false)
|
||||||
|
assertEquals(result3.unwrap(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
iotest("should return the underlying predicate for a singular entry") {
|
||||||
|
val p = True[IO]
|
||||||
|
val or = Or(p)
|
||||||
|
val or2 = Or(UUID.v7(), p)
|
||||||
|
|
||||||
|
for
|
||||||
|
result <- or.eval(())
|
||||||
|
result2 <- or2.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), true)
|
||||||
|
assertEquals(result2.unwrap(), true)
|
||||||
|
assertEquals(p, or)
|
||||||
|
assertEquals(p, or2)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
|
import Predicate.Result
|
||||||
|
import gs.predicate.v0.api.Predicate.Result.allMatch
|
||||||
|
import munit.FunSuite
|
||||||
|
|
||||||
|
class PredicateResultTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should represent matched and missed") {
|
||||||
|
val r1 = Result.matched()
|
||||||
|
val r2 = Result.missed()
|
||||||
|
assertEquals(r1.unwrap(), true)
|
||||||
|
assertEquals(r1.isMatch, true)
|
||||||
|
assertEquals(r1.isMiss, false)
|
||||||
|
assertEquals(r2.unwrap(), false)
|
||||||
|
assertEquals(r2.isMatch, false)
|
||||||
|
assertEquals(r2.isMiss, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should instantiate given some Boolean value") {
|
||||||
|
val r1 = Result(true)
|
||||||
|
val r2 = Result(false)
|
||||||
|
assertEquals(r1.unwrap(), true)
|
||||||
|
assertEquals(r1.isMatch, true)
|
||||||
|
assertEquals(r1.isMiss, false)
|
||||||
|
assertEquals(r2.unwrap(), false)
|
||||||
|
assertEquals(r2.isMatch, false)
|
||||||
|
assertEquals(r2.isMiss, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support logical AND") {
|
||||||
|
val r1 = Result.matched()
|
||||||
|
val r2 = Result.missed()
|
||||||
|
val r3 = Result.matched()
|
||||||
|
val r4 = Result.matched()
|
||||||
|
val r5 = Result.missed()
|
||||||
|
assertEquals(r1.and(r3).and(r4), Result.matched())
|
||||||
|
assertEquals(r1.and(r2).and(r4), Result.missed())
|
||||||
|
assertEquals(r5.and(r1).and(r4), Result.missed())
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support logical OR") {
|
||||||
|
val r1 = Result.matched()
|
||||||
|
val r2 = Result.missed()
|
||||||
|
val r3 = Result.matched()
|
||||||
|
val r4 = Result.matched()
|
||||||
|
val r5 = Result.missed()
|
||||||
|
assertEquals(r1.or(r3).or(r4), Result.matched())
|
||||||
|
assertEquals(r1.or(r2).or(r4), Result.matched())
|
||||||
|
assertEquals(r5.or(r1).or(r4), Result.matched())
|
||||||
|
assertEquals(r2.or(r5), Result.missed())
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support all (true if all results are true)") {
|
||||||
|
val r1 = Result.matched()
|
||||||
|
val r2 = Result.missed()
|
||||||
|
val r3 = Result.matched()
|
||||||
|
val r4 = Result.matched()
|
||||||
|
val r5 = Result.missed()
|
||||||
|
val l1 = List(r1, r3, r4)
|
||||||
|
val l2 = List(r1, r2, r3, r4, r5)
|
||||||
|
val l3 = List()
|
||||||
|
assertEquals(l1.allMatch(), Result.matched())
|
||||||
|
assertEquals(l2.allMatch(), Result.missed())
|
||||||
|
assertEquals(l3.allMatch(), Result.missed())
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support any (true if any results are true)") {
|
||||||
|
val r1 = Result.matched()
|
||||||
|
val r2 = Result.missed()
|
||||||
|
val r3 = Result.matched()
|
||||||
|
val r4 = Result.matched()
|
||||||
|
val r5 = Result.missed()
|
||||||
|
val l1 = List(r1, r3, r4)
|
||||||
|
val l2 = List(r1, r2, r3, r4, r5)
|
||||||
|
val l3 = List(r2, r5)
|
||||||
|
val l4 = List.empty[Result]
|
||||||
|
assertEquals(l1.anyMatch(), Result.matched())
|
||||||
|
assertEquals(l2.anyMatch(), Result.matched())
|
||||||
|
assertEquals(l3.anyMatch(), Result.missed())
|
||||||
|
assertEquals(l4.anyMatch(), Result.missed())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
|
import munit.FunSuite
|
||||||
|
|
||||||
|
class PredicateTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should not equal non-predicate objects") {
|
||||||
|
val x = "other"
|
||||||
|
val p = Predicate.alwaysTrue[cats.Id]
|
||||||
|
assertEquals(p.equals(x), false)
|
||||||
|
assertNotEquals(p, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should provide a default toString implementation") {
|
||||||
|
val p = Predicate.alwaysFalse[cats.Id]
|
||||||
|
assertEquals(p.toString(), s"predicate:${p.id.withoutDashes()}")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package gs.predicate.v0.api
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import gs.uuid.v0.UUID
|
||||||
|
import support.IOSuite
|
||||||
|
|
||||||
|
class TrueTests extends IOSuite:
|
||||||
|
|
||||||
|
iotest("should return true") {
|
||||||
|
val predicate = True[IO]
|
||||||
|
|
||||||
|
for result <- predicate.eval(())
|
||||||
|
yield assertEquals(result.unwrap(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
iotest("should provide a unique identifier") {
|
||||||
|
val id = UUID.v7()
|
||||||
|
val predicate = True[IO](id)
|
||||||
|
|
||||||
|
for result <- predicate.eval(())
|
||||||
|
yield
|
||||||
|
assertEquals(result.unwrap(), true)
|
||||||
|
assertEquals(predicate.id, id)
|
||||||
|
assertEquals(predicate.hashCode(), id.hashCode())
|
||||||
|
assertEquals(predicate.toString(), s"predicate:${id.withoutDashes()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support equality") {
|
||||||
|
val predicate = True[IO]
|
||||||
|
val p2 = True[IO](predicate.id)
|
||||||
|
val p3 = True[IO]
|
||||||
|
assertEquals(predicate, predicate)
|
||||||
|
assertEquals(predicate, p2)
|
||||||
|
assertNotEquals(predicate, p3)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue