Initial commit

This commit is contained in:
T T 2018-11-06 07:16:03 +01:00
commit a2c8e2b054
5 changed files with 315 additions and 0 deletions

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/you/hello
require github.com/satori/go.uuid v1.2.0

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=

130
main.go Normal file
View File

@ -0,0 +1,130 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"regexp"
"github.com/satori/go.uuid"
)
const (
FormUrl = "/"
FormPostUrl = "/post"
RequestStatusUrlPrefix = "/status/"
ErrorUrl = "/error"
)
var statusRE = regexp.MustCompile(RequestStatusUrlPrefix + `(.+)$`)
type Id uuid.UUID
func (id Id) String() string {
return uuid.UUID(id).String()
}
type Request struct {
Username string
Email string
Why string
SSHPublicKey string
Status string
}
func (r *Request) IsValid() bool {
return r.Username != "" &&
r.Email != "" &&
r.Why != "" &&
r.SSHPublicKey != ""
}
type Io interface {
Save(r Request) (Id, error)
Load(id Id) (*Request, error)
}
type FsIo struct {
}
func (io *FsIo) Save(r Request) (Id, error) {
b, err := json.MarshalIndent(r, "", "\t")
if err != nil {
return Id{}, err
}
id := Id(uuid.NewV4())
return id, ioutil.WriteFile(id.String()+".json", b, 0600)
}
func (io *FsIo) Load(id Id) (*Request, error) {
b, err := ioutil.ReadFile(id.String() + ".json")
if err != nil {
return nil, err
}
var req Request
if err := json.Unmarshal(b, &req); err != nil {
return nil, err
}
return &req, nil
}
type Server struct {
Io Io
}
func (s *Server) RequestPage(w http.ResponseWriter, r *http.Request) {
m := statusRE.FindStringSubmatch(r.URL.String())
if len(m) != 2 {
http.Error(w, "missing request id", http.StatusBadRequest)
return
}
uid := m[1]
id, err := uuid.FromString(uid)
if err != nil {
http.Error(w, "no such request: '"+uid+"'", http.StatusBadRequest)
return
}
req, err := s.Io.Load(Id(id))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w, "Status: %v", req.Status)
}
func (s *Server) IncorrectRequest(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) FormPostHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
req := Request{}
req.Username = r.PostFormValue("username")
req.Email = r.PostFormValue("email")
req.Why = r.PostFormValue("why")
req.SSHPublicKey = r.PostFormValue("sshpublickey")
req.Status = "Pending"
if !req.IsValid() {
log.Println("Invalid request", r.PostForm)
http.Redirect(w, r, ErrorUrl, http.StatusSeeOther)
return
}
id, err := s.Io.Save(req)
log.Println("Valid request", r.PostForm, err)
http.Redirect(w, r, RequestStatusUrlPrefix+id.String(), http.StatusSeeOther)
}
func (s *Server) FormPage(w http.ResponseWriter, r *http.Request) {
formTemplate.Execute(w, nil)
}
func main() {
var io FsIo
server := Server{Io: &io}
http.HandleFunc(RequestStatusUrlPrefix, server.RequestPage)
http.HandleFunc(ErrorUrl, server.IncorrectRequest)
http.HandleFunc(FormPostUrl, server.FormPostHandler)
http.HandleFunc(FormUrl, server.FormPage)
log.Fatal(http.ListenAndServe(":8080", nil))
}

158
main_test.go Normal file
View File

@ -0,0 +1,158 @@
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
uuid "github.com/satori/go.uuid"
)
var expected = Request{
Username: "name",
Email: "test@example.com",
Why: "foo bar baz",
SSHPublicKey: "123",
Status: "Pending",
}
type ReqEntry struct {
Request Request
Id Id
}
type IoStub struct {
Saved []ReqEntry
Loads []Id
}
func NewIoStub() *IoStub {
return &IoStub{}
}
func (io *IoStub) Save(r Request) (Id, error) {
id := Id(uuid.NewV4())
io.Saved = append(io.Saved, ReqEntry{r, id})
return id, nil
}
func (io *IoStub) Load(id Id) (*Request, error) {
io.Loads = append(io.Loads, id)
for _, r := range io.Saved {
if r.Id == id {
return &r.Request, nil
}
}
return nil, fmt.Errorf("Missing Request for id: %v", id)
}
func requestForm(target string, values map[string]string) *http.Request {
r := httptest.NewRequest("POST", target, nil)
r.PostForm = url.Values{}
for k, v := range values {
r.PostForm.Set(k, v)
}
return r
}
func TestRequestSaveAfterCorrectFormPost(t *testing.T) {
io := NewIoStub()
server := Server{Io: io}
req := requestForm(FormPostUrl, map[string]string{
"username": expected.Username,
"email": expected.Email,
"why": expected.Why,
"sshpublickey": expected.SSHPublicKey,
})
recorder := httptest.NewRecorder()
server.FormPostHandler(recorder, req)
resp := recorder.Result()
if resp.StatusCode != http.StatusSeeOther {
t.Fatalf("Expected status %v, got: %v", http.StatusSeeOther, resp.StatusCode)
}
expectedLoc := RequestStatusUrlPrefix + io.Saved[0].Id.String()
if loc := resp.Header.Get("Location"); loc != expectedLoc {
t.Fatalf("Expected location '%v', got '%v'", expectedLoc, loc)
}
if !reflect.DeepEqual(expected, io.Saved[0].Request) {
t.Fatalf("\nExpected '%#v'\n got '%#v'", expected, io.Saved[0])
}
}
func TestRedirectToFailureWhenAnyRequestFieldIsEmtpy(t *testing.T) {
data := []Request{
{Username: "", Email: expected.Email, Why: expected.Why, SSHPublicKey: expected.SSHPublicKey},
{Username: expected.Username, Email: "", Why: expected.Why, SSHPublicKey: expected.SSHPublicKey},
{Username: expected.Username, Email: expected.Email, Why: "", SSHPublicKey: expected.SSHPublicKey},
{Username: expected.Username, Email: expected.Email, Why: expected.Why, SSHPublicKey: ""},
}
for _, r := range data {
io := NewIoStub()
server := Server{Io: io}
req := requestForm(FormPostUrl, map[string]string{
"username": r.Username,
"email": r.Email,
"why": r.Why,
"sshpublickey": r.SSHPublicKey,
})
recorder := httptest.NewRecorder()
server.FormPostHandler(recorder, req)
resp := recorder.Result()
if resp.StatusCode != http.StatusSeeOther {
t.Fatalf("Expected see other status, got: %v", resp.StatusCode)
}
if loc := resp.Header.Get("Location"); loc != ErrorUrl {
t.Fatalf("Expected location %v, got: %v", ErrorUrl, loc)
}
}
}
func TestStatusPageOk(t *testing.T) {
io := NewIoStub()
server := Server{Io: io}
id, _ := io.Save(expected)
req := httptest.NewRequest("GET", RequestStatusUrlPrefix+id.String(), nil)
rec := httptest.NewRecorder()
server.RequestPage(rec, req)
if io.Loads[0] != id {
t.Fatalf("Expected load of %v, loaded %v instead", id, io.Loads[0])
}
}
func TestStatusPageUnknownId(t *testing.T) {
io := NewIoStub()
server := Server{Io: io}
id := Id(uuid.NewV4())
req := httptest.NewRequest("GET", RequestStatusUrlPrefix+id.String(), nil)
rec := httptest.NewRecorder()
server.RequestPage(rec, req)
if io.Loads[0] != id {
t.Fatalf("Expected load of %v, loaded %v instead", id, io.Loads[0])
}
}
func TestStatusPageMalformedId(t *testing.T) {
io := NewIoStub()
server := Server{Io: io}
id := Id(uuid.NewV4())
req := httptest.NewRequest("GET", RequestStatusUrlPrefix+id.String()+"abc", nil)
rec := httptest.NewRecorder()
server.RequestPage(rec, req)
if l := len(io.Loads); l != 0 {
t.Fatalf("Expected zero loads, got %v", l)
}
}
func TestStatusPageMissingId(t *testing.T) {
io := NewIoStub()
server := Server{Io: io}
req := httptest.NewRequest("GET", RequestStatusUrlPrefix, nil)
rec := httptest.NewRecorder()
server.RequestPage(rec, req)
if l := len(io.Loads); l != 0 {
t.Fatalf("Expected zero loads, got %v", l)
}
}

22
templates.go Normal file
View File

@ -0,0 +1,22 @@
package main
import "html/template"
var formTemplate = template.Must(template.New("form").Parse(`
<html>
<body>
<h1>~🐱 signup form</h1>
<form action="/post" method="post">
Username:
<input type="text" name="username"><br/>
Email:
<input type="email" name="email"><br/>
Why would you want an account here?
<textarea name="why" cols=50 rows=10></textarea><br/>
SSH key:
<textarea name="sshpublickey" cols=50 rows=10></textarea><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>
`))