Files
upstream-zulip/zerver/tests/test_checks.py
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

273 lines
11 KiB
Python

import os
import re
from contextlib import ExitStack
from typing import Any
from unittest import mock
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import SystemCheckError
from django.test import override_settings
from typing_extensions import override
from zerver.lib.test_classes import ZulipTestCase
class TestChecks(ZulipTestCase):
@override
def setUp(self) -> None:
super().setUp()
# The base tearDown removes LOCAL_UPLOADS_DIR after each test.
# Since call_command("check") runs check_uploads_settings which
# verifies the directory exists, we must re-create it.
assert settings.LOCAL_UPLOADS_DIR is not None
os.makedirs(settings.LOCAL_UPLOADS_DIR, exist_ok=True)
def assert_check_with_error(self, test: re.Pattern[str] | str | None, **kwargs: Any) -> None:
with open(os.devnull, "w") as DEVNULL, override_settings(**kwargs), ExitStack() as stack:
if isinstance(test, str):
stack.enter_context(self.assertRaisesMessage(SystemCheckError, test))
elif isinstance(test, re.Pattern):
stack.enter_context(self.assertRaisesRegex(SystemCheckError, test))
call_command("check", stdout=DEVNULL)
@override_settings(RUNNING_IN_DOCKER=False)
def test_checks_required_setting(self) -> None:
self.assert_check_with_error(
"(zulip.E001) ZULIP_ADMINISTRATOR is still set to the example value "
"'zulip-admin@example.com'; change it in /etc/zulip/settings.py",
ZULIP_ADMINISTRATOR="zulip-admin@example.com",
)
self.assert_check_with_error(
"(zulip.E001) You must set ZULIP_ADMINISTRATOR in /etc/zulip/settings.py",
ZULIP_ADMINISTRATOR="",
)
self.assert_check_with_error(
"(zulip.E001) You must set ZULIP_ADMINISTRATOR in /etc/zulip/settings.py",
ZULIP_ADMINISTRATOR=None,
)
@override_settings(RUNNING_IN_DOCKER=True)
def test_checks_required_setting_docker(self) -> None:
self.assert_check_with_error(
"(zulip.E001) SETTING_ZULIP_ADMINISTRATOR is still set to the example value "
"'zulip-admin@example.com'; change it in your Docker environment configuration",
ZULIP_ADMINISTRATOR="zulip-admin@example.com",
)
self.assert_check_with_error(
"(zulip.E001) You must set SETTING_ZULIP_ADMINISTRATOR in your Docker environment configuration",
ZULIP_ADMINISTRATOR="",
)
self.assert_check_with_error(
"(zulip.E001) You must set SETTING_ZULIP_ADMINISTRATOR in your Docker environment configuration",
ZULIP_ADMINISTRATOR=None,
)
@override_settings(RUNNING_IN_DOCKER=True, RUNNING_IN_HELM=True)
def test_checks_required_setting_helm(self) -> None:
self.assert_check_with_error(
"(zulip.E001) zulip.environment.SETTING_ZULIP_ADMINISTRATOR is still set to "
"the example value 'zulip-admin@example.com'; change it in your Helm values",
ZULIP_ADMINISTRATOR="zulip-admin@example.com",
)
self.assert_check_with_error(
"(zulip.E001) You must set zulip.environment.SETTING_ZULIP_ADMINISTRATOR in your Helm values",
ZULIP_ADMINISTRATOR="",
)
self.assert_check_with_error(
"(zulip.E001) You must set zulip.environment.SETTING_ZULIP_ADMINISTRATOR in your Helm values",
ZULIP_ADMINISTRATOR=None,
)
@override_settings(RUNNING_IN_DOCKER=True)
def test_checks_required_setting_docker_manual_configuration(self) -> None:
# With MANUAL_CONFIGURATION, the admin manages
# /etc/zulip/settings.py themselves, so the bare-metal
# message is the accurate one.
with mock.patch.dict(os.environ, {"MANUAL_CONFIGURATION": "True"}):
self.assert_check_with_error(
"(zulip.E001) ZULIP_ADMINISTRATOR is still set to the example value "
"'zulip-admin@example.com'; change it in /etc/zulip/settings.py",
ZULIP_ADMINISTRATOR="zulip-admin@example.com",
)
@override_settings(DEVELOPMENT=False, PRODUCTION=True, EXTERNAL_URI_SCHEME="https://")
def test_checks_external_host_domain(self) -> None:
message_re = r"\(zulip\.E002\) EXTERNAL_HOST \(\S+\) does not contain a domain part"
try:
# We default to skippping this check in CI, because
# "testserver" is part of so many tests. We temporarily
# strip out the environment variable we use to detect
# that, so we can trigger the check.
del os.environ["ZULIP_TEST_SUITE"]
self.assert_check_with_error(None, EXTERNAL_HOST="server-1.local")
self.assert_check_with_error(
re.compile(rf"{message_re}\s*HINT: Add .local to the end"), EXTERNAL_HOST="server-1"
)
self.assert_check_with_error(
re.compile(rf"{message_re}\s*HINT: Add .localdomain to the end"),
EXTERNAL_HOST="localhost",
)
finally:
os.environ["ZULIP_TEST_SUITE"] = "true"
def test_checks_external_host_value(self) -> None:
self.assert_check_with_error(None, EXTERNAL_HOST="testserver.local")
self.assert_check_with_error(None, EXTERNAL_HOST="testserver.local:443")
self.assert_check_with_error(None, EXTERNAL_HOST="testserver.local:https")
self.assert_check_with_error(
re.compile(r"EXTERNAL_HOST \(\S+\) is too long"),
EXTERNAL_HOST=("a234567890." * 25 + "local"),
)
self.assert_check_with_error(
re.compile(
r"\(zulip\.E002\) EXTERNAL_HOST \(\S+\) contains non-ASCII characters\n.*xn--wgv71a119e\.example\.com"
),
EXTERNAL_HOST="日本語.example.com",
)
self.assert_check_with_error(
"EXTERNAL_HOST (-bogus-.example.com) does not validate as a hostname",
EXTERNAL_HOST="-bogus-.example.com:443",
)
def test_checks_external_scheme(self) -> None:
self.assert_check_with_error(
None, EXTERNAL_URI_SCHEME="https://", DEVELOPMENT=False, PRODUCTION=True
)
self.assert_check_with_error(
None, EXTERNAL_URI_SCHEME="http://", DEVELOPMENT=True, PRODUCTION=False
)
self.assert_check_with_error(
"Zulip does not support a non-HTTPS external scheme in production",
EXTERNAL_URI_SCHEME="http://",
DEVELOPMENT=False,
PRODUCTION=True,
)
self.assert_check_with_error(
"Zulip does not support a non-HTTPS external scheme in production",
EXTERNAL_URI_SCHEME="https",
DEVELOPMENT=False,
PRODUCTION=True,
)
def test_checks_auth(self) -> None:
self.assert_check_with_error(
(
'SOCIAL_AUTH_SAML_ENABLED_IDPS["idp_name"]["extra_attrs"]: '
"(zulip.E003) zulip_groups can't be listed in extra_attrs"
),
SOCIAL_AUTH_SAML_ENABLED_IDPS={
"idp_name": {
"entity_id": "https://idp.testshib.org/idp/shibboleth",
"url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO",
"attr_user_permanent_id": "email",
"attr_first_name": "first_name",
"attr_last_name": "last_name",
"attr_username": "email",
"attr_email": "email",
"extra_attrs": ["title", "mobilePhone", "zulip_role", "zulip_groups"],
}
},
)
self.assert_check_with_error(
(
'settings.SOCIAL_AUTH_SYNC_ATTRS_DICT["example_org"]["saml"]["custom__groups"]: '
"(zulip.E004) zulip_groups can't be listed as a SAML attribute"
),
SOCIAL_AUTH_SYNC_ATTRS_DICT={
"example_org": {
"saml": {
"role": "zulip_role",
"custom__groups": "zulip_groups",
"custom__title": "title",
"groups": ["group1", "group2", ("samlgroup3", "zulipgroup3"), "group4"],
}
}
},
)
def test_checks_uploads_s3_missing_buckets(self) -> None:
self.assert_check_with_error(
"Neither settings.LOCAL_UPLOADS_DIR nor settings.S3_AUTH_UPLOADS_BUCKET is set",
LOCAL_UPLOADS_DIR=None,
S3_AUTH_UPLOADS_BUCKET="",
S3_AVATAR_BUCKET="some-avatar-bucket",
)
self.assert_check_with_error(
"Neither settings.LOCAL_UPLOADS_DIR nor settings.S3_AVATAR_BUCKET is set",
LOCAL_UPLOADS_DIR=None,
S3_AUTH_UPLOADS_BUCKET="some-uploads-bucket",
S3_AVATAR_BUCKET="",
)
self.assert_check_with_error(
"Neither settings.LOCAL_UPLOADS_DIR nor settings.S3_AUTH_UPLOADS_BUCKET is set",
LOCAL_UPLOADS_DIR=None,
S3_AUTH_UPLOADS_BUCKET="",
S3_AVATAR_BUCKET="",
)
def test_checks_uploads_s3_configured(self) -> None:
self.assert_check_with_error(
None,
LOCAL_UPLOADS_DIR=None,
S3_AUTH_UPLOADS_BUCKET="some-uploads-bucket",
S3_AVATAR_BUCKET="some-avatar-bucket",
)
def test_checks_uploads_local_dir_missing(self) -> None:
self.assert_check_with_error(
"(zulip.E006) settings.LOCAL_UPLOADS_DIR (/nonexistent/path) does not exist",
LOCAL_UPLOADS_DIR="/nonexistent/path",
)
def test_checks_uploads_local_dir_not_writable(self) -> None:
with mock.patch("os.access", return_value=False):
self.assert_check_with_error(
f"(zulip.E006) settings.LOCAL_UPLOADS_DIR ({settings.LOCAL_UPLOADS_DIR}) is not writable",
)
def test_checks_uploads_local_dir_valid(self) -> None:
self.assert_check_with_error(None)
@override_settings(RUNNING_IN_DOCKER=False)
def test_check_jitsi_server_url(self) -> None:
self.assert_check_with_error(
"(zulip.E007) JITSI_SERVER_URL in /etc/zulip/settings.py is not a URL",
JITSI_SERVER_URL="example.jitsi.com",
)
@override_settings(RUNNING_IN_DOCKER=True)
def test_check_jitsi_server_url_in_docker(self) -> None:
self.assert_check_with_error(
"(zulip.E007) SETTING_JITSI_SERVER_URL in your Docker environment configuration is not a URL",
JITSI_SERVER_URL="jitsi[etc]",
)
@override_settings(RUNNING_IN_DOCKER=True, RUNNING_IN_HELM=True)
def test_check_jitsi_server_url_in_helm(self) -> None:
self.assert_check_with_error(
"(zulip.E007) zulip.environment.SETTING_JITSI_SERVER_URL in your Helm values is not a URL",
JITSI_SERVER_URL="example.com/jitsi",
)
@override_settings(JITSI_SERVER_URL="https://example.com/jitsi")
def test_check_jitsi_server_url_valid(self) -> None:
self.assert_check_with_error(None)