mirror of https://github.com/dirtbags/moth.git
Check for new mothballs with the same name
Also updated some error messages to pass newer linter. Fixes #172
This commit is contained in:
parent
dd8ca81186
commit
f68201ab53
|
@ -117,11 +117,11 @@ func (h *HTTPServer) RegisterHandler(mh MothRequestHandler, w http.ResponseWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mh.Register(teamName); err == ErrAlreadyRegistered {
|
if err := mh.Register(teamName); err == ErrAlreadyRegistered {
|
||||||
jsend.Sendf(w, jsend.Success, "already registered", "Team ID has already been registered")
|
jsend.Sendf(w, jsend.Success, "already registered", "team ID has already been registered")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
jsend.Sendf(w, jsend.Fail, "not registered", err.Error())
|
jsend.Sendf(w, jsend.Fail, "not registered", err.Error())
|
||||||
} else {
|
} else {
|
||||||
jsend.Sendf(w, jsend.Success, "registered", "Team ID registered")
|
jsend.Sendf(w, jsend.Success, "registered", "team ID registered")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,8 @@ func (hs *HTTPServer) TestRequest(path string, args map[string]string) *httptest
|
||||||
vals := url.Values{}
|
vals := url.Values{}
|
||||||
vals.Set("pid", TestParticipantID)
|
vals.Set("pid", TestParticipantID)
|
||||||
vals.Set("id", TestTeamID)
|
vals.Set("id", TestTeamID)
|
||||||
if args != nil {
|
for k, v := range args {
|
||||||
for k, v := range args {
|
vals.Set(k, v)
|
||||||
vals.Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
@ -56,19 +54,19 @@ func TestHttpd(t *testing.T) {
|
||||||
|
|
||||||
if r := hs.TestRequest("/register", map[string]string{"id": "bad team id", "name": "GoTeam"}); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/register", map[string]string{"id": "bad team id", "name": "GoTeam"}); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"status":"fail","data":{"short":"not registered","description":"Team ID not found in list of valid Team IDs"}}` {
|
} else if r.Body.String() != `{"status":"fail","data":{"short":"not registered","description":"team ID not found in list of valid team IDs"}}` {
|
||||||
t.Error("Register bad team ID failed")
|
t.Error("Register bad team ID failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if r := hs.TestRequest("/register", map[string]string{"name": "GoTeam"}); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/register", map[string]string{"name": "GoTeam"}); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"status":"success","data":{"short":"registered","description":"Team ID registered"}}` {
|
} else if r.Body.String() != `{"status":"success","data":{"short":"registered","description":"team ID registered"}}` {
|
||||||
t.Error("Register failed")
|
t.Error("Register failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if r := hs.TestRequest("/register", map[string]string{"name": "GoTeam"}); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/register", map[string]string{"name": "GoTeam"}); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"status":"success","data":{"short":"already registered","description":"Team ID has already been registered"}}` {
|
} else if r.Body.String() != `{"status":"success","data":{"short":"already registered","description":"team ID has already been registered"}}` {
|
||||||
t.Error("Register failed", r.Body.String())
|
t.Error("Register failed", r.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +100,7 @@ func TestHttpd(t *testing.T) {
|
||||||
|
|
||||||
if r := hs.TestRequest("/answer", map[string]string{"cat": "pategory", "points": "1", "answer": "moo"}); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/answer", map[string]string{"cat": "pategory", "points": "1", "answer": "moo"}); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"status":"fail","data":{"short":"not accepted","description":"Incorrect answer"}}` {
|
} else if r.Body.String() != `{"status":"fail","data":{"short":"not accepted","description":"incorrect answer"}}` {
|
||||||
t.Error("Unexpected body", r.Body.String())
|
t.Error("Unexpected body", r.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +129,7 @@ func TestHttpd(t *testing.T) {
|
||||||
|
|
||||||
if r := hs.TestRequest("/answer", map[string]string{"cat": "pategory", "points": "1", "answer": "answer123"}); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/answer", map[string]string{"cat": "pategory", "points": "1", "answer": "answer123"}); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"status":"fail","data":{"short":"not accepted","description":"Error awarding points: Points already awarded to this team in this category"}}` {
|
} else if r.Body.String() != `{"status":"fail","data":{"short":"not accepted","description":"error awarding points: points already awarded to this team in this category"}}` {
|
||||||
t.Error("Unexpected body", r.Body.String())
|
t.Error("Unexpected body", r.Body.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
type zipCategory struct {
|
type zipCategory struct {
|
||||||
afero.Fs
|
afero.Fs
|
||||||
io.Closer
|
io.Closer
|
||||||
|
mtime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mothballs provides a collection of active mothball files (puzzle categories)
|
// Mothballs provides a collection of active mothball files (puzzle categories)
|
||||||
|
@ -48,7 +49,7 @@ func (m *Mothballs) getCat(cat string) (zipCategory, bool) {
|
||||||
func (m *Mothballs) Open(cat string, points int, filename string) (ReadSeekCloser, time.Time, error) {
|
func (m *Mothballs) Open(cat string, points int, filename string) (ReadSeekCloser, time.Time, error) {
|
||||||
zc, ok := m.getCat(cat)
|
zc, ok := m.getCat(cat)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, time.Time{}, fmt.Errorf("No such category: %s", cat)
|
return nil, time.Time{}, fmt.Errorf("no such category: %s", cat)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := zc.Open(fmt.Sprintf("%d/%s", points, filename))
|
f, err := zc.Open(fmt.Sprintf("%d/%s", points, filename))
|
||||||
|
@ -91,12 +92,12 @@ func (m *Mothballs) Inventory() []Category {
|
||||||
func (m *Mothballs) CheckAnswer(cat string, points int, answer string) (bool, error) {
|
func (m *Mothballs) CheckAnswer(cat string, points int, answer string) (bool, error) {
|
||||||
zfs, ok := m.getCat(cat)
|
zfs, ok := m.getCat(cat)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("No such category: %s", cat)
|
return false, fmt.Errorf("no such category: %s", cat)
|
||||||
}
|
}
|
||||||
|
|
||||||
af, err := zfs.Open("answers.txt")
|
af, err := zfs.Open("answers.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("No answers.txt file")
|
return false, fmt.Errorf("no answers.txt file")
|
||||||
}
|
}
|
||||||
defer af.Close()
|
defer af.Close()
|
||||||
|
|
||||||
|
@ -132,7 +133,18 @@ func (m *Mothballs) refresh() {
|
||||||
categoryName := strings.TrimSuffix(filename, ".mb")
|
categoryName := strings.TrimSuffix(filename, ".mb")
|
||||||
found[categoryName] = true
|
found[categoryName] = true
|
||||||
|
|
||||||
if _, ok := m.categories[categoryName]; !ok {
|
reopen := false
|
||||||
|
if existingMothball, ok := m.categories[categoryName]; !ok {
|
||||||
|
reopen = true
|
||||||
|
} else if si, err := m.Fs.Stat(filename); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else if si.ModTime().After(existingMothball.mtime) {
|
||||||
|
existingMothball.Close()
|
||||||
|
delete(m.categories, categoryName)
|
||||||
|
reopen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if reopen {
|
||||||
f, err := m.Fs.Open(filename)
|
f, err := m.Fs.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
@ -174,7 +186,7 @@ func (m *Mothballs) refresh() {
|
||||||
|
|
||||||
// Mothball just returns an error
|
// Mothball just returns an error
|
||||||
func (m *Mothballs) Mothball(cat string, w io.Writer) error {
|
func (m *Mothballs) Mothball(cat string, w io.Writer) error {
|
||||||
return fmt.Errorf("Refusing to repackage a compiled mothball")
|
return fmt.Errorf("refusing to repackage a compiled mothball")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintain performs housekeeping for Mothballs.
|
// Maintain performs housekeeping for Mothballs.
|
||||||
|
|
|
@ -3,6 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
@ -14,14 +16,13 @@ var testFiles = []struct {
|
||||||
{"puzzles.txt", "1\n3\n2\n"},
|
{"puzzles.txt", "1\n3\n2\n"},
|
||||||
{"answers.txt", "1 answer123\n1 answer456\n2 wat\n"},
|
{"answers.txt", "1 answer123\n1 answer456\n2 wat\n"},
|
||||||
{"1/puzzle.json", `{"name": "moo"}`},
|
{"1/puzzle.json", `{"name": "moo"}`},
|
||||||
{"1/moo.txt", `moo`},
|
|
||||||
{"2/puzzle.json", `{}`},
|
{"2/puzzle.json", `{}`},
|
||||||
{"2/moo.txt", `moo`},
|
{"2/moo.txt", `moo`},
|
||||||
{"3/puzzle.json", `{}`},
|
{"3/puzzle.json", `{}`},
|
||||||
{"3/moo.txt", `moo`},
|
{"3/moo.txt", `moo`},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mothballs) createMothball(cat string) {
|
func (m *Mothballs) createMothballWithMoo1(cat string, moo1 string) {
|
||||||
f, _ := m.Create(fmt.Sprintf("%s.mb", cat))
|
f, _ := m.Create(fmt.Sprintf("%s.mb", cat))
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
@ -32,6 +33,13 @@ func (m *Mothballs) createMothball(cat string) {
|
||||||
of, _ := w.Create(file.Name)
|
of, _ := w.Create(file.Name)
|
||||||
of.Write([]byte(file.Body))
|
of.Write([]byte(file.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
of, _ := w.Create("1/moo.txt")
|
||||||
|
of.Write([]byte(moo1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mothballs) createMothball(cat string) {
|
||||||
|
m.createMothballWithMoo1(cat, "moo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestMothballs() *Mothballs {
|
func NewTestMothballs() *Mothballs {
|
||||||
|
@ -92,10 +100,23 @@ func TestMothballs(t *testing.T) {
|
||||||
}
|
}
|
||||||
if ok, err := m.CheckAnswer("nealegory", 1, "moo"); ok {
|
if ok, err := m.CheckAnswer("nealegory", 1, "moo"); ok {
|
||||||
t.Error("Checking answer in non-existent category should fail")
|
t.Error("Checking answer in non-existent category should fail")
|
||||||
} else if err.Error() != "No such category: nealegory" {
|
} else if err.Error() != "no such category: nealegory" {
|
||||||
t.Error("Wrong error message")
|
t.Error("Wrong error message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goofyText := "bozonics"
|
||||||
|
log.Print("Hey Bozo")
|
||||||
|
//time.Sleep(1 * time.Second) // I don't love this, but we need the mtime to increase, and it's only accurate to 1s
|
||||||
|
m.createMothballWithMoo1("pategory", goofyText)
|
||||||
|
m.refresh()
|
||||||
|
if f, _, err := m.Open("pategory", 1, "moo.txt"); err != nil {
|
||||||
|
t.Error("pategory/1/moo.txt", err)
|
||||||
|
} else if contents, err := ioutil.ReadAll(f); err != nil {
|
||||||
|
t.Error("read all pategory/1/moo.txt", err)
|
||||||
|
} else if string(contents) != goofyText {
|
||||||
|
t.Error("read all replacement pategory/1/moo.txt contents wrong, got", string(contents))
|
||||||
|
}
|
||||||
|
|
||||||
m.createMothball("test2")
|
m.createMothball("test2")
|
||||||
m.Fs.Remove("pategory.mb")
|
m.Fs.Remove("pategory.mb")
|
||||||
m.refresh()
|
m.refresh()
|
||||||
|
|
|
@ -125,7 +125,7 @@ func (pc ProviderCommand) CheckAnswer(cat string, points int, answer string) (bo
|
||||||
|
|
||||||
// Mothball just returns an error
|
// Mothball just returns an error
|
||||||
func (pc ProviderCommand) Mothball(cat string) (*bytes.Reader, error) {
|
func (pc ProviderCommand) Mothball(cat string) (*bytes.Reader, error) {
|
||||||
return nil, fmt.Errorf("Can't package a command-generated category")
|
return nil, fmt.Errorf("can't package a command-generated category")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintain does nothing: a command puzzle ProviderCommand has no housekeeping
|
// Maintain does nothing: a command puzzle ProviderCommand has no housekeeping
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (mh *MothRequestHandler) PuzzlesOpen(cat string, points int, path string) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return nil, time.Time{}, fmt.Errorf("Puzzle does not exist or is locked")
|
return nil, time.Time{}, fmt.Errorf("puzzle does not exist or is locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try every provider until someone doesn't return an error
|
// Try every provider until someone doesn't return an error
|
||||||
|
@ -146,16 +146,16 @@ func (mh *MothRequestHandler) CheckAnswer(cat string, points int, answer string)
|
||||||
}
|
}
|
||||||
if !correct {
|
if !correct {
|
||||||
mh.State.LogEvent("wrong", mh.participantID, mh.teamID, cat, points)
|
mh.State.LogEvent("wrong", mh.participantID, mh.teamID, cat, points)
|
||||||
return fmt.Errorf("Incorrect answer")
|
return fmt.Errorf("incorrect answer")
|
||||||
}
|
}
|
||||||
|
|
||||||
mh.State.LogEvent("correct", mh.participantID, mh.teamID, cat, points)
|
mh.State.LogEvent("correct", mh.participantID, mh.teamID, cat, points)
|
||||||
|
|
||||||
if _, err := mh.State.TeamName(mh.teamID); err != nil {
|
if _, err := mh.State.TeamName(mh.teamID); err != nil {
|
||||||
return fmt.Errorf("Invalid team ID")
|
return fmt.Errorf("invalid team ID")
|
||||||
}
|
}
|
||||||
if err := mh.State.AwardPoints(mh.teamID, cat, points); err != nil {
|
if err := mh.State.AwardPoints(mh.teamID, cat, points); err != nil {
|
||||||
return fmt.Errorf("Error awarding points: %s", err)
|
return fmt.Errorf("error awarding points: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -170,7 +170,7 @@ func (mh *MothRequestHandler) ThemeOpen(path string) (ReadSeekCloser, time.Time,
|
||||||
func (mh *MothRequestHandler) Register(teamName string) error {
|
func (mh *MothRequestHandler) Register(teamName string) error {
|
||||||
// BUG(neale): Register returns an error if a team is already registered; it may make more sense to return success
|
// BUG(neale): Register returns an error if a team is already registered; it may make more sense to return success
|
||||||
if teamName == "" {
|
if teamName == "" {
|
||||||
return fmt.Errorf("Empty team name")
|
return fmt.Errorf("empty team name")
|
||||||
}
|
}
|
||||||
mh.State.LogEvent("register", mh.participantID, mh.teamID, "", 0)
|
mh.State.LogEvent("register", mh.participantID, mh.teamID, "", 0)
|
||||||
return mh.State.SetTeamName(mh.teamID, teamName)
|
return mh.State.SetTeamName(mh.teamID, teamName)
|
||||||
|
@ -254,7 +254,7 @@ func (mh *MothRequestHandler) Mothball(cat string, w io.Writer) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !mh.Config.Devel {
|
if !mh.Config.Devel {
|
||||||
return fmt.Errorf("Cannot mothball in production mode")
|
return fmt.Errorf("cannot mothball in production mode")
|
||||||
}
|
}
|
||||||
for _, provider := range mh.PuzzleProviders {
|
for _, provider := range mh.PuzzleProviders {
|
||||||
if err = provider.Mothball(cat, w); err == nil {
|
if err = provider.Mothball(cat, w); err == nil {
|
||||||
|
|
|
@ -27,7 +27,7 @@ const DistinguishableChars = "34678abcdefhikmnpqrtwxy="
|
||||||
const RFC3339Space = "2006-01-02 15:04:05Z07:00"
|
const RFC3339Space = "2006-01-02 15:04:05Z07:00"
|
||||||
|
|
||||||
// ErrAlreadyRegistered means a team cannot be registered because it was registered previously.
|
// ErrAlreadyRegistered means a team cannot be registered because it was registered previously.
|
||||||
var ErrAlreadyRegistered = errors.New("Team ID has already been registered")
|
var ErrAlreadyRegistered = errors.New("team ID has already been registered")
|
||||||
|
|
||||||
// State defines the current state of a MOTH instance.
|
// State defines the current state of a MOTH instance.
|
||||||
// We use the filesystem for synchronization between threads.
|
// We use the filesystem for synchronization between threads.
|
||||||
|
@ -123,9 +123,9 @@ func (s *State) TeamName(teamID string) (string, error) {
|
||||||
teamFs := afero.NewBasePathFs(s.Fs, "teams")
|
teamFs := afero.NewBasePathFs(s.Fs, "teams")
|
||||||
teamNameBytes, err := afero.ReadFile(teamFs, teamID)
|
teamNameBytes, err := afero.ReadFile(teamFs, teamID)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return "", fmt.Errorf("Unregistered team ID: %s", teamID)
|
return "", fmt.Errorf("unregistered team ID: %s", teamID)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return "", fmt.Errorf("Unregistered team ID: %s (%s)", teamID, err)
|
return "", fmt.Errorf("unregistered team ID: %s (%s)", teamID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
teamName := strings.TrimSpace(string(teamNameBytes))
|
teamName := strings.TrimSpace(string(teamNameBytes))
|
||||||
|
@ -137,7 +137,7 @@ func (s *State) TeamName(teamID string) (string, error) {
|
||||||
func (s *State) SetTeamName(teamID, teamName string) error {
|
func (s *State) SetTeamName(teamID, teamName string) error {
|
||||||
idsFile, err := s.Open("teamids.txt")
|
idsFile, err := s.Open("teamids.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Team IDs file does not exist")
|
return fmt.Errorf("team IDs file does not exist")
|
||||||
}
|
}
|
||||||
defer idsFile.Close()
|
defer idsFile.Close()
|
||||||
found := false
|
found := false
|
||||||
|
@ -149,7 +149,7 @@ func (s *State) SetTeamName(teamID, teamName string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("Team ID not found in list of valid Team IDs")
|
return fmt.Errorf("team ID not found in list of valid team IDs")
|
||||||
}
|
}
|
||||||
|
|
||||||
teamFilename := filepath.Join("teams", teamID)
|
teamFilename := filepath.Join("teams", teamID)
|
||||||
|
@ -211,7 +211,7 @@ func (s *State) AwardPoints(teamID, category string, points int) error {
|
||||||
|
|
||||||
for _, e := range s.PointsLog() {
|
for _, e := range s.PointsLog() {
|
||||||
if a.Equal(e) {
|
if a.Equal(e) {
|
||||||
return fmt.Errorf("Points already awarded to this team in this category")
|
return fmt.Errorf("points already awarded to this team in this category")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,13 +363,6 @@ func (s *State) maybeInitialize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logstr(s string) string {
|
|
||||||
if s == "" {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogEvent writes to the event log
|
// LogEvent writes to the event log
|
||||||
func (s *State) LogEvent(event, participantID, teamID, cat string, points int, extra ...string) {
|
func (s *State) LogEvent(event, participantID, teamID, cat string, points int, extra ...string) {
|
||||||
s.eventStream <- append(
|
s.eventStream <- append(
|
||||||
|
|
Loading…
Reference in New Issue