feat: add client latest release api
This commit is contained in:
+188
-1
@@ -8,6 +8,7 @@ from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
from cryptography.fernet import Fernet
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase, override_settings
|
||||
@@ -19,7 +20,7 @@ from rest_framework.views import APIView
|
||||
|
||||
from apps.api.authentication import ApiKeyAuthentication
|
||||
from apps.api.throttles import GenerateRateThrottle
|
||||
from apps.api.views import ExternalApiView, ModelsView
|
||||
from apps.api.views import ClientLatestReleaseView, ExternalApiView, ModelsView
|
||||
from apps.ai.models import AiModel, ModelAlias
|
||||
from apps.ai.providers import (
|
||||
AiCapabilityError,
|
||||
@@ -41,6 +42,7 @@ from apps.billing.payment_gateways import (
|
||||
from apps.billing.services import RechargePayment
|
||||
from apps.moderation.models import SensitiveWord
|
||||
from apps.moderation.providers.keyword import reset_keyword_matcher_cache
|
||||
from apps.portal.models import DownloadRelease
|
||||
from apps.users.models import ApiKey
|
||||
from apps.users.models import UserWallet
|
||||
|
||||
@@ -404,6 +406,191 @@ class ModelsCatalogApiTests(TestCase):
|
||||
self.assertEqual(item["prices"], [])
|
||||
|
||||
|
||||
class ClientLatestReleaseApiTests(TestCase):
|
||||
url = "/api/v1/client/releases/latest"
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
self.client = APIClient()
|
||||
|
||||
def create_release(
|
||||
self,
|
||||
*,
|
||||
platform: str = DownloadRelease.Platform.WINDOWS,
|
||||
version: str = "1.0.0",
|
||||
is_current: bool = True,
|
||||
external_url: str = "https://download.example.com/cmhub-desktop.exe",
|
||||
file_name: str = "",
|
||||
sha256: str = "a" * 64,
|
||||
release_notes: str = "首版 Windows 客户端",
|
||||
) -> DownloadRelease:
|
||||
return DownloadRelease.objects.create(
|
||||
platform=platform,
|
||||
version=version,
|
||||
is_current=is_current,
|
||||
external_url=external_url,
|
||||
file=file_name,
|
||||
sha256=sha256,
|
||||
release_notes=release_notes,
|
||||
)
|
||||
|
||||
def test_latest_release_is_public_without_api_key_and_returns_current_release(self):
|
||||
release = self.create_release(
|
||||
version="1.2.3",
|
||||
external_url="https://download.example.com/cmhub-1.2.3.exe",
|
||||
sha256="b" * 64,
|
||||
release_notes="修复下载入口并补充 SHA256",
|
||||
)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn(GenerateRateThrottle, ClientLatestReleaseView.throttle_classes)
|
||||
self.assertEqual(response.data["platform"], "windows")
|
||||
self.assertEqual(
|
||||
set(response.data["release"]),
|
||||
{"version", "download_url", "sha256", "release_notes", "published_at"},
|
||||
)
|
||||
self.assertEqual(response.data["release"]["version"], "1.2.3")
|
||||
self.assertEqual(
|
||||
response.data["release"]["download_url"],
|
||||
"https://download.example.com/cmhub-1.2.3.exe",
|
||||
)
|
||||
self.assertEqual(response.data["release"]["sha256"], "b" * 64)
|
||||
self.assertEqual(
|
||||
response.data["release"]["release_notes"],
|
||||
"修复下载入口并补充 SHA256",
|
||||
)
|
||||
self.assertEqual(
|
||||
response.data["release"]["published_at"],
|
||||
timezone.localtime(release.updated_at).isoformat(),
|
||||
)
|
||||
|
||||
def test_latest_release_ignores_web_session_and_does_not_return_user_data(self):
|
||||
user = get_user_model().objects.create_user(
|
||||
username="release-session-user",
|
||||
email="release-session-user@example.com",
|
||||
password="password",
|
||||
)
|
||||
self.create_release()
|
||||
self.client.force_login(user)
|
||||
|
||||
response = self.client.get(self.url, HTTP_AUTHORIZATION="Bearer sk_cmhub_invalid")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_body = json.dumps(response.data, ensure_ascii=False)
|
||||
self.assertNotIn(user.username, response_body)
|
||||
self.assertNotIn(user.email, response_body)
|
||||
self.assertNotIn("api_key", response_body)
|
||||
self.assertNotIn("key_hash", response_body)
|
||||
|
||||
def test_latest_release_builds_absolute_file_url(self):
|
||||
self.create_release(
|
||||
external_url="",
|
||||
file_name="downloads/cmhub-desktop-1.0.0.exe",
|
||||
)
|
||||
|
||||
response = self.client.get(self.url, secure=True)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.data["release"]["download_url"],
|
||||
"https://testserver/media/downloads/cmhub-desktop-1.0.0.exe",
|
||||
)
|
||||
|
||||
def test_latest_release_prefers_external_url_over_uploaded_file(self):
|
||||
self.create_release(
|
||||
external_url="https://cdn.example.com/cmhub-desktop-1.0.0.exe",
|
||||
file_name="downloads/local-secret-name.exe",
|
||||
)
|
||||
|
||||
response = self.client.get(self.url, secure=True)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.data["release"]["download_url"],
|
||||
"https://cdn.example.com/cmhub-desktop-1.0.0.exe",
|
||||
)
|
||||
response_body = json.dumps(response.data, ensure_ascii=False)
|
||||
self.assertNotIn("local-secret-name.exe", response_body)
|
||||
self.assertNotIn(str(settings.MEDIA_ROOT), response_body)
|
||||
|
||||
def test_latest_release_returns_unpublished_when_no_current_release(self):
|
||||
self.create_release(version="0.9.0", is_current=False)
|
||||
|
||||
response = self.client.get(f"{self.url}?platform=windows")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{
|
||||
"platform": "windows",
|
||||
"release": None,
|
||||
"message": "暂未发布",
|
||||
},
|
||||
)
|
||||
|
||||
def test_latest_release_returns_unpublished_when_current_release_has_no_download_url(self):
|
||||
self.create_release(external_url="", file_name="")
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIsNone(response.data["release"])
|
||||
self.assertEqual(response.data["message"], "暂未发布")
|
||||
|
||||
def test_latest_release_rejects_invalid_platform(self):
|
||||
response = self.client.get(f"{self.url}?platform=android")
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(response.data["error"]["code"], "bad_request")
|
||||
|
||||
def test_latest_release_supports_non_windows_platform(self):
|
||||
self.create_release(
|
||||
platform=DownloadRelease.Platform.MACOS,
|
||||
version="2.0.0",
|
||||
external_url="https://download.example.com/cmhub-2.0.0.dmg",
|
||||
sha256="c" * 64,
|
||||
release_notes="macOS 客户端",
|
||||
)
|
||||
|
||||
response = self.client.get(f"{self.url}?platform=macos")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["platform"], "macos")
|
||||
self.assertEqual(response.data["release"]["version"], "2.0.0")
|
||||
|
||||
def test_latest_release_response_does_not_expose_internal_fields(self):
|
||||
self.create_release(
|
||||
external_url="",
|
||||
file_name="downloads/cmhub-desktop-1.0.0.exe",
|
||||
)
|
||||
|
||||
response = self.client.get(self.url, secure=True)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(set(response.data), {"platform", "release"})
|
||||
self.assertEqual(
|
||||
set(response.data["release"]),
|
||||
{"version", "download_url", "sha256", "release_notes", "published_at"},
|
||||
)
|
||||
response_body = json.dumps(response.data, ensure_ascii=False)
|
||||
for forbidden in (
|
||||
"id",
|
||||
"is_current",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"MEDIA_ROOT",
|
||||
str(settings.MEDIA_ROOT),
|
||||
"user",
|
||||
"email",
|
||||
"api_key",
|
||||
"api_key_encrypted",
|
||||
"model_used",
|
||||
):
|
||||
self.assertNotIn(forbidden, response_body)
|
||||
|
||||
|
||||
@override_settings(
|
||||
PAYMENT_CALLBACK_MODE="mock",
|
||||
PAYMENT_MOCK_CALLBACK_SECRET="test-payment-callback-secret",
|
||||
|
||||
Reference in New Issue
Block a user