dm
/
vain
forked from sm/vain
1
0
Fork 0

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
This commit is contained in:
Stephen McQuay 2016-02-23 22:09:29 -08:00
parent a1f6e15f28
commit ce00d933aa
7 changed files with 151 additions and 83 deletions

View File

@ -2,21 +2,21 @@ package vain
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"strings" "strings"
"testing" "testing"
) )
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
ms := NewSimpleStore("") ms := NewSimpleStore("")
s := &Server{ sm := http.NewServeMux()
storage: ms, _ = NewServer(sm, ms)
} ts := httptest.NewServer(sm)
ts := httptest.NewServer(s)
s.hostname = ts.URL
resp, err := http.Get(ts.URL) resp, err := http.Get(ts.URL)
if err != nil { if err != nil {
t.Errorf("couldn't GET: %v", err) 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) 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 { if err != nil {
t.Errorf("couldn't POST: %v", err) 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) 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] p, ok := ms.p[good]
if !ok { if !ok {
t.Fatalf("did not find package for %s; should have posted a valid package", good) 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 { 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) t.Errorf("repo did not go through as expected; got %q, want %q", p.Repo, want)
} }
if want := Git; p.Vcs != want { if got, want := p.Vcs, "git"; got != want {
t.Errorf("Vcs did not go through as expected; got %q, want %q", p.Vcs, want) t.Errorf("Vcs did not go through as expected; got %q, want %q", got, want)
} }
resp, err = http.Get(ts.URL) 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 { 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) 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) { func TestInvalidPath(t *testing.T) {
@ -83,7 +122,6 @@ func TestInvalidPath(t *testing.T) {
storage: ms, storage: ms,
} }
ts := httptest.NewServer(s) 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"}`)) resp, err := http.Post(ts.URL, "application/json", strings.NewReader(`{"repo": "https://s.mcquay.me/sm/vain"}`))
if err != nil { if err != nil {
@ -103,7 +141,6 @@ func TestCannotDuplicateExistingPath(t *testing.T) {
storage: ms, storage: ms,
} }
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", 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"}`)) 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, storage: ms,
} }
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo/bar", 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"}`)) 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, storage: ms,
} }
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL) url := fmt.Sprintf("%s/foo", ts.URL)
resp, err := http.Post(url, "application/json", strings.NewReader(`{}`)) resp, err := http.Post(url, "application/json", strings.NewReader(`{}`))
if err != nil { if err != nil {
@ -176,7 +211,6 @@ func TestBadJson(t *testing.T) {
storage: ms, storage: ms,
} }
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL) url := fmt.Sprintf("%s/foo", ts.URL)
resp, err := http.Post(url, "application/json", strings.NewReader(`{`)) resp, err := http.Post(url, "application/json", strings.NewReader(`{`))
if err != nil { if err != nil {
@ -196,7 +230,6 @@ func TestUnsupportedMethod(t *testing.T) {
storage: ms, storage: ms,
} }
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL) url := fmt.Sprintf("%s/foo", ts.URL)
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest("PUT", url, nil) req, err := http.NewRequest("PUT", url, nil)
@ -215,9 +248,8 @@ func TestUnsupportedMethod(t *testing.T) {
func TestNewServer(t *testing.T) { func TestNewServer(t *testing.T) {
ms := NewSimpleStore("") ms := NewSimpleStore("")
sm := http.NewServeMux() sm := http.NewServeMux()
s := NewServer(sm, ms, "foo") s := NewServer(sm, ms)
ts := httptest.NewServer(s) ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL) url := fmt.Sprintf("%s/foo", ts.URL)
client := &http.Client{} client := &http.Client{}
req, err := http.NewRequest("PUT", url, nil) req, err := http.NewRequest("PUT", url, nil)

View File

@ -65,7 +65,7 @@ func main() {
c := &config{ c := &config{
Port: 4040, 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) fmt.Fprintf(os.Stderr, "problem processing environment: %v", err)
os.Exit(1) os.Exit(1)
} }
@ -77,7 +77,7 @@ func main() {
} }
} }
if c.DB == "" { 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" hostname := "localhost"
if hn, err := os.Hostname(); err != nil { if hn, err := os.Hostname(); err != nil {

View File

@ -12,7 +12,7 @@ func NewServer(sm *http.ServeMux, store Storage) *Server {
s := &Server{ s := &Server{
storage: store, storage: store,
} }
sm.Handle("/", s) addRoutes(sm, s)
return 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) http.Error(w, fmt.Sprintf("invalid repository %q", req.URL.Path), http.StatusBadRequest)
return 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, "/")) p.path = fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/"))
if !Valid(p.path, s.storage.All()) { if !Valid(p.path, s.storage.All()) {
http.Error(w, fmt.Sprintf("invalid path; prefix already taken %q", req.URL.Path), http.StatusConflict) 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) 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)
}

View File

@ -107,5 +107,14 @@ func (ms *SimpleStore) Load() error {
return err return err
} }
defer f.Close() 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
} }

View File

@ -1,10 +1,13 @@
package vain package vain
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
) )
@ -13,7 +16,7 @@ func equiv(a, b map[string]Package) (bool, []error) {
errs := []error{} errs := []error{}
if got, want := len(a), len(b); got != want { if got, want := len(a), len(b); got != want {
equiv = false 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 return false, errs
} }
@ -37,10 +40,14 @@ func TestSimpleStorage(t *testing.T) {
db := filepath.Join(root, "vain.json") db := filepath.Join(root, "vain.json")
ms := NewSimpleStore(db) ms := NewSimpleStore(db)
orig := map[string]Package{ orig := map[string]Package{
"foo": {}, "foo": {Vcs: "mercurial"},
"bar": {}, "bar": {Vcs: "bzr"},
"baz": {}, "baz": {},
} }
for k, v := range orig {
v.path = k
orig[k] = v
}
ms.p = orig ms.p = orig
if err := ms.Save(); err != nil { if err := ms.Save(); err != nil {
t.Errorf("should have been able to Save: %v", err) 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 { if err := ms.Load(); err != nil {
t.Errorf("should have been able to Load: %v", err) 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 { for _, err := range errs {
t.Error(err) 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)
}
}
}

60
vain.go
View File

@ -3,45 +3,20 @@
// The executable, cmd/vaind, is located in the respective subdirectory. // The executable, cmd/vaind, is located in the respective subdirectory.
package vain package vain
import ( import "fmt"
"encoding/json"
"fmt"
)
type vcs int var vcss = map[string]bool{
"hg": true,
const ( "git": true,
// Git is the default Vcs. "bzr": true,
Git vcs = iota "svn": true,
// Hg is mercurial
Hg
// Svn
Svn
// Bazaar
Bzr
)
var vcss = [...]string{
"git",
"mercurial",
"svn",
"bazaar",
} }
var labelToVcs = map[string]vcs{ func valid(vcs string) bool {
"git": Git, _, ok := vcss[vcs]
"mercurial": Hg, return ok
"hg": Hg,
"svn": Svn,
"bazaar": Bzr,
} }
// 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 // 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 // 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 // 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 // https://golang.org/cmd/go/#hdr-Remote_import_paths
type Package struct { type Package struct {
//Vcs (version control system) supported: "git", "mercurial" //Vcs (version control system) supported: "hg", "git", "bzr", "svn"
Vcs vcs `json:"vcs"` Vcs string `json:"vcs"`
// Repo: the remote repository url // Repo: the remote repository url
Repo string `json:"repo"` Repo string `json:"repo"`
@ -65,16 +40,3 @@ func (p Package) String() string {
p.Repo, 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
}

View File

@ -7,6 +7,7 @@ import (
func TestString(t *testing.T) { func TestString(t *testing.T) {
p := Package{ p := Package{
Vcs: "git",
path: "mcquay.me/bps", path: "mcquay.me/bps",
Repo: "https://s.mcquay.me/sm/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 { tests := []struct {
got string in string
want string want bool
}{ }{
{fmt.Sprintf("%+v", Git), "git"}, {"hg", true},
{fmt.Sprintf("%+v", Hg), "mercurial"}, {"git", true},
{fmt.Sprintf("%+v", Svn), "svn"}, {"bzr", true},
{fmt.Sprintf("%+v", Bzr), "bazaar"},
{"", false},
{"bazar", false},
{"mercurial", false},
} }
for _, test := range tests { for _, test := range tests {
if test.got != test.want { if got, want := valid(test.in), test.want; got != want {
t.Errorf("incorrect conversion of vain.Vcs -> string; got %q, want %q", test.got, test.want) t.Errorf("%s: %t is incorrect validity", test.in, got)
} }
} }
} }