detached fda3c2b

releases_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2package kvstore_test
3
4import (
5	"context"
6	"sync"
7	"testing"
8
9	kv "bnema.dev/ermite-kv"
10	"github.com/stretchr/testify/require"
11
12	"bnema.dev/ermite/internal/domain"
13	"bnema.dev/ermite/internal/ports"
14)
15
16func TestReleaseStorage(t *testing.T) {
17	ctx := context.Background()
18	dir := t.TempDir()
19	s := openStore(t, dir)
20	repo := name(t, "release-test")
21	require.NoError(t, s.Create(ctx, domain.Repo{Name: repo, Visibility: domain.VisibilityPublic, CreatedAt: created, Settings: domain.DefaultRepoSettings()}))
22	st := s.Releases()
23	id, err := st.NextReleaseID(ctx)
24	require.NoError(t, err)
25	r := domain.Release{ID: id, Repo: repo, Tag: "v1", Title: "title", Notes: "notes", MakeLatest: true, TargetCommitish: "abc", Author: "owner", CreatedAt: created, PublishedAt: created, UpdatedAt: created}
26	assetID, err := st.NextReleaseID(ctx)
27	require.NoError(t, err)
28	a := domain.ReleaseAsset{ID: assetID, ReleaseID: id, Repo: repo, Name: "binary", Label: "label", ContentType: "application/octet-stream", Size: 3, Digest: "sha256:abc", State: domain.ReleaseAssetUploading, Uploader: "owner", Charged: 3, MultipartID: "multipart", CreatedAt: created, UpdatedAt: created}
29	w := ports.ReleaseWrite{Repo: repo, Used: 3, PutReleases: []domain.Release{r}, PutAssets: []domain.ReleaseAsset{a}}
30	require.NoError(t, st.ApplyReleases(ctx, w))
31	got, err := st.GetReleaseByTag(ctx, repo, "v1")
32	require.NoError(t, err)
33	r.Version = got.Version
34	require.Equal(t, r, got)
35	ar, err := st.GetAssetByName(ctx, repo, id, "binary")
36	require.NoError(t, err)
37	a.Version = ar.Version
38	require.Equal(t, a, ar)
39	rr, err := st.ReleaseRepo(ctx, id)
40	require.NoError(t, err)
41	require.Equal(t, repo, rr)
42	rs, err := st.ListReleases(ctx, repo)
43	require.NoError(t, err)
44	require.Equal(t, []domain.Release{r}, rs)
45	as, err := st.ListAssets(ctx, repo, id)
46	require.NoError(t, err)
47	require.Equal(t, []domain.ReleaseAsset{a}, as)
48	all, err := st.AllAssets(ctx)
49	require.NoError(t, err)
50	require.Equal(t, as, all)
51	require.ErrorIs(t, st.ApplyReleases(ctx, w), domain.ErrConflict)
52	used, version, err := st.ReleaseUsage(ctx, repo)
53	require.NoError(t, err)
54	require.EqualValues(t, 3, used)
55	duplicate := r
56	duplicate.ID += 10
57	duplicate.Version = 0
58	require.ErrorIs(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, PutReleases: []domain.Release{duplicate}}), domain.ErrAlreadyExists)
59	da := a
60	da.ID += 10
61	da.Version = 0
62	require.ErrorIs(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, PutAssets: []domain.ReleaseAsset{da}}), domain.ErrAlreadyExists)
63	stale := r
64	stale.Version++
65	require.ErrorIs(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, PutReleases: []domain.Release{stale}}), domain.ErrConflict)
66	staleAsset := a
67	staleAsset.Version++
68	require.ErrorIs(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, DeleteAssets: []domain.ReleaseAsset{staleAsset}}), domain.ErrConflict)
69	r.Tag = "v2"
70	a.Name = "renamed"
71	require.NoError(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, Used: 3, PutReleases: []domain.Release{r}, PutAssets: []domain.ReleaseAsset{a}}))
72	_, err = st.GetReleaseByTag(ctx, repo, "v1")
73	require.ErrorIs(t, err, domain.ErrNotFound)
74	_, err = st.GetAssetByName(ctx, repo, id, "binary")
75	require.ErrorIs(t, err, domain.ErrNotFound)
76	renamedRelease, err := st.GetReleaseByTag(ctx, repo, "v2")
77	require.NoError(t, err)
78	renamedAsset, err := st.GetAssetByName(ctx, repo, id, "renamed")
79	require.NoError(t, err)
80	_, version, err = st.ReleaseUsage(ctx, repo)
81	require.NoError(t, err)
82	require.NoError(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, DeleteReleases: []domain.Release{renamedRelease}, DeleteAssets: []domain.ReleaseAsset{renamedAsset}}))
83	_, err = st.GetRelease(ctx, repo, id)
84	require.ErrorIs(t, err, domain.ErrNotFound)
85	_, err = st.GetAsset(ctx, a.ID)
86	require.ErrorIs(t, err, domain.ErrNotFound)
87	_, version, err = st.ReleaseUsage(ctx, repo)
88	require.NoError(t, err)
89	r.Version, a.Version = 0, 0
90	require.NoError(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo, UsageVersion: version, Used: 3, PutReleases: []domain.Release{r}, PutAssets: []domain.ReleaseAsset{a}}))
91	require.NoError(t, s.Delete(ctx, repo))
92	_, err = st.GetRelease(ctx, repo, id)
93	require.ErrorIs(t, err, domain.ErrNotFound)
94	_, err = st.ReleaseRepo(ctx, id)
95	require.ErrorIs(t, err, domain.ErrNotFound)
96	_, err = st.GetAsset(ctx, a.ID)
97	require.ErrorIs(t, err, domain.ErrNotFound)
98	all, err = st.AllAssets(ctx)
99	require.NoError(t, err)
100	require.Empty(t, all)
101	used, version, err = st.ReleaseUsage(ctx, repo)
102	require.NoError(t, err)
103	require.Zero(t, used)
104	require.Zero(t, version)
105	require.ErrorIs(t, st.ApplyReleases(ctx, ports.ReleaseWrite{Repo: repo}), domain.ErrNotFound)
106	require.NoError(t, s.Close(ctx))
107	db, err := kv.Open(ctx, dir, kv.Options{})
108	require.NoError(t, err)
109	defer db.Close(ctx)
110	for _, prefix := range []string{"rel/", "reltag/", "relid/", "relasset/", "relassets/", "reluse/"} {
111		for key := range db.Snapshot(ctx).Prefix(ctx, []byte(prefix)) {
112			t.Errorf("repository deletion left release key %q", key)
113		}
114	}
115}
116func TestConcurrentReleaseIDs(t *testing.T) {
117	st := openStore(t, t.TempDir()).Releases()
118	var wg sync.WaitGroup
119	ids := make(chan int64, 32)
120	for range 32 {
121		wg.Go(func() { id, err := st.NextReleaseID(context.Background()); require.NoError(t, err); ids <- id })
122	}
123	wg.Wait()
124	close(ids)
125	seen := map[int64]bool{}
126	for id := range ids {
127		require.False(t, seen[id])
128		seen[id] = true
129	}
130	require.Len(t, seen, 32)
131}

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
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close