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

3 issue api #44

Merged
merged 7 commits into from
May 26, 2015
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
62 changes: 54 additions & 8 deletions src/main/scala/codecheck/github/models/Issue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.json4s.JValue
import org.json4s.JString
import org.json4s.JNothing
import org.json4s.JNull
import org.json4s.JInt
import org.json4s.JArray
import org.json4s.JsonDSL._
import org.joda.time.DateTime
Expand Down Expand Up @@ -54,6 +55,18 @@ object IssueSort {
def fromString(str: String) = values.filter(_.name == str).head
}

sealed abstract class MilestoneSearchOption(val name: String) {
override def toString = name
}

object MilestoneSearchOption {
case object all extends MilestoneSearchOption("*")
case object none extends MilestoneSearchOption("none")
case class Specified(number: Int) extends MilestoneSearchOption(number.toString())

def apply(number: Int) = Specified(number)
}

case class IssueListOption(
filter: IssueFilter = IssueFilter.assigned,
state: IssueState = IssueState.open,
Expand All @@ -63,11 +76,31 @@ case class IssueListOption(
since: Option[DateTime] = None
) {
def q = s"?filter=$filter&state=$state&sort=$sort&direction=$direction" +
(if (labels.length == 0) "" else "&labels=" + labels.mkString(",")) +
since.map("&since=" + _.toString("yyyy-MM-dd'T'HH:mm:ssZ"))
(if (!labels.isEmpty) "&labels=" + labels.mkString(",") else "") +
(if (!since.isEmpty) (since map ("&since=" + _.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"))).get else "")
}

/*case*/ class IssueListOption4Repository extends ToDo
case class IssueListOption4Repository(
milestone: Option[MilestoneSearchOption] = None,
state: IssueState = IssueState.open,
assignee: Option[String] = None,
creator: Option[String] = None,
mentioned: Option[String] = None,
labels: Seq[String] = Nil,
sort: IssueSort = IssueSort.created,
direction: SortDirection = SortDirection.desc,
since: Option[DateTime] = None
) {
def q = "?" + (if (!milestone.isEmpty) (milestone map (t => s"milestone=$t&")).get else "") +
s"state=$state" +
(if (!assignee.isEmpty) (assignee map (t => s"&assignee=$t")).get else "") +
(if (!creator.isEmpty) (creator map (t => s"&creator=$t")).get else "") +
(if (!mentioned.isEmpty) (mentioned map (t => s"&mentioned=$t")).get else "") +
(if (!labels.isEmpty) "&labels=" + labels.mkString(",") else "") +
s"&sort=$sort" +
s"&direction=$direction" +
(if (!since.isEmpty) (since map ("&since=" + _.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"))).get else "")
}

case class IssueInput(
title: Option[String] = None,
Expand Down Expand Up @@ -96,23 +129,36 @@ object IssueInput {
def apply(title: String, body: Option[String], assignee: Option[String], milestone: Option[Int], labels: Seq[String]): IssueInput =
IssueInput(Some(title), body, assignee, milestone, labels, None)
}

case class Issue(value: JValue) extends AbstractJson(value) {
def url = get("url")
def labels_url = get("labels_url")
def comments_url = get("comments_url")
def events_url = get("events_url")
def html_url = get("html_url")
def id = get("id").toLong
def number = get("number").toLong
def title = get("title")
def body = opt("body")

lazy val assignee = objectOpt("assignee")(v => User(v))
lazy val milestone = objectOpt("milestone")(v => Milestone(v))

lazy val user = new User(value \ "user")
lazy val labels = (value \ "labels") match {
case JArray(arr) => arr.map(new Label(_))
case _ => Nil
}
lazy val repository = new Repository(value \ "repository")

def state = get("state")
def locked = boolean("locked")

lazy val assignee = objectOpt("assignee")(v => User(v))
lazy val milestone = objectOpt("milestone")(v => Milestone(v))

def comments = get("comments").toInt
def created_at = getDate("created_at")
def updated_at = getDate("updated_at")
def closed_at = dateOpt("closed_at")
def body = opt("body")

lazy val closed_by = objectOpt("closed_by")(v => User(v))

lazy val repository = new Repository(value \ "repository")
}
16 changes: 8 additions & 8 deletions src/main/scala/codecheck/github/operations/IssueOp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,33 @@ import codecheck.github.models.Issue
import codecheck.github.models.IssueListOption
import codecheck.github.models.IssueListOption4Repository

import codecheck.github.utils.ToDo

trait IssueOp {
self: GitHubAPI =>

private def doList(path: String): Future[List[Issue]] = {
exec("GET", path).map(
exec("GET", path).map(
_.body match {
case JArray(arr) => arr.map(v => Issue(v))
case _ => throw new IllegalStateException()
}
)
}

def listAllIssues(option: IssueListOption = IssueListOption()): Future[List[Issue]] =
//Only listAll/User/OrgIssues return Repository object
def listAllIssues(option: IssueListOption = IssueListOption()): Future[List[Issue]] =
doList("/issues" + option.q)

def listUserIssues(option: IssueListOption = IssueListOption()): Future[List[Issue]] =
def listUserIssues(option: IssueListOption = IssueListOption()): Future[List[Issue]] =
doList("/user/issues" + option.q)

def listOrgIssues(org: String, option: IssueListOption = IssueListOption()): Future[List[Issue]] =
doList(s"/orgs/$org/issues" + option.q)

def listRepositoryIssues(owner: String, repo: String, option: IssueListOption4Repository): Future[List[Issue]] = ToDo[Future[List[Issue]]]
def listRepositoryIssues(owner: String, repo: String, option: IssueListOption4Repository = IssueListOption4Repository()): Future[List[Issue]] =
doList(s"/repos/$owner/$repo/issues" + option.q)

def getIssue(owner: String, repo: String, number: Long): Future[Option[Issue]] =
exec("GET", s"/repos/$owner/$repo/issues/$number", fail404=false).map(res =>
def getIssue(owner: String, repo: String, number: Long): Future[Option[Issue]] =
exec("GET", s"/repos/$owner/$repo/issues/$number", fail404=false).map(res =>
res.statusCode match {
case 404 => None
case 200 => Some(Issue(res.body))
Expand Down
1 change: 0 additions & 1 deletion src/test/scala/Constants.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import com.ning.http.client.AsyncHttpClient
import codecheck.github.api.GitHubAPI
import scala.concurrent.duration._
Expand Down
222 changes: 214 additions & 8 deletions src/test/scala/IssueOpSpec.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,226 @@
import org.scalatest.FunSpec
import org.scalatest.BeforeAndAfterAll
import scala.concurrent.Await
import org.joda.time.DateTime
import org.joda.time.DateTimeZone

class IssueOpSpec extends FunSpec with Constants {
import codecheck.github.models.IssueListOption
import codecheck.github.models.IssueFilter
import codecheck.github.models.IssueListOption4Repository
import codecheck.github.models.IssueState
import codecheck.github.models.Issue
import codecheck.github.models.IssueInput
import codecheck.github.models.MilestoneSearchOption

import codecheck.github.models.MilestoneInput
import codecheck.github.models.MilestoneListOption
import codecheck.github.models.MilestoneState
import codecheck.github.models.Milestone

class IssueOpSpec extends FunSpec with Constants with BeforeAndAfterAll {

val number = 1
var nUser: Long = 0
var nOrg: Long = 0
var nTime: DateTime = DateTime.now().toDateTime(DateTimeZone.UTC)
val tRepo = repo + "2"

describe("assign operations") {
it("assign should succeed") {
val result = Await.result(api.assign(organization, repo, number, user), TIMEOUT)
showResponse(result)
assert(result.get("assignee.login") == user)
override def beforeAll() {
val userMilestones = Await.result(api.listMilestones(user, userRepo, MilestoneListOption(state=MilestoneState.all)), TIMEOUT)
userMilestones.foreach { m =>
Await.result(api.removeMilestone(user, userRepo, m.number), TIMEOUT)
}

val orgMilestones = Await.result(api.listMilestones(organization, tRepo, MilestoneListOption(state=MilestoneState.all)), TIMEOUT)
orgMilestones.foreach { m =>
Await.result(api.removeMilestone(organization, tRepo, m.number), TIMEOUT)
}

val nInput = new MilestoneInput(Some("test milestone"))
val nInput2 = new MilestoneInput(Some("test milestone 2"))

Await.result(api.createMilestone(user, userRepo, nInput), TIMEOUT)
Await.result(api.createMilestone(user, userRepo, nInput2), TIMEOUT)

Await.result(api.createMilestone(organization, tRepo, nInput), TIMEOUT)
Await.result(api.createMilestone(organization, tRepo, nInput2), TIMEOUT)
}

describe("createIssue(owner, repo, input)") {
val input = IssueInput(Some("test issue"), Some("testing"), Some(user), Some(1), Seq("question"))

it("should create issue for user's own repo.") {
val result = Await.result(api.createIssue(user, userRepo, input), TIMEOUT)
nUser = result.number
assert(result.url == "https://api.github.com/repos/" + user + "/" + userRepo + "/issues/" + nUser)
assert(result.labels_url == "https://api.github.com/repos/" + user + "/" + userRepo + "/issues/" + nUser + "/labels{/name}")
assert(result.comments_url == "https://api.github.com/repos/" + user + "/" + userRepo + "/issues/" + nUser + "/comments")
assert(result.events_url == "https://api.github.com/repos/" + user + "/" + userRepo + "/issues/" + nUser + "/events")
assert(result.html_url == "https://github.com/" + user + "/" + userRepo + "/issues/" + nUser)
assert(result.title == "test issue")
assert(result.user.login == user)
assert(result.labels.head.name == "question")
assert(result.state == "open")
assert(result.locked == false)
assert(result.assignee.get.login == user)
assert(result.milestone.get.number == 1)
assert(result.comments == 0)
assert(result.created_at.toDateTime(DateTimeZone.UTC).getMillis() - DateTime.now(DateTimeZone.UTC).getMillis() <= 5000)
assert(result.updated_at.toDateTime(DateTimeZone.UTC).getMillis() - DateTime.now(DateTimeZone.UTC).getMillis() <= 5000)
assert(result.closed_at.isEmpty)
assert(result.body.get == "testing")
assert(result.closed_by.isEmpty)
}

it("should create issue for organization's repo.") {
val result = Await.result(api.createIssue(organization, tRepo, input), TIMEOUT)
nOrg = result.number
assert(result.url == "https://api.github.com/repos/" + organization + "/" + tRepo + "/issues/" + nOrg)
assert(result.labels_url == "https://api.github.com/repos/" + organization + "/" + tRepo + "/issues/" + nOrg + "/labels{/name}")
assert(result.comments_url == "https://api.github.com/repos/" + organization + "/" + tRepo+ "/issues/" + nOrg + "/comments")
assert(result.events_url == "https://api.github.com/repos/" + organization + "/" + tRepo + "/issues/" + nOrg + "/events")
assert(result.html_url == "https://github.com/" + organization + "/" + tRepo + "/issues/" + nOrg)
assert(result.title == "test issue")
assert(result.user.login == user)
assert(result.labels.head.name == "question")
assert(result.state == "open")
assert(result.locked == false)
assert(result.assignee.get.login == user)
assert(result.milestone.get.number == 1)
assert(result.comments == 0)
assert(result.created_at.toDateTime(DateTimeZone.UTC).getMillis() - DateTime.now(DateTimeZone.UTC).getMillis() <= 5000)
assert(result.updated_at.toDateTime(DateTimeZone.UTC).getMillis() - DateTime.now(DateTimeZone.UTC).getMillis() <= 5000)
assert(result.body.get == "testing")
assert(result.closed_by.isEmpty)
}
}

describe("getIssue(owner, repo, number)") {
it("should return issue from user's own repo.") {
val result = Await.result(api.getIssue(user, userRepo, nUser), TIMEOUT)
assert(result.get.title == "test issue")
}

it("should return issue from organization's repo.") {
val result = Await.result(api.getIssue(organization, tRepo, nOrg), TIMEOUT)
assert(result.get.title == "test issue")
}
}

describe("unassign(owner, repo, number)") {
it("should succeed with valid inputs on issues in user's own repo.") {
val result = Await.result(api.unassign(user, userRepo, nUser), TIMEOUT)
assert(result.opt("assignee").isEmpty)
}

it("unassign should succeed") {
val result = Await.result(api.unassign(organization, repo, number), TIMEOUT)
it("should succeed with valid inputs on issues in organization's repo.") {
val result = Await.result(api.unassign(organization, tRepo, nOrg), TIMEOUT)
assert(result.opt("assignee").isEmpty)
}
}

describe("assign(owner, repo, number, assignee)") {
it("should succeed with valid inputs on issues in user's own repo.") {
val result = Await.result(api.assign(user, userRepo, nUser, user), TIMEOUT)
assert(result.get("assignee.login") == user)
}

it("should succeed with valid inputs on issues in organization's repo.") {
val result = Await.result(api.assign(organization, tRepo, nOrg, user), TIMEOUT)
assert(result.get("assignee.login") == user)
}
}

describe("listAllIssues(option)") {
it("shold return at least one issue.") {
val result = Await.result(api.listAllIssues(), TIMEOUT)
assert(result.length > 0)
}

it("shold return only two issues when using options.") {
val option = IssueListOption(IssueFilter.created, IssueState.open, Seq("question"), since=Some(nTime))
val result = Await.result(api.listAllIssues(option), TIMEOUT)
assert(result.length == 2)
assert(result.head.title == "test issue")
}
}

describe("listUserIssues(option)") {
it("shold return at least one issue.") {
val result = Await.result(api.listUserIssues(), TIMEOUT)
assert(result.length > 0)
}

it("shold return only one issues when using options.") {
val option = IssueListOption(IssueFilter.created, IssueState.open, Seq("question"), since=Some(nTime))
val result = Await.result(api.listUserIssues(option), TIMEOUT)
assert(result.length == 1)
assert(result.head.title == "test issue")
}
}

describe("listOrgIssues(org, option)") {
it("should return at least one issue.") {
val result = Await.result(api.listOrgIssues(organization), TIMEOUT)
assert(result.length > 0)
}

it("shold return only one issues when using options.") {
val option = IssueListOption(IssueFilter.created, IssueState.open, Seq("question"), since=Some(nTime))
val result = Await.result(api.listOrgIssues(organization, option), TIMEOUT)
assert(result.length == 1)
assert(result.head.title == "test issue")
}
}

describe("listRepositoryIssues(owner, repo, option)") {
it("should return at least one issue from user's own repo.") {
val result = Await.result(api.listRepositoryIssues(user, userRepo), TIMEOUT)
assert(result.length > 0)
}

it("should return at least one issue from organization's repo.") {
val result = Await.result(api.listRepositoryIssues(organization, tRepo), TIMEOUT)
assert(result.length > 0)
}

it("should return only one issue from user's own repo when using options.") {
val option = new IssueListOption4Repository(Some(MilestoneSearchOption(1)), IssueState.open, Some(user), Some(user), labels=Seq("question"), since=Some(nTime))
val result = Await.result(api.listRepositoryIssues(user, userRepo, option), TIMEOUT)
//showResponse(option.q)
assert(result.length == 1)
assert(result.head.title == "test issue")
}

it("should return only one issue from organization's repo when using options.") {
val option = new IssueListOption4Repository(Some(MilestoneSearchOption(1)), IssueState.open, Some(user), Some(user), labels=Seq("question"), since=Some(nTime))
val result = Await.result(api.listRepositoryIssues(organization, tRepo, option), TIMEOUT)
assert(result.length == 1)
assert(result.head.title == "test issue")
}
}

describe("editIssue(owner, repo, number, input)") {
val input = IssueInput(Some("test issue edited"), Some("testing again"), Some(user), Some(2), Seq("question", "bug"), Some(IssueState.closed))

it("should edit the issue in user's own repo.") {
val result = Await.result(api.editIssue(user, userRepo, nUser, input), TIMEOUT)
assert(result.title == "test issue edited")
assert(result.body.get == "testing again")
assert(result.milestone.get.number == 2)
assert(result.labels.head.name == "bug")
assert(result.state == "closed")
assert(result.updated_at.toDateTime(DateTimeZone.UTC).getMillis() - DateTime.now(DateTimeZone.UTC).getMillis() <= 5000)
}

it("should edit the issue in organization's repo.") {
val result = Await.result(api.editIssue(organization, tRepo, nOrg, input), TIMEOUT)
assert(result.title == "test issue edited")
assert(result.body.get == "testing again")
assert(result.milestone.get.number == 2)
assert(result.labels.head.name == "bug")
assert(result.state == "closed")
assert(result.updated_at.toDateTime(DateTimeZone.UTC).getMillis() - DateTime.now(DateTimeZone.UTC).getMillis() <= 5000)
}
}
}
Loading