Compare commits

...

17 Commits

Author SHA1 Message Date
48c84ec319 chore(pip-prod)(deps): bump astroid from 3.3.2 to 3.3.3
Bumps [astroid](https://github.com/pylint-dev/astroid) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/pylint-dev/astroid/releases)
- [Changelog](https://github.com/pylint-dev/astroid/blob/main/ChangeLog)
- [Commits](https://github.com/pylint-dev/astroid/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: astroid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 19:10:12 +01:00
1611f1dce6 chore(pip-prod)(deps): bump platformdirs from 4.3.3 to 4.3.6
Bumps [platformdirs](https://github.com/tox-dev/platformdirs) from 4.3.3 to 4.3.6.
- [Release notes](https://github.com/tox-dev/platformdirs/releases)
- [Changelog](https://github.com/tox-dev/platformdirs/blob/main/CHANGES.rst)
- [Commits](https://github.com/tox-dev/platformdirs/compare/4.3.3...4.3.6)

---
updated-dependencies:
- dependency-name: platformdirs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 21:18:15 +01:00
bcce79b5f0 chore(pip-prod)(deps): bump virtualenv from 20.26.4 to 20.26.5
Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.26.4 to 20.26.5.
- [Release notes](https://github.com/pypa/virtualenv/releases)
- [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/virtualenv/compare/20.26.4...20.26.5)

---
updated-dependencies:
- dependency-name: virtualenv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 19:34:16 +01:00
90be5d3a79 chore(pip-prod)(deps): bump filelock from 3.16.0 to 3.16.1
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.16.0 to 3.16.1.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.16.0...3.16.1)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 18:46:02 +01:00
3d7354bd27 chore(pip-prod)(deps): bump idna from 3.8 to 3.10
Bumps [idna](https://github.com/kjd/idna) from 3.8 to 3.10.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.8...v3.10)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 22:21:40 +01:00
4041beb667 chore(pip-prod)(deps): bump platformdirs from 4.3.2 to 4.3.3
Bumps [platformdirs](https://github.com/tox-dev/platformdirs) from 4.3.2 to 4.3.3.
- [Release notes](https://github.com/tox-dev/platformdirs/releases)
- [Changelog](https://github.com/tox-dev/platformdirs/blob/main/CHANGES.rst)
- [Commits](https://github.com/tox-dev/platformdirs/compare/4.3.2...4.3.3)

---
updated-dependencies:
- dependency-name: platformdirs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 20:54:44 +01:00
9013262ed2 chore(pip-prod)(deps): bump identify from 2.6.0 to 2.6.1
Bumps [identify](https://github.com/pre-commit/identify) from 2.6.0 to 2.6.1.
- [Commits](https://github.com/pre-commit/identify/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: identify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 19:16:10 +01:00
59536eac2c chore(pip-prod)(deps): bump urllib3 from 2.2.2 to 2.2.3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.2.2...2.2.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-12 18:26:11 +01:00
268e7e7f6a chore(pip-prod)(deps): bump filelock from 3.15.4 to 3.16.0
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.15.4 to 3.16.0.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.15.4...3.16.0)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-10 18:24:48 +01:00
5cfe2a41d4 chore(pip-prod)(deps): bump platformdirs from 4.2.2 to 4.3.2
Bumps [platformdirs](https://github.com/platformdirs/platformdirs) from 4.2.2 to 4.3.2.
- [Release notes](https://github.com/platformdirs/platformdirs/releases)
- [Changelog](https://github.com/tox-dev/platformdirs/blob/main/CHANGES.rst)
- [Commits](https://github.com/platformdirs/platformdirs/compare/4.2.2...4.3.2)

---
updated-dependencies:
- dependency-name: platformdirs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 19:54:49 +01:00
94a9e7fc10 chore(pip-prod)(deps): bump virtualenv from 20.26.3 to 20.26.4
Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.26.3 to 20.26.4.
- [Release notes](https://github.com/pypa/virtualenv/releases)
- [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pypa/virtualenv/compare/20.26.3...20.26.4)

---
updated-dependencies:
- dependency-name: virtualenv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 19:47:58 +01:00
4305728d53 chore(pip-prod)(deps): bump sentry-sdk from 2.13.0 to 2.14.0
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.13.0 to 2.14.0.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.13.0...2.14.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 19:12:49 +01:00
c0f06a8555 chore(pip-prod)(deps): bump certifi from 2024.7.4 to 2024.8.30
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.7.4 to 2024.8.30.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.07.04...2024.08.30)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 20:10:57 +01:00
b758d0dfda feat(security): add approved rooms/users/domains as env variables (#277)
* feat(security): add approved rooms/users/domains as env variables
* chore(lint): fix R0902
* chore(lint): fix C0114
* chore(lint): fix C0116
* chore(lint): fix C0413
2024-08-30 19:38:56 +01:00
56f1cb924e chore(pip-prod)(deps): bump pyparsing from 3.1.2 to 3.1.4
Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.1.2 to 3.1.4.
- [Release notes](https://github.com/pyparsing/pyparsing/releases)
- [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES)
- [Commits](https://github.com/pyparsing/pyparsing/compare/pyparsing_3.1.2...3.1.4)

---
updated-dependencies:
- dependency-name: pyparsing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 18:40:22 +01:00
4d4514dd95 chore(pip-prod)(deps): bump idna from 3.7 to 3.8
Bumps [idna](https://github.com/kjd/idna) from 3.7 to 3.8.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.7...v3.8)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-23 18:05:14 +01:00
c7128daf73 chore(pip-prod)(deps): bump webex-bot from 0.5.1 to 0.5.2
Bumps [webex-bot](https://github.com/fbradyirl/webex_bot) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/fbradyirl/webex_bot/releases)
- [Commits](https://github.com/fbradyirl/webex_bot/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: webex-bot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-21 18:39:44 +01:00
9 changed files with 120 additions and 39 deletions

View File

@ -1,8 +1,11 @@
APP_LIFECYCLE="dev"
SENTRY_ENABLED="False"
SENTRY_DSN=""
ADMIN_EMAIL="" ADMIN_EMAIL=""
ADMIN_FIRST_NAME="" ADMIN_FIRST_NAME=""
APP_LIFECYCLE="dev"
APPROVED_DOMAINS="example.com,hello.com"
APPROVED_ROOMS="abc123,def456"
APPROVED_USERS="bob@example.com,john@me.com"
BOT_NAME="" BOT_NAME=""
N8N_WEBHOOK_URL="" N8N_WEBHOOK_URL=""
SENTRY_DSN=""
SENTRY_ENABLED="False"
WEBEX_API_KEY="" WEBEX_API_KEY=""

View File

@ -9,8 +9,14 @@ Add tasks to a Wekan to do list via Webex and n8n.
3. Edit `.env` as required: 3. Edit `.env` as required:
- `ADMIN_EMAIL` - comma-separated list of admin (who owns the to-do list) email addresses - `ADMIN_EMAIL` - comma-separated list of admin (who owns the to-do list) email addresses
- `ADMIN_FIRST_NAME` - admin first name - `ADMIN_FIRST_NAME` - admin first name
- `APP_LIFECYCLE` - for use in Sentry only, set the name of the environment
- `APPROVED_DOMAINS` - comma-separated list of domains that users are allowed to message the bot from
- `APPROVED_ROOMS` - comma-separated list of room IDs that users are allowed to message the bot from
- `APPROVED_USERS` - comma-separated list of email addresses of approved users
- `BOT_NAME` - Webex bot name - `BOT_NAME` - Webex bot name
- `N8N_WEBHOOK_URL` - n8n webhook URL - `N8N_WEBHOOK_URL` - n8n webhook URL
- `SENTRY_DSN` - for use in Sentry only, set the DSN of the Sentry project
- `SENTRY_ENABLED` - for use in Sentry only, enable sending data to Sentry
- `WEBEX_API_KEY` - Webex API key - `WEBEX_API_KEY` - Webex API key
## How to use ## How to use

View File

@ -2,14 +2,12 @@
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.stdlib import StdlibIntegration from sentry_sdk.integrations.stdlib import StdlibIntegration
from webex_bot.webex_bot import WebexBot from webex_bot.webex_bot import WebexBot
from app.commands.exit import ExitCommand from app.commands.exit import ExitCommand
from app.commands.submit_task import SubmitTaskCommand from app.commands.submit_task import SubmitTaskCommand
from app.utils.config import config from app.utils.config import config
if config.sentry_enabled: if config.sentry_enabled:
apm = sentry_sdk.init( apm = sentry_sdk.init(
dsn=config.sentry_dsn, dsn=config.sentry_dsn,
@ -17,7 +15,7 @@ if config.sentry_enabled:
environment=config.environment, environment=config.environment,
release=config.version, release=config.version,
integrations=[StdlibIntegration()], integrations=[StdlibIntegration()],
spotlight=True spotlight=True,
) )
@ -26,7 +24,9 @@ def create_bot() -> WebexBot:
webex_bot: WebexBot = WebexBot( webex_bot: WebexBot = WebexBot(
bot_name=config.bot_name, bot_name=config.bot_name,
teams_bot_token=config.webex_token, teams_bot_token=config.webex_token,
approved_domains=["cisco.com"], approved_domains=config.approved_domains,
approved_rooms=config.approved_rooms,
approved_users=config.approved_users,
) )
webex_bot.commands.clear() webex_bot.commands.clear()
webex_bot.add_command(SubmitTaskCommand()) webex_bot.add_command(SubmitTaskCommand())

View File

@ -2,19 +2,19 @@
import os import os
from app.utils.helpers import validate_email_syntax
class Config: class Config:
"""Configuration module.""" """Configuration module."""
def __init__(self) -> None: def __init__(self) -> None:
"""Configuration module.""" """Configuration module."""
self.__environment: str = os.environ.get("APP_LIFECYCLE", "DEV").upper()
self.__version: str = os.environ["APP_VERSION"] # Sentry config needs to be processed first for loop prevention.
self.__bot_name: str = os.environ["BOT_NAME"]
self.__webex_token: str = os.environ["WEBEX_API_KEY"]
self.__admin_first_name: str = os.environ["ADMIN_FIRST_NAME"]
self.__admin_emails: list = os.environ["ADMIN_EMAIL"].split(",")
self.__n8n_webhook_url: str = os.environ["N8N_WEBHOOK_URL"]
self.__sentry_dsn: str = os.environ.get("SENTRY_DSN", "") self.__sentry_dsn: str = os.environ.get("SENTRY_DSN", "")
self.__sentry_enabled: bool = bool( self.__sentry_enabled: bool = bool(
os.environ.get("SENTRY_ENABLED", "False").upper() == "TRUE" os.environ.get("SENTRY_ENABLED", "False").upper() == "TRUE"
and self.__sentry_dsn != "" and self.__sentry_dsn != ""
@ -23,12 +23,12 @@ class Config:
@property @property
def environment(self) -> str: def environment(self) -> str:
"""Returns the current app lifecycle.""" """Returns the current app lifecycle."""
return self.__environment return os.environ.get("APP_LIFECYCLE", "DEV").upper()
@property @property
def version(self) -> str: def version(self) -> str:
"""Returns the current app version.""" """Returns the current app version."""
return self.__version return os.environ["APP_VERSION"]
@property @property
def sentry_enabled(self) -> bool: def sentry_enabled(self) -> bool:
@ -46,27 +46,46 @@ class Config:
@property @property
def bot_name(self) -> str: def bot_name(self) -> str:
"""Returns the bot name.""" """Returns the bot name."""
return self.__bot_name return os.environ["BOT_NAME"]
@property @property
def webex_token(self) -> str: def webex_token(self) -> str:
"""Returns the Webex API key.""" """Returns the Webex API key."""
return self.__webex_token return os.environ["WEBEX_API_KEY"]
@property @property
def admin_first_name(self) -> str: def admin_first_name(self) -> str:
"""Returns the first name of the bot admin.""" """Returns the first name of the bot admin."""
return self.__admin_first_name return os.environ["ADMIN_FIRST_NAME"]
@property @property
def admin_emails(self) -> list: def admin_emails(self) -> list:
"""Returns a list of admin email addresses.""" """Returns a list of admin email addresses."""
return self.__admin_emails return os.environ["ADMIN_EMAIL"].split(",")
@property @property
def n8n_webhook_url(self) -> str: def n8n_webhook_url(self) -> str:
"""Returns the n8n webhook URL.""" """Returns the n8n webhook URL."""
return self.__n8n_webhook_url return os.environ["N8N_WEBHOOK_URL"]
@property
def approved_users(self) -> list:
"""Returns a list of approved users."""
emails: list[str] = os.environ.get("APPROVED_USERS", "").split(",")
emails = [i.strip() for i in emails if validate_email_syntax(i.strip())]
return emails
@property
def approved_rooms(self) -> list:
"""Returns a list of approved rooms."""
rooms: list[str] = os.environ.get("APPROVED_ROOMS", "").split(",")
return [i.strip() for i in rooms]
@property
def approved_domains(self) -> list:
"""Returns a list of approved domains."""
domains: list[str] = os.environ.get("APPROVED_DOMAINS", "").split(",")
return [i.strip() for i in domains]
config: Config = Config() config: Config = Config()

16
app/utils/helpers.py Normal file
View File

@ -0,0 +1,16 @@
"""Standalone helper functions."""
import re
def validate_email_syntax(email: str) -> bool:
"""Validate email syntax.
Args:
email (str): Email address.
Returns:
bool: True if valid, else False.
"""
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
return re.match(pattern, email) is not None

View File

@ -1,20 +1,20 @@
appdirs==1.4.4 appdirs==1.4.4
astroid==3.3.2 astroid==3.3.3
attrs==24.2.0 attrs==24.2.0
autopep8==2.3.1 autopep8==2.3.1
backoff==2.2.1 backoff==2.2.1
certifi==2024.7.4 certifi==2024.8.30
cfgv==3.4.0 cfgv==3.4.0
charset-normalizer==3.3.2 charset-normalizer==3.3.2
click==8.1.7 click==8.1.7
coloredlogs==15.0.1 coloredlogs==15.0.1
dill==0.3.8 dill==0.3.8
distlib==0.3.8 distlib==0.3.8
filelock==3.15.4 filelock==3.16.1
future==1.0.0 future==1.0.0
humanfriendly==10.0 humanfriendly==10.0
identify==2.6.0 identify==2.6.1
idna==3.7 idna==3.10
iniconfig==2.0.0 iniconfig==2.0.0
lazy-object-proxy==1.10.0 lazy-object-proxy==1.10.0
mccabe==0.7.0 mccabe==0.7.0
@ -22,25 +22,25 @@ mypy-extensions==1.0.0
nodeenv==1.9.1 nodeenv==1.9.1
packaging==24.1 packaging==24.1
pathspec==0.12.1 pathspec==0.12.1
platformdirs==4.2.2 platformdirs==4.3.6
pluggy==1.5.0 pluggy==1.5.0
py==1.11.0 py==1.11.0
pycodestyle==2.12.1 pycodestyle==2.12.1
PyJWT==2.9.0 PyJWT==2.9.0
pyparsing==3.1.2 pyparsing==3.1.4
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-dotenv==1.0.1 python-dotenv==1.0.1
PyYAML==6.0.2 PyYAML==6.0.2
requests==2.32.3 requests==2.32.3
requests-toolbelt==1.0.0 requests-toolbelt==1.0.0
sentry-sdk==2.13.0 sentry-sdk==2.14.0
six==1.16.0 six==1.16.0
toml==0.10.2 toml==0.10.2
tomli==2.0.1 tomli==2.0.1
tomlkit==0.13.2 tomlkit==0.13.2
urllib3==2.2.2 urllib3==2.2.3
virtualenv==20.26.3 virtualenv==20.26.5
webex-bot==0.5.1 webex-bot==0.5.2
webexteamssdk==1.6.1 webexteamssdk==1.6.1
websockets==11.0.3 websockets==11.0.3
wrapt==1.16.0 wrapt==1.16.0

View File

@ -1,3 +1,3 @@
export $(cat .env | xargs) export $(cat .env.test | xargs)
python -B -m app.main python -B -m app.main
unset APP_LIFECYCLE SENTRY_ENABLED SENTRY_DSN BOT_NAME WEBEX_API_KEY ADMIN_FIRST_NAME ADMIN_EMAIL N8N_WEBHOOK_URL unset ADMIN_EMAIL ADMIN_FIRST_NAME APP_LIFECYCLE APP_VERSION APPROVED_DOMAINS APPROVED_ROOMS APPROVED_USERS BOT_NAME N8N_WEBHOOK_URL SENTRY_DSN SENTRY_ENABLED WEBEX_API_KEY

View File

@ -1,10 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# ruff: noqa: E402 pylint: disable=wrong-import-position
"""Provides test cases for app/utils/config.py.""" """Provides test cases for app/utils/config.py."""
import os import os
vars: dict = { vars: dict = {
"APP_VERSION": "dev", "APP_VERSION": "dev",
"BOT_NAME": "TestBot", "BOT_NAME": "TestBot",
@ -13,7 +14,10 @@ vars: dict = {
"ADMIN_EMAIL": "test@test.com", "ADMIN_EMAIL": "test@test.com",
"N8N_WEBHOOK_URL": "https://n8n.test.com/webhook/abcdefg", "N8N_WEBHOOK_URL": "https://n8n.test.com/webhook/abcdefg",
"SENTRY_ENABLED": "false", "SENTRY_ENABLED": "false",
"SENTRY_DSN": "http://localhost" "SENTRY_DSN": "http://localhost",
"APPROVED_USERS": "test@test.com",
"APPROVED_DOMAINS": "test.com",
"APPROVED_ROOMS": "test",
} }
@ -25,8 +29,18 @@ from app.utils.config import config # pragma: no cover
def test_config() -> None: def test_config() -> None:
assert config.bot_name == vars["BOT_NAME"]
assert config.webex_token == vars["WEBEX_API_KEY"]
assert config.admin_first_name == vars["ADMIN_FIRST_NAME"]
assert config.admin_emails == vars["ADMIN_EMAIL"].split(",") assert config.admin_emails == vars["ADMIN_EMAIL"].split(",")
assert config.admin_first_name == vars["ADMIN_FIRST_NAME"]
assert config.approved_domains == vars["APPROVED_DOMAINS"].split(",")
assert config.approved_rooms == vars["APPROVED_ROOMS"].split(",")
assert config.approved_users == vars["APPROVED_USERS"].split(",")
assert config.bot_name == vars["BOT_NAME"]
assert config.n8n_webhook_url == vars["N8N_WEBHOOK_URL"] assert config.n8n_webhook_url == vars["N8N_WEBHOOK_URL"]
assert config.sentry_enabled == bool(vars["SENTRY_ENABLED"].upper() == "TRUE")
assert config.version == vars["APP_VERSION"]
assert config.webex_token == vars["WEBEX_API_KEY"]
if config.sentry_enabled:
assert config.sentry_dsn == vars["SENTRY_DSN"]
else:
assert config.sentry_dsn == ""

23
tests/test_helpers.py Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
"""Provides test cases for app/utils/helpers.py."""
from app.utils.helpers import validate_email_syntax # pragma: no cover
def test_validate_email_syntax_true() -> None:
"""Test validate_email_syntax() with a real email address."""
email: str = "test@test.com"
assert validate_email_syntax(email)
def test_validate_email_syntax_false1() -> None:
"""Test validate_email_syntax() with an invalid email address."""
email: str = "test@test"
assert not validate_email_syntax(email)
def test_validate_email_syntax_false2() -> None:
"""Test validate_email_syntax() with an invalid email address."""
email: str = "test"
assert not validate_email_syntax(email)