Commit 7875bef
Changed files (11)
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