main
Bel LaPointe 2024-12-15 01:15:36 -07:00
parent 659bf0f559
commit 39b1a6a1e8
6 changed files with 247 additions and 19 deletions

View File

@ -3,7 +3,6 @@ package main
import ( import (
"context" "context"
"database/sql" "database/sql"
"io"
"sync" "sync"
"time" "time"
@ -107,7 +106,3 @@ func (db DB) dial(ctx context.Context) (*sql.DB, error) {
} }
return c, nil return c, nil
} }
func (db DB) GetParty(id string) (string, error) {
return "", io.EOF
}

95
cmd/server/db_test.go Normal file
View File

@ -0,0 +1,95 @@
package main
import (
"context"
"database/sql"
"path"
"testing"
"time"
)
func newTestDB(t *testing.T) DB {
ctx, can := context.WithTimeout(context.Background(), time.Minute)
defer can()
conn := path.Join(t.TempDir(), "db")
db, err := NewDB(ctx, "sqlite", conn)
if err != nil {
t.Fatal(err)
}
return db
}
func TestDB(t *testing.T) {
ctx := context.Background()
db := newTestDB(t)
t.Run("with lock", func(t *testing.T) {
var called [2]bool
if err := db.WithLock(func() error {
for i := range called {
if err := db.Query(ctx, func(rows *sql.Rows) error {
return rows.Scan(&called[i])
}, `SELECT true`); err != nil {
t.Fatal(err)
}
}
return nil
}); err != nil {
t.Fatal(err)
}
if !called[0] {
t.Error(0)
}
if !called[1] {
t.Error(1)
}
})
t.Run("exec, query", func(t *testing.T) {
if err := db.Exec(ctx, `
CREATE TABLE IF NOT EXISTS my_table (
text TEXT,
datetime DATETIME,
number NUMBER
)
`); err != nil {
t.Fatal(err)
}
if err := db.Exec(ctx, `
INSERT INTO my_table (
text,
datetime,
number
) VALUES (?, ?, ?)
`, "text", time.Now(), 1); err != nil {
t.Fatal(err)
}
var text string
var datetime time.Time
var number int
if err := db.Query(ctx, func(rows *sql.Rows) error {
return rows.Scan(&text, &datetime, &number)
}, `
SELECT
text,
datetime,
number
FROM my_table
`); err != nil {
t.Fatal(err)
}
if text != "text" {
t.Error(text)
}
if datetime.IsZero() {
t.Error(datetime)
}
if number != 1 {
t.Error(number)
}
})
}

View File

@ -8,6 +8,8 @@ import (
"io" "io"
"slices" "slices"
"time" "time"
"github.com/google/uuid"
) )
type Games struct { type Games struct {
@ -61,15 +63,15 @@ func (games Games) GameByName(ctx context.Context, uid, name string) (string, er
err := games.db.Query(ctx, func(rows *sql.Rows) error { err := games.db.Query(ctx, func(rows *sql.Rows) error {
return rows.Scan(&result) return rows.Scan(&result)
}, ` }, `
SELECT SELECT
players.games_uuid players.game_uuid
FROM FROM
players players
JOIN games ON players.game_uuid=games.game_uuid JOIN games ON players.game_uuid=games.uuid
WHERE players.user_uuid=? AND games.name=? WHERE players.user_uuid=? AND games.name=?
ORDER BY games.timestamp DESC ORDER BY games.updated DESC
LIMIT 1 LIMIT 1
`, uid, name) `, uid, name)
return result, err return result, err
} }
@ -209,6 +211,32 @@ func (games Games) GameState(ctx context.Context, id string) (GameState, error)
return result, err return result, err
} }
func (games Games) CreateGame(ctx context.Context, name string) (string, error) {
var exists string
if err := games.db.Query(ctx,
func(rows *sql.Rows) error {
return rows.Scan(&exists)
},
`
SELECT uuid
FROM games
WHERE name=? AND completed IS NULL
`, name); err != nil {
return "", err
} else if exists != "" {
return exists, nil
}
id := uuid.New().String()
return id, games.db.Exec(ctx, `
INSERT INTO games (
uuid,
updated,
name
) VALUES (?, ?, ?)
`, id, time.Now(), name)
}
func (games Games) CreateEventPlayerJoin(ctx context.Context, id string, player string) error { func (games Games) CreateEventPlayerJoin(ctx context.Context, id string, player string) error {
return games.createEvent(ctx, id, EventPlayerJoin{ID: player}) return games.createEvent(ctx, id, EventPlayerJoin{ID: player})
} }
@ -217,10 +245,6 @@ func (games Games) CreateEventPlayerLeave(ctx context.Context, id string, player
return games.createEvent(ctx, id, EventPlayerLeave{ID: player}) return games.createEvent(ctx, id, EventPlayerLeave{ID: player})
} }
func (games Games) CreateEventGameComplete(ctx context.Context, id string) error {
return games.createEvent(ctx, id, EventGameComplete{})
}
func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, killer, killed, killWord string) error { func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string, killer, killed, killWord string) error {
// TODO gather current assignees // TODO gather current assignees
// TODO get victim's target // TODO get victim's target
@ -230,6 +254,10 @@ func (games Games) CreateEventAssignmentRotation(ctx context.Context, id string,
//return games.createEvent(ctx, id, v) //return games.createEvent(ctx, id, v)
} }
func (games Games) CreateEventGameComplete(ctx context.Context, id string) error {
return games.createEvent(ctx, id, EventGameComplete{})
}
func (games Games) createEvent(ctx context.Context, id string, v any) error { func (games Games) createEvent(ctx context.Context, id string, v any) error {
payload, err := json.Marshal(v) payload, err := json.Marshal(v)
if err != nil { if err != nil {

108
cmd/server/games_test.go Normal file
View File

@ -0,0 +1,108 @@
package main
import (
"context"
"fmt"
"testing"
)
func newTestGames(t *testing.T) Games {
db := newTestDB(t)
games, err := NewGames(context.Background(), db)
if err != nil {
t.Fatal(err)
}
return games
}
func TestGames(t *testing.T) {
ctx := context.Background()
t.Run("empty", func(t *testing.T) {
games := newTestGames(t)
if v, err := games.GamesForUser(ctx, ""); err != nil {
t.Error("err getting games for empty user:", err)
} else if len(v) > 0 {
t.Error(v)
}
if v, err := games.GameByName(ctx, "", ""); err != nil {
t.Error("err getting game by empty name for empty user:", err)
} else if len(v) > 0 {
t.Error(v)
}
if v, err := games.GameState(ctx, ""); err != nil {
t.Error("err getting game state for empty:", err)
} else if len(v.Players) > 0 || !v.Completed.IsZero() {
t.Error(v)
}
})
t.Run("mvp", func(t *testing.T) {
games := newTestGames(t)
id, err := games.CreateGame(ctx, "g1")
if err != nil {
t.Fatal("err creating game:", err)
} else if id2, err := games.CreateGame(ctx, "g1"); err != nil {
t.Fatal("err creating game redundantly:", err)
} else if id != id2 {
t.Fatal("redundant create game didnt return same id:", id2)
}
if err := games.CreateEventPlayerJoin(ctx, id, "p0"); err != nil {
t.Fatal("err creating event player join:", err)
} else if err := games.CreateEventPlayerLeave(ctx, id, "p0"); err != nil {
t.Fatal("err creating event player leave:", err)
}
for i := 0; i < 4; i++ {
p := fmt.Sprintf("p%d", i+1)
if err := games.CreateEventPlayerJoin(ctx, id, p); err != nil {
t.Fatal(p, "err creating event player join", err)
}
}
if err := games.CreateEventAssignmentRotation(ctx, id, "", "", ""); err != nil {
t.Fatal("err creating rotation:", err)
}
if v, err := games.GamesForUser(ctx, "p1"); err != nil {
t.Error("err getting games for user:", err)
} else if len(v) < 1 {
t.Error("no games found for user:", v)
} else if v[0] != id {
t.Error("wrong game found for user:", v)
}
if v, err := games.GameByName(ctx, "p1", "g1"); err != nil {
t.Error("err getting game by name for user:", err)
} else if v != id {
t.Error("wrong game by name for user:", v)
}
if v, err := games.GameState(ctx, id); err != nil {
t.Error("err getting game state:", err)
} else if len(v.Players) != 4 || !v.Completed.IsZero() {
t.Error("wrong game state:", v)
} else {
for i := 0; i < 4; i++ {
p := fmt.Sprintf("p%d", i+1)
if v.Players[p].KillWords.Global == "" {
t.Error(p, "no killwords.global")
} else if v.Players[p].KillWords.Assigned.IsZero() {
t.Error(p, "no killwords.assigned")
} else if v.Players[p].KillWords.Assignee == "" {
t.Error(p, "no killwords.assignee")
} else if len(v.Players[p].KillWords.Assignment.Public) == 0 {
t.Error(p, "no killwords.assigment.public")
} else if len(v.Players[p].KillWords.Assignment.Private) == 0 {
t.Error(p, "no killwords.assigment.private")
}
}
}
})
}

2
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect github.com/glebarez/sqlite v1.11.0 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.17 // indirect

2
go.sum
View File

@ -8,6 +8,8 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=