Repository: scala-steward-org/scala-steward
Pull request #2919 (2023-01-14 05:59:10)

Do forge authentication in one function
This adds `ForgeSelection.authenticate` which modifies requests for authentication for a given forge type. This is nicer than to scatter this logic over multiple files and I think it will also be useful in fixing #1468.


Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
index c3fd190a..16a3f4f2 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
@@ -205,8 +205,7 @@ object Context {
       implicit val httpJsonClient: HttpJsonClient[F] = new HttpJsonClient[F]
       implicit val repoCacheRepository: RepoCacheRepository[F] =
         new RepoCacheRepository[F](repoCacheStore)
-      implicit val forgeApiAlg: ForgeApiAlg[F] =
-        new ForgeSelection[F](config, forgeUser).forgeApiAlg
+      implicit val forgeApiAlg: ForgeApiAlg[F] = ForgeSelection.forgeApiAlg[F](config, forgeUser)
       implicit val forgeRepoAlg: ForgeRepoAlg[F] = new ForgeRepoAlg[F](config)
       implicit val updateInfoUrlFinder: UpdateInfoUrlFinder[F] =
         new UpdateInfoUrlFinder[F](config.forgeCfg)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala
index 1d35b972..8e609b11 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala
@@ -16,8 +16,10 @@
 
 package org.scalasteward.core.forge
 
-import cats.MonadThrow
-import org.http4s.Header
+import cats.syntax.all._
+import cats.{Applicative, MonadThrow}
+import org.http4s.headers.Authorization
+import org.http4s.{BasicCredentials, Header, Request}
 import org.scalasteward.core.application.Config
 import org.scalasteward.core.forge.ForgeType._
 import org.scalasteward.core.forge.azurerepos.AzureReposApiAlg
@@ -30,57 +32,43 @@ import org.scalasteward.core.util.HttpJsonClient
 import org.typelevel.ci._
 import org.typelevel.log4cats.Logger
 
-final class ForgeSelection[F[_]](config: Config, user: AuthenticatedUser)(implicit
-    client: HttpJsonClient[F],
-    logger: Logger[F],
-    F: MonadThrow[F]
-) {
-  private def gitHubApiAlg: GitHubApiAlg[F] =
-    new GitHubApiAlg[F](
-      config.forgeCfg.apiHost,
-      _ => github.authentication.addCredentials(user)
-    )
-
-  private def gitLabApiAlg: GitLabApiAlg[F] =
-    new GitLabApiAlg[F](
-      config.forgeCfg,
-      config.gitLabCfg,
-      _ => gitlab.authentication.addCredentials(user)
-    )
-
-  private def bitbucketApiAlg: BitbucketApiAlg[F] =
-    new BitbucketApiAlg(
-      config.forgeCfg,
-      config.bitbucketCfg,
-      _ => bitbucket.authentication.addCredentials(user)
-    )
-
-  private def bitbucketServerApiAlg: BitbucketServerApiAlg[F] = {
-    // Bypass the server-side XSRF check, see
-    // https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
-    val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")
-
-    new BitbucketServerApiAlg[F](
-      config.forgeCfg.apiHost,
-      config.bitbucketServerCfg,
-      _ =>
-        req => bitbucket.authentication.addCredentials(user).apply(req.putHeaders(xAtlassianToken))
-    )
+object ForgeSelection {
+  def forgeApiAlg[F[_]](config: Config, user: AuthenticatedUser)(implicit
+      httpJsonClient: HttpJsonClient[F],
+      logger: Logger[F],
+      F: MonadThrow[F]
+  ): ForgeApiAlg[F] = {
+    val auth = (_: Any) => authenticate(config.forgeCfg.tpe, user)
+    config.forgeCfg.tpe match {
+      case AzureRepos =>
+        new AzureReposApiAlg[F](config.forgeCfg.apiHost, config.azureReposConfig, auth)
+      case Bitbucket =>
+        new BitbucketApiAlg(config.forgeCfg, config.bitbucketCfg, auth)
+      case BitbucketServer =>
+        new BitbucketServerApiAlg[F](config.forgeCfg.apiHost, config.bitbucketServerCfg, auth)
+      case GitHub =>
+        new GitHubApiAlg[F](config.forgeCfg.apiHost, auth)
+      case GitLab =>
+        new GitLabApiAlg[F](config.forgeCfg, config.gitLabCfg, auth)
+    }
   }
 
-  private def azureReposApiAlg: AzureReposApiAlg[F] =
-    new AzureReposApiAlg[F](
-      config.forgeCfg.apiHost,
-      config.azureReposConfig,
-      _ => azurerepos.authentication.addCredentials(user)
-    )
-
-  def forgeApiAlg: ForgeApiAlg[F] =
-    config.forgeCfg.tpe match {
-      case GitHub          => gitHubApiAlg
-      case GitLab          => gitLabApiAlg
-      case Bitbucket       => bitbucketApiAlg
-      case BitbucketServer => bitbucketServerApiAlg
-      case AzureRepos      => azureReposApiAlg
+  def authenticate[F[_]](
+      forgeType: ForgeType,
+      user: AuthenticatedUser
+  )(implicit F: Applicative[F]): Request[F] => F[Request[F]] =
+    forgeType match {
+      case AzureRepos      => _.putHeaders(basicAuth(user)).pure[F]
+      case Bitbucket       => _.putHeaders(basicAuth(user)).pure[F]
+      case BitbucketServer =>
+        // Bypass the server-side XSRF check, see
+        // https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
+        val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")
+        _.putHeaders(basicAuth(user), xAtlassianToken).pure[F]
+      case GitHub => _.putHeaders(basicAuth(user)).pure[F]
+      case GitLab => _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
     }
+
+  private def basicAuth(user: AuthenticatedUser): Authorization =
+    Authorization(BasicCredentials(user.login, user.accessToken))
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/authentication.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/authentication.scala
deleted file mode 100644
index 7ea47ba1..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/azurerepos/authentication.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018-2023 Scala Steward contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.scalasteward.core.forge.azurerepos
-
-import cats.Applicative
-import cats.syntax.all._
-import org.http4s.headers.Authorization
-import org.http4s.{BasicCredentials, Request}
-import org.scalasteward.core.forge.data.AuthenticatedUser
-
-object authentication {
-  def addCredentials[F[_]: Applicative](user: AuthenticatedUser): Request[F] => F[Request[F]] =
-    _.putHeaders(Authorization(BasicCredentials(user.login, user.accessToken))).pure[F]
-}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/authentication.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/authentication.scala
deleted file mode 100644
index b89f882a..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/bitbucket/authentication.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018-2023 Scala Steward contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.scalasteward.core.forge.bitbucket
-
-import cats.Applicative
-import cats.syntax.all._
-import org.http4s.headers.Authorization
-import org.http4s.{BasicCredentials, Request}
-import org.scalasteward.core.forge.data.AuthenticatedUser
-
-object authentication {
-  def addCredentials[F[_]: Applicative](user: AuthenticatedUser): Request[F] => F[Request[F]] =
-    _.putHeaders(Authorization(BasicCredentials(user.login, user.accessToken))).pure[F]
-}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/github/authentication.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/github/authentication.scala
deleted file mode 100644
index 8e64fc15..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/github/authentication.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018-2023 Scala Steward contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.scalasteward.core.forge.github
-
-import cats.Applicative
-import cats.syntax.all._
-import org.http4s.headers.Authorization
-import org.http4s.{BasicCredentials, Request}
-import org.scalasteward.core.forge.data.AuthenticatedUser
-
-object authentication {
-  def addCredentials[F[_]: Applicative](user: AuthenticatedUser): Request[F] => F[Request[F]] =
-    _.putHeaders(Authorization(BasicCredentials(user.login, user.accessToken))).pure[F]
-}
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/authentication.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/authentication.scala
deleted file mode 100644
index 361e5f0b..00000000
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/authentication.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018-2023 Scala Steward contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.scalasteward.core.forge.gitlab
-
-import cats.Applicative
-import cats.syntax.all._
-import org.http4s.{Header, Request}
-import org.scalasteward.core.forge.data.AuthenticatedUser
-import org.typelevel.ci._
-
-object authentication {
-  def addCredentials[F[_]: Applicative](user: AuthenticatedUser): Request[F] => F[Request[F]] =
-    _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
-}


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 fccbf887..20146982 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
@@ -175,13 +175,13 @@ class AzureReposApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
   private val state = MockState.empty.copy(clientResponses = auth <+> httpApp)
 
   private val azureRepoCfg = AzureReposConfig(organization = Some("azure-org"))
-  private val azureReposApiAlg = new ForgeSelection[MockEff](
+  private val azureReposApiAlg = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(
       forgeCfg = config.forgeCfg.copy(apiHost = apiHost, tpe = ForgeType.AzureRepos),
       azureReposConfig = azureRepoCfg
     ),
     user
-  ).forgeApiAlg
+  )
 
   test("getRepo") {
     val obtained = azureReposApiAlg.getRepo(repo).runA(state)
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlgTest.scala
index 46c1948a..67072064 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucket/BitbucketApiAlgTest.scala
@@ -186,13 +186,13 @@ class BitbucketApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
   }
   private val state = MockState.empty.copy(clientResponses = auth <+> httpApp)
 
-  private val bitbucketApiAlg = new ForgeSelection[MockEff](
+  private val bitbucketApiAlg = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(
       forgeCfg = config.forgeCfg.copy(tpe = ForgeType.Bitbucket),
       bitbucketCfg = BitbucketCfg(useDefaultReviewers = true)
     ),
     user
-  ).forgeApiAlg
+  )
 
   private val prUrl = uri"https://bitbucket.org/fthomas/base.g8/pullrequests/2"
   private val repo = Repo("fthomas", "base.g8")
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlgTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlgTest.scala
index 13cb7932..4f83a745 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlgTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/bitbucketserver/BitbucketServerApiAlgTest.scala
@@ -111,13 +111,13 @@ class BitbucketServerApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff]
   }
   private val state = MockState.empty.copy(clientResponses = auth <+> httpApp)
 
-  private val bitbucketServerApiAlg = new ForgeSelection[MockEff](
+  private val bitbucketServerApiAlg = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(
       forgeCfg = config.forgeCfg.copy(tpe = ForgeType.BitbucketServer),
       bitbucketServerCfg = BitbucketServerCfg(useDefaultReviewers = false)
     ),
     user
-  ).forgeApiAlg
+  )
 
   test("createPullRequest") {
     val data = NewPullRequestData(
@@ -146,13 +146,16 @@ class BitbucketServerApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff]
       base = main,
       labels = Nil
     )
-    val pr = new ForgeSelection[MockEff](
-      config.copy(
-        forgeCfg = config.forgeCfg.copy(tpe = ForgeType.BitbucketServer),
-        bitbucketServerCfg = BitbucketServerCfg(useDefaultReviewers = false)
-      ),
-      user
-    ).forgeApiAlg.createPullRequest(repo, data).runA(state)
+    val pr = ForgeSelection
+      .forgeApiAlg[MockEff](
+        config.copy(
+          forgeCfg = config.forgeCfg.copy(tpe = ForgeType.BitbucketServer),
+          bitbucketServerCfg = BitbucketServerCfg(useDefaultReviewers = false)
+        ),
+        user
+      )
+      .createPullRequest(repo, data)
+      .runA(state)
     val expected =
       PullRequestOut(
         html_url = uri"https://example.org/fthomas/base.g8/pullrequests/2",
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 17593562..cc6a72c4 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
@@ -124,10 +124,10 @@ class GitHubApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
   }
   private val state = MockState.empty.copy(clientResponses = auth <+> httpApp)
 
-  private val gitHubApiAlg = new ForgeSelection[MockEff](
+  private val gitHubApiAlg = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(forgeCfg = config.forgeCfg.copy(tpe = ForgeType.GitHub)),
     user
-  ).forgeApiAlg
+  )
 
   private val repo = Repo("fthomas", "base.g8")
 
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala
index db851d1b..384a6be8 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala
@@ -4,12 +4,14 @@ import cats.Id
 import munit.FunSuite
 import org.http4s.headers.{Accept, Authorization}
 import org.http4s.{BasicCredentials, Headers, MediaType, Request}
+import org.scalasteward.core.forge.ForgeSelection
+import org.scalasteward.core.forge.ForgeType.GitHub
 import org.scalasteward.core.forge.data.AuthenticatedUser
 
 class authenticationTest extends FunSuite {
   test("addCredentials") {
-    val request = authentication
-      .addCredentials[Id](AuthenticatedUser("user", "pass"))
+    val request = ForgeSelection
+      .authenticate[Id](GitHub, AuthenticatedUser("user", "pass"))
       .apply(Request(headers = Headers(Accept(MediaType.text.plain))))
 
     assertEquals(
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 3450d223..341f6544 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
@@ -112,29 +112,29 @@ class GitLabApiAlgTest extends CatsEffectSuite with Http4sDsl[MockEff] {
   }
   private val state = MockState.empty.copy(clientResponses = auth <+> httpApp)
 
-  private val gitlabApiAlg = new ForgeSelection[MockEff](
+  private val gitlabApiAlg = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(
       forgeCfg = config.forgeCfg.copy(tpe = ForgeType.GitLab),
       gitLabCfg = GitLabCfg(mergeWhenPipelineSucceeds = false, requiredReviewers = None)
     ),
     user
-  ).forgeApiAlg
+  )
 
-  private val gitlabApiAlgNoFork = new ForgeSelection[MockEff](
+  private val gitlabApiAlgNoFork = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(
       forgeCfg = config.forgeCfg.copy(tpe = ForgeType.GitLab, doNotFork = true),
       gitLabCfg = GitLabCfg(mergeWhenPipelineSucceeds = false, requiredReviewers = None)
     ),
     user
-  ).forgeApiAlg
+  )
 
-  private val gitlabApiAlgLessReviewersRequired = new ForgeSelection[MockEff](
+  private val gitlabApiAlgLessReviewersRequired = ForgeSelection.forgeApiAlg[MockEff](
     config.copy(
       forgeCfg = config.forgeCfg.copy(tpe = ForgeType.GitLab, doNotFork = true),
       gitLabCfg = GitLabCfg(mergeWhenPipelineSucceeds = true, requiredReviewers = Some(0))
     ),
     user
-  ).forgeApiAlg
+  )
 
   private val data = UpdateData(
     RepoData(Repo("foo", "bar"), dummyRepoCache, RepoConfig.empty),