Skip to content

Commit

Permalink
tests: add unit testing
Browse files Browse the repository at this point in the history
  • Loading branch information
ReenigneArcher committed Apr 22, 2024
1 parent 76a903c commit 6ba2bfe
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 21 deletions.
55 changes: 53 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,64 @@ on:
workflow_dispatch:

jobs:
release:
name: Release
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11

- name: Install Python Dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade -r requirements.txt
python -m pip install --upgrade -r requirements-dev.txt
- name: Restore PRAW refresh token
run: |
mkdir -p ./data
echo ${{ secrets.PRAW_REFRESH_TOKEN }} > ./data/refresh_token
- name: Test with pytest
id: test
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_TEST_BOT_TOKEN }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_TEST_BOT_WEBHOOK }}
GRAVATAR_EMAIL: ${{ secrets.GRAVATAR_EMAIL }}
PRAW_CLIENT_ID: ${{ secrets.PRAW_TEST_CLIENT_ID }}
PRAW_CLIENT_SECRET: ${{ secrets.PRAW_TEST_CLIENT_SECRET }}
PRAW_SUBREDDIT: "AskReddit"
REDIRECT_URI: "http://localhost:8080/"
shell: bash
run: |
python -m pytest \
-rxXs \
--tb=native \
--verbose \
--cov=src \
tests
- name: Cleanup
if: always()
run: |
rm -rf ./data
- name: Upload coverage
# any except canceled or skipped
if: >-
always() &&
(steps.test.outcome == 'success' || steps.test.outcome == 'failure') &&
startsWith(github.repository, 'LizardByte/')
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

- name: Setup Release
id: setup_release
uses: LizardByte/setup-release-action@v2024.419.10846
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ WORKDIR /app/
COPY . .
RUN python -m pip install --no-cache-dir -r requirements.txt

CMD ["python", "./src/main.py"]
CMD ["python", "-m", "src"]
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ platforms such as GitHub discussions/issues could be added.
| IGDB_CLIENT_SECRET | False | None | Required if daily_releases is enabled. |

* Running bot:
* `python ./src/main.py`
* `python -m src`
* Invite bot to server:
* `https://discord.com/api/oauth2/authorize?client_id=<the client id of the bot>&permissions=8&scope=bot%20applications.commands`

Expand All @@ -66,9 +66,9 @@ platforms such as GitHub discussions/issues could be added.

* First run (or manually get a new refresh token):
* Delete `./data/refresh_token` file if needed
* `python ./src/main.py`
* `python -m src`
* Open browser and login to reddit account to use with bot
* Navigate to URL printed in console and accept
* `./data/refresh_token` file is written
* Running after refresh_token already obtained:
* `python ./src/main.py`
* `python -m src`
15 changes: 15 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
codecov:
branch: master

coverage:
status:
project:
default:
target: auto
threshold: 10%

comment:
layout: "diff, flags, files"
behavior: default
require_changes: false # if true: only post the comment if coverage changes
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest==8.1.1
pytest-asyncio==0.23.6
pytest-cov==5.0.0
6 changes: 3 additions & 3 deletions src/main.py → src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

# local imports
if True: # hack for flake8
import discord_bot
import keep_alive
import reddit_bot
from src import discord_bot
from src import keep_alive
from src import reddit_bot

Check warning on line 13 in src/__main__.py

View check run for this annotation

Codecov / codecov/patch

src/__main__.py#L11-L13

Added lines #L11 - L13 were not covered by tests


def main():
Expand Down
2 changes: 1 addition & 1 deletion src/discord_avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import requests

# local imports
import common
from src import common

# avatar
avatar = common.get_bot_avatar(gravatar=os.environ['GRAVATAR_EMAIL'])
Expand Down
24 changes: 18 additions & 6 deletions src/discord_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@
import requests

# local imports
from discord_constants import org_name, bot_name, bot_url
from discord_helpers import igdb_authorization, month_dictionary
from discord_avatar import avatar, avatar_img
from discord_views import DocsCommandView, DonateCommandView, RefundCommandView
from src.discord_constants import org_name, bot_name, bot_url
from src.discord_helpers import igdb_authorization, month_dictionary
from src.discord_avatar import avatar, avatar_img
from src.discord_views import DocsCommandView, DonateCommandView, RefundCommandView


def get_bot(loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()) -> discord.Bot:
asyncio.set_event_loop(loop)
b = discord.Bot(intents=discord.Intents.all(), auto_sync_commands=True, loop=loop)
return b


# constants
bot_token = os.environ['DISCORD_BOT_TOKEN']
bot = discord.Bot(intents=discord.Intents.all(), auto_sync_commands=True)
bot = get_bot()


user_mention_desc = 'Select the user to mention'
recommended_channel_desc = 'Select the recommended channel'
Expand Down Expand Up @@ -522,11 +530,15 @@ def start():
stop()


def stop():
def stop(future: asyncio.Future = None):
print("Attempting to stop daily tasks")
daily_task.stop()
print("Attempting to close bot connection")
if bot_thread is not None and bot_thread.is_alive():
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop)
bot_thread.join()
print("Closed bot")

# Set a result for the future to mark it as done (unit testing)
if future and not future.done():
future.set_result(None)
8 changes: 4 additions & 4 deletions src/discord_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import requests

# local imports
from discord_avatar import avatar
from discord_constants import bot_name
from discord_helpers import get_json
from discord_modals import RefundModal
from src.discord_avatar import avatar
from src.discord_constants import bot_name
from src.discord_helpers import get_json
from src.discord_modals import RefundModal


class DocsCommandDefaultProjects:
Expand Down
2 changes: 1 addition & 1 deletion src/reddit_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from praw.util.token_manager import FileTokenManager

# local imports
import common
from src import common

Check warning on line 19 in src/reddit_bot.py

View check run for this annotation

Codecov / codecov/patch

src/reddit_bot.py#L19

Added line #L19 was not covered by tests

# modify as required
APP = 'lizardbyte-bot'
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# lib imports
import dotenv

dotenv.load_dotenv(override=False) # environment secrets take priority over .env file
43 changes: 43 additions & 0 deletions tests/unit/test_discord_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# standard imports
import asyncio

# lib imports
import pytest
import pytest_asyncio

# local imports
from src import discord_bot
from src import discord_avatar
from src import discord_constants


@pytest_asyncio.fixture
async def bot():
# event_loop fixture is deprecated
_loop = asyncio.get_event_loop()

bot = discord_bot.get_bot(loop=_loop)
future = asyncio.run_coroutine_threadsafe(bot.start(token=discord_bot.bot_token), _loop)
await bot.wait_until_ready() # Wait until the bot is ready
yield bot
discord_bot.stop(future=future)

# wait for the bot to finish
counter = 0
while not future.done() and counter < 30:
await asyncio.sleep(1)
counter += 1
future.cancel() # Cancel the bot when the tests are done


@pytest.mark.asyncio
async def test_bot_on_ready(bot):
assert bot is not None
assert bot.guilds
assert bot.guilds[0].name == "ReenigneArcher's test server"
assert bot.user.id == 939171917578002502
assert bot.user.name == discord_constants.bot_name
assert bot.user.avatar

# compare the bot avatar to our intended avatar
assert await bot.user.avatar.read() == discord_avatar.avatar_img

0 comments on commit 6ba2bfe

Please sign in to comment.