Skip to content

Latest commit

 

History

History
1349 lines (1243 loc) · 68 KB

File metadata and controls

1349 lines (1243 loc) · 68 KB

Short Specializations

0x03. Unittests and Integration Tests

UnitTests Back-end Integration tests

alt text

Unit testing is the process of testing that a particular function returns expected results for different set of inputs. A unit test is supposed to test standard inputs and corner cases. A unit test should only test the logic defined inside the tested function. Most calls to additional functions should be mocked, especially if they make network or database calls.

The goal of a unit test is to answer the question: if everything defined outside this function works as expected, does this function work as expected?

Integration tests aim to test a code path end-to-end. In general, only low level functions that make external calls such as HTTP requests, file I/O, database I/O, etc. are mocked.

Integration tests will test interactions between every part of your code.

Execute your tests with

$ python -m unittest path/to/test_file.py

Resources

Read or watch:

  • unittest — Unit testing framework
  • unittest.mock — mock object library
  • How to mock a readonly property with mock?
  • parameterized
  • Memoization

Learning Objectives

At the end of this project, you are expected to be able to explain to anyone, without the help of Google:

  • The difference between unit and integration tests.
  • Common testing patterns such as mocking, parametrizations and fixtures

Requirements

  • All your files will be interpreted/compiled on Ubuntu 18.04 LTS using python3 (version 3.7)
  • All your files should end with a new line
  • The first line of all your files should be exactly #!/usr/bin/env python3
  • A README.md file, at the root of the folder of the project, is mandatory
  • Your code should use the pycodestyle style (version 2.5)
  • All your files must be executable
  • All your modules should have a documentation (python3 -c 'print(__import__("my_module").__doc__)')
  • All your classes should have a documentation (python3 -c 'print(__import__("my_module").MyClass.__doc__)')
  • All your functions (inside and outside a class) should have a documentation (python3 -c 'print(__import__("my_module").my_function.__doc__)' and python3 -c 'print(__import__("my_module").MyClass.my_function.__doc__)')
  • A documentation is not a simple word, it’s a real sentence explaining what’s the purpose of the module, class or method (the length of it will be verified)
  • All your functions and coroutines must be type-annotated.

Required Files

utils.py (or download)

Click to show/hide file contents
#!/usr/bin/env python3
"""Generic utilities for github org client.
"""
import requests
from functools import wraps
from typing import (
    Mapping,
    Sequence,
    Any,
    Dict,
    Callable,
)

__all__ = [
    "access_nested_map",
    "get_json",
    "memoize",
]


def access_nested_map(nested_map: Mapping, path: Sequence) -> Any:
    """Access nested map with key path.
    Parameters
    ----------
    nested_map: Mapping
        A nested map
    path: Sequence
        a sequence of key representing a path to the value
    Example
    -------
    >>> nested_map = {"a": {"b": {"c": 1}}}
    >>> access_nested_map(nested_map, ["a", "b", "c"])
    1
    """
    for key in path:
        if not isinstance(nested_map, Mapping):
            raise KeyError(key)
        nested_map = nested_map[key]

    return nested_map


def get_json(url: str) -> Dict:
    """Get JSON from remote URL.
    """
    response = requests.get(url)
    return response.json()


def memoize(fn: Callable) -> Callable:
    """Decorator to memoize a method.
    Example
    -------
    class MyClass:
        @memoize
        def a_method(self):
            print("a_method called")
            return 42
    >>> my_object = MyClass()
    >>> my_object.a_method
    a_method called
    42
    >>> my_object.a_method
    42
    """
    attr_name = "_{}".format(fn.__name__)

    @wraps(fn)
    def memoized(self):
        """"memoized wraps"""
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)

    return property(memoized)

client.py (or download)

Click to show/hide file contents
#!/usr/bin/env python3
"""A github org client
"""
from typing import (
    List,
    Dict,
)

from utils import (
    get_json,
    access_nested_map,
    memoize,
)


class GithubOrgClient:
    """A Githib org client
    """
    ORG_URL = "https://api.github.com/orgs/{org}"

    def __init__(self, org_name: str) -> None:
        """Init method of GithubOrgClient"""
        self._org_name = org_name

    @memoize
    def org(self) -> Dict:
        """Memoize org"""
        return get_json(self.ORG_URL.format(org=self._org_name))

    @property
    def _public_repos_url(self) -> str:
        """Public repos URL"""
        return self.org["repos_url"]

    @memoize
    def repos_payload(self) -> Dict:
        """Memoize repos payload"""
        return get_json(self._public_repos_url)

    def public_repos(self, license: str = None) -> List[str]:
        """Public repos"""
        json_payload = self.repos_payload
        public_repos = [
            repo["name"] for repo in json_payload
            if license is None or self.has_license(repo, license)
        ]

        return public_repos

    @staticmethod
    def has_license(repo: Dict[str, Dict], license_key: str) -> bool:
        """Static: has_license"""
        assert license_key is not None, "license_key cannot be None"
        try:
            has_license = access_nested_map(repo, ("license", "key")) == license_key
        except KeyError:
            return False
        return has_license

fixtures.py (or download)

Click to show/hide file contents
#!/usr/bin/env python3

TEST_PAYLOAD = [
  (
    {"repos_url": "https://api.github.com/orgs/google/repos"},
    [
      {
        "id": 7697149,
        "node_id": "MDEwOlJlcG9zaXRvcnk3Njk3MTQ5",
        "name": "episodes.dart",
        "full_name": "google/episodes.dart",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/episodes.dart",
        "description": "A framework for timing performance of web apps.",
        "fork": False,
        "url": "https://api.github.com/repos/google/episodes.dart",
        "forks_url": "https://api.github.com/repos/google/episodes.dart/forks",
        "keys_url": "https://api.github.com/repos/google/episodes.dart/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/episodes.dart/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/episodes.dart/teams",
        "hooks_url": "https://api.github.com/repos/google/episodes.dart/hooks",
        "issue_events_url": "https://api.github.com/repos/google/episodes.dart/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/episodes.dart/events",
        "assignees_url": "https://api.github.com/repos/google/episodes.dart/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/episodes.dart/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/episodes.dart/tags",
        "blobs_url": "https://api.github.com/repos/google/episodes.dart/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/episodes.dart/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/episodes.dart/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/episodes.dart/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/episodes.dart/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/episodes.dart/languages",
        "stargazers_url": "https://api.github.com/repos/google/episodes.dart/stargazers",
        "contributors_url": "https://api.github.com/repos/google/episodes.dart/contributors",
        "subscribers_url": "https://api.github.com/repos/google/episodes.dart/subscribers",
        "subscription_url": "https://api.github.com/repos/google/episodes.dart/subscription",
        "commits_url": "https://api.github.com/repos/google/episodes.dart/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/episodes.dart/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/episodes.dart/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/episodes.dart/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/episodes.dart/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/episodes.dart/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/episodes.dart/merges",
        "archive_url": "https://api.github.com/repos/google/episodes.dart/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/episodes.dart/downloads",
        "issues_url": "https://api.github.com/repos/google/episodes.dart/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/episodes.dart/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/episodes.dart/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/episodes.dart/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/episodes.dart/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/episodes.dart/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/episodes.dart/deployments",
        "created_at": "2013-01-19T00:31:37Z",
        "updated_at": "2019-09-23T11:53:58Z",
        "pushed_at": "2014-10-09T21:39:33Z",
        "git_url": "git://github.com/google/episodes.dart.git",
        "ssh_url": "[email protected]:google/episodes.dart.git",
        "clone_url": "https://github.com/google/episodes.dart.git",
        "svn_url": "https://github.com/google/episodes.dart",
        "homepage": None,
        "size": 191,
        "stargazers_count": 12,
        "watchers_count": 12,
        "language": "Dart",
        "has_issues": True,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": True,
        "has_pages": False,
        "forks_count": 22,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 0,
        "license": {
          "key": "bsd-3-clause",
          "name": "BSD 3-Clause \"New\" or \"Revised\" License",
          "spdx_id": "BSD-3-Clause",
          "url": "https://api.github.com/licenses/bsd-3-clause",
          "node_id": "MDc6TGljZW5zZTU="
        },
        "forks": 22,
        "open_issues": 0,
        "watchers": 12,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 7776515,
        "node_id": "MDEwOlJlcG9zaXRvcnk3Nzc2NTE1",
        "name": "cpp-netlib",
        "full_name": "google/cpp-netlib",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/cpp-netlib",
        "description": "The C++ Network Library Project -- header-only, cross-platform, standards compliant networking library.",
        "fork": True,
        "url": "https://api.github.com/repos/google/cpp-netlib",
        "forks_url": "https://api.github.com/repos/google/cpp-netlib/forks",
        "keys_url": "https://api.github.com/repos/google/cpp-netlib/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/cpp-netlib/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/cpp-netlib/teams",
        "hooks_url": "https://api.github.com/repos/google/cpp-netlib/hooks",
        "issue_events_url": "https://api.github.com/repos/google/cpp-netlib/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/cpp-netlib/events",
        "assignees_url": "https://api.github.com/repos/google/cpp-netlib/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/cpp-netlib/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/cpp-netlib/tags",
        "blobs_url": "https://api.github.com/repos/google/cpp-netlib/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/cpp-netlib/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/cpp-netlib/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/cpp-netlib/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/cpp-netlib/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/cpp-netlib/languages",
        "stargazers_url": "https://api.github.com/repos/google/cpp-netlib/stargazers",
        "contributors_url": "https://api.github.com/repos/google/cpp-netlib/contributors",
        "subscribers_url": "https://api.github.com/repos/google/cpp-netlib/subscribers",
        "subscription_url": "https://api.github.com/repos/google/cpp-netlib/subscription",
        "commits_url": "https://api.github.com/repos/google/cpp-netlib/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/cpp-netlib/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/cpp-netlib/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/cpp-netlib/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/cpp-netlib/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/cpp-netlib/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/cpp-netlib/merges",
        "archive_url": "https://api.github.com/repos/google/cpp-netlib/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/cpp-netlib/downloads",
        "issues_url": "https://api.github.com/repos/google/cpp-netlib/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/cpp-netlib/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/cpp-netlib/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/cpp-netlib/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/cpp-netlib/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/cpp-netlib/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/cpp-netlib/deployments",
        "created_at": "2013-01-23T14:45:32Z",
        "updated_at": "2019-11-15T02:26:31Z",
        "pushed_at": "2018-12-05T17:42:29Z",
        "git_url": "git://github.com/google/cpp-netlib.git",
        "ssh_url": "[email protected]:google/cpp-netlib.git",
        "clone_url": "https://github.com/google/cpp-netlib.git",
        "svn_url": "https://github.com/google/cpp-netlib",
        "homepage": "http://cpp-netlib.github.com/",
        "size": 8937,
        "stargazers_count": 292,
        "watchers_count": 292,
        "language": "C++",
        "has_issues": False,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": True,
        "has_pages": False,
        "forks_count": 59,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 0,
        "license": {
          "key": "bsl-1.0",
          "name": "Boost Software License 1.0",
          "spdx_id": "BSL-1.0",
          "url": "https://api.github.com/licenses/bsl-1.0",
          "node_id": "MDc6TGljZW5zZTI4"
        },
        "forks": 59,
        "open_issues": 0,
        "watchers": 292,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 7968417,
        "node_id": "MDEwOlJlcG9zaXRvcnk3OTY4NDE3",
        "name": "dagger",
        "full_name": "google/dagger",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/dagger",
        "description": "A fast dependency injector for Android and Java.",
        "fork": True,
        "url": "https://api.github.com/repos/google/dagger",
        "forks_url": "https://api.github.com/repos/google/dagger/forks",
        "keys_url": "https://api.github.com/repos/google/dagger/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/dagger/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/dagger/teams",
        "hooks_url": "https://api.github.com/repos/google/dagger/hooks",
        "issue_events_url": "https://api.github.com/repos/google/dagger/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/dagger/events",
        "assignees_url": "https://api.github.com/repos/google/dagger/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/dagger/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/dagger/tags",
        "blobs_url": "https://api.github.com/repos/google/dagger/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/dagger/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/dagger/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/dagger/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/dagger/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/dagger/languages",
        "stargazers_url": "https://api.github.com/repos/google/dagger/stargazers",
        "contributors_url": "https://api.github.com/repos/google/dagger/contributors",
        "subscribers_url": "https://api.github.com/repos/google/dagger/subscribers",
        "subscription_url": "https://api.github.com/repos/google/dagger/subscription",
        "commits_url": "https://api.github.com/repos/google/dagger/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/dagger/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/dagger/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/dagger/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/dagger/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/dagger/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/dagger/merges",
        "archive_url": "https://api.github.com/repos/google/dagger/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/dagger/downloads",
        "issues_url": "https://api.github.com/repos/google/dagger/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/dagger/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/dagger/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/dagger/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/dagger/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/dagger/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/dagger/deployments",
        "created_at": "2013-02-01T23:14:14Z",
        "updated_at": "2019-12-03T12:39:55Z",
        "pushed_at": "2019-11-27T21:20:38Z",
        "git_url": "git://github.com/google/dagger.git",
        "ssh_url": "[email protected]:google/dagger.git",
        "clone_url": "https://github.com/google/dagger.git",
        "svn_url": "https://github.com/google/dagger",
        "homepage": "https://dagger.dev",
        "size": 59129,
        "stargazers_count": 14492,
        "watchers_count": 14492,
        "language": "Java",
        "has_issues": True,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": True,
        "has_pages": True,
        "forks_count": 1741,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 148,
        "license": {
          "key": "apache-2.0",
          "name": "Apache License 2.0",
          "spdx_id": "Apache-2.0",
          "url": "https://api.github.com/licenses/apache-2.0",
          "node_id": "MDc6TGljZW5zZTI="
        },
        "forks": 1741,
        "open_issues": 148,
        "watchers": 14492,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 8165161,
        "node_id": "MDEwOlJlcG9zaXRvcnk4MTY1MTYx",
        "name": "ios-webkit-debug-proxy",
        "full_name": "google/ios-webkit-debug-proxy",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/ios-webkit-debug-proxy",
        "description": "A DevTools proxy (Chrome Remote Debugging Protocol) for iOS devices (Safari Remote Web Inspector).",
        "fork": False,
        "url": "https://api.github.com/repos/google/ios-webkit-debug-proxy",
        "forks_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/forks",
        "keys_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/teams",
        "hooks_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/hooks",
        "issue_events_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/events",
        "assignees_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/tags",
        "blobs_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/languages",
        "stargazers_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/stargazers",
        "contributors_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/contributors",
        "subscribers_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/subscribers",
        "subscription_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/subscription",
        "commits_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/merges",
        "archive_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/downloads",
        "issues_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/deployments",
        "created_at": "2013-02-12T19:08:19Z",
        "updated_at": "2019-12-04T02:06:43Z",
        "pushed_at": "2019-11-24T07:02:13Z",
        "git_url": "git://github.com/google/ios-webkit-debug-proxy.git",
        "ssh_url": "[email protected]:google/ios-webkit-debug-proxy.git",
        "clone_url": "https://github.com/google/ios-webkit-debug-proxy.git",
        "svn_url": "https://github.com/google/ios-webkit-debug-proxy",
        "homepage": "",
        "size": 680,
        "stargazers_count": 4630,
        "watchers_count": 4630,
        "language": "C",
        "has_issues": True,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": False,
        "has_pages": False,
        "forks_count": 395,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 24,
        "license": {
          "key": "other",
          "name": "Other",
          "spdx_id": "NOASSERTION",
          "url": None,
          "node_id": "MDc6TGljZW5zZTA="
        },
        "forks": 395,
        "open_issues": 24,
        "watchers": 4630,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 8459994,
        "node_id": "MDEwOlJlcG9zaXRvcnk4NDU5OTk0",
        "name": "google.github.io",
        "full_name": "google/google.github.io",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/google.github.io",
        "description": None,
        "fork": False,
        "url": "https://api.github.com/repos/google/google.github.io",
        "forks_url": "https://api.github.com/repos/google/google.github.io/forks",
        "keys_url": "https://api.github.com/repos/google/google.github.io/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/google.github.io/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/google.github.io/teams",
        "hooks_url": "https://api.github.com/repos/google/google.github.io/hooks",
        "issue_events_url": "https://api.github.com/repos/google/google.github.io/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/google.github.io/events",
        "assignees_url": "https://api.github.com/repos/google/google.github.io/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/google.github.io/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/google.github.io/tags",
        "blobs_url": "https://api.github.com/repos/google/google.github.io/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/google.github.io/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/google.github.io/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/google.github.io/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/google.github.io/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/google.github.io/languages",
        "stargazers_url": "https://api.github.com/repos/google/google.github.io/stargazers",
        "contributors_url": "https://api.github.com/repos/google/google.github.io/contributors",
        "subscribers_url": "https://api.github.com/repos/google/google.github.io/subscribers",
        "subscription_url": "https://api.github.com/repos/google/google.github.io/subscription",
        "commits_url": "https://api.github.com/repos/google/google.github.io/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/google.github.io/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/google.github.io/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/google.github.io/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/google.github.io/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/google.github.io/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/google.github.io/merges",
        "archive_url": "https://api.github.com/repos/google/google.github.io/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/google.github.io/downloads",
        "issues_url": "https://api.github.com/repos/google/google.github.io/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/google.github.io/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/google.github.io/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/google.github.io/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/google.github.io/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/google.github.io/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/google.github.io/deployments",
        "created_at": "2013-02-27T16:21:19Z",
        "updated_at": "2019-12-03T01:38:02Z",
        "pushed_at": "2019-12-03T01:37:58Z",
        "git_url": "git://github.com/google/google.github.io.git",
        "ssh_url": "[email protected]:google/google.github.io.git",
        "clone_url": "https://github.com/google/google.github.io.git",
        "svn_url": "https://github.com/google/google.github.io",
        "homepage": None,
        "size": 8,
        "stargazers_count": 38,
        "watchers_count": 38,
        "language": "HTML",
        "has_issues": False,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": False,
        "has_pages": True,
        "forks_count": 44,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 0,
        "license": None,
        "forks": 44,
        "open_issues": 0,
        "watchers": 38,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 8566972,
        "node_id": "MDEwOlJlcG9zaXRvcnk4NTY2OTcy",
        "name": "kratu",
        "full_name": "google/kratu",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/kratu",
        "description": None,
        "fork": False,
        "url": "https://api.github.com/repos/google/kratu",
        "forks_url": "https://api.github.com/repos/google/kratu/forks",
        "keys_url": "https://api.github.com/repos/google/kratu/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/kratu/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/kratu/teams",
        "hooks_url": "https://api.github.com/repos/google/kratu/hooks",
        "issue_events_url": "https://api.github.com/repos/google/kratu/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/kratu/events",
        "assignees_url": "https://api.github.com/repos/google/kratu/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/kratu/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/kratu/tags",
        "blobs_url": "https://api.github.com/repos/google/kratu/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/kratu/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/kratu/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/kratu/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/kratu/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/kratu/languages",
        "stargazers_url": "https://api.github.com/repos/google/kratu/stargazers",
        "contributors_url": "https://api.github.com/repos/google/kratu/contributors",
        "subscribers_url": "https://api.github.com/repos/google/kratu/subscribers",
        "subscription_url": "https://api.github.com/repos/google/kratu/subscription",
        "commits_url": "https://api.github.com/repos/google/kratu/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/kratu/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/kratu/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/kratu/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/kratu/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/kratu/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/kratu/merges",
        "archive_url": "https://api.github.com/repos/google/kratu/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/kratu/downloads",
        "issues_url": "https://api.github.com/repos/google/kratu/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/kratu/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/kratu/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/kratu/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/kratu/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/kratu/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/kratu/deployments",
        "created_at": "2013-03-04T22:52:33Z",
        "updated_at": "2019-11-15T22:22:16Z",
        "pushed_at": "2017-08-06T05:44:34Z",
        "git_url": "git://github.com/google/kratu.git",
        "ssh_url": "[email protected]:google/kratu.git",
        "clone_url": "https://github.com/google/kratu.git",
        "svn_url": "https://github.com/google/kratu",
        "homepage": None,
        "size": 1777,
        "stargazers_count": 280,
        "watchers_count": 280,
        "language": "JavaScript",
        "has_issues": True,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": True,
        "has_pages": True,
        "forks_count": 32,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 0,
        "license": {
          "key": "apache-2.0",
          "name": "Apache License 2.0",
          "spdx_id": "Apache-2.0",
          "url": "https://api.github.com/licenses/apache-2.0",
          "node_id": "MDc6TGljZW5zZTI="
        },
        "forks": 32,
        "open_issues": 0,
        "watchers": 280,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 8858648,
        "node_id": "MDEwOlJlcG9zaXRvcnk4ODU4NjQ4",
        "name": "build-debian-cloud",
        "full_name": "google/build-debian-cloud",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/build-debian-cloud",
        "description": "Script to create Debian Squeeze & Wheezy Amazon Machine Images (AMIs) and Google Compute Engine images",
        "fork": True,
        "url": "https://api.github.com/repos/google/build-debian-cloud",
        "forks_url": "https://api.github.com/repos/google/build-debian-cloud/forks",
        "keys_url": "https://api.github.com/repos/google/build-debian-cloud/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/build-debian-cloud/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/build-debian-cloud/teams",
        "hooks_url": "https://api.github.com/repos/google/build-debian-cloud/hooks",
        "issue_events_url": "https://api.github.com/repos/google/build-debian-cloud/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/build-debian-cloud/events",
        "assignees_url": "https://api.github.com/repos/google/build-debian-cloud/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/build-debian-cloud/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/build-debian-cloud/tags",
        "blobs_url": "https://api.github.com/repos/google/build-debian-cloud/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/build-debian-cloud/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/build-debian-cloud/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/build-debian-cloud/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/build-debian-cloud/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/build-debian-cloud/languages",
        "stargazers_url": "https://api.github.com/repos/google/build-debian-cloud/stargazers",
        "contributors_url": "https://api.github.com/repos/google/build-debian-cloud/contributors",
        "subscribers_url": "https://api.github.com/repos/google/build-debian-cloud/subscribers",
        "subscription_url": "https://api.github.com/repos/google/build-debian-cloud/subscription",
        "commits_url": "https://api.github.com/repos/google/build-debian-cloud/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/build-debian-cloud/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/build-debian-cloud/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/build-debian-cloud/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/build-debian-cloud/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/build-debian-cloud/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/build-debian-cloud/merges",
        "archive_url": "https://api.github.com/repos/google/build-debian-cloud/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/build-debian-cloud/downloads",
        "issues_url": "https://api.github.com/repos/google/build-debian-cloud/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/build-debian-cloud/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/build-debian-cloud/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/build-debian-cloud/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/build-debian-cloud/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/build-debian-cloud/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/build-debian-cloud/deployments",
        "created_at": "2013-03-18T16:32:00Z",
        "updated_at": "2019-09-23T11:54:00Z",
        "pushed_at": "2014-06-17T18:52:10Z",
        "git_url": "git://github.com/google/build-debian-cloud.git",
        "ssh_url": "[email protected]:google/build-debian-cloud.git",
        "clone_url": "https://github.com/google/build-debian-cloud.git",
        "svn_url": "https://github.com/google/build-debian-cloud",
        "homepage": "",
        "size": 986,
        "stargazers_count": 32,
        "watchers_count": 32,
        "language": "Shell",
        "has_issues": False,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": False,
        "has_pages": False,
        "forks_count": 22,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 5,
        "license": {
          "key": "other",
          "name": "Other",
          "spdx_id": "NOASSERTION",
          "url": None,
          "node_id": "MDc6TGljZW5zZTA="
        },
        "forks": 22,
        "open_issues": 5,
        "watchers": 32,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 9060347,
        "node_id": "MDEwOlJlcG9zaXRvcnk5MDYwMzQ3",
        "name": "traceur-compiler",
        "full_name": "google/traceur-compiler",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/traceur-compiler",
        "description": "Traceur is a JavaScript.next-to-JavaScript-of-today compiler",
        "fork": False,
        "url": "https://api.github.com/repos/google/traceur-compiler",
        "forks_url": "https://api.github.com/repos/google/traceur-compiler/forks",
        "keys_url": "https://api.github.com/repos/google/traceur-compiler/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/traceur-compiler/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/traceur-compiler/teams",
        "hooks_url": "https://api.github.com/repos/google/traceur-compiler/hooks",
        "issue_events_url": "https://api.github.com/repos/google/traceur-compiler/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/traceur-compiler/events",
        "assignees_url": "https://api.github.com/repos/google/traceur-compiler/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/traceur-compiler/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/traceur-compiler/tags",
        "blobs_url": "https://api.github.com/repos/google/traceur-compiler/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/traceur-compiler/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/traceur-compiler/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/traceur-compiler/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/traceur-compiler/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/traceur-compiler/languages",
        "stargazers_url": "https://api.github.com/repos/google/traceur-compiler/stargazers",
        "contributors_url": "https://api.github.com/repos/google/traceur-compiler/contributors",
        "subscribers_url": "https://api.github.com/repos/google/traceur-compiler/subscribers",
        "subscription_url": "https://api.github.com/repos/google/traceur-compiler/subscription",
        "commits_url": "https://api.github.com/repos/google/traceur-compiler/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/traceur-compiler/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/traceur-compiler/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/traceur-compiler/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/traceur-compiler/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/traceur-compiler/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/traceur-compiler/merges",
        "archive_url": "https://api.github.com/repos/google/traceur-compiler/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/traceur-compiler/downloads",
        "issues_url": "https://api.github.com/repos/google/traceur-compiler/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/traceur-compiler/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/traceur-compiler/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/traceur-compiler/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/traceur-compiler/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/traceur-compiler/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/traceur-compiler/deployments",
        "created_at": "2013-03-27T18:05:40Z",
        "updated_at": "2019-12-02T16:45:54Z",
        "pushed_at": "2018-05-28T04:37:54Z",
        "git_url": "git://github.com/google/traceur-compiler.git",
        "ssh_url": "[email protected]:google/traceur-compiler.git",
        "clone_url": "https://github.com/google/traceur-compiler.git",
        "svn_url": "https://github.com/google/traceur-compiler",
        "homepage": "",
        "size": 27487,
        "stargazers_count": 8033,
        "watchers_count": 8033,
        "language": "JavaScript",
        "has_issues": True,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": True,
        "has_pages": True,
        "forks_count": 604,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 296,
        "license": {
          "key": "apache-2.0",
          "name": "Apache License 2.0",
          "spdx_id": "Apache-2.0",
          "url": "https://api.github.com/licenses/apache-2.0",
          "node_id": "MDc6TGljZW5zZTI="
        },
        "forks": 604,
        "open_issues": 296,
        "watchers": 8033,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      },
      {
        "id": 9065917,
        "node_id": "MDEwOlJlcG9zaXRvcnk5MDY1OTE3",
        "name": "firmata.py",
        "full_name": "google/firmata.py",
        "private": False,
        "owner": {
          "login": "google",
          "id": 1342004,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=",
          "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/google",
          "html_url": "https://github.com/google",
          "followers_url": "https://api.github.com/users/google/followers",
          "following_url": "https://api.github.com/users/google/following{/other_user}",
          "gists_url": "https://api.github.com/users/google/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/google/subscriptions",
          "organizations_url": "https://api.github.com/users/google/orgs",
          "repos_url": "https://api.github.com/users/google/repos",
          "events_url": "https://api.github.com/users/google/events{/privacy}",
          "received_events_url": "https://api.github.com/users/google/received_events",
          "type": "Organization",
          "site_admin": False
        },
        "html_url": "https://github.com/google/firmata.py",
        "description": None,
        "fork": False,
        "url": "https://api.github.com/repos/google/firmata.py",
        "forks_url": "https://api.github.com/repos/google/firmata.py/forks",
        "keys_url": "https://api.github.com/repos/google/firmata.py/keys{/key_id}",
        "collaborators_url": "https://api.github.com/repos/google/firmata.py/collaborators{/collaborator}",
        "teams_url": "https://api.github.com/repos/google/firmata.py/teams",
        "hooks_url": "https://api.github.com/repos/google/firmata.py/hooks",
        "issue_events_url": "https://api.github.com/repos/google/firmata.py/issues/events{/number}",
        "events_url": "https://api.github.com/repos/google/firmata.py/events",
        "assignees_url": "https://api.github.com/repos/google/firmata.py/assignees{/user}",
        "branches_url": "https://api.github.com/repos/google/firmata.py/branches{/branch}",
        "tags_url": "https://api.github.com/repos/google/firmata.py/tags",
        "blobs_url": "https://api.github.com/repos/google/firmata.py/git/blobs{/sha}",
        "git_tags_url": "https://api.github.com/repos/google/firmata.py/git/tags{/sha}",
        "git_refs_url": "https://api.github.com/repos/google/firmata.py/git/refs{/sha}",
        "trees_url": "https://api.github.com/repos/google/firmata.py/git/trees{/sha}",
        "statuses_url": "https://api.github.com/repos/google/firmata.py/statuses/{sha}",
        "languages_url": "https://api.github.com/repos/google/firmata.py/languages",
        "stargazers_url": "https://api.github.com/repos/google/firmata.py/stargazers",
        "contributors_url": "https://api.github.com/repos/google/firmata.py/contributors",
        "subscribers_url": "https://api.github.com/repos/google/firmata.py/subscribers",
        "subscription_url": "https://api.github.com/repos/google/firmata.py/subscription",
        "commits_url": "https://api.github.com/repos/google/firmata.py/commits{/sha}",
        "git_commits_url": "https://api.github.com/repos/google/firmata.py/git/commits{/sha}",
        "comments_url": "https://api.github.com/repos/google/firmata.py/comments{/number}",
        "issue_comment_url": "https://api.github.com/repos/google/firmata.py/issues/comments{/number}",
        "contents_url": "https://api.github.com/repos/google/firmata.py/contents/{+path}",
        "compare_url": "https://api.github.com/repos/google/firmata.py/compare/{base}...{head}",
        "merges_url": "https://api.github.com/repos/google/firmata.py/merges",
        "archive_url": "https://api.github.com/repos/google/firmata.py/{archive_format}{/ref}",
        "downloads_url": "https://api.github.com/repos/google/firmata.py/downloads",
        "issues_url": "https://api.github.com/repos/google/firmata.py/issues{/number}",
        "pulls_url": "https://api.github.com/repos/google/firmata.py/pulls{/number}",
        "milestones_url": "https://api.github.com/repos/google/firmata.py/milestones{/number}",
        "notifications_url": "https://api.github.com/repos/google/firmata.py/notifications{?since,all,participating}",
        "labels_url": "https://api.github.com/repos/google/firmata.py/labels{/name}",
        "releases_url": "https://api.github.com/repos/google/firmata.py/releases{/id}",
        "deployments_url": "https://api.github.com/repos/google/firmata.py/deployments",
        "created_at": "2013-03-27T23:20:35Z",
        "updated_at": "2019-09-23T11:54:02Z",
        "pushed_at": "2013-03-27T23:34:35Z",
        "git_url": "git://github.com/google/firmata.py.git",
        "ssh_url": "[email protected]:google/firmata.py.git",
        "clone_url": "https://github.com/google/firmata.py.git",
        "svn_url": "https://github.com/google/firmata.py",
        "homepage": None,
        "size": 160,
        "stargazers_count": 15,
        "watchers_count": 15,
        "language": "Python",
        "has_issues": True,
        "has_projects": True,
        "has_downloads": True,
        "has_wiki": True,
        "has_pages": False,
        "forks_count": 15,
        "mirror_url": None,
        "archived": False,
        "disabled": False,
        "open_issues_count": 0,
        "license": {
          "key": "apache-2.0",
          "name": "Apache License 2.0",
          "spdx_id": "Apache-2.0",
          "url": "https://api.github.com/licenses/apache-2.0",
          "node_id": "MDc6TGljZW5zZTI="
        },
        "forks": 15,
        "open_issues": 0,
        "watchers": 15,
        "default_branch": "master",
        "permissions": {
          "admin": False,
          "push": False,
          "pull": True
        }
      }
    ],
    ['episodes.dart', 'cpp-netlib', 'dagger', 'ios-webkit-debug-proxy', 'google.github.io', 'kratu', 'build-debian-cloud', 'traceur-compiler', 'firmata.py'],
    ['dagger', 'kratu', 'traceur-compiler', 'firmata.py'],
  )
]

Tasks

0. Parameterize a unit test (mandatory)

Familiarize yourself with the utils.access_nested_map function and understand its purpose. Play with it in the Python console to make sure you understand.

In this task you will write the first unit test for utils.access_nested_map.

Create a TestAccessNestedMap class that inherits from unittest.TestCase.

Implement the TestAccessNestedMap.test_access_nested_map method to test that the method returns what it is supposed to.

Decorate the method with @parameterized.expand to test the function for following inputs:

nested_map={"a": 1}, path=("a",)
nested_map={"a": {"b": 2}}, path=("a",)
nested_map={"a": {"b": 2}}, path=("a", "b")

For each of these inputs, test with assertEqual that the function returns the expected result.

The body of the test method should not be longer than 2 lines.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_utils.py

1. Parameterize a unit test (mandatory)

Implement TestAccessNestedMap.test_access_nested_map_exception. Use the assertRaises context manager to test that a KeyError is raised for the following inputs (use @parameterized.expand):

nested_map={}, path=("a",)
nested_map={"a": 1}, path=("a", "b")

Also make sure that the exception message is as expected.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_utils.py

2. Mock HTTP calls (mandatory)

Familiarize yourself with the utils.get_json function.

Define the TestGetJson(unittest.TestCase) class and implement the TestGetJson.test_get_json method to test that utils.get_json returns the expected result.

We don’t want to make any actual external HTTP calls. Use unittest.mock.patch to patch requests.get. Make sure it returns a Mock object with a json method that returns test_payload which you parametrize alongside the test_url that you will pass to get_json with the following inputs:

test_url="http://example.com", test_payload={"payload": True}
test_url="http://holberton.io", test_payload={"payload": False}

Test that the mocked get method was called exactly once (per input) with test_url as argument.

Test that the output of get_json is equal to test_payload.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_utils.py

3. Parameterize and patch (mandatory)

Read about memoization and familiarize yourself with the utils.memoize decorator.

Implement the TestMemoize(unittest.TestCase) class with a test_memoize method.

Inside test_memoize, define following class

class TestClass:

    def a_method(self):
        return 42

    @memoize
    def a_property(self):
        return self.a_method()

Use unittest.mock.patch to mock a_method. Test that when calling a_property twice, the correct result is returned but a_method is only called once using assert_called_once.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_utils.py

4. Parameterize and patch as decorators (mandatory)

Familiarize yourself with the client.GithubOrgClient class.

In a new test_client.py file, declare the TestGithubOrgClient(unittest.TestCase) class and implement the test_org method.

This method should test that GithubOrgClient.org returns the correct value.

Use @patch as a decorator to make sure get_json is called once with the expected argument but make sure it is not executed.

Use @parameterized.expand as a decorator to parametrize the test with a couple of org examples to pass to GithubOrgClient, in this order:

  • google
  • abc

Of course, no external HTTP calls should be made.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_client.py

5. Mocking a property (mandatory)

memoize turns methods into properties. Read up on how to mock a property (see resource).

Implement the test_public_repos_url method to unit-test GithubOrgClient._public_repos_url.

Use patch as a context manager to patch GithubOrgClient.org and make it return a known payload.

Test that the result of _public_repos_url is the expected one based on the mocked payload.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_client.py

6. More patching (mandatory)

Implement TestGithubOrgClient.test_public_repos to unit-test GithubOrgClient.public_repos.

Use @patch as a decorator to mock get_json and make it return a payload of your choice.

Use patch as a context manager to mock GithubOrgClient._public_repos_url and return a value of your choice.

Test that the list of repos is what you expect from the chosen payload.

Test that the mocked property and the mocked get_json was called once.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_client.py

7. Parameterize (mandatory)

Implement TestGithubOrgClient.test_has_license to unit-test GithubOrgClient.has_license.

Parametrize the test with the following inputs

repo={"license": {"key": "my_license"}}, license_key="my_license"
repo={"license": {"key": "other_license"}}, license_key="my_license"

You should also parameterize the expected returned value.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_client.py

8. Integration test: fixtures (mandatory)

We want to test the GithubOrgClient.public_repos method in an integration test. That means that we will only mock code that sends external requests.

Create the TestIntegrationGithubOrgClient(unittest.TestCase) class and implement the setUpClass and tearDownClass which are part of the unittest.TestCase API.

Use @parameterized_class to decorate the class and parameterize it with fixtures found in fixtures.py. The file contains the following fixtures:

org_payload, repos_payload, expected_repos, apache2_repos

The setupClass should mock requests.get to return example payloads found in the fixtures.

Use patch to start a patcher named get_patcher, and use side_effect to make sure the mock of requests.get(url).json() returns the correct fixtures for the various values of url that you anticipate to receive.

Implement the tearDownClass class method to stop the patcher.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_client.py

9. Integration tests (#advanced)

Implement the test_public_repos method to test GithubOrgClient.public_repos.

Make sure that the method returns the expected results based on the fixtures.

Implement test_public_repos_with_license to test the public_repos with the argument license="apache-2.0" and make sure the result matches the expected value from the fixtures.

Repo:

  • GitHub repository: alx-backend-python
  • Directory: 0x03-Unittests_and_integration_tests
  • File: test_client.py

Copyright © 2024 ALX, All rights reserved.