Skip to content

Commit

Permalink
Init tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ovv committed Aug 4, 2019
1 parent 4030eb8 commit ea0af01
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 44 deletions.
16 changes: 13 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ jobs:
working_directory: ~/app
docker:
- image: circleci/python:3.7.3-node-browsers
- image: circleci/redis:5.0.5-alpine
- image: circleci/postgres:11.4-alpine
environment:
POSTGRES_USER: main
POSTGRES_DB: main
POSTGRES_PASSWORD: main

steps:
- checkout
- restore_cache:
Expand All @@ -22,9 +29,12 @@ jobs:
key: 'venv-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/testing.txt" }}-{{ checksum "requirements/development.txt" }}-{{ checksum "requirements/production.txt" }}'
paths:
- .venv
- run: |
source .venv/bin/activate
tox
- run:
environment:
POSTGRESQL_DSN: postgresql://main:[email protected]:5432/main
command: |
source .venv/bin/activate
tox
- save_cache:
key: 'tox-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/testing.txt" }}-{{ checksum "requirements/development.txt" }}-{{ checksum "requirements/production.txt" }}'
paths:
Expand Down
6 changes: 5 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
build: .
depends_on:
- redis
- postgresql
environment:
REDIS_URL: "redis://redis:6379/0"
POSTGRESQL_DSN: "postgresql://main:main@postgresql:5432/main"
Expand All @@ -15,7 +16,7 @@ services:
- "8000:8000"
volumes:
- "${PWD}:/app"
- /app/.tox
- tox-data:/app/.tox
command: gunicorn pyslackersweb:app_factory --access-logfile - --bind=0.0.0.0:8000 --worker-class=aiohttp.GunicornWebWorker --reload

redis:
Expand All @@ -26,3 +27,6 @@ services:
environment:
POSTGRES_PASSWORD: main
POSTGRES_USER: main

volumes:
tox-data: {}
5 changes: 4 additions & 1 deletion pyslackersweb/contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
async def apscheduler(app: web.Application) -> AsyncGenerator[None, None]:
app["scheduler"] = app["website_app"]["scheduler"] = AsyncIOScheduler()
app["scheduler"].start()

yield
app["scheduler"].shutdown()

if app["scheduler"].running:
app["scheduler"].shutdown()


async def client_session(app: web.Application) -> AsyncGenerator[None, None]:
Expand Down
2 changes: 1 addition & 1 deletion pyslackersweb/website/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

class InviteSchema(Schema):
email = fields.Email(required=True)
agree_tos = fields.Boolean(required=True)
agree_tos = fields.Boolean(required=True, validate=bool)
32 changes: 19 additions & 13 deletions pyslackersweb/website/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ class Channel:

async def sync_github_repositories(
session: ClientSession, redis: RedisConnection, *, cache_key: str = GITHUB_REPO_CACHE_KEY
) -> None:
) -> List[Repository]:
logger.debug("Refreshing GitHub cache")
repositories = []
try:
async with session.get(
"https://api.github.com/orgs/pyslackers/repos",
headers={"Accept": "application/vnd.github.mercy-preview+json"},
) as r:
repos = await r.json()

repositories = []
for repo in repos:
if repo["archived"]:
continue
Expand All @@ -72,13 +72,15 @@ async def sync_github_repositories(

repositories.sort(key=lambda r: r.stars, reverse=True)

await redis.set(cache_key, json.dumps([x.__dict__ for x in repositories[:6]]))

await redis.set(
cache_key, json.dumps([dataclasses.asdict(repo) for repo in repositories[:6]])
)
except asyncio.CancelledError:
logger.debug("Github cache refresh canceled")
except Exception:
except Exception: # pylint: disable=broad-except
logger.exception("Error refreshing GitHub cache")
raise

return repositories


async def sync_slack_users(
Expand All @@ -87,11 +89,11 @@ async def sync_slack_users(
*,
cache_key_tz: str = SLACK_TZ_CACHE_KEY,
cache_key_count: str = SLACK_COUNT_CACHE_KEY,
):
) -> Counter:
logger.debug("Refreshing slack users cache.")

counter: Counter = Counter()
try:
counter: Counter = Counter()
async for user in slack_client.iter(slack.methods.USERS_LIST, minimum_time=3):
if user["deleted"] or user["is_bot"] or not user["tz"]:
continue
Expand All @@ -112,16 +114,17 @@ async def sync_slack_users(
logger.debug("Slack users cache refresh canceled")
except Exception: # pylint: disable=broad-except
logger.exception("Error refreshing slack users cache")
return

return counter


async def sync_slack_channels(
slack_client: SlackAPI, redis: RedisConnection, *, cache_key: str = SLACK_CHANNEL_CACHE_KEY
) -> None:
) -> List[Channel]:
logger.debug("Refreshing slack channels cache.")

channels = []
try:
channels = []
async for channel in slack_client.iter(slack.methods.CHANNELS_LIST):
channels.append(
Channel(
Expand All @@ -137,10 +140,13 @@ async def sync_slack_channels(

logger.debug("Found %s slack channels", len(channels))

await redis.set(cache_key, json.dumps([x.__dict__ for x in channels]))
await redis.set(
cache_key, json.dumps([dataclasses.asdict(channel) for channel in channels])
)

except asyncio.CancelledError:
logger.debug("Slack channels cache refresh canceled")
except Exception: # pylint: disable=broad-except
logger.exception("Error refreshing slack channels cache")
return

return channels
28 changes: 15 additions & 13 deletions pyslackersweb/website/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
import logging

import slack.exceptions

from aiohttp import web
from aiohttp_jinja2 import template
from marshmallow.exceptions import ValidationError
Expand All @@ -24,7 +26,9 @@ async def get(self):

return {
"member_count": int((await redis.get(SLACK_COUNT_CACHE_KEY, encoding="utf-8")) or 0),
"projects": json.loads(await redis.get(GITHUB_REPO_CACHE_KEY, encoding="utf-8")),
"projects": json.loads(
await redis.get(GITHUB_REPO_CACHE_KEY, encoding="utf-8") or "{}"
),
"sponsors": [
{
"image": self.request.app.router["static"].url_for(
Expand Down Expand Up @@ -65,19 +69,17 @@ async def post(self):

try:
invite = self.schema.load(await self.request.post())
async with self.request.app["client_session"].post(
"https://slack.com/api/users.admin.invite",
headers={"Authorization": f"Bearer {self.request.app['slack_invite_token']}"},
data={"email": invite["email"], "resend": True},
) as r:
body = await r.json()

if body["ok"]:
context["success"] = True
else:
logger.warning("Error sending slack invite: %s", body["error"], extra=body)
context["errors"].update(non_field=[body["error"]])
await self.request.app["slack_client"].query(
url="users.admin.invite", data={"email": invite["email"], "resend": True}
)
context["success"] = True
except ValidationError as e:
context["errors"] = e.normalized_messages()
except slack.exceptions.SlackAPIError as e:
logger.warning("Error sending slack invite: %s", e.error, extra=e.data)
context["errors"].update(non_field=[e.error])
except slack.exceptions.HTTPException:
logger.exception("Error contacting slack API")
context["errors"].update(non_field=["Error contacting slack API"])

return context
1 change: 1 addition & 0 deletions requirements/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pytest-cov==2.7.1
pytest-aiohttp==0.3.0
tox==3.13.2
mypy==0.720
asynctest==0.13.0
Empty file added tests/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest

import pyslackersweb
import pyslackersweb.website.tasks

pytest_plugins = ("slack.tests.plugin",)


@pytest.fixture
async def client(aiohttp_client, slack_client):

application = await pyslackersweb.app_factory()

app_client = await aiohttp_client(application)
app_client.app["scheduler"].shutdown()
app_client.app["website_app"]["slack_client"] = slack_client

return app_client
2 changes: 0 additions & 2 deletions tests/test_nothing.py

This file was deleted.

111 changes: 111 additions & 0 deletions tests/test_website.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import logging

from collections import namedtuple

import pytest
import aiohttp.web

from pyslackersweb.website import tasks

SlackInviteTestParam = namedtuple("Param", "response data expected")


async def test_endpoint_index(client):
r = await client.get("/")

assert r.history[0].url.path == "/"
assert r.history[0].status == 302

assert r.status == 200
assert r.url.path == "/web"


async def test_endpoint_slack(client):
r = await client.get("/web/slack")
assert r.status == 200


@pytest.mark.parametrize(
"slack_client,data,expected",
(
SlackInviteTestParam(
response={},
data={"email": "[email protected]", "agree_tos": True},
expected="successAlert",
),
SlackInviteTestParam(
response={}, data={"agree_tos": True}, expected="Missing data for required field"
),
SlackInviteTestParam(
response={},
data={"email": "[email protected]", "agree_tos": False},
expected="There was an error processing your invite",
),
SlackInviteTestParam(
response={},
data={"email": "foobar", "agree_tos": True},
expected="Not a valid email address",
),
SlackInviteTestParam(
response={"body": {"ok": False, "error": "already_in_team"}},
data={"email": "[email protected]", "agree_tos": True},
expected="Reason: already_in_team",
),
SlackInviteTestParam(
response={"body": {"ok": False, "error": "not_authed"}},
data={"email": "[email protected]", "agree_tos": True},
expected="Reason: not_authed",
),
SlackInviteTestParam(
response={"status": 500},
data={"email": "[email protected]", "agree_tos": True},
expected="Reason: Error contacting slack API",
),
),
indirect=["slack_client"],
)
async def test_endpoint_slack_invite(client, data, expected):
r = await client.post(path="/web/slack", data=data)
html = await r.text()

assert r.status == 200
assert expected in html


async def test_task_sync_github_repositories(client, caplog):

async with aiohttp.ClientSession() as session:
result = await tasks.sync_github_repositories(session, client.app["redis"])

assert result

for record in caplog.records:
assert record.levelno <= logging.INFO


@pytest.mark.parametrize("slack_client", ({"body": ["users_iter", "users"]},), indirect=True)
async def test_task_sync_slack_users(client, caplog):

result = await tasks.sync_slack_users(
client.app["website_app"]["slack_client"], client.app["redis"]
)

assert result
assert len(result) == 1
assert result["America/Los_Angeles"] == 2

for record in caplog.records:
assert record.levelno <= logging.INFO


@pytest.mark.parametrize("slack_client", ({"body": ["channels_iter", "channels"]},), indirect=True)
async def test_task_sync_slack_channels(client, caplog):

result = await tasks.sync_slack_channels(
client.app["website_app"]["slack_client"], client.app["redis"]
)

assert result

for record in caplog.records:
assert record.levelno <= logging.INFO
25 changes: 15 additions & 10 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
[tox]
envlist = py37,lint,fmt
envlist = py37,lint
skipsdist = true

[testenv]
deps = -r requirements/testing.txt
commands = python -m pytest --verbose --cov=pyslackersweb/ --cov-report=term-missing --junit-xml={envdir}/artifacts/test-results.xml tests/
passenv =
REDIS_URL
POSTGRESQL_DSN
commands =
pip install -r requirements/testing.txt
python -m pytest --verbose --cov=pyslackersweb/ --cov-report=term-missing --junit-xml={envdir}/artifacts/test-results.xml {posargs:tests/}

[testenv:lint]
deps =
-r requirements/testing.txt
commands =
pylint ./pyslackersweb
mypy ./pyslackersweb --ignore-missing-imports
pip install -r requirements/testing.txt
black --check .
pylint pyslackersweb tests
mypy . --ignore-missing-imports

[testenv:fmt]
deps = black
commands = black --check pyslackersweb/ tests/
[testenv:autoformat]
commands =
pip install -r requirements/testing.txt
black .

0 comments on commit ea0af01

Please sign in to comment.