Check for new mothballs with the same name

Also updated some error messages to pass newer linter.

Fixes #172
This commit is contained in:
Neale Pickett 2021-10-13 18:25:27 -06:00
parent dd8ca81186
commit f68201ab53
7 changed files with 63 additions and 39 deletions

View File

@ -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")
}
}

View File

@ -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())
}
}

View File

@ -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.

View File

@ -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()

View File

@ -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

View File

@ -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 {

View File

@ -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(