chore: sync GovOPlaN module split state
This commit is contained in:
215
dev/connectors/smoke.py
Normal file
215
dev/connectors/smoke.py
Normal file
@@ -0,0 +1,215 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from govoplan_files.backend.storage.connector_browse import browse_connector_profile
|
||||
from govoplan_files.backend.storage.connector_imports import read_connector_file
|
||||
from govoplan_files.backend.storage.connector_profiles import connector_profiles_from_payload
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Smoke-test GovOPlaN connector helpers against the local dev compose stack.")
|
||||
parser.add_argument("--require-smb", action="store_true", help="Fail if the SMB connector cannot browse/import.")
|
||||
parser.add_argument("--debug-smb", action="store_true", help="Print direct smbclient probes before the connector smoke check.")
|
||||
args = parser.parse_args()
|
||||
|
||||
_load_dotenv()
|
||||
_default_env()
|
||||
preflight_failures = _preflight_services(require_smb=args.require_smb)
|
||||
if preflight_failures:
|
||||
for failure in preflight_failures:
|
||||
print(f"FAIL {failure}")
|
||||
print("Start or recreate the connector stack from this directory with: docker compose up -d nextcloud nextcloud-db webdav smb")
|
||||
return 1
|
||||
_seed_webdav_fixture()
|
||||
_seed_smb_fixture()
|
||||
try:
|
||||
_seed_nextcloud_fixture()
|
||||
except httpx.HTTPError as exc:
|
||||
print(f"FAIL dev-nextcloud seed failed: {exc}")
|
||||
return 1
|
||||
|
||||
profiles = {profile.id: profile for profile in connector_profiles_from_payload({"profiles": _profile_payloads()})}
|
||||
if args.debug_smb:
|
||||
_debug_smb(profiles["dev-smb"])
|
||||
failures: list[str] = []
|
||||
for profile_id, folder, file_path, expected in (
|
||||
("dev-webdav", "GovOPlaN", "GovOPlaN/webdav-live.txt", "webdav live fixture"),
|
||||
("dev-nextcloud", "GovOPlaN", "GovOPlaN/nextcloud-live.txt", "nextcloud live fixture"),
|
||||
):
|
||||
try:
|
||||
_exercise_profile(profiles[profile_id], folder=folder, file_path=file_path, expected=expected)
|
||||
except Exception as exc:
|
||||
failures.append(f"{profile_id}: {exc}")
|
||||
|
||||
try:
|
||||
_exercise_profile(profiles["dev-smb"], folder="GovOPlaN", file_path="GovOPlaN/smb-live.txt", expected="smb live fixture")
|
||||
except Exception as exc:
|
||||
message = f"dev-smb: {exc}"
|
||||
if args.require_smb:
|
||||
failures.append(message)
|
||||
else:
|
||||
print(f"SKIP {message}")
|
||||
|
||||
if failures:
|
||||
for failure in failures:
|
||||
print(f"FAIL {failure}")
|
||||
return 1
|
||||
print("OK connector dev stack smoke checks passed")
|
||||
return 0
|
||||
|
||||
|
||||
def _default_env() -> None:
|
||||
defaults = {
|
||||
"NEXTCLOUD_ADMIN_USER": "admin",
|
||||
"NEXTCLOUD_ADMIN_PASSWORD": "govoplan-nextcloud-admin",
|
||||
"WEBDAV_USER": "govoplan",
|
||||
"WEBDAV_PASSWORD": "govoplan-webdav",
|
||||
"SMB_SHARE_NAME": "files",
|
||||
"SMB_USER": "govoplan",
|
||||
"SMB_PASSWORD": "govoplan-smb",
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
os.environ.setdefault(key, value)
|
||||
|
||||
|
||||
def _load_dotenv() -> None:
|
||||
path = ROOT / ".env"
|
||||
if not path.exists():
|
||||
return
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
text = line.strip()
|
||||
if not text or text.startswith("#") or "=" not in text:
|
||||
continue
|
||||
key, value = text.split("=", 1)
|
||||
key = key.strip()
|
||||
if not key or key in os.environ:
|
||||
continue
|
||||
os.environ[key] = value.strip().strip("\"'")
|
||||
|
||||
|
||||
def _preflight_services(*, require_smb: bool) -> list[str]:
|
||||
failures: list[str] = []
|
||||
nextcloud_url = f"http://127.0.0.1:{os.getenv('NEXTCLOUD_HOST_PORT', '9081')}/status.php"
|
||||
try:
|
||||
response = httpx.get(nextcloud_url, timeout=3.0)
|
||||
if response.status_code != 200:
|
||||
failures.append(f"dev-nextcloud expected HTTP 200 at {nextcloud_url}, got HTTP {response.status_code}")
|
||||
except httpx.HTTPError as exc:
|
||||
failures.append(f"dev-nextcloud is not reachable at {nextcloud_url}: {exc}")
|
||||
|
||||
webdav_url = f"http://127.0.0.1:{os.getenv('WEBDAV_HOST_PORT', '9083')}/"
|
||||
try:
|
||||
response = httpx.get(webdav_url, timeout=3.0)
|
||||
if response.status_code not in {200, 401}:
|
||||
failures.append(f"dev-webdav expected HTTP 200/401 at {webdav_url}, got HTTP {response.status_code}")
|
||||
except httpx.HTTPError as exc:
|
||||
failures.append(f"dev-webdav is not reachable at {webdav_url}: {exc}")
|
||||
|
||||
if require_smb:
|
||||
smb_port = int(os.getenv("SMB_HOST_PORT", "1445"))
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", smb_port), timeout=3.0):
|
||||
pass
|
||||
except OSError as exc:
|
||||
failures.append(f"dev-smb is not reachable at 127.0.0.1:{smb_port}: {exc}")
|
||||
return failures
|
||||
|
||||
|
||||
def _profile_payloads() -> list[dict[str, object]]:
|
||||
return [
|
||||
{
|
||||
"id": "dev-webdav",
|
||||
"provider": "webdav",
|
||||
"endpoint_url": f"http://127.0.0.1:{os.getenv('WEBDAV_HOST_PORT', '9083')}/",
|
||||
"credential_mode": "basic",
|
||||
"username": os.getenv("WEBDAV_USER", "govoplan"),
|
||||
"password_env": "WEBDAV_PASSWORD",
|
||||
},
|
||||
{
|
||||
"id": "dev-nextcloud",
|
||||
"provider": "nextcloud",
|
||||
"endpoint_url": f"http://127.0.0.1:{os.getenv('NEXTCLOUD_HOST_PORT', '9081')}/remote.php/dav/files/{os.getenv('NEXTCLOUD_ADMIN_USER', 'admin')}/",
|
||||
"credential_mode": "basic",
|
||||
"username": os.getenv("NEXTCLOUD_ADMIN_USER", "admin"),
|
||||
"password_env": "NEXTCLOUD_ADMIN_PASSWORD",
|
||||
},
|
||||
{
|
||||
"id": "dev-smb",
|
||||
"provider": "smb",
|
||||
"endpoint_url": f"smb://127.0.0.1:{os.getenv('SMB_HOST_PORT', '1445')}/{os.getenv('SMB_SHARE_NAME', 'files')}",
|
||||
"credential_mode": "basic",
|
||||
"username": os.getenv("SMB_USER", "govoplan"),
|
||||
"password_env": "SMB_PASSWORD",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _exercise_profile(profile, *, folder: str, file_path: str, expected: str) -> None:
|
||||
items = browse_connector_profile(profile, path=folder)
|
||||
paths = {item.path for item in items}
|
||||
if file_path not in paths:
|
||||
raise RuntimeError(f"{file_path!r} not found; saw {sorted(paths)!r}")
|
||||
downloaded = read_connector_file(profile, library_id="", path=file_path, max_bytes=1024 * 1024)
|
||||
text = downloaded.data.decode("utf-8").strip()
|
||||
if text != expected:
|
||||
raise RuntimeError(f"{file_path!r} content mismatch: {text!r}")
|
||||
print(f"OK {profile.id} browse/import {file_path}")
|
||||
|
||||
|
||||
def _debug_smb(profile) -> None:
|
||||
try:
|
||||
import smbclient
|
||||
except ImportError as exc:
|
||||
print(f"DEBUG smbclient import failed: {exc}")
|
||||
return
|
||||
from govoplan_files.backend.storage.connector_browse import _smb_client_kwargs, _smb_location, _smb_unc_path
|
||||
|
||||
location = _smb_location(profile)
|
||||
kwargs = _smb_client_kwargs(profile, location)
|
||||
print(f"DEBUG SMB endpoint=//{location.server}:{location.port}/{location.share} root_path={location.root_path!r}")
|
||||
print(f"DEBUG SMB user={kwargs.get('username')!r} require_signing={kwargs.get('require_signing')!r} auth_protocol={kwargs.get('auth_protocol')!r}")
|
||||
try:
|
||||
smbclient.reset_connection_cache()
|
||||
except Exception as exc:
|
||||
print(f"DEBUG SMB reset cache failed: {exc}")
|
||||
for path in (_smb_unc_path(location, ""), _smb_unc_path(location, "GovOPlaN")):
|
||||
try:
|
||||
print(f"DEBUG SMB list {path}: {smbclient.listdir(path, **kwargs)}")
|
||||
except Exception as exc:
|
||||
print(f"DEBUG SMB list {path} failed: {type(exc).__name__}: {exc}")
|
||||
|
||||
|
||||
def _seed_webdav_fixture() -> None:
|
||||
path = ROOT / "data" / "webdav" / "GovOPlaN"
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
(path / "webdav-live.txt").write_text("webdav live fixture\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _seed_smb_fixture() -> None:
|
||||
path = ROOT / "data" / "smb" / "GovOPlaN"
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
(path / "smb-live.txt").write_text("smb live fixture\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _seed_nextcloud_fixture() -> None:
|
||||
base_url = f"http://127.0.0.1:{os.getenv('NEXTCLOUD_HOST_PORT', '9081')}/remote.php/dav/files/{os.getenv('NEXTCLOUD_ADMIN_USER', 'admin')}/GovOPlaN"
|
||||
auth = (os.getenv("NEXTCLOUD_ADMIN_USER", "admin"), os.getenv("NEXTCLOUD_ADMIN_PASSWORD", "govoplan-nextcloud-admin"))
|
||||
response = httpx.request("MKCOL", base_url, auth=auth, timeout=15.0)
|
||||
if response.status_code not in {201, 405}:
|
||||
raise RuntimeError(f"Nextcloud MKCOL failed with HTTP {response.status_code}: {response.text[:200]}")
|
||||
response = httpx.put(f"{base_url}/nextcloud-live.txt", content=b"nextcloud live fixture\n", auth=auth, timeout=15.0)
|
||||
if response.status_code not in {200, 201, 204}:
|
||||
raise RuntimeError(f"Nextcloud PUT failed with HTTP {response.status_code}: {response.text[:200]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user