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 (
"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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}
}

60
vain.go
View File

@ -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
}

View File

@ -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)
}
}
}