|  | 
|  | 1 | +package org.scalasteward.core.nurture | 
|  | 2 | + | 
|  | 3 | +import munit.ScalaCheckSuite | 
|  | 4 | +import org.scalasteward.core.TestInstances._ | 
|  | 5 | +import org.scalasteward.core.data.{ProcessResult, RepoData, Update, UpdateData} | 
|  | 6 | + | 
|  | 7 | +import better.files.File | 
|  | 8 | +import cats.Applicative | 
|  | 9 | +import cats.effect._ | 
|  | 10 | +import cats.effect.concurrent.Ref | 
|  | 11 | +import org.http4s.HttpApp | 
|  | 12 | +import org.http4s.client.Client | 
|  | 13 | +import org.scalasteward.core.TestSyntax._ | 
|  | 14 | +import org.scalasteward.core.application.{Config, Context} | 
|  | 15 | +import org.scalasteward.core.git.FileGitAlgTest.{master, Supplement} | 
|  | 16 | +import org.scalasteward.core.git.{Branch, Commit, FileGitAlg, GenGitAlg, Sha1} | 
|  | 17 | +import org.scalasteward.core.io.{FileAlg, ProcessAlg, WorkspaceAlg} | 
|  | 18 | +import org.scalasteward.core.mock.MockContext | 
|  | 19 | +import org.scalasteward.core.mock.MockContext.{config, mockRoot} | 
|  | 20 | +import org.scalasteward.core.repocache._ | 
|  | 21 | +import org.scalasteward.core.repoconfig.RepoConfig | 
|  | 22 | +import org.scalasteward.core.util.Nel | 
|  | 23 | +import org.scalasteward.core.vcs.data.Repo | 
|  | 24 | + | 
|  | 25 | +class ApplyAlgTest extends ScalaCheckSuite { | 
|  | 26 | +  def step0(implicit | 
|  | 27 | +      CE: ConcurrentEffect[IO] | 
|  | 28 | +  ): Resource[IO, (ProcessAlg[IO], FileAlg[IO], WorkspaceAlg[IO], Context[IO])] = for { | 
|  | 29 | +    blocker <- Blocker[IO] | 
|  | 30 | +    config = Config.from(MockContext.args) | 
|  | 31 | +    implicit0(client: Client[IO]) = Client.fromHttpApp[IO](HttpApp.notFound) | 
|  | 32 | +    implicit0(fileAlg: FileAlg[IO]) = FileAlg.create[IO] | 
|  | 33 | +    _ <- Resource.eval(fileAlg.ensureExists(config.gitCfg.gitAskPass.parent)) | 
|  | 34 | +    _ <- Resource.eval( | 
|  | 35 | +      fileAlg.writeFile( | 
|  | 36 | +        config.gitCfg.gitAskPass, | 
|  | 37 | +        """ echo bogus-password """ | 
|  | 38 | +      ) | 
|  | 39 | +    ) | 
|  | 40 | +    _ <- Resource.eval(fileAlg.ensureExecutable(config.gitCfg.gitAskPass)) | 
|  | 41 | +    implicit0(processAlg: ProcessAlg[IO]) = ProcessAlg.create[IO](blocker, config.processCfg) | 
|  | 42 | +    implicit0(workspaceAlg: WorkspaceAlg[IO]) = WorkspaceAlg.create[IO](config) | 
|  | 43 | +    context <- Resource.eval(Context.step1[IO](config)) | 
|  | 44 | +  } yield (processAlg, fileAlg, workspaceAlg, context) | 
|  | 45 | + | 
|  | 46 | +  def setupRepo(repo: Repo, identicalBranch: (Branch, Update.Single)): IO[Unit] = | 
|  | 47 | +    step0.use { | 
|  | 48 | +      case ( | 
|  | 49 | +            implicit0(processAlg: ProcessAlg[IO]), | 
|  | 50 | +            implicit0(fileAlg: FileAlg[IO]), | 
|  | 51 | +            implicit0(workspaceAlg: WorkspaceAlg[IO]), | 
|  | 52 | +            context | 
|  | 53 | +          ) => | 
|  | 54 | +        val (branch, update) = identicalBranch | 
|  | 55 | +        implicit val ioGitAlg: GenGitAlg[IO, File] = | 
|  | 56 | +          new FileGitAlg[IO](config.gitCfg).contramapRepoF(Applicative[IO].pure) | 
|  | 57 | +        val supplement = new Supplement[IO] | 
|  | 58 | +        val repoDir = mockRoot / "workspace" / "repos" / repo.owner / repo.repo | 
|  | 59 | +        for { | 
|  | 60 | +          _ <- supplement.createRepo(repoDir) | 
|  | 61 | +          _ <- fileAlg.writeFile( | 
|  | 62 | +            repoDir / "build.sbt", | 
|  | 63 | +            """libraryDependency += "foo" % "bar" % "1.2.3" """ | 
|  | 64 | +          ) | 
|  | 65 | +          _ <- supplement.git("add", "build.sbt")(repoDir) | 
|  | 66 | +          _ <- context.gitAlg.commitAll(repo, "Initial commit") | 
|  | 67 | +          // Create another simulated curated update branch with | 
|  | 68 | +          _ <- context.gitAlg.createBranch(repo, branch) | 
|  | 69 | +          _ <- fileAlg.writeFile( | 
|  | 70 | +            repoDir / "build.sbt", | 
|  | 71 | +            s"""libraryDependency += "foo" % "bar" % "${update.newerVersions.head}" """ | 
|  | 72 | +          ) | 
|  | 73 | +          _ <- supplement.git("add", "build.sbt")(repoDir) | 
|  | 74 | +          _ <- context.gitAlg.commitAll(repo, "Update bar to 1.2.4") | 
|  | 75 | +          _ <- context.gitAlg.checkoutBranch(repo, master) | 
|  | 76 | +        } yield () | 
|  | 77 | +    } | 
|  | 78 | + | 
|  | 79 | +  test("Ensure unique patchesets are pushed") { | 
|  | 80 | +    val firstBranch = Branch("update/foo-1.2.4") | 
|  | 81 | +    val duplicateBranch = Branch("update/foo-duplicate-1.2.4") | 
|  | 82 | +    val update = Update.Single("foo" % "bar" % "1.2.3", Nel.one("1.2.4")) | 
|  | 83 | +    val firstChangeset = (firstBranch, update) | 
|  | 84 | +    val res = ({ | 
|  | 85 | +      def pushCommits( | 
|  | 86 | +          seenBranchesRef: Ref[IO, List[Branch]] | 
|  | 87 | +      ): (UpdateData, List[Commit]) => IO[ProcessResult] = { (data, _) => | 
|  | 88 | +        for { | 
|  | 89 | +          _ <- seenBranchesRef.update(data.updateBranch :: _) | 
|  | 90 | +        } yield ProcessResult.Updated | 
|  | 91 | +      } | 
|  | 92 | + | 
|  | 93 | +      val createPullRequest: UpdateData => IO[ProcessResult] = _ => IO.pure(ProcessResult.Updated) | 
|  | 94 | + | 
|  | 95 | +      val repo = Repo("myorg", "myrepo") | 
|  | 96 | +      val fork = Repo("myfork", "myrepo") | 
|  | 97 | +      step0.use { | 
|  | 98 | +        case ( | 
|  | 99 | +              implicit0(processAlg: ProcessAlg[IO]), | 
|  | 100 | +              implicit0(fileAlg: FileAlg[IO]), | 
|  | 101 | +              implicit0(workspaceAlg: WorkspaceAlg[IO]), | 
|  | 102 | +              context | 
|  | 103 | +            ) => | 
|  | 104 | +          for { | 
|  | 105 | +            _ <- setupRepo(repo, firstChangeset) | 
|  | 106 | +            seenBranchesRef <- Ref[IO].of(List.empty[Branch]) | 
|  | 107 | +            sha1 <- IO.fromEither(Sha1.from("adc83b19e793491b1c6ea0fd8b46cd9f32e592fc")) | 
|  | 108 | +            firstData = UpdateData( | 
|  | 109 | +              RepoData( | 
|  | 110 | +                repo, | 
|  | 111 | +                RepoCache(sha1, List.empty, Option.empty), | 
|  | 112 | +                RepoConfig() | 
|  | 113 | +              ), | 
|  | 114 | +              fork, | 
|  | 115 | +              update, | 
|  | 116 | +              master, | 
|  | 117 | +              sha1, | 
|  | 118 | +              Branch("bump") | 
|  | 119 | +            ) | 
|  | 120 | +            secondData = firstData.copy( | 
|  | 121 | +              updateBranch = duplicateBranch, | 
|  | 122 | +              update = update | 
|  | 123 | +            ) | 
|  | 124 | +            seenBranches <- seenBranchesRef.getAndUpdate(identity _) | 
|  | 125 | +            res1 <- context.applyAlg.applyNewUpdate( | 
|  | 126 | +              firstData, | 
|  | 127 | +              seenBranches, | 
|  | 128 | +              pushCommits(seenBranchesRef), | 
|  | 129 | +              createPullRequest | 
|  | 130 | +            ) | 
|  | 131 | +            seenBranches <- seenBranchesRef.getAndUpdate(identity _) | 
|  | 132 | +            res2 <- context.applyAlg.applyNewUpdate( | 
|  | 133 | +              secondData, | 
|  | 134 | +              seenBranches, | 
|  | 135 | +              pushCommits(seenBranchesRef), | 
|  | 136 | +              createPullRequest | 
|  | 137 | +            ) | 
|  | 138 | +          } yield (res1, res2) | 
|  | 139 | +      } | 
|  | 140 | +    }).unsafeRunSync() | 
|  | 141 | + | 
|  | 142 | +    assertEquals(res, (ProcessResult.Updated, ProcessResult.Ignored)) | 
|  | 143 | +  } | 
|  | 144 | + | 
|  | 145 | +  test("Ensure non-unique patchesets are not pushed") { | 
|  | 146 | +    val branch = Branch("update/foo-1.2.4") | 
|  | 147 | +    val update = Update.Single("foo" % "bar" % "1.2.3", Nel.one("1.2.4")) | 
|  | 148 | +    val identicalBranch = (branch, update) | 
|  | 149 | +    val res = ({ | 
|  | 150 | +      val pushCommits: (UpdateData, List[Commit]) => IO[ProcessResult] = | 
|  | 151 | +        (_, _) => IO.pure(ProcessResult.Updated) | 
|  | 152 | + | 
|  | 153 | +      val createPullRequest: UpdateData => IO[ProcessResult] = _ => IO.pure(ProcessResult.Updated) | 
|  | 154 | + | 
|  | 155 | +      val repo = Repo("myorg", "myrepo") | 
|  | 156 | +      val fork = Repo("myfork", "myrepo") | 
|  | 157 | +      step0.use { | 
|  | 158 | +        case ( | 
|  | 159 | +              implicit0(processAlg: ProcessAlg[IO]), | 
|  | 160 | +              implicit0(fileAlg: FileAlg[IO]), | 
|  | 161 | +              implicit0(workspaceAlg: WorkspaceAlg[IO]), | 
|  | 162 | +              context | 
|  | 163 | +            ) => | 
|  | 164 | +          for { | 
|  | 165 | +            _ <- setupRepo(repo, identicalBranch) | 
|  | 166 | +            seenBranchesRef <- Ref[IO].of(List(branch)) | 
|  | 167 | +            sha1 <- IO.fromEither(Sha1.from("adc83b19e793491b1c6ea0fd8b46cd9f32e592fc")) | 
|  | 168 | +            data = UpdateData( | 
|  | 169 | +              RepoData( | 
|  | 170 | +                repo, | 
|  | 171 | +                RepoCache(sha1, List.empty, Option.empty), | 
|  | 172 | +                RepoConfig() | 
|  | 173 | +              ), | 
|  | 174 | +              fork, | 
|  | 175 | +              update, | 
|  | 176 | +              master, | 
|  | 177 | +              sha1, | 
|  | 178 | +              Branch("bump") | 
|  | 179 | +            ) | 
|  | 180 | +            seenBranches <- seenBranchesRef.getAndUpdate(identity _) | 
|  | 181 | +            res <- context.applyAlg.applyNewUpdate( | 
|  | 182 | +              data, | 
|  | 183 | +              seenBranches, | 
|  | 184 | +              pushCommits, | 
|  | 185 | +              createPullRequest | 
|  | 186 | +            ) | 
|  | 187 | +          } yield res | 
|  | 188 | +      } | 
|  | 189 | +    }).unsafeRunSync() | 
|  | 190 | + | 
|  | 191 | +    assertEquals(res, ProcessResult.Ignored) | 
|  | 192 | +  } | 
|  | 193 | +} | 
0 commit comments