detached 5dbd27f

#24 feat(releases): GitHub-like releases and S3 blob storage

state
merged · 5dbd27f · fast-forward
branch
feat/releases → main
opened
bnema ·
updated

GitHub-like releases for ermite, plus a pluggable blob store (ADR 0034).

What it adds

  • Releases bound to a Git tag: title, Markdown notes, assets, draft/prerelease, latest (make_latest pin). Deleting the tag turns the release back into a draft.
  • Interfaces: GitHub-compatible REST under /-/github/api (also /api/v3 for go-github/GoReleaser), ConnectRPC ReleaseService, ermite release CLI, read-only web pages.
  • Stable links: //releases/latest/download/ and //releases/download//. Downloads are always relayed by ermite (Range/If-Range supported).
  • Scope release:write (in the cli preset), or a job token with contents: write. Branch-limited tokens are refused.
  • Blob store: disk ($ERMITE_DATA_DIR/blobs) or any S3-compatible provider via ERMITE_S3_* (common S3 subset, ermite never creates buckets). CI artifacts move onto it; legacy disk artifacts stay readable.
  • Storage schema 6: unrestricted tokens holding every schema-5 scope gain release:write.
  • Requires ermite-api v0.16.0 (SCOPE_RELEASE_WRITE = 13, release events 27-30).

Checks

  • go build, vet, hexcheck, go test -race ./... green.
  • e2e on the local dev stack: disk and MinIO backends, Firecracker runner with action-gh-release and upload-artifact (20 MiB), Playwright on the web pages. S3 contract suite against MinIO and a real Scaleway bucket.
  • Wrap-up review: 13 findings, all fixed with regression tests.

Deployment (after merge): Gordon secrets and S3 config for staging then prod, re-enable release.yml workflows.

CommitSubjectAuthorDateSignatureStatus
5dbd27ffix(lint): check ignored close and remove errors, simplify release conditionsbriceGPG Verified
b869944chore(deps): require ermite-api v0.16.0 and drop the local replacebriceGPG Verified
1428b20fix(releases): hide draft events and cursors, honour update targets, share range handlingbriceGPG Verified
97e2b33fix(blobs): reclaim crashed temp files, skip broken multipart entries, sync new directoriesbriceGPG Verified
8361f60fix(ci): bound concurrent artifact block uploads and reuse their buffersbriceGPG Verified
6abe390test(blobs): run the blob contract on MinIO, which lists unfinished uploads by keybriceGPG Verified
27d9aa4fix(githubapi): accept the api/v3 base that GoReleaser appends to the API URLbriceGPG Verified
d50b7cdfeat(app): wire releases and the blob store, S3 configuration, MinIO compose profile and docsbriceGPG Verified
0933a48feat(cli): ermite release commands and release:write in the cli presetbriceGPG Verified
f67dc6afeat(githubapi): GitHub-compatible REST API for releases and assetsbriceGPG Verified
eab40eefeat(web): releases tab, release pages and stable asset download linksbriceGPG Verified
bfabb0cfeat(rpc): ConnectRPC ReleaseService with streamed asset upload and downloadbriceGPG Verified
b8fe0a4fix(rpc): map the release:write scope to the contractbriceGPG Verified
f9e6941feat(ci): store CI artifacts on the blob store, keep legacy disk artifacts readablebriceGPG Verified
eb72c2ffeat(releases): release, asset and sweep use casesbriceGPG Verified
0c4e1e3feat(releases): git tag writer and release hooks on pushes and repository deletionbriceGPG Verified
8fd2b19feat(blobs): blob store port with disk and provider-agnostic S3 adaptersbriceGPG Verified
c57f9ecfeat(releases): release domain, kv storage, ports and schema 6 with release:writebriceGPG Verified

Checks 8

149 files · +17035 -809
8/8
M .hexcheck.yaml +12 -2
M README.md +1 -0
M compose.yaml +39 -0
M docs/ci-artifacts.md +2 -1
M docs/cli.md +45 -0
M docs/configuration.md +29 -1
A docs/releases.md +99 -0
A docs/storage.md +92 -0
M docs/tokens.md +9 -0
M gen/ermite/storage/ci_artifact.pb.go +15 -5
A gen/ermite/storage/release.pb.go +463 -0
M go.mod +16 -1
M go.sum +40 -2
A internal/adapters/blobdisk/blobdisk.go +572 -0
A internal/adapters/blobdisk/blobdisk_internal_test.go +87 -0
A internal/adapters/blobdisk/blobdisk_test.go +29 -0
A internal/adapters/blobs3/blobs3.go +461 -0
A internal/adapters/blobs3/blobs3_test.go +85 -0
A internal/adapters/blobs3/config.go +13 -0
A internal/adapters/blobs3/integration_test.go +44 -0
A internal/adapters/blobtest/suite.go +256 -0
M internal/adapters/ciartifacts/ciartifacts.go +5 -171
M internal/adapters/ciartifacts/ciartifacts_test.go +8 -105
M internal/adapters/client/client.go +5 -0
M internal/adapters/client/client_test.go +3 -3
M internal/adapters/client/features.go +3 -1
A internal/adapters/client/release_test.go +43 -0
M internal/adapters/client/scopes_test.go +1 -0
A internal/adapters/gitcli/tags.go +42 -0
A internal/adapters/gitcli/tags_test.go +31 -0
M internal/adapters/githubapi/handler.go +23 -2
A internal/adapters/githubapi/releases.go +413 -0
A internal/adapters/githubapi/releases_test.go +466 -0
A internal/adapters/githubapi/testdata/releases/draft.json +40 -0
A internal/adapters/githubapi/testdata/releases/release.json +113 -0
M internal/adapters/kvstore/ci_artifact.go +2 -1
M internal/adapters/kvstore/ci_artifact_test.go +5 -0
M internal/adapters/kvstore/issues_test.go +16 -8
A internal/adapters/kvstore/releases.go +408 -0
A internal/adapters/kvstore/releases_test.go +131 -0
M internal/adapters/kvstore/repos.go +1 -0
M internal/adapters/kvstore/store.go +25 -4
M internal/adapters/kvstore/store_test.go +57 -0
M internal/adapters/rpc/api.go +9 -0
M internal/adapters/rpc/convert.go +28 -3
M internal/adapters/rpc/errors.go +2 -0
M internal/adapters/rpc/event.go +26 -0
A internal/adapters/rpc/release.go +358 -0
A internal/adapters/rpc/release_test.go +472 -0
M internal/adapters/web/fragments.go +8 -2
M internal/adapters/web/fragments_test.go +20 -0
A internal/adapters/web/releases.go +244 -0
A internal/adapters/web/releases.templ +67 -0
A internal/adapters/web/releases_templ.go +439 -0
A internal/adapters/web/releases_test.go +298 -0
M internal/adapters/web/repo.go +3 -0
M internal/adapters/web/web.go +11 -0
M internal/app/api_test.go +1 -1
M internal/app/ci_artifacts_test.go +110 -12
M internal/app/ci_e2e_test.go +2 -0
M internal/app/cli_e2e_test.go +4 -3
A internal/app/cli_release.go +603 -0
A internal/app/cli_release_e2e_test.go +65 -0
A internal/app/cli_release_test.go +388 -0
M internal/app/cli_token.go +9 -0
M internal/app/cli_token_test.go +40 -1
M internal/app/cli_version.go +1 -0
M internal/app/cli_version_test.go +5 -5
M internal/app/forge.go +49 -4
A internal/app/release_e2e_test.go +160 -0
M internal/app/root.go +1 -1
M internal/app/testdata/web/blob-binary.html +15 -5
M internal/app/testdata/web/blob-code.html +15 -5
M internal/app/testdata/web/blob-empty.html +15 -5
M internal/app/testdata/web/blob-image.html +15 -5
M internal/app/testdata/web/blob-markdown.html +15 -5
M internal/app/testdata/web/blob-svg.html +15 -5
M internal/app/testdata/web/blob-text.html +15 -5
M internal/app/testdata/web/blob-too-large.html +15 -5
M internal/app/testdata/web/blob-trojan.html +15 -5
M internal/app/testdata/web/commit-big.html +15 -5
M internal/app/testdata/web/commit-binary.html +15 -5
M internal/app/testdata/web/commit-merge.html +15 -5
M internal/app/testdata/web/commit-modify-split.html +15 -5
M internal/app/testdata/web/commit-modify.html +15 -5
M internal/app/testdata/web/commit-rename.html +15 -5
M internal/app/testdata/web/commit-root.html +15 -5
M internal/app/testdata/web/commit-trojan.html +15 -5
M internal/app/testdata/web/commits-page2.html +14 -4
M internal/app/testdata/web/commits-path.html +14 -4
M internal/app/testdata/web/commits-root.html +14 -4
M internal/app/testdata/web/commits-small.html +16 -6
M internal/app/testdata/web/refs.html +17 -7
M internal/app/testdata/web/summary.html +17 -7
M internal/app/testdata/web/tree-nested.html +15 -5
M internal/app/testdata/web/tree-root.html +15 -5
M internal/app/testdata/web/tree-slash-ref.html +15 -5
M internal/app/testdata/web/tree-subdir.html +15 -5
M internal/app/web.go +1 -0
A internal/app/web_release_test.go +111 -0
M internal/boundary/boundary_test.go +14 -2
@@ -35,6 +35,7 @@ const (
35 layerUsecase packageLayer = "usecase"35 layerUsecase packageLayer = "usecase"
36 layerAdapter packageLayer = "adapter"36 layerAdapter packageLayer = "adapter"
37 layerClientLimit packageLayer = "clientlimit"37 layerClientLimit packageLayer = "clientlimit"
38+ layerHTTPRange packageLayer = "httprange"
38 layerConfig packageLayer = "config"39 layerConfig packageLayer = "config"
39 layerPorts packageLayer = "ports"40 layerPorts packageLayer = "ports"
40 layerDomain packageLayer = "domain"41 layerDomain packageLayer = "domain"
@@ -55,13 +56,16 @@ const (
55 // each other, and keeps the web adapter off the generated API code: like56 // each other, and keeps the web adapter off the generated API code: like
56 // every other adapter it reads and writes through the ports (ADR 0020).57 // every other adapter it reads and writes through the ports (ADR 0020).
57 // - internal/clientlimit is infrastructure shared by the HTTP adapters.58 // - internal/clientlimit is infrastructure shared by the HTTP adapters.
59+// - internal/httprange is infrastructure shared by the HTTP adapters that
60+// serve byte ranges; it imports nothing of the module.
58 // - internal/config parses the environment into plain values.61 // - internal/config parses the environment into plain values.
59 var productionDependencies = map[packageLayer]map[packageLayer]bool{62 var productionDependencies = map[packageLayer]map[packageLayer]bool{
60 layerCommand: {layerApp: true},63 layerCommand: {layerApp: true},
61- layerApp: {layerUsecase: true, layerAdapter: true, layerClientLimit: true, layerConfig: true, layerPorts: true, layerDomain: true, layerGenerated: true},64+ layerApp: {layerUsecase: true, layerAdapter: true, layerClientLimit: true, layerHTTPRange: true, layerConfig: true, layerPorts: true, layerDomain: true, layerGenerated: true},
62 layerUsecase: {layerUsecase: true, layerPorts: true, layerDomain: true},65 layerUsecase: {layerUsecase: true, layerPorts: true, layerDomain: true},
63- layerAdapter: {layerAdapter: true, layerClientLimit: true, layerPorts: true, layerDomain: true, layerGenerated: true},66+ layerAdapter: {layerAdapter: true, layerClientLimit: true, layerHTTPRange: true, layerPorts: true, layerDomain: true, layerGenerated: true},
64 layerClientLimit: {layerPorts: true},67 layerClientLimit: {layerPorts: true},
68+ layerHTTPRange: {},
65 layerConfig: {},69 layerConfig: {},
66 layerPorts: {layerDomain: true},70 layerPorts: {layerDomain: true},
67 layerDomain: {layerDomain: true},71 layerDomain: {layerDomain: true},
@@ -149,6 +153,12 @@ func TestImportBoundaryFixtures(t *testing.T) {
149 {"adapter rejects config", p("internal/adapters/rpc"), p("internal/config"), false, false},153 {"adapter rejects config", p("internal/adapters/rpc"), p("internal/config"), false, false},
150 {"adapter rejects sibling adapter", p("internal/adapters/web"), p("internal/adapters/gitcli"), false, false},154 {"adapter rejects sibling adapter", p("internal/adapters/web"), p("internal/adapters/gitcli"), false, false},
151 {"adapter test may import sibling adapter", p("internal/adapters/ermitegit"), p("internal/adapters/gitcli"), true, true},155 {"adapter test may import sibling adapter", p("internal/adapters/ermitegit"), p("internal/adapters/gitcli"), true, true},
156+ {"app accepts httprange", p("internal/app"), p("internal/httprange"), false, true},
157+ {"usecase rejects httprange", p("internal/usecase"), p("internal/httprange"), false, false},
158+ {"domain rejects httprange", p("internal/domain"), p("internal/httprange"), false, false},
159+ {"ports reject httprange", p("internal/ports"), p("internal/httprange"), false, false},
160+ {"adapter accepts httprange", p("internal/adapters/web"), p("internal/httprange"), false, true},
161+ {"httprange rejects domain", p("internal/httprange"), p("internal/domain"), false, false},
152 {"clientlimit accepts ports", p("internal/clientlimit"), p("internal/ports"), false, true},162 {"clientlimit accepts ports", p("internal/clientlimit"), p("internal/ports"), false, true},
153 {"clientlimit rejects usecase", p("internal/clientlimit"), p("internal/usecase"), false, false},163 {"clientlimit rejects usecase", p("internal/clientlimit"), p("internal/usecase"), false, false},
154 {"config rejects domain", p("internal/config"), p("internal/domain"), false, false},164 {"config rejects domain", p("internal/config"), p("internal/domain"), false, false},
@@ -319,6 +329,8 @@ func classifyPackage(path string) (packageLayer, error) {
319 return layerAdapter, nil329 return layerAdapter, nil
320 case under(path, p("internal/clientlimit")):330 case under(path, p("internal/clientlimit")):
321 return layerClientLimit, nil331 return layerClientLimit, nil
332+ case under(path, p("internal/httprange")):
333+ return layerHTTPRange, nil
322 case under(path, p("internal/config")):334 case under(path, p("internal/config")):
323 return layerConfig, nil335 return layerConfig, nil
324 case path == p("internal/ports"):336 case path == p("internal/ports"):
M internal/config/config.go +117 -0
@@ -12,6 +12,7 @@ import (
12 "net"12 "net"
13 "net/netip"13 "net/netip"
14 "net/url"14 "net/url"
15+ "os"
15 "path/filepath"16 "path/filepath"
16 "strconv"17 "strconv"
17 "strings"18 "strings"
@@ -56,6 +57,16 @@ const (
56 EnvCIArtifactRetention = "ERMITE_CI_ARTIFACT_RETENTION"57 EnvCIArtifactRetention = "ERMITE_CI_ARTIFACT_RETENTION"
57 EnvCIArtifactQuota = "ERMITE_CI_ARTIFACT_QUOTA"58 EnvCIArtifactQuota = "ERMITE_CI_ARTIFACT_QUOTA"
58 EnvCIArtifactMax = "ERMITE_CI_ARTIFACT_MAX"59 EnvCIArtifactMax = "ERMITE_CI_ARTIFACT_MAX"
60+ EnvS3Bucket = "ERMITE_S3_BUCKET"
61+ EnvS3Endpoint = "ERMITE_S3_ENDPOINT"
62+ EnvS3Region = "ERMITE_S3_REGION"
63+ EnvS3AccessKeyID = "ERMITE_S3_ACCESS_KEY_ID"
64+ EnvS3SecretAccessKey = "ERMITE_S3_SECRET_ACCESS_KEY"
65+ EnvS3ForcePathStyle = "ERMITE_S3_FORCE_PATH_STYLE"
66+ EnvS3Prefix = "ERMITE_S3_PREFIX"
67+ EnvReleaseAssetMax = "ERMITE_RELEASE_ASSET_MAX"
68+ EnvReleaseAssetsMax = "ERMITE_RELEASE_ASSETS_MAX"
69+ EnvReleaseQuota = "ERMITE_RELEASE_QUOTA"
59 )70 )
60 71
61 // SecretsKeyLen is the length of the secrets key in bytes (AES-256).72 // SecretsKeyLen is the length of the secrets key in bytes (AES-256).
@@ -140,6 +151,12 @@ type Config struct {
140 CIArtifactRetention time.Duration151 CIArtifactRetention time.Duration
141 CIArtifactQuota int64152 CIArtifactQuota int64
142 CIArtifactMax int64153 CIArtifactMax int64
154+ // S3Bucket selects S3; empty selects local disk. Credentials are secrets.
155+ S3Bucket, S3Endpoint, S3Region, S3AccessKeyID, S3SecretAccessKey, S3Prefix string
156+ S3ForcePathStyle bool
157+ // Zero release limits leave defaults to the release use case.
158+ ReleaseAssetMax, ReleaseQuota int64
159+ ReleaseAssetsMax int
143 }160 }
144 161
145 // Error lists every configuration problem found, in variable order.162 // Error lists every configuration problem found, in variable order.
@@ -167,6 +184,7 @@ func Load(get func(string) string) (Config, error) {
167 bad := func(key, format string, args ...any) {184 bad := func(key, format string, args ...any) {
168 problems = append(problems, key+": "+fmt.Sprintf(format, args...))185 problems = append(problems, key+": "+fmt.Sprintf(format, args...))
169 }186 }
187+ get = resolveFiles(get, &problems)
170 var c Config188 var c Config
171 189
172 c.Addr = get(EnvAddr)190 c.Addr = get(EnvAddr)
@@ -342,12 +360,111 @@ func Load(get func(string) string) (Config, error) {
342 }360 }
343 }361 }
344 362
363+ loadStorage(get, &c, &problems)
345 if len(problems) > 0 {364 if len(problems) > 0 {
346 return Config{}, &Error{Problems: problems}365 return Config{}, &Error{Problems: problems}
347 }366 }
348 return c, nil367 return c, nil
349 }368 }
350 369
370+// resolveFiles resolves only the four supported secrets, once at startup.
371+// Neither file paths, file errors nor secret contents enter diagnostics.
372+func resolveFiles(get func(string) string, problems *[]string) func(string) string {
373+ values := make(map[string]string)
374+ for _, key := range []string{EnvAdminToken, EnvSecretsKey, EnvS3AccessKeyID, EnvS3SecretAccessKey} {
375+ value, file := get(key), get(key+"_FILE")
376+ if value != "" && file != "" {
377+ *problems = append(*problems, key+": cannot set both variable and _FILE")
378+ } else if file != "" {
379+ data, err := os.ReadFile(file)
380+ if err != nil {
381+ *problems = append(*problems, key+"_FILE: cannot read secret file")
382+ } else {
383+ value = strings.TrimRight(string(data), "\r\n")
384+ if value == "" {
385+ *problems = append(*problems, key+"_FILE: secret file is empty")
386+ }
387+ }
388+ }
389+ values[key] = value
390+ }
391+ return func(key string) string {
392+ if value, ok := values[key]; ok {
393+ return value
394+ }
395+ return get(key)
396+ }
397+}
398+
399+func loadStorage(get func(string) string, c *Config, problems *[]string) {
400+ bad := func(key, message string) { *problems = append(*problems, key+": "+message) }
401+ c.S3Bucket = get(EnvS3Bucket)
402+ c.S3Endpoint = get(EnvS3Endpoint)
403+ c.S3Region = get(EnvS3Region)
404+ c.S3AccessKeyID = get(EnvS3AccessKeyID)
405+ c.S3SecretAccessKey = get(EnvS3SecretAccessKey)
406+ c.S3Prefix = strings.Trim(get(EnvS3Prefix), "/")
407+ if c.S3Prefix != "" {
408+ valid := len(c.S3Prefix) <= 512
409+ for _, part := range strings.Split(c.S3Prefix, "/") {
410+ if part == "" || part == "." || part == ".." {
411+ valid = false
412+ }
413+ }
414+ for _, r := range c.S3Prefix {
415+ if r < 0x21 || r > 0x7e || r == '\\' {
416+ valid = false
417+ }
418+ }
419+ if !valid {
420+ bad(EnvS3Prefix, "must be an ASCII key prefix without empty, dot or dot-dot segments")
421+ }
422+ c.S3Prefix += "/"
423+ }
424+ if c.S3Endpoint != "" {
425+ if endpoint, err := parseURL(c.S3Endpoint, false); err != nil {
426+ bad(EnvS3Endpoint, "must be an http(s) URL without credentials, path, query or fragment")
427+ } else {
428+ c.S3Endpoint = endpoint
429+ }
430+ }
431+ switch get(EnvS3ForcePathStyle) {
432+ case "", "0", "false":
433+ case "1", "true":
434+ c.S3ForcePathStyle = true
435+ default:
436+ bad(EnvS3ForcePathStyle, "must be 0, 1, false or true")
437+ }
438+ if c.S3Bucket != "" {
439+ for _, v := range []struct{ key, value string }{{EnvS3Region, c.S3Region}, {EnvS3AccessKeyID, c.S3AccessKeyID}, {EnvS3SecretAccessKey, c.S3SecretAccessKey}} {
440+ if strings.TrimSpace(v.value) == "" {
441+ bad(v.key, "required when ERMITE_S3_BUCKET is set")
442+ }
443+ }
444+ }
445+ for _, v := range []struct {
446+ key string
447+ dst *int64
448+ }{{EnvReleaseAssetMax, &c.ReleaseAssetMax}, {EnvReleaseQuota, &c.ReleaseQuota}} {
449+ if raw := strings.TrimSpace(get(v.key)); raw != "" {
450+ n, err := parseSize(raw)
451+ if err != nil {
452+ bad(v.key, err.Error())
453+ } else {
454+ *v.dst = n
455+ }
456+ }
457+ }
458+ if raw := get(EnvReleaseAssetsMax); raw != "" {
459+ n, err := strconv.Atoi(raw)
460+ if err != nil || n < 1 {
461+ bad(EnvReleaseAssetsMax, "must be a positive integer")
462+ } else {
463+ c.ReleaseAssetsMax = n
464+ }
465+ }
466+}
467+
351 // sizeUnits are the suffixes of parseSize, longest first.468 // sizeUnits are the suffixes of parseSize, longest first.
352 var sizeUnits = []struct {469 var sizeUnits = []struct {
353 suffix string470 suffix string
A internal/config/storage_test.go +111 -0
@@ -0,0 +1,111 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+package config_test
3+
4+import (
5+ "crypto/sha256"
6+ "encoding/base64"
7+ "os"
8+ "path/filepath"
9+ "strings"
10+ "testing"
11+
12+ "bnema.dev/ermite/internal/config"
13+ "github.com/stretchr/testify/assert"
14+ "github.com/stretchr/testify/require"
15+)
16+
17+func s3Env() map[string]string {
18+ return map[string]string{
19+ config.EnvS3Bucket: "bucket", config.EnvS3Region: "us-east-1",
20+ config.EnvS3AccessKeyID: "private-id", config.EnvS3SecretAccessKey: "private-secret",
21+ }
22+}
23+func TestStorageConfig(t *testing.T) {
24+ c, err := config.Load(lookup(valid(nil)))
25+ require.NoError(t, err)
26+ assert.Empty(t, c.S3Bucket)
27+ env := s3Env()
28+ env[config.EnvS3Endpoint] = "http://localhost:9000/"
29+ env[config.EnvS3Prefix] = "/forge/blobs/"
30+ env[config.EnvS3ForcePathStyle] = "1"
31+ env[config.EnvReleaseAssetMax] = "512MiB"
32+ env[config.EnvReleaseQuota] = "10GiB"
33+ env[config.EnvReleaseAssetsMax] = "7"
34+ c, err = config.Load(lookup(valid(env)))
35+ require.NoError(t, err)
36+ assert.Equal(t, "forge/blobs/", c.S3Prefix)
37+ assert.Equal(t, "http://localhost:9000", c.S3Endpoint)
38+ assert.True(t, c.S3ForcePathStyle)
39+ assert.EqualValues(t, 512<<20, c.ReleaseAssetMax)
40+ assert.EqualValues(t, 10<<30, c.ReleaseQuota)
41+ assert.Equal(t, 7, c.ReleaseAssetsMax)
42+ for _, key := range []string{config.EnvS3Region, config.EnvS3AccessKeyID, config.EnvS3SecretAccessKey} {
43+ t.Run(key, func(t *testing.T) {
44+ env := s3Env()
45+ env[key] = ""
46+ _, err := config.Load(lookup(valid(env)))
47+ require.Error(t, err)
48+ assert.Contains(t, err.Error(), key)
49+ assert.NotContains(t, err.Error(), "private-")
50+ })
51+ }
52+ for key, value := range map[string]string{config.EnvS3Endpoint: "https://private-id:private-secret@example.org", config.EnvS3ForcePathStyle: "yes", config.EnvS3Prefix: "x/../y", config.EnvReleaseAssetMax: "0", config.EnvReleaseQuota: "-1", config.EnvReleaseAssetsMax: "0"} {
53+ t.Run(key+" invalid", func(t *testing.T) {
54+ env := s3Env()
55+ env[key] = value
56+ _, err := config.Load(lookup(valid(env)))
57+ require.Error(t, err)
58+ assert.Contains(t, err.Error(), key)
59+ assert.NotContains(t, err.Error(), "private-")
60+ })
61+ }
62+ for _, value := range []string{"0", "1", "false", "true"} {
63+ env := s3Env()
64+ env[config.EnvS3ForcePathStyle] = value
65+ _, err := config.Load(lookup(valid(env)))
66+ require.NoError(t, err)
67+ }
68+}
69+func TestSecretFiles(t *testing.T) {
70+ for _, key := range []string{config.EnvAdminToken, config.EnvSecretsKey, config.EnvS3AccessKeyID, config.EnvS3SecretAccessKey} {
71+ t.Run(key, func(t *testing.T) {
72+ secret := strings.Repeat("s", 32)
73+ if key == config.EnvSecretsKey {
74+ secret = base64.StdEncoding.EncodeToString([]byte(secret))
75+ }
76+ path := filepath.Join(t.TempDir(), "secret")
77+ require.NoError(t, os.WriteFile(path, []byte(secret+"\r\n"), 0600))
78+ env := valid(s3Env())
79+ env[key] = ""
80+ env[key+"_FILE"] = path
81+ c, err := config.Load(lookup(env))
82+ require.NoError(t, err)
83+ switch key {
84+ case config.EnvAdminToken:
85+ assert.Equal(t, sha256.Sum256([]byte(secret)), c.AdminTokenHash)
86+ case config.EnvSecretsKey:
87+ assert.Len(t, c.SecretsKey, 32)
88+ case config.EnvS3AccessKeyID:
89+ assert.Equal(t, secret, c.S3AccessKeyID)
90+ case config.EnvS3SecretAccessKey:
91+ assert.Equal(t, secret, c.S3SecretAccessKey)
92+ }
93+ env[key] = secret
94+ _, err = config.Load(lookup(env))
95+ require.Error(t, err)
96+ assert.Contains(t, err.Error(), "both")
97+ assert.NotContains(t, err.Error(), secret)
98+ env[key] = ""
99+ env[key+"_FILE"] = path + "missing"
100+ _, err = config.Load(lookup(env))
101+ require.Error(t, err)
102+ assert.Contains(t, err.Error(), key+"_FILE")
103+ assert.NotContains(t, err.Error(), path)
104+ require.NoError(t, os.WriteFile(path, []byte("\n"), 0600))
105+ env[key+"_FILE"] = path
106+ _, err = config.Load(lookup(env))
107+ require.Error(t, err)
108+ assert.Contains(t, err.Error(), "empty")
109+ })
110+ }
111+}
M internal/domain/ci_artifact.go +21 -0
@@ -28,6 +28,8 @@ const (
28 CIArtifactMaxPerRun = 100028 CIArtifactMaxPerRun = 1000
29 // CIArtifactChunk is the largest piece of data of one message, both ways.29 // CIArtifactChunk is the largest piece of data of one message, both ways.
30 CIArtifactChunk = 1 << 2030 CIArtifactChunk = 1 << 20
31+ // CIArtifactMaxBlockSize bounds the in-memory staging of a toolkit block.
32+ CIArtifactMaxBlockSize = 8 << 20
31 // CIArtifactMinRetention is the shortest retention of an artifact.33 // CIArtifactMinRetention is the shortest retention of an artifact.
32 CIArtifactMinRetention = 24 * time.Hour34 CIArtifactMinRetention = 24 * time.Hour
33 // CIArtifactPendingMax is how long an upload may stay unfinished before the35 // CIArtifactPendingMax is how long an upload may stay unfinished before the
@@ -42,6 +44,16 @@ const (
42 CIArtifactZip = "application/zip"44 CIArtifactZip = "application/zip"
43 )45 )
44 46
47+// CIArtifactStore identifies the storage of an artifact's contents.
48+type CIArtifactStore string
49+
50+const (
51+ // CIArtifactStoreLegacy is the pre-blob-store layout under ci/artifacts.
52+ CIArtifactStoreLegacy CIArtifactStore = ""
53+ // CIArtifactStoreBlob stores the contents through ports.BlobStore.
54+ CIArtifactStoreBlob CIArtifactStore = "blob"
55+)
56+
45 // CIArtifactState is where an artifact is in its upload.57 // CIArtifactState is where an artifact is in its upload.
46 type CIArtifactState string58 type CIArtifactState string
47 59
@@ -74,6 +86,7 @@ type CIArtifact struct {
74 // Digest is "sha256:<hex>" of the content, known from the commit.86 // Digest is "sha256:<hex>" of the content, known from the commit.
75 Digest string87 Digest string
76 State CIArtifactState88 State CIArtifactState
89+ Store CIArtifactStore
77 CreatedAt time.Time90 CreatedAt time.Time
78 ExpiresAt time.Time91 ExpiresAt time.Time
79 // Charged is what the artifact counts against the quota of its92 // Charged is what the artifact counts against the quota of its
@@ -85,6 +98,14 @@ type CIArtifact struct {
85 Version uint6498 Version uint64
86 }99 }
87 100
101+// BlobKey names the contents by immutable run and artifact ids only.
102+func (a CIArtifact) BlobKey() string {
103+ return fmt.Sprintf("ci/%020d/%020d", a.RunID, a.ID)
104+}
105+
106+// BlockKey names a staged block; file is generated by the use case, not a user.
107+func (a CIArtifact) BlockKey(file string) string { return a.BlobKey() + ".blocks/" + file }
108+
88 // Final reports whether the artifact is finalized.109 // Final reports whether the artifact is finalized.
89 func (a CIArtifact) Final() bool { return a.State == CIArtifactFinal }110 func (a CIArtifact) Final() bool { return a.State == CIArtifactFinal }
90 111
M internal/domain/ci_artifact_test.go +7 -0
@@ -13,6 +13,13 @@ import (
13 "bnema.dev/ermite/internal/domain"13 "bnema.dev/ermite/internal/domain"
14 )14 )
15 15
16+func TestCIArtifactBlobKeys(t *testing.T) {
17+ a := domain.CIArtifact{RunID: 7, ID: 42, Name: "not-used-in-keys"}
18+ assert.Equal(t, "ci/00000000000000000007/00000000000000000042", a.BlobKey())
19+ assert.Equal(t, a.BlobKey()+".blocks/abcd.0123", a.BlockKey("abcd.0123"))
20+ assert.Equal(t, domain.CIArtifactStoreLegacy, a.Store)
21+}
22+
16 func TestParseArtifactName(t *testing.T) {23 func TestParseArtifactName(t *testing.T) {
17 for _, ok := range []string{"dist", "my artifact (linux) v1.2", "é", strings.Repeat("é", 256), "..", "a.b-c_d"} {24 for _, ok := range []string{"dist", "my artifact (linux) v1.2", "é", strings.Repeat("é", 256), "..", "a.b-c_d"} {
18 assert.NoError(t, domain.ParseArtifactName(ok), "%q", ok)25 assert.NoError(t, domain.ParseArtifactName(ok), "%q", ok)
M internal/domain/event.go +44 -0
@@ -366,3 +366,47 @@ type MirrorRecovered struct {
366 }366 }
367 367
368 func (MirrorRecovered) EventKind() string { return "mirror.recovered" }368 func (MirrorRecovered) EventKind() string { return "mirror.recovered" }
369+
370+// ReleasePublished records a release lifecycle change.
371+type ReleasePublished struct {
372+ Release Release
373+ At time.Time
374+ Visibility Visibility
375+ CreatedAt time.Time
376+}
377+
378+// EventKind implements Event.
379+func (ReleasePublished) EventKind() string { return "release.published" }
380+
381+// ReleaseUpdated records a release lifecycle change.
382+type ReleaseUpdated struct {
383+ Release Release
384+ At time.Time
385+ Visibility Visibility
386+ CreatedAt time.Time
387+}
388+
389+// EventKind implements Event.
390+func (ReleaseUpdated) EventKind() string { return "release.updated" }
391+
392+// ReleaseDeleted records a release lifecycle change.
393+type ReleaseDeleted struct {
394+ Release Release
395+ At time.Time
396+ Visibility Visibility
397+ CreatedAt time.Time
398+}
399+
400+// EventKind implements Event.
401+func (ReleaseDeleted) EventKind() string { return "release.deleted" }
402+
403+// ReleaseUnpublished records a release lifecycle change.
404+type ReleaseUnpublished struct {
405+ Release Release
406+ At time.Time
407+ Visibility Visibility
408+ CreatedAt time.Time
409+}
410+
411+// EventKind implements Event.
412+func (ReleaseUnpublished) EventKind() string { return "release.unpublished" }
A internal/domain/release.go +165 -0
@@ -0,0 +1,165 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package domain
4+
5+import (
6+ "fmt"
7+ "mime"
8+ "strings"
9+ "time"
10+ "unicode"
11+ "unicode/utf8"
12+
13+ "golang.org/x/text/unicode/norm"
14+)
15+
16+// Release binds release notes and assets to a repository tag.
17+type Release struct {
18+ ID int64
19+ Repo RepoName
20+ Tag, Title, Notes string
21+ Draft, Prerelease, MakeLatest bool
22+ // TargetCommitish is the commit at publication, empty for an unresolved draft.
23+ TargetCommitish string
24+ Author string
25+ CreatedAt, PublishedAt, UpdatedAt time.Time
26+ Version uint64
27+}
28+
29+// ReleaseAssetState distinguishes reserved uploads from visible assets.
30+type ReleaseAssetState string
31+
32+const (
33+ ReleaseAssetUploading ReleaseAssetState = "uploading"
34+ ReleaseAssetUploaded ReleaseAssetState = "uploaded"
35+)
36+
37+// ReleaseAsset is metadata only; its contents live in the blob store.
38+type ReleaseAsset struct {
39+ ID, ReleaseID int64
40+ Repo RepoName
41+ Name, Label, ContentType string
42+ Size int64
43+ Digest string
44+ State ReleaseAssetState
45+ Uploader string
46+ // Charged counts bytes reserved against repository quota.
47+ Charged int64
48+ // MultipartID identifies an unfinished upload for cleanup.
49+ MultipartID string
50+ CreatedAt, UpdatedAt time.Time
51+ Version uint64
52+}
53+
54+// BlobKey contains only a globally allocated identifier.
55+func (a ReleaseAsset) BlobKey() string { return fmt.Sprintf("release/%020d", a.ID) }
56+
57+const (
58+ ReleaseMaxTitleLen = 256
59+ ReleaseMaxNotesLen = 125_000
60+ ReleaseAssetMaxNameLen = 255
61+ ReleaseAssetMaxLabelLen = 255
62+ ReleaseAssetPendingMax = 24 * time.Hour
63+ DefaultReleaseAssetMax = 2 << 30
64+ DefaultReleaseAssetsMax = 1000
65+ DefaultReleaseQuota = 20 << 30
66+)
67+
68+// ParseReleaseTag validates a short tag using Git reference-name rules.
69+func ParseReleaseTag(s string) (string, error) {
70+ if s == "" {
71+ return "", fmt.Errorf("%w: empty tag name", ErrInvalid)
72+ }
73+ if strings.HasPrefix(s, "refs/") {
74+ return "", fmt.Errorf("%w: %q: give the short tag name", ErrInvalid, s)
75+ }
76+ if reason := refNameProblem(s); reason != "" {
77+ return "", fmt.Errorf("%w: %q: %s", ErrInvalid, s, reason)
78+ }
79+ return s, nil
80+}
81+
82+func cleanReleaseText(s string, limit int, multiline bool) (string, error) {
83+ if !utf8.ValidString(s) {
84+ return "", fmt.Errorf("%w: invalid UTF-8", ErrInvalid)
85+ }
86+ s = norm.NFC.String(s)
87+ s = strings.Map(func(r rune) rune {
88+ if multiline && (r == '\n' || r == '\t' || r == '\r') {
89+ return r
90+ }
91+ if unicode.IsControl(r) || (!multiline && isBidiControl(r)) {
92+ return -1
93+ }
94+ return r
95+ }, s)
96+ if utf8.RuneCountInString(s) > limit {
97+ return "", fmt.Errorf("%w: text exceeds %d characters", ErrInvalid, limit)
98+ }
99+ return s, nil
100+}
101+
102+// ParseReleaseTitle normalizes and cleans an optional title.
103+func ParseReleaseTitle(s string) (string, error) {
104+ return cleanReleaseText(s, ReleaseMaxTitleLen, false)
105+}
106+
107+// ParseReleaseNotes validates and cleans multiline notes.
108+func ParseReleaseNotes(s string) (string, error) {
109+ return cleanReleaseText(s, ReleaseMaxNotesLen, true)
110+}
111+
112+// ParseReleaseAssetLabel normalizes and cleans an optional label.
113+func ParseReleaseAssetLabel(s string) (string, error) {
114+ return cleanReleaseText(s, ReleaseAssetMaxLabelLen, false)
115+}
116+
117+// ParseReleaseAssetName validates a single filename without rewriting it.
118+func ParseReleaseAssetName(s string) (string, error) {
119+ if !utf8.ValidString(s) || s == "" || s == "." || s == ".." || utf8.RuneCountInString(s) > ReleaseAssetMaxNameLen || strings.ContainsAny(s, "/\\") {
120+ return "", fmt.Errorf("%w: invalid asset name", ErrInvalid)
121+ }
122+ for _, r := range s {
123+ if unicode.IsControl(r) {
124+ return "", fmt.Errorf("%w: control character in asset name", ErrInvalid)
125+ }
126+ }
127+ return s, nil
128+}
129+
130+// ParseReleaseContentType validates a printable ASCII media type.
131+func ParseReleaseContentType(s string) (string, error) {
132+ if s == "" {
133+ return "application/octet-stream", nil
134+ }
135+ if len(s) > 255 {
136+ return "", fmt.Errorf("%w: media type too long", ErrInvalid)
137+ }
138+ for _, r := range s {
139+ if r < 32 || r > 126 {
140+ return "", fmt.Errorf("%w: invalid media type", ErrInvalid)
141+ }
142+ }
143+ if _, _, err := mime.ParseMediaType(s); err != nil {
144+ return "", fmt.Errorf("%w: media type: %v", ErrInvalid, err)
145+ }
146+ return s, nil
147+}
148+
149+// Published reports whether the release is public to repository readers.
150+func (r Release) Published() bool { return !r.Draft }
151+
152+// LatestRelease selects a pin first, then the newest stable published release.
153+func LatestRelease(rs []Release) (Release, bool) {
154+ var best Release
155+ ok := false
156+ for _, r := range rs {
157+ if r.Draft || r.Prerelease {
158+ continue
159+ }
160+ if !ok || (r.MakeLatest && !best.MakeLatest) || (r.MakeLatest == best.MakeLatest && (r.CreatedAt.After(best.CreatedAt) || (r.CreatedAt.Equal(best.CreatedAt) && r.ID > best.ID))) {
161+ best, ok = r, true
162+ }
163+ }
164+ return best, ok
165+}
A internal/domain/release_test.go +59 -0
@@ -0,0 +1,59 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+package domain_test
3+
4+import (
5+ "bnema.dev/ermite/internal/domain"
6+ "github.com/stretchr/testify/require"
7+ "strings"
8+ "testing"
9+ "time"
10+)
11+
12+func TestReleaseParsers(t *testing.T) {
13+ for _, tc := range []struct {
14+ parse func(string) (string, error)
15+ good, bad []string
16+ }{
17+ {domain.ParseReleaseTag, []string{"v1.0", "nightly/x"}, []string{"", "refs/tags/v1", "..", "a..b", "a b"}},
18+ {domain.ParseReleaseTitle, []string{"", strings.Repeat("é", 256)}, []string{strings.Repeat("x", 257), "\xff"}},
19+ {domain.ParseReleaseNotes, []string{"", "a\n\t\rb", strings.Repeat("x", 125000)}, []string{"\xff", strings.Repeat("x", 125001)}},
20+ {domain.ParseReleaseAssetName, []string{"a b", "é", strings.Repeat("x", 255)}, []string{"", ".", "..", "a/b", "a\\b", "a\x00", "\xff", strings.Repeat("x", 256)}},
21+ {domain.ParseReleaseAssetLabel, []string{"", strings.Repeat("x", 255)}, []string{"\xff", strings.Repeat("x", 256)}},
22+ {domain.ParseReleaseContentType, []string{"", "text/plain; charset=utf-8"}, []string{"bad/type/type", "text/plain\n", strings.Repeat("x", 256), "é"}},
23+ } {
24+ for _, s := range tc.good {
25+ _, err := tc.parse(s)
26+ require.NoError(t, err, s)
27+ }
28+ for _, s := range tc.bad {
29+ _, err := tc.parse(s)
30+ require.ErrorIs(t, err, domain.ErrInvalid, s)
31+ }
32+ }
33+ title, err := domain.ParseReleaseTitle("e\u0301\x00\u202e")
34+ require.NoError(t, err)
35+ require.Equal(t, "é", title)
36+ notes, err := domain.ParseReleaseNotes("a\x00\n\t\r")
37+ require.NoError(t, err)
38+ require.Equal(t, "a\n\t\r", notes)
39+ require.Equal(t, "release/00000000000000000001", (domain.ReleaseAsset{ID: 1}).BlobKey())
40+ scope, err := domain.ParseScope("release:write")
41+ require.NoError(t, err)
42+ require.Equal(t, domain.ScopeReleaseWrite, scope)
43+}
44+func TestLatestRelease(t *testing.T) {
45+ now := time.Now()
46+ rs := []domain.Release{{ID: 1, CreatedAt: now, MakeLatest: true}, {ID: 2, CreatedAt: now.Add(time.Hour)}, {ID: 3, Draft: true, MakeLatest: true}, {ID: 4, Prerelease: true, MakeLatest: true}}
47+ r, ok := domain.LatestRelease(rs)
48+ require.True(t, ok)
49+ require.EqualValues(t, 1, r.ID)
50+ rs[0].MakeLatest = false
51+ r, ok = domain.LatestRelease(rs)
52+ require.True(t, ok)
53+ require.EqualValues(t, 2, r.ID)
54+ rs[0].CreatedAt = rs[1].CreatedAt
55+ r, _ = domain.LatestRelease(rs)
56+ require.EqualValues(t, 2, r.ID)
57+ _, ok = domain.LatestRelease(rs[2:])
58+ require.False(t, ok)
59+}
M internal/domain/token.go +4 -2
@@ -45,16 +45,18 @@ const (
45 // ScopeIssueWrite creates, comments on, closes and reopens issues. Moderation45 // ScopeIssueWrite creates, comments on, closes and reopens issues. Moderation
46 // is not part of it: it stays with the owner.46 // is not part of it: it stays with the owner.
47 ScopeIssueWrite Scope = "issue:write"47 ScopeIssueWrite Scope = "issue:write"
48+ // ScopeReleaseWrite creates, edits and deletes releases and their assets.
49+ ScopeReleaseWrite Scope = "release:write"
48 )50 )
49 51
50-// AllScopes returns the twelve scopes in a stable order.52+// AllScopes returns the thirteen scopes in a stable order.
51 func AllScopes() []Scope {53 func AllScopes() []Scope {
52 return []Scope{54 return []Scope{
53 ScopeRepoRead, ScopeRepoWrite, ScopeRepoAdmin,55 ScopeRepoRead, ScopeRepoWrite, ScopeRepoAdmin,
54 ScopeMRWrite, ScopeMRMerge, ScopeMRForceMerge, ScopeMRReview,56 ScopeMRWrite, ScopeMRMerge, ScopeMRForceMerge, ScopeMRReview,
55 ScopeCIWrite, ScopeSecretsWrite, ScopeRunnerRegister,57 ScopeCIWrite, ScopeSecretsWrite, ScopeRunnerRegister,
56 ScopeTokenAdmin,58 ScopeTokenAdmin,
57- ScopeIssueWrite,59+ ScopeIssueWrite, ScopeReleaseWrite,
58 }60 }
59 }61 }
60 62
M internal/domain/token_test.go +5 -1
@@ -16,7 +16,11 @@ import (
16 func TestScopes(t *testing.T) {16 func TestScopes(t *testing.T) {
17 t.Parallel()17 t.Parallel()
18 all := domain.AllScopes()18 all := domain.AllScopes()
19- assert.Len(t, all, 12)19+ assert.Len(t, all, 13)
20+ assert.Equal(t, domain.ScopeMRForceMerge, all[5])
21+ assert.Equal(t, domain.ScopeReleaseWrite, all[len(all)-1])
22+ assert.Contains(t, all, domain.ScopeReleaseWrite)
23+ assert.False(t, domain.TokenPrincipal(domain.Token{Scopes: all[:len(all)-1]}).HasOwnerRights(), "missing only release:write")
20 assert.Contains(t, all, domain.ScopeIssueWrite)24 assert.Contains(t, all, domain.ScopeIssueWrite)
21 got, err := domain.ParseScope("issue:write")25 got, err := domain.ParseScope("issue:write")
22 require.NoError(t, err)26 require.NoError(t, err)
A internal/httprange/httprange.go +77 -0
@@ -0,0 +1,77 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+// Package httprange selects the byte range of a download from the Range and
4+// If-Range headers of a request. The HTTP adapters that serve release assets
5+// share it, so they answer the same way.
6+package httprange
7+
8+import (
9+ "errors"
10+ "net/http"
11+ "strconv"
12+ "strings"
13+)
14+
15+// ErrUnsatisfiable means the request asked for a range the content cannot give.
16+// The caller answers 416 with "Content-Range: bytes */<size>".
17+var ErrUnsatisfiable = errors.New("httprange: unsatisfiable byte range")
18+
19+// Select returns the part of a size-byte body to send.
20+//
21+// Without a usable Range it returns the whole body (0, size, false, nil):
22+// - the method is not GET (HEAD answers with the headers of the full body);
23+// - there is no Range header, its unit is not "bytes", or it lists several
24+// ranges (ADR 0034: multipart ranges are not served);
25+// - If-Range is present and is not exactly etag. The comparison is strong:
26+// a weak validator or an HTTP date never matches.
27+//
28+// A single "bytes=a-b", "bytes=a-" or "bytes=-n" gives (offset, length, true,
29+// nil); an end past the content is cut to it. A malformed range, a start at or
30+// past size, a suffix of zero, or any range of an empty body returns
31+// ErrUnsatisfiable.
32+func Select(r *http.Request, size int64, etag string) (offset, length int64, partial bool, err error) {
33+ whole := func() (int64, int64, bool, error) { return 0, size, false, nil }
34+ if r.Method != http.MethodGet {
35+ return whole()
36+ }
37+ value := strings.Join(r.Header.Values("Range"), ",")
38+ if value == "" || strings.Contains(value, ",") || !strings.HasPrefix(value, "bytes=") {
39+ return whole()
40+ }
41+ if ifRange := r.Header.Get("If-Range"); ifRange != "" && (etag == "" || ifRange != etag || strings.HasPrefix(ifRange, "W/")) {
42+ return whole()
43+ }
44+ a, b, ok := strings.Cut(strings.TrimSpace(strings.TrimPrefix(value, "bytes=")), "-")
45+ if !ok || size <= 0 {
46+ return 0, 0, false, ErrUnsatisfiable
47+ }
48+ if a == "" {
49+ n, e := strconv.ParseInt(b, 10, 64)
50+ if e != nil || n <= 0 {
51+ return 0, 0, false, ErrUnsatisfiable
52+ }
53+ n = min(n, size)
54+ return size - n, n, true, nil
55+ }
56+ start, e := strconv.ParseInt(a, 10, 64)
57+ if e != nil || start < 0 || start >= size {
58+ return 0, 0, false, ErrUnsatisfiable
59+ }
60+ end := size - 1
61+ if b != "" {
62+ end, e = strconv.ParseInt(b, 10, 64)
63+ if e != nil || end < start {
64+ return 0, 0, false, ErrUnsatisfiable
65+ }
66+ end = min(end, size-1)
67+ }
68+ return start, end - start + 1, true, nil
69+}
70+
71+// ContentRange is the Content-Range value of a partial answer.
72+func ContentRange(offset, length, size int64) string {
73+ return "bytes " + strconv.FormatInt(offset, 10) + "-" + strconv.FormatInt(offset+length-1, 10) + "/" + strconv.FormatInt(size, 10)
74+}
75+
76+// Unsatisfied is the Content-Range value of a 416 answer.
77+func Unsatisfied(size int64) string { return "bytes */" + strconv.FormatInt(size, 10) }
A internal/httprange/httprange_test.go +72 -0
@@ -0,0 +1,72 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package httprange_test
4+
5+import (
6+ "net/http"
7+ "net/http/httptest"
8+ "testing"
9+
10+ "github.com/stretchr/testify/require"
11+
12+ "bnema.dev/ermite/internal/httprange"
13+)
14+
15+func TestSelect(t *testing.T) {
16+ const etag = `"sha256-abc"`
17+ for _, tc := range []struct {
18+ name string
19+ method, rng, ifRange string
20+ size int64
21+ offset, length int64
22+ partial bool
23+ err error
24+ }{
25+ {name: "no range", size: 10, length: 10},
26+ {name: "closed", rng: "bytes=2-4", size: 10, offset: 2, length: 3, partial: true},
27+ {name: "open", rng: "bytes=7-", size: 10, offset: 7, length: 3, partial: true},
28+ {name: "suffix", rng: "bytes=-4", size: 10, offset: 6, length: 4, partial: true},
29+ {name: "suffix longer than body", rng: "bytes=-40", size: 10, offset: 0, length: 10, partial: true},
30+ {name: "end cut to size", rng: "bytes=8-99", size: 10, offset: 8, length: 2, partial: true},
31+ {name: "spaces", rng: "bytes= 1-2 ", size: 10, offset: 1, length: 2, partial: true},
32+ {name: "start past end", rng: "bytes=10-", size: 10, err: httprange.ErrUnsatisfiable},
33+ {name: "end before start", rng: "bytes=5-4", size: 10, err: httprange.ErrUnsatisfiable},
34+ {name: "zero suffix", rng: "bytes=-0", size: 10, err: httprange.ErrUnsatisfiable},
35+ {name: "garbage", rng: "bytes=a-b", size: 10, err: httprange.ErrUnsatisfiable},
36+ {name: "no dash", rng: "bytes=5", size: 10, err: httprange.ErrUnsatisfiable},
37+ {name: "empty body", rng: "bytes=0-", size: 0, err: httprange.ErrUnsatisfiable},
38+ {name: "multiple ignored", rng: "bytes=0-1,3-4", size: 10, length: 10},
39+ {name: "other unit ignored", rng: "items=0-1", size: 10, length: 10},
40+ {name: "head ignores range", method: http.MethodHead, rng: "bytes=2-4", size: 10, length: 10},
41+ {name: "head ignores bad range", method: http.MethodHead, rng: "bytes=99-", size: 10, length: 10},
42+ {name: "matching if-range", rng: "bytes=2-4", ifRange: etag, size: 10, offset: 2, length: 3, partial: true},
43+ {name: "stale if-range", rng: "bytes=2-4", ifRange: `"other"`, size: 10, length: 10},
44+ {name: "date if-range", rng: "bytes=2-4", ifRange: "Wed, 21 Oct 2015 07:28:00 GMT", size: 10, length: 10},
45+ {name: "weak if-range", rng: "bytes=2-4", ifRange: "W/" + etag, size: 10, length: 10},
46+ {name: "stale if-range with bad range", rng: "bytes=99-", ifRange: `"other"`, size: 10, length: 10},
47+ } {
48+ t.Run(tc.name, func(t *testing.T) {
49+ method := tc.method
50+ if method == "" {
51+ method = http.MethodGet
52+ }
53+ r := httptest.NewRequest(method, "/x", nil)
54+ if tc.rng != "" {
55+ r.Header.Set("Range", tc.rng)
56+ }
57+ if tc.ifRange != "" {
58+ r.Header.Set("If-Range", tc.ifRange)
59+ }
60+ offset, length, partial, err := httprange.Select(r, tc.size, etag)
61+ require.ErrorIs(t, err, tc.err)
62+ require.Equal(t, tc.offset, offset)
63+ require.Equal(t, tc.length, length)
64+ require.Equal(t, tc.partial, partial)
65+ })
66+ }
67+}
68+
69+func TestContentRange(t *testing.T) {
70+ require.Equal(t, "bytes 2-4/10", httprange.ContentRange(2, 3, 10))
71+ require.Equal(t, "bytes */10", httprange.Unsatisfied(10))
72+}
A internal/ports/blob.go +72 -0
@@ -0,0 +1,72 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package ports
4+
5+import (
6+ "context"
7+ "io"
8+ "strings"
9+ "time"
10+)
11+
12+// BlobStore keeps the contents of release assets and CI artifacts, never
13+// their metadata. Keys are built by the use cases from ids only ("a/b/c",
14+// ASCII, no empty segment, no ".", no ".." segment, no leading "/", ≤ 512
15+// bytes); an adapter rejects any other key with domain.ErrInvalid.
16+// *blobdisk.Store and *blobs3.Store implement it.
17+type BlobStore interface {
18+ Put(ctx context.Context, key string, r io.Reader, size int64) (BlobInfo, error) // size ≥ 0 required; replaces; a failure leaves no object
19+ Open(ctx context.Context, key string, offset, length int64) (io.ReadCloser, BlobInfo, error) // length < 0: to the end; offset ≥ size → domain.ErrOutOfRange
20+ Stat(ctx context.Context, key string) (BlobInfo, error)
21+ Delete(ctx context.Context, key string) error // absent: nil
22+ List(ctx context.Context, prefix string, fn func(BlobInfo) error) error // lexicographic; fn error stops and is returned
23+ NewMultipart(ctx context.Context, key string) (MultipartUpload, error)
24+ ListMultipart(ctx context.Context, prefix string, fn func(MultipartInfo) error) error
25+ AbortMultipart(ctx context.Context, key, id string) error // for GC of uploads listed by ListMultipart
26+ Check(ctx context.Context) error // startup probe; never creates anything
27+}
28+type BlobInfo struct {
29+ Key string
30+ Size int64
31+ Digest string
32+ ModTime time.Time
33+} // Digest "sha256:<hex>" from Put, "" from Stat/List/Open on S3
34+type MultipartInfo struct {
35+ Key, ID string
36+ Started time.Time
37+}
38+
39+// MultipartUpload uploads one object in parts of BlobMinPartSize or more
40+// (the last may be smaller), at most BlobMaxParts, numbered from 1 in the
41+// order given to Complete. Abort discards everything; Complete or Abort
42+// must be called exactly once.
43+type MultipartUpload interface {
44+ ID() string
45+ UploadPart(ctx context.Context, n int, r io.Reader, size int64) error
46+ Complete(ctx context.Context) (BlobInfo, error) // parts in number order; Digest "" (the caller hashes)
47+ Abort(ctx context.Context) error
48+}
49+
50+const (
51+ BlobMinPartSize = 5 << 20
52+ BlobPartSize = 8 << 20
53+ BlobMaxParts = 10_000
54+)
55+
56+// ValidBlobKey reports whether key follows the rule above.
57+func ValidBlobKey(key string) bool {
58+ if len(key) == 0 || len(key) > 512 {
59+ return false
60+ }
61+ for _, c := range []byte(key) {
62+ if c > 127 {
63+ return false
64+ }
65+ }
66+ for _, segment := range strings.Split(key, "/") {
67+ if segment == "" || segment == "." || segment == ".." {
68+ return false
69+ }
70+ }
71+ return true
72+}
A internal/ports/blob_test.go +19 -0
@@ -0,0 +1,19 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+package ports_test
3+
4+import (
5+ "strings"
6+ "testing"
7+
8+ "bnema.dev/ermite/internal/ports"
9+ "github.com/stretchr/testify/require"
10+)
11+
12+func TestValidBlobKey(t *testing.T) {
13+ for _, v := range []struct {
14+ key string
15+ ok bool
16+ }{{"ci/00000000000000000001/x", true}, {"a/.tmp-object", true}, {"a.parts/b", true}, {"", false}, {"/a", false}, {"a//b", false}, {"a/../b", false}, {"a/./b", false}, {"é", false}, {strings.Repeat("a", 513), false}} {
17+ t.Run(v.key, func(t *testing.T) { require.Equal(t, v.ok, ports.ValidBlobKey(v.key)) })
18+ }
19+}
M internal/ports/ci_artifact.go +3 -13
@@ -75,24 +75,14 @@ type CIArtifactRef struct {
75 ID int6475 ID int64
76 }76 }
77 77
78-// CIArtifactFiles keeps the contents of the CI artifacts and their staged78+// CIArtifactFiles reads and removes the artifacts recorded before the blob
79-// blocks, never in the metadata store. Files are named by ids only.79+// store (ADR 0034); removed once they have all expired. *ciartifacts.Store
80+// implements it. Files are named by ids only.
80 type CIArtifactFiles interface {81 type CIArtifactFiles interface {
81- // PutBlock writes src into a new block file of the artifact, synced, and
82- // returns its size. A failure of src or of the write leaves no file.
83- PutBlock(ctx context.Context, ref CIArtifactRef, file string, src io.Reader) (int64, error)
84- // RemoveBlock deletes one block file; a missing one is not an error.
85- RemoveBlock(ctx context.Context, ref CIArtifactRef, file string) error
86- // Assemble concatenates the block files in order into the content of the
87- // artifact, which replaces any earlier one whole, and returns its size and
88- // digest ("sha256:<hex>"). A missing block file is domain.ErrNotFound.
89- Assemble(ctx context.Context, ref CIArtifactRef, files []string) (size int64, digest string, err error)
90 // Open returns the content from offset, or domain.ErrNotFound.82 // Open returns the content from offset, or domain.ErrNotFound.
91 Open(ctx context.Context, ref CIArtifactRef, offset int64) (io.ReadCloser, error)83 Open(ctx context.Context, ref CIArtifactRef, offset int64) (io.ReadCloser, error)
92 // Remove deletes the content and the blocks of the artifact.84 // Remove deletes the content and the blocks of the artifact.
93 Remove(ctx context.Context, ref CIArtifactRef) error85 Remove(ctx context.Context, ref CIArtifactRef) error
94- // RemoveBlocks deletes the blocks of the artifact.
95- RemoveBlocks(ctx context.Context, ref CIArtifactRef) error
96 // RemoveRuns deletes the files of the runs.86 // RemoveRuns deletes the files of the runs.
97 RemoveRuns(ctx context.Context, runIDs []int64) error87 RemoveRuns(ctx context.Context, runIDs []int64) error
98 // RunIDs lists the runs that have a directory, ArtifactIDs the artifacts88 // RunIDs lists the runs that have a directory, ArtifactIDs the artifacts
A internal/ports/mocks/BlobStore.go +644 -0
@@ -0,0 +1,644 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+ "io"
10+
11+ "bnema.dev/ermite/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewBlobStore creates a new instance of BlobStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewBlobStore(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *BlobStore {
21+ if helper, ok := t.(interface{ Helper() }); ok {
22+ helper.Helper()
23+ }
24+
25+ mock := &BlobStore{}
26+ mock.Mock.Test(t)
27+
28+ t.Cleanup(func() {
29+ if helper, ok := t.(interface{ Helper() }); ok {
30+ helper.Helper()
31+ }
32+ mock.AssertExpectations(t)
33+ })
34+
35+ return mock
36+}
37+
38+// BlobStore is an autogenerated mock type for the BlobStore type
39+type BlobStore struct {
40+ mock.Mock
41+}
42+
43+type BlobStore_Expecter struct {
44+ mock *mock.Mock
45+}
46+
47+func (_m *BlobStore) EXPECT() *BlobStore_Expecter {
48+ return &BlobStore_Expecter{mock: &_m.Mock}
49+}
50+
51+// AbortMultipart provides a mock function for the type BlobStore
52+func (_mock *BlobStore) AbortMultipart(ctx context.Context, key string, id string) error {
53+ ret := _mock.Called(ctx, key, id)
54+
55+ if len(ret) == 0 {
56+ panic("no return value specified for AbortMultipart")
57+ }
58+
59+ var r0 error
60+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
61+ r0 = returnFunc(ctx, key, id)
62+ } else {
63+ r0 = ret.Error(0)
64+ }
65+ return r0
66+}
67+
68+// BlobStore_AbortMultipart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AbortMultipart'
69+type BlobStore_AbortMultipart_Call struct {
70+ *mock.Call
71+}
72+
73+// AbortMultipart is a helper method to define mock.On call
74+// - ctx context.Context
75+// - key string
76+// - id string
77+func (_e *BlobStore_Expecter) AbortMultipart(ctx any, key any, id any) *BlobStore_AbortMultipart_Call {
78+ return &BlobStore_AbortMultipart_Call{Call: _e.mock.On("AbortMultipart", ctx, key, id)}
79+}
80+
81+func (_c *BlobStore_AbortMultipart_Call) Run(run func(ctx context.Context, key string, id string)) *BlobStore_AbortMultipart_Call {
82+ _c.Call.Run(func(args mock.Arguments) {
83+ var arg0 context.Context
84+ if args[0] != nil {
85+ arg0 = args[0].(context.Context)
86+ }
87+ var arg1 string
88+ if args[1] != nil {
89+ arg1 = args[1].(string)
90+ }
91+ var arg2 string
92+ if args[2] != nil {
93+ arg2 = args[2].(string)
94+ }
95+ run(
96+ arg0,
97+ arg1,
98+ arg2,
99+ )
100+ })
101+ return _c
102+}
103+
104+func (_c *BlobStore_AbortMultipart_Call) Return(err error) *BlobStore_AbortMultipart_Call {
105+ _c.Call.Return(err)
106+ return _c
107+}
108+
109+func (_c *BlobStore_AbortMultipart_Call) RunAndReturn(run func(ctx context.Context, key string, id string) error) *BlobStore_AbortMultipart_Call {
110+ _c.Call.Return(run)
111+ return _c
112+}
113+
114+// Check provides a mock function for the type BlobStore
115+func (_mock *BlobStore) Check(ctx context.Context) error {
116+ ret := _mock.Called(ctx)
117+
118+ if len(ret) == 0 {
119+ panic("no return value specified for Check")
120+ }
121+
122+ var r0 error
123+ if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {
124+ r0 = returnFunc(ctx)
125+ } else {
126+ r0 = ret.Error(0)
127+ }
128+ return r0
129+}
130+
131+// BlobStore_Check_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Check'
132+type BlobStore_Check_Call struct {
133+ *mock.Call
134+}
135+
136+// Check is a helper method to define mock.On call
137+// - ctx context.Context
138+func (_e *BlobStore_Expecter) Check(ctx any) *BlobStore_Check_Call {
139+ return &BlobStore_Check_Call{Call: _e.mock.On("Check", ctx)}
140+}
141+
142+func (_c *BlobStore_Check_Call) Run(run func(ctx context.Context)) *BlobStore_Check_Call {
143+ _c.Call.Run(func(args mock.Arguments) {
144+ var arg0 context.Context
145+ if args[0] != nil {
146+ arg0 = args[0].(context.Context)
147+ }
148+ run(
149+ arg0,
150+ )
151+ })
152+ return _c
153+}
154+
155+func (_c *BlobStore_Check_Call) Return(err error) *BlobStore_Check_Call {
156+ _c.Call.Return(err)
157+ return _c
158+}
159+
160+func (_c *BlobStore_Check_Call) RunAndReturn(run func(ctx context.Context) error) *BlobStore_Check_Call {
161+ _c.Call.Return(run)
162+ return _c
163+}
164+
165+// Delete provides a mock function for the type BlobStore
166+func (_mock *BlobStore) Delete(ctx context.Context, key string) error {
167+ ret := _mock.Called(ctx, key)
168+
169+ if len(ret) == 0 {
170+ panic("no return value specified for Delete")
171+ }
172+
173+ var r0 error
174+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
175+ r0 = returnFunc(ctx, key)
176+ } else {
177+ r0 = ret.Error(0)
178+ }
179+ return r0
180+}
181+
182+// BlobStore_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
183+type BlobStore_Delete_Call struct {
184+ *mock.Call
185+}
186+
187+// Delete is a helper method to define mock.On call
188+// - ctx context.Context
189+// - key string
190+func (_e *BlobStore_Expecter) Delete(ctx any, key any) *BlobStore_Delete_Call {
191+ return &BlobStore_Delete_Call{Call: _e.mock.On("Delete", ctx, key)}
192+}
193+
194+func (_c *BlobStore_Delete_Call) Run(run func(ctx context.Context, key string)) *BlobStore_Delete_Call {
195+ _c.Call.Run(func(args mock.Arguments) {
196+ var arg0 context.Context
197+ if args[0] != nil {
198+ arg0 = args[0].(context.Context)
199+ }
200+ var arg1 string
201+ if args[1] != nil {
202+ arg1 = args[1].(string)
203+ }
204+ run(
205+ arg0,
206+ arg1,
207+ )
208+ })
209+ return _c
210+}
211+
212+func (_c *BlobStore_Delete_Call) Return(err error) *BlobStore_Delete_Call {
213+ _c.Call.Return(err)
214+ return _c
215+}
216+
217+func (_c *BlobStore_Delete_Call) RunAndReturn(run func(ctx context.Context, key string) error) *BlobStore_Delete_Call {
218+ _c.Call.Return(run)
219+ return _c
220+}
221+
222+// List provides a mock function for the type BlobStore
223+func (_mock *BlobStore) List(ctx context.Context, prefix string, fn func(ports.BlobInfo) error) error {
224+ ret := _mock.Called(ctx, prefix, fn)
225+
226+ if len(ret) == 0 {
227+ panic("no return value specified for List")
228+ }
229+
230+ var r0 error
231+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, func(ports.BlobInfo) error) error); ok {
232+ r0 = returnFunc(ctx, prefix, fn)
233+ } else {
234+ r0 = ret.Error(0)
235+ }
236+ return r0
237+}
238+
239+// BlobStore_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
240+type BlobStore_List_Call struct {
241+ *mock.Call
242+}
243+
244+// List is a helper method to define mock.On call
245+// - ctx context.Context
246+// - prefix string
247+// - fn func(ports.BlobInfo) error
248+func (_e *BlobStore_Expecter) List(ctx any, prefix any, fn any) *BlobStore_List_Call {
249+ return &BlobStore_List_Call{Call: _e.mock.On("List", ctx, prefix, fn)}
250+}
251+
252+func (_c *BlobStore_List_Call) Run(run func(ctx context.Context, prefix string, fn func(ports.BlobInfo) error)) *BlobStore_List_Call {
253+ _c.Call.Run(func(args mock.Arguments) {
254+ var arg0 context.Context
255+ if args[0] != nil {
256+ arg0 = args[0].(context.Context)
257+ }
258+ var arg1 string
259+ if args[1] != nil {
260+ arg1 = args[1].(string)
261+ }
262+ var arg2 func(ports.BlobInfo) error
263+ if args[2] != nil {
264+ arg2 = args[2].(func(ports.BlobInfo) error)
265+ }
266+ run(
267+ arg0,
268+ arg1,
269+ arg2,
270+ )
271+ })
272+ return _c
273+}
274+
275+func (_c *BlobStore_List_Call) Return(err error) *BlobStore_List_Call {
276+ _c.Call.Return(err)
277+ return _c
278+}
279+
280+func (_c *BlobStore_List_Call) RunAndReturn(run func(ctx context.Context, prefix string, fn func(ports.BlobInfo) error) error) *BlobStore_List_Call {
281+ _c.Call.Return(run)
282+ return _c
283+}
284+
285+// ListMultipart provides a mock function for the type BlobStore
286+func (_mock *BlobStore) ListMultipart(ctx context.Context, prefix string, fn func(ports.MultipartInfo) error) error {
287+ ret := _mock.Called(ctx, prefix, fn)
288+
289+ if len(ret) == 0 {
290+ panic("no return value specified for ListMultipart")
291+ }
292+
293+ var r0 error
294+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, func(ports.MultipartInfo) error) error); ok {
295+ r0 = returnFunc(ctx, prefix, fn)
296+ } else {
297+ r0 = ret.Error(0)
298+ }
299+ return r0
300+}
301+
302+// BlobStore_ListMultipart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListMultipart'
303+type BlobStore_ListMultipart_Call struct {
304+ *mock.Call
305+}
306+
307+// ListMultipart is a helper method to define mock.On call
308+// - ctx context.Context
309+// - prefix string
310+// - fn func(ports.MultipartInfo) error
311+func (_e *BlobStore_Expecter) ListMultipart(ctx any, prefix any, fn any) *BlobStore_ListMultipart_Call {
312+ return &BlobStore_ListMultipart_Call{Call: _e.mock.On("ListMultipart", ctx, prefix, fn)}
313+}
314+
315+func (_c *BlobStore_ListMultipart_Call) Run(run func(ctx context.Context, prefix string, fn func(ports.MultipartInfo) error)) *BlobStore_ListMultipart_Call {
316+ _c.Call.Run(func(args mock.Arguments) {
317+ var arg0 context.Context
318+ if args[0] != nil {
319+ arg0 = args[0].(context.Context)
320+ }
321+ var arg1 string
322+ if args[1] != nil {
323+ arg1 = args[1].(string)
324+ }
325+ var arg2 func(ports.MultipartInfo) error
326+ if args[2] != nil {
327+ arg2 = args[2].(func(ports.MultipartInfo) error)
328+ }
329+ run(
330+ arg0,
331+ arg1,
332+ arg2,
333+ )
334+ })
335+ return _c
336+}
337+
338+func (_c *BlobStore_ListMultipart_Call) Return(err error) *BlobStore_ListMultipart_Call {
339+ _c.Call.Return(err)
340+ return _c
341+}
342+
343+func (_c *BlobStore_ListMultipart_Call) RunAndReturn(run func(ctx context.Context, prefix string, fn func(ports.MultipartInfo) error) error) *BlobStore_ListMultipart_Call {
344+ _c.Call.Return(run)
345+ return _c
346+}
347+
348+// NewMultipart provides a mock function for the type BlobStore
349+func (_mock *BlobStore) NewMultipart(ctx context.Context, key string) (ports.MultipartUpload, error) {
350+ ret := _mock.Called(ctx, key)
351+
352+ if len(ret) == 0 {
353+ panic("no return value specified for NewMultipart")
354+ }
355+
356+ var r0 ports.MultipartUpload
357+ var r1 error
358+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) (ports.MultipartUpload, error)); ok {
359+ return returnFunc(ctx, key)
360+ }
361+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) ports.MultipartUpload); ok {
362+ r0 = returnFunc(ctx, key)
363+ } else {
364+ if ret.Get(0) != nil {
365+ r0 = ret.Get(0).(ports.MultipartUpload)
366+ }
367+ }
368+ if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
369+ r1 = returnFunc(ctx, key)
370+ } else {
371+ r1 = ret.Error(1)
372+ }
373+ return r0, r1
374+}
375+
376+// BlobStore_NewMultipart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NewMultipart'
377+type BlobStore_NewMultipart_Call struct {
378+ *mock.Call
379+}
380+
381+// NewMultipart is a helper method to define mock.On call
382+// - ctx context.Context
383+// - key string
384+func (_e *BlobStore_Expecter) NewMultipart(ctx any, key any) *BlobStore_NewMultipart_Call {
385+ return &BlobStore_NewMultipart_Call{Call: _e.mock.On("NewMultipart", ctx, key)}
386+}
387+
388+func (_c *BlobStore_NewMultipart_Call) Run(run func(ctx context.Context, key string)) *BlobStore_NewMultipart_Call {
389+ _c.Call.Run(func(args mock.Arguments) {
390+ var arg0 context.Context
391+ if args[0] != nil {
392+ arg0 = args[0].(context.Context)
393+ }
394+ var arg1 string
395+ if args[1] != nil {
396+ arg1 = args[1].(string)
397+ }
398+ run(
399+ arg0,
400+ arg1,
401+ )
402+ })
403+ return _c
404+}
405+
406+func (_c *BlobStore_NewMultipart_Call) Return(multipartUpload ports.MultipartUpload, err error) *BlobStore_NewMultipart_Call {
407+ _c.Call.Return(multipartUpload, err)
408+ return _c
409+}
410+
411+func (_c *BlobStore_NewMultipart_Call) RunAndReturn(run func(ctx context.Context, key string) (ports.MultipartUpload, error)) *BlobStore_NewMultipart_Call {
412+ _c.Call.Return(run)
413+ return _c
414+}
415+
416+// Open provides a mock function for the type BlobStore
417+func (_mock *BlobStore) Open(ctx context.Context, key string, offset int64, length int64) (io.ReadCloser, ports.BlobInfo, error) {
418+ ret := _mock.Called(ctx, key, offset, length)
419+
420+ if len(ret) == 0 {
421+ panic("no return value specified for Open")
422+ }
423+
424+ var r0 io.ReadCloser
425+ var r1 ports.BlobInfo
426+ var r2 error
427+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64, int64) (io.ReadCloser, ports.BlobInfo, error)); ok {
428+ return returnFunc(ctx, key, offset, length)
429+ }
430+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, int64, int64) io.ReadCloser); ok {
431+ r0 = returnFunc(ctx, key, offset, length)
432+ } else {
433+ if ret.Get(0) != nil {
434+ r0 = ret.Get(0).(io.ReadCloser)
435+ }
436+ }
437+ if returnFunc, ok := ret.Get(1).(func(context.Context, string, int64, int64) ports.BlobInfo); ok {
438+ r1 = returnFunc(ctx, key, offset, length)
439+ } else {
440+ r1 = ret.Get(1).(ports.BlobInfo)
441+ }
442+ if returnFunc, ok := ret.Get(2).(func(context.Context, string, int64, int64) error); ok {
443+ r2 = returnFunc(ctx, key, offset, length)
444+ } else {
445+ r2 = ret.Error(2)
446+ }
447+ return r0, r1, r2
448+}
449+
450+// BlobStore_Open_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Open'
451+type BlobStore_Open_Call struct {
452+ *mock.Call
453+}
454+
455+// Open is a helper method to define mock.On call
456+// - ctx context.Context
457+// - key string
458+// - offset int64
459+// - length int64
460+func (_e *BlobStore_Expecter) Open(ctx any, key any, offset any, length any) *BlobStore_Open_Call {
461+ return &BlobStore_Open_Call{Call: _e.mock.On("Open", ctx, key, offset, length)}
462+}
463+
464+func (_c *BlobStore_Open_Call) Run(run func(ctx context.Context, key string, offset int64, length int64)) *BlobStore_Open_Call {
465+ _c.Call.Run(func(args mock.Arguments) {
466+ var arg0 context.Context
467+ if args[0] != nil {
468+ arg0 = args[0].(context.Context)
469+ }
470+ var arg1 string
471+ if args[1] != nil {
472+ arg1 = args[1].(string)
473+ }
474+ var arg2 int64
475+ if args[2] != nil {
476+ arg2 = args[2].(int64)
477+ }
478+ var arg3 int64
479+ if args[3] != nil {
480+ arg3 = args[3].(int64)
481+ }
482+ run(
483+ arg0,
484+ arg1,
485+ arg2,
486+ arg3,
487+ )
488+ })
489+ return _c
490+}
491+
492+func (_c *BlobStore_Open_Call) Return(readCloser io.ReadCloser, blobInfo ports.BlobInfo, err error) *BlobStore_Open_Call {
493+ _c.Call.Return(readCloser, blobInfo, err)
494+ return _c
495+}
496+
497+func (_c *BlobStore_Open_Call) RunAndReturn(run func(ctx context.Context, key string, offset int64, length int64) (io.ReadCloser, ports.BlobInfo, error)) *BlobStore_Open_Call {
498+ _c.Call.Return(run)
499+ return _c
500+}
501+
502+// Put provides a mock function for the type BlobStore
503+func (_mock *BlobStore) Put(ctx context.Context, key string, r io.Reader, size int64) (ports.BlobInfo, error) {
504+ ret := _mock.Called(ctx, key, r, size)
505+
506+ if len(ret) == 0 {
507+ panic("no return value specified for Put")
508+ }
509+
510+ var r0 ports.BlobInfo
511+ var r1 error
512+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, io.Reader, int64) (ports.BlobInfo, error)); ok {
513+ return returnFunc(ctx, key, r, size)
514+ }
515+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, io.Reader, int64) ports.BlobInfo); ok {
516+ r0 = returnFunc(ctx, key, r, size)
517+ } else {
518+ r0 = ret.Get(0).(ports.BlobInfo)
519+ }
520+ if returnFunc, ok := ret.Get(1).(func(context.Context, string, io.Reader, int64) error); ok {
521+ r1 = returnFunc(ctx, key, r, size)
522+ } else {
523+ r1 = ret.Error(1)
524+ }
525+ return r0, r1
526+}
527+
528+// BlobStore_Put_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Put'
529+type BlobStore_Put_Call struct {
530+ *mock.Call
531+}
532+
533+// Put is a helper method to define mock.On call
534+// - ctx context.Context
535+// - key string
536+// - r io.Reader
537+// - size int64
538+func (_e *BlobStore_Expecter) Put(ctx any, key any, r any, size any) *BlobStore_Put_Call {
539+ return &BlobStore_Put_Call{Call: _e.mock.On("Put", ctx, key, r, size)}
540+}
541+
542+func (_c *BlobStore_Put_Call) Run(run func(ctx context.Context, key string, r io.Reader, size int64)) *BlobStore_Put_Call {
543+ _c.Call.Run(func(args mock.Arguments) {
544+ var arg0 context.Context
545+ if args[0] != nil {
546+ arg0 = args[0].(context.Context)
547+ }
548+ var arg1 string
549+ if args[1] != nil {
550+ arg1 = args[1].(string)
551+ }
552+ var arg2 io.Reader
553+ if args[2] != nil {
554+ arg2 = args[2].(io.Reader)
555+ }
556+ var arg3 int64
557+ if args[3] != nil {
558+ arg3 = args[3].(int64)
559+ }
560+ run(
561+ arg0,
562+ arg1,
563+ arg2,
564+ arg3,
565+ )
566+ })
567+ return _c
568+}
569+
570+func (_c *BlobStore_Put_Call) Return(blobInfo ports.BlobInfo, err error) *BlobStore_Put_Call {
571+ _c.Call.Return(blobInfo, err)
572+ return _c
573+}
574+
575+func (_c *BlobStore_Put_Call) RunAndReturn(run func(ctx context.Context, key string, r io.Reader, size int64) (ports.BlobInfo, error)) *BlobStore_Put_Call {
576+ _c.Call.Return(run)
577+ return _c
578+}
579+
580+// Stat provides a mock function for the type BlobStore
581+func (_mock *BlobStore) Stat(ctx context.Context, key string) (ports.BlobInfo, error) {
582+ ret := _mock.Called(ctx, key)
583+
584+ if len(ret) == 0 {
585+ panic("no return value specified for Stat")
586+ }
587+
588+ var r0 ports.BlobInfo
589+ var r1 error
590+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) (ports.BlobInfo, error)); ok {
591+ return returnFunc(ctx, key)
592+ }
593+ if returnFunc, ok := ret.Get(0).(func(context.Context, string) ports.BlobInfo); ok {
594+ r0 = returnFunc(ctx, key)
595+ } else {
596+ r0 = ret.Get(0).(ports.BlobInfo)
597+ }
598+ if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
599+ r1 = returnFunc(ctx, key)
600+ } else {
601+ r1 = ret.Error(1)
602+ }
603+ return r0, r1
604+}
605+
606+// BlobStore_Stat_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stat'
607+type BlobStore_Stat_Call struct {
608+ *mock.Call
609+}
610+
611+// Stat is a helper method to define mock.On call
612+// - ctx context.Context
613+// - key string
614+func (_e *BlobStore_Expecter) Stat(ctx any, key any) *BlobStore_Stat_Call {
615+ return &BlobStore_Stat_Call{Call: _e.mock.On("Stat", ctx, key)}
616+}
617+
618+func (_c *BlobStore_Stat_Call) Run(run func(ctx context.Context, key string)) *BlobStore_Stat_Call {
619+ _c.Call.Run(func(args mock.Arguments) {
620+ var arg0 context.Context
621+ if args[0] != nil {
622+ arg0 = args[0].(context.Context)
623+ }
624+ var arg1 string
625+ if args[1] != nil {
626+ arg1 = args[1].(string)
627+ }
628+ run(
629+ arg0,
630+ arg1,
631+ )
632+ })
633+ return _c
634+}
635+
636+func (_c *BlobStore_Stat_Call) Return(blobInfo ports.BlobInfo, err error) *BlobStore_Stat_Call {
637+ _c.Call.Return(blobInfo, err)
638+ return _c
639+}
640+
641+func (_c *BlobStore_Stat_Call) RunAndReturn(run func(ctx context.Context, key string) (ports.BlobInfo, error)) *BlobStore_Stat_Call {
642+ _c.Call.Return(run)
643+ return _c
644+}
M internal/ports/mocks/CIArtifactFiles.go +0 -276
@@ -116,84 +116,6 @@ func (_c *CIArtifactFiles_ArtifactIDs_Call) RunAndReturn(run func(ctx context.Co
116 return _c116 return _c
117 }117 }
118 118
119-// Assemble provides a mock function for the type CIArtifactFiles
120-func (_mock *CIArtifactFiles) Assemble(ctx context.Context, ref ports.CIArtifactRef, files []string) (int64, string, error) {
121- ret := _mock.Called(ctx, ref, files)
122-
123- if len(ret) == 0 {
124- panic("no return value specified for Assemble")
125- }
126-
127- var r0 int64
128- var r1 string
129- var r2 error
130- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CIArtifactRef, []string) (int64, string, error)); ok {
131- return returnFunc(ctx, ref, files)
132- }
133- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CIArtifactRef, []string) int64); ok {
134- r0 = returnFunc(ctx, ref, files)
135- } else {
136- r0 = ret.Get(0).(int64)
137- }
138- if returnFunc, ok := ret.Get(1).(func(context.Context, ports.CIArtifactRef, []string) string); ok {
139- r1 = returnFunc(ctx, ref, files)
140- } else {
141- r1 = ret.Get(1).(string)
142- }
143- if returnFunc, ok := ret.Get(2).(func(context.Context, ports.CIArtifactRef, []string) error); ok {
144- r2 = returnFunc(ctx, ref, files)
145- } else {
146- r2 = ret.Error(2)
147- }
148- return r0, r1, r2
149-}
150-
151-// CIArtifactFiles_Assemble_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Assemble'
152-type CIArtifactFiles_Assemble_Call struct {
153- *mock.Call
154-}
155-
156-// Assemble is a helper method to define mock.On call
157-// - ctx context.Context
158-// - ref ports.CIArtifactRef
159-// - files []string
160-func (_e *CIArtifactFiles_Expecter) Assemble(ctx any, ref any, files any) *CIArtifactFiles_Assemble_Call {
161- return &CIArtifactFiles_Assemble_Call{Call: _e.mock.On("Assemble", ctx, ref, files)}
162-}
163-
164-func (_c *CIArtifactFiles_Assemble_Call) Run(run func(ctx context.Context, ref ports.CIArtifactRef, files []string)) *CIArtifactFiles_Assemble_Call {
165- _c.Call.Run(func(args mock.Arguments) {
166- var arg0 context.Context
167- if args[0] != nil {
168- arg0 = args[0].(context.Context)
169- }
170- var arg1 ports.CIArtifactRef
171- if args[1] != nil {
172- arg1 = args[1].(ports.CIArtifactRef)
173- }
174- var arg2 []string
175- if args[2] != nil {
176- arg2 = args[2].([]string)
177- }
178- run(
179- arg0,
180- arg1,
181- arg2,
182- )
183- })
184- return _c
185-}
186-
187-func (_c *CIArtifactFiles_Assemble_Call) Return(size int64, digest string, err error) *CIArtifactFiles_Assemble_Call {
188- _c.Call.Return(size, digest, err)
189- return _c
190-}
191-
192-func (_c *CIArtifactFiles_Assemble_Call) RunAndReturn(run func(ctx context.Context, ref ports.CIArtifactRef, files []string) (int64, string, error)) *CIArtifactFiles_Assemble_Call {
193- _c.Call.Return(run)
194- return _c
195-}
196-
197 // Open provides a mock function for the type CIArtifactFiles119 // Open provides a mock function for the type CIArtifactFiles
198 func (_mock *CIArtifactFiles) Open(ctx context.Context, ref ports.CIArtifactRef, offset int64) (io.ReadCloser, error) {120 func (_mock *CIArtifactFiles) Open(ctx context.Context, ref ports.CIArtifactRef, offset int64) (io.ReadCloser, error) {
199 ret := _mock.Called(ctx, ref, offset)121 ret := _mock.Called(ctx, ref, offset)
@@ -268,84 +190,6 @@ func (_c *CIArtifactFiles_Open_Call) RunAndReturn(run func(ctx context.Context,
268 return _c190 return _c
269 }191 }
270 192
271-// PutBlock provides a mock function for the type CIArtifactFiles
272-func (_mock *CIArtifactFiles) PutBlock(ctx context.Context, ref ports.CIArtifactRef, file string, src io.Reader) (int64, error) {
273- ret := _mock.Called(ctx, ref, file, src)
274-
275- if len(ret) == 0 {
276- panic("no return value specified for PutBlock")
277- }
278-
279- var r0 int64
280- var r1 error
281- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CIArtifactRef, string, io.Reader) (int64, error)); ok {
282- return returnFunc(ctx, ref, file, src)
283- }
284- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CIArtifactRef, string, io.Reader) int64); ok {
285- r0 = returnFunc(ctx, ref, file, src)
286- } else {
287- r0 = ret.Get(0).(int64)
288- }
289- if returnFunc, ok := ret.Get(1).(func(context.Context, ports.CIArtifactRef, string, io.Reader) error); ok {
290- r1 = returnFunc(ctx, ref, file, src)
291- } else {
292- r1 = ret.Error(1)
293- }
294- return r0, r1
295-}
296-
297-// CIArtifactFiles_PutBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutBlock'
298-type CIArtifactFiles_PutBlock_Call struct {
299- *mock.Call
300-}
301-
302-// PutBlock is a helper method to define mock.On call
303-// - ctx context.Context
304-// - ref ports.CIArtifactRef
305-// - file string
306-// - src io.Reader
307-func (_e *CIArtifactFiles_Expecter) PutBlock(ctx any, ref any, file any, src any) *CIArtifactFiles_PutBlock_Call {
308- return &CIArtifactFiles_PutBlock_Call{Call: _e.mock.On("PutBlock", ctx, ref, file, src)}
309-}
310-
311-func (_c *CIArtifactFiles_PutBlock_Call) Run(run func(ctx context.Context, ref ports.CIArtifactRef, file string, src io.Reader)) *CIArtifactFiles_PutBlock_Call {
312- _c.Call.Run(func(args mock.Arguments) {
313- var arg0 context.Context
314- if args[0] != nil {
315- arg0 = args[0].(context.Context)
316- }
317- var arg1 ports.CIArtifactRef
318- if args[1] != nil {
319- arg1 = args[1].(ports.CIArtifactRef)
320- }
321- var arg2 string
322- if args[2] != nil {
323- arg2 = args[2].(string)
324- }
325- var arg3 io.Reader
326- if args[3] != nil {
327- arg3 = args[3].(io.Reader)
328- }
329- run(
330- arg0,
331- arg1,
332- arg2,
333- arg3,
334- )
335- })
336- return _c
337-}
338-
339-func (_c *CIArtifactFiles_PutBlock_Call) Return(n int64, err error) *CIArtifactFiles_PutBlock_Call {
340- _c.Call.Return(n, err)
341- return _c
342-}
343-
344-func (_c *CIArtifactFiles_PutBlock_Call) RunAndReturn(run func(ctx context.Context, ref ports.CIArtifactRef, file string, src io.Reader) (int64, error)) *CIArtifactFiles_PutBlock_Call {
345- _c.Call.Return(run)
346- return _c
347-}
348-
349 // Remove provides a mock function for the type CIArtifactFiles193 // Remove provides a mock function for the type CIArtifactFiles
350 func (_mock *CIArtifactFiles) Remove(ctx context.Context, ref ports.CIArtifactRef) error {194 func (_mock *CIArtifactFiles) Remove(ctx context.Context, ref ports.CIArtifactRef) error {
351 ret := _mock.Called(ctx, ref)195 ret := _mock.Called(ctx, ref)
@@ -403,126 +247,6 @@ func (_c *CIArtifactFiles_Remove_Call) RunAndReturn(run func(ctx context.Context
403 return _c247 return _c
404 }248 }
405 249
406-// RemoveBlock provides a mock function for the type CIArtifactFiles
407-func (_mock *CIArtifactFiles) RemoveBlock(ctx context.Context, ref ports.CIArtifactRef, file string) error {
408- ret := _mock.Called(ctx, ref, file)
409-
410- if len(ret) == 0 {
411- panic("no return value specified for RemoveBlock")
412- }
413-
414- var r0 error
415- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CIArtifactRef, string) error); ok {
416- r0 = returnFunc(ctx, ref, file)
417- } else {
418- r0 = ret.Error(0)
419- }
420- return r0
421-}
422-
423-// CIArtifactFiles_RemoveBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveBlock'
424-type CIArtifactFiles_RemoveBlock_Call struct {
425- *mock.Call
426-}
427-
428-// RemoveBlock is a helper method to define mock.On call
429-// - ctx context.Context
430-// - ref ports.CIArtifactRef
431-// - file string
432-func (_e *CIArtifactFiles_Expecter) RemoveBlock(ctx any, ref any, file any) *CIArtifactFiles_RemoveBlock_Call {
433- return &CIArtifactFiles_RemoveBlock_Call{Call: _e.mock.On("RemoveBlock", ctx, ref, file)}
434-}
435-
436-func (_c *CIArtifactFiles_RemoveBlock_Call) Run(run func(ctx context.Context, ref ports.CIArtifactRef, file string)) *CIArtifactFiles_RemoveBlock_Call {
437- _c.Call.Run(func(args mock.Arguments) {
438- var arg0 context.Context
439- if args[0] != nil {
440- arg0 = args[0].(context.Context)
441- }
442- var arg1 ports.CIArtifactRef
443- if args[1] != nil {
444- arg1 = args[1].(ports.CIArtifactRef)
445- }
446- var arg2 string
447- if args[2] != nil {
448- arg2 = args[2].(string)
449- }
450- run(
451- arg0,
452- arg1,
453- arg2,
454- )
455- })
456- return _c
457-}
458-
459-func (_c *CIArtifactFiles_RemoveBlock_Call) Return(err error) *CIArtifactFiles_RemoveBlock_Call {
460- _c.Call.Return(err)
461- return _c
462-}
463-
464-func (_c *CIArtifactFiles_RemoveBlock_Call) RunAndReturn(run func(ctx context.Context, ref ports.CIArtifactRef, file string) error) *CIArtifactFiles_RemoveBlock_Call {
465- _c.Call.Return(run)
466- return _c
467-}
468-
469-// RemoveBlocks provides a mock function for the type CIArtifactFiles
470-func (_mock *CIArtifactFiles) RemoveBlocks(ctx context.Context, ref ports.CIArtifactRef) error {
471- ret := _mock.Called(ctx, ref)
472-
473- if len(ret) == 0 {
474- panic("no return value specified for RemoveBlocks")
475- }
476-
477- var r0 error
478- if returnFunc, ok := ret.Get(0).(func(context.Context, ports.CIArtifactRef) error); ok {
479- r0 = returnFunc(ctx, ref)
480- } else {
481- r0 = ret.Error(0)
482- }
483- return r0
484-}
485-
486-// CIArtifactFiles_RemoveBlocks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveBlocks'
487-type CIArtifactFiles_RemoveBlocks_Call struct {
488- *mock.Call
489-}
490-
491-// RemoveBlocks is a helper method to define mock.On call
492-// - ctx context.Context
493-// - ref ports.CIArtifactRef
494-func (_e *CIArtifactFiles_Expecter) RemoveBlocks(ctx any, ref any) *CIArtifactFiles_RemoveBlocks_Call {
495- return &CIArtifactFiles_RemoveBlocks_Call{Call: _e.mock.On("RemoveBlocks", ctx, ref)}
496-}
497-
498-func (_c *CIArtifactFiles_RemoveBlocks_Call) Run(run func(ctx context.Context, ref ports.CIArtifactRef)) *CIArtifactFiles_RemoveBlocks_Call {
499- _c.Call.Run(func(args mock.Arguments) {
500- var arg0 context.Context
501- if args[0] != nil {
502- arg0 = args[0].(context.Context)
503- }
504- var arg1 ports.CIArtifactRef
505- if args[1] != nil {
506- arg1 = args[1].(ports.CIArtifactRef)
507- }
508- run(
509- arg0,
510- arg1,
511- )
512- })
513- return _c
514-}
515-
516-func (_c *CIArtifactFiles_RemoveBlocks_Call) Return(err error) *CIArtifactFiles_RemoveBlocks_Call {
517- _c.Call.Return(err)
518- return _c
519-}
520-
521-func (_c *CIArtifactFiles_RemoveBlocks_Call) RunAndReturn(run func(ctx context.Context, ref ports.CIArtifactRef) error) *CIArtifactFiles_RemoveBlocks_Call {
522- _c.Call.Return(run)
523- return _c
524-}
525-
526 // RemoveRuns provides a mock function for the type CIArtifactFiles250 // RemoveRuns provides a mock function for the type CIArtifactFiles
527 func (_mock *CIArtifactFiles) RemoveRuns(ctx context.Context, runIDs []int64) error {251 func (_mock *CIArtifactFiles) RemoveRuns(ctx context.Context, runIDs []int64) error {
528 ret := _mock.Called(ctx, runIDs)252 ret := _mock.Called(ctx, runIDs)
A internal/ports/mocks/GitHubReleaseAPI.go +1044 -0
@@ -0,0 +1,1044 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+ "io"
10+
11+ "bnema.dev/ermite/internal/domain"
12+ "bnema.dev/ermite/internal/ports"
13+ mock "github.com/stretchr/testify/mock"
14+)
15+
16+// NewGitHubReleaseAPI creates a new instance of GitHubReleaseAPI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
17+// The first argument is typically a *testing.T value.
18+func NewGitHubReleaseAPI(t interface {
19+ mock.TestingT
20+ Cleanup(func())
21+}) *GitHubReleaseAPI {
22+ if helper, ok := t.(interface{ Helper() }); ok {
23+ helper.Helper()
24+ }
25+
26+ mock := &GitHubReleaseAPI{}
27+ mock.Mock.Test(t)
28+
29+ t.Cleanup(func() {
30+ if helper, ok := t.(interface{ Helper() }); ok {
31+ helper.Helper()
32+ }
33+ mock.AssertExpectations(t)
34+ })
35+
36+ return mock
37+}
38+
39+// GitHubReleaseAPI is an autogenerated mock type for the GitHubReleaseAPI type
40+type GitHubReleaseAPI struct {
41+ mock.Mock
42+}
43+
44+type GitHubReleaseAPI_Expecter struct {
45+ mock *mock.Mock
46+}
47+
48+func (_m *GitHubReleaseAPI) EXPECT() *GitHubReleaseAPI_Expecter {
49+ return &GitHubReleaseAPI_Expecter{mock: &_m.Mock}
50+}
51+
52+// AssetByID provides a mock function for the type GitHubReleaseAPI
53+func (_mock *GitHubReleaseAPI) AssetByID(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64) (domain.ReleaseAsset, error) {
54+ ret := _mock.Called(ctx, p, owner, repo, assetID)
55+
56+ if len(ret) == 0 {
57+ panic("no return value specified for AssetByID")
58+ }
59+
60+ var r0 domain.ReleaseAsset
61+ var r1 error
62+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64) (domain.ReleaseAsset, error)); ok {
63+ return returnFunc(ctx, p, owner, repo, assetID)
64+ }
65+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64) domain.ReleaseAsset); ok {
66+ r0 = returnFunc(ctx, p, owner, repo, assetID)
67+ } else {
68+ r0 = ret.Get(0).(domain.ReleaseAsset)
69+ }
70+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int64) error); ok {
71+ r1 = returnFunc(ctx, p, owner, repo, assetID)
72+ } else {
73+ r1 = ret.Error(1)
74+ }
75+ return r0, r1
76+}
77+
78+// GitHubReleaseAPI_AssetByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AssetByID'
79+type GitHubReleaseAPI_AssetByID_Call struct {
80+ *mock.Call
81+}
82+
83+// AssetByID is a helper method to define mock.On call
84+// - ctx context.Context
85+// - p domain.Principal
86+// - owner string
87+// - repo string
88+// - assetID int64
89+func (_e *GitHubReleaseAPI_Expecter) AssetByID(ctx any, p any, owner any, repo any, assetID any) *GitHubReleaseAPI_AssetByID_Call {
90+ return &GitHubReleaseAPI_AssetByID_Call{Call: _e.mock.On("AssetByID", ctx, p, owner, repo, assetID)}
91+}
92+
93+func (_c *GitHubReleaseAPI_AssetByID_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64)) *GitHubReleaseAPI_AssetByID_Call {
94+ _c.Call.Run(func(args mock.Arguments) {
95+ var arg0 context.Context
96+ if args[0] != nil {
97+ arg0 = args[0].(context.Context)
98+ }
99+ var arg1 domain.Principal
100+ if args[1] != nil {
101+ arg1 = args[1].(domain.Principal)
102+ }
103+ var arg2 string
104+ if args[2] != nil {
105+ arg2 = args[2].(string)
106+ }
107+ var arg3 string
108+ if args[3] != nil {
109+ arg3 = args[3].(string)
110+ }
111+ var arg4 int64
112+ if args[4] != nil {
113+ arg4 = args[4].(int64)
114+ }
115+ run(
116+ arg0,
117+ arg1,
118+ arg2,
119+ arg3,
120+ arg4,
121+ )
122+ })
123+ return _c
124+}
125+
126+func (_c *GitHubReleaseAPI_AssetByID_Call) Return(releaseAsset domain.ReleaseAsset, err error) *GitHubReleaseAPI_AssetByID_Call {
127+ _c.Call.Return(releaseAsset, err)
128+ return _c
129+}
130+
131+func (_c *GitHubReleaseAPI_AssetByID_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64) (domain.ReleaseAsset, error)) *GitHubReleaseAPI_AssetByID_Call {
132+ _c.Call.Return(run)
133+ return _c
134+}
135+
136+// CreateRelease provides a mock function for the type GitHubReleaseAPI
137+func (_mock *GitHubReleaseAPI) CreateRelease(ctx context.Context, p domain.Principal, owner string, repo string, body []byte) ([]byte, error) {
138+ ret := _mock.Called(ctx, p, owner, repo, body)
139+
140+ if len(ret) == 0 {
141+ panic("no return value specified for CreateRelease")
142+ }
143+
144+ var r0 []byte
145+ var r1 error
146+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, []byte) ([]byte, error)); ok {
147+ return returnFunc(ctx, p, owner, repo, body)
148+ }
149+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, []byte) []byte); ok {
150+ r0 = returnFunc(ctx, p, owner, repo, body)
151+ } else {
152+ if ret.Get(0) != nil {
153+ r0 = ret.Get(0).([]byte)
154+ }
155+ }
156+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, []byte) error); ok {
157+ r1 = returnFunc(ctx, p, owner, repo, body)
158+ } else {
159+ r1 = ret.Error(1)
160+ }
161+ return r0, r1
162+}
163+
164+// GitHubReleaseAPI_CreateRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRelease'
165+type GitHubReleaseAPI_CreateRelease_Call struct {
166+ *mock.Call
167+}
168+
169+// CreateRelease is a helper method to define mock.On call
170+// - ctx context.Context
171+// - p domain.Principal
172+// - owner string
173+// - repo string
174+// - body []byte
175+func (_e *GitHubReleaseAPI_Expecter) CreateRelease(ctx any, p any, owner any, repo any, body any) *GitHubReleaseAPI_CreateRelease_Call {
176+ return &GitHubReleaseAPI_CreateRelease_Call{Call: _e.mock.On("CreateRelease", ctx, p, owner, repo, body)}
177+}
178+
179+func (_c *GitHubReleaseAPI_CreateRelease_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, body []byte)) *GitHubReleaseAPI_CreateRelease_Call {
180+ _c.Call.Run(func(args mock.Arguments) {
181+ var arg0 context.Context
182+ if args[0] != nil {
183+ arg0 = args[0].(context.Context)
184+ }
185+ var arg1 domain.Principal
186+ if args[1] != nil {
187+ arg1 = args[1].(domain.Principal)
188+ }
189+ var arg2 string
190+ if args[2] != nil {
191+ arg2 = args[2].(string)
192+ }
193+ var arg3 string
194+ if args[3] != nil {
195+ arg3 = args[3].(string)
196+ }
197+ var arg4 []byte
198+ if args[4] != nil {
199+ arg4 = args[4].([]byte)
200+ }
201+ run(
202+ arg0,
203+ arg1,
204+ arg2,
205+ arg3,
206+ arg4,
207+ )
208+ })
209+ return _c
210+}
211+
212+func (_c *GitHubReleaseAPI_CreateRelease_Call) Return(bytes []byte, err error) *GitHubReleaseAPI_CreateRelease_Call {
213+ _c.Call.Return(bytes, err)
214+ return _c
215+}
216+
217+func (_c *GitHubReleaseAPI_CreateRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, body []byte) ([]byte, error)) *GitHubReleaseAPI_CreateRelease_Call {
218+ _c.Call.Return(run)
219+ return _c
220+}
221+
222+// DeleteAsset provides a mock function for the type GitHubReleaseAPI
223+func (_mock *GitHubReleaseAPI) DeleteAsset(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64) error {
224+ ret := _mock.Called(ctx, p, owner, repo, assetID)
225+
226+ if len(ret) == 0 {
227+ panic("no return value specified for DeleteAsset")
228+ }
229+
230+ var r0 error
231+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64) error); ok {
232+ r0 = returnFunc(ctx, p, owner, repo, assetID)
233+ } else {
234+ r0 = ret.Error(0)
235+ }
236+ return r0
237+}
238+
239+// GitHubReleaseAPI_DeleteAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAsset'
240+type GitHubReleaseAPI_DeleteAsset_Call struct {
241+ *mock.Call
242+}
243+
244+// DeleteAsset is a helper method to define mock.On call
245+// - ctx context.Context
246+// - p domain.Principal
247+// - owner string
248+// - repo string
249+// - assetID int64
250+func (_e *GitHubReleaseAPI_Expecter) DeleteAsset(ctx any, p any, owner any, repo any, assetID any) *GitHubReleaseAPI_DeleteAsset_Call {
251+ return &GitHubReleaseAPI_DeleteAsset_Call{Call: _e.mock.On("DeleteAsset", ctx, p, owner, repo, assetID)}
252+}
253+
254+func (_c *GitHubReleaseAPI_DeleteAsset_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64)) *GitHubReleaseAPI_DeleteAsset_Call {
255+ _c.Call.Run(func(args mock.Arguments) {
256+ var arg0 context.Context
257+ if args[0] != nil {
258+ arg0 = args[0].(context.Context)
259+ }
260+ var arg1 domain.Principal
261+ if args[1] != nil {
262+ arg1 = args[1].(domain.Principal)
263+ }
264+ var arg2 string
265+ if args[2] != nil {
266+ arg2 = args[2].(string)
267+ }
268+ var arg3 string
269+ if args[3] != nil {
270+ arg3 = args[3].(string)
271+ }
272+ var arg4 int64
273+ if args[4] != nil {
274+ arg4 = args[4].(int64)
275+ }
276+ run(
277+ arg0,
278+ arg1,
279+ arg2,
280+ arg3,
281+ arg4,
282+ )
283+ })
284+ return _c
285+}
286+
287+func (_c *GitHubReleaseAPI_DeleteAsset_Call) Return(err error) *GitHubReleaseAPI_DeleteAsset_Call {
288+ _c.Call.Return(err)
289+ return _c
290+}
291+
292+func (_c *GitHubReleaseAPI_DeleteAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64) error) *GitHubReleaseAPI_DeleteAsset_Call {
293+ _c.Call.Return(run)
294+ return _c
295+}
296+
297+// DeleteRelease provides a mock function for the type GitHubReleaseAPI
298+func (_mock *GitHubReleaseAPI) DeleteRelease(ctx context.Context, p domain.Principal, owner string, repo string, id int64) error {
299+ ret := _mock.Called(ctx, p, owner, repo, id)
300+
301+ if len(ret) == 0 {
302+ panic("no return value specified for DeleteRelease")
303+ }
304+
305+ var r0 error
306+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64) error); ok {
307+ r0 = returnFunc(ctx, p, owner, repo, id)
308+ } else {
309+ r0 = ret.Error(0)
310+ }
311+ return r0
312+}
313+
314+// GitHubReleaseAPI_DeleteRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteRelease'
315+type GitHubReleaseAPI_DeleteRelease_Call struct {
316+ *mock.Call
317+}
318+
319+// DeleteRelease is a helper method to define mock.On call
320+// - ctx context.Context
321+// - p domain.Principal
322+// - owner string
323+// - repo string
324+// - id int64
325+func (_e *GitHubReleaseAPI_Expecter) DeleteRelease(ctx any, p any, owner any, repo any, id any) *GitHubReleaseAPI_DeleteRelease_Call {
326+ return &GitHubReleaseAPI_DeleteRelease_Call{Call: _e.mock.On("DeleteRelease", ctx, p, owner, repo, id)}
327+}
328+
329+func (_c *GitHubReleaseAPI_DeleteRelease_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64)) *GitHubReleaseAPI_DeleteRelease_Call {
330+ _c.Call.Run(func(args mock.Arguments) {
331+ var arg0 context.Context
332+ if args[0] != nil {
333+ arg0 = args[0].(context.Context)
334+ }
335+ var arg1 domain.Principal
336+ if args[1] != nil {
337+ arg1 = args[1].(domain.Principal)
338+ }
339+ var arg2 string
340+ if args[2] != nil {
341+ arg2 = args[2].(string)
342+ }
343+ var arg3 string
344+ if args[3] != nil {
345+ arg3 = args[3].(string)
346+ }
347+ var arg4 int64
348+ if args[4] != nil {
349+ arg4 = args[4].(int64)
350+ }
351+ run(
352+ arg0,
353+ arg1,
354+ arg2,
355+ arg3,
356+ arg4,
357+ )
358+ })
359+ return _c
360+}
361+
362+func (_c *GitHubReleaseAPI_DeleteRelease_Call) Return(err error) *GitHubReleaseAPI_DeleteRelease_Call {
363+ _c.Call.Return(err)
364+ return _c
365+}
366+
367+func (_c *GitHubReleaseAPI_DeleteRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64) error) *GitHubReleaseAPI_DeleteRelease_Call {
368+ _c.Call.Return(run)
369+ return _c
370+}
371+
372+// GetAsset provides a mock function for the type GitHubReleaseAPI
373+func (_mock *GitHubReleaseAPI) GetAsset(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64) ([]byte, error) {
374+ ret := _mock.Called(ctx, p, owner, repo, assetID)
375+
376+ if len(ret) == 0 {
377+ panic("no return value specified for GetAsset")
378+ }
379+
380+ var r0 []byte
381+ var r1 error
382+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64) ([]byte, error)); ok {
383+ return returnFunc(ctx, p, owner, repo, assetID)
384+ }
385+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64) []byte); ok {
386+ r0 = returnFunc(ctx, p, owner, repo, assetID)
387+ } else {
388+ if ret.Get(0) != nil {
389+ r0 = ret.Get(0).([]byte)
390+ }
391+ }
392+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int64) error); ok {
393+ r1 = returnFunc(ctx, p, owner, repo, assetID)
394+ } else {
395+ r1 = ret.Error(1)
396+ }
397+ return r0, r1
398+}
399+
400+// GitHubReleaseAPI_GetAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAsset'
401+type GitHubReleaseAPI_GetAsset_Call struct {
402+ *mock.Call
403+}
404+
405+// GetAsset is a helper method to define mock.On call
406+// - ctx context.Context
407+// - p domain.Principal
408+// - owner string
409+// - repo string
410+// - assetID int64
411+func (_e *GitHubReleaseAPI_Expecter) GetAsset(ctx any, p any, owner any, repo any, assetID any) *GitHubReleaseAPI_GetAsset_Call {
412+ return &GitHubReleaseAPI_GetAsset_Call{Call: _e.mock.On("GetAsset", ctx, p, owner, repo, assetID)}
413+}
414+
415+func (_c *GitHubReleaseAPI_GetAsset_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64)) *GitHubReleaseAPI_GetAsset_Call {
416+ _c.Call.Run(func(args mock.Arguments) {
417+ var arg0 context.Context
418+ if args[0] != nil {
419+ arg0 = args[0].(context.Context)
420+ }
421+ var arg1 domain.Principal
422+ if args[1] != nil {
423+ arg1 = args[1].(domain.Principal)
424+ }
425+ var arg2 string
426+ if args[2] != nil {
427+ arg2 = args[2].(string)
428+ }
429+ var arg3 string
430+ if args[3] != nil {
431+ arg3 = args[3].(string)
432+ }
433+ var arg4 int64
434+ if args[4] != nil {
435+ arg4 = args[4].(int64)
436+ }
437+ run(
438+ arg0,
439+ arg1,
440+ arg2,
441+ arg3,
442+ arg4,
443+ )
444+ })
445+ return _c
446+}
447+
448+func (_c *GitHubReleaseAPI_GetAsset_Call) Return(bytes []byte, err error) *GitHubReleaseAPI_GetAsset_Call {
449+ _c.Call.Return(bytes, err)
450+ return _c
451+}
452+
453+func (_c *GitHubReleaseAPI_GetAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64) ([]byte, error)) *GitHubReleaseAPI_GetAsset_Call {
454+ _c.Call.Return(run)
455+ return _c
456+}
457+
458+// GetRelease provides a mock function for the type GitHubReleaseAPI
459+func (_mock *GitHubReleaseAPI) GetRelease(ctx context.Context, p domain.Principal, owner string, repo string, ref ports.ReleaseRef) ([]byte, error) {
460+ ret := _mock.Called(ctx, p, owner, repo, ref)
461+
462+ if len(ret) == 0 {
463+ panic("no return value specified for GetRelease")
464+ }
465+
466+ var r0 []byte
467+ var r1 error
468+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, ports.ReleaseRef) ([]byte, error)); ok {
469+ return returnFunc(ctx, p, owner, repo, ref)
470+ }
471+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, ports.ReleaseRef) []byte); ok {
472+ r0 = returnFunc(ctx, p, owner, repo, ref)
473+ } else {
474+ if ret.Get(0) != nil {
475+ r0 = ret.Get(0).([]byte)
476+ }
477+ }
478+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, ports.ReleaseRef) error); ok {
479+ r1 = returnFunc(ctx, p, owner, repo, ref)
480+ } else {
481+ r1 = ret.Error(1)
482+ }
483+ return r0, r1
484+}
485+
486+// GitHubReleaseAPI_GetRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRelease'
487+type GitHubReleaseAPI_GetRelease_Call struct {
488+ *mock.Call
489+}
490+
491+// GetRelease is a helper method to define mock.On call
492+// - ctx context.Context
493+// - p domain.Principal
494+// - owner string
495+// - repo string
496+// - ref ports.ReleaseRef
497+func (_e *GitHubReleaseAPI_Expecter) GetRelease(ctx any, p any, owner any, repo any, ref any) *GitHubReleaseAPI_GetRelease_Call {
498+ return &GitHubReleaseAPI_GetRelease_Call{Call: _e.mock.On("GetRelease", ctx, p, owner, repo, ref)}
499+}
500+
501+func (_c *GitHubReleaseAPI_GetRelease_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, ref ports.ReleaseRef)) *GitHubReleaseAPI_GetRelease_Call {
502+ _c.Call.Run(func(args mock.Arguments) {
503+ var arg0 context.Context
504+ if args[0] != nil {
505+ arg0 = args[0].(context.Context)
506+ }
507+ var arg1 domain.Principal
508+ if args[1] != nil {
509+ arg1 = args[1].(domain.Principal)
510+ }
511+ var arg2 string
512+ if args[2] != nil {
513+ arg2 = args[2].(string)
514+ }
515+ var arg3 string
516+ if args[3] != nil {
517+ arg3 = args[3].(string)
518+ }
519+ var arg4 ports.ReleaseRef
520+ if args[4] != nil {
521+ arg4 = args[4].(ports.ReleaseRef)
522+ }
523+ run(
524+ arg0,
525+ arg1,
526+ arg2,
527+ arg3,
528+ arg4,
529+ )
530+ })
531+ return _c
532+}
533+
534+func (_c *GitHubReleaseAPI_GetRelease_Call) Return(bytes []byte, err error) *GitHubReleaseAPI_GetRelease_Call {
535+ _c.Call.Return(bytes, err)
536+ return _c
537+}
538+
539+func (_c *GitHubReleaseAPI_GetRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, ref ports.ReleaseRef) ([]byte, error)) *GitHubReleaseAPI_GetRelease_Call {
540+ _c.Call.Return(run)
541+ return _c
542+}
543+
544+// ListAssets provides a mock function for the type GitHubReleaseAPI
545+func (_mock *GitHubReleaseAPI) ListAssets(ctx context.Context, p domain.Principal, owner string, repo string, id int64, page int, perPage int) ([]byte, string, error) {
546+ ret := _mock.Called(ctx, p, owner, repo, id, page, perPage)
547+
548+ if len(ret) == 0 {
549+ panic("no return value specified for ListAssets")
550+ }
551+
552+ var r0 []byte
553+ var r1 string
554+ var r2 error
555+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, int, int) ([]byte, string, error)); ok {
556+ return returnFunc(ctx, p, owner, repo, id, page, perPage)
557+ }
558+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, int, int) []byte); ok {
559+ r0 = returnFunc(ctx, p, owner, repo, id, page, perPage)
560+ } else {
561+ if ret.Get(0) != nil {
562+ r0 = ret.Get(0).([]byte)
563+ }
564+ }
565+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int64, int, int) string); ok {
566+ r1 = returnFunc(ctx, p, owner, repo, id, page, perPage)
567+ } else {
568+ r1 = ret.Get(1).(string)
569+ }
570+ if returnFunc, ok := ret.Get(2).(func(context.Context, domain.Principal, string, string, int64, int, int) error); ok {
571+ r2 = returnFunc(ctx, p, owner, repo, id, page, perPage)
572+ } else {
573+ r2 = ret.Error(2)
574+ }
575+ return r0, r1, r2
576+}
577+
578+// GitHubReleaseAPI_ListAssets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAssets'
579+type GitHubReleaseAPI_ListAssets_Call struct {
580+ *mock.Call
581+}
582+
583+// ListAssets is a helper method to define mock.On call
584+// - ctx context.Context
585+// - p domain.Principal
586+// - owner string
587+// - repo string
588+// - id int64
589+// - page int
590+// - perPage int
591+func (_e *GitHubReleaseAPI_Expecter) ListAssets(ctx any, p any, owner any, repo any, id any, page any, perPage any) *GitHubReleaseAPI_ListAssets_Call {
592+ return &GitHubReleaseAPI_ListAssets_Call{Call: _e.mock.On("ListAssets", ctx, p, owner, repo, id, page, perPage)}
593+}
594+
595+func (_c *GitHubReleaseAPI_ListAssets_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64, page int, perPage int)) *GitHubReleaseAPI_ListAssets_Call {
596+ _c.Call.Run(func(args mock.Arguments) {
597+ var arg0 context.Context
598+ if args[0] != nil {
599+ arg0 = args[0].(context.Context)
600+ }
601+ var arg1 domain.Principal
602+ if args[1] != nil {
603+ arg1 = args[1].(domain.Principal)
604+ }
605+ var arg2 string
606+ if args[2] != nil {
607+ arg2 = args[2].(string)
608+ }
609+ var arg3 string
610+ if args[3] != nil {
611+ arg3 = args[3].(string)
612+ }
613+ var arg4 int64
614+ if args[4] != nil {
615+ arg4 = args[4].(int64)
616+ }
617+ var arg5 int
618+ if args[5] != nil {
619+ arg5 = args[5].(int)
620+ }
621+ var arg6 int
622+ if args[6] != nil {
623+ arg6 = args[6].(int)
624+ }
625+ run(
626+ arg0,
627+ arg1,
628+ arg2,
629+ arg3,
630+ arg4,
631+ arg5,
632+ arg6,
633+ )
634+ })
635+ return _c
636+}
637+
638+func (_c *GitHubReleaseAPI_ListAssets_Call) Return(body []byte, link string, err error) *GitHubReleaseAPI_ListAssets_Call {
639+ _c.Call.Return(body, link, err)
640+ return _c
641+}
642+
643+func (_c *GitHubReleaseAPI_ListAssets_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64, page int, perPage int) ([]byte, string, error)) *GitHubReleaseAPI_ListAssets_Call {
644+ _c.Call.Return(run)
645+ return _c
646+}
647+
648+// ListReleases provides a mock function for the type GitHubReleaseAPI
649+func (_mock *GitHubReleaseAPI) ListReleases(ctx context.Context, p domain.Principal, owner string, repo string, page int, perPage int) ([]byte, string, error) {
650+ ret := _mock.Called(ctx, p, owner, repo, page, perPage)
651+
652+ if len(ret) == 0 {
653+ panic("no return value specified for ListReleases")
654+ }
655+
656+ var r0 []byte
657+ var r1 string
658+ var r2 error
659+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int, int) ([]byte, string, error)); ok {
660+ return returnFunc(ctx, p, owner, repo, page, perPage)
661+ }
662+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int, int) []byte); ok {
663+ r0 = returnFunc(ctx, p, owner, repo, page, perPage)
664+ } else {
665+ if ret.Get(0) != nil {
666+ r0 = ret.Get(0).([]byte)
667+ }
668+ }
669+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int, int) string); ok {
670+ r1 = returnFunc(ctx, p, owner, repo, page, perPage)
671+ } else {
672+ r1 = ret.Get(1).(string)
673+ }
674+ if returnFunc, ok := ret.Get(2).(func(context.Context, domain.Principal, string, string, int, int) error); ok {
675+ r2 = returnFunc(ctx, p, owner, repo, page, perPage)
676+ } else {
677+ r2 = ret.Error(2)
678+ }
679+ return r0, r1, r2
680+}
681+
682+// GitHubReleaseAPI_ListReleases_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReleases'
683+type GitHubReleaseAPI_ListReleases_Call struct {
684+ *mock.Call
685+}
686+
687+// ListReleases is a helper method to define mock.On call
688+// - ctx context.Context
689+// - p domain.Principal
690+// - owner string
691+// - repo string
692+// - page int
693+// - perPage int
694+func (_e *GitHubReleaseAPI_Expecter) ListReleases(ctx any, p any, owner any, repo any, page any, perPage any) *GitHubReleaseAPI_ListReleases_Call {
695+ return &GitHubReleaseAPI_ListReleases_Call{Call: _e.mock.On("ListReleases", ctx, p, owner, repo, page, perPage)}
696+}
697+
698+func (_c *GitHubReleaseAPI_ListReleases_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, page int, perPage int)) *GitHubReleaseAPI_ListReleases_Call {
699+ _c.Call.Run(func(args mock.Arguments) {
700+ var arg0 context.Context
701+ if args[0] != nil {
702+ arg0 = args[0].(context.Context)
703+ }
704+ var arg1 domain.Principal
705+ if args[1] != nil {
706+ arg1 = args[1].(domain.Principal)
707+ }
708+ var arg2 string
709+ if args[2] != nil {
710+ arg2 = args[2].(string)
711+ }
712+ var arg3 string
713+ if args[3] != nil {
714+ arg3 = args[3].(string)
715+ }
716+ var arg4 int
717+ if args[4] != nil {
718+ arg4 = args[4].(int)
719+ }
720+ var arg5 int
721+ if args[5] != nil {
722+ arg5 = args[5].(int)
723+ }
724+ run(
725+ arg0,
726+ arg1,
727+ arg2,
728+ arg3,
729+ arg4,
730+ arg5,
731+ )
732+ })
733+ return _c
734+}
735+
736+func (_c *GitHubReleaseAPI_ListReleases_Call) Return(body []byte, link string, err error) *GitHubReleaseAPI_ListReleases_Call {
737+ _c.Call.Return(body, link, err)
738+ return _c
739+}
740+
741+func (_c *GitHubReleaseAPI_ListReleases_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, page int, perPage int) ([]byte, string, error)) *GitHubReleaseAPI_ListReleases_Call {
742+ _c.Call.Return(run)
743+ return _c
744+}
745+
746+// UpdateAsset provides a mock function for the type GitHubReleaseAPI
747+func (_mock *GitHubReleaseAPI) UpdateAsset(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64, body []byte) ([]byte, error) {
748+ ret := _mock.Called(ctx, p, owner, repo, assetID, body)
749+
750+ if len(ret) == 0 {
751+ panic("no return value specified for UpdateAsset")
752+ }
753+
754+ var r0 []byte
755+ var r1 error
756+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, []byte) ([]byte, error)); ok {
757+ return returnFunc(ctx, p, owner, repo, assetID, body)
758+ }
759+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, []byte) []byte); ok {
760+ r0 = returnFunc(ctx, p, owner, repo, assetID, body)
761+ } else {
762+ if ret.Get(0) != nil {
763+ r0 = ret.Get(0).([]byte)
764+ }
765+ }
766+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int64, []byte) error); ok {
767+ r1 = returnFunc(ctx, p, owner, repo, assetID, body)
768+ } else {
769+ r1 = ret.Error(1)
770+ }
771+ return r0, r1
772+}
773+
774+// GitHubReleaseAPI_UpdateAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAsset'
775+type GitHubReleaseAPI_UpdateAsset_Call struct {
776+ *mock.Call
777+}
778+
779+// UpdateAsset is a helper method to define mock.On call
780+// - ctx context.Context
781+// - p domain.Principal
782+// - owner string
783+// - repo string
784+// - assetID int64
785+// - body []byte
786+func (_e *GitHubReleaseAPI_Expecter) UpdateAsset(ctx any, p any, owner any, repo any, assetID any, body any) *GitHubReleaseAPI_UpdateAsset_Call {
787+ return &GitHubReleaseAPI_UpdateAsset_Call{Call: _e.mock.On("UpdateAsset", ctx, p, owner, repo, assetID, body)}
788+}
789+
790+func (_c *GitHubReleaseAPI_UpdateAsset_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64, body []byte)) *GitHubReleaseAPI_UpdateAsset_Call {
791+ _c.Call.Run(func(args mock.Arguments) {
792+ var arg0 context.Context
793+ if args[0] != nil {
794+ arg0 = args[0].(context.Context)
795+ }
796+ var arg1 domain.Principal
797+ if args[1] != nil {
798+ arg1 = args[1].(domain.Principal)
799+ }
800+ var arg2 string
801+ if args[2] != nil {
802+ arg2 = args[2].(string)
803+ }
804+ var arg3 string
805+ if args[3] != nil {
806+ arg3 = args[3].(string)
807+ }
808+ var arg4 int64
809+ if args[4] != nil {
810+ arg4 = args[4].(int64)
811+ }
812+ var arg5 []byte
813+ if args[5] != nil {
814+ arg5 = args[5].([]byte)
815+ }
816+ run(
817+ arg0,
818+ arg1,
819+ arg2,
820+ arg3,
821+ arg4,
822+ arg5,
823+ )
824+ })
825+ return _c
826+}
827+
828+func (_c *GitHubReleaseAPI_UpdateAsset_Call) Return(bytes []byte, err error) *GitHubReleaseAPI_UpdateAsset_Call {
829+ _c.Call.Return(bytes, err)
830+ return _c
831+}
832+
833+func (_c *GitHubReleaseAPI_UpdateAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, assetID int64, body []byte) ([]byte, error)) *GitHubReleaseAPI_UpdateAsset_Call {
834+ _c.Call.Return(run)
835+ return _c
836+}
837+
838+// UpdateRelease provides a mock function for the type GitHubReleaseAPI
839+func (_mock *GitHubReleaseAPI) UpdateRelease(ctx context.Context, p domain.Principal, owner string, repo string, id int64, body []byte) ([]byte, error) {
840+ ret := _mock.Called(ctx, p, owner, repo, id, body)
841+
842+ if len(ret) == 0 {
843+ panic("no return value specified for UpdateRelease")
844+ }
845+
846+ var r0 []byte
847+ var r1 error
848+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, []byte) ([]byte, error)); ok {
849+ return returnFunc(ctx, p, owner, repo, id, body)
850+ }
851+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, []byte) []byte); ok {
852+ r0 = returnFunc(ctx, p, owner, repo, id, body)
853+ } else {
854+ if ret.Get(0) != nil {
855+ r0 = ret.Get(0).([]byte)
856+ }
857+ }
858+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int64, []byte) error); ok {
859+ r1 = returnFunc(ctx, p, owner, repo, id, body)
860+ } else {
861+ r1 = ret.Error(1)
862+ }
863+ return r0, r1
864+}
865+
866+// GitHubReleaseAPI_UpdateRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateRelease'
867+type GitHubReleaseAPI_UpdateRelease_Call struct {
868+ *mock.Call
869+}
870+
871+// UpdateRelease is a helper method to define mock.On call
872+// - ctx context.Context
873+// - p domain.Principal
874+// - owner string
875+// - repo string
876+// - id int64
877+// - body []byte
878+func (_e *GitHubReleaseAPI_Expecter) UpdateRelease(ctx any, p any, owner any, repo any, id any, body any) *GitHubReleaseAPI_UpdateRelease_Call {
879+ return &GitHubReleaseAPI_UpdateRelease_Call{Call: _e.mock.On("UpdateRelease", ctx, p, owner, repo, id, body)}
880+}
881+
882+func (_c *GitHubReleaseAPI_UpdateRelease_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64, body []byte)) *GitHubReleaseAPI_UpdateRelease_Call {
883+ _c.Call.Run(func(args mock.Arguments) {
884+ var arg0 context.Context
885+ if args[0] != nil {
886+ arg0 = args[0].(context.Context)
887+ }
888+ var arg1 domain.Principal
889+ if args[1] != nil {
890+ arg1 = args[1].(domain.Principal)
891+ }
892+ var arg2 string
893+ if args[2] != nil {
894+ arg2 = args[2].(string)
895+ }
896+ var arg3 string
897+ if args[3] != nil {
898+ arg3 = args[3].(string)
899+ }
900+ var arg4 int64
901+ if args[4] != nil {
902+ arg4 = args[4].(int64)
903+ }
904+ var arg5 []byte
905+ if args[5] != nil {
906+ arg5 = args[5].([]byte)
907+ }
908+ run(
909+ arg0,
910+ arg1,
911+ arg2,
912+ arg3,
913+ arg4,
914+ arg5,
915+ )
916+ })
917+ return _c
918+}
919+
920+func (_c *GitHubReleaseAPI_UpdateRelease_Call) Return(bytes []byte, err error) *GitHubReleaseAPI_UpdateRelease_Call {
921+ _c.Call.Return(bytes, err)
922+ return _c
923+}
924+
925+func (_c *GitHubReleaseAPI_UpdateRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64, body []byte) ([]byte, error)) *GitHubReleaseAPI_UpdateRelease_Call {
926+ _c.Call.Return(run)
927+ return _c
928+}
929+
930+// UploadAsset provides a mock function for the type GitHubReleaseAPI
931+func (_mock *GitHubReleaseAPI) UploadAsset(ctx context.Context, p domain.Principal, owner string, repo string, id int64, name string, label string, contentType string, size int64, r io.Reader) ([]byte, error) {
932+ ret := _mock.Called(ctx, p, owner, repo, id, name, label, contentType, size, r)
933+
934+ if len(ret) == 0 {
935+ panic("no return value specified for UploadAsset")
936+ }
937+
938+ var r0 []byte
939+ var r1 error
940+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, string, string, string, int64, io.Reader) ([]byte, error)); ok {
941+ return returnFunc(ctx, p, owner, repo, id, name, label, contentType, size, r)
942+ }
943+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string, string, int64, string, string, string, int64, io.Reader) []byte); ok {
944+ r0 = returnFunc(ctx, p, owner, repo, id, name, label, contentType, size, r)
945+ } else {
946+ if ret.Get(0) != nil {
947+ r0 = ret.Get(0).([]byte)
948+ }
949+ }
950+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, string, string, int64, string, string, string, int64, io.Reader) error); ok {
951+ r1 = returnFunc(ctx, p, owner, repo, id, name, label, contentType, size, r)
952+ } else {
953+ r1 = ret.Error(1)
954+ }
955+ return r0, r1
956+}
957+
958+// GitHubReleaseAPI_UploadAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadAsset'
959+type GitHubReleaseAPI_UploadAsset_Call struct {
960+ *mock.Call
961+}
962+
963+// UploadAsset is a helper method to define mock.On call
964+// - ctx context.Context
965+// - p domain.Principal
966+// - owner string
967+// - repo string
968+// - id int64
969+// - name string
970+// - label string
971+// - contentType string
972+// - size int64
973+// - r io.Reader
974+func (_e *GitHubReleaseAPI_Expecter) UploadAsset(ctx any, p any, owner any, repo any, id any, name any, label any, contentType any, size any, r any) *GitHubReleaseAPI_UploadAsset_Call {
975+ return &GitHubReleaseAPI_UploadAsset_Call{Call: _e.mock.On("UploadAsset", ctx, p, owner, repo, id, name, label, contentType, size, r)}
976+}
977+
978+func (_c *GitHubReleaseAPI_UploadAsset_Call) Run(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64, name string, label string, contentType string, size int64, r io.Reader)) *GitHubReleaseAPI_UploadAsset_Call {
979+ _c.Call.Run(func(args mock.Arguments) {
980+ var arg0 context.Context
981+ if args[0] != nil {
982+ arg0 = args[0].(context.Context)
983+ }
984+ var arg1 domain.Principal
985+ if args[1] != nil {
986+ arg1 = args[1].(domain.Principal)
987+ }
988+ var arg2 string
989+ if args[2] != nil {
990+ arg2 = args[2].(string)
991+ }
992+ var arg3 string
993+ if args[3] != nil {
994+ arg3 = args[3].(string)
995+ }
996+ var arg4 int64
997+ if args[4] != nil {
998+ arg4 = args[4].(int64)
999+ }
1000+ var arg5 string
1001+ if args[5] != nil {
1002+ arg5 = args[5].(string)
1003+ }
1004+ var arg6 string
1005+ if args[6] != nil {
1006+ arg6 = args[6].(string)
1007+ }
1008+ var arg7 string
1009+ if args[7] != nil {
1010+ arg7 = args[7].(string)
1011+ }
1012+ var arg8 int64
1013+ if args[8] != nil {
1014+ arg8 = args[8].(int64)
1015+ }
1016+ var arg9 io.Reader
1017+ if args[9] != nil {
1018+ arg9 = args[9].(io.Reader)
1019+ }
1020+ run(
1021+ arg0,
1022+ arg1,
1023+ arg2,
1024+ arg3,
1025+ arg4,
1026+ arg5,
1027+ arg6,
1028+ arg7,
1029+ arg8,
1030+ arg9,
1031+ )
1032+ })
1033+ return _c
1034+}
1035+
1036+func (_c *GitHubReleaseAPI_UploadAsset_Call) Return(bytes []byte, err error) *GitHubReleaseAPI_UploadAsset_Call {
1037+ _c.Call.Return(bytes, err)
1038+ return _c
1039+}
1040+
1041+func (_c *GitHubReleaseAPI_UploadAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, owner string, repo string, id int64, name string, label string, contentType string, size int64, r io.Reader) ([]byte, error)) *GitHubReleaseAPI_UploadAsset_Call {
1042+ _c.Call.Return(run)
1043+ return _c
1044+}
A internal/ports/mocks/MultipartUpload.go +273 -0
@@ -0,0 +1,273 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+ "io"
10+
11+ "bnema.dev/ermite/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewMultipartUpload creates a new instance of MultipartUpload. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewMultipartUpload(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *MultipartUpload {
21+ if helper, ok := t.(interface{ Helper() }); ok {
22+ helper.Helper()
23+ }
24+
25+ mock := &MultipartUpload{}
26+ mock.Mock.Test(t)
27+
28+ t.Cleanup(func() {
29+ if helper, ok := t.(interface{ Helper() }); ok {
30+ helper.Helper()
31+ }
32+ mock.AssertExpectations(t)
33+ })
34+
35+ return mock
36+}
37+
38+// MultipartUpload is an autogenerated mock type for the MultipartUpload type
39+type MultipartUpload struct {
40+ mock.Mock
41+}
42+
43+type MultipartUpload_Expecter struct {
44+ mock *mock.Mock
45+}
46+
47+func (_m *MultipartUpload) EXPECT() *MultipartUpload_Expecter {
48+ return &MultipartUpload_Expecter{mock: &_m.Mock}
49+}
50+
51+// Abort provides a mock function for the type MultipartUpload
52+func (_mock *MultipartUpload) Abort(ctx context.Context) error {
53+ ret := _mock.Called(ctx)
54+
55+ if len(ret) == 0 {
56+ panic("no return value specified for Abort")
57+ }
58+
59+ var r0 error
60+ if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {
61+ r0 = returnFunc(ctx)
62+ } else {
63+ r0 = ret.Error(0)
64+ }
65+ return r0
66+}
67+
68+// MultipartUpload_Abort_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Abort'
69+type MultipartUpload_Abort_Call struct {
70+ *mock.Call
71+}
72+
73+// Abort is a helper method to define mock.On call
74+// - ctx context.Context
75+func (_e *MultipartUpload_Expecter) Abort(ctx any) *MultipartUpload_Abort_Call {
76+ return &MultipartUpload_Abort_Call{Call: _e.mock.On("Abort", ctx)}
77+}
78+
79+func (_c *MultipartUpload_Abort_Call) Run(run func(ctx context.Context)) *MultipartUpload_Abort_Call {
80+ _c.Call.Run(func(args mock.Arguments) {
81+ var arg0 context.Context
82+ if args[0] != nil {
83+ arg0 = args[0].(context.Context)
84+ }
85+ run(
86+ arg0,
87+ )
88+ })
89+ return _c
90+}
91+
92+func (_c *MultipartUpload_Abort_Call) Return(err error) *MultipartUpload_Abort_Call {
93+ _c.Call.Return(err)
94+ return _c
95+}
96+
97+func (_c *MultipartUpload_Abort_Call) RunAndReturn(run func(ctx context.Context) error) *MultipartUpload_Abort_Call {
98+ _c.Call.Return(run)
99+ return _c
100+}
101+
102+// Complete provides a mock function for the type MultipartUpload
103+func (_mock *MultipartUpload) Complete(ctx context.Context) (ports.BlobInfo, error) {
104+ ret := _mock.Called(ctx)
105+
106+ if len(ret) == 0 {
107+ panic("no return value specified for Complete")
108+ }
109+
110+ var r0 ports.BlobInfo
111+ var r1 error
112+ if returnFunc, ok := ret.Get(0).(func(context.Context) (ports.BlobInfo, error)); ok {
113+ return returnFunc(ctx)
114+ }
115+ if returnFunc, ok := ret.Get(0).(func(context.Context) ports.BlobInfo); ok {
116+ r0 = returnFunc(ctx)
117+ } else {
118+ r0 = ret.Get(0).(ports.BlobInfo)
119+ }
120+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
121+ r1 = returnFunc(ctx)
122+ } else {
123+ r1 = ret.Error(1)
124+ }
125+ return r0, r1
126+}
127+
128+// MultipartUpload_Complete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Complete'
129+type MultipartUpload_Complete_Call struct {
130+ *mock.Call
131+}
132+
133+// Complete is a helper method to define mock.On call
134+// - ctx context.Context
135+func (_e *MultipartUpload_Expecter) Complete(ctx any) *MultipartUpload_Complete_Call {
136+ return &MultipartUpload_Complete_Call{Call: _e.mock.On("Complete", ctx)}
137+}
138+
139+func (_c *MultipartUpload_Complete_Call) Run(run func(ctx context.Context)) *MultipartUpload_Complete_Call {
140+ _c.Call.Run(func(args mock.Arguments) {
141+ var arg0 context.Context
142+ if args[0] != nil {
143+ arg0 = args[0].(context.Context)
144+ }
145+ run(
146+ arg0,
147+ )
148+ })
149+ return _c
150+}
151+
152+func (_c *MultipartUpload_Complete_Call) Return(blobInfo ports.BlobInfo, err error) *MultipartUpload_Complete_Call {
153+ _c.Call.Return(blobInfo, err)
154+ return _c
155+}
156+
157+func (_c *MultipartUpload_Complete_Call) RunAndReturn(run func(ctx context.Context) (ports.BlobInfo, error)) *MultipartUpload_Complete_Call {
158+ _c.Call.Return(run)
159+ return _c
160+}
161+
162+// ID provides a mock function for the type MultipartUpload
163+func (_mock *MultipartUpload) ID() string {
164+ ret := _mock.Called()
165+
166+ if len(ret) == 0 {
167+ panic("no return value specified for ID")
168+ }
169+
170+ var r0 string
171+ if returnFunc, ok := ret.Get(0).(func() string); ok {
172+ r0 = returnFunc()
173+ } else {
174+ r0 = ret.Get(0).(string)
175+ }
176+ return r0
177+}
178+
179+// MultipartUpload_ID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ID'
180+type MultipartUpload_ID_Call struct {
181+ *mock.Call
182+}
183+
184+// ID is a helper method to define mock.On call
185+func (_e *MultipartUpload_Expecter) ID() *MultipartUpload_ID_Call {
186+ return &MultipartUpload_ID_Call{Call: _e.mock.On("ID")}
187+}
188+
189+func (_c *MultipartUpload_ID_Call) Run(run func()) *MultipartUpload_ID_Call {
190+ _c.Call.Run(func(args mock.Arguments) {
191+ run()
192+ })
193+ return _c
194+}
195+
196+func (_c *MultipartUpload_ID_Call) Return(s string) *MultipartUpload_ID_Call {
197+ _c.Call.Return(s)
198+ return _c
199+}
200+
201+func (_c *MultipartUpload_ID_Call) RunAndReturn(run func() string) *MultipartUpload_ID_Call {
202+ _c.Call.Return(run)
203+ return _c
204+}
205+
206+// UploadPart provides a mock function for the type MultipartUpload
207+func (_mock *MultipartUpload) UploadPart(ctx context.Context, n int, r io.Reader, size int64) error {
208+ ret := _mock.Called(ctx, n, r, size)
209+
210+ if len(ret) == 0 {
211+ panic("no return value specified for UploadPart")
212+ }
213+
214+ var r0 error
215+ if returnFunc, ok := ret.Get(0).(func(context.Context, int, io.Reader, int64) error); ok {
216+ r0 = returnFunc(ctx, n, r, size)
217+ } else {
218+ r0 = ret.Error(0)
219+ }
220+ return r0
221+}
222+
223+// MultipartUpload_UploadPart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadPart'
224+type MultipartUpload_UploadPart_Call struct {
225+ *mock.Call
226+}
227+
228+// UploadPart is a helper method to define mock.On call
229+// - ctx context.Context
230+// - n int
231+// - r io.Reader
232+// - size int64
233+func (_e *MultipartUpload_Expecter) UploadPart(ctx any, n any, r any, size any) *MultipartUpload_UploadPart_Call {
234+ return &MultipartUpload_UploadPart_Call{Call: _e.mock.On("UploadPart", ctx, n, r, size)}
235+}
236+
237+func (_c *MultipartUpload_UploadPart_Call) Run(run func(ctx context.Context, n int, r io.Reader, size int64)) *MultipartUpload_UploadPart_Call {
238+ _c.Call.Run(func(args mock.Arguments) {
239+ var arg0 context.Context
240+ if args[0] != nil {
241+ arg0 = args[0].(context.Context)
242+ }
243+ var arg1 int
244+ if args[1] != nil {
245+ arg1 = args[1].(int)
246+ }
247+ var arg2 io.Reader
248+ if args[2] != nil {
249+ arg2 = args[2].(io.Reader)
250+ }
251+ var arg3 int64
252+ if args[3] != nil {
253+ arg3 = args[3].(int64)
254+ }
255+ run(
256+ arg0,
257+ arg1,
258+ arg2,
259+ arg3,
260+ )
261+ })
262+ return _c
263+}
264+
265+func (_c *MultipartUpload_UploadPart_Call) Return(err error) *MultipartUpload_UploadPart_Call {
266+ _c.Call.Return(err)
267+ return _c
268+}
269+
270+func (_c *MultipartUpload_UploadPart_Call) RunAndReturn(run func(ctx context.Context, n int, r io.Reader, size int64) error) *MultipartUpload_UploadPart_Call {
271+ _c.Call.Return(run)
272+ return _c
273+}
A internal/ports/mocks/ReleaseRepoCleaner.go +168 -0
@@ -0,0 +1,168 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ mock "github.com/stretchr/testify/mock"
12+)
13+
14+// NewReleaseRepoCleaner creates a new instance of ReleaseRepoCleaner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15+// The first argument is typically a *testing.T value.
16+func NewReleaseRepoCleaner(t interface {
17+ mock.TestingT
18+ Cleanup(func())
19+}) *ReleaseRepoCleaner {
20+ if helper, ok := t.(interface{ Helper() }); ok {
21+ helper.Helper()
22+ }
23+
24+ mock := &ReleaseRepoCleaner{}
25+ mock.Mock.Test(t)
26+
27+ t.Cleanup(func() {
28+ if helper, ok := t.(interface{ Helper() }); ok {
29+ helper.Helper()
30+ }
31+ mock.AssertExpectations(t)
32+ })
33+
34+ return mock
35+}
36+
37+// ReleaseRepoCleaner is an autogenerated mock type for the ReleaseRepoCleaner type
38+type ReleaseRepoCleaner struct {
39+ mock.Mock
40+}
41+
42+type ReleaseRepoCleaner_Expecter struct {
43+ mock *mock.Mock
44+}
45+
46+func (_m *ReleaseRepoCleaner) EXPECT() *ReleaseRepoCleaner_Expecter {
47+ return &ReleaseRepoCleaner_Expecter{mock: &_m.Mock}
48+}
49+
50+// AssetsOf provides a mock function for the type ReleaseRepoCleaner
51+func (_mock *ReleaseRepoCleaner) AssetsOf(ctx context.Context, repo domain.RepoName) ([]domain.ReleaseAsset, error) {
52+ ret := _mock.Called(ctx, repo)
53+
54+ if len(ret) == 0 {
55+ panic("no return value specified for AssetsOf")
56+ }
57+
58+ var r0 []domain.ReleaseAsset
59+ var r1 error
60+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) ([]domain.ReleaseAsset, error)); ok {
61+ return returnFunc(ctx, repo)
62+ }
63+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) []domain.ReleaseAsset); ok {
64+ r0 = returnFunc(ctx, repo)
65+ } else {
66+ if ret.Get(0) != nil {
67+ r0 = ret.Get(0).([]domain.ReleaseAsset)
68+ }
69+ }
70+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) error); ok {
71+ r1 = returnFunc(ctx, repo)
72+ } else {
73+ r1 = ret.Error(1)
74+ }
75+ return r0, r1
76+}
77+
78+// ReleaseRepoCleaner_AssetsOf_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AssetsOf'
79+type ReleaseRepoCleaner_AssetsOf_Call struct {
80+ *mock.Call
81+}
82+
83+// AssetsOf is a helper method to define mock.On call
84+// - ctx context.Context
85+// - repo domain.RepoName
86+func (_e *ReleaseRepoCleaner_Expecter) AssetsOf(ctx any, repo any) *ReleaseRepoCleaner_AssetsOf_Call {
87+ return &ReleaseRepoCleaner_AssetsOf_Call{Call: _e.mock.On("AssetsOf", ctx, repo)}
88+}
89+
90+func (_c *ReleaseRepoCleaner_AssetsOf_Call) Run(run func(ctx context.Context, repo domain.RepoName)) *ReleaseRepoCleaner_AssetsOf_Call {
91+ _c.Call.Run(func(args mock.Arguments) {
92+ var arg0 context.Context
93+ if args[0] != nil {
94+ arg0 = args[0].(context.Context)
95+ }
96+ var arg1 domain.RepoName
97+ if args[1] != nil {
98+ arg1 = args[1].(domain.RepoName)
99+ }
100+ run(
101+ arg0,
102+ arg1,
103+ )
104+ })
105+ return _c
106+}
107+
108+func (_c *ReleaseRepoCleaner_AssetsOf_Call) Return(releaseAssets []domain.ReleaseAsset, err error) *ReleaseRepoCleaner_AssetsOf_Call {
109+ _c.Call.Return(releaseAssets, err)
110+ return _c
111+}
112+
113+func (_c *ReleaseRepoCleaner_AssetsOf_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName) ([]domain.ReleaseAsset, error)) *ReleaseRepoCleaner_AssetsOf_Call {
114+ _c.Call.Return(run)
115+ return _c
116+}
117+
118+// OnRepoDeleted provides a mock function for the type ReleaseRepoCleaner
119+func (_mock *ReleaseRepoCleaner) OnRepoDeleted(ctx context.Context, repo domain.RepoName, assets []domain.ReleaseAsset) {
120+ _mock.Called(ctx, repo, assets)
121+ return
122+}
123+
124+// ReleaseRepoCleaner_OnRepoDeleted_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnRepoDeleted'
125+type ReleaseRepoCleaner_OnRepoDeleted_Call struct {
126+ *mock.Call
127+}
128+
129+// OnRepoDeleted is a helper method to define mock.On call
130+// - ctx context.Context
131+// - repo domain.RepoName
132+// - assets []domain.ReleaseAsset
133+func (_e *ReleaseRepoCleaner_Expecter) OnRepoDeleted(ctx any, repo any, assets any) *ReleaseRepoCleaner_OnRepoDeleted_Call {
134+ return &ReleaseRepoCleaner_OnRepoDeleted_Call{Call: _e.mock.On("OnRepoDeleted", ctx, repo, assets)}
135+}
136+
137+func (_c *ReleaseRepoCleaner_OnRepoDeleted_Call) Run(run func(ctx context.Context, repo domain.RepoName, assets []domain.ReleaseAsset)) *ReleaseRepoCleaner_OnRepoDeleted_Call {
138+ _c.Call.Run(func(args mock.Arguments) {
139+ var arg0 context.Context
140+ if args[0] != nil {
141+ arg0 = args[0].(context.Context)
142+ }
143+ var arg1 domain.RepoName
144+ if args[1] != nil {
145+ arg1 = args[1].(domain.RepoName)
146+ }
147+ var arg2 []domain.ReleaseAsset
148+ if args[2] != nil {
149+ arg2 = args[2].([]domain.ReleaseAsset)
150+ }
151+ run(
152+ arg0,
153+ arg1,
154+ arg2,
155+ )
156+ })
157+ return _c
158+}
159+
160+func (_c *ReleaseRepoCleaner_OnRepoDeleted_Call) Return() *ReleaseRepoCleaner_OnRepoDeleted_Call {
161+ _c.Call.Return()
162+ return _c
163+}
164+
165+func (_c *ReleaseRepoCleaner_OnRepoDeleted_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, assets []domain.ReleaseAsset)) *ReleaseRepoCleaner_OnRepoDeleted_Call {
166+ _c.Run(run)
167+ return _c
168+}
A internal/ports/mocks/ReleaseService.go +997 -0
@@ -0,0 +1,997 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+ "io"
10+
11+ "bnema.dev/ermite/internal/domain"
12+ "bnema.dev/ermite/internal/ports"
13+ mock "github.com/stretchr/testify/mock"
14+)
15+
16+// NewReleaseService creates a new instance of ReleaseService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
17+// The first argument is typically a *testing.T value.
18+func NewReleaseService(t interface {
19+ mock.TestingT
20+ Cleanup(func())
21+}) *ReleaseService {
22+ if helper, ok := t.(interface{ Helper() }); ok {
23+ helper.Helper()
24+ }
25+
26+ mock := &ReleaseService{}
27+ mock.Mock.Test(t)
28+
29+ t.Cleanup(func() {
30+ if helper, ok := t.(interface{ Helper() }); ok {
31+ helper.Helper()
32+ }
33+ mock.AssertExpectations(t)
34+ })
35+
36+ return mock
37+}
38+
39+// ReleaseService is an autogenerated mock type for the ReleaseService type
40+type ReleaseService struct {
41+ mock.Mock
42+}
43+
44+type ReleaseService_Expecter struct {
45+ mock *mock.Mock
46+}
47+
48+func (_m *ReleaseService) EXPECT() *ReleaseService_Expecter {
49+ return &ReleaseService_Expecter{mock: &_m.Mock}
50+}
51+
52+// CreateRelease provides a mock function for the type ReleaseService
53+func (_mock *ReleaseService) CreateRelease(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.CreateReleaseParams) (domain.Release, error) {
54+ ret := _mock.Called(ctx, p, repo, params)
55+
56+ if len(ret) == 0 {
57+ panic("no return value specified for CreateRelease")
58+ }
59+
60+ var r0 domain.Release
61+ var r1 error
62+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.CreateReleaseParams) (domain.Release, error)); ok {
63+ return returnFunc(ctx, p, repo, params)
64+ }
65+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.CreateReleaseParams) domain.Release); ok {
66+ r0 = returnFunc(ctx, p, repo, params)
67+ } else {
68+ r0 = ret.Get(0).(domain.Release)
69+ }
70+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.CreateReleaseParams) error); ok {
71+ r1 = returnFunc(ctx, p, repo, params)
72+ } else {
73+ r1 = ret.Error(1)
74+ }
75+ return r0, r1
76+}
77+
78+// ReleaseService_CreateRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRelease'
79+type ReleaseService_CreateRelease_Call struct {
80+ *mock.Call
81+}
82+
83+// CreateRelease is a helper method to define mock.On call
84+// - ctx context.Context
85+// - p domain.Principal
86+// - repo domain.RepoName
87+// - params ports.CreateReleaseParams
88+func (_e *ReleaseService_Expecter) CreateRelease(ctx any, p any, repo any, params any) *ReleaseService_CreateRelease_Call {
89+ return &ReleaseService_CreateRelease_Call{Call: _e.mock.On("CreateRelease", ctx, p, repo, params)}
90+}
91+
92+func (_c *ReleaseService_CreateRelease_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.CreateReleaseParams)) *ReleaseService_CreateRelease_Call {
93+ _c.Call.Run(func(args mock.Arguments) {
94+ var arg0 context.Context
95+ if args[0] != nil {
96+ arg0 = args[0].(context.Context)
97+ }
98+ var arg1 domain.Principal
99+ if args[1] != nil {
100+ arg1 = args[1].(domain.Principal)
101+ }
102+ var arg2 domain.RepoName
103+ if args[2] != nil {
104+ arg2 = args[2].(domain.RepoName)
105+ }
106+ var arg3 ports.CreateReleaseParams
107+ if args[3] != nil {
108+ arg3 = args[3].(ports.CreateReleaseParams)
109+ }
110+ run(
111+ arg0,
112+ arg1,
113+ arg2,
114+ arg3,
115+ )
116+ })
117+ return _c
118+}
119+
120+func (_c *ReleaseService_CreateRelease_Call) Return(release domain.Release, err error) *ReleaseService_CreateRelease_Call {
121+ _c.Call.Return(release, err)
122+ return _c
123+}
124+
125+func (_c *ReleaseService_CreateRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.CreateReleaseParams) (domain.Release, error)) *ReleaseService_CreateRelease_Call {
126+ _c.Call.Return(run)
127+ return _c
128+}
129+
130+// DeleteAsset provides a mock function for the type ReleaseService
131+func (_mock *ReleaseService) DeleteAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, assetID int64) error {
132+ ret := _mock.Called(ctx, p, repo, assetID)
133+
134+ if len(ret) == 0 {
135+ panic("no return value specified for DeleteAsset")
136+ }
137+
138+ var r0 error
139+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) error); ok {
140+ r0 = returnFunc(ctx, p, repo, assetID)
141+ } else {
142+ r0 = ret.Error(0)
143+ }
144+ return r0
145+}
146+
147+// ReleaseService_DeleteAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAsset'
148+type ReleaseService_DeleteAsset_Call struct {
149+ *mock.Call
150+}
151+
152+// DeleteAsset is a helper method to define mock.On call
153+// - ctx context.Context
154+// - p domain.Principal
155+// - repo domain.RepoName
156+// - assetID int64
157+func (_e *ReleaseService_Expecter) DeleteAsset(ctx any, p any, repo any, assetID any) *ReleaseService_DeleteAsset_Call {
158+ return &ReleaseService_DeleteAsset_Call{Call: _e.mock.On("DeleteAsset", ctx, p, repo, assetID)}
159+}
160+
161+func (_c *ReleaseService_DeleteAsset_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, assetID int64)) *ReleaseService_DeleteAsset_Call {
162+ _c.Call.Run(func(args mock.Arguments) {
163+ var arg0 context.Context
164+ if args[0] != nil {
165+ arg0 = args[0].(context.Context)
166+ }
167+ var arg1 domain.Principal
168+ if args[1] != nil {
169+ arg1 = args[1].(domain.Principal)
170+ }
171+ var arg2 domain.RepoName
172+ if args[2] != nil {
173+ arg2 = args[2].(domain.RepoName)
174+ }
175+ var arg3 int64
176+ if args[3] != nil {
177+ arg3 = args[3].(int64)
178+ }
179+ run(
180+ arg0,
181+ arg1,
182+ arg2,
183+ arg3,
184+ )
185+ })
186+ return _c
187+}
188+
189+func (_c *ReleaseService_DeleteAsset_Call) Return(err error) *ReleaseService_DeleteAsset_Call {
190+ _c.Call.Return(err)
191+ return _c
192+}
193+
194+func (_c *ReleaseService_DeleteAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, assetID int64) error) *ReleaseService_DeleteAsset_Call {
195+ _c.Call.Return(run)
196+ return _c
197+}
198+
199+// DeleteRelease provides a mock function for the type ReleaseService
200+func (_mock *ReleaseService) DeleteRelease(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64) error {
201+ ret := _mock.Called(ctx, p, repo, id)
202+
203+ if len(ret) == 0 {
204+ panic("no return value specified for DeleteRelease")
205+ }
206+
207+ var r0 error
208+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) error); ok {
209+ r0 = returnFunc(ctx, p, repo, id)
210+ } else {
211+ r0 = ret.Error(0)
212+ }
213+ return r0
214+}
215+
216+// ReleaseService_DeleteRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteRelease'
217+type ReleaseService_DeleteRelease_Call struct {
218+ *mock.Call
219+}
220+
221+// DeleteRelease is a helper method to define mock.On call
222+// - ctx context.Context
223+// - p domain.Principal
224+// - repo domain.RepoName
225+// - id int64
226+func (_e *ReleaseService_Expecter) DeleteRelease(ctx any, p any, repo any, id any) *ReleaseService_DeleteRelease_Call {
227+ return &ReleaseService_DeleteRelease_Call{Call: _e.mock.On("DeleteRelease", ctx, p, repo, id)}
228+}
229+
230+func (_c *ReleaseService_DeleteRelease_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64)) *ReleaseService_DeleteRelease_Call {
231+ _c.Call.Run(func(args mock.Arguments) {
232+ var arg0 context.Context
233+ if args[0] != nil {
234+ arg0 = args[0].(context.Context)
235+ }
236+ var arg1 domain.Principal
237+ if args[1] != nil {
238+ arg1 = args[1].(domain.Principal)
239+ }
240+ var arg2 domain.RepoName
241+ if args[2] != nil {
242+ arg2 = args[2].(domain.RepoName)
243+ }
244+ var arg3 int64
245+ if args[3] != nil {
246+ arg3 = args[3].(int64)
247+ }
248+ run(
249+ arg0,
250+ arg1,
251+ arg2,
252+ arg3,
253+ )
254+ })
255+ return _c
256+}
257+
258+func (_c *ReleaseService_DeleteRelease_Call) Return(err error) *ReleaseService_DeleteRelease_Call {
259+ _c.Call.Return(err)
260+ return _c
261+}
262+
263+func (_c *ReleaseService_DeleteRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64) error) *ReleaseService_DeleteRelease_Call {
264+ _c.Call.Return(run)
265+ return _c
266+}
267+
268+// DownloadAsset provides a mock function for the type ReleaseService
269+func (_mock *ReleaseService) DownloadAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef, offset int64, length int64, send func(ports.AssetChunk) error) error {
270+ ret := _mock.Called(ctx, p, repo, ref, offset, length, send)
271+
272+ if len(ret) == 0 {
273+ panic("no return value specified for DownloadAsset")
274+ }
275+
276+ var r0 error
277+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.AssetRef, int64, int64, func(ports.AssetChunk) error) error); ok {
278+ r0 = returnFunc(ctx, p, repo, ref, offset, length, send)
279+ } else {
280+ r0 = ret.Error(0)
281+ }
282+ return r0
283+}
284+
285+// ReleaseService_DownloadAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DownloadAsset'
286+type ReleaseService_DownloadAsset_Call struct {
287+ *mock.Call
288+}
289+
290+// DownloadAsset is a helper method to define mock.On call
291+// - ctx context.Context
292+// - p domain.Principal
293+// - repo domain.RepoName
294+// - ref ports.AssetRef
295+// - offset int64
296+// - length int64
297+// - send func(ports.AssetChunk) error
298+func (_e *ReleaseService_Expecter) DownloadAsset(ctx any, p any, repo any, ref any, offset any, length any, send any) *ReleaseService_DownloadAsset_Call {
299+ return &ReleaseService_DownloadAsset_Call{Call: _e.mock.On("DownloadAsset", ctx, p, repo, ref, offset, length, send)}
300+}
301+
302+func (_c *ReleaseService_DownloadAsset_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef, offset int64, length int64, send func(ports.AssetChunk) error)) *ReleaseService_DownloadAsset_Call {
303+ _c.Call.Run(func(args mock.Arguments) {
304+ var arg0 context.Context
305+ if args[0] != nil {
306+ arg0 = args[0].(context.Context)
307+ }
308+ var arg1 domain.Principal
309+ if args[1] != nil {
310+ arg1 = args[1].(domain.Principal)
311+ }
312+ var arg2 domain.RepoName
313+ if args[2] != nil {
314+ arg2 = args[2].(domain.RepoName)
315+ }
316+ var arg3 ports.AssetRef
317+ if args[3] != nil {
318+ arg3 = args[3].(ports.AssetRef)
319+ }
320+ var arg4 int64
321+ if args[4] != nil {
322+ arg4 = args[4].(int64)
323+ }
324+ var arg5 int64
325+ if args[5] != nil {
326+ arg5 = args[5].(int64)
327+ }
328+ var arg6 func(ports.AssetChunk) error
329+ if args[6] != nil {
330+ arg6 = args[6].(func(ports.AssetChunk) error)
331+ }
332+ run(
333+ arg0,
334+ arg1,
335+ arg2,
336+ arg3,
337+ arg4,
338+ arg5,
339+ arg6,
340+ )
341+ })
342+ return _c
343+}
344+
345+func (_c *ReleaseService_DownloadAsset_Call) Return(err error) *ReleaseService_DownloadAsset_Call {
346+ _c.Call.Return(err)
347+ return _c
348+}
349+
350+func (_c *ReleaseService_DownloadAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef, offset int64, length int64, send func(ports.AssetChunk) error) error) *ReleaseService_DownloadAsset_Call {
351+ _c.Call.Return(run)
352+ return _c
353+}
354+
355+// GetAsset provides a mock function for the type ReleaseService
356+func (_mock *ReleaseService) GetAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef) (domain.ReleaseAsset, error) {
357+ ret := _mock.Called(ctx, p, repo, ref)
358+
359+ if len(ret) == 0 {
360+ panic("no return value specified for GetAsset")
361+ }
362+
363+ var r0 domain.ReleaseAsset
364+ var r1 error
365+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.AssetRef) (domain.ReleaseAsset, error)); ok {
366+ return returnFunc(ctx, p, repo, ref)
367+ }
368+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.AssetRef) domain.ReleaseAsset); ok {
369+ r0 = returnFunc(ctx, p, repo, ref)
370+ } else {
371+ r0 = ret.Get(0).(domain.ReleaseAsset)
372+ }
373+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.AssetRef) error); ok {
374+ r1 = returnFunc(ctx, p, repo, ref)
375+ } else {
376+ r1 = ret.Error(1)
377+ }
378+ return r0, r1
379+}
380+
381+// ReleaseService_GetAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAsset'
382+type ReleaseService_GetAsset_Call struct {
383+ *mock.Call
384+}
385+
386+// GetAsset is a helper method to define mock.On call
387+// - ctx context.Context
388+// - p domain.Principal
389+// - repo domain.RepoName
390+// - ref ports.AssetRef
391+func (_e *ReleaseService_Expecter) GetAsset(ctx any, p any, repo any, ref any) *ReleaseService_GetAsset_Call {
392+ return &ReleaseService_GetAsset_Call{Call: _e.mock.On("GetAsset", ctx, p, repo, ref)}
393+}
394+
395+func (_c *ReleaseService_GetAsset_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef)) *ReleaseService_GetAsset_Call {
396+ _c.Call.Run(func(args mock.Arguments) {
397+ var arg0 context.Context
398+ if args[0] != nil {
399+ arg0 = args[0].(context.Context)
400+ }
401+ var arg1 domain.Principal
402+ if args[1] != nil {
403+ arg1 = args[1].(domain.Principal)
404+ }
405+ var arg2 domain.RepoName
406+ if args[2] != nil {
407+ arg2 = args[2].(domain.RepoName)
408+ }
409+ var arg3 ports.AssetRef
410+ if args[3] != nil {
411+ arg3 = args[3].(ports.AssetRef)
412+ }
413+ run(
414+ arg0,
415+ arg1,
416+ arg2,
417+ arg3,
418+ )
419+ })
420+ return _c
421+}
422+
423+func (_c *ReleaseService_GetAsset_Call) Return(releaseAsset domain.ReleaseAsset, err error) *ReleaseService_GetAsset_Call {
424+ _c.Call.Return(releaseAsset, err)
425+ return _c
426+}
427+
428+func (_c *ReleaseService_GetAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.AssetRef) (domain.ReleaseAsset, error)) *ReleaseService_GetAsset_Call {
429+ _c.Call.Return(run)
430+ return _c
431+}
432+
433+// GetRelease provides a mock function for the type ReleaseService
434+func (_mock *ReleaseService) GetRelease(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.ReleaseRef) (domain.Release, error) {
435+ ret := _mock.Called(ctx, p, repo, ref)
436+
437+ if len(ret) == 0 {
438+ panic("no return value specified for GetRelease")
439+ }
440+
441+ var r0 domain.Release
442+ var r1 error
443+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ReleaseRef) (domain.Release, error)); ok {
444+ return returnFunc(ctx, p, repo, ref)
445+ }
446+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ReleaseRef) domain.Release); ok {
447+ r0 = returnFunc(ctx, p, repo, ref)
448+ } else {
449+ r0 = ret.Get(0).(domain.Release)
450+ }
451+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.ReleaseRef) error); ok {
452+ r1 = returnFunc(ctx, p, repo, ref)
453+ } else {
454+ r1 = ret.Error(1)
455+ }
456+ return r0, r1
457+}
458+
459+// ReleaseService_GetRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRelease'
460+type ReleaseService_GetRelease_Call struct {
461+ *mock.Call
462+}
463+
464+// GetRelease is a helper method to define mock.On call
465+// - ctx context.Context
466+// - p domain.Principal
467+// - repo domain.RepoName
468+// - ref ports.ReleaseRef
469+func (_e *ReleaseService_Expecter) GetRelease(ctx any, p any, repo any, ref any) *ReleaseService_GetRelease_Call {
470+ return &ReleaseService_GetRelease_Call{Call: _e.mock.On("GetRelease", ctx, p, repo, ref)}
471+}
472+
473+func (_c *ReleaseService_GetRelease_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.ReleaseRef)) *ReleaseService_GetRelease_Call {
474+ _c.Call.Run(func(args mock.Arguments) {
475+ var arg0 context.Context
476+ if args[0] != nil {
477+ arg0 = args[0].(context.Context)
478+ }
479+ var arg1 domain.Principal
480+ if args[1] != nil {
481+ arg1 = args[1].(domain.Principal)
482+ }
483+ var arg2 domain.RepoName
484+ if args[2] != nil {
485+ arg2 = args[2].(domain.RepoName)
486+ }
487+ var arg3 ports.ReleaseRef
488+ if args[3] != nil {
489+ arg3 = args[3].(ports.ReleaseRef)
490+ }
491+ run(
492+ arg0,
493+ arg1,
494+ arg2,
495+ arg3,
496+ )
497+ })
498+ return _c
499+}
500+
501+func (_c *ReleaseService_GetRelease_Call) Return(release domain.Release, err error) *ReleaseService_GetRelease_Call {
502+ _c.Call.Return(release, err)
503+ return _c
504+}
505+
506+func (_c *ReleaseService_GetRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, ref ports.ReleaseRef) (domain.Release, error)) *ReleaseService_GetRelease_Call {
507+ _c.Call.Return(run)
508+ return _c
509+}
510+
511+// ListAssets provides a mock function for the type ReleaseService
512+func (_mock *ReleaseService) ListAssets(ctx context.Context, p domain.Principal, repo domain.RepoName, releaseID int64) ([]domain.ReleaseAsset, error) {
513+ ret := _mock.Called(ctx, p, repo, releaseID)
514+
515+ if len(ret) == 0 {
516+ panic("no return value specified for ListAssets")
517+ }
518+
519+ var r0 []domain.ReleaseAsset
520+ var r1 error
521+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) ([]domain.ReleaseAsset, error)); ok {
522+ return returnFunc(ctx, p, repo, releaseID)
523+ }
524+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) []domain.ReleaseAsset); ok {
525+ r0 = returnFunc(ctx, p, repo, releaseID)
526+ } else {
527+ if ret.Get(0) != nil {
528+ r0 = ret.Get(0).([]domain.ReleaseAsset)
529+ }
530+ }
531+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64) error); ok {
532+ r1 = returnFunc(ctx, p, repo, releaseID)
533+ } else {
534+ r1 = ret.Error(1)
535+ }
536+ return r0, r1
537+}
538+
539+// ReleaseService_ListAssets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAssets'
540+type ReleaseService_ListAssets_Call struct {
541+ *mock.Call
542+}
543+
544+// ListAssets is a helper method to define mock.On call
545+// - ctx context.Context
546+// - p domain.Principal
547+// - repo domain.RepoName
548+// - releaseID int64
549+func (_e *ReleaseService_Expecter) ListAssets(ctx any, p any, repo any, releaseID any) *ReleaseService_ListAssets_Call {
550+ return &ReleaseService_ListAssets_Call{Call: _e.mock.On("ListAssets", ctx, p, repo, releaseID)}
551+}
552+
553+func (_c *ReleaseService_ListAssets_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, releaseID int64)) *ReleaseService_ListAssets_Call {
554+ _c.Call.Run(func(args mock.Arguments) {
555+ var arg0 context.Context
556+ if args[0] != nil {
557+ arg0 = args[0].(context.Context)
558+ }
559+ var arg1 domain.Principal
560+ if args[1] != nil {
561+ arg1 = args[1].(domain.Principal)
562+ }
563+ var arg2 domain.RepoName
564+ if args[2] != nil {
565+ arg2 = args[2].(domain.RepoName)
566+ }
567+ var arg3 int64
568+ if args[3] != nil {
569+ arg3 = args[3].(int64)
570+ }
571+ run(
572+ arg0,
573+ arg1,
574+ arg2,
575+ arg3,
576+ )
577+ })
578+ return _c
579+}
580+
581+func (_c *ReleaseService_ListAssets_Call) Return(releaseAssets []domain.ReleaseAsset, err error) *ReleaseService_ListAssets_Call {
582+ _c.Call.Return(releaseAssets, err)
583+ return _c
584+}
585+
586+func (_c *ReleaseService_ListAssets_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, releaseID int64) ([]domain.ReleaseAsset, error)) *ReleaseService_ListAssets_Call {
587+ _c.Call.Return(run)
588+ return _c
589+}
590+
591+// ListReleases provides a mock function for the type ReleaseService
592+func (_mock *ReleaseService) ListReleases(ctx context.Context, p domain.Principal, repo domain.RepoName, pageSize int, pageToken string) (ports.ReleasePage, error) {
593+ ret := _mock.Called(ctx, p, repo, pageSize, pageToken)
594+
595+ if len(ret) == 0 {
596+ panic("no return value specified for ListReleases")
597+ }
598+
599+ var r0 ports.ReleasePage
600+ var r1 error
601+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int, string) (ports.ReleasePage, error)); ok {
602+ return returnFunc(ctx, p, repo, pageSize, pageToken)
603+ }
604+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int, string) ports.ReleasePage); ok {
605+ r0 = returnFunc(ctx, p, repo, pageSize, pageToken)
606+ } else {
607+ r0 = ret.Get(0).(ports.ReleasePage)
608+ }
609+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int, string) error); ok {
610+ r1 = returnFunc(ctx, p, repo, pageSize, pageToken)
611+ } else {
612+ r1 = ret.Error(1)
613+ }
614+ return r0, r1
615+}
616+
617+// ReleaseService_ListReleases_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReleases'
618+type ReleaseService_ListReleases_Call struct {
619+ *mock.Call
620+}
621+
622+// ListReleases is a helper method to define mock.On call
623+// - ctx context.Context
624+// - p domain.Principal
625+// - repo domain.RepoName
626+// - pageSize int
627+// - pageToken string
628+func (_e *ReleaseService_Expecter) ListReleases(ctx any, p any, repo any, pageSize any, pageToken any) *ReleaseService_ListReleases_Call {
629+ return &ReleaseService_ListReleases_Call{Call: _e.mock.On("ListReleases", ctx, p, repo, pageSize, pageToken)}
630+}
631+
632+func (_c *ReleaseService_ListReleases_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, pageSize int, pageToken string)) *ReleaseService_ListReleases_Call {
633+ _c.Call.Run(func(args mock.Arguments) {
634+ var arg0 context.Context
635+ if args[0] != nil {
636+ arg0 = args[0].(context.Context)
637+ }
638+ var arg1 domain.Principal
639+ if args[1] != nil {
640+ arg1 = args[1].(domain.Principal)
641+ }
642+ var arg2 domain.RepoName
643+ if args[2] != nil {
644+ arg2 = args[2].(domain.RepoName)
645+ }
646+ var arg3 int
647+ if args[3] != nil {
648+ arg3 = args[3].(int)
649+ }
650+ var arg4 string
651+ if args[4] != nil {
652+ arg4 = args[4].(string)
653+ }
654+ run(
655+ arg0,
656+ arg1,
657+ arg2,
658+ arg3,
659+ arg4,
660+ )
661+ })
662+ return _c
663+}
664+
665+func (_c *ReleaseService_ListReleases_Call) Return(releasePage ports.ReleasePage, err error) *ReleaseService_ListReleases_Call {
666+ _c.Call.Return(releasePage, err)
667+ return _c
668+}
669+
670+func (_c *ReleaseService_ListReleases_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, pageSize int, pageToken string) (ports.ReleasePage, error)) *ReleaseService_ListReleases_Call {
671+ _c.Call.Return(run)
672+ return _c
673+}
674+
675+// UpdateAsset provides a mock function for the type ReleaseService
676+func (_mock *ReleaseService) UpdateAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, assetID int64, params ports.UpdateAssetParams) (domain.ReleaseAsset, error) {
677+ ret := _mock.Called(ctx, p, repo, assetID, params)
678+
679+ if len(ret) == 0 {
680+ panic("no return value specified for UpdateAsset")
681+ }
682+
683+ var r0 domain.ReleaseAsset
684+ var r1 error
685+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.UpdateAssetParams) (domain.ReleaseAsset, error)); ok {
686+ return returnFunc(ctx, p, repo, assetID, params)
687+ }
688+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.UpdateAssetParams) domain.ReleaseAsset); ok {
689+ r0 = returnFunc(ctx, p, repo, assetID, params)
690+ } else {
691+ r0 = ret.Get(0).(domain.ReleaseAsset)
692+ }
693+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.UpdateAssetParams) error); ok {
694+ r1 = returnFunc(ctx, p, repo, assetID, params)
695+ } else {
696+ r1 = ret.Error(1)
697+ }
698+ return r0, r1
699+}
700+
701+// ReleaseService_UpdateAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAsset'
702+type ReleaseService_UpdateAsset_Call struct {
703+ *mock.Call
704+}
705+
706+// UpdateAsset is a helper method to define mock.On call
707+// - ctx context.Context
708+// - p domain.Principal
709+// - repo domain.RepoName
710+// - assetID int64
711+// - params ports.UpdateAssetParams
712+func (_e *ReleaseService_Expecter) UpdateAsset(ctx any, p any, repo any, assetID any, params any) *ReleaseService_UpdateAsset_Call {
713+ return &ReleaseService_UpdateAsset_Call{Call: _e.mock.On("UpdateAsset", ctx, p, repo, assetID, params)}
714+}
715+
716+func (_c *ReleaseService_UpdateAsset_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, assetID int64, params ports.UpdateAssetParams)) *ReleaseService_UpdateAsset_Call {
717+ _c.Call.Run(func(args mock.Arguments) {
718+ var arg0 context.Context
719+ if args[0] != nil {
720+ arg0 = args[0].(context.Context)
721+ }
722+ var arg1 domain.Principal
723+ if args[1] != nil {
724+ arg1 = args[1].(domain.Principal)
725+ }
726+ var arg2 domain.RepoName
727+ if args[2] != nil {
728+ arg2 = args[2].(domain.RepoName)
729+ }
730+ var arg3 int64
731+ if args[3] != nil {
732+ arg3 = args[3].(int64)
733+ }
734+ var arg4 ports.UpdateAssetParams
735+ if args[4] != nil {
736+ arg4 = args[4].(ports.UpdateAssetParams)
737+ }
738+ run(
739+ arg0,
740+ arg1,
741+ arg2,
742+ arg3,
743+ arg4,
744+ )
745+ })
746+ return _c
747+}
748+
749+func (_c *ReleaseService_UpdateAsset_Call) Return(releaseAsset domain.ReleaseAsset, err error) *ReleaseService_UpdateAsset_Call {
750+ _c.Call.Return(releaseAsset, err)
751+ return _c
752+}
753+
754+func (_c *ReleaseService_UpdateAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, assetID int64, params ports.UpdateAssetParams) (domain.ReleaseAsset, error)) *ReleaseService_UpdateAsset_Call {
755+ _c.Call.Return(run)
756+ return _c
757+}
758+
759+// UpdateRelease provides a mock function for the type ReleaseService
760+func (_mock *ReleaseService) UpdateRelease(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64, params ports.UpdateReleaseParams) (domain.Release, error) {
761+ ret := _mock.Called(ctx, p, repo, id, params)
762+
763+ if len(ret) == 0 {
764+ panic("no return value specified for UpdateRelease")
765+ }
766+
767+ var r0 domain.Release
768+ var r1 error
769+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.UpdateReleaseParams) (domain.Release, error)); ok {
770+ return returnFunc(ctx, p, repo, id, params)
771+ }
772+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.UpdateReleaseParams) domain.Release); ok {
773+ r0 = returnFunc(ctx, p, repo, id, params)
774+ } else {
775+ r0 = ret.Get(0).(domain.Release)
776+ }
777+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.UpdateReleaseParams) error); ok {
778+ r1 = returnFunc(ctx, p, repo, id, params)
779+ } else {
780+ r1 = ret.Error(1)
781+ }
782+ return r0, r1
783+}
784+
785+// ReleaseService_UpdateRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateRelease'
786+type ReleaseService_UpdateRelease_Call struct {
787+ *mock.Call
788+}
789+
790+// UpdateRelease is a helper method to define mock.On call
791+// - ctx context.Context
792+// - p domain.Principal
793+// - repo domain.RepoName
794+// - id int64
795+// - params ports.UpdateReleaseParams
796+func (_e *ReleaseService_Expecter) UpdateRelease(ctx any, p any, repo any, id any, params any) *ReleaseService_UpdateRelease_Call {
797+ return &ReleaseService_UpdateRelease_Call{Call: _e.mock.On("UpdateRelease", ctx, p, repo, id, params)}
798+}
799+
800+func (_c *ReleaseService_UpdateRelease_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64, params ports.UpdateReleaseParams)) *ReleaseService_UpdateRelease_Call {
801+ _c.Call.Run(func(args mock.Arguments) {
802+ var arg0 context.Context
803+ if args[0] != nil {
804+ arg0 = args[0].(context.Context)
805+ }
806+ var arg1 domain.Principal
807+ if args[1] != nil {
808+ arg1 = args[1].(domain.Principal)
809+ }
810+ var arg2 domain.RepoName
811+ if args[2] != nil {
812+ arg2 = args[2].(domain.RepoName)
813+ }
814+ var arg3 int64
815+ if args[3] != nil {
816+ arg3 = args[3].(int64)
817+ }
818+ var arg4 ports.UpdateReleaseParams
819+ if args[4] != nil {
820+ arg4 = args[4].(ports.UpdateReleaseParams)
821+ }
822+ run(
823+ arg0,
824+ arg1,
825+ arg2,
826+ arg3,
827+ arg4,
828+ )
829+ })
830+ return _c
831+}
832+
833+func (_c *ReleaseService_UpdateRelease_Call) Return(release domain.Release, err error) *ReleaseService_UpdateRelease_Call {
834+ _c.Call.Return(release, err)
835+ return _c
836+}
837+
838+func (_c *ReleaseService_UpdateRelease_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, id int64, params ports.UpdateReleaseParams) (domain.Release, error)) *ReleaseService_UpdateRelease_Call {
839+ _c.Call.Return(run)
840+ return _c
841+}
842+
843+// UploadAsset provides a mock function for the type ReleaseService
844+func (_mock *ReleaseService) UploadAsset(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.UploadAssetParams, body io.Reader) (domain.ReleaseAsset, error) {
845+ ret := _mock.Called(ctx, p, repo, params, body)
846+
847+ if len(ret) == 0 {
848+ panic("no return value specified for UploadAsset")
849+ }
850+
851+ var r0 domain.ReleaseAsset
852+ var r1 error
853+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.UploadAssetParams, io.Reader) (domain.ReleaseAsset, error)); ok {
854+ return returnFunc(ctx, p, repo, params, body)
855+ }
856+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.UploadAssetParams, io.Reader) domain.ReleaseAsset); ok {
857+ r0 = returnFunc(ctx, p, repo, params, body)
858+ } else {
859+ r0 = ret.Get(0).(domain.ReleaseAsset)
860+ }
861+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.UploadAssetParams, io.Reader) error); ok {
862+ r1 = returnFunc(ctx, p, repo, params, body)
863+ } else {
864+ r1 = ret.Error(1)
865+ }
866+ return r0, r1
867+}
868+
869+// ReleaseService_UploadAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UploadAsset'
870+type ReleaseService_UploadAsset_Call struct {
871+ *mock.Call
872+}
873+
874+// UploadAsset is a helper method to define mock.On call
875+// - ctx context.Context
876+// - p domain.Principal
877+// - repo domain.RepoName
878+// - params ports.UploadAssetParams
879+// - body io.Reader
880+func (_e *ReleaseService_Expecter) UploadAsset(ctx any, p any, repo any, params any, body any) *ReleaseService_UploadAsset_Call {
881+ return &ReleaseService_UploadAsset_Call{Call: _e.mock.On("UploadAsset", ctx, p, repo, params, body)}
882+}
883+
884+func (_c *ReleaseService_UploadAsset_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.UploadAssetParams, body io.Reader)) *ReleaseService_UploadAsset_Call {
885+ _c.Call.Run(func(args mock.Arguments) {
886+ var arg0 context.Context
887+ if args[0] != nil {
888+ arg0 = args[0].(context.Context)
889+ }
890+ var arg1 domain.Principal
891+ if args[1] != nil {
892+ arg1 = args[1].(domain.Principal)
893+ }
894+ var arg2 domain.RepoName
895+ if args[2] != nil {
896+ arg2 = args[2].(domain.RepoName)
897+ }
898+ var arg3 ports.UploadAssetParams
899+ if args[3] != nil {
900+ arg3 = args[3].(ports.UploadAssetParams)
901+ }
902+ var arg4 io.Reader
903+ if args[4] != nil {
904+ arg4 = args[4].(io.Reader)
905+ }
906+ run(
907+ arg0,
908+ arg1,
909+ arg2,
910+ arg3,
911+ arg4,
912+ )
913+ })
914+ return _c
915+}
916+
917+func (_c *ReleaseService_UploadAsset_Call) Return(releaseAsset domain.ReleaseAsset, err error) *ReleaseService_UploadAsset_Call {
918+ _c.Call.Return(releaseAsset, err)
919+ return _c
920+}
921+
922+func (_c *ReleaseService_UploadAsset_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.UploadAssetParams, body io.Reader) (domain.ReleaseAsset, error)) *ReleaseService_UploadAsset_Call {
923+ _c.Call.Return(run)
924+ return _c
925+}
926+
927+// Usage provides a mock function for the type ReleaseService
928+func (_mock *ReleaseService) Usage(ctx context.Context, p domain.Principal, repo domain.RepoName) (ports.ReleaseUsage, error) {
929+ ret := _mock.Called(ctx, p, repo)
930+
931+ if len(ret) == 0 {
932+ panic("no return value specified for Usage")
933+ }
934+
935+ var r0 ports.ReleaseUsage
936+ var r1 error
937+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName) (ports.ReleaseUsage, error)); ok {
938+ return returnFunc(ctx, p, repo)
939+ }
940+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName) ports.ReleaseUsage); ok {
941+ r0 = returnFunc(ctx, p, repo)
942+ } else {
943+ r0 = ret.Get(0).(ports.ReleaseUsage)
944+ }
945+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName) error); ok {
946+ r1 = returnFunc(ctx, p, repo)
947+ } else {
948+ r1 = ret.Error(1)
949+ }
950+ return r0, r1
951+}
952+
953+// ReleaseService_Usage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Usage'
954+type ReleaseService_Usage_Call struct {
955+ *mock.Call
956+}
957+
958+// Usage is a helper method to define mock.On call
959+// - ctx context.Context
960+// - p domain.Principal
961+// - repo domain.RepoName
962+func (_e *ReleaseService_Expecter) Usage(ctx any, p any, repo any) *ReleaseService_Usage_Call {
963+ return &ReleaseService_Usage_Call{Call: _e.mock.On("Usage", ctx, p, repo)}
964+}
965+
966+func (_c *ReleaseService_Usage_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName)) *ReleaseService_Usage_Call {
967+ _c.Call.Run(func(args mock.Arguments) {
968+ var arg0 context.Context
969+ if args[0] != nil {
970+ arg0 = args[0].(context.Context)
971+ }
972+ var arg1 domain.Principal
973+ if args[1] != nil {
974+ arg1 = args[1].(domain.Principal)
975+ }
976+ var arg2 domain.RepoName
977+ if args[2] != nil {
978+ arg2 = args[2].(domain.RepoName)
979+ }
980+ run(
981+ arg0,
982+ arg1,
983+ arg2,
984+ )
985+ })
986+ return _c
987+}
988+
989+func (_c *ReleaseService_Usage_Call) Return(releaseUsage ports.ReleaseUsage, err error) *ReleaseService_Usage_Call {
990+ _c.Call.Return(releaseUsage, err)
991+ return _c
992+}
993+
994+func (_c *ReleaseService_Usage_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName) (ports.ReleaseUsage, error)) *ReleaseService_Usage_Call {
995+ _c.Call.Return(run)
996+ return _c
997+}
A internal/ports/mocks/ReleaseStore.go +796 -0
@@ -0,0 +1,796 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ "bnema.dev/ermite/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewReleaseStore creates a new instance of ReleaseStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewReleaseStore(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *ReleaseStore {
21+ if helper, ok := t.(interface{ Helper() }); ok {
22+ helper.Helper()
23+ }
24+
25+ mock := &ReleaseStore{}
26+ mock.Mock.Test(t)
27+
28+ t.Cleanup(func() {
29+ if helper, ok := t.(interface{ Helper() }); ok {
30+ helper.Helper()
31+ }
32+ mock.AssertExpectations(t)
33+ })
34+
35+ return mock
36+}
37+
38+// ReleaseStore is an autogenerated mock type for the ReleaseStore type
39+type ReleaseStore struct {
40+ mock.Mock
41+}
42+
43+type ReleaseStore_Expecter struct {
44+ mock *mock.Mock
45+}
46+
47+func (_m *ReleaseStore) EXPECT() *ReleaseStore_Expecter {
48+ return &ReleaseStore_Expecter{mock: &_m.Mock}
49+}
50+
51+// AllAssets provides a mock function for the type ReleaseStore
52+func (_mock *ReleaseStore) AllAssets(ctx context.Context) ([]domain.ReleaseAsset, error) {
53+ ret := _mock.Called(ctx)
54+
55+ if len(ret) == 0 {
56+ panic("no return value specified for AllAssets")
57+ }
58+
59+ var r0 []domain.ReleaseAsset
60+ var r1 error
61+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]domain.ReleaseAsset, error)); ok {
62+ return returnFunc(ctx)
63+ }
64+ if returnFunc, ok := ret.Get(0).(func(context.Context) []domain.ReleaseAsset); ok {
65+ r0 = returnFunc(ctx)
66+ } else {
67+ if ret.Get(0) != nil {
68+ r0 = ret.Get(0).([]domain.ReleaseAsset)
69+ }
70+ }
71+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
72+ r1 = returnFunc(ctx)
73+ } else {
74+ r1 = ret.Error(1)
75+ }
76+ return r0, r1
77+}
78+
79+// ReleaseStore_AllAssets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AllAssets'
80+type ReleaseStore_AllAssets_Call struct {
81+ *mock.Call
82+}
83+
84+// AllAssets is a helper method to define mock.On call
85+// - ctx context.Context
86+func (_e *ReleaseStore_Expecter) AllAssets(ctx any) *ReleaseStore_AllAssets_Call {
87+ return &ReleaseStore_AllAssets_Call{Call: _e.mock.On("AllAssets", ctx)}
88+}
89+
90+func (_c *ReleaseStore_AllAssets_Call) Run(run func(ctx context.Context)) *ReleaseStore_AllAssets_Call {
91+ _c.Call.Run(func(args mock.Arguments) {
92+ var arg0 context.Context
93+ if args[0] != nil {
94+ arg0 = args[0].(context.Context)
95+ }
96+ run(
97+ arg0,
98+ )
99+ })
100+ return _c
101+}
102+
103+func (_c *ReleaseStore_AllAssets_Call) Return(releaseAssets []domain.ReleaseAsset, err error) *ReleaseStore_AllAssets_Call {
104+ _c.Call.Return(releaseAssets, err)
105+ return _c
106+}
107+
108+func (_c *ReleaseStore_AllAssets_Call) RunAndReturn(run func(ctx context.Context) ([]domain.ReleaseAsset, error)) *ReleaseStore_AllAssets_Call {
109+ _c.Call.Return(run)
110+ return _c
111+}
112+
113+// ApplyReleases provides a mock function for the type ReleaseStore
114+func (_mock *ReleaseStore) ApplyReleases(ctx context.Context, w ports.ReleaseWrite) error {
115+ ret := _mock.Called(ctx, w)
116+
117+ if len(ret) == 0 {
118+ panic("no return value specified for ApplyReleases")
119+ }
120+
121+ var r0 error
122+ if returnFunc, ok := ret.Get(0).(func(context.Context, ports.ReleaseWrite) error); ok {
123+ r0 = returnFunc(ctx, w)
124+ } else {
125+ r0 = ret.Error(0)
126+ }
127+ return r0
128+}
129+
130+// ReleaseStore_ApplyReleases_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyReleases'
131+type ReleaseStore_ApplyReleases_Call struct {
132+ *mock.Call
133+}
134+
135+// ApplyReleases is a helper method to define mock.On call
136+// - ctx context.Context
137+// - w ports.ReleaseWrite
138+func (_e *ReleaseStore_Expecter) ApplyReleases(ctx any, w any) *ReleaseStore_ApplyReleases_Call {
139+ return &ReleaseStore_ApplyReleases_Call{Call: _e.mock.On("ApplyReleases", ctx, w)}
140+}
141+
142+func (_c *ReleaseStore_ApplyReleases_Call) Run(run func(ctx context.Context, w ports.ReleaseWrite)) *ReleaseStore_ApplyReleases_Call {
143+ _c.Call.Run(func(args mock.Arguments) {
144+ var arg0 context.Context
145+ if args[0] != nil {
146+ arg0 = args[0].(context.Context)
147+ }
148+ var arg1 ports.ReleaseWrite
149+ if args[1] != nil {
150+ arg1 = args[1].(ports.ReleaseWrite)
151+ }
152+ run(
153+ arg0,
154+ arg1,
155+ )
156+ })
157+ return _c
158+}
159+
160+func (_c *ReleaseStore_ApplyReleases_Call) Return(err error) *ReleaseStore_ApplyReleases_Call {
161+ _c.Call.Return(err)
162+ return _c
163+}
164+
165+func (_c *ReleaseStore_ApplyReleases_Call) RunAndReturn(run func(ctx context.Context, w ports.ReleaseWrite) error) *ReleaseStore_ApplyReleases_Call {
166+ _c.Call.Return(run)
167+ return _c
168+}
169+
170+// GetAsset provides a mock function for the type ReleaseStore
171+func (_mock *ReleaseStore) GetAsset(ctx context.Context, id int64) (domain.ReleaseAsset, error) {
172+ ret := _mock.Called(ctx, id)
173+
174+ if len(ret) == 0 {
175+ panic("no return value specified for GetAsset")
176+ }
177+
178+ var r0 domain.ReleaseAsset
179+ var r1 error
180+ if returnFunc, ok := ret.Get(0).(func(context.Context, int64) (domain.ReleaseAsset, error)); ok {
181+ return returnFunc(ctx, id)
182+ }
183+ if returnFunc, ok := ret.Get(0).(func(context.Context, int64) domain.ReleaseAsset); ok {
184+ r0 = returnFunc(ctx, id)
185+ } else {
186+ r0 = ret.Get(0).(domain.ReleaseAsset)
187+ }
188+ if returnFunc, ok := ret.Get(1).(func(context.Context, int64) error); ok {
189+ r1 = returnFunc(ctx, id)
190+ } else {
191+ r1 = ret.Error(1)
192+ }
193+ return r0, r1
194+}
195+
196+// ReleaseStore_GetAsset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAsset'
197+type ReleaseStore_GetAsset_Call struct {
198+ *mock.Call
199+}
200+
201+// GetAsset is a helper method to define mock.On call
202+// - ctx context.Context
203+// - id int64
204+func (_e *ReleaseStore_Expecter) GetAsset(ctx any, id any) *ReleaseStore_GetAsset_Call {
205+ return &ReleaseStore_GetAsset_Call{Call: _e.mock.On("GetAsset", ctx, id)}
206+}
207+
208+func (_c *ReleaseStore_GetAsset_Call) Run(run func(ctx context.Context, id int64)) *ReleaseStore_GetAsset_Call {
209+ _c.Call.Run(func(args mock.Arguments) {
210+ var arg0 context.Context
211+ if args[0] != nil {
212+ arg0 = args[0].(context.Context)
213+ }
214+ var arg1 int64
215+ if args[1] != nil {
216+ arg1 = args[1].(int64)
217+ }
218+ run(
219+ arg0,
220+ arg1,
221+ )
222+ })
223+ return _c
224+}
225+
226+func (_c *ReleaseStore_GetAsset_Call) Return(releaseAsset domain.ReleaseAsset, err error) *ReleaseStore_GetAsset_Call {
227+ _c.Call.Return(releaseAsset, err)
228+ return _c
229+}
230+
231+func (_c *ReleaseStore_GetAsset_Call) RunAndReturn(run func(ctx context.Context, id int64) (domain.ReleaseAsset, error)) *ReleaseStore_GetAsset_Call {
232+ _c.Call.Return(run)
233+ return _c
234+}
235+
236+// GetAssetByName provides a mock function for the type ReleaseStore
237+func (_mock *ReleaseStore) GetAssetByName(ctx context.Context, repo domain.RepoName, releaseID int64, name string) (domain.ReleaseAsset, error) {
238+ ret := _mock.Called(ctx, repo, releaseID, name)
239+
240+ if len(ret) == 0 {
241+ panic("no return value specified for GetAssetByName")
242+ }
243+
244+ var r0 domain.ReleaseAsset
245+ var r1 error
246+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, string) (domain.ReleaseAsset, error)); ok {
247+ return returnFunc(ctx, repo, releaseID, name)
248+ }
249+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, string) domain.ReleaseAsset); ok {
250+ r0 = returnFunc(ctx, repo, releaseID, name)
251+ } else {
252+ r0 = ret.Get(0).(domain.ReleaseAsset)
253+ }
254+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64, string) error); ok {
255+ r1 = returnFunc(ctx, repo, releaseID, name)
256+ } else {
257+ r1 = ret.Error(1)
258+ }
259+ return r0, r1
260+}
261+
262+// ReleaseStore_GetAssetByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAssetByName'
263+type ReleaseStore_GetAssetByName_Call struct {
264+ *mock.Call
265+}
266+
267+// GetAssetByName is a helper method to define mock.On call
268+// - ctx context.Context
269+// - repo domain.RepoName
270+// - releaseID int64
271+// - name string
272+func (_e *ReleaseStore_Expecter) GetAssetByName(ctx any, repo any, releaseID any, name any) *ReleaseStore_GetAssetByName_Call {
273+ return &ReleaseStore_GetAssetByName_Call{Call: _e.mock.On("GetAssetByName", ctx, repo, releaseID, name)}
274+}
275+
276+func (_c *ReleaseStore_GetAssetByName_Call) Run(run func(ctx context.Context, repo domain.RepoName, releaseID int64, name string)) *ReleaseStore_GetAssetByName_Call {
277+ _c.Call.Run(func(args mock.Arguments) {
278+ var arg0 context.Context
279+ if args[0] != nil {
280+ arg0 = args[0].(context.Context)
281+ }
282+ var arg1 domain.RepoName
283+ if args[1] != nil {
284+ arg1 = args[1].(domain.RepoName)
285+ }
286+ var arg2 int64
287+ if args[2] != nil {
288+ arg2 = args[2].(int64)
289+ }
290+ var arg3 string
291+ if args[3] != nil {
292+ arg3 = args[3].(string)
293+ }
294+ run(
295+ arg0,
296+ arg1,
297+ arg2,
298+ arg3,
299+ )
300+ })
301+ return _c
302+}
303+
304+func (_c *ReleaseStore_GetAssetByName_Call) Return(releaseAsset domain.ReleaseAsset, err error) *ReleaseStore_GetAssetByName_Call {
305+ _c.Call.Return(releaseAsset, err)
306+ return _c
307+}
308+
309+func (_c *ReleaseStore_GetAssetByName_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, releaseID int64, name string) (domain.ReleaseAsset, error)) *ReleaseStore_GetAssetByName_Call {
310+ _c.Call.Return(run)
311+ return _c
312+}
313+
314+// GetRelease provides a mock function for the type ReleaseStore
315+func (_mock *ReleaseStore) GetRelease(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error) {
316+ ret := _mock.Called(ctx, repo, id)
317+
318+ if len(ret) == 0 {
319+ panic("no return value specified for GetRelease")
320+ }
321+
322+ var r0 domain.Release
323+ var r1 error
324+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) (domain.Release, error)); ok {
325+ return returnFunc(ctx, repo, id)
326+ }
327+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) domain.Release); ok {
328+ r0 = returnFunc(ctx, repo, id)
329+ } else {
330+ r0 = ret.Get(0).(domain.Release)
331+ }
332+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64) error); ok {
333+ r1 = returnFunc(ctx, repo, id)
334+ } else {
335+ r1 = ret.Error(1)
336+ }
337+ return r0, r1
338+}
339+
340+// ReleaseStore_GetRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRelease'
341+type ReleaseStore_GetRelease_Call struct {
342+ *mock.Call
343+}
344+
345+// GetRelease is a helper method to define mock.On call
346+// - ctx context.Context
347+// - repo domain.RepoName
348+// - id int64
349+func (_e *ReleaseStore_Expecter) GetRelease(ctx any, repo any, id any) *ReleaseStore_GetRelease_Call {
350+ return &ReleaseStore_GetRelease_Call{Call: _e.mock.On("GetRelease", ctx, repo, id)}
351+}
352+
353+func (_c *ReleaseStore_GetRelease_Call) Run(run func(ctx context.Context, repo domain.RepoName, id int64)) *ReleaseStore_GetRelease_Call {
354+ _c.Call.Run(func(args mock.Arguments) {
355+ var arg0 context.Context
356+ if args[0] != nil {
357+ arg0 = args[0].(context.Context)
358+ }
359+ var arg1 domain.RepoName
360+ if args[1] != nil {
361+ arg1 = args[1].(domain.RepoName)
362+ }
363+ var arg2 int64
364+ if args[2] != nil {
365+ arg2 = args[2].(int64)
366+ }
367+ run(
368+ arg0,
369+ arg1,
370+ arg2,
371+ )
372+ })
373+ return _c
374+}
375+
376+func (_c *ReleaseStore_GetRelease_Call) Return(release domain.Release, err error) *ReleaseStore_GetRelease_Call {
377+ _c.Call.Return(release, err)
378+ return _c
379+}
380+
381+func (_c *ReleaseStore_GetRelease_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, id int64) (domain.Release, error)) *ReleaseStore_GetRelease_Call {
382+ _c.Call.Return(run)
383+ return _c
384+}
385+
386+// GetReleaseByTag provides a mock function for the type ReleaseStore
387+func (_mock *ReleaseStore) GetReleaseByTag(ctx context.Context, repo domain.RepoName, tag string) (domain.Release, error) {
388+ ret := _mock.Called(ctx, repo, tag)
389+
390+ if len(ret) == 0 {
391+ panic("no return value specified for GetReleaseByTag")
392+ }
393+
394+ var r0 domain.Release
395+ var r1 error
396+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string) (domain.Release, error)); ok {
397+ return returnFunc(ctx, repo, tag)
398+ }
399+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string) domain.Release); ok {
400+ r0 = returnFunc(ctx, repo, tag)
401+ } else {
402+ r0 = ret.Get(0).(domain.Release)
403+ }
404+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, string) error); ok {
405+ r1 = returnFunc(ctx, repo, tag)
406+ } else {
407+ r1 = ret.Error(1)
408+ }
409+ return r0, r1
410+}
411+
412+// ReleaseStore_GetReleaseByTag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetReleaseByTag'
413+type ReleaseStore_GetReleaseByTag_Call struct {
414+ *mock.Call
415+}
416+
417+// GetReleaseByTag is a helper method to define mock.On call
418+// - ctx context.Context
419+// - repo domain.RepoName
420+// - tag string
421+func (_e *ReleaseStore_Expecter) GetReleaseByTag(ctx any, repo any, tag any) *ReleaseStore_GetReleaseByTag_Call {
422+ return &ReleaseStore_GetReleaseByTag_Call{Call: _e.mock.On("GetReleaseByTag", ctx, repo, tag)}
423+}
424+
425+func (_c *ReleaseStore_GetReleaseByTag_Call) Run(run func(ctx context.Context, repo domain.RepoName, tag string)) *ReleaseStore_GetReleaseByTag_Call {
426+ _c.Call.Run(func(args mock.Arguments) {
427+ var arg0 context.Context
428+ if args[0] != nil {
429+ arg0 = args[0].(context.Context)
430+ }
431+ var arg1 domain.RepoName
432+ if args[1] != nil {
433+ arg1 = args[1].(domain.RepoName)
434+ }
435+ var arg2 string
436+ if args[2] != nil {
437+ arg2 = args[2].(string)
438+ }
439+ run(
440+ arg0,
441+ arg1,
442+ arg2,
443+ )
444+ })
445+ return _c
446+}
447+
448+func (_c *ReleaseStore_GetReleaseByTag_Call) Return(release domain.Release, err error) *ReleaseStore_GetReleaseByTag_Call {
449+ _c.Call.Return(release, err)
450+ return _c
451+}
452+
453+func (_c *ReleaseStore_GetReleaseByTag_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, tag string) (domain.Release, error)) *ReleaseStore_GetReleaseByTag_Call {
454+ _c.Call.Return(run)
455+ return _c
456+}
457+
458+// ListAssets provides a mock function for the type ReleaseStore
459+func (_mock *ReleaseStore) ListAssets(ctx context.Context, repo domain.RepoName, releaseID int64) ([]domain.ReleaseAsset, error) {
460+ ret := _mock.Called(ctx, repo, releaseID)
461+
462+ if len(ret) == 0 {
463+ panic("no return value specified for ListAssets")
464+ }
465+
466+ var r0 []domain.ReleaseAsset
467+ var r1 error
468+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) ([]domain.ReleaseAsset, error)); ok {
469+ return returnFunc(ctx, repo, releaseID)
470+ }
471+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) []domain.ReleaseAsset); ok {
472+ r0 = returnFunc(ctx, repo, releaseID)
473+ } else {
474+ if ret.Get(0) != nil {
475+ r0 = ret.Get(0).([]domain.ReleaseAsset)
476+ }
477+ }
478+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64) error); ok {
479+ r1 = returnFunc(ctx, repo, releaseID)
480+ } else {
481+ r1 = ret.Error(1)
482+ }
483+ return r0, r1
484+}
485+
486+// ReleaseStore_ListAssets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListAssets'
487+type ReleaseStore_ListAssets_Call struct {
488+ *mock.Call
489+}
490+
491+// ListAssets is a helper method to define mock.On call
492+// - ctx context.Context
493+// - repo domain.RepoName
494+// - releaseID int64
495+func (_e *ReleaseStore_Expecter) ListAssets(ctx any, repo any, releaseID any) *ReleaseStore_ListAssets_Call {
496+ return &ReleaseStore_ListAssets_Call{Call: _e.mock.On("ListAssets", ctx, repo, releaseID)}
497+}
498+
499+func (_c *ReleaseStore_ListAssets_Call) Run(run func(ctx context.Context, repo domain.RepoName, releaseID int64)) *ReleaseStore_ListAssets_Call {
500+ _c.Call.Run(func(args mock.Arguments) {
501+ var arg0 context.Context
502+ if args[0] != nil {
503+ arg0 = args[0].(context.Context)
504+ }
505+ var arg1 domain.RepoName
506+ if args[1] != nil {
507+ arg1 = args[1].(domain.RepoName)
508+ }
509+ var arg2 int64
510+ if args[2] != nil {
511+ arg2 = args[2].(int64)
512+ }
513+ run(
514+ arg0,
515+ arg1,
516+ arg2,
517+ )
518+ })
519+ return _c
520+}
521+
522+func (_c *ReleaseStore_ListAssets_Call) Return(releaseAssets []domain.ReleaseAsset, err error) *ReleaseStore_ListAssets_Call {
523+ _c.Call.Return(releaseAssets, err)
524+ return _c
525+}
526+
527+func (_c *ReleaseStore_ListAssets_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, releaseID int64) ([]domain.ReleaseAsset, error)) *ReleaseStore_ListAssets_Call {
528+ _c.Call.Return(run)
529+ return _c
530+}
531+
532+// ListReleases provides a mock function for the type ReleaseStore
533+func (_mock *ReleaseStore) ListReleases(ctx context.Context, repo domain.RepoName) ([]domain.Release, error) {
534+ ret := _mock.Called(ctx, repo)
535+
536+ if len(ret) == 0 {
537+ panic("no return value specified for ListReleases")
538+ }
539+
540+ var r0 []domain.Release
541+ var r1 error
542+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) ([]domain.Release, error)); ok {
543+ return returnFunc(ctx, repo)
544+ }
545+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) []domain.Release); ok {
546+ r0 = returnFunc(ctx, repo)
547+ } else {
548+ if ret.Get(0) != nil {
549+ r0 = ret.Get(0).([]domain.Release)
550+ }
551+ }
552+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) error); ok {
553+ r1 = returnFunc(ctx, repo)
554+ } else {
555+ r1 = ret.Error(1)
556+ }
557+ return r0, r1
558+}
559+
560+// ReleaseStore_ListReleases_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReleases'
561+type ReleaseStore_ListReleases_Call struct {
562+ *mock.Call
563+}
564+
565+// ListReleases is a helper method to define mock.On call
566+// - ctx context.Context
567+// - repo domain.RepoName
568+func (_e *ReleaseStore_Expecter) ListReleases(ctx any, repo any) *ReleaseStore_ListReleases_Call {
569+ return &ReleaseStore_ListReleases_Call{Call: _e.mock.On("ListReleases", ctx, repo)}
570+}
571+
572+func (_c *ReleaseStore_ListReleases_Call) Run(run func(ctx context.Context, repo domain.RepoName)) *ReleaseStore_ListReleases_Call {
573+ _c.Call.Run(func(args mock.Arguments) {
574+ var arg0 context.Context
575+ if args[0] != nil {
576+ arg0 = args[0].(context.Context)
577+ }
578+ var arg1 domain.RepoName
579+ if args[1] != nil {
580+ arg1 = args[1].(domain.RepoName)
581+ }
582+ run(
583+ arg0,
584+ arg1,
585+ )
586+ })
587+ return _c
588+}
589+
590+func (_c *ReleaseStore_ListReleases_Call) Return(releases []domain.Release, err error) *ReleaseStore_ListReleases_Call {
591+ _c.Call.Return(releases, err)
592+ return _c
593+}
594+
595+func (_c *ReleaseStore_ListReleases_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName) ([]domain.Release, error)) *ReleaseStore_ListReleases_Call {
596+ _c.Call.Return(run)
597+ return _c
598+}
599+
600+// NextReleaseID provides a mock function for the type ReleaseStore
601+func (_mock *ReleaseStore) NextReleaseID(ctx context.Context) (int64, error) {
602+ ret := _mock.Called(ctx)
603+
604+ if len(ret) == 0 {
605+ panic("no return value specified for NextReleaseID")
606+ }
607+
608+ var r0 int64
609+ var r1 error
610+ if returnFunc, ok := ret.Get(0).(func(context.Context) (int64, error)); ok {
611+ return returnFunc(ctx)
612+ }
613+ if returnFunc, ok := ret.Get(0).(func(context.Context) int64); ok {
614+ r0 = returnFunc(ctx)
615+ } else {
616+ r0 = ret.Get(0).(int64)
617+ }
618+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
619+ r1 = returnFunc(ctx)
620+ } else {
621+ r1 = ret.Error(1)
622+ }
623+ return r0, r1
624+}
625+
626+// ReleaseStore_NextReleaseID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NextReleaseID'
627+type ReleaseStore_NextReleaseID_Call struct {
628+ *mock.Call
629+}
630+
631+// NextReleaseID is a helper method to define mock.On call
632+// - ctx context.Context
633+func (_e *ReleaseStore_Expecter) NextReleaseID(ctx any) *ReleaseStore_NextReleaseID_Call {
634+ return &ReleaseStore_NextReleaseID_Call{Call: _e.mock.On("NextReleaseID", ctx)}
635+}
636+
637+func (_c *ReleaseStore_NextReleaseID_Call) Run(run func(ctx context.Context)) *ReleaseStore_NextReleaseID_Call {
638+ _c.Call.Run(func(args mock.Arguments) {
639+ var arg0 context.Context
640+ if args[0] != nil {
641+ arg0 = args[0].(context.Context)
642+ }
643+ run(
644+ arg0,
645+ )
646+ })
647+ return _c
648+}
649+
650+func (_c *ReleaseStore_NextReleaseID_Call) Return(n int64, err error) *ReleaseStore_NextReleaseID_Call {
651+ _c.Call.Return(n, err)
652+ return _c
653+}
654+
655+func (_c *ReleaseStore_NextReleaseID_Call) RunAndReturn(run func(ctx context.Context) (int64, error)) *ReleaseStore_NextReleaseID_Call {
656+ _c.Call.Return(run)
657+ return _c
658+}
659+
660+// ReleaseRepo provides a mock function for the type ReleaseStore
661+func (_mock *ReleaseStore) ReleaseRepo(ctx context.Context, id int64) (domain.RepoName, error) {
662+ ret := _mock.Called(ctx, id)
663+
664+ if len(ret) == 0 {
665+ panic("no return value specified for ReleaseRepo")
666+ }
667+
668+ var r0 domain.RepoName
669+ var r1 error
670+ if returnFunc, ok := ret.Get(0).(func(context.Context, int64) (domain.RepoName, error)); ok {
671+ return returnFunc(ctx, id)
672+ }
673+ if returnFunc, ok := ret.Get(0).(func(context.Context, int64) domain.RepoName); ok {
674+ r0 = returnFunc(ctx, id)
675+ } else {
676+ r0 = ret.Get(0).(domain.RepoName)
677+ }
678+ if returnFunc, ok := ret.Get(1).(func(context.Context, int64) error); ok {
679+ r1 = returnFunc(ctx, id)
680+ } else {
681+ r1 = ret.Error(1)
682+ }
683+ return r0, r1
684+}
685+
686+// ReleaseStore_ReleaseRepo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReleaseRepo'
687+type ReleaseStore_ReleaseRepo_Call struct {
688+ *mock.Call
689+}
690+
691+// ReleaseRepo is a helper method to define mock.On call
692+// - ctx context.Context
693+// - id int64
694+func (_e *ReleaseStore_Expecter) ReleaseRepo(ctx any, id any) *ReleaseStore_ReleaseRepo_Call {
695+ return &ReleaseStore_ReleaseRepo_Call{Call: _e.mock.On("ReleaseRepo", ctx, id)}
696+}
697+
698+func (_c *ReleaseStore_ReleaseRepo_Call) Run(run func(ctx context.Context, id int64)) *ReleaseStore_ReleaseRepo_Call {
699+ _c.Call.Run(func(args mock.Arguments) {
700+ var arg0 context.Context
701+ if args[0] != nil {
702+ arg0 = args[0].(context.Context)
703+ }
704+ var arg1 int64
705+ if args[1] != nil {
706+ arg1 = args[1].(int64)
707+ }
708+ run(
709+ arg0,
710+ arg1,
711+ )
712+ })
713+ return _c
714+}
715+
716+func (_c *ReleaseStore_ReleaseRepo_Call) Return(repoName domain.RepoName, err error) *ReleaseStore_ReleaseRepo_Call {
717+ _c.Call.Return(repoName, err)
718+ return _c
719+}
720+
721+func (_c *ReleaseStore_ReleaseRepo_Call) RunAndReturn(run func(ctx context.Context, id int64) (domain.RepoName, error)) *ReleaseStore_ReleaseRepo_Call {
722+ _c.Call.Return(run)
723+ return _c
724+}
725+
726+// ReleaseUsage provides a mock function for the type ReleaseStore
727+func (_mock *ReleaseStore) ReleaseUsage(ctx context.Context, repo domain.RepoName) (int64, uint64, error) {
728+ ret := _mock.Called(ctx, repo)
729+
730+ if len(ret) == 0 {
731+ panic("no return value specified for ReleaseUsage")
732+ }
733+
734+ var r0 int64
735+ var r1 uint64
736+ var r2 error
737+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) (int64, uint64, error)); ok {
738+ return returnFunc(ctx, repo)
739+ }
740+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) int64); ok {
741+ r0 = returnFunc(ctx, repo)
742+ } else {
743+ r0 = ret.Get(0).(int64)
744+ }
745+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) uint64); ok {
746+ r1 = returnFunc(ctx, repo)
747+ } else {
748+ r1 = ret.Get(1).(uint64)
749+ }
750+ if returnFunc, ok := ret.Get(2).(func(context.Context, domain.RepoName) error); ok {
751+ r2 = returnFunc(ctx, repo)
752+ } else {
753+ r2 = ret.Error(2)
754+ }
755+ return r0, r1, r2
756+}
757+
758+// ReleaseStore_ReleaseUsage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReleaseUsage'
759+type ReleaseStore_ReleaseUsage_Call struct {
760+ *mock.Call
761+}
762+
763+// ReleaseUsage is a helper method to define mock.On call
764+// - ctx context.Context
765+// - repo domain.RepoName
766+func (_e *ReleaseStore_Expecter) ReleaseUsage(ctx any, repo any) *ReleaseStore_ReleaseUsage_Call {
767+ return &ReleaseStore_ReleaseUsage_Call{Call: _e.mock.On("ReleaseUsage", ctx, repo)}
768+}
769+
770+func (_c *ReleaseStore_ReleaseUsage_Call) Run(run func(ctx context.Context, repo domain.RepoName)) *ReleaseStore_ReleaseUsage_Call {
771+ _c.Call.Run(func(args mock.Arguments) {
772+ var arg0 context.Context
773+ if args[0] != nil {
774+ arg0 = args[0].(context.Context)
775+ }
776+ var arg1 domain.RepoName
777+ if args[1] != nil {
778+ arg1 = args[1].(domain.RepoName)
779+ }
780+ run(
781+ arg0,
782+ arg1,
783+ )
784+ })
785+ return _c
786+}
787+
788+func (_c *ReleaseStore_ReleaseUsage_Call) Return(used int64, version uint64, err error) *ReleaseStore_ReleaseUsage_Call {
789+ _c.Call.Return(used, version, err)
790+ return _c
791+}
792+
793+func (_c *ReleaseStore_ReleaseUsage_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName) (int64, uint64, error)) *ReleaseStore_ReleaseUsage_Call {
794+ _c.Call.Return(run)
795+ return _c
796+}
A internal/ports/mocks/ReleaseSweeper.go +87 -0
@@ -0,0 +1,87 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ mock "github.com/stretchr/testify/mock"
11+)
12+
13+// NewReleaseSweeper creates a new instance of ReleaseSweeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
14+// The first argument is typically a *testing.T value.
15+func NewReleaseSweeper(t interface {
16+ mock.TestingT
17+ Cleanup(func())
18+}) *ReleaseSweeper {
19+ if helper, ok := t.(interface{ Helper() }); ok {
20+ helper.Helper()
21+ }
22+
23+ mock := &ReleaseSweeper{}
24+ mock.Mock.Test(t)
25+
26+ t.Cleanup(func() {
27+ if helper, ok := t.(interface{ Helper() }); ok {
28+ helper.Helper()
29+ }
30+ mock.AssertExpectations(t)
31+ })
32+
33+ return mock
34+}
35+
36+// ReleaseSweeper is an autogenerated mock type for the ReleaseSweeper type
37+type ReleaseSweeper struct {
38+ mock.Mock
39+}
40+
41+type ReleaseSweeper_Expecter struct {
42+ mock *mock.Mock
43+}
44+
45+func (_m *ReleaseSweeper) EXPECT() *ReleaseSweeper_Expecter {
46+ return &ReleaseSweeper_Expecter{mock: &_m.Mock}
47+}
48+
49+// Sweep provides a mock function for the type ReleaseSweeper
50+func (_mock *ReleaseSweeper) Sweep(ctx context.Context) {
51+ _mock.Called(ctx)
52+ return
53+}
54+
55+// ReleaseSweeper_Sweep_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Sweep'
56+type ReleaseSweeper_Sweep_Call struct {
57+ *mock.Call
58+}
59+
60+// Sweep is a helper method to define mock.On call
61+// - ctx context.Context
62+func (_e *ReleaseSweeper_Expecter) Sweep(ctx any) *ReleaseSweeper_Sweep_Call {
63+ return &ReleaseSweeper_Sweep_Call{Call: _e.mock.On("Sweep", ctx)}
64+}
65+
66+func (_c *ReleaseSweeper_Sweep_Call) Run(run func(ctx context.Context)) *ReleaseSweeper_Sweep_Call {
67+ _c.Call.Run(func(args mock.Arguments) {
68+ var arg0 context.Context
69+ if args[0] != nil {
70+ arg0 = args[0].(context.Context)
71+ }
72+ run(
73+ arg0,
74+ )
75+ })
76+ return _c
77+}
78+
79+func (_c *ReleaseSweeper_Sweep_Call) Return() *ReleaseSweeper_Sweep_Call {
80+ _c.Call.Return()
81+ return _c
82+}
83+
84+func (_c *ReleaseSweeper_Sweep_Call) RunAndReturn(run func(ctx context.Context)) *ReleaseSweeper_Sweep_Call {
85+ _c.Run(run)
86+ return _c
87+}
A internal/ports/mocks/ReleaseTagTrigger.go +100 -0
@@ -0,0 +1,100 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ mock "github.com/stretchr/testify/mock"
12+)
13+
14+// NewReleaseTagTrigger creates a new instance of ReleaseTagTrigger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15+// The first argument is typically a *testing.T value.
16+func NewReleaseTagTrigger(t interface {
17+ mock.TestingT
18+ Cleanup(func())
19+}) *ReleaseTagTrigger {
20+ if helper, ok := t.(interface{ Helper() }); ok {
21+ helper.Helper()
22+ }
23+
24+ mock := &ReleaseTagTrigger{}
25+ mock.Mock.Test(t)
26+
27+ t.Cleanup(func() {
28+ if helper, ok := t.(interface{ Helper() }); ok {
29+ helper.Helper()
30+ }
31+ mock.AssertExpectations(t)
32+ })
33+
34+ return mock
35+}
36+
37+// ReleaseTagTrigger is an autogenerated mock type for the ReleaseTagTrigger type
38+type ReleaseTagTrigger struct {
39+ mock.Mock
40+}
41+
42+type ReleaseTagTrigger_Expecter struct {
43+ mock *mock.Mock
44+}
45+
46+func (_m *ReleaseTagTrigger) EXPECT() *ReleaseTagTrigger_Expecter {
47+ return &ReleaseTagTrigger_Expecter{mock: &_m.Mock}
48+}
49+
50+// OnRefsMoved provides a mock function for the type ReleaseTagTrigger
51+func (_mock *ReleaseTagTrigger) OnRefsMoved(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate) {
52+ _mock.Called(ctx, repo, updates)
53+ return
54+}
55+
56+// ReleaseTagTrigger_OnRefsMoved_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OnRefsMoved'
57+type ReleaseTagTrigger_OnRefsMoved_Call struct {
58+ *mock.Call
59+}
60+
61+// OnRefsMoved is a helper method to define mock.On call
62+// - ctx context.Context
63+// - repo domain.RepoName
64+// - updates []domain.RefUpdate
65+func (_e *ReleaseTagTrigger_Expecter) OnRefsMoved(ctx any, repo any, updates any) *ReleaseTagTrigger_OnRefsMoved_Call {
66+ return &ReleaseTagTrigger_OnRefsMoved_Call{Call: _e.mock.On("OnRefsMoved", ctx, repo, updates)}
67+}
68+
69+func (_c *ReleaseTagTrigger_OnRefsMoved_Call) Run(run func(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate)) *ReleaseTagTrigger_OnRefsMoved_Call {
70+ _c.Call.Run(func(args mock.Arguments) {
71+ var arg0 context.Context
72+ if args[0] != nil {
73+ arg0 = args[0].(context.Context)
74+ }
75+ var arg1 domain.RepoName
76+ if args[1] != nil {
77+ arg1 = args[1].(domain.RepoName)
78+ }
79+ var arg2 []domain.RefUpdate
80+ if args[2] != nil {
81+ arg2 = args[2].([]domain.RefUpdate)
82+ }
83+ run(
84+ arg0,
85+ arg1,
86+ arg2,
87+ )
88+ })
89+ return _c
90+}
91+
92+func (_c *ReleaseTagTrigger_OnRefsMoved_Call) Return() *ReleaseTagTrigger_OnRefsMoved_Call {
93+ _c.Call.Return()
94+ return _c
95+}
96+
97+func (_c *ReleaseTagTrigger_OnRefsMoved_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, updates []domain.RefUpdate)) *ReleaseTagTrigger_OnRefsMoved_Call {
98+ _c.Run(run)
99+ return _c
100+}
A internal/ports/mocks/ReleaseTagWriter.go +117 -0
@@ -0,0 +1,117 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ mock "github.com/stretchr/testify/mock"
12+)
13+
14+// NewReleaseTagWriter creates a new instance of ReleaseTagWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15+// The first argument is typically a *testing.T value.
16+func NewReleaseTagWriter(t interface {
17+ mock.TestingT
18+ Cleanup(func())
19+}) *ReleaseTagWriter {
20+ if helper, ok := t.(interface{ Helper() }); ok {
21+ helper.Helper()
22+ }
23+
24+ mock := &ReleaseTagWriter{}
25+ mock.Mock.Test(t)
26+
27+ t.Cleanup(func() {
28+ if helper, ok := t.(interface{ Helper() }); ok {
29+ helper.Helper()
30+ }
31+ mock.AssertExpectations(t)
32+ })
33+
34+ return mock
35+}
36+
37+// ReleaseTagWriter is an autogenerated mock type for the ReleaseTagWriter type
38+type ReleaseTagWriter struct {
39+ mock.Mock
40+}
41+
42+type ReleaseTagWriter_Expecter struct {
43+ mock *mock.Mock
44+}
45+
46+func (_m *ReleaseTagWriter) EXPECT() *ReleaseTagWriter_Expecter {
47+ return &ReleaseTagWriter_Expecter{mock: &_m.Mock}
48+}
49+
50+// CreateTag provides a mock function for the type ReleaseTagWriter
51+func (_mock *ReleaseTagWriter) CreateTag(ctx context.Context, repo domain.RepoName, tag string, target domain.ObjectID) error {
52+ ret := _mock.Called(ctx, repo, tag, target)
53+
54+ if len(ret) == 0 {
55+ panic("no return value specified for CreateTag")
56+ }
57+
58+ var r0 error
59+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string, domain.ObjectID) error); ok {
60+ r0 = returnFunc(ctx, repo, tag, target)
61+ } else {
62+ r0 = ret.Error(0)
63+ }
64+ return r0
65+}
66+
67+// ReleaseTagWriter_CreateTag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTag'
68+type ReleaseTagWriter_CreateTag_Call struct {
69+ *mock.Call
70+}
71+
72+// CreateTag is a helper method to define mock.On call
73+// - ctx context.Context
74+// - repo domain.RepoName
75+// - tag string
76+// - target domain.ObjectID
77+func (_e *ReleaseTagWriter_Expecter) CreateTag(ctx any, repo any, tag any, target any) *ReleaseTagWriter_CreateTag_Call {
78+ return &ReleaseTagWriter_CreateTag_Call{Call: _e.mock.On("CreateTag", ctx, repo, tag, target)}
79+}
80+
81+func (_c *ReleaseTagWriter_CreateTag_Call) Run(run func(ctx context.Context, repo domain.RepoName, tag string, target domain.ObjectID)) *ReleaseTagWriter_CreateTag_Call {
82+ _c.Call.Run(func(args mock.Arguments) {
83+ var arg0 context.Context
84+ if args[0] != nil {
85+ arg0 = args[0].(context.Context)
86+ }
87+ var arg1 domain.RepoName
88+ if args[1] != nil {
89+ arg1 = args[1].(domain.RepoName)
90+ }
91+ var arg2 string
92+ if args[2] != nil {
93+ arg2 = args[2].(string)
94+ }
95+ var arg3 domain.ObjectID
96+ if args[3] != nil {
97+ arg3 = args[3].(domain.ObjectID)
98+ }
99+ run(
100+ arg0,
101+ arg1,
102+ arg2,
103+ arg3,
104+ )
105+ })
106+ return _c
107+}
108+
109+func (_c *ReleaseTagWriter_CreateTag_Call) Return(err error) *ReleaseTagWriter_CreateTag_Call {
110+ _c.Call.Return(err)
111+ return _c
112+}
113+
114+func (_c *ReleaseTagWriter_CreateTag_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, tag string, target domain.ObjectID) error) *ReleaseTagWriter_CreateTag_Call {
115+ _c.Call.Return(run)
116+ return _c
117+}
A internal/ports/release.go +119 -0
M internal/ports/services.go +17 -0
M internal/usecase/ci.go +15 -7
M internal/usecase/ci_artifacts.go +325 -12
A internal/usecase/ci_artifacts_blob_test.go +301 -0
A internal/usecase/ci_artifacts_slots_test.go +179 -0
M internal/usecase/ci_watch.go +4 -3
M internal/usecase/events.go +25 -0
M internal/usecase/events_test.go +95 -0
M internal/usecase/pushes.go +21 -14
A internal/usecase/releases.go +494 -0
A internal/usecase/releases_assets.go +441 -0
A internal/usecase/releases_assets_test.go +308 -0
A internal/usecase/releases_github.go +387 -0
A internal/usecase/releases_github_test.go +360 -0
A internal/usecase/releases_integration_test.go +336 -0
A internal/usecase/releases_sweep.go +106 -0
A internal/usecase/releases_sweep_test.go +147 -0
A internal/usecase/releases_test.go +57 -0
M internal/usecase/repos.go +9 -1
M internal/usecase/repos_write.go +11 -0
M proto/ermite/storage/ci_artifact.proto +4 -2
A proto/ermite/storage/release.proto +46 -0
M skills/ermite/CLI.md +20 -0

Comments0 open / 0

no comments yet.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
l
merge request: the list of files (j k move, enter opens, esc back)
M
merge request: the merge menu; there 1–3 pick the mode, D deletes the branch, enter merges
R
merge request: mark the head read
esc
close a dialog, leave a field

Branches 2

Tags 43

↑↓ moveenter openesc close