769 lines
30 KiB
Python
769 lines
30 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
import tomllib
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
CURRENT_VERSION = "0.5.0rc3"
|
|
CURRENT_DESKTOP_VERSION = "0.5.0-rc3"
|
|
CURRENT_TAG = f"v{CURRENT_VERSION}"
|
|
HISTORICAL_PREVIEW_VERSION = "0.2.0rc1"
|
|
HISTORICAL_PREVIEW_TAG = f"v{HISTORICAL_PREVIEW_VERSION}"
|
|
|
|
|
|
def test_pyproject_version_matches_current_release() -> None:
|
|
config = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
version = config["project"]["version"]
|
|
assert version == CURRENT_VERSION, (
|
|
f"pyproject.toml version must match the current release; got '{version}'"
|
|
)
|
|
|
|
|
|
def test_lockfile_version_matches_current_release() -> None:
|
|
lock = tomllib.loads(Path("uv.lock").read_text(encoding="utf-8"))
|
|
package = next(item for item in lock["package"] if item["name"] == "opensquilla")
|
|
|
|
assert package["version"] == CURRENT_VERSION
|
|
|
|
|
|
def test_desktop_electron_release_config_matches_current_release() -> None:
|
|
package = json.loads(Path("desktop/electron/package.json").read_text(encoding="utf-8"))
|
|
lock = json.loads(Path("desktop/electron/package-lock.json").read_text(encoding="utf-8"))
|
|
build = package["build"]
|
|
|
|
assert package["version"] == CURRENT_DESKTOP_VERSION
|
|
assert lock["version"] == CURRENT_DESKTOP_VERSION
|
|
assert lock["packages"][""]["version"] == CURRENT_DESKTOP_VERSION
|
|
assert re.fullmatch(r"\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?", package["version"])
|
|
assert not re.search(r"(?<=\d)(?:a|b|rc)\d+$", package["version"])
|
|
assert package["repository"] == {
|
|
"type": "git",
|
|
"url": "https://github.com/opensquilla/opensquilla.git",
|
|
}
|
|
assert build["artifactName"] == "OpenSquilla-${version}-${os}-${arch}.${ext}"
|
|
assert build["mac"]["target"] == ["dmg", "zip"]
|
|
assert build["mac"].get("identity", "auto") is not None
|
|
assert build["win"]["target"] == ["nsis"]
|
|
assert build["nsis"]["oneClick"] is False
|
|
assert build["nsis"]["allowToChangeInstallationDirectory"] is True
|
|
assert build["nsis"]["deleteAppDataOnUninstall"] is False
|
|
package_verifier = Path("desktop/electron/scripts/verify-package.mjs").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert "deleteAppDataOnUninstall !== false" in package_verifier
|
|
|
|
|
|
def test_release_workflow_builds_desktop_installers() -> None:
|
|
workflow = Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
|
|
assert "name: Release Assets" in workflow
|
|
assert "build-desktop-macos:" in workflow
|
|
assert "build-desktop-windows:" in workflow
|
|
assert "npx electron-builder --mac --publish never" in workflow
|
|
assert "npx electron-builder --win --publish never" in workflow
|
|
assert "desktop_asset_version" in workflow
|
|
assert "OpenSquilla-{desktop_version}-mac-arm64.dmg" in workflow
|
|
assert "OpenSquilla-{desktop_version}-win-x64.exe" in workflow
|
|
assert "latest-mac.yml" in workflow
|
|
assert "latest.yml" in workflow
|
|
assert "NOTES_FILE=\"docs/releases/${TAG#v}.md\"" in workflow
|
|
assert "--notes-file \"${NOTES_FILE}\"" in workflow
|
|
assert "gh release upload \"${TAG}\" dist/* --clobber" in workflow
|
|
|
|
|
|
def _release_upload_script() -> str:
|
|
workflow = yaml.safe_load(
|
|
Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
)
|
|
return next(
|
|
step["run"]
|
|
for step in workflow["jobs"]["publish-release"]["steps"]
|
|
if step.get("name") == "Upload to GitHub Release"
|
|
)
|
|
|
|
|
|
def _run_release_upload_with_fake_gh(
|
|
tmp_path: Path,
|
|
*,
|
|
tag: str,
|
|
draft: bool,
|
|
prerelease: bool,
|
|
) -> tuple[subprocess.CompletedProcess[str], str]:
|
|
fake_bin = tmp_path / "bin"
|
|
fake_bin.mkdir(parents=True)
|
|
call_log = tmp_path / "gh-calls.log"
|
|
fake_gh = fake_bin / "gh"
|
|
fake_gh.write_text(
|
|
"""#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
printf '%s\\n' "$*" >> "$FAKE_GH_LOG"
|
|
if [[ "$*" == *"--json"* ]]; then
|
|
printf '%s\\n' "$FAKE_RELEASE_STATE"
|
|
fi
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
fake_gh.chmod(0o755)
|
|
env = os.environ.copy()
|
|
env.update(
|
|
{
|
|
"FAKE_GH_LOG": str(call_log),
|
|
"FAKE_RELEASE_STATE": json.dumps(
|
|
{"assets": [], "isDraft": draft, "isPrerelease": prerelease}
|
|
),
|
|
"GH_REPO": "opensquilla/opensquilla",
|
|
"GH_TOKEN": "synthetic-test-token",
|
|
"PATH": (
|
|
f"{fake_bin}{os.pathsep}{Path(sys.executable).parent}"
|
|
f"{os.pathsep}{env['PATH']}"
|
|
),
|
|
"TAG": tag,
|
|
}
|
|
)
|
|
result = subprocess.run(
|
|
["bash", "-c", _release_upload_script()],
|
|
cwd=Path.cwd(),
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
calls = call_log.read_text(encoding="utf-8") if call_log.exists() else ""
|
|
return result, calls
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "win32",
|
|
reason="The release upload Bash step runs in the required Ubuntu packaging job.",
|
|
)
|
|
def test_release_upload_refuses_to_mutate_an_existing_public_release(tmp_path: Path) -> None:
|
|
result, calls = _run_release_upload_with_fake_gh(
|
|
tmp_path,
|
|
tag="v9.9.9rc1",
|
|
draft=False,
|
|
prerelease=True,
|
|
)
|
|
|
|
assert result.returncode != 0
|
|
assert "non-Draft" in result.stderr
|
|
for mutating_call in (
|
|
"release create",
|
|
"release edit",
|
|
"release delete-asset",
|
|
"release upload",
|
|
):
|
|
assert mutating_call not in calls
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform == "win32",
|
|
reason="The release upload Bash step runs in the required Ubuntu packaging job.",
|
|
)
|
|
def test_release_upload_derives_preview_state_from_each_tag(tmp_path: Path) -> None:
|
|
stable_result, stable_calls = _run_release_upload_with_fake_gh(
|
|
tmp_path / "stable",
|
|
tag="v9.9.9",
|
|
draft=True,
|
|
prerelease=False,
|
|
)
|
|
preview_result, preview_calls = _run_release_upload_with_fake_gh(
|
|
tmp_path / "preview",
|
|
tag="v9.9.9rc7",
|
|
draft=True,
|
|
prerelease=True,
|
|
)
|
|
|
|
assert stable_result.returncode == 0, stable_result.stderr
|
|
assert preview_result.returncode == 0, preview_result.stderr
|
|
assert "release upload v9.9.9" in stable_calls
|
|
assert "release upload v9.9.9rc7" in preview_calls
|
|
|
|
|
|
def test_release_profile_preservation_probe_covers_identity_config_and_chat_db(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
probe = Path(".github/scripts/verify-release-profile-preservation.py")
|
|
home = tmp_path / "Application Support" / "OpenSquilla" / "opensquilla"
|
|
label = "contract-probe"
|
|
|
|
subprocess.run(
|
|
[sys.executable, str(probe), "seed", "--home", str(home), "--label", label],
|
|
check=True,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
verified = subprocess.run(
|
|
[sys.executable, str(probe), "verify", "--home", str(home), "--label", label],
|
|
check=True,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
assert "profile preservation verified" in verified.stdout
|
|
|
|
reseed = subprocess.run(
|
|
[sys.executable, str(probe), "seed", "--home", str(home), "--label", label],
|
|
check=False,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
assert reseed.returncode != 0
|
|
assert "refusing to overwrite" in reseed.stderr
|
|
|
|
identity = home / "workspace" / "IDENTITY.md"
|
|
identity.write_text("changed\n", encoding="utf-8")
|
|
rejected = subprocess.run(
|
|
[sys.executable, str(probe), "verify", "--home", str(home), "--label", label],
|
|
check=False,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
assert rejected.returncode != 0
|
|
assert "IDENTITY.md" in rejected.stderr
|
|
|
|
identity.write_text(f"# Synthetic {label} identity sentinel\n", encoding="utf-8")
|
|
with sqlite3.connect(home / "state" / "sessions.db") as connection:
|
|
connection.execute("UPDATE release_preservation_chat SET body = 'changed'")
|
|
rejected = subprocess.run(
|
|
[sys.executable, str(probe), "verify", "--home", str(home), "--label", label],
|
|
check=False,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
assert rejected.returncode != 0
|
|
assert "sessions.db retained-chat row changed" in rejected.stderr
|
|
|
|
|
|
def test_release_workflow_gates_built_and_downloaded_installers_on_profile_retention() -> None:
|
|
workflow = Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
mac_helper = Path(".github/scripts/verify-release-macos-upgrade.sh").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
windows_helper = Path(".github/scripts/verify-release-windows-upgrade.ps1").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
probe = Path(".github/scripts/verify-release-profile-preservation.py").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
mac_build = workflow[
|
|
workflow.index(" build-desktop-macos:") : workflow.index(
|
|
" build-desktop-windows:"
|
|
)
|
|
]
|
|
windows_build = workflow[
|
|
workflow.index(" build-desktop-windows:") : workflow.index(" publish-release:")
|
|
]
|
|
assert "verify-release-macos-upgrade.sh" in mac_build
|
|
assert "verify-release-windows-upgrade.ps1" in windows_build
|
|
assert mac_build.index("verify-release-macos-upgrade.sh") < mac_build.index(
|
|
"Upload macOS Electron artifacts"
|
|
)
|
|
assert windows_build.index("verify-release-windows-upgrade.ps1") < windows_build.index(
|
|
"Upload Windows Electron artifacts"
|
|
)
|
|
|
|
for artifact in (
|
|
"config.toml",
|
|
"IDENTITY.md",
|
|
"USER.md",
|
|
"SOUL.md",
|
|
"MEMORY.md",
|
|
"sessions.db",
|
|
"PRAGMA quick_check",
|
|
"synthetic retained chat",
|
|
):
|
|
assert artifact in probe
|
|
for helper in (mac_helper, windows_helper):
|
|
assert "v0.5.0rc3" in helper
|
|
assert "recovery inspect" in helper
|
|
assert "verify-release-profile-preservation.py" in helper
|
|
assert "workspace" in helper
|
|
assert "state" in helper
|
|
|
|
mac_audit = workflow[
|
|
workflow.index(" audit-downloaded-macos-release:") : workflow.index(
|
|
" audit-downloaded-windows-release:"
|
|
)
|
|
]
|
|
windows_audit = workflow[workflow.index(" audit-downloaded-windows-release:") :]
|
|
for audit in (mac_audit, windows_audit):
|
|
assert "needs: publish-release" in audit
|
|
assert "gh release download" in audit
|
|
assert "SHA256SUMS" in audit
|
|
assert "isDraft" in audit
|
|
assert "codesign --verify --deep --strict" in mac_audit
|
|
assert "spctl -a -vv -t exec" in mac_audit
|
|
assert "xcrun stapler validate" in mac_audit
|
|
assert "@electron/asar@3.4.1 extract-file" in mac_audit
|
|
assert "verify-release-macos-upgrade.sh" in mac_audit
|
|
assert "Get-FileHash -Algorithm SHA256" in windows_audit
|
|
assert "verify-release-windows-upgrade.ps1" in windows_audit
|
|
|
|
|
|
def test_manual_release_workflow_without_a_tag_only_uploads_aggregate_artifacts() -> None:
|
|
workflow = yaml.safe_load(
|
|
Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
)
|
|
publish_steps = workflow["jobs"]["publish-release"]["steps"]
|
|
aggregate = next(
|
|
step
|
|
for step in publish_steps
|
|
if step["name"] == "Upload aggregate workflow artifact"
|
|
)
|
|
github_upload = next(
|
|
step for step in publish_steps if step["name"] == "Upload to GitHub Release"
|
|
)
|
|
|
|
assert "if" not in aggregate
|
|
assert "github.event.inputs.tag != ''" in github_upload["if"]
|
|
for job_name in ("audit-downloaded-macos-release", "audit-downloaded-windows-release"):
|
|
assert "github.event.inputs.tag != ''" in workflow["jobs"][job_name]["if"]
|
|
|
|
|
|
def test_release_workflow_hydrates_and_smokes_desktop_router_runtime() -> None:
|
|
workflow = Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
|
|
for job_name in ["build-desktop-macos", "build-desktop-windows"]:
|
|
start = workflow.index(f" {job_name}:")
|
|
end = len(workflow)
|
|
for next_job in ["build-desktop-windows", "publish-release"]:
|
|
marker = f"\n {next_job}:"
|
|
pos = workflow.find(marker, start + 1)
|
|
if pos != -1:
|
|
end = min(end, pos)
|
|
job = workflow[start:end]
|
|
assert "lfs: true" in job
|
|
assert 'git lfs pull --include="src/opensquilla/squilla_router/models/**"' in job
|
|
assert "npm run build:gateway" in job
|
|
assert "npm run verify:package" in job
|
|
assert "npm run verify:gateway-smoke" in job
|
|
assert 'OPENSQUILLA_REQUIRE_PACKAGED_GATEWAY_SMOKE: "1"' in job
|
|
if job_name == "build-desktop-macos":
|
|
assert 'OPENSQUILLA_GATEWAY_SMOKE_TIMEOUT_MS: "240000"' in job
|
|
else:
|
|
assert "OPENSQUILLA_GATEWAY_SMOKE_TIMEOUT_MS" not in job
|
|
|
|
|
|
def test_release_workflow_keeps_macos_signing_identity_auto_selected() -> None:
|
|
workflow = Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
mac_step = workflow.split("- name: Build signed macOS installer", 1)[1].split(
|
|
"- name: Verify Electron package", 1
|
|
)[0]
|
|
|
|
assert "CSC_LINK: ${{ secrets.MAC_CSC_LINK }}" in mac_step
|
|
assert "CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}" in mac_step
|
|
assert "APPLE_ID: ${{ secrets.APPLE_ID }}" in mac_step
|
|
assert "CSC_NAME" not in mac_step
|
|
assert "GH_TOKEN" not in mac_step
|
|
|
|
|
|
def test_release_workflow_keeps_windows_build_unsigned_until_signing_is_available() -> None:
|
|
workflow = Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
windows_step = workflow.split("- name: Build unsigned Windows installer", 1)[1].split(
|
|
"- name: Verify Electron package", 1
|
|
)[0]
|
|
|
|
assert "npx electron-builder --win --publish never" in windows_step
|
|
assert 'CSC_IDENTITY_AUTO_DISCOVERY: "false"' in windows_step
|
|
assert not Path("desktop/electron/electron-builder.release.cjs").exists()
|
|
|
|
for env_name in [
|
|
"OPENSQUILLA_WINDOWS_AZURE_SIGNING",
|
|
"AZURE_TENANT_ID",
|
|
"AZURE_CLIENT_ID",
|
|
"AZURE_CLIENT_SECRET",
|
|
"AZURE_TRUSTED_SIGNING_PUBLISHER_NAME",
|
|
"AZURE_TRUSTED_SIGNING_ENDPOINT",
|
|
"AZURE_TRUSTED_SIGNING_ACCOUNT_NAME",
|
|
"AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME",
|
|
]:
|
|
assert env_name not in windows_step
|
|
|
|
assert "azureSignOptions" not in workflow
|
|
assert "forceCodeSigning: true" not in workflow
|
|
assert "timestampRfc3161: 'http://timestamp.acs.microsoft.com'" not in workflow
|
|
|
|
|
|
def test_release_docs_describe_unsigned_windows_policy() -> None:
|
|
readme = Path("README.md").read_text(encoding="utf-8")
|
|
localized_readmes = {
|
|
"zh-Hans": Path("README.zh-Hans.md").read_text(encoding="utf-8"),
|
|
"ja": Path("README.ja.md").read_text(encoding="utf-8"),
|
|
"fr": Path("README.fr.md").read_text(encoding="utf-8"),
|
|
"de": Path("README.de.md").read_text(encoding="utf-8"),
|
|
"es": Path("README.es.md").read_text(encoding="utf-8"),
|
|
}
|
|
releases = Path("RELEASES.md").read_text(encoding="utf-8")
|
|
release_notes = Path(f"docs/releases/{CURRENT_VERSION}.md").read_text(encoding="utf-8")
|
|
signing_policy = Path("docs/code-signing-policy.md").read_text(encoding="utf-8")
|
|
privacy_policy = Path("PRIVACY.md").read_text(encoding="utf-8")
|
|
|
|
assert "Code signing policy:" in readme
|
|
assert "Windows builds are currently unsigned" in readme
|
|
assert "Windows desktop installer is currently unsigned" in releases
|
|
assert "Windows release builds are currently unsigned" in signing_policy
|
|
assert "claim Windows code signing" in signing_policy
|
|
assert "[`PRIVACY.md`](../PRIVACY.md)" in signing_policy
|
|
assert "[@Open-Squilla](https://github.com/Open-Squilla)" in signing_policy
|
|
assert "Initial SignPath approvers" in signing_policy
|
|
assert "network observability" in signing_policy
|
|
|
|
for text in [readme, releases, release_notes]:
|
|
assert "code-signing-policy.md" in text
|
|
|
|
assert "PRIVACY.md" in readme
|
|
assert "THIRD_PARTY_NOTICES.md" in readme
|
|
assert "Installation Telemetry" in privacy_policy
|
|
assert "OPENSQUILLA_TELEMETRY_DISABLED=true" in privacy_policy
|
|
assert "future signing plan" not in readme
|
|
|
|
for text in [readme, releases]:
|
|
assert "signed desktop installers" not in text
|
|
|
|
for locale, phrase in {
|
|
"zh-Hans": "已签名的桌面",
|
|
"ja": "署名済みのデスクトップ",
|
|
"fr": "installateurs de bureau signés",
|
|
"de": "signierten Desktop",
|
|
"es": "instaladores de escritorio firmados",
|
|
}.items():
|
|
assert phrase not in localized_readmes[locale]
|
|
|
|
|
|
def test_release_docs_warn_rc3_users_to_upgrade_in_place() -> None:
|
|
readmes = [
|
|
Path("README.md"),
|
|
Path("README.zh-Hans.md"),
|
|
Path("README.ja.md"),
|
|
Path("README.fr.md"),
|
|
Path("README.de.md"),
|
|
Path("README.es.md"),
|
|
]
|
|
for path in readmes:
|
|
text = path.read_text(encoding="utf-8")
|
|
assert "RC3" in text, path
|
|
assert "RC4" in text, path
|
|
assert r"%APPDATA%\OpenSquilla" in text, path
|
|
|
|
releases = Path("RELEASES.md").read_text(encoding="utf-8")
|
|
current_notes = Path(f"docs/releases/{CURRENT_VERSION}.md").read_text(encoding="utf-8")
|
|
assert "must install the\nnew version directly over the existing installation" in releases
|
|
assert "must not uninstall RC3\nfirst" in releases
|
|
assert "deleteAppDataOnUninstall=false" in releases
|
|
assert "Do not uninstall Preview 3" in current_notes
|
|
|
|
|
|
def test_privacy_docs_describe_network_observability_controls() -> None:
|
|
docs = {
|
|
"README.md": Path("README.md").read_text(encoding="utf-8"),
|
|
"README.zh-Hans.md": Path("README.zh-Hans.md").read_text(encoding="utf-8"),
|
|
"PRIVACY.md": Path("PRIVACY.md").read_text(encoding="utf-8"),
|
|
"RELEASES.md": Path("RELEASES.md").read_text(encoding="utf-8"),
|
|
f"docs/releases/{CURRENT_VERSION}.md": Path(
|
|
f"docs/releases/{CURRENT_VERSION}.md"
|
|
).read_text(encoding="utf-8"),
|
|
"docs/code-signing-policy.md": Path("docs/code-signing-policy.md").read_text(
|
|
encoding="utf-8"
|
|
),
|
|
}
|
|
|
|
for path, text in docs.items():
|
|
assert "OPENSQUILLA_PRIVACY_DISABLE_NETWORK_OBSERVABILITY=true" in text, path
|
|
assert "disable_network_observability = true" in text, path
|
|
assert "OPENSQUILLA_TELEMETRY_DISABLED=true" in text, path
|
|
assert "OPENSQUILLA_UPDATE_CHECK_DISABLED=true" in text, path
|
|
|
|
privacy = docs["PRIVACY.md"]
|
|
assert "automatic install telemetry" in privacy
|
|
assert "passive update checks" in privacy
|
|
assert "desktop startup auto-update checks" in privacy
|
|
assert "Manual user-initiated actions may still contact network services" in privacy
|
|
|
|
|
|
def _dep_names(specs: list[str]) -> set[str]:
|
|
names: set[str] = set()
|
|
for spec in specs:
|
|
head = spec.strip()
|
|
for sep in ("[", " ", ";", "=", ">", "<", "~", "!"):
|
|
head = head.split(sep, 1)[0]
|
|
if head:
|
|
names.add(head.lower())
|
|
return names
|
|
|
|
|
|
def test_recommended_extra_uses_onnx_tokenizers_without_transformers() -> None:
|
|
config = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
recommended = config["project"]["optional-dependencies"]["recommended"]
|
|
|
|
assert any(dep.startswith("onnxruntime") for dep in recommended)
|
|
assert any(dep.startswith("tokenizers") for dep in recommended)
|
|
assert not any(dep.startswith("transformers") for dep in recommended)
|
|
|
|
|
|
def test_default_recommended_install_contract_covers_router_and_channels() -> None:
|
|
config = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
project = config["project"]
|
|
dependencies = _dep_names(project["dependencies"])
|
|
extras = project["optional-dependencies"]
|
|
recommended = _dep_names(extras["recommended"])
|
|
|
|
assert {
|
|
"lightgbm",
|
|
"numpy",
|
|
"onnxruntime",
|
|
"scikit-learn",
|
|
"tokenizers",
|
|
} <= recommended
|
|
assert {
|
|
"cryptography", # WeCom callback crypto
|
|
"dingtalk-stream",
|
|
"httpx", # Slack, Telegram, Feishu, WeCom HTTP calls
|
|
"lark-oapi",
|
|
"python-telegram-bot",
|
|
"qq-botpy",
|
|
"websockets", # Discord gateway and Feishu SDK transport
|
|
} <= dependencies
|
|
for alias in ("feishu", "telegram", "dingtalk", "wecom", "qq"):
|
|
assert alias not in extras
|
|
|
|
assert "matrix-nio" in "\n".join(extras["matrix"])
|
|
|
|
|
|
def test_core_dependencies_support_default_pptx_skill() -> None:
|
|
config = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
dependencies = config["project"]["dependencies"]
|
|
|
|
assert any(dep.startswith("python-pptx") for dep in dependencies)
|
|
|
|
|
|
def test_releases_md_exists_and_references_current_and_preview_tags() -> None:
|
|
releases = Path("RELEASES.md")
|
|
assert releases.is_file(), "RELEASES.md must exist at the repository root"
|
|
text = releases.read_text(encoding="utf-8")
|
|
assert CURRENT_TAG in text, f"RELEASES.md must reference the tag '{CURRENT_TAG}'"
|
|
assert (
|
|
HISTORICAL_PREVIEW_TAG in text
|
|
), f"RELEASES.md must retain the historical tag '{HISTORICAL_PREVIEW_TAG}'"
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-mac-arm64.dmg" in text
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-win-x64.exe" in text
|
|
assert "do not publish Windows portable zips" in text
|
|
assert "legacy Windows portable downloads" in text
|
|
assert "separately branded macOS or Linux portable bundles" in text
|
|
assert "macOS `.zip` is the Electron desktop and updater artifact" in text
|
|
assert "macOS portable zips" not in text
|
|
assert "`0.5.0rc4` /\n `v0.5.0rc4`" in text
|
|
assert "tracks the most recently pushed release tag" in text
|
|
|
|
|
|
def test_changelog_has_current_release_section_and_unreleased() -> None:
|
|
changelog = Path("CHANGELOG.md")
|
|
assert changelog.is_file(), "CHANGELOG.md must exist at the repository root"
|
|
text = changelog.read_text(encoding="utf-8")
|
|
assert (
|
|
f"[{CURRENT_VERSION}]" in text
|
|
), f"CHANGELOG.md must contain a [{CURRENT_VERSION}] section"
|
|
assert "[Unreleased]" in text, "CHANGELOG.md must retain an [Unreleased] section"
|
|
|
|
|
|
def test_readme_release_install_uses_latest_assets_and_pinned_alternative() -> None:
|
|
readme = Path("README.md").read_text(encoding="utf-8")
|
|
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-mac-arm64.dmg" in readme
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-win-x64.exe" in readme
|
|
assert "Simplified release assets" in readme
|
|
assert "Electron installers" in readme
|
|
assert "versioned Python wheel" in readme
|
|
assert "releases/latest/download/OpenSquilla-windows-x64-portable.zip" not in readme
|
|
assert (
|
|
f"releases/download/{CURRENT_TAG}/opensquilla-{CURRENT_VERSION}-py3-none-any.whl"
|
|
in readme
|
|
)
|
|
assert "opensquilla-latest-py3-none-any.whl" not in readme
|
|
assert "Python wheel installs use versioned wheel filenames" in readme
|
|
assert "Release install commands use published GitHub release assets" in readme
|
|
|
|
|
|
def test_all_readmes_default_install_paths_to_the_current_preview() -> None:
|
|
wheel_url = (
|
|
f"releases/download/{CURRENT_TAG}/opensquilla-{CURRENT_VERSION}-py3-none-any.whl"
|
|
)
|
|
readmes = [
|
|
Path("README.md"),
|
|
Path("README.zh-Hans.md"),
|
|
Path("README.ja.md"),
|
|
Path("README.fr.md"),
|
|
Path("README.de.md"),
|
|
Path("README.es.md"),
|
|
]
|
|
|
|
for path in readmes:
|
|
text = path.read_text(encoding="utf-8")
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-mac-arm64.dmg" in text, path
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-win-x64.exe" in text, path
|
|
assert wheel_url in text, path
|
|
assert "ghcr.io/opensquilla/opensquilla:latest" in text, path
|
|
assert "0.5.0-Preview-2-Desktop" not in text, path
|
|
|
|
|
|
def test_user_facing_install_docs_use_current_release_wheel() -> None:
|
|
current_wheel_url = (
|
|
f"releases/download/{CURRENT_TAG}/opensquilla-{CURRENT_VERSION}-py3-none-any.whl"
|
|
)
|
|
wheel_url_pattern = re.compile(
|
|
r"releases/download/v(?P<tag_version>[^/]+)/"
|
|
r"opensquilla-(?P<file_version>[^/]+)-py3-none-any\.whl"
|
|
)
|
|
install_docs = [
|
|
Path("README.md"),
|
|
Path("README.product.md"),
|
|
Path("docs/quickstart.md"),
|
|
Path("docs/cli.md"),
|
|
Path("docs/mcp-server.md"),
|
|
Path("docs/operations.md"),
|
|
]
|
|
|
|
for path in install_docs:
|
|
text = path.read_text(encoding="utf-8")
|
|
wheel_urls = list(wheel_url_pattern.finditer(text))
|
|
|
|
assert wheel_urls, f"{path} must include a pinned release wheel URL"
|
|
assert current_wheel_url in text, f"{path} must install from {CURRENT_TAG}"
|
|
for match in wheel_urls:
|
|
assert match.group("tag_version") == CURRENT_VERSION
|
|
assert match.group("file_version") == CURRENT_VERSION
|
|
|
|
|
|
def test_release_installers_default_to_current_tag() -> None:
|
|
for path in [Path("install.sh"), Path("install.ps1")]:
|
|
text = path.read_text(encoding="utf-8")
|
|
assert CURRENT_TAG in text
|
|
assert "opensquilla-$releaseVersion-py3-none-any.whl" in text or (
|
|
"opensquilla-${release_version}-py3-none-any.whl" in text
|
|
)
|
|
assert "opensquilla-latest-py3-none-any.whl" not in text
|
|
|
|
|
|
def test_release_workflow_marks_preview_tags_as_prereleases() -> None:
|
|
workflow = Path(".github/workflows/wheelhouse-release.yml").read_text(encoding="utf-8")
|
|
|
|
assert "IS_PRERELEASE" in workflow
|
|
assert "--prerelease" in workflow
|
|
assert "OpenSquilla {match.group(1)} Preview {match.group(2)}" in workflow
|
|
assert "0.5+ release assets must not include Windows portable zips" in workflow
|
|
assert "OpenSquilla-windows-x64-portable.zip" not in workflow
|
|
assert "opensquilla-latest-py3-none-any.whl" not in workflow
|
|
|
|
|
|
def test_container_workflow_gates_latest_promotion() -> None:
|
|
workflow = Path(".github/workflows/docker-image.yml").read_text(encoding="utf-8")
|
|
|
|
assert 'tags:\n - "v*"' in workflow
|
|
assert 'tag_version="${GITHUB_REF_NAME#v}"' in workflow
|
|
assert 'project["project"]["version"]' in workflow
|
|
assert "does not match project version" in workflow
|
|
assert "packages: write" in workflow
|
|
assert "platforms: linux/amd64,linux/arm64" in workflow
|
|
assert "type=ref,event=tag" in workflow
|
|
assert "type=raw,value=latest" not in workflow
|
|
assert "provenance: false" in workflow
|
|
assert "most recently pushed release tag" in workflow
|
|
assert '["docker", "buildx", "imagetools", "inspect", image_ref, "--raw"]' in workflow
|
|
assert 'expected = {"linux/amd64", "linux/arm64"}' in workflow
|
|
assert 'docker run --detach --pull=always "${IMAGE_REF}"' in workflow
|
|
assert ".State.Health.Status" in workflow
|
|
assert '[[ "${health}" == "healthy" ]]' in workflow
|
|
assert "docker buildx imagetools create" in workflow
|
|
|
|
build = workflow.index("- name: Build multi-arch image")
|
|
verify = workflow.index("- name: Verify pushed manifest platforms")
|
|
smoke = workflow.index("- name: Smoke pushed image HEALTHCHECK")
|
|
promote = workflow.index("- name: Promote verified release image to latest")
|
|
assert build < verify < smoke < promote
|
|
|
|
|
|
def test_historical_040_release_notes_remain_available() -> None:
|
|
notes = Path("docs/releases/0.4.0.md").read_text(encoding="utf-8")
|
|
|
|
assert "# OpenSquilla 0.4.0" in notes
|
|
assert "OpenSquilla-0.4.0-mac-arm64.dmg" in notes
|
|
|
|
|
|
def test_current_release_notes_cover_migration_upgrade_and_containers() -> None:
|
|
notes = Path(f"docs/releases/{CURRENT_VERSION}.md").read_text(encoding="utf-8")
|
|
|
|
assert "## Downloads" in notes
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-mac-arm64.dmg" in notes
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-mac-arm64.zip" in notes
|
|
assert f"OpenSquilla-{CURRENT_DESKTOP_VERSION}-win-x64.exe" in notes
|
|
assert f"opensquilla-{CURRENT_VERSION}-py3-none-any.whl" in notes
|
|
assert notes.index("### Legacy home migration and upgrade safety") < notes.index(
|
|
"### Providers, models, and routing"
|
|
)
|
|
assert notes.index("### Providers, models, and routing") < notes.index(
|
|
"### Desktop, terminal, and Control UI"
|
|
)
|
|
assert notes.index("### Runtime, safety, and data reliability") < notes.index(
|
|
"### Container deployment"
|
|
)
|
|
assert "No Windows portable assets are published for 0.5.0 preview releases" in notes
|
|
assert "0.5.0rc3 portable zip" in notes
|
|
assert "## Upgrading from Preview 2, Preview 1, or 0.4.1" in notes
|
|
assert "should not wait for an in-app RC3\nnotification" in notes
|
|
assert "ghcr.io/opensquilla/opensquilla:v0.5.0rc3" in notes
|
|
assert "Docker `latest` follows the most recently pushed release tag" in notes
|
|
assert (
|
|
"Configuration\nformats from every released OpenSquilla version remain supported"
|
|
in notes
|
|
)
|
|
assert "Synthetic fixtures" not in notes
|
|
assert "release gate" not in notes
|
|
assert "## Acknowledgements" in notes
|
|
for login in [
|
|
"@ab2ence",
|
|
"@JarvisPei",
|
|
"@labulalala",
|
|
"@Liu-RK",
|
|
"@lyteen",
|
|
"@nice-code-la",
|
|
"@TUOXI293",
|
|
]:
|
|
assert login in notes
|
|
assert "CONTRIBUTORS.md" in notes
|
|
|
|
|
|
def test_docs_index_links_current_release_notes() -> None:
|
|
index = Path("docs/README.md").read_text(encoding="utf-8")
|
|
|
|
assert f"releases/{CURRENT_VERSION}.md" in index
|
|
assert "releases/0.4.0.md" in index
|
|
|
|
|
|
def test_current_contributor_ledger_records_050rc3_attribution() -> None:
|
|
ledger = Path("CONTRIBUTORS.md").read_text(encoding="utf-8")
|
|
section = ledger.split("## OpenSquilla 0.5.0rc3", 1)[1].split(
|
|
"## OpenSquilla 0.5.0rc2", 1
|
|
)[0]
|
|
|
|
expected = {
|
|
"@ab2ence": "#491",
|
|
"@JarvisPei": "#550",
|
|
"@labulalala": "#502",
|
|
"@Liu-RK": "#486",
|
|
"@lyteen": "#212",
|
|
"@nice-code-la": "#560",
|
|
"@TUOXI293": "#487",
|
|
}
|
|
for login, evidence in expected.items():
|
|
assert login in section
|
|
assert evidence in section
|
|
assert "Codex" not in section
|
|
assert "Claude Code" not in section
|