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 }