Skip to content

Commit

Permalink
fix(RE-5): use property-based testing
Browse files Browse the repository at this point in the history
 * ensure we don't tamper with `hashCode()`
  • Loading branch information
polentino committed Mar 23, 2024
1 parent 8ba337f commit a0e393c
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 73 deletions.
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ lazy val redactedTests = (project in file("tests"))
.settings(scalafixSettings)
.settings(
publish / skip := true,
libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "3.2.17" % Test),
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.scalatestplus" %% "scalacheck-1-17" % "3.2.17.0" % Test
),
scalacOptions ++= {
val jar = (redactedCompilerPlugin / assembly).value
val addPlugin = "-Xplugin:" + jar.getAbsolutePath
Expand Down
155 changes: 83 additions & 72 deletions tests/src/test/scala/io/github/polentino/redacted/RedactedSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,98 +2,109 @@ package io.github.polentino.redacted

import org.scalatest.Checkpoints.*
import org.scalatest.flatspec.AnyFlatSpec

import io.github.polentino.redacted.RedactionWithNestedCaseClass.Inner
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks

import java.util.UUID

class RedactedSpec extends AnyFlatSpec {
class RedactedSpec extends AnyFlatSpec with ScalaCheckPropertyChecks {

behavior of "@redacted"

it should "work with a redacted case class of just one member" in {
case class OneMember(@redacted name: String)
val name = "berfu"
val expected = "OneMember(***)"

val testing = OneMember(name)
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp { assert(testing.name == name) }
cp.reportAll()

forAll { (name: String) =>
val expected = "OneMember(***)"
val testing = OneMember(name)
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp { assert(testing.name == name) }
cp.reportAll()
}
}

it should "work with a redacted case class with many members" in {
case class ManyMembers(field1: String, @redacted field2: String, @redacted field3: String, field4: String)
val field1 = "field-1"
val field2 = "field-2"
val field3 = "field-3"
val field4 = "field-4"
val expected = s"ManyMembers($field1,***,***,$field4)"

val testing = ManyMembers(field1, field2, field3, field4)
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp {
assert(testing.field1 == field1 &&
testing.field2 == field2 &&
testing.field3 == field3 &&
testing.field4 == field4)

forAll { (field1: String, field2: String, field3: String, field4: String) =>
val expected = s"ManyMembers($field1,***,***,$field4)"
val testing = ManyMembers(field1, field2, field3, field4)
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp {
assert(testing.field1 == field1 &&
testing.field2 == field2 &&
testing.field3 == field3 &&
testing.field4 == field4)
}
cp.reportAll()
}
cp.reportAll()
}

it should "work with nested case classes" in {
val id = "id-1"
val name1 = "Diego"
val age1 = 999
val name2 = "Berfu"
val age2 = 888
val expected = s"RedactionWithNestedCaseClass($id,***,Inner(***,$age2))"

val testing = RedactionWithNestedCaseClass(id, Inner(name1, age1), Inner(name2, age2))
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp {
assert(testing.id == id &&
testing.inner1.name == name1 &&
testing.inner1.age == age1 &&
testing.inner2.name == name2 &&
testing.inner2.age == age2)
it should "work with case class nested in companion object" in {
forAll { (id: String, name1: String, age1: Int, name2: String, age2: Int) =>
val expected = s"RedactionWithNestedCaseClass($id,***,Inner(***,$age2))"
val testing = RedactionWithNestedCaseClass(id, Inner(name1, age1), Inner(name2, age2))
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp {
assert(testing.id == id &&
testing.inner1.name == name1 &&
testing.inner1.age == age1 &&
testing.inner2.name == name2 &&
testing.inner2.age == age2)
}
cp.reportAll()
}
cp.reportAll()
}

it should "work with nested case classes in case class" in {
case class Inner(userId: String, @redacted balance: Int)
case class Outer(inner: Inner)
val userId = "user-123"
val balance = 123_456_789
val expected = s"Outer(Inner($userId,***))"

val testing = Outer(Inner(userId, balance))
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp {
assert(
testing.inner.userId == userId &&
testing.inner.balance == balance
)

forAll { (userId: String, balance: Int) =>
val expected = s"Outer(Inner($userId,***))"
val testing = Outer(Inner(userId, balance))
val implicitToString = s"$testing"
val explicitToString = testing.toString

val cp = new Checkpoint
cp { assert(implicitToString == expected) }
cp { assert(explicitToString == expected) }
cp {
assert(
testing.inner.userId == userId &&
testing.inner.balance == balance
)
}
cp.reportAll()
}
}

it must "not change the behavior of `hashCode`" in {
final case class TestClass(uuid: UUID, name: String, age: Int)
object RedactedTestClass {
final case class TestClass(uuid: UUID, @redacted name: String, @redacted age: Int)
}

forAll { (uuid: UUID, name: String, age: Int) =>
val testClass = TestClass(uuid, name, age)
val redactedTestClass = RedactedTestClass.TestClass(uuid, name, age)

assert(testClass.hashCode() == redactedTestClass.hashCode())
}
cp.reportAll()
}
}

0 comments on commit a0e393c

Please sign in to comment.