+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module test_api is test
+
+intrude import api
+
+# GithubAPI testing
+#
+# To avoid test flakyness we test the GithubAPI against a mock of the real one.
+# For each api request we return a cache file of the real API response body.
+#
+# Cache files can be automatically created and updated by setting
+# `update_responses_cache` to `true` then running `nitunit`.
+class MockGithubCurl
+ super GithubCurl
+
+ # Mock so it returns the response from a file
+ #
+ # See `update_responses_cache`.
+ redef fun get_and_parse(uri) do
+ print uri # for debugging
+
+ var path = uri.replace("https://api.github.com/", "/")
+ assert has_response(path)
+
+ if update_responses_cache then
+ var file = response_file(path)
+ save_actual_response(uri, file)
+ end
+
+ var response = response_string(path).parse_json
+ if response_is_error(path) then
+ var title = "GithubAPIError"
+ var msg = response.as(JsonObject)["message"].as(String)
+ var err = new GithubError(msg, title)
+ err.json["requested_uri"] = uri
+ err.json["status_code"] = response_code(path)
+ return err
+ end
+ return response
+ end
+
+ var test_responses: Map[String, String] do
+ var map = new HashMap[String, String]
+ map["/user"] = "user_Morriar"
+ map["/users/Morriar"] = "user_Morriar"
+ map["/repos/nitlang/nit"] = "repo_nit"
+ map["/repos/nitlang/nit/labels/ok_will_merge"] = "repo_labels_ok_will_merge"
+ map["/repos/nitlang/nit/milestones/4"] = "repo_milestones_4"
+ map["/repos/nitlang/nit/branches"] = "repo_branches_nit"
+ map["/repos/nitlang/nit/branches/master"] = "repo_branches_master"
+ map["/repos/nitlang/nit/issues/1000"] = "repo_issues_1000"
+ map["/repos/nitlang/nit/issues/comments/6020149"] = "repo_issues_comments_6020149"
+ map["/repos/nitlang/nit/issues/events/199674194"] = "repo_issues_events_199674194"
+ map["/repos/nitlang/nit/pulls/1000"] = "repo_pulls_1000"
+ map["/repos/nitlang/nit/commits/64ce1f"] = "repo_commits_64ce1f"
+ map["/repos/nitlang/nit/comments/8982707"] = "repo_comments_8982707"
+ map["/repos/nitlang/nit/pulls/comments/21010363"] = "repo_pulls_comment_21010363"
+ # errors
+ map["/users/not_found/not_found"] = "errors_404"
+ return map
+ end
+
+ # Does `self` have a mock response for Github `path`?
+ fun has_response(path: String): Bool do
+ return test_responses.has_key(path)
+ end
+
+ # Root responses cache directory
+ var responses_dir: String is lazy do
+ var path = "NIT_TESTING_PATH".environ.dirname / "mock"
+ path.mkdir
+ return path
+ end
+
+ # Returns the response file path for a Github `path`
+ fun response_file(path: String): String do
+ assert has_response(path)
+ return "{responses_dir / test_responses[path]}.res"
+ end
+
+ # Returns the response body string for a Github `path`
+ fun response_string(path: String): String do
+ var file = response_file(path)
+ assert file.file_exists
+ return file.to_path.read_all
+ end
+
+ # Is this response a simulated error?
+ fun response_is_error(path: String): Bool do
+ assert has_response(path)
+ return test_responses[path].has_prefix("errors_")
+ end
+
+ # Status code of a simulated error
+ #
+ # See `response_is_error`.
+ fun response_code(path: String): String do
+ assert response_is_error(path)
+ return test_responses[path].split("_").last
+ end
+
+ # Response caching
+
+ # Activate caching
+ #
+ # Change this value to `true` then run nitunit to cache the responses
+ # from the Github API.
+ #
+ # Default is `false`.
+ var update_responses_cache = false
+
+ # Save the actual Github API response body for `uri` to a `file`
+ private fun save_actual_response(uri, file: String) do
+ assert update_responses_cache
+
+ var request = new CurlHTTPRequest(uri)
+ request.user_agent = actual_curl.user_agent
+ request.headers = actual_curl.header
+ var response = request.execute
+
+ if response isa CurlResponseSuccess then
+ response.body_str.write_to_file(file)
+ else if response isa CurlResponseFailed then
+ response.error_msg.write_to_file(file)
+ else abort
+
+ print "Response to `{uri}` saved at `{file}`"
+ end
+
+ # Actual GithubCurl instance used for caching
+ private var actual_curl = new GithubCurl(get_github_oauth, "nitunit")
+end
+
+class TestGithubAPI
+ test
+
+ var mock = new MockGithubCurl("test", "test")
+
+ fun api: GithubAPI do
+ var api = new GithubAPI("test")
+ api.ghcurl = mock
+ return api
+ end
+
+ fun test_deserialize is test do
+ var response = mock.response_string("/users/Morriar")
+ var obj = api.deserialize(response)
+ assert obj isa User
+ assert obj.login == "Morriar"
+ end
+
+ fun test_sanitize_url is test do
+ # TODO better tests
+ assert api.sanitize_uri("/repos/Nit with spaces/") == "/repos/Nit%20with%20spaces/"
+ end
+
+ fun test_get is test do
+ var api = self.api
+ var obj = api.get("/users/Morriar")
+ assert not api.was_error
+ assert api.last_error == null
+ assert obj isa JsonObject
+ assert obj["login"] == "Morriar"
+ end
+
+ fun test_get_404 is test do
+ var api = self.api
+ var res = api.get("/users/not_found/not_found")
+ assert res == null
+ assert api.was_error
+ var err = api.last_error
+ assert err isa GithubError
+ assert err.name == "GithubAPIError"
+ assert err.message == "Not Found"
+ end
+
+ fun test_load_from_github is test do
+ var api = self.api
+ var obj = api.load_from_github("/users/Morriar")
+ assert not api.was_error
+ assert api.last_error == null
+ assert obj isa User
+ assert obj.login == "Morriar"
+ end
+
+ fun test_load_from_github_404 is test do
+ var api = self.api
+ var res = api.load_from_github("/users/not_found/not_found")
+ assert res == null
+ assert api.was_error
+ var err = api.last_error
+ assert err isa GithubError
+ assert err.name == "GithubAPIError"
+ assert err.message == "Not Found"
+ end
+
+ # TODO test more error cases
+
+ fun test_get_auth_user is test do
+ var user = api.load_auth_user
+ assert user isa User
+ assert user.login == "Morriar"
+ assert user.avatar_url == "https://avatars2.githubusercontent.com/u/583144?v=4"
+ assert user.name == "Alexandre Terrasa"
+ assert user.email == "alexandre@moz-code.org"
+ assert user.blog == "moz-code.org"
+ end
+
+ fun test_get_user is test do
+ var user = api.load_user("Morriar")
+ assert user isa User
+ assert user.login == "Morriar"
+ assert user.avatar_url == "https://avatars2.githubusercontent.com/u/583144?v=4"
+ assert user.name == "Alexandre Terrasa"
+ assert user.email == "alexandre@moz-code.org"
+ assert user.blog == "moz-code.org"
+ end
+
+ fun test_get_repo is test do
+ var repo = api.load_repo("nitlang/nit")
+ assert repo isa Repo
+ assert repo.full_name == "nitlang/nit"
+ assert repo.name == "nit"
+ assert repo.owner.login == "nitlang"
+ assert repo.default_branch == "master"
+ end
+
+ private var repo: Repo is lazy do return api.load_repo("nitlang/nit").as(not null)
+
+ fun test_get_branches is test do
+ var branches = api.load_repo_branches(repo)
+ assert branches.length == 2
+ assert branches.first.name == "master"
+ assert branches.last.name == "next"
+ end
+
+ # TODO issues
+ # TODO repo_last_issue
+ # TODO labels
+ # TODO milestones
+ # TODO pulls
+ # TODO contrib_stats
+
+ fun test_get_branch is test do
+ var branch = api.load_branch(repo, "master")
+ assert branch isa Branch
+ assert branch.name == "master"
+ end
+
+ # TODO branch commits
+
+ fun test_get_commit is test do
+ var commit = api.load_commit(repo, "64ce1f")
+ assert commit isa Commit
+ assert commit.sha == "64ce1f587209024f5de46d06c70526a569ff537f"
+ # TODO other fields
+ end
+
+ fun test_get_issue is test do
+ var issue = api.load_issue(repo, 1000)
+ assert issue isa Issue
+ assert issue.number == 1000
+ assert issue.title == "Raise nitc from the dead"
+ assert issue.user.as(User).login == "privat"
+ assert issue.comments == 7
+ assert issue.created_at == "2014-12-11T02:55:09Z"
+ assert issue.closed_at == "2014-12-13T15:38:09Z"
+ assert issue.closed_by.as(User).login == "privat"
+ assert issue.body == "Raise dead on `nitc`.\nIt's super effective...\n"
+ assert issue.is_pull_request
+ end
+
+ # TODO issue comments
+ # TODO issue events
+
+ fun test_get_pull is test do
+ var pull = api.load_pull(repo, 1000)
+ assert pull isa Issue
+ assert pull.number == 1000
+ assert pull.title == "Raise nitc from the dead"
+ assert pull.user.as(User).login == "privat"
+ assert pull.comments == 7
+ assert pull.created_at == "2014-12-11T02:55:09Z"
+ assert pull.closed_at == "2014-12-13T15:38:09Z"
+ assert pull.merged_by.as(User).login == "privat"
+ assert pull.body == "Raise dead on `nitc`.\nIt's super effective...\n"
+ end
+
+ fun test_get_label is test do
+ var labl = api.load_label(repo, "ok_will_merge")
+ assert labl isa Label
+ assert labl.name == "ok_will_merge"
+ end
+
+ fun test_get_milestone is test do
+ var milestone = api.load_milestone(repo, 4)
+ assert milestone isa Milestone
+ assert milestone.title == "v1.0prealpha"
+ # TODO other fields
+ end
+
+ fun test_get_issue_event is test do
+ var event = api.load_issue_event(repo, 199674194)
+ assert event isa IssueEvent
+ assert event.actor.login == "privat"
+ assert event.event == "labeled"
+ assert event.labl.as(Label).name == "need_review"
+ end
+
+ fun test_get_issue_comment is test do
+ var comment = api.load_issue_comment(repo, 6020149)
+ assert comment isa IssueComment
+ assert comment.user.login == "privat"
+ assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
+ assert comment.issue_number == 10
+ end
+
+ fun test_get_comment is test do
+ var comment = api.load_commit_comment(repo, 8982707)
+ assert comment isa CommitComment
+ assert comment.user.login == "Morriar"
+ assert comment.body == "For testing purposes...\n"
+ assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
+ end
+
+ fun test_get_review_comments is test do
+ var comment = api.load_review_comment(repo, 21010363)
+ assert comment isa ReviewComment
+ assert comment.path == "src/modelize/modelize_property.nit"
+ assert comment.original_position == 26
+ assert comment.pull_number == 945
+ end
+end