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.

Introduced properties

init defaultinit(auth: nullable String, user_agent: nullable String)

github :: MockGithubAPI :: defaultinit

fun has_response(path: String): Bool

github :: MockGithubAPI :: has_response

Does self have a mock response for Github path?
fun response_code(path: String): String

github :: MockGithubAPI :: response_code

Status code of a simulated error
fun response_file(path: String): String

github :: MockGithubAPI :: response_file

Returns the response file path for a Github path
fun response_is_error(path: String): Bool

github :: MockGithubAPI :: response_is_error

Is this response a simulated error?
fun response_string(path: String): String

github :: MockGithubAPI :: response_string

Returns the response body string for a Github path
fun responses_dir: String

github :: MockGithubAPI :: responses_dir

Root responses cache directory
protected fun responses_dir=(responses_dir: String)

github :: MockGithubAPI :: responses_dir=

Root responses cache directory
protected fun test_responses=(test_responses: Map[String, String])

github :: MockGithubAPI :: test_responses=

protected fun update_responses_cache=(update_responses_cache: Bool)

github :: MockGithubAPI :: update_responses_cache=

Activate caching

Redefined properties

redef type SELF: MockGithubAPI

github $ MockGithubAPI :: SELF

Type of this instance, automatically specialized in every class
redef fun send(method: String, path: String, headers: nullable HeaderMap, body: nullable String): nullable String

github $ MockGithubAPI :: send

Mock so it returns the response from a file

All properties

fun !=(other: nullable Object): Bool

core :: Object :: !=

Have self and other different values?
fun ==(other: nullable Object): Bool

core :: Object :: ==

Have self and other the same value?
type CLASS: Class[SELF]

core :: Object :: CLASS

The type of the class of self.
type SELF: Object

core :: Object :: SELF

Type of this instance, automatically specialized in every class
fun api_url: String

github :: GithubAPI :: api_url

Github API base url.
protected fun api_url=(api_url: String)

github :: GithubAPI :: api_url=

Github API base url.
fun auth: nullable String

github :: GithubAPI :: auth

Github API OAuth token
protected fun auth=(auth: nullable String)

github :: GithubAPI :: auth=

Github API OAuth token
protected fun class_factory(name: String): CLASS

core :: Object :: class_factory

Implementation used by get_class to create the specific class.
fun class_name: String

core :: Object :: class_name

The class name of the object.
fun clear_cache

github :: GithubAPI :: clear_cache

Delete the cache directory.
init defaultinit(auth: nullable String, user_agent: nullable String)

github :: MockGithubAPI :: defaultinit

init defaultinit(auth: nullable String, user_agent: nullable String)

github :: GithubAPI :: defaultinit

fun deserialize(string: nullable Serializable): nullable Object

github :: GithubAPI :: deserialize

Deserialize an object
fun enable_cache: Bool

github :: GithubAPI :: enable_cache

Enable caching for this client.
fun enable_cache=(enable_cache: Bool)

github :: GithubAPI :: enable_cache=

Enable caching for this client.
fun get(path: String, headers: nullable HeaderMap, data: nullable String): nullable Object

github :: GithubAPI :: get

Execute a GET request on Github API.
fun get_auth_user: nullable User

github :: GithubAPI :: get_auth_user

Get the Github logged user from auth token.
fun get_branch(repo_slug: String, name: String): nullable Branch

github :: GithubAPI :: get_branch

Get the Github branch with name.
fun get_class: CLASS

core :: Object :: get_class

The meta-object representing the dynamic type of self.
fun get_commit(repo_slug: String, sha: String): nullable Commit

github :: GithubAPI :: get_commit

Get the Github commit with sha.
fun get_commit_comment(repo_slug: String, id: Int): nullable CommitComment

github :: GithubAPI :: get_commit_comment

Get the Github commit comment with id.
fun get_commit_status(repo_slug: String, sha: String): nullable CommitStatus

github :: GithubAPI :: get_commit_status

Get the status of a commit
fun get_issue(repo_slug: String, number: Int): nullable Issue

github :: GithubAPI :: get_issue

Get the Github issue #number.
fun get_issue_comment(repo_slug: String, id: Int): nullable IssueComment

github :: GithubAPI :: get_issue_comment

Get the Github issue comment with id.
fun get_issue_comments(repo_slug: String, issue_number: Int, page: nullable Int, per_page: nullable Int): Array[IssueComment]

github :: GithubAPI :: get_issue_comments

List of event on this issue.
fun get_issue_event(repo_slug: String, id: Int): nullable IssueEvent

github :: GithubAPI :: get_issue_event

Get the Github issue event with id.
fun get_issue_events(repo_slug: String, issue_number: Int, page: nullable Int, per_page: nullable Int): Array[IssueEvent]

github :: GithubAPI :: get_issue_events

List of events on this issue.
fun get_label(repo_slug: String, name: String): nullable Label

github :: GithubAPI :: get_label

Get the Github label with name.
fun get_milestone(repo_slug: String, id: Int): nullable Milestone

github :: GithubAPI :: get_milestone

Get the Github milestone with id.
fun get_pull(repo_slug: String, number: Int): nullable PullRequest

github :: GithubAPI :: get_pull

Get the Github pull request #number.
fun get_pull_comment(repo_slug: String, id: Int): nullable PullComment

github :: GithubAPI :: get_pull_comment

Get a specific pull request comment
fun get_pull_comments(repo_slug: String, pull_number: Int, page: nullable Int, per_page: nullable Int): Array[PullComment]

github :: GithubAPI :: get_pull_comments

List of comments on a pull request
fun get_repo(repo_slug: String): nullable Repo

github :: GithubAPI :: get_repo

Get the Github repo with full_name.
fun get_repo_branches(repo_slug: String, page: nullable Int, per_page: nullable Int): Array[Branch]

github :: GithubAPI :: get_repo_branches

List of repo branches.
fun get_repo_contrib_stats(repo_slug: String): Array[ContributorStats]

github :: GithubAPI :: get_repo_contrib_stats

List of contributor related statistics.
fun get_repo_issues(repo_slug: String, page: nullable Int, per_page: nullable Int): Array[Issue]

github :: GithubAPI :: get_repo_issues

List of issues associated with their ids.
fun get_repo_labels(repo_slug: String, page: nullable Int, per_page: nullable Int): Array[Label]

github :: GithubAPI :: get_repo_labels

List of labels associated with their names.
fun get_repo_milestones(repo_slug: String, page: nullable Int, per_page: nullable Int): Array[Milestone]

github :: GithubAPI :: get_repo_milestones

List of milestones associated with their ids.
fun get_repo_pulls(repo_slug: String, page: nullable Int, per_page: nullable Int): Array[PullRequest]

github :: GithubAPI :: get_repo_pulls

List of pull-requests associated with their ids.
fun get_user(login: String): nullable User

github :: GithubAPI :: get_user

Get the Github user with login
fun has_cache(key: String): Bool

github :: GithubAPI :: has_cache

Check if a cache file exists for key.
fun has_response(path: String): Bool

github :: MockGithubAPI :: has_response

Does self have a mock response for Github path?
fun hash: Int

core :: Object :: hash

The hash code of the object.
init init

core :: Object :: init

fun inspect: String

core :: Object :: inspect

Developer readable representation of self.
protected fun inspect_head: String

core :: Object :: inspect_head

Return "CLASSNAME:#OBJECTID".
intern fun is_same_instance(other: nullable Object): Bool

core :: Object :: is_same_instance

Return true if self and other are the same instance (i.e. same identity).
fun is_same_serialized(other: nullable Object): Bool

core :: Object :: is_same_serialized

Is self the same as other in a serialization context?
intern fun is_same_type(other: Object): Bool

core :: Object :: is_same_type

Return true if self and other have the same dynamic type.
fun last_error: nullable Error

github :: GithubAPI :: last_error

Last error occured during Github API communications.
fun last_error=(last_error: nullable Error)

github :: GithubAPI :: last_error=

Last error occured during Github API communications.
fun new_headers: HeaderMap

github :: GithubAPI :: new_headers

Headers to use on all requests
intern fun object_id: Int

core :: Object :: object_id

An internal hash code for the object based on its identity.
fun output

core :: Object :: output

Display self on stdout (debug only).
intern fun output_class_name

core :: Object :: output_class_name

Display class name on stdout (debug only).
fun response_code(path: String): String

github :: MockGithubAPI :: response_code

Status code of a simulated error
fun response_file(path: String): String

github :: MockGithubAPI :: response_file

Returns the response file path for a Github path
fun response_is_error(path: String): Bool

github :: MockGithubAPI :: response_is_error

Is this response a simulated error?
fun response_string(path: String): String

github :: MockGithubAPI :: response_string

Returns the response body string for a Github path
fun responses_dir: String

github :: MockGithubAPI :: responses_dir

Root responses cache directory
protected fun responses_dir=(responses_dir: String)

github :: MockGithubAPI :: responses_dir=

Root responses cache directory
fun search_repo_issues(repo_slug: String, query: String, page: nullable Int, per_page: nullable Int): nullable SearchResults

github :: GithubAPI :: search_repo_issues

Search issues in this repo form an advanced query.
fun send(method: String, path: String, headers: nullable HeaderMap, body: nullable String): nullable String

github :: GithubAPI :: send

Send a HTTPRequest to the Github API
fun serialization_hash: Int

core :: Object :: serialization_hash

Hash value use for serialization
fun store: JsonStore

github :: GithubAPI :: store

JsonStore used to cache data.
fun store=(store: JsonStore)

github :: GithubAPI :: store=

JsonStore used to cache data.
intern fun sys: Sys

core :: Object :: sys

Return the global sys object, the only instance of the Sys class.
protected fun test_responses=(test_responses: Map[String, String])

github :: MockGithubAPI :: test_responses=

abstract fun to_jvalue(env: JniEnv): JValue

core :: Object :: to_jvalue

fun to_s: String

core :: Object :: to_s

User readable representation of self.
protected fun update_responses_cache=(update_responses_cache: Bool)

github :: MockGithubAPI :: update_responses_cache=

Activate caching
fun user_agent: String

github :: GithubAPI :: user_agent

User agent used for HTTP requests.
protected fun user_agent=(user_agent: nullable String)

github :: GithubAPI :: user_agent=

User agent used for HTTP requests.
fun valid_tokens: Array[String]

github :: GithubAPI :: valid_tokens

Tokens mocked as valid
protected fun valid_tokens=(valid_tokens: Array[String])

github :: GithubAPI :: valid_tokens=

Tokens mocked as valid
fun was_error: Bool

github :: GithubAPI :: was_error

Does the last request provoqued an error?
protected fun was_error=(was_error: Bool)

github :: GithubAPI :: was_error=

Does the last request provoqued an error?
package_diagram github::MockGithubAPI MockGithubAPI github::GithubAPI GithubAPI github::MockGithubAPI->github::GithubAPI core::Object Object github::GithubAPI->core::Object ...core::Object ... ...core::Object->core::Object

Ancestors

interface Object

core :: Object

The root of the class hierarchy.

Parents

class GithubAPI

github :: GithubAPI

Client to Github API

Class definitions

github $ MockGithubAPI
# 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 MockGithubAPI
	super GithubAPI

	# Mock so it returns the response from a file
	#
	# See `update_responses_cache`.
	redef fun send(method, path, headers, body) do
		print path # for debugging

		assert has_response(path)

		if update_responses_cache then
			var file = response_file(path)
			save_actual_response(path, file)
		end

		var response = response_string(path)
		if response_is_error(path) then
			last_error = new GithubAPIError(
				response.parse_json.as(JsonObject)["message"].as(String),
				response_code(path).to_i,
				path
			)
			was_error = true
			return null
		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?page=1&per_page=3"] = "repo_labels_nit"
		map["/repos/nitlang/nit/labels/ok_will_merge"] = "repo_labels_ok_will_merge"
		map["/repos/nitlang/nit/milestones?page=1&per_page=3"] = "repo_milestones_nit"
		map["/repos/nitlang/nit/milestones/4"] = "repo_milestones_4"
		map["/repos/nitlang/nit/branches?page=1&per_page=2"] = "repo_branches_nit"
		map["/repos/nitlang/nit/branches/master"] = "repo_branches_master"
		map["/repos/nitlang/nit/issues?page=1&per_page=3"] = "repo_issues_nit"
		map["/repos/nitlang/nit/issues/1000"] = "repo_issues_1000"
		map["/repos/nitlang/nit/issues/1000/comments?page=1&per_page=3"] = "repo_issues_comments_nit"
		map["/repos/nitlang/nit/issues/comments/6020149"] = "repo_issues_comments_6020149"
		map["/repos/nitlang/nit/issues/1000/events?page=1&per_page=3"] = "repo_issues_events_nit"
		map["/repos/nitlang/nit/issues/events/199674194"] = "repo_issues_events_199674194"
		map["/repos/nitlang/nit/pulls?page=1&per_page=3"] = "repo_pulls_nit"
		map["/repos/nitlang/nit/pulls/1000"] = "repo_pulls_1000"
		map["/repos/nitlang/nit/pulls/945/comments?page=1&per_page=3"] = "repo_pulls_945_comments"
		map["/repos/nitlang/nit/pulls/comments/21010363"] = "repo_pulls_comment_21010363"
		map["/repos/nitlang/nit/commits/64ce1f"] = "repo_commits_64ce1f"
		map["/repos/nitlang/nit/commits/4e3c688d/status"] = "repo_commits_4e3c68_status"
		map["/repos/nitlang/nit/comments/8982707"] = "repo_comments_8982707"
		map["/search/issues?q=foo repo:nitlang/nit&page=1&per_page=3"] = "repo_search_issues_nit"
		map["/repos/nitlang/nit/stats/contributors"] = "repo_nit_contributors"
		# 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("{api_url}{sanitize_uri(uri)}")
		request.user_agent = actual_api.user_agent
		request.headers = actual_api.new_headers
		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_api = new GithubAPI(get_github_oauth, "nitunit")
end
lib/github/tests/test_api.nit:19,1--155,3