#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import os
import sys
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Dict
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from server import ( # noqa: E402
classroom_accept_invitation,
classroom_activate_course,
classroom_add_teacher_to_course,
classroom_archive_course,
classroom_create_course,
classroom_create_invitation,
classroom_delete_course,
classroom_delete_invitation,
classroom_get_invitation,
classroom_list_course_students,
classroom_list_course_teachers,
classroom_list_invitations,
classroom_remove_student_from_course,
classroom_remove_teacher_from_course,
classroom_set_course_state,
classroom_update_course,
)
def _arg_or_env(value: str | None, env_name: str) -> str | None:
return value if value else os.getenv(env_name)
def _require(value: str | None, env_name: str) -> str:
resolved = _arg_or_env(value, env_name)
if not resolved:
raise RuntimeError(f"Missing required value. Provide --{env_name.lower()} or set {env_name}.")
return resolved
def _record(
report: Dict[str, Any],
name: str,
ok: bool,
summary: Dict[str, Any] | None = None,
error: str | None = None,
) -> None:
item: Dict[str, Any] = {"name": name, "ok": ok}
if summary is not None:
item["summary"] = summary
if error is not None:
item["error"] = error
report["tests"].append(item)
print(("PASS" if ok else "FAIL"), name, ("" if ok else error))
def _cleanup(report: Dict[str, Any], name: str, ok: bool, detail: str) -> None:
report["cleanup"].append({"name": name, "ok": ok, "detail": detail})
print(("OK" if ok else "WARN"), f"cleanup::{name}", detail)
def main() -> int:
parser = argparse.ArgumentParser(
description="Admin-focused smoke test for Classroom MCP course/teacher/invitation tools."
)
parser.add_argument("--admin-email")
parser.add_argument("--teacher-email")
parser.add_argument("--student-email")
parser.add_argument("--keep-artifacts", action="store_true")
args = parser.parse_args()
admin_email = _require(args.admin_email, "ADMIN_SMOKE_ADMIN_EMAIL")
teacher_email = _require(args.teacher_email, "ADMIN_SMOKE_TEACHER_EMAIL")
student_email = _require(args.student_email, "ADMIN_SMOKE_STUDENT_EMAIL")
stamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S")
report: Dict[str, Any] = {
"timestamp_utc": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
"phase": "admin_smoke_test",
"inputs": {
"admin_email": admin_email,
"teacher_email": teacher_email,
"student_email": student_email,
"keep_artifacts": args.keep_artifacts,
},
"tests": [],
"cleanup": [],
}
course_id: str | None = None
invitation_id: str | None = None
teacher_added = False
student_joined = False
success = True
try:
created = classroom_create_course(
name=f"ZZ ADMIN SMOKE {stamp}",
section="Sandbox",
description_heading="Admin smoke test",
description="Temporary course for MCP validation",
room="Lab",
course_state="PROVISIONED",
acting_user_email=admin_email,
)
course_id = created.get("id")
_record(
report,
"create_course",
True,
{"course_id": course_id, "state": created.get("courseState")},
)
updated = classroom_update_course(
course_id=course_id,
description="Updated by admin smoke",
room="Lab-2",
acting_user_email=admin_email,
)
_record(
report,
"update_course",
True,
{"room": updated.get("room"), "description": updated.get("description")},
)
active = classroom_activate_course(
course_id=course_id,
acting_user_email=admin_email,
)
_record(report, "activate_course", True, {"state": active.get("courseState")})
t = classroom_add_teacher_to_course(
course_id=course_id,
teacher_email=teacher_email,
acting_user_email=admin_email,
)
teacher_added = True
_record(report, "add_teacher_to_course", True, {"userId": t.get("userId")})
tlist = classroom_list_course_teachers(
course_id=course_id, acting_user_email=admin_email
)
_record(report, "list_course_teachers", True, {"count": tlist.get("count")})
inv = classroom_create_invitation(
course_id=course_id,
user_id=student_email,
role="STUDENT",
acting_user_email=admin_email,
)
invitation_id = inv.get("id")
_record(
report,
"create_student_invitation",
True,
{"invitation_id": invitation_id, "role": inv.get("role")},
)
inv_got = classroom_get_invitation(
invitation_id=invitation_id, acting_user_email=admin_email
)
_record(report, "get_invitation", True, {"id": inv_got.get("id")})
inv_list = classroom_list_invitations(
course_id=course_id,
user_id=student_email,
acting_user_email=admin_email,
)
_record(report, "list_invitations", True, {"count": inv_list.get("count")})
classroom_accept_invitation(
invitation_id=invitation_id, acting_user_email=student_email
)
student_joined = True
_record(report, "accept_invitation_as_student", True, {"id": invitation_id})
invitation_id = None
s_list = classroom_list_course_students(
course_id=course_id, acting_user_email=admin_email
)
_record(
report,
"verify_student_joined",
s_list.get("count", 0) >= 1,
{"count": s_list.get("count")},
)
archived = classroom_archive_course(
course_id=course_id, acting_user_email=admin_email
)
_record(report, "archive_course", True, {"state": archived.get("courseState")})
restored = classroom_set_course_state(
course_id=course_id, course_state="ACTIVE", acting_user_email=admin_email
)
_record(
report,
"set_course_state_active",
True,
{"state": restored.get("courseState")},
)
except Exception as exc:
success = False
_record(
report,
"admin_smoke_flow",
False,
error=f"{type(exc).__name__}: {exc}",
)
finally:
if args.keep_artifacts:
_cleanup(report, "keep_artifacts", True, "enabled")
else:
if invitation_id:
try:
classroom_delete_invitation(
invitation_id=invitation_id, acting_user_email=admin_email
)
_cleanup(report, "delete_pending_invitation", True, invitation_id)
except Exception as exc:
_cleanup(
report,
"delete_pending_invitation",
False,
f"{type(exc).__name__}: {exc}",
)
success = False
else:
_cleanup(report, "delete_pending_invitation", True, "skipped")
if course_id and student_joined:
try:
classroom_remove_student_from_course(
course_id=course_id,
student_email=student_email,
acting_user_email=admin_email,
)
_cleanup(report, "remove_student_from_course", True, student_email)
except Exception as exc:
_cleanup(
report,
"remove_student_from_course",
False,
f"{type(exc).__name__}: {exc}",
)
success = False
else:
_cleanup(report, "remove_student_from_course", True, "skipped")
if course_id and teacher_added:
try:
classroom_remove_teacher_from_course(
course_id=course_id,
teacher_email=teacher_email,
acting_user_email=admin_email,
)
_cleanup(report, "remove_teacher_from_course", True, teacher_email)
except Exception as exc:
_cleanup(
report,
"remove_teacher_from_course",
False,
f"{type(exc).__name__}: {exc}",
)
success = False
else:
_cleanup(report, "remove_teacher_from_course", True, "skipped")
if course_id:
try:
classroom_delete_course(
course_id=course_id, acting_user_email=admin_email
)
_cleanup(report, "delete_course", True, course_id)
except Exception as exc:
_cleanup(
report, "delete_course", False, f"{type(exc).__name__}: {exc}"
)
success = False
else:
_cleanup(report, "delete_course", True, "skipped")
print("\n=== ADMIN SMOKE TEST REPORT ===")
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0 if success else 1
if __name__ == "__main__":
raise SystemExit(main())