mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
127 lines
3.4 KiB
Python
Executable File
127 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Validates that 3 data sources agree about the structure of Zulip's events API:
|
|
#
|
|
# * Node fixtures for the server_events_dispatch.js tests.
|
|
# * OpenAPI definitions in zerver/openapi/zulip.yaml
|
|
# * The schemas defined in zerver/lib/events_schema.py used for the
|
|
# Zulip server's test suite.
|
|
#
|
|
# We compare the Python and OpenAPI schemas by converting the OpenAPI data
|
|
# into the event_schema style of types and the diffing the schemas.
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
import orjson
|
|
|
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, os.path.dirname(TOOLS_DIR))
|
|
ROOT_DIR = os.path.dirname(TOOLS_DIR)
|
|
|
|
EVENTS_JS = "web/tests/lib/events.cjs"
|
|
|
|
# check for the venv
|
|
from tools.lib import sanity_check
|
|
|
|
sanity_check.check_venv(__file__)
|
|
|
|
USAGE = """
|
|
|
|
This program reads in fixture data for our
|
|
node tests, and then it validates the fixture
|
|
data with checkers from event_schema.py (which
|
|
are the same Python functions we use to validate
|
|
events in test_events.py).
|
|
|
|
It currently takes no arguments.
|
|
"""
|
|
|
|
parser = argparse.ArgumentParser(usage=USAGE)
|
|
parser.parse_args()
|
|
|
|
# We can eliminate the django dependency in event_schema,
|
|
# but unfortunately it"s coupled to modules like validate.py
|
|
# and topic.py.
|
|
import django
|
|
|
|
os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.test_settings"
|
|
django.setup()
|
|
|
|
from zerver.lib import event_schema
|
|
|
|
make_checker = event_schema.__dict__["make_checker"]
|
|
|
|
|
|
def get_event_checker(event: dict[str, Any]) -> Callable[[str, dict[str, Any]], None]:
|
|
# Follow the naming convention to find the event checker.
|
|
# Start by grabbing the event type.
|
|
name = event["type"]
|
|
|
|
# Handle things like AttachmentRemoveEvent
|
|
if "op" in event:
|
|
name += "_" + event["op"].title()
|
|
|
|
# Change to CamelCase
|
|
name = name.replace("_", " ").title().replace(" ", "")
|
|
|
|
# Use EventModernPresence type to check "presence" events
|
|
if name == "Presence":
|
|
name = "Modern" + name
|
|
|
|
# And add the prefix.
|
|
name = "Event" + name
|
|
|
|
if not hasattr(event_schema, name):
|
|
raise ValueError(f"We could not find {name} in event_schemas.py")
|
|
|
|
return make_checker(getattr(event_schema, name))
|
|
|
|
|
|
def check_event(name: str, event: dict[str, Any]) -> None:
|
|
event["id"] = 1
|
|
checker = get_event_checker(event)
|
|
try:
|
|
checker(name, event)
|
|
except AssertionError:
|
|
print(f"\n{EVENTS_JS} has bad data for {name}:\n\n")
|
|
raise
|
|
|
|
|
|
def read_fixtures() -> dict[str, Any]:
|
|
cmd = [
|
|
"node",
|
|
os.path.join(TOOLS_DIR, "node_lib/dump_fixtures.js"),
|
|
]
|
|
schema = subprocess.check_output(cmd)
|
|
return orjson.loads(schema)
|
|
|
|
|
|
def verify_fixtures_are_sorted(names: list[str]) -> None:
|
|
for i in range(1, len(names)):
|
|
if names[i] < names[i - 1]:
|
|
raise Exception(
|
|
f"""
|
|
Please keep your fixtures in order within
|
|
your events.js file. The following
|
|
key is out of order
|
|
|
|
{names[i]}
|
|
"""
|
|
)
|
|
|
|
|
|
def run() -> None:
|
|
fixtures = read_fixtures()
|
|
verify_fixtures_are_sorted(list(fixtures.keys()))
|
|
for name, event in fixtures.items():
|
|
check_event(name, event)
|
|
print(f"Successfully checked {len(fixtures)} fixtures. All tests passed.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|