closes #6827
[arvados.git] / services / arv-git-httpd / server_test.go
1 package main
2
3 import (
4         "errors"
5         "io/ioutil"
6         "os"
7         "os/exec"
8         "strings"
9         "testing"
10
11         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
12         check "gopkg.in/check.v1"
13 )
14
15 var _ = check.Suite(&IntegrationSuite{})
16
17 const (
18         spectatorToken = "zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu"
19         activeToken    = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"
20         anonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
21         expiredToken   = "2ym314ysp27sk7h943q6vtc378srb06se3pq6ghurylyf3pdmx"
22 )
23
24 // IntegrationSuite tests need an API server and an arv-git-httpd server
25 type IntegrationSuite struct {
26         tmpRepoRoot string
27         tmpWorkdir  string
28         testServer  *server
29 }
30
31 func (s *IntegrationSuite) TestPathVariants(c *check.C) {
32         s.makeArvadosRepo(c)
33         for _, repo := range []string{"active/foo.git", "active/foo/.git", "arvados.git", "arvados/.git"} {
34                 err := s.runGit(c, spectatorToken, "fetch", repo)
35                 c.Assert(err, check.Equals, nil)
36         }
37 }
38
39 func (s *IntegrationSuite) TestReadonly(c *check.C) {
40         err := s.runGit(c, spectatorToken, "fetch", "active/foo.git")
41         c.Assert(err, check.Equals, nil)
42         err = s.runGit(c, spectatorToken, "push", "active/foo.git", "master:newbranchfail")
43         c.Assert(err, check.ErrorMatches, `.*HTTP code = 403.*`)
44         _, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666/.git/refs/heads/newbranchfail")
45         c.Assert(err, check.FitsTypeOf, &os.PathError{})
46 }
47
48 func (s *IntegrationSuite) TestReadwrite(c *check.C) {
49         err := s.runGit(c, activeToken, "fetch", "active/foo.git")
50         c.Assert(err, check.Equals, nil)
51         err = s.runGit(c, activeToken, "push", "active/foo.git", "master:newbranch")
52         c.Assert(err, check.Equals, nil)
53         _, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666/.git/refs/heads/newbranch")
54         c.Assert(err, check.Equals, nil)
55 }
56
57 func (s *IntegrationSuite) TestNonexistent(c *check.C) {
58         err := s.runGit(c, spectatorToken, "fetch", "thisrepodoesnotexist.git")
59         c.Assert(err, check.ErrorMatches, `.* not found.*`)
60 }
61
62 func (s *IntegrationSuite) TestMissingGitdirReadableRepository(c *check.C) {
63         err := s.runGit(c, activeToken, "fetch", "active/foo2.git")
64         c.Assert(err, check.ErrorMatches, `.* not found.*`)
65 }
66
67 func (s *IntegrationSuite) TestNoPermission(c *check.C) {
68         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
69                 err := s.runGit(c, anonymousToken, "fetch", repo)
70                 c.Assert(err, check.ErrorMatches, `.* not found.*`)
71         }
72 }
73
74 func (s *IntegrationSuite) TestExpiredToken(c *check.C) {
75         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
76                 err := s.runGit(c, expiredToken, "fetch", repo)
77                 c.Assert(err, check.ErrorMatches, `.* 500 while accessing.*`)
78         }
79 }
80
81 func (s *IntegrationSuite) TestInvalidToken(c *check.C) {
82         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
83                 err := s.runGit(c, "no-such-token-in-the-system", "fetch", repo)
84                 c.Assert(err, check.ErrorMatches, `.* 500 while accessing.*`)
85         }
86 }
87
88 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
89         arvadostest.StartAPI()
90 }
91
92 func (s *IntegrationSuite) SetUpTest(c *check.C) {
93         arvadostest.ResetEnv()
94         s.testServer = &server{}
95         var err error
96         s.tmpRepoRoot, err = ioutil.TempDir("", "arv-git-httpd")
97         c.Assert(err, check.Equals, nil)
98         s.tmpWorkdir, err = ioutil.TempDir("", "arv-git-httpd")
99         c.Assert(err, check.Equals, nil)
100         _, err = exec.Command("git", "init", s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666").Output()
101         c.Assert(err, check.Equals, nil)
102         _, err = exec.Command("sh", "-c", "cd "+s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666 && echo test >test && git add test && git -c user.name=Foo -c user.email=Foo commit -am 'foo: test'").CombinedOutput()
103         c.Assert(err, check.Equals, nil)
104         _, err = exec.Command("git", "init", s.tmpWorkdir).Output()
105         c.Assert(err, check.Equals, nil)
106         _, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && echo work >work && git add work && git -c user.name=Foo -c user.email=Foo commit -am 'workdir: test'").CombinedOutput()
107         c.Assert(err, check.Equals, nil)
108
109         _, err = exec.Command("git", "config",
110                 "--file", s.tmpWorkdir+"/.git/config",
111                 "credential.http://"+s.testServer.Addr+"/.helper",
112                 "!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred").Output()
113         c.Assert(err, check.Equals, nil)
114         _, err = exec.Command("git", "config",
115                 "--file", s.tmpWorkdir+"/.git/config",
116                 "credential.http://"+s.testServer.Addr+"/.username",
117                 "none").Output()
118         c.Assert(err, check.Equals, nil)
119
120         theConfig = &config{
121                 Addr:       ":",
122                 GitCommand: "/usr/bin/git",
123                 Root:       s.tmpRepoRoot,
124         }
125         err = s.testServer.Start()
126         c.Assert(err, check.Equals, nil)
127
128         // Clear ARVADOS_API_TOKEN after starting up the server, to
129         // make sure arv-git-httpd doesn't use it.
130         os.Setenv("ARVADOS_API_TOKEN", "unused-token-placates-client-library")
131 }
132
133 func (s *IntegrationSuite) TearDownTest(c *check.C) {
134         var err error
135         if s.testServer != nil {
136                 err = s.testServer.Close()
137         }
138         c.Check(err, check.Equals, nil)
139         if s.tmpRepoRoot != "" {
140                 err = os.RemoveAll(s.tmpRepoRoot)
141                 c.Check(err, check.Equals, nil)
142         }
143         if s.tmpWorkdir != "" {
144                 err = os.RemoveAll(s.tmpWorkdir)
145                 c.Check(err, check.Equals, nil)
146         }
147 }
148
149 func (s *IntegrationSuite) runGit(c *check.C, token, gitCmd, repo string, args ...string) error {
150         cwd, err := os.Getwd()
151         c.Assert(err, check.Equals, nil)
152         defer os.Chdir(cwd)
153         os.Chdir(s.tmpWorkdir)
154
155         gitargs := append([]string{
156                 gitCmd, "http://" + s.testServer.Addr + "/" + repo,
157         }, args...)
158         cmd := exec.Command("git", gitargs...)
159         cmd.Env = append(os.Environ(), "ARVADOS_API_TOKEN="+token)
160         w, err := cmd.StdinPipe()
161         c.Assert(err, check.Equals, nil)
162         w.Close()
163         output, err := cmd.CombinedOutput()
164         c.Log("git ", gitargs, " => ", err)
165         c.Log(string(output))
166         if err != nil && len(output) > 0 {
167                 // If messages appeared on stderr, they are more
168                 // helpful than the err returned by CombinedOutput().
169                 //
170                 // Easier to match error strings without newlines:
171                 err = errors.New(strings.Replace(string(output), "\n", " // ", -1))
172         }
173         return err
174 }
175
176 // Make a bare arvados repo at {tmpRepoRoot}/arvados.git
177 func (s *IntegrationSuite) makeArvadosRepo(c *check.C) {
178         msg, err := exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git").CombinedOutput()
179         c.Log(string(msg))
180         c.Assert(err, check.Equals, nil)
181         msg, err = exec.Command("git", "--git-dir", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git", "fetch", "../../.git", "HEAD:master").CombinedOutput()
182         c.Log(string(msg))
183         c.Assert(err, check.Equals, nil)
184 }
185
186 // Gocheck boilerplate
187 func Test(t *testing.T) {
188         check.TestingT(t)
189 }