import base64 import asyncio import json from types import SimpleNamespace from fastapi import HTTPException def fake_request(**kwargs): return SimpleNamespace( headers=kwargs.get("headers", {}), client=SimpleNamespace(host=kwargs.get("host", "127.0.0.1")), state=SimpleNamespace(**kwargs.get("state", {})), ) def add_app(app_id="testapp", app_name="Test App"): from app.api.routes import admin_app_channel from app.schemas.app_channel import AppCreateRequest response = admin_app_channel.admin_add_app(AppCreateRequest(app_id=app_id, app_name=app_name)) assert response["msg"] == "应用创建成功" def create_license(app_id="testapp", channel="stable"): from app.api.routes import admin_license from app.schemas.license import LicenseCreateRequest response = admin_license.admin_license_create( LicenseCreateRequest( app_id=app_id, channel=channel, customer_name="自动化测试客户", max_devices=1, valid_until="2099-12-31T23:59:59Z", ) ) return response["license_key"] def encode_device_credential(issue_response: dict) -> str: wrapper = { "identity_text": issue_response["identity_text"], "signature": issue_response["signature"], } return base64.b64encode(json.dumps(wrapper, separators=(",", ":")).encode()).decode() async def run_admin_login_license_and_device_issue(server_module): async with server_module.lifespan(server_module.app): from app.api.routes import admin_auth, admin_license, client_update from app.schemas.admin_auth import AdminLoginRequest from app.schemas.client_update import CheckUpdateRequest, DeviceIssueRequest login = admin_auth.login( AdminLoginRequest(username="admin", password="AdminPass2026"), fake_request(headers={"user-agent": "pytest"}), ) assert login["success"] is True assert login["data"]["accessToken"] add_app() license_key = create_license() issue_data = await client_update.issue_device( fake_request(), DeviceIssueRequest( app_id="testapp", channel="stable", license_key=license_key, installation_id="install-test-000001", machine_hash="ab" * 32, ), ) assert issue_data["identity"]["app_id"] == "testapp" assert issue_data["identity"]["channel"] == "stable" credential = encode_device_credential(issue_data) assert credential check_data = await client_update.check_update( fake_request(state={"device_identity": issue_data["identity"]}), CheckUpdateRequest( app_id="testapp", channel="stable", current_version="1.0.0", client_protocol=3, ), ) assert check_data["allow_run"] is True assert check_data["release_available"] is False licenses = admin_license.admin_license_list(app_id="testapp") assert licenses["list"][0]["used_devices"] == 1 def test_admin_login_license_and_device_issue(server_module): asyncio.run(run_admin_login_license_and_device_issue(server_module)) async def run_git_tags_policy_gate(server_module, monkeypatch): async with server_module.lifespan(server_module.app): from app.api.routes import admin_policy, client_update, git_tags from app.schemas.client_update import DeviceIssueRequest, GitTagsRequest from app.schemas.policy import PolicySaveRequest add_app("gitapp", "Git App") license_key = create_license("gitapp", "stable") issue_data = await client_update.issue_device( fake_request(), DeviceIssueRequest( app_id="gitapp", channel="stable", license_key=license_key, installation_id="install-git-tags-000001", machine_hash="cd" * 32, ), ) request = fake_request(state={"device_identity": issue_data["identity"]}) body = GitTagsRequest(app_id="gitapp", channel="stable") try: git_tags.client_git_tags(request, body) assert False, "Git tags should be blocked before the policy is enabled" except HTTPException as exc: assert exc.status_code == 403 assert exc.detail["error"] == "git_tags_disabled" admin_policy.admin_save_policy( PolicySaveRequest( app_id="gitapp", channel="stable", valid_until="2099-12-31T23:59:59Z", git_tags_enabled=True, ) ) monkeypatch.setattr( git_tags, "load_git_tags", lambda force_refresh=False: { "generated_at": "2026-07-20T00:00:00Z", "repo_count": 1, "tag_count": 2, "tags_text": "[owner/repo]\nv1.0.0\nv1.0.1\n", }, ) result = git_tags.client_git_tags(request, body) assert result["repo_count"] == 1 assert result["tag_count"] == 2 assert "v1.0.1" in result["tags_text"] def test_git_tags_policy_gate(server_module, monkeypatch): asyncio.run(run_git_tags_policy_gate(server_module, monkeypatch)) async def run_publish_payload_creates_version(server_module, tmp_path): async with server_module.lifespan(server_module.app): from app.api.routes import admin_version from app.services import publish_service add_app("publishapp", "Publish App") release_dir = tmp_path / "release" bin_dir = release_dir / "bin" bin_dir.mkdir(parents=True) exe_path = bin_dir / "SimCAE.exe" readme_path = release_dir / "README.txt" exe_path.write_bytes(b"fake exe") readme_path.write_text("hello", encoding="utf-8") result = await publish_service.publish_payload( { "upload_items": [ {"path": "bin/SimCAE.exe", "local_path": exe_path}, {"path": "README.txt", "local_path": readme_path}, ], "archive_upload": None, "archive_temp_dir": None, "app_id": "publishapp", "channel": "stable", "version": "1.0.1", "client_protocol": 3, } ) assert result["file_count"] == 2 versions = admin_version.admin_get_version_list("publishapp") assert any(item["version"] == "1.0.1" for item in versions["list"]) def test_publish_payload_creates_version(server_module, tmp_path): asyncio.run(run_publish_payload_creates_version(server_module, tmp_path))