diff options
author | 2018-07-02 19:27:19 +0000 | |
---|---|---|
committer | 2018-07-02 19:27:19 +0000 | |
commit | f07a74c3ac7592747e6fd1d0ffe62ec65aa24c78 (patch) | |
tree | a8fe7d8ea8a2c754830014bc9b24a0ae216b39fa | |
parent | Merge branch 'momo/optimize-team-list-v2' into 'master' (diff) | |
parent | Add a simple API for off-topic category names. (diff) |
Merge branch 'off-topic-channel-names-api' into 'master'
Add a simple API for off-topic category names.
See merge request python-discord/projects/site!13
-rw-r--r-- | pysite/tables.py | 6 | ||||
-rw-r--r-- | pysite/views/api/bot/off_topic_names.py | 83 | ||||
-rw-r--r-- | tests/test_api_bot_off_topic_names.py | 90 |
3 files changed, 179 insertions, 0 deletions
diff --git a/pysite/tables.py b/pysite/tables.py index de9499e8..784183f8 100644 --- a/pysite/tables.py +++ b/pysite/tables.py @@ -138,6 +138,12 @@ TABLES = { ]) ), + "off_topic_names": Table( # Names for the off-topic category channels + primary_key="name", + keys=("name",), + locked=False + ), + "snake_facts": Table( # Snake facts primary_key="fact", keys=sorted([ diff --git a/pysite/views/api/bot/off_topic_names.py b/pysite/views/api/bot/off_topic_names.py new file mode 100644 index 00000000..e0871067 --- /dev/null +++ b/pysite/views/api/bot/off_topic_names.py @@ -0,0 +1,83 @@ +import random + +from flask import jsonify, request +from schema import And, Schema + +from pysite.base_route import APIView +from pysite.constants import ValidationTypes +from pysite.decorators import api_key, api_params +from pysite.mixins import DBMixin + + +POST_SCHEMA = Schema({ + 'name': And( + str, + len, + lambda name: all(c.isalpha() or c == '-' for c in name), + str.islower, + lambda name: len(name) <= 96, + error=( + "The channel name must be a non-blank string consisting only of" + " lowercase regular characters and '-' with a maximum length of 96" + ) + ) +}) + + +class OffTopicNamesView(APIView, DBMixin): + path = "/bot/off-topic-names" + name = "bot.off_topic_names" + table_name = "off_topic_names" + + @api_key + def get(self): + """ + Fetch all known off-topic channel names from the database. + Returns a list of strings, the strings being the off-topic names. + + If the query argument `random_items` is provided (a non-negative integer), + then this view will return `random_items` random names from the database + instead of returning all items at once. + + API key must be provided as header. + """ + + names = [ + entry['name'] for entry in self.db.get_all(self.table_name) + ] + + if 'random_items' in request.args: + random_count = request.args['random_items'] + if not random_count.isdigit(): + response = {'message': "`random_items` must be a valid integer"} + return jsonify(response), 400 + + samples = random.sample(names, int(random_count)) + return jsonify(samples) + + return jsonify(names) + + @api_key + @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.params) + def post(self, data): + """ + Add a new off-topic channel name to the database. + Expects the new channel's name as the `name` argument. + The name must consist only of alphanumeric characters or minus signs, + and must not be empty or exceed 96 characters. + + Data must be provided as params. + API key must be provided as header. + """ + + if self.db.get(self.table_name, data['name']) is not None: + response = { + 'message': "An entry with the given name already exists" + } + return jsonify(response), 400 + + self.db.insert( + self.table_name, + {'name': data['name']} + ) + return jsonify({'message': 'ok'}) diff --git a/tests/test_api_bot_off_topic_names.py b/tests/test_api_bot_off_topic_names.py new file mode 100644 index 00000000..4c9c782b --- /dev/null +++ b/tests/test_api_bot_off_topic_names.py @@ -0,0 +1,90 @@ +"""Tests the `/api/bot/off-topic-names` endpoint.""" + +from tests import SiteTest, app + + +class EmptyDatabaseOffTopicEndpointTests(SiteTest): + """Tests fetching all entries from the endpoint with an empty database.""" + + def test_get_returns_empty_list(self): + response = self.client.get( + '/bot/off-topic-names', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + self.assertEqual(response.json, []) + + +class AddingANameOffTopicEndpointTests(SiteTest): + """Tests adding a channel name to the database.""" + + def test_returns_400_on_bad_data(self): + response = self.client.post( + '/bot/off-topic-names?name=my%20TOTALLY%20VALID%20CHANNE%20NAME', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert400(response) + + def test_can_add_new_package(self): + response = self.client.post( + '/bot/off-topic-names?name=lemons-lemon-shop', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + + +class AddingChannelNameToDatabaseEndpointTests(SiteTest): + """Tests fetching names from the database with GET.""" + + CHANNEL_NAME = 'bisks-disks' + + def setUp(self): + response = self.client.post( + f'/bot/off-topic-names?name={self.CHANNEL_NAME}', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + + def test_name_is_in_all_entries(self): + response = self.client.get( + '/bot/off-topic-names', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + self.assertIn(self.CHANNEL_NAME, response.json) + + +class RandomSampleEndpointTests(SiteTest): + """Tests fetching random names from the website with GET.""" + + CHANNEL_NAME_1 = 'chicken-shed' + CHANNEL_NAME_2 = 'robot-kindergarten' + + def setUp(self): + response = self.client.post( + f'/bot/off-topic-names?name={self.CHANNEL_NAME_1}', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + + response = self.client.post( + f'/bot/off-topic-names?name={self.CHANNEL_NAME_2}', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + + def test_returns_limited_names_with_random_query_param(self): + response = self.client.get( + '/bot/off-topic-names?random_items=1', + app.config['API_SUBDOMAIN'], + headers=app.config['TEST_HEADER'] + ) + self.assert200(response) + self.assertEqual(len(response.json), 1) |