From f68201ab53156a40b167ce090c02db29b2f133e1 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 13 Oct 2021 18:25:27 -0600 Subject: [PATCH] Check for new mothballs with the same name Also updated some error messages to pass newer linter. Fixes #172 --- cmd/mothd/httpd.go | 4 ++-- cmd/mothd/httpd_test.go | 16 +++++++--------- cmd/mothd/mothballs.go | 22 +++++++++++++++++----- cmd/mothd/mothballs_test.go | 27 ++++++++++++++++++++++++--- cmd/mothd/providercommand.go | 2 +- cmd/mothd/server.go | 12 ++++++------ cmd/mothd/state.go | 19 ++++++------------- 7 files changed, 63 insertions(+), 39 deletions(-) diff --git a/cmd/mothd/httpd.go b/cmd/mothd/httpd.go index 87f8acf..54ca156 100644 --- a/cmd/mothd/httpd.go +++ b/cmd/mothd/httpd.go @@ -117,11 +117,11 @@ func (h *HTTPServer) RegisterHandler(mh MothRequestHandler, w http.ResponseWrite } 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 { jsend.Sendf(w, jsend.Fail, "not registered", err.Error()) } else { - jsend.Sendf(w, jsend.Success, "registered", "Team ID registered") + jsend.Sendf(w, jsend.Success, "registered", "team ID registered") } } diff --git a/cmd/mothd/httpd_test.go b/cmd/mothd/httpd_test.go index f8bbb5b..6c623c3 100644 --- a/cmd/mothd/httpd_test.go +++ b/cmd/mothd/httpd_test.go @@ -18,10 +18,8 @@ func (hs *HTTPServer) TestRequest(path string, args map[string]string) *httptest vals := url.Values{} vals.Set("pid", TestParticipantID) vals.Set("id", TestTeamID) - if args != nil { - for k, v := range args { - vals.Set(k, v) - } + for k, v := range args { + vals.Set(k, v) } 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 { 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") } if r := hs.TestRequest("/register", map[string]string{"name": "GoTeam"}); r.Result().StatusCode != 200 { 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") } if r := hs.TestRequest("/register", map[string]string{"name": "GoTeam"}); r.Result().StatusCode != 200 { 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()) } @@ -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 { 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()) } @@ -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 { 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()) } } diff --git a/cmd/mothd/mothballs.go b/cmd/mothd/mothballs.go index 8d44a0e..67b7679 100644 --- a/cmd/mothd/mothballs.go +++ b/cmd/mothd/mothballs.go @@ -19,6 +19,7 @@ import ( type zipCategory struct { afero.Fs io.Closer + mtime time.Time } // 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) { zc, ok := m.getCat(cat) 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)) @@ -91,12 +92,12 @@ func (m *Mothballs) Inventory() []Category { func (m *Mothballs) CheckAnswer(cat string, points int, answer string) (bool, error) { zfs, ok := m.getCat(cat) 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") if err != nil { - return false, fmt.Errorf("No answers.txt file") + return false, fmt.Errorf("no answers.txt file") } defer af.Close() @@ -132,7 +133,18 @@ func (m *Mothballs) refresh() { categoryName := strings.TrimSuffix(filename, ".mb") 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) if err != nil { log.Println(err) @@ -174,7 +186,7 @@ func (m *Mothballs) refresh() { // Mothball just returns an 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. diff --git a/cmd/mothd/mothballs_test.go b/cmd/mothd/mothballs_test.go index 9b75838..61cfff5 100644 --- a/cmd/mothd/mothballs_test.go +++ b/cmd/mothd/mothballs_test.go @@ -3,6 +3,8 @@ package main import ( "archive/zip" "fmt" + "io/ioutil" + "log" "testing" "github.com/spf13/afero" @@ -14,14 +16,13 @@ var testFiles = []struct { {"puzzles.txt", "1\n3\n2\n"}, {"answers.txt", "1 answer123\n1 answer456\n2 wat\n"}, {"1/puzzle.json", `{"name": "moo"}`}, - {"1/moo.txt", `moo`}, {"2/puzzle.json", `{}`}, {"2/moo.txt", `moo`}, {"3/puzzle.json", `{}`}, {"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)) defer f.Close() @@ -32,6 +33,13 @@ func (m *Mothballs) createMothball(cat string) { of, _ := w.Create(file.Name) 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 { @@ -92,10 +100,23 @@ func TestMothballs(t *testing.T) { } if ok, err := m.CheckAnswer("nealegory", 1, "moo"); ok { 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") } + 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.Fs.Remove("pategory.mb") m.refresh() diff --git a/cmd/mothd/providercommand.go b/cmd/mothd/providercommand.go index ef0ca0b..1bdd693 100644 --- a/cmd/mothd/providercommand.go +++ b/cmd/mothd/providercommand.go @@ -125,7 +125,7 @@ func (pc ProviderCommand) CheckAnswer(cat string, points int, answer string) (bo // Mothball just returns an 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 diff --git a/cmd/mothd/server.go b/cmd/mothd/server.go index f9d6775..608e6d6 100644 --- a/cmd/mothd/server.go +++ b/cmd/mothd/server.go @@ -115,7 +115,7 @@ func (mh *MothRequestHandler) PuzzlesOpen(cat string, points int, path string) ( } } 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 @@ -146,16 +146,16 @@ func (mh *MothRequestHandler) CheckAnswer(cat string, points int, answer string) } if !correct { 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) 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 { - return fmt.Errorf("Error awarding points: %s", err) + return fmt.Errorf("error awarding points: %s", err) } return nil @@ -170,7 +170,7 @@ func (mh *MothRequestHandler) ThemeOpen(path string) (ReadSeekCloser, time.Time, 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 if teamName == "" { - return fmt.Errorf("Empty team name") + return fmt.Errorf("empty team name") } mh.State.LogEvent("register", mh.participantID, mh.teamID, "", 0) return mh.State.SetTeamName(mh.teamID, teamName) @@ -254,7 +254,7 @@ func (mh *MothRequestHandler) Mothball(cat string, w io.Writer) error { var err error 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 { if err = provider.Mothball(cat, w); err == nil { diff --git a/cmd/mothd/state.go b/cmd/mothd/state.go index a542caa..e354b22 100644 --- a/cmd/mothd/state.go +++ b/cmd/mothd/state.go @@ -27,7 +27,7 @@ const DistinguishableChars = "34678abcdefhikmnpqrtwxy=" const RFC3339Space = "2006-01-02 15:04:05Z07:00" // 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. // 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") teamNameBytes, err := afero.ReadFile(teamFs, teamID) if os.IsNotExist(err) { - return "", fmt.Errorf("Unregistered team ID: %s", teamID) + return "", fmt.Errorf("unregistered team ID: %s", teamID) } 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)) @@ -137,7 +137,7 @@ func (s *State) TeamName(teamID string) (string, error) { func (s *State) SetTeamName(teamID, teamName string) error { idsFile, err := s.Open("teamids.txt") if err != nil { - return fmt.Errorf("Team IDs file does not exist") + return fmt.Errorf("team IDs file does not exist") } defer idsFile.Close() found := false @@ -149,7 +149,7 @@ func (s *State) SetTeamName(teamID, teamName string) error { } } 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) @@ -211,7 +211,7 @@ func (s *State) AwardPoints(teamID, category string, points int) error { for _, e := range s.PointsLog() { 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 func (s *State) LogEvent(event, participantID, teamID, cat string, points int, extra ...string) { s.eventStream <- append(