Repository: scala-steward-org/scala-steward
Pull request #3000 (2023-03-03 13:08:13)

refactoring: remove ForgeApiAlg.labelPullRequest
ForgeApiAlg.labelPullRequest only used during creation of pull request. Some providers such as GitLab, Azure, Gitea can add labels to PRs in the same API call used for PR creation and it's unreasonable to make it as a separate call. For providers that do not support it, labelPullRequest() is kept as private member


Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeApiAlg.scala
index 9fb6e58f..9773c55e 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeApiAlg.scala
@@ -40,8 +40,6 @@ trait ForgeApiAlg[F[_]] {
 
   def commentPullRequest(repo: Repo, number: PullRequestNumber, comment: String): F[Comment]
 
-  def labelPullRequest(repo: Repo, number: PullRequestNumber, labels: List[String]): F[Unit]
-
   final def createForkOrGetRepo(repo: Repo, doNotFork: Boolean): F[RepoOut] =
     if (doNotFork) getRepo(repo) else createFork(repo)
 
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlg.scala
index 92054c74..103c75b6 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlg.scala
@@ -18,8 +18,6 @@ package org.scalasteward.core.forge.azurerepos
 
 import cats.MonadThrow
 import cats.syntax.all._
-import io.circe.Json
-import io.circe.syntax.KeyOps
 import org.http4s.{Request, Uri}
 import org.scalasteward.core.application.Config.AzureReposCfg
 import org.scalasteward.core.data.Repo
@@ -90,20 +88,6 @@ final class AzureReposApiAlg[F[_]](
       modify(repo)
     )
 
-  // https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-labels/create?view=azure-devops-rest-7.1
-  override def labelPullRequest(
-      repo: Repo,
-      number: PullRequestNumber,
-      labels: List[String]
-  ): F[Unit] =
-    client
-      .postWithBody[Json, Json](
-        url.labelPullRequest(repo, number),
-        Json.obj("name" := labels.mkString("-")),
-        modify(repo)
-      )
-      .void
-
   private def warnIfAssigneesAreUsed =
     logger.warn("assignees are not supported by AzureRepos")
 
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/JsonCodec.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/JsonCodec.scala
index 68ed19e9..8be29111 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/JsonCodec.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/JsonCodec.scala
@@ -28,6 +28,7 @@ final private[azurerepos] case class PullRequestPayload(
     sourceRefName: String,
     targetRefName: String,
     title: String,
+    labels: Option[List[String]],
     description: String
 )
 
@@ -39,6 +40,7 @@ private[azurerepos] object PullRequestPayload {
       sourceRefName = withPrefix(data.head),
       targetRefName = data.base.name,
       title = data.title,
+      labels = Option.when(data.labels.nonEmpty)(data.labels),
       description = data.body
     )
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/Url.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/Url.scala
index 3ec514ce..0d196d2e 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/Url.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/Url.scala
@@ -49,7 +49,4 @@ class Url(apiHost: Uri, organization: String) {
 
   def commentPullRequest(repo: Repo, number: PullRequestNumber): Uri =
     pullRequests(repo) / number.value / "threads"
-
-  def labelPullRequest(repo: Repo, number: PullRequestNumber): Uri =
-    pullRequests(repo) / number.value / "labels"
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlg.scala
index af227bf1..946fd674 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlg.scala
@@ -92,6 +92,7 @@ class BitbucketApiAlg[F[_]](
     for {
       _ <- F.whenA(data.assignees.nonEmpty)(warnIfAssigneesAreUsed)
       _ <- F.whenA(data.reviewers.nonEmpty)(warnIfReviewersAreUsed)
+      _ <- F.whenA(data.labels.nonEmpty)(warnIfLabelsAreUsed)
       pullRequestOut <- create
     } yield pullRequestOut
   }
@@ -132,11 +133,7 @@ class BitbucketApiAlg[F[_]](
       )
       .map((cc: CreateComment) => Comment(cc.content.raw))
 
-  override def labelPullRequest(
-      repo: Repo,
-      number: PullRequestNumber,
-      labels: List[String]
-  ): F[Unit] =
+  private def warnIfLabelsAreUsed =
     logger.warn(
       "Bitbucket does not support PR labels, remove --add-labels to make this warning disappear"
     )
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlg.scala
index 12a17c2a..4c4fa270 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlg.scala
@@ -70,6 +70,7 @@ final class BitbucketServerApiAlg[F[_]](
       reviewers <- useDefaultReviewers(repo)
       _ <- F.whenA(data.assignees.nonEmpty)(warnIfAssigneesAreUsed)
       _ <- F.whenA(data.reviewers.nonEmpty)(warnIfReviewersAreUsed)
+      _ <- F.whenA(data.labels.nonEmpty)(warnIfLabelsAreUsed)
       req = Json.NewPR(
         title = data.title,
         description = data.body,
@@ -121,11 +122,7 @@ final class BitbucketServerApiAlg[F[_]](
       .get[Json.Page[Json.PR]](url.listPullRequests(repo, s"refs/heads/$head"), modify(repo))
       .map(_.values.map(_.toPullRequestOut))
 
-  override def labelPullRequest(
-      repo: Repo,
-      number: PullRequestNumber,
-      labels: List[String]
-  ): F[Unit] =
+  private def warnIfLabelsAreUsed =
     logger.warn(
       "Bitbucket does not support PR labels, remove --add-labels to make this warning disappear"
     )
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala
index 1eca988d..d464cfdf 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/data/NewPullRequestData.scala
@@ -223,6 +223,7 @@ object NewPullRequestData {
       artifactIdToUrl: Map[String, Uri] = Map.empty,
       artifactIdToUpdateInfoUrls: Map[String, List[UpdateInfoUrl]] = Map.empty,
       filesWithOldVersion: List[String] = List.empty,
+      addLabels: Boolean = false,
       labels: List[String] = List.empty
   ): NewPullRequestData =
     NewPullRequestData(
@@ -243,7 +244,7 @@ object NewPullRequestData {
       ),
       head = branchName,
       base = data.baseBranch,
-      labels = labels,
+      labels = if (addLabels) labels else List.empty,
       assignees = data.repoConfig.assignees,
       reviewers = data.repoConfig.reviewers
     )
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/gitea/GiteaApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/gitea/GiteaApiAlg.scala
index dc0cc920..1fdd076c 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/gitea/GiteaApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/gitea/GiteaApiAlg.scala
@@ -255,25 +255,6 @@ final class GiteaApiAlg[F[_]: HttpJsonClient](
       }
   }
 
-  override def labelPullRequest(
-      repo: Repo,
-      number: PullRequestNumber,
-      labels: List[String]
-  ): F[Unit] = {
-    def attachLabels(labels: Vector[Int]): F[Unit] =
-      if (labels.nonEmpty)
-        client
-          .postWithBody[Vector[Label], AttachLabelReq](
-            url.pullRequestLabels(repo, number),
-            AttachLabelReq(labels),
-            modify(repo)
-          )
-          .void
-      else ().pure[F]
-
-    getOrCreateLabel(repo, labels.toVector) >>= attachLabels
-  }
-
   def getOrCreateLabel(repo: Repo, labels: Vector[String]): F[Vector[Int]] =
     listLabels(repo).flatMap { repoLabels =>
       val existing = repoLabels.filter(label => labels.contains(label.name))
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/github/GitHubApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/github/GitHubApiAlg.scala
index 1efeb7a9..7d1ba66f 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/github/GitHubApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/github/GitHubApiAlg.scala
@@ -53,6 +53,7 @@ final class GitHubApiAlg[F[_]](
 
     for {
       pullRequestOut <- create
+      _ <- F.whenA(data.labels.nonEmpty)(labelPullRequest(repo, pullRequestOut.number, data.labels))
       _ <-
         F.whenA(data.assignees.nonEmpty)(addAssignees(repo, pullRequestOut.number, data.assignees))
       _ <-
@@ -92,7 +93,7 @@ final class GitHubApiAlg[F[_]](
       .postWithBody(url.comments(repo, number), Comment(comment), modify(repo))
 
   /** https://docs.github.com/en/rest/reference/issues#add-labels-to-an-issue */
-  override def labelPullRequest(
+  private def labelPullRequest(
       repo: Repo,
       number: PullRequestNumber,
       labels: List[String]
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlg.scala
index 8d3adec3..9f333d9b 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlg.scala
@@ -36,6 +36,7 @@ final private[gitlab] case class MergeRequestPayload(
     id: String,
     title: String,
     description: String,
+    labels: Option[List[String]],
     assignee_ids: Option[List[Int]],
     reviewer_ids: Option[List[Int]],
     target_project_id: Long,
@@ -58,6 +59,7 @@ private[gitlab] object MergeRequestPayload {
       description = data.body,
       assignee_ids = Option.when(assignees.nonEmpty)(assignees),
       reviewer_ids = Option.when(reviewers.nonEmpty)(reviewers),
+      labels = Option.when(data.labels.nonEmpty)(data.labels),
       target_project_id = projectId,
       source_branch = data.head,
       target_branch = data.base
@@ -329,17 +331,4 @@ final class GitLabApiAlg[F[_]: Parallel](
   ): F[Comment] =
     client.postWithBody(url.comments(repo, number), Comment(comment), modify(repo))
 
-  // https://docs.gitlab.com/ee/api/merge_requests.html#update-mr
-  override def labelPullRequest(
-      repo: Repo,
-      number: PullRequestNumber,
-      labels: List[String]
-  ): F[Unit] =
-    client
-      .putWithBody[Json, Json](
-        url.existingMergeRequest(repo, number),
-        Json.obj("labels" := labels.mkString(",")),
-        modify(repo)
-      )
-      .void
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
index 408a61da..ff9aacc5 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/NurtureAlg.scala
@@ -234,13 +234,14 @@ final class NurtureAlg[F[_]](config: ForgeCfg)(implicit
       allLabels = labelsFor(data.update, edits, filesWithOldVersion, artifactIdToVersionScheme)
       labels = filterLabels(allLabels, data.repoData.config.pullRequests.includeMatchedLabels)
       requestData = NewPullRequestData.from(
-        data,
-        branchName,
-        edits,
-        artifactIdToUrl,
-        artifactIdToUpdateInfoUrls.toMap,
-        filesWithOldVersion,
-        labels
+        data = data,
+        branchName = branchName,
+        edits = edits,
+        artifactIdToUrl = artifactIdToUrl,
+        artifactIdToUpdateInfoUrls = artifactIdToUpdateInfoUrls.toMap,
+        filesWithOldVersion = filesWithOldVersion,
+        addLabels = config.addLabels,
+        labels = labels
       )
     } yield requestData
 
@@ -249,9 +250,6 @@ final class NurtureAlg[F[_]](config: ForgeCfg)(implicit
       _ <- logger.info(s"Create PR ${data.updateBranch.name}")
       requestData <- preparePullRequest(data, edits)
       pr <- forgeApiAlg.createPullRequest(data.repo, requestData)
-      _ <- forgeApiAlg
-        .labelPullRequest(data.repo, pr.number, requestData.labels)
-        .whenA(config.addLabels && requestData.labels.nonEmpty)
       prData = PullRequestData[Id](
         pr.html_url,
         data.baseSha1,


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlgTest.scala
index d1d7b6dd..70b11e54 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/AzureReposApiAlgTest.scala
@@ -266,10 +266,4 @@ class AzureReposApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
     assertIO(obtained, expected)
   }
 
-  test("labelPullRequest") {
-    azureReposApiAlg
-      .labelPullRequest(repo, PullRequestNumber(26), List("dependency-updates"))
-      .runA(state)
-      .assert
-  }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/JsonCodecTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/JsonCodecTest.scala
new file mode 100644
index 00000000..07ae4ee6
--- /dev/null
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/JsonCodecTest.scala
@@ -0,0 +1,49 @@
+package org.scalasteward.core.forge.azurerepos
+
+import io.circe.literal._
+import io.circe.syntax._
+import munit.FunSuite
+import org.scalasteward.core.forge.data.NewPullRequestData
+import org.scalasteward.core.git.Branch
+
+class JsonCodecTest extends FunSuite {
+  import JsonCodec._
+
+  private val data = NewPullRequestData(
+    title = "Test MR title",
+    body = "Test MR body",
+    head = "source",
+    base = Branch("main"),
+    labels = Nil,
+    assignees = Nil,
+    reviewers = Nil
+  )
+
+  test("PullRequestPayload") {
+    val obtained = PullRequestPayload.from(data).asJson
+    val expected =
+      json"""{
+               "sourceRefName" : "refs/heads/source",
+               "targetRefName" : "main",
+               "title" : "Test MR title",
+               "labels" : null,
+               "description" : "Test MR body"
+             }"""
+
+    assertEquals(obtained, expected)
+  }
+
+  test("PullRequestPayload with labels") {
+    val obtained = PullRequestPayload.from(data.copy(labels = List("foo", "bar"))).asJson
+    val expected =
+      json"""{
+               "sourceRefName" : "refs/heads/source",
+               "targetRefName" : "main",
+               "title" : "Test MR title",
+               "labels" : [ "foo", "bar" ],
+               "description" : "Test MR body"
+             }"""
+
+    assertEquals(obtained, expected)
+  }
+}
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/UrlTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/UrlTest.scala
index 022df4e6..bb29f826 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/UrlTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/azurerepos/UrlTest.scala
@@ -60,11 +60,4 @@ class UrlTest extends FunSuite {
     )
   }
 
-  test("labelPullRequest") {
-    assertEquals(
-      url.labelPullRequest(repo, PullRequestNumber(42)).toString,
-      "https://dev.azure.com/my-azure-org/scala-steward-org/" +
-        "_apis%2Fgit%2Frepositories/scala-steward/pullrequests/42/labels?api-version=7.1-preview.1"
-    )
-  }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala
index 3d703a45..f3752fbb 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/data/NewPullRequestDataTest.scala
@@ -572,6 +572,7 @@ class NewPullRequestDataTest extends FunSuite {
     val obtained = from(
       data,
       "scala-steward:update/logback-classic-1.2.3",
+      addLabels = true,
       labels = labelsFor(data.update)
     )
 
@@ -643,6 +644,7 @@ class NewPullRequestDataTest extends FunSuite {
     val obtained = from(
       data,
       "scala-steward:update/logback-classic-1.2.3",
+      addLabels = true,
       labels = labelsFor(data.update)
     )
 
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/gitea/GiteaApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/gitea/GiteaApiAlgTest.scala
index e4fae824..80f066bd 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/gitea/GiteaApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/gitea/GiteaApiAlgTest.scala
@@ -191,12 +191,6 @@ class GiteaApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
       }
   }
 
-  test("label pr") {
-    giteaAlg
-      .labelPullRequest(repo, PullRequestNumber(2), List("label1"))
-      .runA(state)
-  }
-
   test("create fork") {
     giteaAlg
       .createFork(repo)
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/github/GitHubApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/github/GitHubApiAlgTest.scala
index 883a99d5..65323574 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/github/GitHubApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/github/GitHubApiAlgTest.scala
@@ -115,6 +115,14 @@ class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
             "title": "new-feature"
             } """)
 
+    case POST -> Root / "repos" / "fthomas" / "cant-add-labels" / "pulls" =>
+      Created(json"""  {
+            "html_url": "https://github.com/octocat/Hello-World/pull/13",
+            "state": "open",
+            "number": 13,
+            "title": "can't add labels to me"
+            } """)
+
     case POST -> Root / "repos" / "fthomas" / "base.g8" / "issues" / IntVar(_) / "labels" =>
       // Response taken from https://docs.github.com/en/rest/reference/issues#labels, is ignored
       Created(json"""[
@@ -128,6 +136,9 @@ class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
             "default": true
           }]""")
 
+    case POST -> Root / "repos" / "fthomas" / "cant-add-labels" / "issues" / "13" / "labels" =>
+      BadRequest("can't add labels")
+
     case POST -> Root / "repos" / "fthomas" / "base.g8" / "issues" / IntVar(_) / "assignees" =>
       Created(json"{}")
 
@@ -310,6 +321,47 @@ class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
     assertIO(pullRequestOut, expectedPullRequestOut)
   }
 
+  test("createPullRequest with labels") {
+    val data = NewPullRequestData(
+      title = "new-feature",
+      body = "body",
+      head = "aaa",
+      base = Branch("master"),
+      labels = List("foo", "bar"),
+      assignees = Nil,
+      reviewers = Nil
+    )
+    val pr = gitHubApiAlg.createPullRequest(repo, data).runA(state)
+    assertIO(pr, pullRequest)
+  }
+
+  test("createPullRequest should fail when can't add labels") {
+    val data = NewPullRequestData(
+      title = "new-feature",
+      body = "body",
+      head = "aaa",
+      base = Branch("master"),
+      labels = List("foo", "bar"),
+      assignees = Nil,
+      reviewers = Nil
+    )
+    val error =
+      gitHubApiAlg
+        .createPullRequest(repo.copy(repo = "cant-add-labels"), data)
+        .runA(state)
+        .attempt
+        .map(_.swap.map(_.getMessage))
+    val expectedError =
+      """|uri: http://example.com/repos/fthomas/cant-add-labels/issues/13/labels
+         |method: POST
+         |status: 400 Bad Request
+         |headers:
+         |  Content-Type: text/plain; charset=UTF-8
+         |  Content-Length: 16
+         |body: can't add labels""".stripMargin
+    assertIO(error, Right(expectedError))
+  }
+
   test("listPullRequests") {
     val prs = gitHubApiAlg.listPullRequests(repo, "master", Branch("master")).runA(state)
     assertIO(prs, List(pullRequest))
@@ -332,10 +384,4 @@ class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
     assertIO(comment, Comment("Superseded by #1234"))
   }
 
-  test("labelPullRequest") {
-    gitHubApiAlg
-      .labelPullRequest(repo, PullRequestNumber(1347), List("A", "B"))
-      .runA(state)
-      .assert
-  }
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlgTest.scala
index 0ba2ad11..2a1a5aaf 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlgTest.scala
@@ -374,13 +374,6 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
     )
   }
 
-  test("labelPullRequest") {
-    gitlabApiAlg
-      .labelPullRequest(Repo("foo", "bar"), PullRequestNumber(150), List("A", "B"))
-      .runA(state)
-      .assert
-  }
-
   private val getMr = json"""
     {
       "id": 26328,
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/MergeRequestPayloadTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/MergeRequestPayloadTest.scala
index 30b04658..67b9dc1b 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/MergeRequestPayloadTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/gitlab/MergeRequestPayloadTest.scala
@@ -28,6 +28,7 @@ class MergeRequestPayloadTest extends FunSuite {
                "id" : "123",
                "title" : "Test MR title",
                "description" : "Test MR body",
+               "labels" : null,
                "assignee_ids" : null,
                "reviewer_ids" : null,
                "target_project_id" : 321,
@@ -44,6 +45,25 @@ class MergeRequestPayloadTest extends FunSuite {
                "id" : "123",
                "title" : "Draft: Test MR title",
                "description" : "Test MR body",
+               "labels" : null,
+               "assignee_ids" : null,
+               "reviewer_ids" : null,
+               "target_project_id" : 321,
+               "source_branch" : "source",
+               "target_branch" : "master"
+             }"""
+    assertEquals(obtained, expected)
+  }
+
+  test("asJson with labels") {
+    val obtained =
+      MergeRequestPayload(id, projectId, data.copy(labels = List("foo", "bar")), Map.empty).asJson
+    val expected =
+      json"""{
+               "id" : "123",
+               "title" : "Test MR title",
+               "description" : "Test MR body",
+               "labels" : [ "foo", "bar" ],
                "assignee_ids" : null,
                "reviewer_ids" : null,
                "target_project_id" : 321,
@@ -65,6 +85,7 @@ class MergeRequestPayloadTest extends FunSuite {
                "id" : "123",
                "title" : "Test MR title",
                "description" : "Test MR body",
+               "labels" : null,
                "assignee_ids": [ 1 ],
                "reviewer_ids": [ 2 ],
                "target_project_id" : 321,
diff --git a/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala b/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala
index 37bc4373..c2116bae 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/mock/MockConfig.scala
@@ -19,6 +19,7 @@ object MockConfig {
     "--env-var=VAR1=val1",
     "--env-var=VAR2=val2",
     "--cache-ttl=1hour",
+    "--add-labels",
     "--refresh-backoff-period=1hour"
   )
   val Success(Config.StewardUsage.Regular(config)) = Cli.parseArgs(args)
diff --git a/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala b/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala
index e5fe428d..b0733d82 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/mock/MockContext.scala
@@ -15,6 +15,7 @@ import org.scalasteward.core.repoconfig.RepoConfigLoader
 import org.scalasteward.core.update.artifact.ArtifactMigrationsLoader
 import org.scalasteward.core.util.UrlCheckerClient
 import org.typelevel.log4cats.Logger
+import org.scalasteward.core.application.Config
 
 object MockContext {
   implicit private val client: Client[MockEff] =
@@ -43,7 +44,9 @@ object MockContext {
       ioFileAlg.readResource("scalafix-migrations.conf").unsafeRunSync()
   )
 
-  val context: Context[MockEff] = mockState.toRef.flatMap(Context.step1(config).run).unsafeRunSync()
+  val context: Context[MockEff] = context(config)
+  def context(stewardConfig: Config): Context[MockEff] =
+    mockState.toRef.flatMap(Context.step1(stewardConfig).run).unsafeRunSync()
   def validateRepoConfigContext(file: File): StewardContext.ValidateRepoConfig[MockEff] =
     Context.initValidateRepoConfig(file)
 }
diff --git a/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala
index 52bd67fd..ac39bf0f 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/nurture/NurtureAlgTest.scala
@@ -9,12 +9,13 @@ import org.scalasteward.core.data.{DependencyInfo, Repo, RepoData, UpdateData}
 import org.scalasteward.core.edit.EditAttempt.UpdateEdit
 import org.scalasteward.core.forge.data.NewPullRequestData
 import org.scalasteward.core.git.{Branch, Commit}
-import org.scalasteward.core.mock.MockContext.context.nurtureAlg
-import org.scalasteward.core.mock.{MockEff, MockState}
+import org.scalasteward.core.mock.MockContext.context
+import org.scalasteward.core.mock.{MockConfig, MockEff, MockState}
 import org.scalasteward.core.repoconfig.RepoConfig
 
 class NurtureAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
   test("preparePullRequest") {
+    val nurtureAlg = context.nurtureAlg
     val repo = Repo("scala-steward-org", "scala-steward")
     val dependency = "org.typelevel".g % ("cats-effect", "cats-effect_2.13").a % "3.3.0"
     val repoCache = dummyRepoCache.copy(dependencyInfos =
@@ -82,4 +83,37 @@ class NurtureAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
     )
     assertIO(obtained, expected)
   }
+
+  test("preparePullRequest should not set labels if ForgeConfig.addLabels = false") {
+    val config =
+      MockConfig.config.copy(forgeCfg = MockConfig.config.forgeCfg.copy(addLabels = false))
+    val nurtureAlg = context(config).nurtureAlg
+    val repo = Repo("scala-steward-org", "scala-steward")
+    val dependency = "org.typelevel".g % ("cats-effect", "cats-effect_2.13").a % "3.3.0"
+    val repoCache = dummyRepoCache.copy(dependencyInfos =
+      List(List(DependencyInfo(dependency, Nil)).withMavenCentral)
+    )
+    val repoData =
+      RepoData(repo, repoCache, RepoConfig(assignees = List("foo"), reviewers = List("bar")))
+    val fork = Repo("scala-steward", "scala-steward")
+    val update = (dependency %> "3.4.0").single
+    val baseBranch = Branch("main")
+    val updateBranch = Branch("update/cats-effect-3.4.0")
+    val updateData = UpdateData(repoData, fork, update, baseBranch, dummySha1, updateBranch)
+    val edits = List(UpdateEdit(update, Commit(dummySha1)))
+    val state = MockState.empty.copy(clientResponses = HttpApp {
+      case HEAD -> Root / "typelevel" / "cats-effect"                                 => Ok()
+      case HEAD -> Root / "typelevel" / "cats-effect" / "releases" / "tag" / "v3.4.0" => Ok()
+      case HEAD -> Root / "typelevel" / "cats-effect" / "compare" / "v3.3.0...v3.4.0" => Ok()
+      case _                                                                          => NotFound()
+    })
+    nurtureAlg.preparePullRequest(updateData, edits).runA(state).map { obtained =>
+      assert(obtained.labels.isEmpty)
+      val lastLine = obtained.body.split("\n").last
+      assertEquals(
+        lastLine,
+        "labels: library-update, early-semver-minor, semver-spec-minor, version-scheme:early-semver, commit-count:1"
+      )
+    }
+  }
 }