Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check if user is a collaborator #530

Merged
merged 12 commits into from
Jul 23, 2020
20 changes: 20 additions & 0 deletions github4s/src/main/scala/github4s/algebras/Repositories.scala
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,26 @@ trait Repositories[F[_]] {
headers: Map[String, String] = Map()
): F[GHResponse[List[User]]]

/**
* Get whether a user is a repository collaborator
*
* For organization-owned repositories, the list of collaborators includes outside collaborators,
* organization members that are direct collaborators, organization members with access through team memberships,
* organization members with access through default organization permissions, and organization owners.
*
BenFradet marked this conversation as resolved.
Show resolved Hide resolved
* @param owner of the repo
* @param repo name of the repo
* @param username Github username
* @param headers optional user headers to include in the request
* @return a unit GHResponse
*/
def userIsCollaborator(
BenFradet marked this conversation as resolved.
Show resolved Hide resolved
owner: String,
repo: String,
username: String,
headers: Map[String, String] = Map()
): F[GHResponse[Unit]]

/**
* Get the repository permission of a collaborator
*
Expand Down
15 changes: 12 additions & 3 deletions github4s/src/main/scala/github4s/http/HttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ import cats.effect.Sync
import cats.instances.string._
import cats.syntax.either._
import cats.syntax.functor._
import github4s._
import github4s.GHError._
import github4s._
import github4s.domain.Pagination
import github4s.http.Http4sSyntax._
import io.circe.{Decoder, Encoder}
import org.http4s.{EntityDecoder, Request, Response, Status}
import org.http4s.client.Client
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.jsonOf
import org.http4s.client.Client
import org.http4s.{EntityDecoder, Request, Response, Status}

class HttpClient[F[_]: Sync](client: Client[F], val config: GithubConfig) {
import HttpClient._
Expand All @@ -52,6 +52,15 @@ class HttpClient[F[_]: Sync](client: Client[F], val config: GithubConfig) {
)
)

def getWithoutResponse(
accessToken: Option[String] = None,
url: String,
headers: Map[String, String] = Map.empty
): F[GHResponse[Unit]] =
run[Unit, Unit](
RequestBuilder(buildURL(url)).withHeaders(headers).withAuth(accessToken)
)

def patch[Req: Encoder, Res: Decoder](
accessToken: Option[String] = None,
method: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ class RepositoriesInterpreter[F[_]](implicit client: HttpClient[F], accessToken:
pagination
)

override def userIsCollaborator(
owner: String,
repo: String,
username: String,
headers: Map[String, String]
): F[GHResponse[Unit]] =
client.getWithoutResponse(
accessToken,
s"repos/$owner/$repo/collaborators/$username",
headers
)

override def getRepoPermissionForUser(
owner: String,
repo: String,
Expand Down
34 changes: 34 additions & 0 deletions github4s/src/test/scala/github4s/integration/ReposSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,40 @@ trait ReposSpec extends BaseIntegrationSpec {
response.statusCode shouldBe notFoundStatusCode
}

"Repos >> UserIsCollaborator" should "return an empty body with no content status code" taggedAs Integration in {
val response = clientResource
.use { client =>
Github[IO](client, accessToken).repos
.userIsCollaborator(
validRepoOwner,
validRepoName,
validUsername,
headers = headerUserAgent
)
}
.unsafeRunSync()

testIsRight[Unit](response, r => r should be(()))
response.statusCode shouldBe noContentStatusCode
}

it should "return a not found error when username is not a repo collaborator" taggedAs Integration in {
val response = clientResource
.use { client =>
Github[IO](client, accessToken).repos
.userIsCollaborator(
validRepoName,
zachkirlew marked this conversation as resolved.
Show resolved Hide resolved
validRepoName,
invalidUsername,
headers = headerUserAgent
)
}
.unsafeRunSync()

testIsLeft[NotFoundError, Unit](response)
response.statusCode shouldBe notFoundStatusCode
}

"Repos >> GetRepoPermissionForUser" should "return user repo permission" taggedAs Integration in {
val response = clientResource
.use { client =>
Expand Down
19 changes: 19 additions & 0 deletions github4s/src/test/scala/github4s/unit/ReposSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,25 @@ class ReposSpec extends BaseSpec {
repos.listCollaborators(validRepoOwner, validRepoName, headers = headerUserAgent)
}

"Repos.userIsCollaborator" should "call to httpClient.getWithoutResponse with the right parameters" in {
val response: IO[GHResponse[Unit]] =
IO(GHResponse(().asRight, okStatusCode, Map.empty))

implicit val httpClientMock = httpClientMockGetWithoutResponse(
url = s"repos/$validRepoOwner/$validRepoName/collaborators/$validUsername",
response = response
)

val repos = new RepositoriesInterpreter[IO]

repos.userIsCollaborator(
validRepoOwner,
validRepoName,
validUsername,
headers = headerUserAgent
)
}

"Repos.getRepoPermissionForUser" should "call to httpClient.get with the right parameters" in {
val response: IO[GHResponse[UserRepoPermission]] =
IO(GHResponse(userRepoPermission.asRight, okStatusCode, Map.empty))
Expand Down
15 changes: 13 additions & 2 deletions github4s/src/test/scala/github4s/utils/BaseSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@
package github4s.utils

import cats.effect.IO
import github4s.GithubConfig
import github4s.GHResponse
import github4s.domain.Pagination
import github4s.http.HttpClient
import github4s.{GHResponse, GithubConfig}
import io.circe.{Decoder, Encoder}
import org.http4s.client.Client
import org.scalamock.scalatest.MockFactory
Expand Down Expand Up @@ -61,6 +60,18 @@ trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory
httpClientMock
}

def httpClientMockGetWithoutResponse(
url: String,
response: IO[GHResponse[Unit]]
): HttpClient[IO] = {
val httpClientMock = mock[HttpClientTest]
(httpClientMock
.getWithoutResponse(_: Option[String], _: String, _: Map[String, String]))
.expects(sampleToken, url, headerUserAgent)
.returns(response)
httpClientMock
}

def httpClientMockPost[In, Out](
url: String,
req: In,
Expand Down
21 changes: 21 additions & 0 deletions microsite/docs/repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ with Github4s, you can interact with:
- [List user repositories](#list-user-repositories)
- [List contributors](#list-contributors)
- [List collaborators](#list-collaborators)
- [Check user is a repository collaborator](#check-if-a-user-is-a-repository-collaborator)
zachkirlew marked this conversation as resolved.
Show resolved Hide resolved
- [Get repository permissions for a user](#get-repository-permissions-for-a-user)
- [Commits](#commits)
- [List commits on a repository](#list-commits-on-a-repository)
Expand Down Expand Up @@ -182,6 +183,26 @@ The `result` on the right is the corresponding [List[User]][user-scala].
See [the API doc](https://developer.github.com/v3/repos/collaborators/#list-collaborators) for full
reference.

### Check if a user is a repository collaborator

Response `statusCode` when user is a collaborator is 204 (No Content)

Response `statusCode` when user is not a collaborator is 404 (Not Found)

```scala mdoc:compile-only
val userIsCollaborator = gh.repos.userIsCollaborator("47degrees", "github4s", "rafaparadela")
val response = userIsCollaborator.unsafeRunSync()
response.statusCode match {
case 204 => println("User is a collaborator")
case 404 => println("User is not a collaborator")
}
```

The `result` on the right is `Unit`

See [the API doc](https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-repository-collaborator)
for full reference.

### Get repository permissions for a user

Checks the repository permission of a collaborator.
Expand Down