~jan0sch/smederee
Showing details for patch 41fc72b7bdf917cf1f31cf3cacbeb03afed88830.
diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieAccountManagementRepositoryTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieAccountManagementRepositoryTest.scala --- old-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieAccountManagementRepositoryTest.scala 2025-01-11 02:47:17.870494389 +0000 +++ new-smederee/modules/hub/src/test/scala/de/smederee/hub/DoobieAccountManagementRepositoryTest.scala 2025-01-11 02:47:17.870494389 +0000 @@ -14,11 +14,17 @@ import cats.syntax.all.* import de.smederee.TestTags.* import de.smederee.hub.Generators.* +import de.smederee.hub.Generators.given +import de.smederee.i18n.LanguageCode import de.smederee.security.* import de.smederee.ssh.* import doobie.* +import org.scalacheck.effect.PropF + final class DoobieAccountManagementRepositoryTest extends BaseSpec { + override def scalaCheckTestParameters = super.scalaCheckTestParameters.withMinSuccessfulTests(1) + val sshKeyWithComment = ResourceSuiteLocalFixture( "ssh-key-with-comment", Resource.make(IO { @@ -30,7 +36,7 @@ .getLines() .mkString val keyString = SshPublicKeyString(input) - PublicSshKey.from(UUID.randomUUID())(UserId.randomUserId)(OffsetDateTime.now(ZoneOffset.UTC))(keyString) + PublicSshKey.from(UUID.randomUUID())(UserId.randomUserId)(OffsetDateTime.now(ZoneOffset.UTC))(keyString).get })(_ => IO.unit) ) @@ -45,242 +51,255 @@ .getLines() .mkString val keyString = SshPublicKeyString(input) - PublicSshKey.from(UUID.randomUUID())(UserId.randomUserId)(OffsetDateTime.now(ZoneOffset.UTC))(keyString) + PublicSshKey.from(UUID.randomUUID())(UserId.randomUserId)(OffsetDateTime.now(ZoneOffset.UTC))(keyString).get })(_ => IO.unit) ) override def munitFixtures = List(sshKeyWithComment, sshKeyWithoutComment) test("addSshKey must save the key to the database".tag(NeedsDatabase)) { - (genValidAccount.sample, sshKeyWithComment()) match { - case (Some(account), Some(sshKey)) => - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val attempts = scala.util.Random.nextInt(128) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, None, Option(attempts)) - w <- repo.addSshKey(sshKey.copy(ownerId = account.uid)) - keys <- repo.listSshKeys(account.uid).compile.toList - } yield (w, keys) - test.map { result => - val (written, keys) = result - assert(written === 1, "No database rows written!") - assert(keys.exists(_.id === sshKey.id), "Key must be in the key list of the user!") - } - case _ => fail("Could not generate data samples!") + PropF.forAllF { (account: Account) => + val sshKey = sshKeyWithComment() + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val attempts = scala.util.Random.nextInt(128) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, None, Option(attempts)) + w <- repo.addSshKey(sshKey.copy(ownerId = account.uid)) + keys <- repo.listSshKeys(account.uid).compile.toList + } yield (w, keys) + test.start.flatMap(_.joinWithNever).map { result => + val (written, keys) = result + assert(written === 1, "No database rows written!") + assert(keys.exists(_.id === sshKey.id), "Key must be in the key list of the user!") + } } } test("addSshKey must fail if a key with the same fingerprint already exists".tag(NeedsDatabase)) { - (genValidAccount.sample, sshKeyWithoutComment()) match { - case (Some(account), Some(sshKey)) => - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val attempts = scala.util.Random.nextInt(128) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, None, Option(attempts)) - _ <- repo.addSshKey(sshKey.copy(ownerId = account.uid)) - _ <- repo.addSshKey(sshKey.copy(ownerId = account.uid)) - } yield () - test.attempt.map { result => - assert(result.isLeft, "Writing a key with a duplicate fingerprint must fail!") - } - case _ => fail("Could not generate data samples!") + PropF.forAllF { (account: Account) => + val sshKey = sshKeyWithoutComment() + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val attempts = scala.util.Random.nextInt(128) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, None, Option(attempts)) + _ <- repo.addSshKey(sshKey.copy(ownerId = account.uid)) + _ <- repo.addSshKey(sshKey.copy(ownerId = account.uid)) + } yield () + test.start.flatMap(_.joinWithNever).attempt.map { result => + assert(result.isLeft, "Writing a key with a duplicate fingerprint must fail!") + } } } test("deleteAccount must remove the account from the database".tag(NeedsDatabase)) { - genValidAccount.sample match { - case None => fail("Could not generate data samples!") - case Some(account) => - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val attempts = scala.util.Random.nextInt(128) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, None, Option(attempts)) - _ <- repo.deleteAccount(account.uid) - o <- loadAccount(account.uid) - } yield o - test.map(result => assert(result === None, "Account not deleted from database!")) + PropF.forAllF { (account: Account) => + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val attempts = scala.util.Random.nextInt(128) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, None, Option(attempts)) + _ <- repo.deleteAccount(account.uid) + o <- loadAccount(account.uid) + } yield o + test.start + .flatMap(_.joinWithNever) + .map(result => assert(result === None, "Account not deleted from database!")) } } test("findByValidationToken must return the matching account".tag(NeedsDatabase)) { - genValidAccount.sample match { - case None => fail("Could not generate data samples!") - case Some(ac) => - val account = ac.copy(validatedEmail = true) - val token = ValidationToken.generate - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, validationToken = token.some) - o <- repo.findByValidationToken(token) - } yield o - test.map(result => assert(result === Some(account))) + PropF.forAllF { (ac: Account) => + val account = ac.copy(validatedEmail = true) + val token = ValidationToken.generate + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, validationToken = token.some) + o <- repo.findByValidationToken(token) + } yield o + test.start.flatMap(_.joinWithNever).map(result => assert(result === Some(account))) } } test("findPasswordHash must return correct hash".tag(NeedsDatabase)) { - genValidAccount.sample match { - case None => fail("Could not generate data samples!") - case Some(account) => - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val attempts = scala.util.Random.nextInt(128) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, None, Option(attempts)) - o <- repo.findPasswordHash(account.uid) - } yield o - test.map(result => assert(result.exists(_ === hash), "Unexpected result from database!")) + PropF.forAllF { (account: Account) => + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val attempts = scala.util.Random.nextInt(128) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, None, Option(attempts)) + o <- repo.findPasswordHash(account.uid) + } yield o + test.start + .flatMap(_.joinWithNever) + .map(result => assert(result.exists(_ === hash), "Unexpected result from database!")) } } test("listSshKeys must return all keys for the user".tag(NeedsDatabase)) { - (genValidAccount.sample, sshKeyWithComment(), sshKeyWithoutComment()) match { - case (Some(account), Some(sshKeyA), Some(sshKeyB)) => - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val attempts = scala.util.Random.nextInt(128) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, None, Option(attempts)) - _ <- repo.addSshKey(sshKeyA.copy(ownerId = account.uid)) - _ <- repo.addSshKey(sshKeyB.copy(ownerId = account.uid)) - keys <- repo.listSshKeys(account.uid).compile.toList - } yield keys - test.map { keys => - assertEquals(keys.length, 2, "Expected 2 keys in the key list!") - assert(keys.exists(_.id === sshKeyA.id), "Key A must be in the key list of the user!") - assert(keys.exists(_.id === sshKeyB.id), "Key B must be in the key list of the user!") - } - case _ => fail("Could not generate data samples!") + PropF.forAllF { (account: Account) => + val sshKeyA = sshKeyWithComment() + val sshKeyB = sshKeyWithoutComment() + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val attempts = scala.util.Random.nextInt(128) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, None, Option(attempts)) + _ <- repo.addSshKey(sshKeyA.copy(ownerId = account.uid)) + _ <- repo.addSshKey(sshKeyB.copy(ownerId = account.uid)) + keys <- repo.listSshKeys(account.uid).compile.toList + } yield keys + test.start.flatMap(_.joinWithNever).map { keys => + assertEquals(keys.length, 2, "Expected 2 keys in the key list!") + assert(keys.exists(_.id === sshKeyA.id), "Key A must be in the key list of the user!") + assert(keys.exists(_.id === sshKeyB.id), "Key B must be in the key list of the user!") + } } } test("markAsValidated must clear the validation token and set the validated column to true".tag(NeedsDatabase)) { - genValidAccount.sample match { - case None => fail("Could not generate data samples!") - case Some(ac) => - val account = ac.copy(validatedEmail = true) - val token = ValidationToken.generate - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash, validationToken = token.some) - _ <- repo.markAsValidated(account.uid) - cols <- loadValidationColumns(account.uid) - } yield cols - test.map { result => - assert(result === Some((true, None)), "Unexpected result from database!") - } + PropF.forAllF { (ac: Account) => + val account = ac.copy(validatedEmail = true) + val token = ValidationToken.generate + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash, validationToken = token.some) + _ <- repo.markAsValidated(account.uid) + cols <- loadValidationColumns(account.uid) + } yield cols + test.start.flatMap(_.joinWithNever).map { result => + assert(result === Some((true, None)), "Unexpected result from database!") + } } } test("setLanguage must set the language".tag(NeedsDatabase)) { - genValidAccount.sample match { - case None => fail("Could not generate data samples!") - case Some(account) => - val language = genLanguageCode.sample - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash) - _ <- repo.setLanguage(account.uid, language) - modifiedAccount <- loadAccount(account.uid) - } yield modifiedAccount - test.map { modifiedAccount => - assert(modifiedAccount.exists(_.language === language), "Written language field does not match!") - } + PropF.forAllF { (account: Account, language: LanguageCode) => + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash) + _ <- repo.setLanguage(account.uid, language.some) + modifiedAccount <- loadAccount(account.uid) + } yield modifiedAccount + test.start.flatMap(_.joinWithNever).map { modifiedAccount => + assert(modifiedAccount.exists(_.language === language.some), "Written language field does not match!") + } + } + } + + test("setLanguage must delete the language".tag(NeedsDatabase)) { + PropF.forAllF { (account: Account, language: LanguageCode) => + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash) + _ <- repo.setLanguage(account.uid, language.some) + _ <- repo.setLanguage(account.uid, None) + modifiedAccount <- loadAccount(account.uid) + } yield modifiedAccount + test.start.flatMap(_.joinWithNever).map { modifiedAccount => + assert(modifiedAccount.exists(_.language === None), "Written language field does not match!") + } } } test("setValidationToken must set the validation token".tag(NeedsDatabase)) { - genValidAccount.sample match { - case None => fail("Could not generate data samples!") - case Some(account) => - val token = ValidationToken.generate - val dbConfig = configuration.database - val tx = Transactor.fromDriverManager[IO]( - driver = dbConfig.driver, - url = dbConfig.url, - user = dbConfig.user, - password = dbConfig.pass, - logHandler = None - ) - val repo = new DoobieAccountManagementRepository[IO](tx) - val hash = PasswordHash("Yet another weak password!") - val test = for { - _ <- createAccount(account, hash) - _ <- repo.setValidationToken(account.uid, token) - cols <- loadValidationColumns(account.uid) - } yield cols - test.map { result => - assert(result === Some((account.validatedEmail, Some(token))), "Unexpected result from database!") - } + PropF.forAllF { (account: Account) => + val token = ValidationToken.generate + val dbConfig = configuration.database + val tx = Transactor.fromDriverManager[IO]( + driver = dbConfig.driver, + url = dbConfig.url, + user = dbConfig.user, + password = dbConfig.pass, + logHandler = None + ) + val repo = new DoobieAccountManagementRepository[IO](tx) + val hash = PasswordHash("Yet another weak password!") + val test = for { + _ <- createAccount(account, hash) + _ <- repo.setValidationToken(account.uid, token) + cols <- loadValidationColumns(account.uid) + } yield cols + test.start.flatMap(_.joinWithNever).map { result => + assert(result === Some((account.validatedEmail, Some(token))), "Unexpected result from database!") + } } } } diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/hub/Generators.scala new-smederee/modules/hub/src/test/scala/de/smederee/hub/Generators.scala --- old-smederee/modules/hub/src/test/scala/de/smederee/hub/Generators.scala 2025-01-11 02:47:17.870494389 +0000 +++ new-smederee/modules/hub/src/test/scala/de/smederee/hub/Generators.scala 2025-01-11 02:47:17.874494392 +0000 @@ -31,6 +31,8 @@ val genLanguageCode: Gen[LanguageCode] = genLocale.map(_.getISO3Language).filter(_.nonEmpty).map(LanguageCode.apply) + given Arbitrary[LanguageCode] = Arbitrary(genLanguageCode) + val genFiniteDuration: Gen[FiniteDuration] = Gen.choose(0, Int.MaxValue).map(seconds => FiniteDuration(seconds, SECONDS))