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

Get repository permissions for a user #528

Merged
merged 6 commits into from
Jul 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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