Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions github3/repos/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from . import stats
from . import status
from . import tag
from . import topics

from ..decorators import requires_auth

Expand All @@ -51,6 +52,10 @@ class _Repository(models.GitHubCore):
method to ensure that all attributes are present on the object.
"""

PREVIEW_HEADERS = {
'Accept': 'application/vnd.github.mercy-preview+json'
}

STAR_HEADERS = {
'Accept': 'application/vnd.github.v3.star+json'
}
Expand Down Expand Up @@ -2161,6 +2166,31 @@ def remove_collaborator(self, username):
base_url=self._api)
return self._boolean(self._delete(url), 204, 404)

@requires_auth
def replace_topics(self, new_topics):
"""Replace the repository topics with ``new_topics``.

:param topics:
(required), new topics of the repository
:type topics:
list
:returns:
new topics of the repository
:rtype:
:class:`~github3.repos.topics.Topics`
"""
url = self._build_url('topics', base_url=self._api)
data = {'names': new_topics}
json = self._json(
self._put(
url,
data=jsonlib.dumps(data),
headers=self.PREVIEW_HEADERS
),
200
)
return self._instance_or_null(topics.Topics, json)

def stargazers(self, number=-1, etag=None):
"""List users who have starred this repository.

Expand Down Expand Up @@ -2310,6 +2340,18 @@ def teams(self, number=-1, etag=None):
url = self._build_url('teams', base_url=self._api)
return self._iter(int(number), url, orgs.ShortTeam, etag=etag)

def topics(self):
"""Get the topics of this repository.

:returns:
this repository's topics
:rtype:
:class:`~github3.repos.topics.Topics`
"""
url = self._build_url('topics', base_url=self._api)
json = self._json(self._get(url, headers=self.PREVIEW_HEADERS), 200)
return self._instance_or_null(topics.Topics, json)

def tree(self, sha):
"""Get a tree.

Expand Down
20 changes: 20 additions & 0 deletions github3/repos/topics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""Topics related logic."""
from __future__ import unicode_literals

from .. import models


class Topics(models.GitHubCore):
"""Representation of the repository topics.

.. attribute:: names

The names of the topics.
"""
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of adding an __iter__ and a __len__, so that essentially Topics becomes a facade of its names attribute? I didn't do it outright as this pattern (AFAICT) is not used elsewhere in the code base.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not. As you mentioned we don't have that precedent anywhere else.

Further, if GitHub adds more attributes to this endpoint (which I'm guess they will based on the structure of the JSON [1]) then iterating over the Topics object will be more confusing.

It almost feels like we shouldn't have an object for this, but again, I suspect they'll be expanding the response without telling anyone.

[1] I'm assuming that they haven't given up on consistency for the API behaviour which is what this looks like on first blush.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further, if GitHub adds more attributes to this endpoint [...] then iterating over the Topics object will be more confusing.

Good point, that's an excellent argument for avoiding this magic 👍


def _update_attributes(self, topics):
self.names = topics['names']

def _repr(self):
return '<Topics [{0}]>'.format(', '.join(self.names))
1 change: 1 addition & 0 deletions tests/cassettes/Repository_replace_topics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "GET", "uri": "https://api.github.com/repos/jacquerie/flask-shell-bpython"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA62Yb3PiNhDGvwrD2wYM5D8zmWs7vblJp1zbG669yRtG2AIryJJPkqHEk+/eR5KNbSaXOInfJGC0Pz1e7653lfdZ1J9ejM8vJ9eTy5O+kBFd2Ev92W8fd3/y33n46fqBfPuyDcVmMpt/PZ3db/afdzc3fSwmCcXKFSd6M9Ax5XywTPcmlgI/rjLOF8WKexJ+z6hiNHh6rdwJqvrTvM/lmgkgDwYAWTWnV+PJ1agp7++Lf7595uH97Gw2vz2b/eIkkS0xRC0yxUGJjUn1NAj8RT0ZrpmJs2WmqQqlMFSYYSiTIAs8/sP25gyItSogzg24cARLWcHxxoDpoK43Ngk/EuD3devrK1eSc7mD/bHeZ7cIDmbWyw7BxPotCJjlgTQxhcNwG4/25pk2r5TjTPLA/kPkWIjGI1A0ep2kwgiCbDA85oGiqXS0bKlDxVLDpHiltIYpUFKtiWAP5A0omGoQrKhXinAmMKVbBNwrbb1NHqSKbUm4t+5QNKRsC+++hXdkDJzZpzaHv+L5W18zQxckSmwSrgjX9PGk7/Y2WOQunCCrWsX3D1I9ooeHiW2/0JSTkPYQg72IrkjGTc+ViJ4rJz1kTEJE1NshcXukp1nCOFE9KWhPZUIggHu//uUqzhDqV1JtDjKfzVr3TKpc/IFWy3vhgbUAIV+BgbwN3XdAs5Q8wN8i2UJUALKUihj5UiFpI7aBy4P6Vxt9hpKkg5twGOBiKbvwsMMAx7TOaKvEaOMKR9NBmYMiS5a+SLbJvDYbeA50E63ZWlDagWcPqDwoa/pSERHGXcBLUh74Ty4iyLoD2ZYC2JLLZQc0vG8Dh8oDHRP/XjOLbpRatiU10IquOpJtSQe0UZ3EhJNsUQcwXrYG4dGB5pIU5IWnORHrjKy7YB9QiAzbHqzJw4vNUpu8q1gA205QsWXWVfmsaFa1711QN7pwdQWr0K4ter7TauWSWpPlnJIk7KVmpQ23ADWSpTO4jevjDez3l/usttItKQ+qyu9fMMUe7/d68YYpNdd3KgaUDsKmJAX5Tykxsa2G2DAlir7/BgpQkC8JesXhcJjHlLgZIKGqkxrgOQASFcboe9+vOS9J6MsSYtyksbKSI0weXJKoA58fUMD6h/x+3Z5Tj5EUI3YHYh2mzkWTTbVBk90BvGLVdxDSsBUL2wxibVK1gcs/aCZCekI4P0GUGxYyxD1GBfuM0SrTLnzmObglHIH4qYxTpEAHDlPUk/LAD9MRRiS576im1WC2DCiKwS5aEINRbDIaXwxGF4PJaD6+no4m0/HZHdZkadRYczUYXQ7Gp3MsOL2YjtyaNMO0VmGw5HwwwpKr6eR0OrqyS1CqiwzAJxzJPHUi8tQUZo9aYK51XJn/XBlPnz1ZKoxDjlA+yr/X7r89fse2BUB8LBOaoiPyp0maPeDTeNxoaUKZCTyE05P+jhj06mgXqktlGwR7P+taJtELXxD6U6MyO5bjSqrkPQ2Nrl+rSlFt4Y5tWMPQNmyH0dnPvZWAhCkli0M1gWJxKMY4ICsOBWRKRSGoVD5BtrGQCo3bze3wC/1oKiC+OBWc3c57fxQr4I00+q84ebyd28BrHuE1j8QKsA4KYO3UMryYf+L3d/+eP9zNPz70cYDh52/r3JrK/hT6Smc7zxfnDws/2lixRBt3KpJSlcDZ9vzJ3kpxPuLdbmO/9KQtpP4zdkX5kbuF/p4RxK57i5XL/C/uEnxlG63mL4rat2nTRlCzwzFCLSrqrWXxqMaP/wPrRXVIzxUAAA==", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Last-Modified": ["Fri, 13 Jul 2018 02:36:04 GMT"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["9A4E:7DB6:8AB0F67:11AD54DE:5B48110E"], "ETag": ["W/\"7107ffa6cfdb88d58106f3cb7649cbfb\""], "Date": ["Fri, 13 Jul 2018 02:40:15 GMT"], "X-RateLimit-Remaining": ["4992"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["public_repo"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.057600"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": ["repo"], "X-RateLimit-Reset": ["1531452513"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/jacquerie/flask-shell-bpython"}, "recorded_at": "2018-07-13T02:40:15"}, {"request": {"body": {"string": "{\"names\": [\"flask\", \"bpython\", \"python\"]}", "encoding": "utf-8"}, "headers": {"Content-Length": ["41"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.mercy-preview+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "PUT", "uri": "https://api.github.com/repos/jacquerie/flask-shell-bpython/topics"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA6tWykvMTS1WsopWSstJLM5W0lFKKqgsycjPA7KgjNhaAGF4mvYmAAAA", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["9A4E:7DB6:8AB0F76:11AD54FF:5B48110F"], "ETag": ["W/\"8ae34e77215a659579641afb01a6a267\""], "Date": ["Fri, 13 Jul 2018 02:40:15 GMT"], "X-RateLimit-Remaining": ["4991"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["public_repo"], "X-GitHub-Media-Type": ["github.mercy-preview; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.105731"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": ["repo"], "X-RateLimit-Reset": ["1531452513"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/jacquerie/flask-shell-bpython/topics"}, "recorded_at": "2018-07-13T02:40:15"}], "recorded_with": "betamax/0.8.1"}
1 change: 1 addition & 0 deletions tests/cassettes/Repository_topics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "GET", "uri": "https://api.github.com/repos/jacquerie/flask-shell-bpython"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA62Yb2/bNhDGv4qht7NN2/lvIOg2rCgyzO1WuFuRNwYt0RYTitRIyp4t5LvvISVbspEmcqI3TaLyfnx0vDvdMQ94FIwvhxdXo5vRVTeQKmIz9yiY/PZx/UX8LsJPN1v6/esqlI+jyfTb2eThcfN5fXsbYDFNGFYuBDWPPRMzIXrzdGNjJfGfi0yIWbnigYb/ZkxzRp5fq9aS6WCcB0ItuQRybwCQU3N2PRxdDw7l/XX59/fPInyYnE+md+eTX7wkuqKW6lmmBSixtakZE1I8NKP+kts4m2eG6VBJy6TthyohGSnwH1a350AsdQnxbsCDI1jKS05hDJghdb2xTcSRgGJfv76+cqGEUGvYH+t9cQuyN3Ne9ggul29BwCwnysYMDsNrPLmX58aeKMeb5MT9QOQ4iMERaBadJqk0giAXDE850SxVnpbNTah5armSJ0o7MAVK6SWVfEvfgIKpAcGJOlGEN4EpWyHgTrQtbHKSar6i4ca5Q7OQ8RW8+xbekTFwdpO6HP6G83e+5pbNaJS4JFxQYdhTN/B7WyzyD7rIqkbx/YNUj9j+MLHtV5YKGrIOYrATsQXNhO34EtHx5aSDjEmojDprJG6HdgxPuKC6oyTr6ExKBHDn1z99xelD/ULpx73MF7PWn0mViz/Q6nivHFgDEPIVGMh7ZJsWaI6SE/xbJluICkDnSlOrXiskTcQe4HJS/9NFn2U0aeElPAa4WKk2POwxwHFjMtYoMZq4wtMM2eWgzJJ5USSbZF6TDQoOdFNj+FIy1oJn96ic7Gr6XFMZxm3Ad6ScFL/5iKDLFmQ7CmBzoeYt0PC9JR6VExPT4rtmZ+0odWxHOkBrtmhJtiPt0Va3EhNeskPtwfjYWoRHC5p3JJKXnhZULjO6bIO9RyEyXHuwpNtXm6UmeVexAHadoObzrK3yWdGc6qJ3Qd1ow9UVrEL7tujlTquRS2pNlndKkvDXmpUm3BJ0kCytwV1cH2/g/n69z2oq3ZFyUlX+4gNT7vF+r5dfmJ3m+k7lgNJC2OxIJP8ppTZ21RAbplSz979ACSL5nKJX7Pf7ecyonwESplupAQUHQKrDGH3v+zXnOxL6soRaP2ksnOQIk4dQNGrB53sUsMUhv193wanHSIoRuwWxHlPnoslmxqLJbgFeseo7SGX5godNBrEmqXqAyz8YLkPWpUJ0EeWWhxxxj1HBnTFaZdaGzwoOXglXIMVUJhhSoAWHaVaQclIM0xFGJLVpqabVYK4MaIbBLppRi1FsNBhe9gaXvdFgOrwZD0bj4fk91mRpdLDmuje46g3PplhwPhgPL9yaNMO0VmGw5KI3wJLr8ehsPLh2S1CqywzAb7iSee5G5LkpzF21wNyYuDL/uTIev3izVBqHAqF8lH+n7r86/sY2BUB8rBKWoiMqbpMM3+K34fCgpQlVJnEIZ91gTS16dbQL1aNdGwT7YtZ1TGpmRUEIxlZnbizHk1SrBxZaU39WlaLawjV/5AeGrmHbj87F3FsJSLjWqrxUkygW+2KMC7LyUkClTJaCdspHyDYeMmnwurkbfqEfTQXEl7eCk7tp549yBbyRRv+VN493Uxd4h1d4h1diJdiQEli7tQwvp5/Ew/0/F9v76cdtgAuMYv52zq2pDMbQt3O293x5/zArRhsnlhrrb0Uks2vM8LUjqfd1pZ+GT/8D5BPhF0wVAAA=", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Last-Modified": ["Fri, 13 Jul 2018 02:40:15 GMT"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["9AC2:7DB7:B5F3A84:1538CBC2:5B481450"], "ETag": ["W/\"d029658658da401bc28144b5257f64c6\""], "Date": ["Fri, 13 Jul 2018 02:54:09 GMT"], "X-RateLimit-Remaining": ["57"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.046247"], "Vary": ["Accept"], "X-RateLimit-Limit": ["60"], "Cache-Control": ["public, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-RateLimit-Reset": ["1531452470"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/jacquerie/flask-shell-bpython"}, "recorded_at": "2018-07-13T02:54:09"}, {"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.mercy-preview+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"]}, "method": "GET", "uri": "https://api.github.com/repos/jacquerie/flask-shell-bpython/topics"}, "response": {"body": {"string": "", "base64_string": "H4sIAAAAAAAAA6tWykvMTS1WsopWSstJLM5W0lFKKqgsycjPA7KgjNhaAGF4mvYmAAAA", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["9AC2:7DB7:B5F3AD4:1538CC0C:5B481451"], "ETag": ["W/\"61a23bad4105517f68226fb33bebcb2d\""], "Date": ["Fri, 13 Jul 2018 02:54:09 GMT"], "X-RateLimit-Remaining": ["56"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-GitHub-Media-Type": ["github.mercy-preview; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.048572"], "Vary": ["Accept"], "X-RateLimit-Limit": ["60"], "Cache-Control": ["public, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-RateLimit-Reset": ["1531452470"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/repos/jacquerie/flask-shell-bpython/topics"}, "recorded_at": "2018-07-13T02:54:09"}], "recorded_with": "betamax/0.8.1"}
21 changes: 21 additions & 0 deletions tests/integration/test_repos_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,17 @@ def test_remove_collaborator(self):

assert removed_collaborator is True

def test_replace_topics(self):
"""Test the ability to replace the topics of a repository."""
self.token_login()
cassette_name = self.cassette_name('replace_topics')
with self.recorder.use_cassette(cassette_name):
repository = self.gh.repository('jacquerie', 'flask-shell-bpython')
topics = repository.replace_topics(['flask', 'bpython', 'python'])

assert isinstance(topics, github3.repos.topics.Topics)
assert len(topics.names) == 3

def test_stargazers(self):
"""Test the ability to retrieve the stargazers on a repository."""
cassette_name = self.cassette_name('stargazers')
Expand Down Expand Up @@ -1251,6 +1262,16 @@ def test_teams(self):
for team in teams:
assert isinstance(team, github3.orgs.ShortTeam)

def test_topics(self):
"""Test the ability to retrieve topics from a repository."""
cassette_name = self.cassette_name('topics')
with self.recorder.use_cassette(cassette_name):
repository = self.gh.repository('jacquerie', 'flask-shell-bpython')
topics = repository.topics()

assert isinstance(topics, github3.repos.topics.Topics)
assert len(topics.names) == 3

def test_tree(self):
"""Test the ability to retrieve a tree from a repository."""
cassette_name = self.cassette_name('tree')
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_repos_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,16 @@ def test_remove_collaborator_required_username(self):

assert self.session.delete.called is False

def test_replace_topics(self):
"""Verify the request for replacing the topics."""
self.instance.replace_topics(['flask', 'bpython', 'python'])

self.session.put.assert_called_once_with(
url_for('topics'),
data='{"names": ["flask", "bpython", "python"]}',
headers=self.instance.PREVIEW_HEADERS
)

def test_source(self):
"""Verify that the source of the repository can be retrieved."""
source = self.instance.source
Expand Down Expand Up @@ -1000,6 +1010,15 @@ def test_tag_required_sha(self):

assert self.session.get.called is False

def test_topics(self):
"""Verify the request for retrieving the topics."""
self.instance.topics()

self.session.get.assert_called_once_with(
url_for('topics'),
headers=self.instance.PREVIEW_HEADERS
)

def test_tree(self):
"""Verify the request for retrieving a tree."""
self.instance.tree('fake-sha')
Expand Down Expand Up @@ -1664,6 +1683,10 @@ def test_remove_collaborator(self):
"""Show that a user must be authenticated to remove a collaborator."""
self.assert_requires_auth(self.instance.remove_collaborator)

def test_replace_topics(self):
"""Show that a user must be authenticated to replace the topics."""
self.assert_requires_auth(self.instance.replace_topics)

def test_subscription(self):
"""
Show that a user must be authenticated to retrieve the
Expand Down