Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaime Martinez <jmartinez@gitlab.com>2022-06-20 04:24:08 +0300
committerJaime Martinez <jmartinez@gitlab.com>2022-06-20 04:24:08 +0300
commit24344cc0b0c24e16939a93a610dc09eacece6deb (patch)
tree3a51fd0070974cf1d8af7e1478d4cb834ef6f2b6
parentf370e42bd5ffd1745081569e343251e38b4fc893 (diff)
parent29f275ebd38c3cc8887da70a1f886e011cb688b5 (diff)
Merge branch 'feat/cmd-stub' into 'master'
feat: extract gitlab stub server in a separate package See merge request gitlab-org/gitlab-pages!673
-rw-r--r--test/acceptance/artifacts_test.go4
-rw-r--r--test/acceptance/helpers_test.go131
-rw-r--r--test/acceptance/serving_test.go24
-rw-r--r--test/acceptance/stub_test.go75
-rw-r--r--test/gitlabstub/api_responses.go (renamed from test/acceptance/testdata/api_responses.go)34
-rw-r--r--test/gitlabstub/cmd/server/main.go53
-rw-r--r--test/gitlabstub/handlers.go154
-rw-r--r--test/gitlabstub/option.go32
-rw-r--r--test/gitlabstub/server.go43
9 files changed, 322 insertions, 228 deletions
diff --git a/test/acceptance/artifacts_test.go b/test/acceptance/artifacts_test.go
index 65018c2c..e56a1390 100644
--- a/test/acceptance/artifacts_test.go
+++ b/test/acceptance/artifacts_test.go
@@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
+ "gitlab.com/gitlab-org/gitlab-pages/test/gitlabstub"
)
func TestArtifactProxyRequest(t *testing.T) {
@@ -149,7 +150,8 @@ func TestArtifactProxyRequest(t *testing.T) {
}
func TestPrivateArtifactProxyRequest(t *testing.T) {
- testServer := NewGitlabUnstartedServerStub(t, &stubOpts{})
+ testServer, err := gitlabstub.NewUnstartedServer()
+ require.NoError(t, err)
keyFile, certFile := CreateHTTPSFixtureFiles(t)
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
diff --git a/test/acceptance/helpers_test.go b/test/acceptance/helpers_test.go
index 0ac4a40b..4c365c5e 100644
--- a/test/acceptance/helpers_test.go
+++ b/test/acceptance/helpers_test.go
@@ -5,14 +5,10 @@ import (
"context"
"crypto/tls"
"crypto/x509"
- "encoding/json"
- "errors"
"fmt"
"io"
- "io/fs"
"net"
"net/http"
- "net/http/httptest"
"os"
"os/exec"
"path"
@@ -21,14 +17,13 @@ import (
"testing"
"time"
- "github.com/gorilla/mux"
"github.com/pires/go-proxyproto"
"github.com/stretchr/testify/require"
"golang.org/x/net/nettest"
"gitlab.com/gitlab-org/gitlab-pages/internal/request"
"gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
- "gitlab.com/gitlab-org/gitlab-pages/test/acceptance/testdata"
+ "gitlab.com/gitlab-org/gitlab-pages/test/gitlabstub"
)
// The HTTPS certificate isn't signed by anyone. This http client is set up
@@ -250,11 +245,8 @@ func RunPagesProcess(t *testing.T, opts ...processOption) *LogCaptureBuffer {
opt(&processCfg)
}
- if processCfg.gitlabStubOpts.pagesRoot == "" {
- processCfg.gitlabStubOpts.pagesRoot = wd
- }
-
- source := NewGitlabUnstartedServerStub(t, processCfg.gitlabStubOpts)
+ source, err := gitlabstub.NewUnstartedServer(processCfg.gitlabStubOpts...)
+ require.NoError(t, err)
source.Start()
gitLabAPISecretKey := CreateGitLabAPISecretKeyFixtureFile(t)
@@ -465,123 +457,6 @@ func ClientWithConfig(tlsConfig *tls.Config) (*http.Client, func()) {
return client, tr.CloseIdleConnections
}
-type stubOpts struct {
- m sync.RWMutex
- apiCalled bool
- pagesHandler http.HandlerFunc
- pagesRoot string
- delay time.Duration
-}
-
-func NewGitlabUnstartedServerStub(t *testing.T, opts *stubOpts) *httptest.Server {
- t.Helper()
- require.NotNil(t, opts)
-
- router := mux.NewRouter()
-
- pagesHandler := defaultAPIHandler(t, opts)
- if opts.pagesHandler != nil {
- pagesHandler = opts.pagesHandler
- }
-
- router.HandleFunc("/api/v4/internal/pages", pagesHandler)
-
- authHandler := defaultAuthHandler(t)
- router.HandleFunc("/oauth/token", authHandler)
-
- userHandler := defaultUserHandler(t)
- router.HandleFunc("/api/v4/user", userHandler)
-
- router.HandleFunc("/api/v4/projects/{project_id:[0-9]+}/pages_access", func(w http.ResponseWriter, r *http.Request) {
- handleAccessControlRequests(t, w, r)
- })
-
- router.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- ok := handleAccessControlArtifactRequests(t, w, r)
- require.True(t, ok)
- })
-
- return httptest.NewUnstartedServer(router)
-}
-
-func (o *stubOpts) setAPICalled(v bool) {
- o.m.Lock()
- defer o.m.Unlock()
-
- o.apiCalled = v
-}
-
-func (o *stubOpts) getAPICalled() bool {
- o.m.RLock()
- defer o.m.RUnlock()
-
- return o.apiCalled
-}
-
-func lookupFromFile(t *testing.T, domain string, w http.ResponseWriter) {
- fixture, err := os.Open("../../shared/lookups/" + domain + ".json")
- if errors.Is(err, fs.ErrNotExist) {
- w.WriteHeader(http.StatusNoContent)
-
- t.Logf("GitLab domain %s source stub served 204", domain)
- return
- }
-
- defer fixture.Close()
- require.NoError(t, err)
-
- _, err = io.Copy(w, fixture)
- require.NoError(t, err)
-
- t.Logf("GitLab domain %s source stub served lookup", domain)
-}
-
-func defaultAPIHandler(t *testing.T, opts *stubOpts) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- domain := r.URL.Query().Get("host")
- if domain == "127.0.0.1" {
- // shortcut for healthy checkup done by WaitUntilRequestSucceeds
- w.WriteHeader(http.StatusNoContent)
- return
- }
- // to test slow responses from the API
- if opts.delay > 0 {
- time.Sleep(opts.delay)
- }
-
- opts.setAPICalled(true)
-
- // check if predefined response exists
- if responseFn, ok := testdata.DomainResponses[domain]; ok {
- err := json.NewEncoder(w).Encode(responseFn(t, opts.pagesRoot))
- require.NoError(t, err)
- return
- }
-
- // serve lookup from files
- lookupFromFile(t, domain, w)
- }
-}
-
-func defaultAuthHandler(t *testing.T) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- require.Equal(t, "POST", r.Method)
- err := json.NewEncoder(w).Encode(struct {
- AccessToken string `json:"access_token"`
- }{
- AccessToken: "abc",
- })
- require.NoError(t, err)
- }
-}
-
-func defaultUserHandler(t *testing.T) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusOK)
- }
-}
-
func newConfigFile(t *testing.T, configs ...string) string {
t.Helper()
diff --git a/test/acceptance/serving_test.go b/test/acceptance/serving_test.go
index 1808b5e7..92e9c8c7 100644
--- a/test/acceptance/serving_test.go
+++ b/test/acceptance/serving_test.go
@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
+ "gitlab.com/gitlab-org/gitlab-pages/test/gitlabstub"
)
func TestUnknownHostReturnsNotFound(t *testing.T) {
@@ -358,9 +359,6 @@ func TestKnownHostInReverseProxySetupReturns200(t *testing.T) {
func TestDomainResolverError(t *testing.T) {
domainName := "new-source-test.gitlab.io"
- opts := &stubOpts{
- apiCalled: false,
- }
tests := map[string]struct {
status int
@@ -380,14 +378,17 @@ func TestDomainResolverError(t *testing.T) {
for name, test := range tests {
t.Run(name, func(t *testing.T) {
+ called := false
+
// handler setup
- opts.pagesHandler = func(w http.ResponseWriter, r *http.Request) {
+ pagesHandler := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("host") != domainName {
w.WriteHeader(http.StatusNoContent)
return
}
- opts.setAPICalled(true)
+ called = true
+
if test.panic {
panic("server failed")
}
@@ -404,7 +405,7 @@ func TestDomainResolverError(t *testing.T) {
RunPagesProcess(t,
withListeners([]ListenSpec{httpListener}),
- withStubOptions(opts),
+ withStubOptions(gitlabstub.WithPagesHandler(pagesHandler)),
withArguments(pagesArgs),
)
@@ -412,7 +413,7 @@ func TestDomainResolverError(t *testing.T) {
require.NoError(t, err)
testhelpers.Close(t, response.Body)
- require.True(t, opts.getAPICalled(), "api must have been called")
+ require.True(t, called, "api must have been called")
require.Equal(t, http.StatusBadGateway, response.StatusCode)
@@ -556,16 +557,15 @@ func TestDiskDisabledFailsToServeFileAndLocalContent(t *testing.T) {
}
func TestSlowRequests(t *testing.T) {
- opts := &stubOpts{
- delay: 250 * time.Millisecond,
- }
+ delay := 250 * time.Millisecond
+
logBuf := RunPagesProcess(t,
- withStubOptions(opts),
+ withStubOptions(gitlabstub.WithDelay(delay)),
withExtraArgument("gitlab-retrieval-timeout", "1s"),
withListeners([]ListenSpec{httpListener}),
)
- ctx, cancel := context.WithTimeout(context.Background(), opts.delay/2)
+ ctx, cancel := context.WithTimeout(context.Background(), delay/2)
defer cancel()
url := httpListener.URL("/index.html")
diff --git a/test/acceptance/stub_test.go b/test/acceptance/stub_test.go
index 0fcdfa2e..3d54b4d4 100644
--- a/test/acceptance/stub_test.go
+++ b/test/acceptance/stub_test.go
@@ -2,15 +2,13 @@ package acceptance_test
import (
"fmt"
- "net/http"
"os"
- "regexp"
"testing"
- "time"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/fixture"
+ "gitlab.com/gitlab-org/gitlab-pages/test/gitlabstub"
)
var defaultProcessConfig = processConfig{
@@ -19,7 +17,7 @@ var defaultProcessConfig = processConfig{
listeners: supportedListeners(),
envs: []string{},
extraArgs: []string{},
- gitlabStubOpts: &stubOpts{},
+ gitlabStubOpts: []gitlabstub.Option{},
}
type processConfig struct {
@@ -28,7 +26,7 @@ type processConfig struct {
listeners []ListenSpec
envs []string
extraArgs []string
- gitlabStubOpts *stubOpts
+ gitlabStubOpts []gitlabstub.Option
}
type processOption func(*processConfig)
@@ -60,7 +58,7 @@ func withArguments(args []string) processOption {
}
}
-func withStubOptions(opts *stubOpts) processOption {
+func withStubOptions(opts ...gitlabstub.Option) processOption {
return func(config *processConfig) {
config.gitlabStubOpts = opts
}
@@ -98,68 +96,3 @@ func CreateGitLabAPISecretKeyFixtureFile(t *testing.T) (filepath string) {
return secretfile.Name()
}
-
-func handleAccessControlArtifactRequests(t *testing.T, w http.ResponseWriter, r *http.Request) bool {
- authorization := r.Header.Get("Authorization")
-
- switch {
- case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/delayed_200.html`).MatchString(r.URL.Path):
- sleepIfAuthorized(t, authorization, w)
- return true
- case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/404.html`).MatchString(r.URL.Path):
- w.WriteHeader(http.StatusNotFound)
- return true
- case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/500.html`).MatchString(r.URL.Path):
- returnIfAuthorized(t, authorization, w, http.StatusInternalServerError)
- return true
- case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/200.html`).MatchString(r.URL.Path):
- returnIfAuthorized(t, authorization, w, http.StatusOK)
- return true
- case regexp.MustCompile(`/api/v4/projects/group/subgroup/private/jobs/\d+/artifacts/200.html`).MatchString(r.URL.Path):
- returnIfAuthorized(t, authorization, w, http.StatusOK)
- return true
- default:
- return false
- }
-}
-
-func handleAccessControlRequests(t *testing.T, w http.ResponseWriter, r *http.Request) {
- allowedProjects := regexp.MustCompile(`/api/v4/projects/1\d{3}/pages_access`)
- deniedProjects := regexp.MustCompile(`/api/v4/projects/2\d{3}/pages_access`)
- invalidTokenProjects := regexp.MustCompile(`/api/v4/projects/3\d{3}/pages_access`)
-
- switch {
- case allowedProjects.MatchString(r.URL.Path):
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusOK)
- case deniedProjects.MatchString(r.URL.Path):
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusUnauthorized)
- case invalidTokenProjects.MatchString(r.URL.Path):
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusUnauthorized)
- fmt.Fprint(w, "{\"error\":\"invalid_token\"}")
- default:
- t.Logf("Unexpected r.URL.RawPath: %q", r.URL.Path)
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.WriteHeader(http.StatusNotFound)
- }
-}
-
-func returnIfAuthorized(t *testing.T, authorization string, w http.ResponseWriter, status int) {
- if authorization != "" {
- require.Equal(t, "Bearer abc", authorization)
- w.WriteHeader(status)
- } else {
- w.WriteHeader(http.StatusNotFound)
- }
-}
-
-func sleepIfAuthorized(t *testing.T, authorization string, w http.ResponseWriter) {
- if authorization != "" {
- require.Equal(t, "Bearer abc", authorization)
- time.Sleep(2 * time.Second)
- } else {
- w.WriteHeader(http.StatusNotFound)
- }
-}
diff --git a/test/acceptance/testdata/api_responses.go b/test/gitlabstub/api_responses.go
index baa70f8f..3ebf4e1f 100644
--- a/test/acceptance/testdata/api_responses.go
+++ b/test/gitlabstub/api_responses.go
@@ -1,25 +1,23 @@
-package testdata
+package gitlabstub
import (
"crypto/sha256"
"encoding/hex"
"fmt"
+ "log"
"os"
"path/filepath"
"strings"
- "testing"
-
- "github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/api"
)
-type responseFn func(*testing.T, string) api.VirtualDomain
+type responseFn func(string) api.VirtualDomain
-// DomainResponses holds the predefined API responses for certain domains
+// domainResponses holds the predefined API responses for certain domains
// that can be used with the GitLab API stub in acceptance tests
// Assume the working dir is inside shared/pages/
-var DomainResponses = map[string]responseFn{
+var domainResponses = map[string]responseFn{
"zip-from-disk.gitlab.io": customDomain(projectConfig{
projectID: 123,
pathOnDisk: "@hashed/zip-from-disk.gitlab.io",
@@ -120,9 +118,7 @@ var DomainResponses = map[string]responseFn{
// generateVirtualDomainFromDir walks the subdirectory inside of shared/pages/ to find any zip archives.
// It works for subdomains of pages-domain but not for custom domains (yet)
func generateVirtualDomainFromDir(dir, rootDomain string, perPrefixConfig map[string]projectConfig) responseFn {
- return func(t *testing.T, wd string) api.VirtualDomain {
- t.Helper()
-
+ return func(wd string) api.VirtualDomain {
var foundZips []string
// walk over dir and save any paths containing a `.zip` file
@@ -131,10 +127,14 @@ func generateVirtualDomainFromDir(dir, rootDomain string, perPrefixConfig map[st
cleanDir := filepath.Join(wd, dir)
// make sure resolved path inside dir is under wd to avoid https://securego.io/docs/rules/g304.html
- require.Truef(t, strings.HasPrefix(cleanDir, wd), "path %q outside of wd %q", cleanDir, wd)
+ if !strings.HasPrefix(cleanDir, wd) {
+ log.Fatalf("path %q outside of wd %q", cleanDir, wd)
+ }
- filepath.Walk(cleanDir, func(path string, info os.FileInfo, err error) error {
- require.NoError(t, err)
+ walkErr := filepath.Walk(cleanDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
if strings.HasSuffix(info.Name(), ".zip") {
project := strings.TrimPrefix(path, wd+"/"+dir)
@@ -144,6 +144,10 @@ func generateVirtualDomainFromDir(dir, rootDomain string, perPrefixConfig map[st
return nil
})
+ if walkErr != nil {
+ log.Fatal(walkErr)
+ }
+
lookupPaths := make([]api.LookupPath, 0, len(foundZips))
// generate lookup paths
for _, project := range foundZips {
@@ -200,9 +204,7 @@ type projectConfig struct {
// customDomain with per project config
func customDomain(config projectConfig) responseFn {
- return func(t *testing.T, wd string) api.VirtualDomain {
- t.Helper()
-
+ return func(wd string) api.VirtualDomain {
sourcePath := fmt.Sprintf("file://%s/%s/public.zip", wd, config.pathOnDisk)
sum := sha256.Sum256([]byte(sourcePath))
sha := hex.EncodeToString(sum[:])
diff --git a/test/gitlabstub/cmd/server/main.go b/test/gitlabstub/cmd/server/main.go
new file mode 100644
index 00000000..3e33daaa
--- /dev/null
+++ b/test/gitlabstub/cmd/server/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "flag"
+ "log"
+ "net/http"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "gitlab.com/gitlab-org/gitlab-pages/test/gitlabstub"
+)
+
+var (
+ pagesRoot = flag.String("pages-root", "shared/pages", "The directory where pages are stored")
+)
+
+func main() {
+ flag.Parse()
+
+ if err := os.Chdir(*pagesRoot); err != nil {
+ log.Fatalf("error chdir in %s: %v", *pagesRoot, err)
+ }
+
+ wd, err := os.Getwd()
+ if err != nil {
+ log.Fatalf("error getting current dir: %v", err)
+ }
+
+ server, err := gitlabstub.NewUnstartedServer(gitlabstub.WithPagesRoot(wd))
+ if err != nil {
+ log.Fatalf("error starting the server: %v", err)
+ }
+
+ server.Start()
+
+ log.Printf("listening on %s\n", server.URL)
+
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
+
+ <-sigChan
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ if err := server.Config.Shutdown(ctx); err != nil && !errors.Is(err, http.ErrServerClosed) {
+ log.Fatalf("error shutting down %v", err)
+ }
+}
diff --git a/test/gitlabstub/handlers.go b/test/gitlabstub/handlers.go
new file mode 100644
index 00000000..df09d914
--- /dev/null
+++ b/test/gitlabstub/handlers.go
@@ -0,0 +1,154 @@
+package gitlabstub
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "log"
+ "net/http"
+ "os"
+ "regexp"
+ "time"
+)
+
+func defaultAPIHandler(delay time.Duration, pagesRoot string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ domain := r.URL.Query().Get("host")
+ if domain == "127.0.0.1" {
+ // shortcut for healthy checkup done by WaitUntilRequestSucceeds
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+ // to test slow responses from the API
+ if delay > 0 {
+ time.Sleep(delay)
+ }
+
+ // check if predefined response exists
+ if responseFn, ok := domainResponses[domain]; ok {
+ if err := json.NewEncoder(w).Encode(responseFn(pagesRoot)); err != nil {
+ log.Fatal(err)
+ }
+ return
+ }
+
+ // serve lookup from files
+ lookupFromFile(domain, w)
+ }
+}
+
+func defaultAuthHandler() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return
+ }
+
+ err := json.NewEncoder(w).Encode(struct {
+ AccessToken string `json:"access_token"`
+ }{
+ AccessToken: "abc",
+ })
+
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
+func defaultUserHandler() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.Header.Get("Authorization") != "Bearer abc" {
+ w.WriteHeader(http.StatusForbidden)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ }
+}
+
+func lookupFromFile(domain string, w http.ResponseWriter) {
+ fixture, err := os.Open("../../shared/lookups/" + domain + ".json")
+ if errors.Is(err, fs.ErrNotExist) {
+ w.WriteHeader(http.StatusNoContent)
+
+ log.Printf("GitLab domain %s source stub served 204", domain)
+ return
+ }
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer fixture.Close()
+
+ if _, err = io.Copy(w, fixture); err != nil {
+ log.Fatal(err)
+ }
+
+ log.Printf("GitLab domain %s source stub served lookup", domain)
+}
+
+func handleAccessControlArtifactRequests(w http.ResponseWriter, r *http.Request) {
+ authorization := r.Header.Get("Authorization")
+
+ switch {
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/delayed_200.html`).MatchString(r.URL.Path):
+ sleepIfAuthorized(authorization, w)
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/404.html`).MatchString(r.URL.Path):
+ w.WriteHeader(http.StatusNotFound)
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/500.html`).MatchString(r.URL.Path):
+ returnIfAuthorized(authorization, w, http.StatusInternalServerError)
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/200.html`).MatchString(r.URL.Path):
+ returnIfAuthorized(authorization, w, http.StatusOK)
+ case regexp.MustCompile(`/api/v4/projects/group/subgroup/private/jobs/\d+/artifacts/200.html`).MatchString(r.URL.Path):
+ returnIfAuthorized(authorization, w, http.StatusOK)
+ default:
+ log.Printf("Unexpected r.URL.RawPath: %q", r.URL.Path)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusTeapot)
+ }
+}
+
+func handleAccessControlRequests(w http.ResponseWriter, r *http.Request) {
+ authorization := r.Header.Get("Authorization")
+
+ switch {
+ case regexp.MustCompile(`/api/v4/projects/1\d{3}/pages_access`).MatchString(r.URL.Path):
+ returnIfAuthorized(authorization, w, http.StatusOK)
+ case regexp.MustCompile(`/api/v4/projects/2\d{3}/pages_access`).MatchString(r.URL.Path):
+ returnIfAuthorized(authorization, w, http.StatusUnauthorized)
+ case regexp.MustCompile(`/api/v4/projects/3\d{3}/pages_access`).MatchString(r.URL.Path):
+ returnIfAuthorized(authorization, w, http.StatusUnauthorized)
+ fmt.Fprint(w, "{\"error\":\"invalid_token\"}")
+ default:
+ log.Printf("Unexpected r.URL.RawPath: %q", r.URL.Path)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusTeapot)
+ }
+}
+
+func returnIfAuthorized(authorization string, w http.ResponseWriter, status int) {
+ if authorization != "" {
+ checkAuth(authorization)
+ w.WriteHeader(status)
+ } else {
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
+
+func sleepIfAuthorized(authorization string, w http.ResponseWriter) {
+ if authorization != "" {
+ checkAuth(authorization)
+ time.Sleep(2 * time.Second)
+ } else {
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
+
+func checkAuth(authorization string) {
+ if authorization != "Bearer abc" {
+ log.Fatalf("expected bearer abc but go %s", authorization)
+ }
+}
diff --git a/test/gitlabstub/option.go b/test/gitlabstub/option.go
new file mode 100644
index 00000000..d55abec2
--- /dev/null
+++ b/test/gitlabstub/option.go
@@ -0,0 +1,32 @@
+package gitlabstub
+
+import (
+ "net/http"
+ "time"
+)
+
+type config struct {
+ pagesHandler http.HandlerFunc
+ pagesRoot string
+ delay time.Duration
+}
+
+type Option func(*config)
+
+func WithPagesHandler(ph http.HandlerFunc) Option {
+ return func(sc *config) {
+ sc.pagesHandler = ph
+ }
+}
+
+func WithPagesRoot(pagesRoot string) Option {
+ return func(sc *config) {
+ sc.pagesRoot = pagesRoot
+ }
+}
+
+func WithDelay(delay time.Duration) Option {
+ return func(sc *config) {
+ sc.delay = delay
+ }
+}
diff --git a/test/gitlabstub/server.go b/test/gitlabstub/server.go
new file mode 100644
index 00000000..5cf3dacf
--- /dev/null
+++ b/test/gitlabstub/server.go
@@ -0,0 +1,43 @@
+package gitlabstub
+
+import (
+ "net/http/httptest"
+ "os"
+
+ "github.com/gorilla/mux"
+)
+
+func NewUnstartedServer(opts ...Option) (*httptest.Server, error) {
+ wd, err := os.Getwd()
+ if err != nil {
+ return nil, err
+ }
+
+ conf := &config{
+ pagesRoot: wd,
+ }
+
+ for _, so := range opts {
+ so(conf)
+ }
+
+ if conf.pagesHandler == nil {
+ conf.pagesHandler = defaultAPIHandler(conf.delay, conf.pagesRoot)
+ }
+
+ router := mux.NewRouter()
+
+ router.HandleFunc("/api/v4/internal/pages", conf.pagesHandler)
+
+ authHandler := defaultAuthHandler()
+ router.HandleFunc("/oauth/token", authHandler)
+
+ userHandler := defaultUserHandler()
+ router.HandleFunc("/api/v4/user", userHandler)
+
+ router.HandleFunc("/api/v4/projects/{project_id:[0-9]+}/pages_access", handleAccessControlRequests)
+
+ router.PathPrefix("/").HandlerFunc(handleAccessControlArtifactRequests)
+
+ return httptest.NewUnstartedServer(router), nil
+}