Skip to content

Commit

Permalink
Support getting repository perms for a user (#528)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachkirlew authored Jul 16, 2020
1 parent 01206f8 commit df9ec5e
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 7 deletions.
2 changes: 2 additions & 0 deletions github4s/src/main/scala/github4s/Decoders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ object Decoders {
implicit val decoderAuthorization: Decoder[Authorization] = deriveDecoder[Authorization]
implicit val decoderOAuthToken: Decoder[OAuthToken] = deriveDecoder[OAuthToken]
implicit val decoderRelease: Decoder[Release] = deriveDecoder[Release]
implicit val decoderUserRepoPermission: Decoder[UserRepoPermission] =
deriveDecoder[UserRepoPermission]

implicit val decodeStargazer: Decoder[Stargazer] =
decoderUser
Expand Down
16 changes: 16 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,22 @@ trait Repositories[F[_]] {
headers: Map[String, String] = Map()
): F[GHResponse[List[User]]]

/**
* Get the repository permission of a collaborator
*
* @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 GHResponse with UserRepoPermission
*/
def getRepoPermissionForUser(
owner: String,
repo: String,
username: String,
headers: Map[String, String] = Map()
): F[GHResponse[UserRepoPermission]]

/**
* Get a single release
*
Expand Down
3 changes: 3 additions & 0 deletions github4s/src/main/scala/github4s/domain/Repository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ final case class Committer(
name: String,
email: String
)

final case class UserRepoPermission(permission: String, user: User)

object RepoUrlKeys {

val forks_url = "forks_url"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,20 @@ class RepositoriesInterpreter[F[_]](implicit client: HttpClient[F], accessToken:
pagination
)

override def getRepoPermissionForUser(
owner: String,
repo: String,
username: String,
headers: Map[String, String]
): F[GHResponse[UserRepoPermission]] =
client
.get[UserRepoPermission](
accessToken,
s"repos/$owner/$repo/collaborators/$username/permission",
headers,
Map.empty
)

override def latestRelease(
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 >> GetRepoPermissionForUser" should "return user repo permission" taggedAs Integration in {
val response = clientResource
.use { client =>
Github[IO](client, accessToken).repos
.getRepoPermissionForUser(
validRepoOwner,
validRepoName,
validUsername,
headers = headerUserAgent
)
}
.unsafeRunSync()

testIsRight[UserRepoPermission](response, r => r.user.login shouldBe validUsername)
response.statusCode shouldBe okStatusCode
}

it should "return error when invalid username is passed" taggedAs Integration in {
val response = clientResource
.use { client =>
Github[IO](client, accessToken).repos
.getRepoPermissionForUser(
validRepoOwner,
validRepoName,
invalidUsername,
headers = headerUserAgent
)
}
.unsafeRunSync()

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

"Repos >> GetStatus" should "return a combined status" taggedAs Integration in {
val response = clientResource
.use { client =>
Expand Down
4 changes: 4 additions & 0 deletions github4s/src/test/scala/github4s/unit/DecodersSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class DecodersSpec extends AnyFlatSpec with Matchers with FakeResponses {
decode[NonEmptyList[Int]]("[1,2,3]") shouldBe Right(NonEmptyList.of(1, 2, 3))
}

"UserRepoPermission decoder" should "return a UserRepoPermission" in {
decode[UserRepoPermission](getUserRepoPermissionResponse).isRight shouldBe true
}

case class Foo(a: Int)
it should "return a valid NonEmptyList for a valid JSON" in {
decode[NonEmptyList[Foo]]("""{"a": 1}""") shouldBe Right(NonEmptyList(Foo(1), Nil))
Expand Down
4 changes: 2 additions & 2 deletions github4s/src/test/scala/github4s/unit/IssuesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ class IssuesSpec extends BaseSpec {
"Issue.DeleteComment" should "call to httpClient.delete with the right parameters" in {

val response: IO[GHResponse[Unit]] =
IO(GHResponse(().asRight, deletedStatusCode, Map.empty))
IO(GHResponse(().asRight, noContentStatusCode, Map.empty))

implicit val httpClientMock = httpClientMockDelete(
url = s"repos/$validRepoOwner/$validRepoName/issues/comments/$validCommentId",
Expand Down Expand Up @@ -393,7 +393,7 @@ class IssuesSpec extends BaseSpec {
"Issue.DeleteMilestone" should "call to httpClient.delete with the right parameters" in {

val response: IO[GHResponse[Unit]] =
IO(GHResponse(().asRight, deletedStatusCode, Map.empty))
IO(GHResponse(().asRight, noContentStatusCode, Map.empty))

implicit val httpClientMock = httpClientMockDelete(
url = s"repos/$validRepoOwner/$validRepoName/milestones/$validMilestoneNumber",
Expand Down
23 changes: 21 additions & 2 deletions github4s/src/test/scala/github4s/unit/ReposSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

package github4s.unit

import cats.effect.IO
import cats.data.NonEmptyList
import cats.effect.IO
import cats.syntax.either._
import com.github.marklister.base64.Base64._
import github4s.GHResponse
import github4s.domain._
import github4s.interpreters.RepositoriesInterpreter
import github4s.utils.BaseSpec
import com.github.marklister.base64.Base64._

class ReposSpec extends BaseSpec {

Expand Down Expand Up @@ -342,6 +342,25 @@ class ReposSpec extends BaseSpec {
repos.listCollaborators(validRepoOwner, validRepoName, 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))

implicit val httpClientMock = httpClientMockGet[UserRepoPermission](
url = s"repos/$validRepoOwner/$validRepoName/collaborators/$validUsername/permission",
response = response
)

val repos = new RepositoriesInterpreter[IO]

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

"Repos.getCombinedStatus" should "call httpClient.get with the right parameters" in {
val response: IO[GHResponse[CombinedStatus]] =
IO(GHResponse(combinedStatus.asRight, okStatusCode, Map.empty))
Expand Down
27 changes: 27 additions & 0 deletions github4s/src/test/scala/github4s/utils/FakeResponses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -514,4 +514,31 @@ trait FakeResponses {
|}
""".stripMargin

val getUserRepoPermissionResponse =
s"""
|{
| "permission": "admin",
| "user": {
| "login": "octocat",
| "id": 1,
| "node_id": "MDQ6VXNlcjE=",
| "avatar_url": "https://github.com/images/error/octocat_happy.gif",
| "gravatar_id": "",
| "url": "https://api.github.com/users/octocat",
| "html_url": "https://github.com/octocat",
| "followers_url": "https://api.github.com/users/octocat/followers",
| "following_url": "https://api.github.com/users/octocat/following{/other_user}",
| "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
| "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
| "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
| "organizations_url": "https://api.github.com/users/octocat/orgs",
| "repos_url": "https://api.github.com/users/octocat/repos",
| "events_url": "https://api.github.com/users/octocat/events{/privacy}",
| "received_events_url": "https://api.github.com/users/octocat/received_events",
| "type": "User",
| "site_admin": false
| }
|}
|""".stripMargin

}
7 changes: 4 additions & 3 deletions github4s/src/test/scala/github4s/utils/TestData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ trait TestData {
val invalidUsername = "GHInvalidUserName"
val invalidPassword = "invalidPassword"

val githubApiUrl = "http://api.github.com"
val user = User(1, validUsername, githubApiUrl, githubApiUrl)
val githubApiUrl = "http://api.github.com"
val user = User(1, validUsername, githubApiUrl, githubApiUrl)
val userRepoPermission: UserRepoPermission = UserRepoPermission("admin", user)

def validBasicAuth = s"Basic ${s"$validUsername:".getBytes.toBase64}"

Expand Down Expand Up @@ -70,7 +71,7 @@ trait TestData {

val okStatusCode = 200
val createdStatusCode = 201
val deletedStatusCode = 204
val noContentStatusCode = 204
val unauthorizedStatusCode = 401
val notFoundStatusCode = 404

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)
- [Get repository permissions for a user](#get-repository-permissions-for-a-user)
- [Commits](#commits)
- [List commits on a repository](#list-commits-on-a-repository)
- [Contents](#contents)
Expand Down Expand Up @@ -181,6 +182,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.

### Get repository permissions for a user

Checks the repository permission of a collaborator.

The possible repository permissions are `admin`, `write`, `read`, and `none`.

```scala mdoc:compile-only
val userRepoPermission = gh.repos.getRepoPermissionForUser("47degrees", "github4s", "rafaparadela")
val response = userRepoPermission.unsafeRunSync()
response.result match {
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
case Right(r) => println(r)
}
```

The `result` on the right is the corresponding [UserRepoPermission][repository-scala].

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

## Commits

### List commits on a repository
Expand Down

0 comments on commit df9ec5e

Please sign in to comment.