bottleneckcrm/backend/scripts/generate_endpoint_index.py
Ashwin dd6ca2958d Add unit tests for organization admin checks and saved views provider
- Implement tests for `isOrgAdminProvider` to validate admin roles based on organization membership and role.
- Create tests for `saved_views_provider` to ensure correct API interactions for saving, renaming, and deleting saved views.
- Enhance ticket tree tests to accurately reflect inactive and merged ticket handling.
- Update CSV import tests to verify error handling and file saving functionality.
- Introduce tests for deal cards to ensure correct display of next steps and task statuses.
2026-09-26 20:38:32 +05:30

96 lines
3.2 KiB
Python

"""Render every route in the OpenAPI schema to docs/api/endpoint-index.md.
The curated API pages are written by hand. This index is not: a hand-maintained
list of 350 operations would be wrong within a week. Re-run this script when
routes change:
cd backend && uv run python scripts/generate_endpoint_index.py
"""
import os
import sys
from pathlib import Path
import django
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crm.settings")
os.environ.setdefault("SECRET_KEY", "generate-endpoint-index-only")
os.environ.setdefault("ADMIN_EMAIL", "admin@localhost")
django.setup()
from drf_spectacular.generators import SchemaGenerator # noqa: E402
OUTPUT = (
Path(__file__).resolve().parent.parent.parent / "docs" / "api" / "endpoint-index.md"
)
METHODS = ("get", "post", "put", "patch", "delete")
HEADER = """# Endpoint index
Every route the API exposes, generated from the OpenAPI schema. The curated
pages in this section explain the endpoints you are most likely to need; this
table exists so nothing is invisible.
Interactive versions of the same schema are served by a running backend at
`/swagger-ui/` and `/api/schema/redoc/`, with the raw document at `/schema/`.
!!! note
This page is generated by `backend/scripts/generate_endpoint_index.py`.
Edit that script, not this file.
| Path | Methods |
| --- | --- |
"""
# What to use instead of a deprecated operation. Whether an operation is
# deprecated is read from the schema (`deprecated=True` in the view's
# `extend_schema`), so a note here only ever decorates what the code already
# says; `render` refuses a note on an operation the code no longer deprecates,
# rather than printing advice about a route that has moved on.
REPLACEMENTS = {
("/api/leads/upload/", "post"): "use `import/preview/` + `import/commit/`",
(
"/api/leads/create-from-site/",
"post",
): "use `/api/public/forms/{org_id}/{form_id}/submit/`",
}
def render(schema) -> list[str]:
"""One table row per path, each method marked when it is deprecated."""
deprecated = {
(path, method)
for path, operations in schema["paths"].items()
for method, operation in operations.items()
if method in METHODS and operation.get("deprecated")
}
stale = sorted(set(REPLACEMENTS) - deprecated)
if stale:
raise SystemExit(
"REPLACEMENTS names operations the schema does not mark deprecated: "
+ ", ".join(f"{m.upper()} {p}" for p, m in stale)
)
rows = []
for path in sorted(schema["paths"]):
labels = []
for method in sorted(m for m in schema["paths"][path] if m in METHODS):
label = method.upper()
if (path, method) in deprecated:
note = REPLACEMENTS.get((path, method))
label += f" (deprecated; {note})" if note else " (deprecated)"
labels.append(label)
if labels:
rows.append(f"| `{path}` | {', '.join(labels)} |")
return rows
def main() -> None:
rows = render(SchemaGenerator().get_schema(request=None, public=True))
OUTPUT.write_text(HEADER + "\n".join(rows) + "\n")
print(f"wrote {OUTPUT}, {len(rows)} paths")
if __name__ == "__main__":
main()