1 Commits

Author SHA1 Message Date
a2fec2a4af chore(deps): lock file maintenance
All checks were successful
Enforce Conventional Commit PR Title / Validate PR Title (pull_request_target) Successful in 4s
CI / ci (pull_request) Successful in 2m27s
2025-02-10 00:10:09 +00:00
13 changed files with 462 additions and 594 deletions

View File

@@ -12,12 +12,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v5.0.0 uses: actions/checkout@v4.2.2
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Run Hadolint - name: Run Hadolint
uses: hadolint/hadolint-action@v3.3.0 uses: hadolint/hadolint-action@v3.1.0
with: with:
dockerfile: Dockerfile dockerfile: Dockerfile
output-file: hadolint.out output-file: hadolint.out
@@ -25,9 +25,9 @@ jobs:
no-fail: true no-fail: true
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v6 uses: actions/setup-python@v5
with: with:
python-version: "3.14" python-version: "3.13"
- name: uv cache - name: uv cache
uses: actions/cache@v4 uses: actions/cache@v4
@@ -40,15 +40,11 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: uv sync run: uv sync
# - name: Lint
# run: |
# uv run pylint --fail-under=8 --recursive=yes --output-format=parseable --output=lintreport.txt app/ tests/
# cat lintreport.txt
- name: Lint - name: Lint
run: | run: |
uv run pylint --fail-under=8 --recursive=yes --output-format=parseable app/ tests/ uv run pylint --fail-under=8 --recursive=yes --output-format=parseable --output=lintreport.txt app/ tests/
cat lintreport.txt
- name: Unit Test - name: Unit Test
run: | run: |
@@ -58,38 +54,18 @@ jobs:
- name: Minimize uv cache - name: Minimize uv cache
run: uv cache prune --ci run: uv cache prune --ci
- name: Set up environment for Snyk
run: |
uv pip freeze > requirements.txt
mv pyproject.toml pyproject.toml.bak
mv uv.lock uv.lock.bak
- name: Snyk SAST Scan - name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4.2.1
env:
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
- name: Snyk Vulnerability Scan
uses: snyk/actions/python@master uses: snyk/actions/python@master
continue-on-error: true # Sometimes vulns aren't immediately fixable
env: env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with: with:
# command: snyk command: snyk
args: snyk code test #--all-projects --exclude=.archive args: test --all-projects
# - name: SonarQube Scan
# uses: SonarSource/sonarqube-scan-action@v5.2.0
# env:
# SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
# SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
# - name: Snyk Vulnerability Scan
# uses: snyk/actions/python@master
# continue-on-error: true # Sometimes vulns aren't immediately fixable
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# with:
# command: snyk
# args: test --all-projects
- name: Reverse set up environment for Snyk
run: |
rm -f requirements.txt
mv pyproject.toml.bak pyproject.toml
mv uv.lock.bak uv.lock

View File

@@ -1,48 +1,104 @@
name: Release name: Release
on: on:
workflow_dispatch:
schedule: schedule:
- cron: '0 9 * * 0' - cron: "0 9 * * 0"
issue_comment:
types: [created]
jobs: jobs:
# test: manual_trigger:
# name: Test name: Manual Trigger Cleanup
# uses: https://git.tainton.uk/${{ gitea.repository }}/.gitea/workflows/ci.yml@main runs-on: ubuntu-latest
if: ${{ gitea.event_name == 'issue_comment' }}
steps:
- name: Log event metadata
run: |
echo "Issue: ${{ gitea.event.issue.number }}"
echo "Comment: ${{ gitea.event.comment.body }}"
echo "User: ${{ gitea.event.comment.user.login }}"
tag: - name: Stop workflow if required conditions are not met
name: Tag release if: ${{ !contains(gitea.event.issue.number, '436') || !contains(gitea.event.comment.body, '/trigger-release') || !contains(gitea.event.comment.user.login, 'luke') }}
uses: https://git.tainton.uk/actions/gha-workflows/.gitea/workflows/release-with-tag.yaml@main run: exit 1
- name: Delete issue comment
run: |
curl -X DELETE \
-H "Authorization: token ${{ gitea.token }}" \
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/issues/comments/${{ gitea.event.comment.id }}"
# test:
# name: Unit Test
# uses: https://git.tainton.uk/public/webexmemebot/.gitea/workflows/ci.yml@main
# continue-on-error: true
create_release: create_release:
name: Create Release name: Create Release
needs: tag
uses: https://git.tainton.uk/actions/gha-workflows/.gitea/workflows/create-release-preexisting-tag.yaml@main
with:
tag: ${{ needs.tag.outputs.tag_name }}
body: ${{ needs.tag.outputs.changelog }}
secrets:
ACTIONS_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
# get_release_id:
# name: Get Release ID
# runs-on: ubuntu-latest
# needs: create_release
# outputs:
# releaseid: ${{ steps.getid.outputs.releaseid }}
# steps:
# - name: Get Release ID
# id: getid
# run: |
# rid=$(curl -s -X 'GET' \
# -H 'accept: application/json' \
# '${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/latest' | jq -r '.id')
# echo "releaseid=$rid" >> "$GITEA_OUTPUT"
# echo "$rid"
create_docker:
name: Publish Docker Images
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [tag, create_release] # needs: test
outputs:
release_name: ${{ steps.get_next_version.outputs.tag }}
steps:
- name: Check out repository
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
- name: Changes since last tag
id: changes
run: |
rm -f .changes
git log $(git describe --tags --abbrev=0)..HEAD --no-merges --oneline >> .changes
cat .changes
- name: Check for changes
run: |
if [[ -z $(grep '[^[:space:]]' .changes) ]] ; then
echo "changes=false"
echo "changes=false" >> "$GITEA_OUTPUT"
else
echo "changes=true"
echo "changes=true" >> "$GITEA_OUTPUT"
fi
- name: Cancel if no changes
if: steps.changes.outputs.changes == 'false'
run: exit 1
- name: Set server URL
id: set_srvurl
run: |
SRVURL=$(echo "${{ gitea.server_url }}" | sed 's/https:\/\/\(.*\)/\1/')
echo "srvurl=$SRVURL" >> "$GITEA_OUTPUT"
- name: Get next version
uses: TriPSs/conventional-changelog-action@v6
id: get_next_version
with:
git-url: ${{ steps.set_srvurl.outputs.srvurl }}
github-token: ${{ gitea.token }}
preset: "conventionalcommits"
# preset: "angular" # This is the default
skip-commit: true
release-count: 1
output-file: false
create-summary: true
skip-on-empty: true
skip-version-file: true
skip-tag: true
- name: Create release
run: |
curl -s -X POST \
-H "Authorization: token ${{ secrets.ACTIONS_TOKEN }}" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${{ steps.get_next_version.outputs.tag }}\", \"name\": \"${{ steps.get_next_version.outputs.tag }}\", \"body\": \"${{ steps.get_next_version.outputs.changelog }}\"}" \
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases"
build_docker:
name: Build Docker Images
needs: create_release
steps: steps:
- name: Update Docker configuration - name: Update Docker configuration
continue-on-error: true continue-on-error: true
@@ -54,17 +110,11 @@ jobs:
echo "DOCKER_OPTS=\"--insecure-registry ${{ vars.PACKAGES_REGISTRY_URL }}\"" >> /etc/default/docker echo "DOCKER_OPTS=\"--insecure-registry ${{ vars.PACKAGES_REGISTRY_URL }}\"" >> /etc/default/docker
echo "{\"insecure-registries\": [\"${{ vars.PACKAGES_REGISTRY_URL }}\"]}" > /etc/docker/daemon.json echo "{\"insecure-registries\": [\"${{ vars.PACKAGES_REGISTRY_URL }}\"]}" > /etc/docker/daemon.json
- name: Get repo name
id: split
run: echo "repo=${REPO##*/}" >> "$GITEA_OUTPUT"
env:
REPO: ${{ gitea.repository }}
- name: Check out repository - name: Check out repository
uses: actions/checkout@v5.0.0 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ needs.tag.outputs.tag_name }} ref: ${{ needs.create_release.outputs.release_name }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@@ -87,10 +137,10 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
tags: type=semver,pattern=v{{version}},value=${{ needs.tag.outputs.tag_name }}
images: | images: |
ghcr.io/${{ vars.GHCR_USERNAME }}/${{ steps.split.outputs.repo }} ghcr.io/${{ vars.GHCR_USERNAME }}/webexmemebot
${{ vars.PACKAGES_REGISTRY_URL }}/${{ gitea.repository }} ${{ vars.PACKAGES_REGISTRY_URL }}/${{ gitea.repository }}
tags: type=semver,pattern=v{{version}},value=${{ needs.create_release.outputs.release_name }}
- name: Print metadata - name: Print metadata
run: | run: |

View File

@@ -9,25 +9,23 @@ on:
- cron: "@daily" - cron: "@daily"
jobs: jobs:
# sonarqube: sonarqube:
# name: SonarQube
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repo
# uses: actions/checkout@v4.2.2
# - name: SonarQube Scan
# uses: SonarSource/sonarqube-scan-action@v5.2.0
# env:
# SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
# SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
snyk:
name: Snyk
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v5.0.0 uses: actions/checkout@v4.2.2
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4.2.1
env:
SONAR_HOST_URL: ${{ secrets.SONARQUBE_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
snyk:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4.2.2
- name: Snyk - name: Snyk
uses: snyk/actions/python@master uses: snyk/actions/python@master

View File

@@ -1,4 +1,4 @@
FROM python:3.14-slim FROM python:3.13-slim
LABEL maintainer="Luke Tainton <luke@tainton.uk>" LABEL maintainer="Luke Tainton <luke@tainton.uk>"
USER root USER root

View File

@@ -1,13 +1,8 @@
"""Command module for handling the 'exit' command in the Webex meme bot."""
from webex_bot.models.command import Command from webex_bot.models.command import Command
class ExitCommand(Command): class ExitCommand(Command):
"""Command to handle the 'exit' command in the Webex meme bot."""
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the ExitCommand with command keyword and help message."""
super().__init__( super().__init__(
command_keyword="exit", command_keyword="exit",
help_message="Exit", help_message="Exit",
@@ -15,14 +10,11 @@ class ExitCommand(Command):
) )
self.sender: str = "" self.sender: str = ""
def pre_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def pre_execute(self, message, attachment_actions, activity) -> None:
"""Pre-execution logic for the exit command."""
return return
def execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def execute(self, message, attachment_actions, activity) -> None:
"""Execute the exit command."""
return return
def post_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def post_execute(self, message, attachment_actions, activity) -> None:
"""Post-execution logic for the exit command."""
return return

View File

@@ -1,5 +1,3 @@
"""Generates meme images using the memegen.link API."""
import requests import requests
CHAR_REPLACEMENTS: list = [ CHAR_REPLACEMENTS: list = [
@@ -19,11 +17,6 @@ CHAR_REPLACEMENTS: list = [
def get_templates() -> list[dict]: def get_templates() -> list[dict]:
"""Fetches available meme templates from the memegen.link API.
Returns:
list[dict]: A list of dictionaries containing meme template information.
"""
url: str = "https://api.memegen.link/templates" url: str = "https://api.memegen.link/templates"
req: requests.Response = requests.get(url=url, timeout=10) req: requests.Response = requests.get(url=url, timeout=10)
req.raise_for_status() req.raise_for_status()
@@ -47,14 +40,6 @@ def get_templates() -> list[dict]:
def format_meme_string(input_string: str) -> str: def format_meme_string(input_string: str) -> str:
"""Formats a string for use in a meme image URL.
Args:
input_string (str): The string to format.
Returns:
str: The formatted string suitable for meme image URLs.
"""
# https://memegen.link/#special-characters # https://memegen.link/#special-characters
out_string: str = input_string out_string: str = input_string
for char_replacement in CHAR_REPLACEMENTS: for char_replacement in CHAR_REPLACEMENTS:
@@ -63,16 +48,6 @@ def format_meme_string(input_string: str) -> str:
def generate_api_url(template: str, top_str: str, btm_str: str) -> str: def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
"""Generates a meme image URL using the memegen.link API.
Args:
template (str): The template identifier in the format "name.ext".
top_str (str): The text for the top line of the meme.
btm_str (str): The text for the bottom line of the meme.
Returns:
str: The complete URL for the meme image.
"""
tmpl_name: str tmpl_name: str
tmpl_ext: str tmpl_ext: str
tmpl_name, tmpl_ext = template.split(".") tmpl_name, tmpl_ext = template.split(".")
@@ -80,5 +55,7 @@ def generate_api_url(template: str, top_str: str, btm_str: str) -> str:
top_str = format_meme_string(top_str) top_str = format_meme_string(top_str)
btm_str = format_meme_string(btm_str) btm_str = format_meme_string(btm_str)
url: str = f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}" url: str = (
f"https://api.memegen.link/images/{tmpl_name}/{top_str}/{btm_str}.{tmpl_ext}"
)
return url return url

View File

@@ -1,7 +1,5 @@
#!/usr/local/bin/python3 #!/usr/local/bin/python3
"""Main entry point for the Webex Bot application."""
from webex_bot.webex_bot import WebexBot from webex_bot.webex_bot import WebexBot
from app import close, meme from app import close, meme
@@ -20,7 +18,6 @@ def create_bot() -> WebexBot:
def main() -> None: def main() -> None:
"""Main function to run the Webex Bot."""
bot: WebexBot = create_bot() bot: WebexBot = create_bot()
bot.add_command(meme.MakeMemeCommand()) bot.add_command(meme.MakeMemeCommand())
bot.add_command(close.ExitCommand()) bot.add_command(close.ExitCommand())

View File

@@ -1,11 +1,9 @@
"""Generates meme images using the memegen.link API."""
from webex_bot.models.command import Command from webex_bot.models.command import Command
from webex_bot.models.response import Response, response_from_adaptive_card from webex_bot.models.response import Response, response_from_adaptive_card
from webexpythonsdk.models.cards import ( from webexteamssdk.models.cards import (
AdaptiveCard, AdaptiveCard,
Choice, Choice,
ChoiceSet, Choices,
Column, Column,
ColumnSet, ColumnSet,
FontSize, FontSize,
@@ -13,7 +11,7 @@ from webexpythonsdk.models.cards import (
Text, Text,
TextBlock, TextBlock,
) )
from webexpythonsdk.models.cards.actions import OpenUrl, Submit from webexteamssdk.models.cards.actions import OpenUrl, Submit
from app import img from app import img
@@ -24,7 +22,6 @@ class MakeMemeCommand(Command):
"""Class for initial Webex interactive card.""" """Class for initial Webex interactive card."""
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the MakeMemeCommand with command keyword and help message."""
super().__init__( super().__init__(
command_keyword="/meme", command_keyword="/meme",
help_message="Make a Meme", help_message="Make a Meme",
@@ -32,12 +29,10 @@ class MakeMemeCommand(Command):
delete_previous_message=True, delete_previous_message=True,
) )
def pre_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def pre_execute(self, message, attachment_actions, activity) -> None:
"""Pre-execution logic for the MakeMemeCommand."""
return return
def execute(self, message, attachment_actions, activity) -> Response: # pylint: disable=unused-argument def execute(self, message, attachment_actions, activity) -> Response:
"""Execute the MakeMemeCommand and return an adaptive card."""
card_body: list = [ card_body: list = [
ColumnSet( ColumnSet(
columns=[ columns=[
@@ -50,13 +45,13 @@ class MakeMemeCommand(Command):
size=FontSize.MEDIUM, size=FontSize.MEDIUM,
), ),
TextBlock( TextBlock(
"This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.", # pylint: disable=line-too-long "This bot uses memegen.link to generate memes. Click 'View Templates' to view available templates.",
weight=FontWeight.LIGHTER, weight=FontWeight.LIGHTER,
size=FontSize.SMALL, size=FontSize.SMALL,
wrap=True, wrap=True,
), ),
TextBlock( TextBlock(
"Both fields are required. If you do not want to specify a value, please type a space.", # pylint: disable=line-too-long "Both fields are required. If you do not want to specify a value, please type a space.",
weight=FontWeight.LIGHTER, weight=FontWeight.LIGHTER,
size=FontSize.SMALL, size=FontSize.SMALL,
wrap=True, wrap=True,
@@ -70,10 +65,13 @@ class MakeMemeCommand(Command):
Column( Column(
width=1, width=1,
items=[ items=[
ChoiceSet( Choices(
id="meme_type", id="meme_type",
isMultiSelect=False, isMultiSelect=False,
choices=[Choice(title=x["name"], value=x["choiceval"]) for x in TEMPLATES], choices=[
Choice(title=x["name"], value=x["choiceval"])
for x in TEMPLATES
],
), ),
Text(id="text_top", placeholder="Top Text", maxLength=100), Text(id="text_top", placeholder="Top Text", maxLength=100),
Text( Text(
@@ -105,7 +103,6 @@ class MakeMemeCallback(Command):
"""Class to process user data and return meme.""" """Class to process user data and return meme."""
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the MakeMemeCallback with command keyword and help message."""
super().__init__( super().__init__(
card_callback_keyword="make_meme_callback_rbamzfyx", card_callback_keyword="make_meme_callback_rbamzfyx",
delete_previous_message=True, delete_previous_message=True,
@@ -116,8 +113,7 @@ class MakeMemeCallback(Command):
self.meme: str = "" self.meme: str = ""
self.meme_filename: str = "" self.meme_filename: str = ""
def pre_execute(self, message, attachment_actions, activity) -> str: # pylint: disable=unused-argument def pre_execute(self, message, attachment_actions, activity) -> str:
"""Pre-execution logic for the MakeMemeCallback."""
self.meme: str = attachment_actions.inputs.get("meme_type") self.meme: str = attachment_actions.inputs.get("meme_type")
self.text_top: str = attachment_actions.inputs.get("text_top") self.text_top: str = attachment_actions.inputs.get("text_top")
self.text_bottom: str = attachment_actions.inputs.get("text_bottom") self.text_bottom: str = attachment_actions.inputs.get("text_bottom")
@@ -131,12 +127,13 @@ class MakeMemeCallback(Command):
return "Generating your meme..." return "Generating your meme..."
def execute(self, message, attachment_actions, activity) -> Response | None: # pylint: disable=unused-argument def execute(self, message, attachment_actions, activity) -> Response | None:
"""Execute the MakeMemeCallback and return a response with the meme image."""
if self.error: if self.error:
return None return None
self.meme_filename: str = img.generate_api_url(self.meme, self.text_top, self.text_bottom) self.meme_filename: str = img.generate_api_url(
self.meme, self.text_top, self.text_bottom
)
msg: Response = Response( msg: Response = Response(
attributes={ attributes={
"roomId": activity["target"]["globalId"], "roomId": activity["target"]["globalId"],
@@ -146,6 +143,5 @@ class MakeMemeCallback(Command):
) )
return msg return msg
def post_execute(self, message, attachment_actions, activity) -> None: # pylint: disable=unused-argument def post_execute(self, message, attachment_actions, activity) -> None:
"""Post-execution logic for the MakeMemeCallback."""
return return

View File

@@ -8,17 +8,17 @@ authors = [
] ]
requires-python = ">=3.11.2" requires-python = ">=3.11.2"
dependencies = [ dependencies = [
"webex-bot<1.1.0,>=1.0.3", "webex-bot<1.0.0,>=0.5.2",
"pillow<12.0.1,>=12.0.0", "pillow<12.0.0,>=11.0.0",
"astroid<=4.0.1", "astroid<=3.3.8",
] ]
[dependency-groups] [tool.uv]
dev = [ dev-dependencies = [
"black<25.9.1,>=25.9.0", "black<25.2.0,>=25.1.0",
"coverage<8.0.0,>=7.6.10", "coverage<8.0.0,>=7.6.10",
"isort<7.0.1,>=7.0.0", "isort<6.1.0,>=6.0.0",
"pylint<4.1.0,>=4.0.0", "pylint<4.0.0,>=3.3.2",
"pylint-exit<2.0.0,>=1.2.0", "pylint-exit<2.0.0,>=1.2.0",
"pytest<9.0.0,>=8.3.4", "pytest<9.0.0,>=8.3.4",
"pre-commit<5.0.0,>=4.0.1", "pre-commit<5.0.0,>=4.0.1",
@@ -32,6 +32,3 @@ includes = []
[build-system] [build-system]
requires = ["pdm-backend"] requires = ["pdm-backend"]
build-backend = "pdm.backend" build-backend = "pdm.backend"
[tool.black]
line-length = 120

View File

@@ -1,6 +1,6 @@
{ {
"assignAutomerge": false, "assignAutomerge": true,
"assigneesFromCodeOwners": false, "assigneesFromCodeOwners": true,
"dependencyDashboardAutoclose": true, "dependencyDashboardAutoclose": true,
"extends": ["config:recommended"], "extends": ["config:recommended"],
"ignorePaths": ["**/.archive/**"], "ignorePaths": ["**/.archive/**"],

View File

@@ -2,22 +2,19 @@
import os import os
env_vars: dict = { vars: dict = {
"APP_VERSION": "dev", "APP_VERSION": "dev",
"WEBEX_API_KEY": "testing", "WEBEX_API_KEY": "testing",
} }
for var, value in env_vars.items(): for var, value in vars.items():
os.environ[var] = value os.environ[var] = value
# needs to be imported AFTER environment variables are set # needs to be imported AFTER environment variables are set
from app.config import ( from app.config import config # pragma: no cover # noqa: E402
config,
) # pylint: disable=wrong-import-position # pragma: no cover # noqa: E402
def test_config() -> None: def test_config() -> None:
"""Test the configuration settings.""" assert config.webex_token == vars["WEBEX_API_KEY"]
assert config.webex_token == env_vars["WEBEX_API_KEY"] assert config.version == vars["APP_VERSION"]
assert config.version == env_vars["APP_VERSION"]

View File

@@ -29,4 +29,8 @@ def test_error_false() -> None:
callback.text_top = "TEST" callback.text_top = "TEST"
callback.text_bottom = "TEST" callback.text_bottom = "TEST"
result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}}) result: Response = callback.execute(None, None, {"target": {"globalId": "TEST"}})
assert isinstance(result, Response) and result.roomId == "TEST" and result.files[0] == callback.meme_filename assert (
isinstance(result, Response)
and result.roomId == "TEST"
and result.files[0] == callback.meme_filename
)

700
uv.lock generated

File diff suppressed because it is too large Load Diff