111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package parser
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const validSolvedPuzzle = "123456789456789123789123456234567891567891234891234567345678912678912345912345678"
|
|
|
|
func bankRecord(digits string, rating float64) string {
|
|
hash := fmt.Sprintf("%x", sha1.Sum([]byte(digits)))[:12]
|
|
return fmt.Sprintf("%s %s %.1f", hash, digits, rating)
|
|
}
|
|
|
|
func TestPuzzleBankParsesLFCRLFAndFinalLine(t *testing.T) {
|
|
first := "0" + validSolvedPuzzle[1:]
|
|
data := bankRecord(first, 1.2) + "\r\n\n" + bankRecord(validSolvedPuzzle, 2.5)
|
|
parser := &PuzzleBank{}
|
|
if err := parser.Parse([]byte(data)); err != nil {
|
|
t.Fatalf("Parse() error = %v", err)
|
|
}
|
|
if got := len(parser.GetAllFields()); got != 2 {
|
|
t.Fatalf("field count = %d, want 2", got)
|
|
}
|
|
second, err := parser.GetField(1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if second.GetRating() != 2.5 || !second.IsSolved() {
|
|
t.Fatalf("second field: rating=%v solved=%v", second.GetRating(), second.IsSolved())
|
|
}
|
|
}
|
|
|
|
func TestPuzzleBankRejectsMalformedInput(t *testing.T) {
|
|
valid := bankRecord(validSolvedPuzzle, 1.2)
|
|
tests := map[string]string{
|
|
"empty": "\n\r\n",
|
|
"columns": "broken",
|
|
"hash length": "abc " + validSolvedPuzzle + " 1.2",
|
|
"hash mismatch": "ffffffffffff" + valid[12:],
|
|
"bad puzzle": bankRecord("x"+validSolvedPuzzle[1:], 1.2),
|
|
"bad rating": strings.TrimSuffix(valid, "1.2") + "nope",
|
|
}
|
|
for name, input := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
if err := (&PuzzleBank{}).Parse([]byte(input)); err == nil || (name != "empty" && !errors.Is(err, ErrInvalidInput)) {
|
|
t.Fatalf("Parse() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPuzzleBankParseIsAtomicAndDoesNotAppend(t *testing.T) {
|
|
parser := &PuzzleBank{}
|
|
valid := bankRecord(validSolvedPuzzle, 1.2)
|
|
if err := parser.Parse([]byte(valid)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
original, _ := parser.GetField(0)
|
|
if err := parser.Parse([]byte(valid + "\ninvalid")); err == nil {
|
|
t.Fatal("invalid Parse() error = nil")
|
|
}
|
|
afterFailure, _ := parser.GetField(0)
|
|
if afterFailure != original || len(parser.GetAllFields()) != 1 {
|
|
t.Fatal("failed parse changed previously parsed fields")
|
|
}
|
|
if err := parser.Parse([]byte(valid)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(parser.GetAllFields()) != 1 {
|
|
t.Fatal("repeated Parse() appended fields")
|
|
}
|
|
}
|
|
|
|
func TestParserHelperBoundsAndDefensiveSlice(t *testing.T) {
|
|
parser := &PuzzleString{}
|
|
if err := parser.Parse([]byte(validSolvedPuzzle)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, index := range []int{-1, 1} {
|
|
if _, err := parser.GetField(index); !errors.Is(err, ErrFieldIndex) {
|
|
t.Fatalf("GetField(%d) error = %v", index, err)
|
|
}
|
|
}
|
|
fields := parser.GetAllFields()
|
|
fields[0] = nil
|
|
if stored, err := parser.GetField(0); err != nil || stored == nil {
|
|
t.Fatal("GetAllFields exposed its backing slice")
|
|
}
|
|
}
|
|
|
|
func TestPuzzleStringValidatesInput(t *testing.T) {
|
|
parser := &PuzzleString{}
|
|
if err := parser.Parse([]byte(" \n" + validSolvedPuzzle + "\n")); err != nil {
|
|
t.Fatalf("Parse() error = %v", err)
|
|
}
|
|
if fields := parser.GetAllFields(); len(fields) != 1 || !fields[0].IsSolved() {
|
|
t.Fatal("valid puzzle string was not parsed")
|
|
}
|
|
|
|
invalid := []string{"", validSolvedPuzzle[:80], "x" + validSolvedPuzzle[1:], "11" + validSolvedPuzzle[2:]}
|
|
for _, input := range invalid {
|
|
if err := (&PuzzleString{}).Parse([]byte(input)); err == nil {
|
|
t.Fatalf("Parse(%q) error = nil", input)
|
|
}
|
|
}
|
|
}
|