Commit 7875bef

bryfry <bryon.fryer@gmail.com>
2016-04-22 16:33:53
migrate visitr
1 parent b12f0c4
visitr/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
visitr/db.go
@@ -0,0 +1,38 @@
+package main
+
+import "fmt"
+
+type prodDB struct{}
+
+func (db *prodDB) GetVisit(visitID string) (v *Visit, err error) {
+	if len(visitID) == 0 {
+		return nil, fmt.Errorf("visit id required, none provided")
+	} else {
+		return &Visit{ID: visitID}, nil
+	}
+}
+
+func (db *prodDB) GetVisits() (vl []Visit, err error) {
+	return []Visit{Visit{ID: "a"}, Visit{ID: "b"}}, nil
+}
+
+func (db *prodDB) CreateVisit(v Visit) (vl []Visit, err error) {
+	if len(v.ID) == 0 {
+		return nil, fmt.Errorf("visit id required, none providied")
+	}
+	return append([]Visit{Visit{ID: "a"}, Visit{ID: "b"}}, v), nil
+}
+
+func (db *prodDB) CancelVisit(visitID string) (vl []Visit, err error) {
+	vl = []Visit{Visit{ID: "a"}}
+	if len(visitID) == 0 {
+		return vl, fmt.Errorf("visit id required, none providied")
+	}
+	b := vl[:0]
+	for _, x := range vl {
+		if x.ID != visitID {
+			b = append(b, x)
+		}
+	}
+	return b, nil
+}
visitr/handlers.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/gorilla/mux"
+)
+
+type MuxRequest http.Request
+
+type datasource interface {
+	GetVisit(string) (*Visit, error) // string a type?
+	GetVisits() ([]Visit, error)
+	CreateVisit(Visit) ([]Visit, error)
+	CancelVisit(string) ([]Visit, error)
+}
+
+func VisitList() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		vl, err := visitDB.GetVisits()
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		enc := json.NewEncoder(w)
+		err = enc.Encode(vl)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		return
+	})
+}
+
+// path vars expected: visitID
+func VisitDetails() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		visitID, present := mux.Vars(r)["visitID"]
+		if !present {
+			http.Error(w, "visit key not in path", http.StatusInternalServerError)
+			return
+		}
+
+		v, err := visitDB.GetVisit(visitID)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusNotFound)
+		}
+
+		enc := json.NewEncoder(w)
+		err = enc.Encode(v)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		return
+	})
+}
+
+func VisitCreate() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		var v Visit
+		dec := json.NewDecoder(r.Body)
+		err := dec.Decode(&v)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+			return
+		}
+
+		vl, err := visitDB.CreateVisit(v)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+			return
+		}
+
+		w.WriteHeader(http.StatusCreated)
+		enc := json.NewEncoder(w)
+		err = enc.Encode(vl)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		return
+	})
+}
+
+// path vars expected: visitID
+func VisitCancel() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		visitID, present := mux.Vars(r)["visitID"]
+		if !present {
+			http.Error(w, "visit key not in path", http.StatusInternalServerError)
+			return
+		}
+
+		vl, err := visitDB.CancelVisit(visitID)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+		}
+
+		enc := json.NewEncoder(w)
+		err = enc.Encode(vl)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+			return
+		}
+		return
+	})
+}
+
+// path vars expected: visitID, visitorID
+func VisitorList() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	})
+}
+
+// path vars expected: visitID
+func VisitorCreate() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	})
+}
+
+// path vars expected: visitID, visitorID
+func VisitorRemove() http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	})
+}
visitr/handlers_test.go
@@ -0,0 +1,244 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type mockDB struct{}
+type errorDB struct{}
+
+func (db *mockDB) GetVisits() (v []Visit, err error) {
+	return []Visit{Visit{ID: "a"}, Visit{ID: "b"}}, nil
+}
+func (db *errorDB) GetVisits() (v []Visit, err error) {
+	return nil, fmt.Errorf("test error")
+}
+
+func (db *mockDB) GetVisit(visitID string) (v *Visit, err error) {
+	if len(visitID) != 0 {
+		if visitID == "exampleNotFound" {
+			return nil, fmt.Errorf("visit ID [%s] not found", visitID)
+		}
+		return &Visit{ID: visitID}, nil
+	} else {
+		return nil, fmt.Errorf("no visit id providied")
+	}
+}
+func (db *errorDB) GetVisit(visitID string) (v *Visit, err error) {
+	return nil, fmt.Errorf("test error")
+}
+
+func (db *mockDB) CreateVisit(v Visit) (vl []Visit, err error) {
+	if len(v.ID) == 0 {
+		return nil, fmt.Errorf("visit id required, none providied")
+	}
+	return append([]Visit{Visit{ID: "a"}, Visit{ID: "b"}}, v), nil
+}
+func (db *errorDB) CreateVisit(v Visit) (vl []Visit, err error) {
+	return nil, fmt.Errorf("test error")
+}
+
+func (db *mockDB) CancelVisit(visitID string) (vl []Visit, err error) {
+	vl = []Visit{Visit{ID: "a"}}
+	if len(visitID) == 0 {
+		return vl, fmt.Errorf("visit id required, none providied")
+	}
+	b := vl[:0]
+	for _, x := range vl {
+		if x.ID != visitID {
+			b = append(b, x)
+		}
+	}
+	return b, nil
+}
+func (db *errorDB) CancelVisit(visitID string) (vl []Visit, err error) {
+	return nil, fmt.Errorf("test error")
+}
+
+var basepath string = "http://domain.example/"
+
+func Test_VisitList(t *testing.T) {
+	r := assert.New(t)
+
+	gettests := []struct {
+		db   datasource
+		path string
+		code int
+		out  []Visit
+	}{
+		{&mockDB{}, basepath + "visits", http.StatusOK, []Visit{Visit{ID: "a"}, Visit{ID: "b"}}},
+		{&errorDB{}, basepath + "visits", http.StatusInternalServerError, nil},
+	}
+
+	for _, ti := range gettests {
+		visitDB = ti.db
+		req, err := http.NewRequest("GET", ti.path, nil)
+		r.NoError(err)
+
+		res := httptest.NewRecorder()
+		VisitList().ServeHTTP(res, req)
+
+		r.Equal(ti.code, res.Code)
+
+		if res.Code == http.StatusOK {
+			var visits []Visit
+			dec := json.NewDecoder(res.Body)
+			err = dec.Decode(&visits)
+			r.NoError(err)
+
+			for i, v := range ti.out {
+				r.Equal(v.ID, visits[i].ID)
+			}
+		}
+	}
+
+}
+func Test_VisitDetails(t *testing.T) {
+	r := assert.New(t)
+
+	// initialize router and mock database
+	visitDB = &mockDB{}
+
+	gettests := []struct {
+		path string
+		code int
+		item interface{}
+	}{
+		{basepath + "visits/", http.StatusNotFound, nil},
+		{basepath + "visits/AAAA", http.StatusOK, Visit{ID: "AAAA"}},
+		{basepath + "visits/aaaa", http.StatusOK, Visit{ID: "aaaa"}},
+		{basepath + "visits/1111", http.StatusOK, Visit{ID: "1111"}},
+		{basepath + "visits/-", http.StatusNotFound, nil},
+		{basepath + "visits/_", http.StatusNotFound, nil},
+		{basepath + "visits/~", http.StatusNotFound, nil},
+		{basepath + "visits/exampleNotFound", http.StatusNotFound, nil},
+	}
+
+	for _, ti := range gettests {
+		req, err := http.NewRequest("GET", ti.path, nil)
+		r.NoError(err)
+
+		res := httptest.NewRecorder()
+		VisitrApp().ServeHTTP(res, req)
+
+		r.Equal(ti.code, res.Code)
+
+		if res.Code == http.StatusOK {
+			var visit Visit
+			dec := json.NewDecoder(res.Body)
+			err = dec.Decode(&visit)
+			r.NoError(err)
+
+			v, ok := ti.item.(Visit)
+			r.True(ok)
+
+			r.Equal(v.ID, visit.ID)
+		}
+	}
+
+}
+
+func Test_VisitCreate(t *testing.T) {
+	r := assert.New(t)
+
+	// initialize router and mock database
+	visitDB = &mockDB{}
+
+	goodVisit, _ := json.Marshal(Visit{ID: "c"})
+	emptyVisit, _ := json.Marshal(Visit{})
+	postTests := []struct {
+		path  string
+		visit []byte
+		code  int
+		out   []Visit
+	}{
+		{
+			path:  basepath + "visits",
+			visit: goodVisit,
+			code:  http.StatusCreated,
+			out:   []Visit{Visit{ID: "a"}, Visit{ID: "b"}, Visit{ID: "c"}},
+		},
+		{
+			path:  basepath + "visits",
+			visit: emptyVisit,
+			code:  http.StatusBadRequest,
+			out:   nil,
+		},
+		{
+			path:  basepath + "visits",
+			visit: []byte("bad bytes"),
+			code:  http.StatusBadRequest,
+			out:   nil,
+		},
+	}
+
+	for _, ti := range postTests {
+		req, err := http.NewRequest("POST", ti.path, bytes.NewBuffer(ti.visit))
+		r.NoError(err)
+
+		res := httptest.NewRecorder()
+		VisitCreate().ServeHTTP(res, req)
+
+		r.Equal(ti.code, res.Code)
+
+		if res.Code == http.StatusCreated {
+			var visits []Visit
+			dec := json.NewDecoder(res.Body)
+			err = dec.Decode(&visits)
+			r.NoError(err)
+
+			for i, v := range ti.out {
+				r.Equal(v.ID, visits[i].ID)
+			}
+		}
+	}
+}
+
+func Test_VisitCancel(t *testing.T) {
+	r := assert.New(t)
+
+	// initialize router and mock database
+	visitDB = &mockDB{}
+
+	postTests := []struct {
+		path string
+		code int
+		item interface{}
+	}{
+		{basepath + "visits/b", http.StatusOK, []Visit{Visit{ID: "a"}}},
+		{basepath + "visits/a", http.StatusOK, []Visit{Visit{ID: "a"}}},
+		{basepath + "visits", http.StatusNotFound, nil},
+	}
+
+	for _, ti := range postTests {
+		req, err := http.NewRequest("DELETE", ti.path, nil)
+		r.NoError(err)
+
+		res := httptest.NewRecorder()
+		VisitrApp().ServeHTTP(res, req)
+
+		r.Equal(ti.code, res.Code)
+
+		if res.Code == http.StatusOK {
+			var visits []Visit
+			dec := json.NewDecoder(res.Body)
+			err = dec.Decode(&visits)
+			r.NoError(err)
+
+			vl, ok := ti.item.([]Visit)
+			r.True(ok)
+
+			for i, v := range visits {
+				r.Equal(vl[i].ID, v.ID)
+			}
+		}
+	}
+
+}
visitr/main.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"database/sql"
+	"log"
+	"net/http"
+	"os"
+
+	_ "github.com/mattn/go-sqlite3"
+)
+
+var visitDB datasource
+
+func main() {
+	visitDB = &prodDB{}
+	os.Remove("./visitr.db")
+	db, err := sql.Open("sqlite3", "./visitr.db")
+	defer db.Close()
+
+	createVisitTable := `
+  create table visit (
+		id text not null primary key, 
+		username text,
+		first text,
+		last string,
+		type int,
+		affiliation text,
+		us_citizen integer,
+		access_type integer,
+		date_of_birth datetime,
+		birth_country text,
+		birth_state text);
+	`
+
+	_, err = db.Exec(createVisitTable)
+	if err != nil {
+		log.Printf("%q: %s\n", err, createVisitTable)
+		return
+	}
+
+	http.ListenAndServe(":8080", VisitrApp())
+}
visitr/models.go
@@ -0,0 +1,68 @@
+package main
+
+import "time"
+
+type VisitorType int
+
+const (
+	CONTRACTOR VisitorType = iota
+	MILITARY
+	CIVILIAN
+)
+
+var visitorTypes = [...]string{
+	"CONTRACTOR",
+	"MILITARY",
+	"CIVILIAN",
+}
+
+func (v VisitorType) String() string {
+	return visitorTypes[v]
+}
+
+type AccessType int
+
+const (
+	UNCLASSIFIED AccessType = iota
+	CONFIDENTIAL
+	SECRET
+	TOP_SECRET
+	TOP_SECRET_SCI
+)
+
+var accessTypes = [...]string{
+	"UNCLASSIFIED",
+	"CONFIDENTIAL",
+	"SECRET",
+	"TOP SECRET",
+	"TOP SECRET // SCI",
+}
+
+func (a AccessType) String() string {
+	return accessTypes[a]
+}
+
+type Visitor struct {
+	ID           string
+	Username     string
+	First        string
+	Last         string
+	Organization string
+	Type         VisitorType
+	Affiliation  string
+	USCitizen    bool
+	AccessLevel  AccessType
+	DateOfBirth  time.Time
+	BirthCountry string
+	BirthState   string
+}
+
+type Visit struct {
+	ID       string
+	Name     string
+	Start    time.Time
+	End      time.Time
+	Purpose  string
+	Sponsor  string
+	Visitors []Visitor
+}
visitr/models_test.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_VisitorTypeString(t *testing.T) {
+	r := assert.New(t)
+	visitors := []struct {
+		vType   VisitorType
+		vString string
+	}{
+		{CONTRACTOR, "CONTRACTOR"},
+		{MILITARY, "MILITARY"},
+		{CIVILIAN, "CIVILIAN"},
+	}
+	for _, t := range visitors {
+		r.Equal(t.vType.String(), t.vString)
+	}
+}
+
+func Test_AccessTypeString(t *testing.T) {
+	r := assert.New(t)
+	accesses := []struct {
+		aType   AccessType
+		aString string
+	}{
+		{UNCLASSIFIED, "UNCLASSIFIED"},
+		{CONFIDENTIAL, "CONFIDENTIAL"},
+		{SECRET, "SECRET"},
+		{TOP_SECRET, "TOP SECRET"},
+		{TOP_SECRET_SCI, "TOP SECRET // SCI"},
+	}
+	for _, t := range accesses {
+		r.Equal(t.aType.String(), t.aString)
+	}
+}
visitr/README.md
@@ -0,0 +1,5 @@
+# visitr
+
+Things not covered by tests:
+ - json encode/decode failures
+ - gorilla mux route vars not set
visitr/routes.go
@@ -0,0 +1,36 @@
+package main
+
+import "github.com/gorilla/mux"
+
+// path and mux var constants
+const (
+	visitsPath   = "visits"
+	visitorsPath = "visitors"
+	visitKey     = "visitID"
+	visitorKey   = "visitorID"
+)
+
+func VisitrApp() (r *mux.Router) {
+	r = mux.NewRouter()
+	get := r.Methods("GET").Subrouter()
+	post := r.Methods("POST").Subrouter()
+	del := r.Methods("DELETE").Subrouter()
+
+	// visit handlers
+	get.Handle(visitsPath, VisitList())
+	post.Handle(visitsPath, VisitCreate())
+
+	visitID := "{" + visitKey + ":[a-zA-Z0-9]+}"
+	get.Handle("/"+visitsPath+"/"+visitID, VisitDetails())
+	del.Handle("/"+visitsPath+"/"+visitID, VisitCancel())
+
+	// visitor handlers
+	visitorID := "{" + visitorKey + ":[a-zA-Z0-9]+}"
+	//get.Handle("/"+visitsPath+"/"+visitID+"/"+visitorsPath, VisitorList())
+	post.Handle("/"+visitsPath+"/"+visitID+"/"+visitorsPath, VisitorCreate())
+
+	get.Handle("/"+visitsPath+"/"+visitID+"/"+visitorsPath+"/"+visitorID, VisitorList())
+	del.Handle("/"+visitsPath+"/"+visitID+"/"+visitorsPath+"/"+visitorID, VisitorRemove())
+
+	return r
+}
visitr/usc.var
@@ -0,0 +1,68 @@
+
+// A handy map of US state codes to full names
+var usc = map[string]string{
+	"AL": "Alabama",
+	"AK": "Alaska",
+	"AZ": "Arizona",
+	"AR": "Arkansas",
+	"CA": "California",
+	"CO": "Colorado",
+	"CT": "Connecticut",
+	"DE": "Delaware",
+	"FL": "Florida",
+	"GA": "Georgia",
+	"HI": "Hawaii",
+	"ID": "Idaho",
+	"IL": "Illinois",
+	"IN": "Indiana",
+	"IA": "Iowa",
+	"KS": "Kansas",
+	"KY": "Kentucky",
+	"LA": "Louisiana",
+	"ME": "Maine",
+	"MD": "Maryland",
+	"MA": "Massachusetts",
+	"MI": "Michigan",
+	"MN": "Minnesota",
+	"MS": "Mississippi",
+	"MO": "Missouri",
+	"MT": "Montana",
+	"NE": "Nebraska",
+	"NV": "Nevada",
+	"NH": "New Hampshire",
+	"NJ": "New Jersey",
+	"NM": "New Mexico",
+	"NY": "New York",
+	"NC": "North Carolina",
+	"ND": "North Dakota",
+	"OH": "Ohio",
+	"OK": "Oklahoma",
+	"OR": "Oregon",
+	"PA": "Pennsylvania",
+	"RI": "Rhode Island",
+	"SC": "South Carolina",
+	"SD": "South Dakota",
+	"TN": "Tennessee",
+	"TX": "Texas",
+	"UT": "Utah",
+	"VT": "Vermont",
+	"VA": "Virginia",
+	"WA": "Washington",
+	"WV": "West Virginia",
+	"WI": "Wisconsin",
+	"WY": "Wyoming",
+	// Territories
+	"AS": "American Samoa",
+	"DC": "District of Columbia",
+	"FM": "Federated States of Micronesia",
+	"GU": "Guam",
+	"MH": "Marshall Islands",
+	"MP": "Northern Mariana Islands",
+	"PW": "Palau",
+	"PR": "Puerto Rico",
+	"VI": "Virgin Islands",
+	// Armed Forces (AE includes Europe, Africa, Canada, and the Middle East)
+	"AA": "Armed Forces Americas",
+	"AE": "Armed Forces Europe",
+	"AP": "Armed Forces Pacific",
+}
\ No newline at end of file
visitr/visitr
Binary file