From ce00d933aa8da17ad79cf80ace06d622879910b4 Mon Sep 17 00:00:00 2001 From: stephen mcquay Date: Tue, 23 Feb 2016 22:09:29 -0800 Subject: [PATCH] vcs as string, not int. No longer do we keep track of const iota style. Just encode the behavior in the server for defaults, add a validation function, call it a day. Change-Id: I603e9dd287a57084c78c543f1ce83b0acf47a765 --- api_test.go | 66 +++++++++++++++++++++++++++++++++++------------ cmd/vaind/main.go | 4 +-- server.go | 20 +++++++++++++- storage.go | 11 +++++++- storage_test.go | 51 +++++++++++++++++++++++++++++++++--- vain.go | 60 ++++++++---------------------------------- vain_test.go | 22 +++++++++------- 7 files changed, 151 insertions(+), 83 deletions(-) diff --git a/api_test.go b/api_test.go index f7c60fc..49f0da9 100644 --- a/api_test.go +++ b/api_test.go @@ -2,21 +2,21 @@ package vain import ( "bytes" + "encoding/json" "fmt" "io" "net/http" "net/http/httptest" + "net/url" "strings" "testing" ) func TestAdd(t *testing.T) { ms := NewSimpleStore("") - s := &Server{ - storage: ms, - } - ts := httptest.NewServer(s) - s.hostname = ts.URL + sm := http.NewServeMux() + _ = NewServer(sm, ms) + ts := httptest.NewServer(sm) resp, err := http.Get(ts.URL) if err != nil { t.Errorf("couldn't GET: %v", err) @@ -36,8 +36,25 @@ func TestAdd(t *testing.T) { t.Errorf("started with something in it; got %d, want %d", len(ms.p), 0) } - good := fmt.Sprintf("%s/foo", ts.URL) - resp, err = http.Post(good, "application/json", strings.NewReader(`{"repo": "https://s.mcquay.me/sm/vain"}`)) + { + u := fmt.Sprintf("%s/db/", ts.URL) + resp, err := http.Get(u) + if err != nil { + t.Error(err) + } + buf := &bytes.Buffer{} + io.Copy(buf, resp.Body) + pkgs := []Package{} + if err := json.NewDecoder(buf).Decode(&pkgs); err != nil { + t.Errorf("problem parsing json: %v, \n%q", err, buf) + } + if got, want := len(pkgs), 0; got != want { + t.Errorf("should have empty pkg list; got %d, want %d", got, want) + } + } + + u := fmt.Sprintf("%s/foo", ts.URL) + resp, err = http.Post(u, "application/json", strings.NewReader(`{"repo": "https://s.mcquay.me/sm/vain"}`)) if err != nil { t.Errorf("couldn't POST: %v", err) } @@ -46,6 +63,11 @@ func TestAdd(t *testing.T) { t.Errorf("storage should have something in it; got %d, want %d", len(ms.p), 1) } + ur, err := url.Parse(ts.URL) + if err != nil { + t.Error(err) + } + good := fmt.Sprintf("%s/foo", ur.Host) p, ok := ms.p[good] if !ok { t.Fatalf("did not find package for %s; should have posted a valid package", good) @@ -56,8 +78,8 @@ func TestAdd(t *testing.T) { if want := "https://s.mcquay.me/sm/vain"; p.Repo != want { t.Errorf("repo did not go through as expected; got %q, want %q", p.Repo, want) } - if want := Git; p.Vcs != want { - t.Errorf("Vcs did not go through as expected; got %q, want %q", p.Vcs, want) + if got, want := p.Vcs, "git"; got != want { + t.Errorf("Vcs did not go through as expected; got %q, want %q", got, want) } resp, err = http.Get(ts.URL) @@ -75,6 +97,23 @@ func TestAdd(t *testing.T) { if got, want := strings.Count(buf.String(), "meta"), 1; got != want { t.Errorf("did not find all the tags I need; got %d, want %d", got, want) } + + { + u := fmt.Sprintf("%s/db/", ts.URL) + resp, err := http.Get(u) + if err != nil { + t.Error(err) + } + buf := &bytes.Buffer{} + io.Copy(buf, resp.Body) + pkgs := []Package{} + if err := json.NewDecoder(buf).Decode(&pkgs); err != nil { + t.Errorf("problem parsing json: %v, \n%q", err, buf) + } + if got, want := len(pkgs), 1; got != want { + t.Errorf("should (mildly) populated pkg list; got %d, want %d", got, want) + } + } } func TestInvalidPath(t *testing.T) { @@ -83,7 +122,6 @@ func TestInvalidPath(t *testing.T) { storage: ms, } ts := httptest.NewServer(s) - s.hostname = ts.URL resp, err := http.Post(ts.URL, "application/json", strings.NewReader(`{"repo": "https://s.mcquay.me/sm/vain"}`)) if err != nil { @@ -103,7 +141,6 @@ func TestCannotDuplicateExistingPath(t *testing.T) { storage: ms, } ts := httptest.NewServer(s) - s.hostname = ts.URL url := fmt.Sprintf("%s/foo", ts.URL) resp, err := http.Post(url, "application/json", strings.NewReader(`{"repo": "https://s.mcquay.me/sm/vain"}`)) @@ -128,7 +165,6 @@ func TestCannotAddExistingSubPath(t *testing.T) { storage: ms, } ts := httptest.NewServer(s) - s.hostname = ts.URL url := fmt.Sprintf("%s/foo/bar", ts.URL) resp, err := http.Post(url, "application/json", strings.NewReader(`{"repo": "https://s.mcquay.me/sm/vain"}`)) @@ -156,7 +192,6 @@ func TestMissingRepo(t *testing.T) { storage: ms, } ts := httptest.NewServer(s) - s.hostname = ts.URL url := fmt.Sprintf("%s/foo", ts.URL) resp, err := http.Post(url, "application/json", strings.NewReader(`{}`)) if err != nil { @@ -176,7 +211,6 @@ func TestBadJson(t *testing.T) { storage: ms, } ts := httptest.NewServer(s) - s.hostname = ts.URL url := fmt.Sprintf("%s/foo", ts.URL) resp, err := http.Post(url, "application/json", strings.NewReader(`{`)) if err != nil { @@ -196,7 +230,6 @@ func TestUnsupportedMethod(t *testing.T) { storage: ms, } ts := httptest.NewServer(s) - s.hostname = ts.URL url := fmt.Sprintf("%s/foo", ts.URL) client := &http.Client{} req, err := http.NewRequest("PUT", url, nil) @@ -215,9 +248,8 @@ func TestUnsupportedMethod(t *testing.T) { func TestNewServer(t *testing.T) { ms := NewSimpleStore("") sm := http.NewServeMux() - s := NewServer(sm, ms, "foo") + s := NewServer(sm, ms) ts := httptest.NewServer(s) - s.hostname = ts.URL url := fmt.Sprintf("%s/foo", ts.URL) client := &http.Client{} req, err := http.NewRequest("PUT", url, nil) diff --git a/cmd/vaind/main.go b/cmd/vaind/main.go index d3dacf0..af50725 100644 --- a/cmd/vaind/main.go +++ b/cmd/vaind/main.go @@ -65,7 +65,7 @@ func main() { c := &config{ Port: 4040, } - if err := envconfig.Process("ysv", c); err != nil { + if err := envconfig.Process("vain", c); err != nil { fmt.Fprintf(os.Stderr, "problem processing environment: %v", err) os.Exit(1) } @@ -77,7 +77,7 @@ func main() { } } if c.DB == "" { - log.Printf("warning: in-memory db mode; if you do not want this set YSV_DB") + log.Printf("warning: in-memory db mode; if you do not want this set VAIN_DB") } hostname := "localhost" if hn, err := os.Hostname(); err != nil { diff --git a/server.go b/server.go index fb2164d..aec5257 100644 --- a/server.go +++ b/server.go @@ -12,7 +12,7 @@ func NewServer(sm *http.ServeMux, store Storage) *Server { s := &Server{ storage: store, } - sm.Handle("/", s) + addRoutes(sm, s) return s } @@ -43,6 +43,13 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { http.Error(w, fmt.Sprintf("invalid repository %q", req.URL.Path), http.StatusBadRequest) return } + if p.Vcs == "" { + p.Vcs = "git" + } + if !valid(p.Vcs) { + http.Error(w, fmt.Sprintf("invalid vcs %q", p.Vcs), http.StatusBadRequest) + return + } p.path = fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/")) if !Valid(p.path, s.storage.All()) { http.Error(w, fmt.Sprintf("invalid path; prefix already taken %q", req.URL.Path), http.StatusConflict) @@ -56,3 +63,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { http.Error(w, fmt.Sprintf("unsupported method %q; accepted: POST, GET", req.Method), http.StatusMethodNotAllowed) } } + +func (s *Server) db(w http.ResponseWriter, req *http.Request) { + all := s.storage.All() + w.Header().Set("Content-type", "application/json") + json.NewEncoder(w).Encode(&all) +} + +func addRoutes(sm *http.ServeMux, s *Server) { + sm.Handle("/", s) + sm.HandleFunc("/db/", s.db) +} diff --git a/storage.go b/storage.go index 9ea35fb..be04a16 100644 --- a/storage.go +++ b/storage.go @@ -107,5 +107,14 @@ func (ms *SimpleStore) Load() error { return err } defer f.Close() - return json.NewDecoder(f).Decode(&ms.p) + + in := map[string]Package{} + if err := json.NewDecoder(f).Decode(&in); err != nil { + return err + } + for k, v := range in { + v.path = k + ms.p[k] = v + } + return nil } diff --git a/storage_test.go b/storage_test.go index aaaf6e8..25beaef 100644 --- a/storage_test.go +++ b/storage_test.go @@ -1,10 +1,13 @@ package vain import ( + "bytes" + "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" + "strings" "testing" ) @@ -13,7 +16,7 @@ func equiv(a, b map[string]Package) (bool, []error) { errs := []error{} if got, want := len(a), len(b); got != want { equiv = false - errs = append(errs, fmt.Errorf("uncorrect number of elements: got %d, want %d", got, want)) + errs = append(errs, fmt.Errorf("incorrect number of elements: got %d, want %d", got, want)) return false, errs } @@ -37,10 +40,14 @@ func TestSimpleStorage(t *testing.T) { db := filepath.Join(root, "vain.json") ms := NewSimpleStore(db) orig := map[string]Package{ - "foo": {}, - "bar": {}, + "foo": {Vcs: "mercurial"}, + "bar": {Vcs: "bzr"}, "baz": {}, } + for k, v := range orig { + v.path = k + orig[k] = v + } ms.p = orig if err := ms.Save(); err != nil { t.Errorf("should have been able to Save: %v", err) @@ -49,7 +56,7 @@ func TestSimpleStorage(t *testing.T) { if err := ms.Load(); err != nil { t.Errorf("should have been able to Load: %v", err) } - if ok, errs := equiv(orig, ms.p); !ok { + if ok, errs := equiv(ms.p, orig); !ok { for _, err := range errs { t.Error(err) } @@ -82,3 +89,39 @@ func TestRemove(t *testing.T) { } } } + +func TestPackageJsonParsing(t *testing.T) { + tests := []struct { + input string + output string + parsed Package + }{ + { + input: `{"vcs":"git","repo":"https://s.mcquay.me/sm/ud/"}`, + output: `{"vcs":"git","repo":"https://s.mcquay.me/sm/ud/"}`, + parsed: Package{Vcs: "git", Repo: "https://s.mcquay.me/sm/ud/"}, + }, + { + input: `{"vcs":"hg","repo":"https://s.mcquay.me/sm/ud/"}`, + output: `{"vcs":"hg","repo":"https://s.mcquay.me/sm/ud/"}`, + parsed: Package{Vcs: "hg", Repo: "https://s.mcquay.me/sm/ud/"}, + }, + } + + for _, test := range tests { + p := Package{} + if err := json.NewDecoder(strings.NewReader(test.input)).Decode(&p); err != nil { + t.Error(err) + } + if p != test.parsed { + t.Errorf("got:\n\t%v, want\n\t%v", p, test.parsed) + } + buf := &bytes.Buffer{} + if err := json.NewEncoder(buf).Encode(&p); err != nil { + t.Error(err) + } + if got, want := strings.TrimSpace(buf.String()), test.output; got != want { + t.Errorf("got %v, want %v", got, want) + } + } +} diff --git a/vain.go b/vain.go index bb0541d..b715c71 100644 --- a/vain.go +++ b/vain.go @@ -3,45 +3,20 @@ // The executable, cmd/vaind, is located in the respective subdirectory. package vain -import ( - "encoding/json" - "fmt" -) +import "fmt" -type vcs int - -const ( - // Git is the default Vcs. - Git vcs = iota - - // Hg is mercurial - Hg - - // Svn - Svn - - // Bazaar - Bzr -) - -var vcss = [...]string{ - "git", - "mercurial", - "svn", - "bazaar", +var vcss = map[string]bool{ + "hg": true, + "git": true, + "bzr": true, + "svn": true, } -var labelToVcs = map[string]vcs{ - "git": Git, - "mercurial": Hg, - "hg": Hg, - "svn": Svn, - "bazaar": Bzr, +func valid(vcs string) bool { + _, ok := vcss[vcs] + return ok } -// String returns the name of the vcs ("git", "mercurial", ...). -func (v vcs) String() string { return vcss[v] } - // Package stores the three pieces of information needed to create the meta // tag. Two of these (Vcs and Repo) are stored explicitly, and the third is // determined implicitly by the path POSTed to. For more information refer to @@ -49,8 +24,8 @@ func (v vcs) String() string { return vcss[v] } // // https://golang.org/cmd/go/#hdr-Remote_import_paths type Package struct { - //Vcs (version control system) supported: "git", "mercurial" - Vcs vcs `json:"vcs"` + //Vcs (version control system) supported: "hg", "git", "bzr", "svn" + Vcs string `json:"vcs"` // Repo: the remote repository url Repo string `json:"repo"` @@ -65,16 +40,3 @@ func (p Package) String() string { p.Repo, ) } - -func (p *Package) UnmarshalJSON(b []byte) (err error) { - pkg := struct { - Vcs string - Repo string - }{} - err = json.Unmarshal(b, &pkg) - if err != nil { - return err - } - p.Vcs, p.Repo = labelToVcs[pkg.Vcs], pkg.Repo - return nil -} diff --git a/vain_test.go b/vain_test.go index 6889def..202993a 100644 --- a/vain_test.go +++ b/vain_test.go @@ -7,6 +7,7 @@ import ( func TestString(t *testing.T) { p := Package{ + Vcs: "git", path: "mcquay.me/bps", Repo: "https://s.mcquay.me/sm/bps", } @@ -21,19 +22,22 @@ func TestString(t *testing.T) { } } -func TestVcsStrings(t *testing.T) { +func TestSupportedVcsStrings(t *testing.T) { tests := []struct { - got string - want string + in string + want bool }{ - {fmt.Sprintf("%+v", Git), "git"}, - {fmt.Sprintf("%+v", Hg), "mercurial"}, - {fmt.Sprintf("%+v", Svn), "svn"}, - {fmt.Sprintf("%+v", Bzr), "bazaar"}, + {"hg", true}, + {"git", true}, + {"bzr", true}, + + {"", false}, + {"bazar", false}, + {"mercurial", false}, } for _, test := range tests { - if test.got != test.want { - t.Errorf("incorrect conversion of vain.Vcs -> string; got %q, want %q", test.got, test.want) + if got, want := valid(test.in), test.want; got != want { + t.Errorf("%s: %t is incorrect validity", test.in, got) } } }