Skip to content

Commit

Permalink
Init tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ovv committed Jul 24, 2019
1 parent 14bf17d commit a76a670
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 40 deletions.
8 changes: 7 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ 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 @@ -24,7 +30,7 @@ jobs:
- .venv
- run: |
source .venv/bin/activate
tox
tox -e ci -e lint
- save_cache:
key: 'tox-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/testing.txt" }}-{{ checksum "requirements/development.txt" }}-{{ checksum "requirements/production.txt" }}'
paths:
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "3.4"

services:
redis:
image: redis:5-alpine
ports:
- "6379:6379"

postgresql:
image: postgres:11
environment:
POSTGRES_PASSWORD: main
POSTGRES_USER: main
ports:
- "5432:5432"
2 changes: 1 addition & 1 deletion pyslackersweb/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

REDIS_URL = os.getenv("REDIS_URL", "redis://127.0.0.1:6379/0")

POSTGRESQL_DSN = os.getenv("POSTGRESQL_DSN")
POSTGRESQL_DSN = os.getenv("POSTGRESQL_DSN", "postgresql://main:[email protected]:5432/main")

SENTRY_DSN = os.getenv("SENTRY_DSN")

Expand Down
6 changes: 5 additions & 1 deletion pyslackersweb/website/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from marshmallow import Schema, fields


def validate_true(field):
return bool(field)


class InviteSchema(Schema):
email = fields.Email(required=True)
agree_tos = fields.Boolean(required=True)
agree_tos = fields.Boolean(required=True, validate=validate_true)
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
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ apscheduler==3.6.1
gunicorn==19.9.0
marshmallow==3.0.0rc8
sentry-sdk==0.10.2
slack-sansio==1.0.0
slack-sansio==1.1.0
asyncpg==0.18.3
pyyaml==5.1.1
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.
76 changes: 76 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import json
import pytest
import aioredis
import asynctest

import pyslackersweb
import pyslackersweb.website.tasks

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


def pytest_addoption(parser):
parser.addoption("--redis", action="store_true", default=False)
parser.addoption("--postgresql", action="store_true", default=False)


@pytest.fixture
async def app(pytestconfig):
application = await pyslackersweb.app_factory()

if not pytestconfig.getoption("postgresql"):
application.cleanup_ctx.remove(pyslackersweb.contexts.postgresql_pool)

if not pytestconfig.getoption("redis"):
application.cleanup_ctx.remove(pyslackersweb.contexts.redis_pool)

return application


@pytest.fixture
async def redis(loop, pytestconfig):

if pytestconfig.getoption("redis"):
redis = await aioredis.create_redis_pool(pyslackersweb.settings.REDIS_URL)
else:
redis = FakeRedis()

yield redis

if pytestconfig.getoption("redis"):
redis.close()
await redis.wait_closed()


class FakeRedis:
async def get(self, key, *args, **kwargs):
if key == pyslackersweb.website.tasks.GITHUB_REPO_CACHE_KEY:
return json.dumps(
{
"name": "foo",
"descriptions": "bar",
"href": "https://github.com/pyslackers/website",
"stars": 20,
"topics": ["foo", "bar", "baz"],
}
)
elif key == pyslackersweb.website.tasks.SLACK_COUNT_CACHE_KEY:
return 10
elif key == pyslackersweb.website.tasks.SLACK_TZ_CACHE_KEY:
return {"foo": 10, "bar": 20}
else:
raise RuntimeError(f"Mock redis key not found: {key}")

async def hgetall(self, key, *args, **kwargs):
if key == pyslackersweb.website.tasks.SLACK_TZ_CACHE_KEY:
return {"foo": 10, "bar": 20}
else:
raise RuntimeError(f"Mock redis key not found: {key}")

async def set(self, key, *args, **kwargs):
pass

def multi_exec(self):
tx = asynctest.Mock()
tx.execute = asynctest.CoroutineMock()
return tx
Loading

0 comments on commit a76a670

Please sign in to comment.