Files
geo/server/internal/ops/app/desktop_client_release_test.go
T
root 6a4eaf2827
Frontend CI / Frontend (push) Successful in 2m12s
Backend CI / Backend (push) Successful in 14m12s
fix: allow desktop release version history
2026-06-10 13:13:06 +08:00

369 lines
11 KiB
Go

package app
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"os"
"testing"
"time"
"github.com/geo-platform/tenant-api/internal/ops/domain"
"github.com/stretchr/testify/require"
)
func TestNormalizeDesktopClientReleaseInputOSS(t *testing.T) {
t.Parallel()
customURL := "https://download.example.com/client.dmg"
ossKey := "desktop/stable/client.dmg"
got, err := normalizeDesktopClientReleaseInput(DesktopClientReleaseInput{
Platform: " Darwin ",
Arch: "",
Channel: "",
Version: "1.2.3",
DownloadSource: "oss",
OSSObjectKey: &ossKey,
CustomDownloadURL: &customURL,
Enabled: true,
})
require.NoError(t, err)
require.Equal(t, "darwin", got.Platform)
require.Equal(t, "universal", got.Arch)
require.Equal(t, "stable", got.Channel)
require.Equal(t, DesktopClientDownloadSourceOSS, got.DownloadSource)
require.NotNil(t, got.OSSObjectKey)
require.Equal(t, "desktop/stable/client.dmg", *got.OSSObjectKey)
require.Nil(t, got.CustomDownloadURL)
}
func TestNormalizeDesktopClientReleaseInputCustom(t *testing.T) {
t.Parallel()
customURL := "https://download.example.com/client.exe"
ossKey := "desktop/stable/client.exe"
got, err := normalizeDesktopClientReleaseInput(DesktopClientReleaseInput{
Platform: "win32",
Arch: "x64",
Channel: "stable",
Version: "1.2.3",
DownloadSource: "custom",
OSSObjectKey: &ossKey,
CustomDownloadURL: &customURL,
Enabled: true,
})
require.NoError(t, err)
require.Equal(t, DesktopClientDownloadSourceCustom, got.DownloadSource)
require.Nil(t, got.OSSObjectKey)
require.NotNil(t, got.CustomDownloadURL)
require.Equal(t, customURL, *got.CustomDownloadURL)
}
func TestNormalizeDesktopClientReleaseInputRejectsInvalidDownloadTargets(t *testing.T) {
t.Parallel()
invalidOSSKey := "../client.dmg"
_, err := normalizeDesktopClientReleaseInput(DesktopClientReleaseInput{
Platform: "darwin",
Version: "1.2.3",
DownloadSource: "oss",
OSSObjectKey: &invalidOSSKey,
})
require.Error(t, err)
invalidURL := "javascript:alert(1)"
_, err = normalizeDesktopClientReleaseInput(DesktopClientReleaseInput{
Platform: "win32",
Version: "1.2.3",
DownloadSource: "custom",
CustomDownloadURL: &invalidURL,
})
require.Error(t, err)
}
func TestDesktopClientReleaseVersionPolicy(t *testing.T) {
t.Parallel()
require.Positive(t, compareVersionStrings("1.2.10", "1.2.9"))
require.Negative(t, compareVersionStrings("1.2.9", "1.2.10"))
minVersion := "1.2.0"
require.True(t, minSupportedVersionRequiresUpdate(&minVersion, "1.1.9"))
require.False(t, minSupportedVersionRequiresUpdate(&minVersion, "1.2.0"))
}
func TestLatestDesktopClientReleasePrefersVersionThenArch(t *testing.T) {
t.Parallel()
oldX64 := domain.DesktopClientRelease{
ID: 1,
Platform: "win32",
Arch: "x64",
Channel: "stable",
Version: "0.1.0",
UpdatedAt: time.Date(2026, 6, 10, 10, 0, 0, 0, time.UTC),
}
newX64 := domain.DesktopClientRelease{
ID: 2,
Platform: "win32",
Arch: "x64",
Channel: "stable",
Version: "0.1.1",
UpdatedAt: time.Date(2026, 6, 10, 9, 0, 0, 0, time.UTC),
}
newUniversal := domain.DesktopClientRelease{
ID: 3,
Platform: "win32",
Arch: "universal",
Channel: "stable",
Version: "0.2.0",
UpdatedAt: time.Date(2026, 6, 10, 11, 0, 0, 0, time.UTC),
}
require.Equal(t, newX64.ID, latestDesktopClientRelease([]domain.DesktopClientRelease{
newUniversal,
oldX64,
newX64,
}).ID)
latestByTarget := latestDesktopClientReleasesByTarget([]domain.DesktopClientRelease{
oldX64,
newX64,
newUniversal,
})
require.Len(t, latestByTarget, 2)
require.Equal(t, newUniversal.ID, latestByTarget[0].ID)
require.Equal(t, newX64.ID, latestByTarget[1].ID)
}
func TestNormalizeDesktopClientReleaseUploadInput(t *testing.T) {
t.Parallel()
target, fileName, err := normalizeDesktopClientReleaseUploadInput(DesktopClientReleaseUploadInput{
Platform: "darwin",
Arch: "arm64",
Channel: "stable",
Version: "1.2.3",
FileName: "省心推-1.2.3-arm64.dmg",
Content: []byte("package"),
})
require.NoError(t, err)
require.Equal(t, "darwin", target.Platform)
require.Equal(t, "arm64", target.Arch)
require.Equal(t, "stable", target.Channel)
require.Equal(t, "省心推-1.2.3-arm64.dmg", fileName)
}
func TestNormalizeDesktopClientReleaseUploadInputDecodesFileName(t *testing.T) {
t.Parallel()
_, fileName, err := normalizeDesktopClientReleaseUploadInput(DesktopClientReleaseUploadInput{
Platform: "darwin",
Arch: "arm64",
Channel: "stable",
Version: "1.2.3",
FileName: "%E7%9C%81%E5%BF%83%E6%8E%A8-mac-0.1.19.zip",
Content: []byte("package"),
})
require.NoError(t, err)
require.Equal(t, "省心推-mac-0.1.19.zip", fileName)
}
func TestNormalizeDesktopClientReleaseUploadInputRejectsInvalidFile(t *testing.T) {
t.Parallel()
_, _, err := normalizeDesktopClientReleaseUploadInput(DesktopClientReleaseUploadInput{
Platform: "darwin",
Arch: "arm64",
Channel: "stable",
Version: "1.2.3",
FileName: "client.txt",
Content: []byte("package"),
})
require.Error(t, err)
}
func TestBuildDesktopClientUpdaterFeedYAMLUsesSignedURL(t *testing.T) {
t.Parallel()
fileName := "省心推-mac-1.2.3.zip"
sha256 := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
size := int64(1024)
publishedAt := time.Date(2026, 5, 25, 10, 30, 0, 0, time.UTC)
content, err := buildDesktopClientUpdaterFeedYAML(&domain.DesktopClientRelease{
Platform: "darwin",
Version: "1.2.3",
PublishedAt: &publishedAt,
UpdatedAt: publishedAt,
}, desktopClientReleaseAsset{
FileName: &fileName,
FileSizeBytes: &size,
SHA256: &sha256,
}, "https://oss.example.com/desktop/%E7%9C%81%E5%BF%83%E6%8E%A8.zip?Expires=123&Signature=abc")
require.NoError(t, err)
text := string(content)
require.Contains(t, text, "version: 1.2.3")
require.Contains(t, text, "sha2: "+sha256)
require.Contains(t, text, "size: 1024")
require.Contains(t, text, "Expires=123")
require.Contains(t, text, "Signature=abc")
require.NotContains(t, text, "%25E7")
}
func TestValidateDesktopClientUpdaterPackage(t *testing.T) {
t.Parallel()
zipName := "省心推-mac-1.2.3.zip"
require.NoError(t, validateDesktopClientUpdaterPackage("darwin", &zipName, ""))
dmgName := "省心推-mac-1.2.3.dmg"
require.Error(t, validateDesktopClientUpdaterPackage("darwin", &dmgName, ""))
exeURL := "https://download.example.com/%E7%9C%81%E5%BF%83%E6%8E%A8%20Setup%201.2.3.exe"
require.NoError(t, validateDesktopClientUpdaterPackage("win32", nil, exeURL))
}
func TestBuildDesktopClientReleaseObjectKeyReusesContentAddress(t *testing.T) {
t.Parallel()
sum := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
first := buildDesktopClientReleaseObjectKey(sum, "省心推 Setup 1.2.3.exe")
second := buildDesktopClientReleaseObjectKey(sum, "renamed.exe")
require.Equal(t, first, second)
require.Equal(t, "desktop-client/releases/blobs/"+sum+".exe", first)
}
func TestResolveDownloadURLResultUsesSignedDownloadURL(t *testing.T) {
t.Parallel()
objectKey := "desktop-client/releases/blobs/client.zip"
storage := &desktopReleaseDownloadURLStorage{}
svc := &DesktopClientReleaseService{storage: storage}
result, err := svc.resolveDownloadURLResult(&domain.DesktopClientRelease{
DownloadSource: DesktopClientDownloadSourceOSS,
OSSObjectKey: &objectKey,
})
require.NoError(t, err)
require.Equal(t, "https://oss.example.test/signed/client.zip?Expires=123", result.DownloadURL)
require.Equal(t, []string{objectKey}, storage.downloadURLCalls)
require.Empty(t, storage.publicURLCalls)
}
func TestDesktopClientReleaseChunkedUploadCompletesOutOfOrder(t *testing.T) {
t.Parallel()
storage := &desktopReleaseDownloadURLStorage{
existsByKey: map[string]bool{},
objects: map[string][]byte{},
}
svc := NewDesktopClientReleaseService(nil, storage, nil).WithReleaseUploadDir(t.TempDir())
content := bytes.Repeat([]byte("release-package-"), 1024*1024)
sum := sha256.Sum256(content)
wantSHA := hex.EncodeToString(sum[:])
initResult, err := svc.InitiateChunkedUpload(context.Background(), DesktopClientReleaseChunkedUploadInitInput{
Platform: "darwin",
Arch: "arm64",
Channel: "stable",
Version: "1.2.3",
Kind: DesktopClientAssetKindInstaller,
FileName: "client.dmg",
FileSizeBytes: int64(len(content)),
ChunkSizeBytes: 5 * 1024 * 1024,
})
require.NoError(t, err)
require.Equal(t, 4, initResult.ChunkCount)
order := []int{2, 0, 3, 1}
for _, index := range order {
start := int64(index) * initResult.ChunkSizeBytes
end := min(start+initResult.ChunkSizeBytes, int64(len(content)))
result, err := svc.UploadChunk(context.Background(), DesktopClientReleaseChunkUploadInput{
UploadID: initResult.UploadID,
ChunkIndex: index,
ContentReader: bytes.NewReader(content[start:end]),
ContentSizeBytes: end - start,
})
require.NoError(t, err)
require.Equal(t, index, result.ChunkIndex)
}
result, err := svc.CompleteChunkedUpload(context.Background(), initResult.UploadID)
require.NoError(t, err)
require.Equal(t, wantSHA, result.SHA256)
require.Equal(t, int64(len(content)), result.FileSizeBytes)
require.Equal(t, "desktop-client/releases/blobs/"+wantSHA+".dmg", result.OSSObjectKey)
require.Equal(t, content, storage.objects[result.OSSObjectKey])
_, err = os.Stat(svc.desktopReleaseUploadSessionDir(initResult.UploadID))
require.True(t, errors.Is(err, os.ErrNotExist))
}
type desktopReleaseDownloadURLStorage struct {
publicURLCalls []string
downloadURLCalls []string
existsByKey map[string]bool
objects map[string][]byte
}
func (s *desktopReleaseDownloadURLStorage) Validate() error {
return nil
}
func (s *desktopReleaseDownloadURLStorage) PutBytes(context.Context, string, []byte, string) error {
return nil
}
func (s *desktopReleaseDownloadURLStorage) PutReader(_ context.Context, objectKey string, reader io.Reader, _ int64, _ string) error {
if s.objects == nil {
s.objects = map[string][]byte{}
}
content, err := io.ReadAll(reader)
if err != nil {
return err
}
s.objects[objectKey] = content
if s.existsByKey == nil {
s.existsByKey = map[string]bool{}
}
s.existsByKey[objectKey] = true
return nil
}
func (s *desktopReleaseDownloadURLStorage) GetBytes(context.Context, string) ([]byte, error) {
return nil, errors.New("not implemented")
}
func (s *desktopReleaseDownloadURLStorage) Exists(_ context.Context, objectKey string) (bool, error) {
if s.existsByKey != nil {
return s.existsByKey[objectKey], nil
}
return true, nil
}
func (s *desktopReleaseDownloadURLStorage) Delete(context.Context, string) error {
return nil
}
func (s *desktopReleaseDownloadURLStorage) PublicURL(objectKey string) (string, error) {
s.publicURLCalls = append(s.publicURLCalls, objectKey)
return "", errors.New("public url should not be used for private release downloads")
}
func (s *desktopReleaseDownloadURLStorage) DownloadURL(objectKey string) (string, error) {
s.downloadURLCalls = append(s.downloadURLCalls, objectKey)
return "https://oss.example.test/signed/client.zip?Expires=123", nil
}