Stubby state, tests passing

This commit is contained in:
Neale Pickett 2020-02-29 18:36:59 -07:00
parent 8ed07e2e29
commit 5e5592b0d4
9 changed files with 113 additions and 145 deletions

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"strings" "strings"
"encoding/json"
) )
type Award struct { type Award struct {
@ -32,6 +33,20 @@ func (a *Award) String() string {
return fmt.Sprintf("%d %s %s %d", a.When, a.TeamId, a.Category, a.Points) return fmt.Sprintf("%d %s %s %d", a.When, a.TeamId, a.Category, a.Points)
} }
func (a *Award) MarshalJSON() ([]byte, error) {
if a == nil {
return []byte("null"), nil
}
ao := []interface{}{
a.When,
a.TeamId,
a.Category,
a.Points,
}
return json.Marshal(ao)
}
func (a *Award) Same(o *Award) bool { func (a *Award) Same(o *Award) bool {
switch { switch {
case a.TeamId != o.TeamId: case a.TeamId != o.TeamId:

View File

@ -25,6 +25,13 @@ func TestAward(t *testing.T) {
t.Error("String conversion wonky") t.Error("String conversion wonky")
} }
if ja, err := a.MarshalJSON(); err != nil {
t.Error(err)
} else if string(ja) != `[1536958399,"1a2b3c4d","counting",1]` {
t.Error("JSON wrong")
}
if _, err := ParseAward("bad bad bad 1"); err == nil { if _, err := ParseAward("bad bad bad 1"); err == nil {
t.Error("Not throwing error on bad timestamp") t.Error("Not throwing error on bad timestamp")
} }

View File

@ -4,7 +4,6 @@ import (
"net/http" "net/http"
"log" "log"
"strings" "strings"
"github.com/dirtbags/moth/jsend"
) )
type HTTPServer struct { type HTTPServer struct {
@ -30,7 +29,6 @@ func NewHTTPServer(base string, theme ThemeProvider, state StateProvider, puzzle
return h return h
} }
func (h *HTTPServer) Run(bindStr string) { func (h *HTTPServer) Run(bindStr string) {
log.Printf("Listening on %s", bindStr) log.Printf("Listening on %s", bindStr)
log.Fatal(http.ListenAndServe(bindStr, h)) log.Fatal(http.ListenAndServe(bindStr, h))
@ -64,10 +62,6 @@ func (h *HTTPServer) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
func (h *HTTPServer) ThemeHandler(w http.ResponseWriter, req *http.Request) { func (h *HTTPServer) ThemeHandler(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path path := req.URL.Path
if strings.Contains(path, "..") {
http.Error(w, "Invalid URL path", http.StatusBadRequest)
return
}
if path == "/" { if path == "/" {
path = "/index.html" path = "/index.html"
} }
@ -84,17 +78,35 @@ func (h *HTTPServer) ThemeHandler(w http.ResponseWriter, req *http.Request) {
func (h *HTTPServer) StateHandler(w http.ResponseWriter, req *http.Request) { func (h *HTTPServer) StateHandler(w http.ResponseWriter, req *http.Request) {
jsend.Write(w, jsend.Fail, "unimplemented", "I haven't written this yet") var state struct {
Config struct {
Devel bool `json:"devel"`
} `json:"config"`
Messages string `json:"messages"`
Teams []string `json:"teams"`
Points []Award `json:"points"`
Puzzles map[string][]int `json:"puzzles"`
}
state.Messages = "Hello world"
state.Teams = []string{"goobers"}
state.Points = []Award{{0, "0", "sequence", 1}}
state.Puzzles = map[string][]int{"sequence": {1}}
JSONWrite(w, state)
} }
func (h *HTTPServer) RegisterHandler(w http.ResponseWriter, req *http.Request) { func (h *HTTPServer) RegisterHandler(w http.ResponseWriter, req *http.Request) {
jsend.Write(w, jsend.Fail, "unimplemented", "I haven't written this yet") JSendf(w, JSendFail, "unimplemented", "I haven't written this yet")
} }
func (h *HTTPServer) AnswerHandler(w http.ResponseWriter, req *http.Request) { func (h *HTTPServer) AnswerHandler(w http.ResponseWriter, req *http.Request) {
jsend.Write(w, jsend.Fail, "unimplemented", "I haven't written this yet") JSendf(w, JSendFail, "unimplemented", "I haven't written this yet")
} }
func (h *HTTPServer) ContentHandler(w http.ResponseWriter, req *http.Request) { func (h *HTTPServer) ContentHandler(w http.ResponseWriter, req *http.Request) {
jsend.Write(w, jsend.Fail, "unimplemented", "I haven't written this yet") path := req.URL.Path
if path == "/" {
path = "/puzzle.json"
}
JSendf(w, JSendFail, "unimplemented", "I haven't written this yet")
} }

51
cmd/mothd/jsend.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// This provides a JSend function for MOTH
// https://github.com/omniti-labs/jsend
const (
JSendSuccess = "success"
JSendFail = "fail"
JSendError = "error"
)
func JSONWrite(w http.ResponseWriter, data interface{}) {
respBytes, err := json.Marshal(data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(respBytes)))
w.WriteHeader(http.StatusOK) // RFC2616 makes it pretty clear that 4xx codes are for the user-agent
w.Write(respBytes)
}
func JSend(w http.ResponseWriter, status string, data interface{}) {
resp := struct{
Status string `json:"status"`
Data interface{} `json:"data"`
}{}
resp.Status = status
resp.Data = data
JSONWrite(w, resp)
}
func JSendf(w http.ResponseWriter, status, short string, format string, a ...interface{}) {
data := struct{
Short string `json:"short"`
Description string `json:"description"`
}{}
data.Short = short
data.Description = fmt.Sprintf(format, a...)
JSend(w, status, data)
}

View File

@ -18,7 +18,7 @@ func TestState(t *testing.T) {
} }
s := NewState(fs) s := NewState(fs)
s.Cleanup() s.Update()
pl := s.PointsLog() pl := s.PointsLog()
if len(pl) != 0 { if len(pl) != 0 {
@ -51,7 +51,7 @@ func TestState(t *testing.T) {
category := "poot" category := "poot"
points := 3928 points := 3928
s.AwardPoints(teamId, category, points) s.AwardPoints(teamId, category, points)
s.Cleanup() s.Update()
pl = s.PointsLog() pl = s.PointsLog()
if len(pl) != 1 { if len(pl) != 1 {
@ -61,7 +61,7 @@ func TestState(t *testing.T) {
} }
fs.Remove("initialized") fs.Remove("initialized")
s.Cleanup() s.Update()
pl = s.PointsLog() pl = s.PointsLog()
if len(pl) != 0 { if len(pl) != 0 {

View File

@ -2,31 +2,22 @@ package main
import ( import (
"github.com/spf13/afero" "github.com/spf13/afero"
"net/http"
"net/http/httptest"
"testing" "testing"
"io/ioutil"
) )
func TestTheme(t *testing.T) { func TestTheme(t *testing.T) {
fs := new(afero.MemMapFs) fs := new(afero.MemMapFs)
afero.WriteFile(fs, "/index.html", []byte("index"), 0644) index := "this is the index"
afero.WriteFile(fs, "/moo.html", []byte("moo"), 0644) afero.WriteFile(fs, "/index.html", []byte(index), 0644)
s := NewTheme(fs) s := NewTheme(fs)
req, err := http.NewRequest("GET", "/", nil) if f, err := s.Open("/index.html"); err != nil {
if err != nil { t.Error(err)
t.Fatal(err) } else if buf, err := ioutil.ReadAll(f); err != nil {
} t.Error(err)
} else if string(buf) != index {
rr := httptest.NewRecorder() t.Error("Read wrong value from index")
handler := http.HandlerFunc(s.staticHandler)
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Handler returned wrong code: %v", rr.Code)
}
if rr.Body.String() != "index" {
t.Errorf("Handler returned wrong content: %v", rr.Body.String())
} }
} }

View File

@ -4,88 +4,20 @@ import (
"archive/zip" "archive/zip"
"fmt" "fmt"
"io" "io"
"io/ioutil" "github.com/spf13/afero"
"math/rand"
"os"
"testing" "testing"
"time"
) )
func TestZipPerformance(t *testing.T) {
// I get 4.8s for 10,000 reads
if os.Getenv("BENCHMARK") == "" {
return
}
rng := rand.New(rand.NewSource(rand.Int63()))
tf, err := ioutil.TempFile("", "zipfs")
if err != nil {
t.Error(err)
return
}
defer os.Remove(tf.Name())
w := zip.NewWriter(tf)
for i := 0; i < 100; i += 1 {
fsize := 1000
switch {
case i % 10 == 0:
fsize = 400000
case i % 20 == 6:
fsize = 5000000
case i == 80:
fsize = 1000000000
}
f, err := w.Create(fmt.Sprintf("%d.bin", i))
if err != nil {
t.Fatal(err)
return
}
if _, err := io.CopyN(f, rng, int64(fsize)); err != nil {
t.Error(err)
}
}
w.Close()
tfsize, err := tf.Seek(0, 2)
if err != nil {
t.Fatal(err)
}
startTime := time.Now()
nReads := 10000
for i := 0; i < 10000; i += 1 {
r, err := zip.NewReader(tf, tfsize)
if err != nil {
t.Error(err)
return
}
filenum := rng.Intn(len(r.File))
f, err := r.File[filenum].Open()
if err != nil {
t.Error(err)
continue
}
buf, err := ioutil.ReadAll(f)
if err != nil {
t.Error(err)
}
t.Log("Read file of size", len(buf))
f.Close()
}
t.Log(nReads, "reads took", time.Since(startTime))
t.Error("moo")
}
func TestZipfs(t *testing.T) { func TestZipfs(t *testing.T) {
tf, err := ioutil.TempFile("", "zipfs") fs := new(afero.MemMapFs)
tf, err := fs.Create("/test.zip")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
defer os.Remove(tf.Name()) defer fs.Remove(tf.Name())
w := zip.NewWriter(tf) w := zip.NewWriter(tf)
f, err := w.Create("moo.txt") f, err := w.Create("moo.txt")
@ -105,7 +37,7 @@ func TestZipfs(t *testing.T) {
tf.Close() tf.Close()
// Now read it in // Now read it in
mb, err := OpenZipfs(tf.Name()) mb, err := OpenZipfs(fs, tf.Name())
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return

View File

@ -1,39 +0,0 @@
package jsend
import (
"encoding/json"
"fmt"
"net/http"
)
// This provides a JSend function for MOTH
// https://github.com/omniti-labs/jsend
const (
Success = "success"
Fail = "fail"
Error = "error"
)
func Write(w http.ResponseWriter, status, short string, format string, a ...interface{}) {
resp := struct{
Status string `json:"status"`
Data struct {
Short string `json:"short"`
Description string `json:"description"`
} `json:"data"`
}{}
resp.Status = status
resp.Data.Short = short
resp.Data.Description = fmt.Sprintf(format, a...)
respBytes, err := json.Marshal(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) // RFC2616 makes it pretty clear that 4xx codes are for the user-agent
w.Write(respBytes)
}

View File

@ -102,7 +102,6 @@ function renderState(obj) {
renderNotices(obj.messages) renderNotices(obj.messages)
} }
function heartbeat() { function heartbeat() {
let teamId = sessionStorage.id || "" let teamId = sessionStorage.id || ""
let participantId = sessionStorage.pid let participantId = sessionStorage.pid