Write "all interfaces, any port" as ":0" (not ":") for compatibility with Go 1.5...
[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, "s3cr3tp@ssw0rd", "fetch", repo)
84                 c.Assert(err, check.ErrorMatches, `.* requested URL returned error.*`)
85         }
86 }
87
88 func (s *IntegrationSuite) TestShortToken(c *check.C) {
89         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
90                 err := s.runGit(c, "s3cr3t", "fetch", repo)
91                 c.Assert(err, check.ErrorMatches, `.* 500 while accessing.*`)
92         }
93 }
94
95 func (s *IntegrationSuite) TestShortTokenBadReq(c *check.C) {
96         for _, repo := range []string{"bogus"} {
97                 err := s.runGit(c, "s3cr3t", "fetch", repo)
98                 c.Assert(err, check.ErrorMatches, `.* requested URL returned error.*`)
99         }
100 }
101
102 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
103         arvadostest.StartAPI()
104 }
105
106 func (s *IntegrationSuite) TearDownSuite(c *check.C) {
107         arvadostest.StopAPI()
108 }
109
110 func (s *IntegrationSuite) SetUpTest(c *check.C) {
111         arvadostest.ResetEnv()
112         s.testServer = &server{}
113         var err error
114         s.tmpRepoRoot, err = ioutil.TempDir("", "arv-git-httpd")
115         c.Assert(err, check.Equals, nil)
116         s.tmpWorkdir, err = ioutil.TempDir("", "arv-git-httpd")
117         c.Assert(err, check.Equals, nil)
118         _, err = exec.Command("git", "init", s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666").Output()
119         c.Assert(err, check.Equals, nil)
120         _, 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()
121         c.Assert(err, check.Equals, nil)
122         _, err = exec.Command("git", "init", s.tmpWorkdir).Output()
123         c.Assert(err, check.Equals, nil)
124         _, 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()
125         c.Assert(err, check.Equals, nil)
126
127         _, err = exec.Command("git", "config",
128                 "--file", s.tmpWorkdir+"/.git/config",
129                 "credential.http://"+s.testServer.Addr+"/.helper",
130                 "!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred").Output()
131         c.Assert(err, check.Equals, nil)
132         _, err = exec.Command("git", "config",
133                 "--file", s.tmpWorkdir+"/.git/config",
134                 "credential.http://"+s.testServer.Addr+"/.username",
135                 "none").Output()
136         c.Assert(err, check.Equals, nil)
137
138         theConfig = &config{
139                 Addr:       ":0",
140                 GitCommand: "/usr/bin/git",
141                 Root:       s.tmpRepoRoot,
142         }
143         err = s.testServer.Start()
144         c.Assert(err, check.Equals, nil)
145
146         // Clear ARVADOS_API_TOKEN after starting up the server, to
147         // make sure arv-git-httpd doesn't use it.
148         os.Setenv("ARVADOS_API_TOKEN", "unused-token-placates-client-library")
149 }
150
151 func (s *IntegrationSuite) TearDownTest(c *check.C) {
152         var err error
153         if s.testServer != nil {
154                 err = s.testServer.Close()
155         }
156         c.Check(err, check.Equals, nil)
157         if s.tmpRepoRoot != "" {
158                 err = os.RemoveAll(s.tmpRepoRoot)
159                 c.Check(err, check.Equals, nil)
160         }
161         if s.tmpWorkdir != "" {
162                 err = os.RemoveAll(s.tmpWorkdir)
163                 c.Check(err, check.Equals, nil)
164         }
165 }
166
167 func (s *IntegrationSuite) runGit(c *check.C, token, gitCmd, repo string, args ...string) error {
168         cwd, err := os.Getwd()
169         c.Assert(err, check.Equals, nil)
170         defer os.Chdir(cwd)
171         os.Chdir(s.tmpWorkdir)
172
173         gitargs := append([]string{
174                 gitCmd, "http://" + s.testServer.Addr + "/" + repo,
175         }, args...)
176         cmd := exec.Command("git", gitargs...)
177         cmd.Env = append(os.Environ(), "ARVADOS_API_TOKEN="+token)
178         w, err := cmd.StdinPipe()
179         c.Assert(err, check.Equals, nil)
180         w.Close()
181         output, err := cmd.CombinedOutput()
182         c.Log("git ", gitargs, " => ", err)
183         c.Log(string(output))
184         if err != nil && len(output) > 0 {
185                 // If messages appeared on stderr, they are more
186                 // helpful than the err returned by CombinedOutput().
187                 //
188                 // Easier to match error strings without newlines:
189                 err = errors.New(strings.Replace(string(output), "\n", " // ", -1))
190         }
191         return err
192 }
193
194 // Make a bare arvados repo at {tmpRepoRoot}/arvados.git
195 func (s *IntegrationSuite) makeArvadosRepo(c *check.C) {
196         msg, err := exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git").CombinedOutput()
197         c.Log(string(msg))
198         c.Assert(err, check.Equals, nil)
199         msg, err = exec.Command("git", "--git-dir", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git", "fetch", "../../.git", "HEAD:master").CombinedOutput()
200         c.Log(string(msg))
201         c.Assert(err, check.Equals, nil)
202 }
203
204 // Gocheck boilerplate
205 func Test(t *testing.T) {
206         check.TestingT(t)
207 }