From c5160520f7c0b0e029f334e7e0d8a14876c92880 Mon Sep 17 00:00:00 2001 From: sukeshni Date: Fri, 22 May 2015 02:02:19 +0530 Subject: [PATCH 01/10] #36 Implemented searchRepositories v1 --- .../codecheck/github/api/GitHubAPI.scala | 1 + .../codecheck/github/models/Search.scala | 25 +++++++++++++++++++ .../github/operations/SearchOp.scala | 22 ++++++++++++++++ src/test/scala/SearchOpSpec.scala | 21 ++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 src/main/scala/codecheck/github/models/Search.scala create mode 100644 src/main/scala/codecheck/github/operations/SearchOp.scala create mode 100644 src/test/scala/SearchOpSpec.scala diff --git a/src/main/scala/codecheck/github/api/GitHubAPI.scala b/src/main/scala/codecheck/github/api/GitHubAPI.scala index 7117afb..0fc1f37 100644 --- a/src/main/scala/codecheck/github/api/GitHubAPI.scala +++ b/src/main/scala/codecheck/github/api/GitHubAPI.scala @@ -28,6 +28,7 @@ class GitHubAPI(token: String, client: Transport, tokenType: String = "token", d with WebhookOp with CollaboratorOp with BranchOp + with SearchOp { private val endpoint = "https://api.github.com" diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala new file mode 100644 index 0000000..1484e03 --- /dev/null +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -0,0 +1,25 @@ +package codecheck.github.models + +import org.json4s.JValue +import codecheck.github.models.SortDirection + +sealed abstract class SearchSort(val name: String) { + override def toString = name +} +object SearchSort { + case object stars extends SearchSort("stars") + case object forks extends SearchSort("forks") + case object updated extends SearchSort("updated") +} + +case class SearchInput ( + q: String, + sort: Option[SearchSort] = None, + order: SortDirection = SortDirection.desc +) extends AbstractInput + +case class SearchRepositoryResult(value: JValue) extends AbstractJson(value) { + def total_count: Long = get("total_count").toLong + def incomplete_results: Boolean = boolean("incomplete_results") + lazy val items = Repository(value \ "items") +} diff --git a/src/main/scala/codecheck/github/operations/SearchOp.scala b/src/main/scala/codecheck/github/operations/SearchOp.scala new file mode 100644 index 0000000..4689015 --- /dev/null +++ b/src/main/scala/codecheck/github/operations/SearchOp.scala @@ -0,0 +1,22 @@ +package codecheck.github.operations + +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global + +import codecheck.github.api.GitHubAPI +import codecheck.github.models.SearchInput +import codecheck.github.models.SearchRepositoryResult + +trait SearchOp { + self: GitHubAPI => + + def searchRepositories(input: SearchInput): Future[Option[SearchRepositoryResult]] = { + val path = s"/search/repositories?q=${input.q}&sort=${input.sort}&order=${input.order}" + exec("GET", path ).map { res => + res.statusCode match { + case 200 => Some(SearchRepositoryResult(res.body)) + case 404 => None + } + } + } +} diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala new file mode 100644 index 0000000..d4dac55 --- /dev/null +++ b/src/test/scala/SearchOpSpec.scala @@ -0,0 +1,21 @@ +import org.scalatest.path.FunSpec +import scala.concurrent.Await +import scala.concurrent.ExecutionContext.Implicits.global +import codecheck.github.models.SortDirection +import codecheck.github.models.SearchInput +import codecheck.github.models.SearchSort +import codecheck.github.models.SearchRepositoryResult + +class SearchOpSpec extends FunSpec + with Constants +{ + val input = SearchInput("tetris",sort=Some(SearchSort.stars),order=SortDirection.desc) + describe("searchRepositories") { + it("with valid SearchInput should succeed") { + Await.result(api.searchRepositories(input), TIMEOUT).map { res => + assert(res.total_count >= 1) + println("RESULT" + res) + } + } + } +} From 4a3767148f45863ade060a08308f1e56abc210ae Mon Sep 17 00:00:00 2001 From: sukeshni Date: Fri, 22 May 2015 12:37:00 +0530 Subject: [PATCH 02/10] Parsing input Query q,Test for searchRepositories --- .../codecheck/github/models/Search.scala | 6 ++++- src/test/scala/SearchOpSpec.scala | 26 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala index 1484e03..78b4589 100644 --- a/src/main/scala/codecheck/github/models/Search.scala +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -1,6 +1,7 @@ package codecheck.github.models import org.json4s.JValue +import org.json4s.JArray import codecheck.github.models.SortDirection sealed abstract class SearchSort(val name: String) { @@ -21,5 +22,8 @@ case class SearchInput ( case class SearchRepositoryResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") - lazy val items = Repository(value \ "items") + lazy val items = (value \ "items") match { + case JArray(arr) => arr.map(new Repository(_)) + case _ => Nil + } } diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index d4dac55..966c248 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -9,11 +9,33 @@ import codecheck.github.models.SearchRepositoryResult class SearchOpSpec extends FunSpec with Constants { - val input = SearchInput("tetris",sort=Some(SearchSort.stars),order=SortDirection.desc) - describe("searchRepositories") { + + describe("searchRepositories") { it("with valid SearchInput should succeed") { + var q = "tetris language:assembly" + val q1 = q.trim.replaceAll(" ","+"); + val input = SearchInput(q1,sort=Some(SearchSort.stars),order=SortDirection.desc) + Await.result(api.searchRepositories(input), TIMEOUT).map { res => + assert(res.total_count >= 1) + assert(res.items(0).id >= 1 ) + assert(res.items(0).name.length >= 1) + assert(res.items(0).full_name.length >= 1) + assert(res.items(0).description.isDefined) + assert(res.items(0).open_issues_count >= 0) + println("RESULT" + res) + } + } + it("with valid changed query(q) SearchInput should succeed") { + var q = "jquery in:name,description" + val q1 = q.trim.replaceAll(" ","+"); + val input = SearchInput(q1,sort=Some(SearchSort.stars),order=SortDirection.desc) Await.result(api.searchRepositories(input), TIMEOUT).map { res => assert(res.total_count >= 1) + assert(res.items(0).id >= 1 ) + assert(res.items(0).name.length >= 1) + assert(res.items(0).full_name.length >= 1) + assert(res.items(0).description.isDefined) + assert(res.items(0).open_issues_count >= 0) println("RESULT" + res) } } From 37b5cb1c9d1752d2e3f4ae1a370e56e5ceca40ef Mon Sep 17 00:00:00 2001 From: sukeshni Date: Mon, 25 May 2015 10:05:21 +0530 Subject: [PATCH 03/10] searchCode, searchIssues, searchUser --- .../codecheck/github/models/Search.scala | 53 +++++++++++++++++-- .../github/operations/SearchOp.scala | 34 ++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala index 78b4589..3ae2f05 100644 --- a/src/main/scala/codecheck/github/models/Search.scala +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -2,15 +2,28 @@ package codecheck.github.models import org.json4s.JValue import org.json4s.JArray -import codecheck.github.models.SortDirection sealed abstract class SearchSort(val name: String) { override def toString = name } object SearchSort { - case object stars extends SearchSort("stars") - case object forks extends SearchSort("forks") - case object updated extends SearchSort("updated") + //for serachRepositories + case object stars extends SearchSort("stars") + case object forks extends SearchSort("forks") + case object updated extends SearchSort("updated") + + //for searchCode + case object indexed extends SearchSort("indexed") + + //for searchIssues + case object comments extends SearchSort("comments") + case object created extends SearchSort("created") + //case object updated extends SearchSort("updated") + + //for searchUser + case object followers extends SearchSort("followers") + case object repositories extends SearchSort("repositories") + case object joined extends SearchSort("joined") } case class SearchInput ( @@ -27,3 +40,35 @@ case class SearchRepositoryResult(value: JValue) extends AbstractJson(value) { case _ => Nil } } + +case class searchCodeItems (value: JValue) extends AbstractJson(value){ + def name: String = get("name") + lazy val Repo = new Repository(value \ "repository") +} + +case class SearchCodeResult(value: JValue) extends AbstractJson(value) { + def total_count: Long = get("total_count").toLong + def incomplete_results: Boolean = boolean("incomplete_results") + lazy val items = (value \ "items") match { + case JArray(arr) => arr.map(new searchCodeItems(_)) + case _ => Nil + } +} + +case class SearchIssueResult(value: JValue) extends AbstractJson(value) { + def total_count: Long = get("total_count").toLong + def incomplete_results: Boolean = boolean("incomplete_results") + lazy val items = (value \ "items") match { + case JArray(arr) => arr.map(new Issue(_)) + case _ => Nil + } +} + +case class SearchUserResult(value: JValue) extends AbstractJson(value) { + def total_count: Long = get("total_count").toLong + def incomplete_results: Boolean = boolean("incomplete_results") + lazy val items = (value \ "items") match { + case JArray(arr) => arr.map(new User(_)) + case _ => Nil + } +} diff --git a/src/main/scala/codecheck/github/operations/SearchOp.scala b/src/main/scala/codecheck/github/operations/SearchOp.scala index 4689015..87b0bde 100644 --- a/src/main/scala/codecheck/github/operations/SearchOp.scala +++ b/src/main/scala/codecheck/github/operations/SearchOp.scala @@ -6,6 +6,10 @@ import scala.concurrent.ExecutionContext.Implicits.global import codecheck.github.api.GitHubAPI import codecheck.github.models.SearchInput import codecheck.github.models.SearchRepositoryResult +//import codecheck.github.models.SortDirection +import codecheck.github.models.SearchCodeResult +import codecheck.github.models.SearchIssueResult +import codecheck.github.models.SearchUserResult trait SearchOp { self: GitHubAPI => @@ -19,4 +23,34 @@ trait SearchOp { } } } + + def searchCode(input: SearchInput): Future[Option[SearchCodeResult]] = { + val path = s"/search/code?q=${input.q}&sort=${input.sort}&order=${input.order}" + exec("GET", path ).map { res => + res.statusCode match { + case 200 => Some(SearchCodeResult(res.body)) + case 404 => None + } + } + } + + def searchIssues(input: SearchInput): Future[Option[SearchIssueResult]] = { + val path = s"/search/issues?q=${input.q}&sort=${input.sort}&order=${input.order}" + exec("GET", path ).map { res => + res.statusCode match { + case 200 => Some(SearchIssueResult(res.body)) + case 404 => None + } + } + } + + def searchUser(input: SearchInput): Future[Option[SearchUserResult]] = { + val path = s"/search/user?q=${input.q}&sort=${input.sort}&order=${input.order}" + exec("GET", path ).map { res => + res.statusCode match { + case 200 => Some(SearchUserResult(res.body)) + case 404 => None + } + } + } } From 4b91b99c39ab9f1e13e6d1a819d5ae2702dd7d05 Mon Sep 17 00:00:00 2001 From: sukeshni Date: Mon, 25 May 2015 15:42:56 +0530 Subject: [PATCH 04/10] Tests for searchCode,searchIssues,searchUser --- .../codecheck/github/models/Repository.scala | 4 +- .../github/operations/SearchOp.scala | 2 +- src/test/scala/SearchOpSpec.scala | 61 ++++++++++++++++++- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Repository.scala b/src/main/scala/codecheck/github/models/Repository.scala index 6ec3bc4..cb5351d 100644 --- a/src/main/scala/codecheck/github/models/Repository.scala +++ b/src/main/scala/codecheck/github/models/Repository.scala @@ -66,9 +66,11 @@ case class Repository(value: JValue) extends AbstractJson(value) { def name = get("name") def full_name = get("full_name") def url = get("url") + def language = get("language") + def stargazers_count = get("stargazers_count").toLong def description = opt("description") - def open_issues_count = get("open_issues_count").toInt + def open_issues_count = get("open_issues_count").toLong lazy val permissions = Permissions(value \ "permissions") lazy val owner = User(value \ "owner") diff --git a/src/main/scala/codecheck/github/operations/SearchOp.scala b/src/main/scala/codecheck/github/operations/SearchOp.scala index 87b0bde..e8ad461 100644 --- a/src/main/scala/codecheck/github/operations/SearchOp.scala +++ b/src/main/scala/codecheck/github/operations/SearchOp.scala @@ -45,7 +45,7 @@ trait SearchOp { } def searchUser(input: SearchInput): Future[Option[SearchUserResult]] = { - val path = s"/search/user?q=${input.q}&sort=${input.sort}&order=${input.order}" + val path = s"/search/users?q=${input.q}&sort=${input.sort}&order=${input.order}" exec("GET", path ).map { res => res.statusCode match { case 200 => Some(SearchUserResult(res.body)) diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index 966c248..28850e6 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -5,6 +5,9 @@ import codecheck.github.models.SortDirection import codecheck.github.models.SearchInput import codecheck.github.models.SearchSort import codecheck.github.models.SearchRepositoryResult +import codecheck.github.models.SearchCodeResult +import codecheck.github.models.searchCodeItems +import codecheck.github.exceptions.GitHubAPIException class SearchOpSpec extends FunSpec with Constants @@ -22,7 +25,8 @@ class SearchOpSpec extends FunSpec assert(res.items(0).full_name.length >= 1) assert(res.items(0).description.isDefined) assert(res.items(0).open_issues_count >= 0) - println("RESULT" + res) + assert(res.items(0).language == "Assembly") + assert(res.items(0).stargazers_count > res.items(1).stargazers_count) } } it("with valid changed query(q) SearchInput should succeed") { @@ -36,7 +40,60 @@ class SearchOpSpec extends FunSpec assert(res.items(0).full_name.length >= 1) assert(res.items(0).description.isDefined) assert(res.items(0).open_issues_count >= 0) - println("RESULT" + res) + } + } + } + describe("searchCode") { + it("with valid SearchInput q,no SortOrder should succeed") { + var q = "addClass in:file language:js repo:jquery/jquery" + val q1 = q.trim.replaceAll(" ","+"); + val input = SearchInput(q1,sort=None,order=SortDirection.desc) + Await.result(api.searchCode(input), TIMEOUT).map { res => + assert(res.total_count >= 1) + assert(res.items(0).Repo.id >= 1 ) + assert(res.items(0).Repo.full_name == "jquery/jquery") + } + } + //Following test results in error: + // "message" : "Validation Failed", + // "errors" : [ { + // "message" : "Must include at least one user, organization, or repository" + it("with valid SearchInput it should succeed") { + var q = "function size:10000 language:python" + val q1 = q.trim.replaceAll(" ","+"); + val input = SearchInput(q1,sort=Some(SearchSort.indexed),order=SortDirection.desc) + try { + val res = Await.result(api.searchCode(input), TIMEOUT) + } catch { + case e: GitHubAPIException => + assert(e.error.errors.length == 1) + assert(e.error.message == "Validation Failed") + } + } + } + describe("searchIssues") { + it("with valid SearchInput should succeed") { + var q = "windows label:bug language:python state:open" + val q1 = q.trim.replaceAll(" ","+"); + val input = SearchInput(q1,sort=Some(SearchSort.created),order=SortDirection.desc) + val res = Await.result(api.searchIssues(input), TIMEOUT) + assert(res.total_count >= 1) + assert(res.items(0).labels(0).name == "bug" ) + assert(res.items(0).state == "open") + assert(((res.items(0).created_at).compareTo(res.items(1).created_at)) > 0) + } + } + describe("searchUser") { + it("with valid SearchInput should succeed") { + var q = "tom repos:>42 followers:>1000" + q = q.trim.replaceAll(" ","+") + val q1 = q.replaceAll(">","%3E") + println("QUERY: searchUser" + q1) + val input = SearchInput(q1,sort=None,order=SortDirection.desc) + Await.result(api.searchUser(input), TIMEOUT).map { res => + assert(res.total_count >= 1) + assert(res.items(0).login.length >= 1) + assert(res.items(0).id >= 1) } } } From 2d39a09eb6cd0389195ec3a4a7dbcf8299319b10 Mon Sep 17 00:00:00 2001 From: sukeshni Date: Mon, 25 May 2015 16:10:31 +0530 Subject: [PATCH 05/10] Refactoring --- .../github/operations/SearchOp.scala | 29 +++------- src/test/scala/SearchOpSpec.scala | 53 +++++++++---------- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/src/main/scala/codecheck/github/operations/SearchOp.scala b/src/main/scala/codecheck/github/operations/SearchOp.scala index e8ad461..8519a69 100644 --- a/src/main/scala/codecheck/github/operations/SearchOp.scala +++ b/src/main/scala/codecheck/github/operations/SearchOp.scala @@ -6,7 +6,6 @@ import scala.concurrent.ExecutionContext.Implicits.global import codecheck.github.api.GitHubAPI import codecheck.github.models.SearchInput import codecheck.github.models.SearchRepositoryResult -//import codecheck.github.models.SortDirection import codecheck.github.models.SearchCodeResult import codecheck.github.models.SearchIssueResult import codecheck.github.models.SearchUserResult @@ -14,43 +13,31 @@ import codecheck.github.models.SearchUserResult trait SearchOp { self: GitHubAPI => - def searchRepositories(input: SearchInput): Future[Option[SearchRepositoryResult]] = { + def searchRepositories(input: SearchInput): Future[SearchRepositoryResult] = { val path = s"/search/repositories?q=${input.q}&sort=${input.sort}&order=${input.order}" exec("GET", path ).map { res => - res.statusCode match { - case 200 => Some(SearchRepositoryResult(res.body)) - case 404 => None - } + SearchRepositoryResult(res.body) } } - def searchCode(input: SearchInput): Future[Option[SearchCodeResult]] = { + def searchCode(input: SearchInput): Future[SearchCodeResult] = { val path = s"/search/code?q=${input.q}&sort=${input.sort}&order=${input.order}" exec("GET", path ).map { res => - res.statusCode match { - case 200 => Some(SearchCodeResult(res.body)) - case 404 => None - } + SearchCodeResult(res.body) } } - def searchIssues(input: SearchInput): Future[Option[SearchIssueResult]] = { + def searchIssues(input: SearchInput): Future[SearchIssueResult] = { val path = s"/search/issues?q=${input.q}&sort=${input.sort}&order=${input.order}" exec("GET", path ).map { res => - res.statusCode match { - case 200 => Some(SearchIssueResult(res.body)) - case 404 => None - } + SearchIssueResult(res.body) } } - def searchUser(input: SearchInput): Future[Option[SearchUserResult]] = { + def searchUser(input: SearchInput): Future[SearchUserResult] = { val path = s"/search/users?q=${input.q}&sort=${input.sort}&order=${input.order}" exec("GET", path ).map { res => - res.statusCode match { - case 200 => Some(SearchUserResult(res.body)) - case 404 => None - } + SearchUserResult(res.body) } } } diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index 28850e6..d6360d7 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -18,29 +18,27 @@ class SearchOpSpec extends FunSpec var q = "tetris language:assembly" val q1 = q.trim.replaceAll(" ","+"); val input = SearchInput(q1,sort=Some(SearchSort.stars),order=SortDirection.desc) - Await.result(api.searchRepositories(input), TIMEOUT).map { res => - assert(res.total_count >= 1) - assert(res.items(0).id >= 1 ) - assert(res.items(0).name.length >= 1) - assert(res.items(0).full_name.length >= 1) - assert(res.items(0).description.isDefined) - assert(res.items(0).open_issues_count >= 0) - assert(res.items(0).language == "Assembly") - assert(res.items(0).stargazers_count > res.items(1).stargazers_count) - } + val res = Await.result(api.searchRepositories(input), TIMEOUT) + assert(res.total_count >= 1) + assert(res.items(0).id >= 1 ) + assert(res.items(0).name.length >= 1) + assert(res.items(0).full_name.length >= 1) + assert(res.items(0).description.isDefined) + assert(res.items(0).open_issues_count >= 0) + assert(res.items(0).language == "Assembly") + assert(res.items(0).stargazers_count > res.items(1).stargazers_count) } it("with valid changed query(q) SearchInput should succeed") { var q = "jquery in:name,description" val q1 = q.trim.replaceAll(" ","+"); val input = SearchInput(q1,sort=Some(SearchSort.stars),order=SortDirection.desc) - Await.result(api.searchRepositories(input), TIMEOUT).map { res => - assert(res.total_count >= 1) - assert(res.items(0).id >= 1 ) - assert(res.items(0).name.length >= 1) - assert(res.items(0).full_name.length >= 1) - assert(res.items(0).description.isDefined) - assert(res.items(0).open_issues_count >= 0) - } + val res = Await.result(api.searchRepositories(input), TIMEOUT) + assert(res.total_count >= 1) + assert(res.items(0).id >= 1 ) + assert(res.items(0).name.length >= 1) + assert(res.items(0).full_name.length >= 1) + assert(res.items(0).description.isDefined) + assert(res.items(0).open_issues_count >= 0) } } describe("searchCode") { @@ -48,11 +46,10 @@ class SearchOpSpec extends FunSpec var q = "addClass in:file language:js repo:jquery/jquery" val q1 = q.trim.replaceAll(" ","+"); val input = SearchInput(q1,sort=None,order=SortDirection.desc) - Await.result(api.searchCode(input), TIMEOUT).map { res => - assert(res.total_count >= 1) - assert(res.items(0).Repo.id >= 1 ) - assert(res.items(0).Repo.full_name == "jquery/jquery") - } + val res = Await.result(api.searchCode(input), TIMEOUT) + assert(res.total_count >= 1) + assert(res.items(0).Repo.id >= 1 ) + assert(res.items(0).Repo.full_name == "jquery/jquery") } //Following test results in error: // "message" : "Validation Failed", @@ -88,13 +85,11 @@ class SearchOpSpec extends FunSpec var q = "tom repos:>42 followers:>1000" q = q.trim.replaceAll(" ","+") val q1 = q.replaceAll(">","%3E") - println("QUERY: searchUser" + q1) val input = SearchInput(q1,sort=None,order=SortDirection.desc) - Await.result(api.searchUser(input), TIMEOUT).map { res => - assert(res.total_count >= 1) - assert(res.items(0).login.length >= 1) - assert(res.items(0).id >= 1) - } + val res = Await.result(api.searchUser(input), TIMEOUT) + assert(res.total_count >= 1) + assert(res.items(0).login.length >= 1) + assert(res.items(0).id >= 1) } } } From 725803fecce83d489095f830003ba00c0e86ed9b Mon Sep 17 00:00:00 2001 From: sukeshni Date: Tue, 26 May 2015 10:55:09 +0530 Subject: [PATCH 06/10] Assert condition changed for searchUser --- src/test/scala/SearchOpSpec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index d6360d7..65f6ece 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -87,9 +87,9 @@ class SearchOpSpec extends FunSpec val q1 = q.replaceAll(">","%3E") val input = SearchInput(q1,sort=None,order=SortDirection.desc) val res = Await.result(api.searchUser(input), TIMEOUT) - assert(res.total_count >= 1) - assert(res.items(0).login.length >= 1) - assert(res.items(0).id >= 1) + assert(res.total_count >= 0) + assert(res.items(0).login.length >= 0) + assert(res.items(0).id >= 0) } } } From d8ab8ddb879184252f0891f6bb900c4935179da7 Mon Sep 17 00:00:00 2001 From: "Aaron S. Hawley" Date: Thu, 19 Jan 2017 19:46:53 -0500 Subject: [PATCH 07/10] Separate case classes for search sorts --- .../codecheck/github/models/Search.scala | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala index 3ae2f05..b700219 100644 --- a/src/main/scala/codecheck/github/models/Search.scala +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -6,24 +6,44 @@ import org.json4s.JArray sealed abstract class SearchSort(val name: String) { override def toString = name } -object SearchSort { - //for serachRepositories + +object SearchRepositorySort { case object stars extends SearchSort("stars") case object forks extends SearchSort("forks") case object updated extends SearchSort("updated") - //for searchCode + val values = Array(stars, forks, updated) + + def fromString(str: String) = values.filter(_.name == str).head +} + +object SearchCodeSort { case object indexed extends SearchSort("indexed") - //for searchIssues - case object comments extends SearchSort("comments") - case object created extends SearchSort("created") - //case object updated extends SearchSort("updated") + val values = Array(indexed) + + def fromString(str: String) = values.filter(_.name == str).head +} + +object SearchIssueSort { + case object created extends IssueSort("created") + case object updated extends IssueSort("updated") + case object comments extends IssueSort("comments") - //for searchUser + val values = Array(created, updated, comments) + + def fromString(str: String) = values.filter(_.name == str).head +} + + +object SearchUserSort { case object followers extends SearchSort("followers") case object repositories extends SearchSort("repositories") case object joined extends SearchSort("joined") + + val values = Array(followers, repositories, joined) + + def fromString(str: String) = values.filter(_.name == str).head } case class SearchInput ( From cbbc8b6b798e87b56176fab92fd959785887e50b Mon Sep 17 00:00:00 2001 From: "Aaron S. Hawley" Date: Thu, 19 Jan 2017 21:30:53 -0500 Subject: [PATCH 08/10] Clean up sorting and query string for search API --- .../codecheck/github/models/Search.scala | 67 ++++++++++++++----- .../github/operations/SearchOp.scala | 8 +-- src/test/scala/SearchOpSpec.scala | 22 +++--- 3 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala index b700219..9c06596 100644 --- a/src/main/scala/codecheck/github/models/Search.scala +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -3,64 +3,85 @@ package codecheck.github.models import org.json4s.JValue import org.json4s.JArray -sealed abstract class SearchSort(val name: String) { +sealed trait SearchSort { + def name: String override def toString = name } +sealed abstract class SearchRepositorySort(val name: String) extends SearchSort + object SearchRepositorySort { - case object stars extends SearchSort("stars") - case object forks extends SearchSort("forks") - case object updated extends SearchSort("updated") + case object stars extends SearchRepositorySort("stars") + case object forks extends SearchRepositorySort("forks") + case object updated extends SearchRepositorySort("updated") val values = Array(stars, forks, updated) def fromString(str: String) = values.filter(_.name == str).head } +sealed abstract class SearchCodeSort(val name: String) extends SearchSort + object SearchCodeSort { - case object indexed extends SearchSort("indexed") + case object indexed extends SearchCodeSort("indexed") val values = Array(indexed) def fromString(str: String) = values.filter(_.name == str).head } +sealed abstract class SearchIssueSort(val name: String) extends SearchSort + object SearchIssueSort { - case object created extends IssueSort("created") - case object updated extends IssueSort("updated") - case object comments extends IssueSort("comments") + case object created extends SearchIssueSort("created") + case object updated extends SearchIssueSort("updated") + case object comments extends SearchIssueSort("comments") val values = Array(created, updated, comments) def fromString(str: String) = values.filter(_.name == str).head } +sealed abstract class SearchUserSort(val name: String) extends SearchSort object SearchUserSort { - case object followers extends SearchSort("followers") - case object repositories extends SearchSort("repositories") - case object joined extends SearchSort("joined") + case object followers extends SearchUserSort("followers") + case object repositories extends SearchUserSort("repositories") + case object joined extends SearchUserSort("joined") val values = Array(followers, repositories, joined) def fromString(str: String) = values.filter(_.name == str).head } -case class SearchInput ( - q: String, - sort: Option[SearchSort] = None, - order: SortDirection = SortDirection.desc -) extends AbstractInput +sealed trait SearchInput extends AbstractInput { + def q: String + def sort: Option[SearchSort] + def order: SortDirection + def query = s"?q=$q" + sort.map(sortBy => s"&sort=$sortBy&order=$order").getOrElse("") +} + +case class SearchRepositoryInput ( + val q: String, + val sort: Option[SearchRepositorySort] = None, + val order: SortDirection = SortDirection.desc +) extends SearchInput case class SearchRepositoryResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") lazy val items = (value \ "items") match { - case JArray(arr) => arr.map(new Repository(_)) + case JArray(arr) => arr.map(Repository(_)) case _ => Nil } } +case class SearchCodeInput ( + q: String, + sort: Option[SearchCodeSort] = None, + order: SortDirection = SortDirection.desc +) extends SearchInput + case class searchCodeItems (value: JValue) extends AbstractJson(value){ def name: String = get("name") lazy val Repo = new Repository(value \ "repository") @@ -75,6 +96,12 @@ case class SearchCodeResult(value: JValue) extends AbstractJson(value) { } } +case class SearchIssueInput ( + q: String, + sort: Option[SearchIssueSort] = None, + order: SortDirection = SortDirection.desc +) extends SearchInput + case class SearchIssueResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") @@ -84,6 +111,12 @@ case class SearchIssueResult(value: JValue) extends AbstractJson(value) { } } +case class SearchUserInput ( + q: String, + sort: Option[SearchUserSort] = None, + order: SortDirection = SortDirection.desc +) extends SearchInput + case class SearchUserResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") diff --git a/src/main/scala/codecheck/github/operations/SearchOp.scala b/src/main/scala/codecheck/github/operations/SearchOp.scala index 8519a69..9c81930 100644 --- a/src/main/scala/codecheck/github/operations/SearchOp.scala +++ b/src/main/scala/codecheck/github/operations/SearchOp.scala @@ -14,28 +14,28 @@ trait SearchOp { self: GitHubAPI => def searchRepositories(input: SearchInput): Future[SearchRepositoryResult] = { - val path = s"/search/repositories?q=${input.q}&sort=${input.sort}&order=${input.order}" + val path = s"/search/repositories${input.query}" exec("GET", path ).map { res => SearchRepositoryResult(res.body) } } def searchCode(input: SearchInput): Future[SearchCodeResult] = { - val path = s"/search/code?q=${input.q}&sort=${input.sort}&order=${input.order}" + val path = s"/search/code${input.query}" exec("GET", path ).map { res => SearchCodeResult(res.body) } } def searchIssues(input: SearchInput): Future[SearchIssueResult] = { - val path = s"/search/issues?q=${input.q}&sort=${input.sort}&order=${input.order}" + val path = s"/search/issues${input.query}" exec("GET", path ).map { res => SearchIssueResult(res.body) } } def searchUser(input: SearchInput): Future[SearchUserResult] = { - val path = s"/search/users?q=${input.q}&sort=${input.sort}&order=${input.order}" + val path = s"/search/users${input.query}" exec("GET", path ).map { res => SearchUserResult(res.body) } diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index 65f6ece..3fcc66f 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -2,8 +2,14 @@ import org.scalatest.path.FunSpec import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global import codecheck.github.models.SortDirection -import codecheck.github.models.SearchInput -import codecheck.github.models.SearchSort +import codecheck.github.models.SearchRepositoryInput +import codecheck.github.models.SearchCodeInput +import codecheck.github.models.SearchIssueInput +import codecheck.github.models.SearchUserInput +import codecheck.github.models.SearchRepositorySort +import codecheck.github.models.SearchCodeSort +import codecheck.github.models.SearchIssueSort +import codecheck.github.models.SearchUserSort import codecheck.github.models.SearchRepositoryResult import codecheck.github.models.SearchCodeResult import codecheck.github.models.searchCodeItems @@ -17,7 +23,7 @@ class SearchOpSpec extends FunSpec it("with valid SearchInput should succeed") { var q = "tetris language:assembly" val q1 = q.trim.replaceAll(" ","+"); - val input = SearchInput(q1,sort=Some(SearchSort.stars),order=SortDirection.desc) + val input = SearchRepositoryInput(q1,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc) val res = Await.result(api.searchRepositories(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).id >= 1 ) @@ -31,7 +37,7 @@ class SearchOpSpec extends FunSpec it("with valid changed query(q) SearchInput should succeed") { var q = "jquery in:name,description" val q1 = q.trim.replaceAll(" ","+"); - val input = SearchInput(q1,sort=Some(SearchSort.stars),order=SortDirection.desc) + val input = SearchRepositoryInput(q1,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc) val res = Await.result(api.searchRepositories(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).id >= 1 ) @@ -45,7 +51,7 @@ class SearchOpSpec extends FunSpec it("with valid SearchInput q,no SortOrder should succeed") { var q = "addClass in:file language:js repo:jquery/jquery" val q1 = q.trim.replaceAll(" ","+"); - val input = SearchInput(q1,sort=None,order=SortDirection.desc) + val input = SearchCodeInput(q1,sort=None,order=SortDirection.desc) val res = Await.result(api.searchCode(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).Repo.id >= 1 ) @@ -58,7 +64,7 @@ class SearchOpSpec extends FunSpec it("with valid SearchInput it should succeed") { var q = "function size:10000 language:python" val q1 = q.trim.replaceAll(" ","+"); - val input = SearchInput(q1,sort=Some(SearchSort.indexed),order=SortDirection.desc) + val input = SearchCodeInput(q1,sort=Some(SearchCodeSort.indexed),order=SortDirection.desc) try { val res = Await.result(api.searchCode(input), TIMEOUT) } catch { @@ -72,7 +78,7 @@ class SearchOpSpec extends FunSpec it("with valid SearchInput should succeed") { var q = "windows label:bug language:python state:open" val q1 = q.trim.replaceAll(" ","+"); - val input = SearchInput(q1,sort=Some(SearchSort.created),order=SortDirection.desc) + val input = SearchIssueInput(q1,sort=Some(SearchIssueSort.created),order=SortDirection.desc) val res = Await.result(api.searchIssues(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).labels(0).name == "bug" ) @@ -85,7 +91,7 @@ class SearchOpSpec extends FunSpec var q = "tom repos:>42 followers:>1000" q = q.trim.replaceAll(" ","+") val q1 = q.replaceAll(">","%3E") - val input = SearchInput(q1,sort=None,order=SortDirection.desc) + val input = SearchUserInput(q1,sort=None,order=SortDirection.desc) val res = Await.result(api.searchUser(input), TIMEOUT) assert(res.total_count >= 0) assert(res.items(0).login.length >= 0) From 8f62cb41030123943ece1e5b278f2d626490c8ab Mon Sep 17 00:00:00 2001 From: "Aaron S. Hawley" Date: Thu, 19 Jan 2017 21:35:27 -0500 Subject: [PATCH 09/10] Fix case of SearchCodeItems and rename repository member --- src/main/scala/codecheck/github/models/Search.scala | 8 ++++---- src/test/scala/SearchOpSpec.scala | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala index 9c06596..a56c1d8 100644 --- a/src/main/scala/codecheck/github/models/Search.scala +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -82,16 +82,16 @@ case class SearchCodeInput ( order: SortDirection = SortDirection.desc ) extends SearchInput -case class searchCodeItems (value: JValue) extends AbstractJson(value){ +case class SearchCodeItems (value: JValue) extends AbstractJson(value){ def name: String = get("name") - lazy val Repo = new Repository(value \ "repository") + lazy val repository = Repository(value \ "repository") } case class SearchCodeResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") lazy val items = (value \ "items") match { - case JArray(arr) => arr.map(new searchCodeItems(_)) + case JArray(arr) => arr.map(SearchCodeItems(_)) case _ => Nil } } @@ -106,7 +106,7 @@ case class SearchIssueResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") lazy val items = (value \ "items") match { - case JArray(arr) => arr.map(new Issue(_)) + case JArray(arr) => arr.map(Issue(_)) case _ => Nil } } diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index 3fcc66f..4e2f807 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -12,7 +12,7 @@ import codecheck.github.models.SearchIssueSort import codecheck.github.models.SearchUserSort import codecheck.github.models.SearchRepositoryResult import codecheck.github.models.SearchCodeResult -import codecheck.github.models.searchCodeItems +import codecheck.github.models.SearchCodeItems import codecheck.github.exceptions.GitHubAPIException class SearchOpSpec extends FunSpec @@ -54,8 +54,8 @@ class SearchOpSpec extends FunSpec val input = SearchCodeInput(q1,sort=None,order=SortDirection.desc) val res = Await.result(api.searchCode(input), TIMEOUT) assert(res.total_count >= 1) - assert(res.items(0).Repo.id >= 1 ) - assert(res.items(0).Repo.full_name == "jquery/jquery") + assert(res.items(0).repository.id >= 1 ) + assert(res.items(0).repository.full_name == "jquery/jquery") } //Following test results in error: // "message" : "Validation Failed", From 1cd570e226f076e0e317a2b543b09c2ec44b2e40 Mon Sep 17 00:00:00 2001 From: "Aaron S. Hawley" Date: Thu, 19 Jan 2017 22:11:44 -0500 Subject: [PATCH 10/10] Improve tests for Code Search API --- .../codecheck/github/models/Repository.scala | 1 + .../codecheck/github/models/Search.scala | 12 +++-- src/test/scala/SearchOpSpec.scala | 54 ++++++++----------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/main/scala/codecheck/github/models/Repository.scala b/src/main/scala/codecheck/github/models/Repository.scala index cb5351d..c90e5cb 100644 --- a/src/main/scala/codecheck/github/models/Repository.scala +++ b/src/main/scala/codecheck/github/models/Repository.scala @@ -71,6 +71,7 @@ case class Repository(value: JValue) extends AbstractJson(value) { def description = opt("description") def open_issues_count = get("open_issues_count").toLong + def `private` = boolean("private") lazy val permissions = Permissions(value \ "permissions") lazy val owner = User(value \ "owner") diff --git a/src/main/scala/codecheck/github/models/Search.scala b/src/main/scala/codecheck/github/models/Search.scala index a56c1d8..c5e2d12 100644 --- a/src/main/scala/codecheck/github/models/Search.scala +++ b/src/main/scala/codecheck/github/models/Search.scala @@ -82,8 +82,14 @@ case class SearchCodeInput ( order: SortDirection = SortDirection.desc ) extends SearchInput -case class SearchCodeItems (value: JValue) extends AbstractJson(value){ +case class SearchCodeItem(value: JValue) extends AbstractJson(value) { def name: String = get("name") + def path: String = get("path") + def sha: String = get("sha") + def url: String = get("url") + def git_url: String = get("git_url") + def html_url: String = get("html_url") + def score: Double = get("score").toDouble lazy val repository = Repository(value \ "repository") } @@ -91,7 +97,7 @@ case class SearchCodeResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") lazy val items = (value \ "items") match { - case JArray(arr) => arr.map(SearchCodeItems(_)) + case JArray(arr) => arr.map(SearchCodeItem(_)) case _ => Nil } } @@ -121,7 +127,7 @@ case class SearchUserResult(value: JValue) extends AbstractJson(value) { def total_count: Long = get("total_count").toLong def incomplete_results: Boolean = boolean("incomplete_results") lazy val items = (value \ "items") match { - case JArray(arr) => arr.map(new User(_)) + case JArray(arr) => arr.map(User(_)) case _ => Nil } } diff --git a/src/test/scala/SearchOpSpec.scala b/src/test/scala/SearchOpSpec.scala index 4e2f807..3ce9538 100644 --- a/src/test/scala/SearchOpSpec.scala +++ b/src/test/scala/SearchOpSpec.scala @@ -12,7 +12,6 @@ import codecheck.github.models.SearchIssueSort import codecheck.github.models.SearchUserSort import codecheck.github.models.SearchRepositoryResult import codecheck.github.models.SearchCodeResult -import codecheck.github.models.SearchCodeItems import codecheck.github.exceptions.GitHubAPIException class SearchOpSpec extends FunSpec @@ -21,9 +20,8 @@ class SearchOpSpec extends FunSpec describe("searchRepositories") { it("with valid SearchInput should succeed") { - var q = "tetris language:assembly" - val q1 = q.trim.replaceAll(" ","+"); - val input = SearchRepositoryInput(q1,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc) + val q = "tetris language:assembly".trim.replaceAll(" ","+") + val input = SearchRepositoryInput(q,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc) val res = Await.result(api.searchRepositories(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).id >= 1 ) @@ -35,9 +33,8 @@ class SearchOpSpec extends FunSpec assert(res.items(0).stargazers_count > res.items(1).stargazers_count) } it("with valid changed query(q) SearchInput should succeed") { - var q = "jquery in:name,description" - val q1 = q.trim.replaceAll(" ","+"); - val input = SearchRepositoryInput(q1,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc) + val q = "jquery in:name,description".trim.replaceAll(" ","+") + val input = SearchRepositoryInput(q,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc) val res = Await.result(api.searchRepositories(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).id >= 1 ) @@ -49,36 +46,31 @@ class SearchOpSpec extends FunSpec } describe("searchCode") { it("with valid SearchInput q,no SortOrder should succeed") { - var q = "addClass in:file language:js repo:jquery/jquery" - val q1 = q.trim.replaceAll(" ","+"); - val input = SearchCodeInput(q1,sort=None,order=SortDirection.desc) + val q = "addClass in:file language:js repo:jquery/jquery".trim.replaceAll(" ","+") + val input = SearchCodeInput(q,sort=None,order=SortDirection.desc) val res = Await.result(api.searchCode(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).repository.id >= 1 ) + assert(res.items(0).sha.length >= 40) + assert(res.items(0).score >= 0d) assert(res.items(0).repository.full_name == "jquery/jquery") } - //Following test results in error: - // "message" : "Validation Failed", - // "errors" : [ { - // "message" : "Must include at least one user, organization, or repository" it("with valid SearchInput it should succeed") { - var q = "function size:10000 language:python" - val q1 = q.trim.replaceAll(" ","+"); - val input = SearchCodeInput(q1,sort=Some(SearchCodeSort.indexed),order=SortDirection.desc) - try { - val res = Await.result(api.searchCode(input), TIMEOUT) - } catch { - case e: GitHubAPIException => - assert(e.error.errors.length == 1) - assert(e.error.message == "Validation Failed") - } + val q = "function size:10000 language:python".trim.replaceAll(" ","+") + val input = SearchCodeInput(q,sort=Some(SearchCodeSort.indexed),order=SortDirection.asc) + val res = Await.result(api.searchCode(input), TIMEOUT) + assert(res.total_count >= 1) + assert(res.items(0).repository.id >= 1 ) + assert(res.items(0).path.endsWith(".py")) + assert(res.items(0).sha.length >= 40) + assert(res.items(0).score >= 0d) + assert(res.items(0).repository.`private` == false) } } describe("searchIssues") { it("with valid SearchInput should succeed") { - var q = "windows label:bug language:python state:open" - val q1 = q.trim.replaceAll(" ","+"); - val input = SearchIssueInput(q1,sort=Some(SearchIssueSort.created),order=SortDirection.desc) + val q = "windows label:bug language:python state:open".trim.replaceAll(" ","+") + val input = SearchIssueInput(q,sort=Some(SearchIssueSort.created),order=SortDirection.desc) val res = Await.result(api.searchIssues(input), TIMEOUT) assert(res.total_count >= 1) assert(res.items(0).labels(0).name == "bug" ) @@ -88,10 +80,10 @@ class SearchOpSpec extends FunSpec } describe("searchUser") { it("with valid SearchInput should succeed") { - var q = "tom repos:>42 followers:>1000" - q = q.trim.replaceAll(" ","+") - val q1 = q.replaceAll(">","%3E") - val input = SearchUserInput(q1,sort=None,order=SortDirection.desc) + val q = "tom repos:>42 followers:>1000" + .trim.replaceAll(" ","+") + .replaceAll(">","%3E") + val input = SearchUserInput(q,sort=None,order=SortDirection.desc) val res = Await.result(api.searchUser(input), TIMEOUT) assert(res.total_count >= 0) assert(res.items(0).login.length >= 0)