754 lines
24 KiB
Go
754 lines
24 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/ops/domain"
|
|
"github.com/geo-platform/tenant-api/internal/ops/repository"
|
|
"github.com/geo-platform/tenant-api/internal/shared/objectstorage"
|
|
"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 TestDesktopClientReleaseOSSObjectKeysIncludesInstallerAndUpdater(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
installerKey := "desktop-client/releases/blobs/installer.dmg"
|
|
updaterKey := "desktop-client/releases/blobs/updater.zip"
|
|
ossSource := DesktopClientDownloadSourceOSS
|
|
|
|
require.Equal(t, []string{installerKey, updaterKey}, desktopClientReleaseOSSObjectKeys(&domain.DesktopClientRelease{
|
|
DownloadSource: DesktopClientDownloadSourceOSS,
|
|
OSSObjectKey: &installerKey,
|
|
UpdaterDownloadSource: &ossSource,
|
|
UpdaterOSSObjectKey: &updaterKey,
|
|
}))
|
|
}
|
|
|
|
func TestDesktopClientReleaseOSSObjectKeysDedupesSharedInstallerUpdater(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
objectKey := "desktop-client/releases/blobs/shared.zip"
|
|
ossSource := DesktopClientDownloadSourceOSS
|
|
|
|
require.Equal(t, []string{objectKey}, desktopClientReleaseOSSObjectKeys(&domain.DesktopClientRelease{
|
|
DownloadSource: DesktopClientDownloadSourceOSS,
|
|
OSSObjectKey: &objectKey,
|
|
UpdaterDownloadSource: &ossSource,
|
|
UpdaterOSSObjectKey: &objectKey,
|
|
}))
|
|
}
|
|
|
|
func TestDesktopClientReleaseDeleteRemovesUnreferencedOSSObjects(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
installerKey := "desktop-client/releases/blobs/installer.dmg"
|
|
updaterKey := "desktop-client/releases/blobs/updater.zip"
|
|
ossSource := DesktopClientDownloadSourceOSS
|
|
store := &desktopClientReleaseStoreFake{
|
|
items: map[int64]*domain.DesktopClientRelease{
|
|
42: {
|
|
ID: 42,
|
|
DownloadSource: DesktopClientDownloadSourceOSS,
|
|
OSSObjectKey: &installerKey,
|
|
UpdaterDownloadSource: &ossSource,
|
|
UpdaterOSSObjectKey: &updaterKey,
|
|
},
|
|
},
|
|
}
|
|
storage := &desktopReleaseDownloadURLStorage{
|
|
existsByKey: map[string]bool{
|
|
installerKey: true,
|
|
updaterKey: true,
|
|
},
|
|
objects: map[string][]byte{
|
|
installerKey: []byte("installer"),
|
|
updaterKey: []byte("updater"),
|
|
},
|
|
}
|
|
svc := NewDesktopClientReleaseService(nil, storage, nil)
|
|
svc.releases = store
|
|
|
|
err := svc.Delete(context.Background(), nil, 42)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{installerKey, updaterKey}, storage.deleteCalls)
|
|
require.Equal(t, []int64{42}, store.deletedIDs)
|
|
}
|
|
|
|
func TestDesktopClientReleaseDeleteKeepsOSSObjectReferencedByAnotherRelease(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
installerKey := "desktop-client/releases/blobs/installer.dmg"
|
|
updaterKey := "desktop-client/releases/blobs/updater.zip"
|
|
ossSource := DesktopClientDownloadSourceOSS
|
|
store := &desktopClientReleaseStoreFake{
|
|
items: map[int64]*domain.DesktopClientRelease{
|
|
42: {
|
|
ID: 42,
|
|
DownloadSource: DesktopClientDownloadSourceOSS,
|
|
OSSObjectKey: &installerKey,
|
|
UpdaterDownloadSource: &ossSource,
|
|
UpdaterOSSObjectKey: &updaterKey,
|
|
},
|
|
},
|
|
references: map[string]bool{installerKey: true},
|
|
}
|
|
storage := &desktopReleaseDownloadURLStorage{
|
|
existsByKey: map[string]bool{
|
|
installerKey: true,
|
|
updaterKey: true,
|
|
},
|
|
objects: map[string][]byte{
|
|
installerKey: []byte("installer"),
|
|
updaterKey: []byte("updater"),
|
|
},
|
|
}
|
|
svc := NewDesktopClientReleaseService(nil, storage, nil)
|
|
svc.releases = store
|
|
|
|
err := svc.Delete(context.Background(), nil, 42)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{updaterKey}, storage.deleteCalls)
|
|
require.Equal(t, []int64{42}, store.deletedIDs)
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
func TestDesktopClientReleaseDirectUploadUsesPresignedPutURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
content := []byte("release-package")
|
|
sum := sha256.Sum256(content)
|
|
wantSHA := hex.EncodeToString(sum[:])
|
|
storage := &desktopReleaseDownloadURLStorage{
|
|
existsByKey: map[string]bool{},
|
|
objects: map[string][]byte{},
|
|
objectSizes: map[string]int64{},
|
|
}
|
|
svc := NewDesktopClientReleaseService(nil, storage, nil)
|
|
|
|
initResult, err := svc.InitiateDirectUpload(context.Background(), DesktopClientReleaseDirectUploadInitInput{
|
|
Platform: "darwin",
|
|
Arch: "arm64",
|
|
Channel: "stable",
|
|
Version: "1.2.3",
|
|
Kind: DesktopClientAssetKindInstaller,
|
|
FileName: "client.dmg",
|
|
FileSizeBytes: int64(len(content)),
|
|
SHA256: wantSHA,
|
|
ContentMD5: "pt74NOtqjIwBJtPUYvJ8AA==",
|
|
})
|
|
require.NoError(t, err)
|
|
require.False(t, initResult.ObjectExists)
|
|
require.Equal(t, http.MethodPut, initResult.Method)
|
|
require.Equal(t, "desktop-client/releases/blobs/"+wantSHA+".dmg", initResult.OSSObjectKey)
|
|
require.Equal(t, "https://oss.example.test/upload/"+initResult.OSSObjectKey, initResult.UploadURL)
|
|
require.Equal(t, "application/x-apple-diskimage", initResult.Headers["Content-Type"])
|
|
require.NotContains(t, initResult.Headers, "Content-Md5")
|
|
require.NotContains(t, initResult.Headers, "Content-MD5")
|
|
require.Len(t, storage.presignedPutCalls, 1)
|
|
require.Equal(t, initResult.OSSObjectKey, storage.presignedPutCalls[0])
|
|
require.Equal(t, []string{""}, storage.presignedPutMD5s)
|
|
|
|
storage.existsByKey[initResult.OSSObjectKey] = true
|
|
storage.objectSizes[initResult.OSSObjectKey] = int64(len(content))
|
|
storage.objects[initResult.OSSObjectKey] = content
|
|
completeResult, err := svc.CompleteDirectUpload(context.Background(), DesktopClientReleaseDirectUploadCompleteInput{
|
|
Platform: "darwin",
|
|
Arch: "arm64",
|
|
Channel: "stable",
|
|
Version: "1.2.3",
|
|
Kind: DesktopClientAssetKindInstaller,
|
|
OSSObjectKey: initResult.OSSObjectKey,
|
|
FileName: initResult.FileName,
|
|
FileSizeBytes: initResult.FileSizeBytes,
|
|
SHA256: initResult.SHA256,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, initResult.OSSObjectKey, completeResult.OSSObjectKey)
|
|
require.Equal(t, wantSHA, completeResult.SHA256)
|
|
}
|
|
|
|
func TestDesktopClientReleaseDirectUploadRejectsSizeMismatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
sum := sha256.Sum256([]byte("release-package"))
|
|
sha256Hex := hex.EncodeToString(sum[:])
|
|
objectKey := buildDesktopClientReleaseObjectKey(sha256Hex, "client.zip")
|
|
storage := &desktopReleaseDownloadURLStorage{
|
|
existsByKey: map[string]bool{objectKey: true},
|
|
objectSizes: map[string]int64{objectKey: 10},
|
|
}
|
|
svc := NewDesktopClientReleaseService(nil, storage, nil)
|
|
|
|
_, err := svc.CompleteDirectUpload(context.Background(), DesktopClientReleaseDirectUploadCompleteInput{
|
|
Platform: "darwin",
|
|
Arch: "arm64",
|
|
Channel: "stable",
|
|
Version: "1.2.3",
|
|
Kind: DesktopClientAssetKindUpdater,
|
|
OSSObjectKey: objectKey,
|
|
FileName: "client.zip",
|
|
FileSizeBytes: 20,
|
|
SHA256: sha256Hex,
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "direct_upload_size_mismatch")
|
|
}
|
|
|
|
func TestDesktopClientReleaseDirectUploadRejectsSHA256Mismatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
sum := sha256.Sum256([]byte("release-package"))
|
|
sha256Hex := hex.EncodeToString(sum[:])
|
|
objectKey := buildDesktopClientReleaseObjectKey(sha256Hex, "client.zip")
|
|
storage := &desktopReleaseDownloadURLStorage{
|
|
existsByKey: map[string]bool{objectKey: true},
|
|
objects: map[string][]byte{objectKey: []byte("different-package")},
|
|
objectSizes: map[string]int64{objectKey: int64(len("different-package"))},
|
|
}
|
|
svc := NewDesktopClientReleaseService(nil, storage, nil)
|
|
|
|
_, err := svc.CompleteDirectUpload(context.Background(), DesktopClientReleaseDirectUploadCompleteInput{
|
|
Platform: "darwin",
|
|
Arch: "arm64",
|
|
Channel: "stable",
|
|
Version: "1.2.3",
|
|
Kind: DesktopClientAssetKindUpdater,
|
|
OSSObjectKey: objectKey,
|
|
FileName: "client.zip",
|
|
FileSizeBytes: int64(len("different-package")),
|
|
SHA256: sha256Hex,
|
|
})
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid_sha256")
|
|
}
|
|
|
|
func TestDesktopClientReleaseDirectUploadCompleteJobReportsStatus(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
content := []byte("release-package")
|
|
sum := sha256.Sum256(content)
|
|
sha256Hex := hex.EncodeToString(sum[:])
|
|
objectKey := buildDesktopClientReleaseObjectKey(sha256Hex, "client.zip")
|
|
storage := &desktopReleaseDownloadURLStorage{
|
|
existsByKey: map[string]bool{objectKey: true},
|
|
objects: map[string][]byte{objectKey: content},
|
|
objectSizes: map[string]int64{objectKey: int64(len(content))},
|
|
}
|
|
svc := NewDesktopClientReleaseService(nil, storage, nil)
|
|
|
|
job := svc.StartDirectUploadCompleteJob(DesktopClientReleaseDirectUploadCompleteInput{
|
|
Platform: "darwin",
|
|
Arch: "arm64",
|
|
Channel: "stable",
|
|
Version: "1.2.3",
|
|
Kind: DesktopClientAssetKindUpdater,
|
|
OSSObjectKey: objectKey,
|
|
FileName: "client.zip",
|
|
FileSizeBytes: int64(len(content)),
|
|
SHA256: sha256Hex,
|
|
})
|
|
require.NotEmpty(t, job.JobID)
|
|
require.Equal(t, "running", job.Status)
|
|
|
|
require.Eventually(t, func() bool {
|
|
current, err := svc.GetCompleteJob(job.JobID)
|
|
require.NoError(t, err)
|
|
return current.Status == "succeeded" && current.Result != nil && current.Result.SHA256 == sha256Hex
|
|
}, time.Second, 10*time.Millisecond)
|
|
}
|
|
|
|
type desktopReleaseDownloadURLStorage struct {
|
|
publicURLCalls []string
|
|
downloadURLCalls []string
|
|
deleteCalls []string
|
|
existsByKey map[string]bool
|
|
objects map[string][]byte
|
|
objectSizes map[string]int64
|
|
presignedPutCalls []string
|
|
presignedPutMD5s []string
|
|
}
|
|
|
|
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.objectSizes == nil {
|
|
s.objectSizes = map[string]int64{}
|
|
}
|
|
s.objectSizes[objectKey] = int64(len(content))
|
|
if s.existsByKey == nil {
|
|
s.existsByKey = map[string]bool{}
|
|
}
|
|
s.existsByKey[objectKey] = true
|
|
return nil
|
|
}
|
|
|
|
func (s *desktopReleaseDownloadURLStorage) GetBytes(_ context.Context, objectKey string) ([]byte, error) {
|
|
if s.objects == nil {
|
|
return nil, fmt.Errorf("%w: %s", objectstorage.ErrObjectNotFound, objectKey)
|
|
}
|
|
content, ok := s.objects[objectKey]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", objectstorage.ErrObjectNotFound, objectKey)
|
|
}
|
|
return append([]byte(nil), content...), nil
|
|
}
|
|
|
|
func (s *desktopReleaseDownloadURLStorage) GetReader(_ context.Context, objectKey string) (io.ReadCloser, error) {
|
|
content, err := s.GetBytes(context.Background(), objectKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return io.NopCloser(bytes.NewReader(content)), nil
|
|
}
|
|
|
|
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) PresignedPutURL(
|
|
_ context.Context,
|
|
objectKey string,
|
|
contentType string,
|
|
contentMD5Base64 string,
|
|
ttl time.Duration,
|
|
) (*objectstorage.PresignedPutResult, error) {
|
|
s.presignedPutCalls = append(s.presignedPutCalls, objectKey)
|
|
s.presignedPutMD5s = append(s.presignedPutMD5s, contentMD5Base64)
|
|
headers := http.Header{}
|
|
headers.Set("Content-Type", contentType)
|
|
if contentMD5Base64 != "" {
|
|
headers.Set("Content-MD5", contentMD5Base64)
|
|
}
|
|
return &objectstorage.PresignedPutResult{
|
|
URL: "https://oss.example.test/upload/" + objectKey,
|
|
Headers: headers,
|
|
ExpiresAt: time.Now().UTC().Add(ttl),
|
|
}, nil
|
|
}
|
|
|
|
func (s *desktopReleaseDownloadURLStorage) Delete(_ context.Context, objectKey string) error {
|
|
s.deleteCalls = append(s.deleteCalls, objectKey)
|
|
if s.objects != nil {
|
|
delete(s.objects, objectKey)
|
|
}
|
|
if s.existsByKey != nil {
|
|
s.existsByKey[objectKey] = false
|
|
}
|
|
if s.objectSizes != nil {
|
|
delete(s.objectSizes, objectKey)
|
|
}
|
|
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
|
|
}
|
|
|
|
func (s *desktopReleaseDownloadURLStorage) List(context.Context, objectstorage.ListInput) (*objectstorage.ListResult, error) {
|
|
return &objectstorage.ListResult{}, nil
|
|
}
|
|
|
|
func (s *desktopReleaseDownloadURLStorage) Stat(_ context.Context, objectKey string) (*objectstorage.ObjectInfo, error) {
|
|
exists, _ := s.Exists(context.Background(), objectKey)
|
|
if !exists {
|
|
return nil, objectstorage.ErrObjectNotFound
|
|
}
|
|
if s.objectSizes != nil {
|
|
return &objectstorage.ObjectInfo{Key: objectKey, Size: s.objectSizes[objectKey], LastModified: time.Now()}, nil
|
|
}
|
|
if s.objects != nil {
|
|
return &objectstorage.ObjectInfo{Key: objectKey, Size: int64(len(s.objects[objectKey])), LastModified: time.Now()}, nil
|
|
}
|
|
return &objectstorage.ObjectInfo{Key: objectKey, LastModified: time.Now()}, nil
|
|
}
|
|
|
|
func (s *desktopReleaseDownloadURLStorage) Copy(context.Context, string, string) error {
|
|
return nil
|
|
}
|
|
|
|
type desktopClientReleaseStoreFake struct {
|
|
items map[int64]*domain.DesktopClientRelease
|
|
references map[string]bool
|
|
deletedIDs []int64
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) List(context.Context, repository.DesktopClientReleaseFilter) ([]domain.DesktopClientRelease, int64, error) {
|
|
items := make([]domain.DesktopClientRelease, 0, len(s.items))
|
|
for _, item := range s.items {
|
|
items = append(items, *item)
|
|
}
|
|
return items, int64(len(items)), nil
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) ListEnabledByChannel(context.Context, string) ([]domain.DesktopClientRelease, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) Create(context.Context, repository.UpsertDesktopClientReleaseInput) (*domain.DesktopClientRelease, error) {
|
|
return nil, errors.New("unexpected create call")
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) Update(context.Context, int64, repository.UpsertDesktopClientReleaseInput) (*domain.DesktopClientRelease, error) {
|
|
return nil, errors.New("unexpected update call")
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) SetEnabled(context.Context, int64, bool, *int64) (*domain.DesktopClientRelease, error) {
|
|
return nil, errors.New("unexpected set enabled call")
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) GetByID(_ context.Context, id int64) (*domain.DesktopClientRelease, error) {
|
|
item, ok := s.items[id]
|
|
if !ok {
|
|
return nil, repository.ErrDesktopClientReleaseNotFound
|
|
}
|
|
copied := *item
|
|
return &copied, nil
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) ListEnabledByTarget(context.Context, repository.DesktopClientReleaseTarget) ([]domain.DesktopClientRelease, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) HasObjectReference(_ context.Context, objectKey string, _ int64) (bool, error) {
|
|
return s.references[objectKey], nil
|
|
}
|
|
|
|
func (s *desktopClientReleaseStoreFake) Delete(_ context.Context, id int64) error {
|
|
if _, ok := s.items[id]; !ok {
|
|
return repository.ErrDesktopClientReleaseNotFound
|
|
}
|
|
s.deletedIDs = append(s.deletedIDs, id)
|
|
delete(s.items, id)
|
|
return nil
|
|
}
|