1package namespace
2
3import (
4	"os"
5	"path/filepath"
6	"testing"
7
8	"github.com/stretchr/testify/require"
9	"gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config"
10	"gitlab.com/gitlab-org/gitaly/v14/internal/helper"
11	"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper"
12	"gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver"
13	"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
14	"google.golang.org/grpc/codes"
15)
16
17func TestMain(m *testing.M) {
18	os.Exit(testMain(m))
19}
20
21func testMain(m *testing.M) int {
22	defer testhelper.MustHaveNoChildProcess()
23
24	cleanup := testhelper.Configure()
25	defer cleanup()
26
27	return m.Run()
28}
29
30func TestNamespaceExists(t *testing.T) {
31	cfg, client := setupNamespaceService(t, testserver.WithDisablePraefect())
32	existingStorage := cfg.Storages[0]
33
34	ctx, cancel := testhelper.Context()
35	defer cancel()
36
37	const existingNamespace = "existing"
38	require.NoError(t, os.MkdirAll(filepath.Join(existingStorage.Path, existingNamespace), 0755))
39
40	queries := []struct {
41		desc      string
42		request   *gitalypb.NamespaceExistsRequest
43		errorCode codes.Code
44		exists    bool
45	}{
46		{
47			desc: "empty name",
48			request: &gitalypb.NamespaceExistsRequest{
49				StorageName: existingStorage.Name,
50				Name:        "",
51			},
52			errorCode: codes.InvalidArgument,
53		},
54		{
55			desc: "Namespace doesn't exists",
56			request: &gitalypb.NamespaceExistsRequest{
57				StorageName: existingStorage.Name,
58				Name:        "not-existing",
59			},
60			errorCode: codes.OK,
61			exists:    false,
62		},
63		{
64			desc: "Wrong storage path",
65			request: &gitalypb.NamespaceExistsRequest{
66				StorageName: "other",
67				Name:        existingNamespace,
68			},
69			errorCode: codes.OK,
70			exists:    false,
71		},
72		{
73			desc: "Namespace exists",
74			request: &gitalypb.NamespaceExistsRequest{
75				StorageName: existingStorage.Name,
76				Name:        existingNamespace,
77			},
78			errorCode: codes.OK,
79			exists:    true,
80		},
81	}
82
83	for _, tc := range queries {
84		t.Run(tc.desc, func(t *testing.T) {
85			response, err := client.NamespaceExists(ctx, tc.request)
86
87			require.Equal(t, tc.errorCode, helper.GrpcCode(err))
88
89			if tc.errorCode == codes.OK {
90				require.Equal(t, tc.exists, response.Exists)
91			}
92		})
93	}
94}
95
96func getStorageDir(t *testing.T, cfg config.Cfg, storageName string) string {
97	t.Helper()
98	s, found := cfg.Storage(storageName)
99	require.True(t, found)
100	return s.Path
101}
102
103func TestAddNamespace(t *testing.T) {
104	cfg, client := setupNamespaceService(t)
105	existingStorage := cfg.Storages[0]
106
107	queries := []struct {
108		desc      string
109		request   *gitalypb.AddNamespaceRequest
110		errorCode codes.Code
111	}{
112		{
113			desc: "No name",
114			request: &gitalypb.AddNamespaceRequest{
115				StorageName: existingStorage.Name,
116				Name:        "",
117			},
118			errorCode: codes.InvalidArgument,
119		},
120		{
121			desc: "Namespace is successfully created",
122			request: &gitalypb.AddNamespaceRequest{
123				StorageName: existingStorage.Name,
124				Name:        "create-me",
125			},
126			errorCode: codes.OK,
127		},
128		{
129			desc: "Idempotent on creation",
130			request: &gitalypb.AddNamespaceRequest{
131				StorageName: existingStorage.Name,
132				Name:        "create-me",
133			},
134			errorCode: codes.OK,
135		},
136		{
137			desc: "no storage",
138			request: &gitalypb.AddNamespaceRequest{
139				StorageName: "",
140				Name:        "mepmep",
141			},
142			errorCode: codes.InvalidArgument,
143		},
144	}
145
146	for _, tc := range queries {
147		t.Run(tc.desc, func(t *testing.T) {
148			ctx, cancel := testhelper.Context()
149			defer cancel()
150
151			_, err := client.AddNamespace(ctx, tc.request)
152
153			require.Equal(t, tc.errorCode, helper.GrpcCode(err))
154
155			// Clean up
156			if tc.errorCode == codes.OK {
157				require.Equal(t, existingStorage.Name, tc.request.StorageName, "sanity check")
158
159				require.DirExists(t, filepath.Join(existingStorage.Path, tc.request.Name))
160			}
161		})
162	}
163}
164
165func TestRemoveNamespace(t *testing.T) {
166	cfg, client := setupNamespaceService(t)
167	existingStorage := cfg.Storages[0]
168
169	ctx, cancel := testhelper.Context()
170	defer cancel()
171
172	const existingNamespace = "created"
173	require.NoError(t, os.MkdirAll(filepath.Join(existingStorage.Path, existingNamespace), 0755), "test setup")
174
175	queries := []struct {
176		desc      string
177		request   *gitalypb.RemoveNamespaceRequest
178		errorCode codes.Code
179	}{
180		{
181			desc: "Namespace is successfully removed",
182			request: &gitalypb.RemoveNamespaceRequest{
183				StorageName: existingStorage.Name,
184				Name:        existingNamespace,
185			},
186			errorCode: codes.OK,
187		},
188		{
189			desc: "Idempotent on deletion",
190			request: &gitalypb.RemoveNamespaceRequest{
191				StorageName: existingStorage.Name,
192				Name:        "not-there",
193			},
194			errorCode: codes.OK,
195		},
196		{
197			desc: "no storage",
198			request: &gitalypb.RemoveNamespaceRequest{
199				StorageName: "",
200				Name:        "mepmep",
201			},
202			errorCode: codes.InvalidArgument,
203		},
204	}
205
206	for _, tc := range queries {
207		t.Run(tc.desc, func(t *testing.T) {
208			_, err := client.RemoveNamespace(ctx, tc.request)
209			require.Equal(t, tc.errorCode, helper.GrpcCode(err))
210
211			if tc.errorCode == codes.OK {
212				require.Equal(t, existingStorage.Name, tc.request.StorageName, "sanity check")
213				require.NoFileExists(t, filepath.Join(existingStorage.Path, tc.request.Name))
214			}
215		})
216	}
217}
218
219func TestRenameNamespace(t *testing.T) {
220	cfg, client := setupNamespaceService(t)
221	existingStorage := cfg.Storages[0]
222
223	ctx, cancel := testhelper.Context()
224	defer cancel()
225
226	const existingNamespace = "existing"
227	require.NoError(t, os.MkdirAll(filepath.Join(existingStorage.Path, existingNamespace), 0755))
228
229	queries := []struct {
230		desc      string
231		request   *gitalypb.RenameNamespaceRequest
232		errorCode codes.Code
233	}{
234		{
235			desc: "Renaming an existing namespace",
236			request: &gitalypb.RenameNamespaceRequest{
237				From:        existingNamespace,
238				To:          "new-path",
239				StorageName: existingStorage.Name,
240			},
241			errorCode: codes.OK,
242		},
243		{
244			desc: "No from given",
245			request: &gitalypb.RenameNamespaceRequest{
246				From:        "",
247				To:          "new-path",
248				StorageName: existingStorage.Name,
249			},
250			errorCode: codes.InvalidArgument,
251		},
252		{
253			desc: "non-existing namespace",
254			request: &gitalypb.RenameNamespaceRequest{
255				From:        "non-existing",
256				To:          "new-path",
257				StorageName: existingStorage.Name,
258			},
259			errorCode: codes.InvalidArgument,
260		},
261		{
262			desc: "existing destination namespace",
263			request: &gitalypb.RenameNamespaceRequest{
264				From:        existingNamespace,
265				To:          existingNamespace,
266				StorageName: existingStorage.Name,
267			},
268			errorCode: codes.InvalidArgument,
269		},
270	}
271
272	for _, tc := range queries {
273		t.Run(tc.desc, func(t *testing.T) {
274			_, err := client.RenameNamespace(ctx, tc.request)
275
276			require.Equal(t, tc.errorCode, helper.GrpcCode(err))
277
278			if tc.errorCode == codes.OK {
279				toDir := filepath.Join(existingStorage.Path, tc.request.To)
280				require.DirExists(t, toDir)
281				require.NoError(t, os.RemoveAll(toDir))
282			}
283		})
284	}
285}
286
287func TestRenameNamespaceWithNonexistentParentDir(t *testing.T) {
288	cfg, client := setupNamespaceService(t)
289	existingStorage := cfg.Storages[0]
290
291	ctx, cancel := testhelper.Context()
292	defer cancel()
293
294	_, err := client.AddNamespace(ctx, &gitalypb.AddNamespaceRequest{
295		StorageName: existingStorage.Name,
296		Name:        "existing",
297	})
298	require.NoError(t, err)
299
300	testCases := []struct {
301		desc      string
302		request   *gitalypb.RenameNamespaceRequest
303		errorCode codes.Code
304	}{
305		{
306			desc: "existing source, non existing target directory",
307			request: &gitalypb.RenameNamespaceRequest{
308				From:        "existing",
309				To:          "some/other/new-path",
310				StorageName: existingStorage.Name,
311			},
312			errorCode: codes.OK,
313		},
314	}
315
316	for _, tc := range testCases {
317		t.Run(tc.desc, func(t *testing.T) {
318			_, err = client.RenameNamespace(ctx, &gitalypb.RenameNamespaceRequest{
319				From:        "existing",
320				To:          "some/other/new-path",
321				StorageName: existingStorage.Name,
322			})
323			require.Equal(t, tc.errorCode, helper.GrpcCode(err))
324
325			if tc.errorCode == codes.OK {
326				storagePath := getStorageDir(t, cfg, tc.request.StorageName)
327				require.NoError(t, err)
328
329				toDir := namespacePath(storagePath, tc.request.GetTo())
330
331				require.DirExists(t, toDir)
332				require.NoError(t, os.RemoveAll(toDir))
333			}
334		})
335	}
336}
337