diff options
Diffstat (limited to 'internal/serving/disk/projectroot')
-rw-r--r-- | internal/serving/disk/projectroot/root.go | 47 | ||||
-rw-r--r-- | internal/serving/disk/projectroot/root_test.go | 89 |
2 files changed, 136 insertions, 0 deletions
diff --git a/internal/serving/disk/projectroot/root.go b/internal/serving/disk/projectroot/root.go new file mode 100644 index 00000000..64f830c0 --- /dev/null +++ b/internal/serving/disk/projectroot/root.go @@ -0,0 +1,47 @@ +package projectroot + +import ( + "context" + "os" + "path/filepath" + + "gitlab.com/gitlab-org/gitlab-pages/internal/feature" + "gitlab.com/gitlab-org/gitlab-pages/internal/vfs" +) + +// projectRoot implements the more low-level vfs.Root interface and can be used in its +// stead. The difference is, it always resolves the files inside the project's +// rootDirectory by prepending that dir to any open request. +type projectRoot struct { + rootDirectory string + vfsRoot vfs.Root +} + +func New(rootDirectory string, vfsRoot vfs.Root) vfs.Root { + if !feature.ConfigurableRoot.Enabled() || rootDirectory == "" { + // In case the GitLab API is not up-to-date this string may be empty. + // In that case default to the legacy behavior + rootDirectory = "public" + } + + return &projectRoot{ + rootDirectory: rootDirectory, + vfsRoot: vfsRoot, + } +} + +func (r *projectRoot) Open(ctx context.Context, name string) (vfs.File, error) { + return r.vfsRoot.Open(ctx, r.getPath(name)) +} + +func (r *projectRoot) Lstat(ctx context.Context, name string) (os.FileInfo, error) { + return r.vfsRoot.Lstat(ctx, r.getPath(name)) +} + +func (r *projectRoot) Readlink(ctx context.Context, name string) (string, error) { + return r.vfsRoot.Readlink(ctx, r.getPath(name)) +} + +func (r *projectRoot) getPath(name string) string { + return filepath.Join(r.rootDirectory, name) +} diff --git a/internal/serving/disk/projectroot/root_test.go b/internal/serving/disk/projectroot/root_test.go new file mode 100644 index 00000000..b8549d10 --- /dev/null +++ b/internal/serving/disk/projectroot/root_test.go @@ -0,0 +1,89 @@ +package projectroot + +import ( + "context" + "os" + "strconv" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-pages/internal/feature" + "gitlab.com/gitlab-org/gitlab-pages/internal/vfs" +) + +// In case this moch struct ever grows in complexity, +// consider using mockgen instead: +// e.g. internal/domain/mock/resolver_mock.go +type mockRoot struct { + lstatCalledWith string + readlinkCalledWith string + openCalledWith string +} + +func (m *mockRoot) Lstat(ctx context.Context, name string) (os.FileInfo, error) { + m.lstatCalledWith = name + return nil, nil +} + +func (m *mockRoot) Readlink(ctx context.Context, name string) (string, error) { + m.readlinkCalledWith = name + return "", nil +} + +func (m *mockRoot) Open(ctx context.Context, name string) (vfs.File, error) { + m.openCalledWith = name + return nil, nil +} + +func TestProjectRoot(t *testing.T) { + tests := map[string]struct { + path string + rootDir string + expectedPath string + featureEnabled bool + }{ + "when the root dir is provided": { + path: "some/path", + rootDir: "my_root_dir", + expectedPath: "my_root_dir/some/path", + featureEnabled: true, + }, + "when the root dir is not provided": { + path: "some/path", + rootDir: "", + expectedPath: "public/some/path", + featureEnabled: true, + }, + "when the feature is disabled": { + path: "some/path", + rootDir: "my_root_dir", + expectedPath: "public/some/path", + featureEnabled: false, + }, + "when the feature is disabled and no root path is provided": { + path: "some/path", + rootDir: "", + expectedPath: "public/some/path", + featureEnabled: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Setenv(feature.ConfigurableRoot.EnvVariable, + strconv.FormatBool(test.featureEnabled)) + originalRoot := &mockRoot{} + wrappedRoot := New(test.rootDir, originalRoot) + + wrappedRoot.Lstat(context.Background(), test.path) + require.Equal(t, test.expectedPath, originalRoot.lstatCalledWith) + + wrappedRoot.Readlink(context.Background(), test.path) + require.Equal(t, test.expectedPath, originalRoot.readlinkCalledWith) + + wrappedRoot.Open(context.Background(), test.path) + require.Equal(t, test.expectedPath, originalRoot.openCalledWith) + }) + } +} |