Enha: create tx; cardword test

This commit is contained in:
Grail Finder
2025-07-04 07:00:16 +03:00
parent c9196d3202
commit 8b81e2e2c4
3 changed files with 553 additions and 1 deletions

View File

@ -159,6 +159,21 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) {
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return
} }
// Initialize transaction
ctx, tx, err := repo.InitTx(r.Context())
if err != nil {
log.Error("failed to init transaction", "error", err)
abortWithError(w, err.Error())
return
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
fi.Room.IsRunning = true fi.Room.IsRunning = true
fi.Room.IsOver = false fi.Room.IsOver = false
fi.Room.TeamTurn = "blue" fi.Room.TeamTurn = "blue"
@ -174,10 +189,42 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) {
Action: models.ActionTypeGameStarted, Action: models.ActionTypeGameStarted,
} }
fi.Room.ActionHistory = append(fi.Room.ActionHistory, action) fi.Room.ActionHistory = append(fi.Room.ActionHistory, action)
if err := saveFullInfo(r.Context(), fi); err != nil {
// Use the new context with transaction
if err := saveFullInfo(ctx, fi); err != nil {
tx.Rollback()
abortWithError(w, err.Error()) abortWithError(w, err.Error())
return return
} }
// Save action history
action.RoomID = fi.Room.ID
action.CreatedAtUnix = time.Now().Unix()
if err := repo.CreateAction(ctx, fi.Room.ID, &action); err != nil {
tx.Rollback()
log.Error("failed to save action", "error", err)
abortWithError(w, err.Error())
return
}
// Save word cards
for _, card := range fi.Room.Cards {
card.RoomID = fi.Room.ID // Ensure RoomID is set for each card
if err := repo.WordCardsCreate(ctx, &card); err != nil {
tx.Rollback()
log.Error("failed to save word card", "error", err)
abortWithError(w, err.Error())
return
}
}
// Commit the transaction
if err := tx.Commit(); err != nil {
log.Error("failed to commit transaction", "error", err)
abortWithError(w, err.Error())
return
}
// reveal all cards // reveal all cards
if fi.State.Role == "mime" { if fi.State.Role == "mime" {
fi.Room.MimeView() fi.Room.MimeView()

View File

@ -17,6 +17,7 @@ type AllRepos interface {
PlayersRepo PlayersRepo
SessionsRepo SessionsRepo
WordCardsRepo WordCardsRepo
InitTx(ctx context.Context) (context.Context, *sqlx.Tx, error)
} }
type RepoProvider struct { type RepoProvider struct {
@ -95,3 +96,11 @@ func getDB(ctx context.Context, db *sqlx.DB) sqlx.ExtContext {
} }
return db return db
} }
func (p *RepoProvider) InitTx(ctx context.Context) (context.Context, *sqlx.Tx, error) {
tx, err := p.DB.BeginTxx(ctx, nil)
if err != nil {
return nil, nil, err
}
return context.WithValue(ctx, "tx", tx), tx, nil
}

496
repos/word_cards_test.go Normal file
View File

@ -0,0 +1,496 @@
package repos
import (
"context"
"gralias/models"
"os"
"testing"
"time"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
_ "github.com/mattn/go-sqlite3"
)
func TestWordCardsRepo_Create(t *testing.T) {
// Setup temporary file-based SQLite database for this test
tempFile, err := os.CreateTemp("", "test_db_*.db")
assert.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
db, err := sqlx.Connect("sqlite3", tempFile.Name())
assert.NoError(t, err)
defer db.Close()
// Enable foreign key constraints
_, err = db.Exec("PRAGMA foreign_keys = ON;")
assert.NoError(t, err)
// Apply schema
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
creator_name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS word_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id TEXT NOT NULL,
word TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
revealed BOOLEAN NOT NULL DEFAULT FALSE,
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
`
_, err = db.Exec(schema)
assert.NoError(t, err)
ctx := context.Background()
roomID := "test_room_1"
// Insert a room first, as word_cards has a foreign key to rooms
_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name) VALUES (?, ?, ?)`, roomID, time.Now(), "test_creator")
assert.NoError(t, err)
// Test single card creation
card1 := &models.WordCard{
RoomID: roomID,
Word: "apple",
Color: models.WordColorRed,
Revealed: false,
Mime: false,
}
_, err = db.Exec(`INSERT INTO word_cards (room_id, word, color, revealed, mime_view) VALUES (?, ?, ?, ?, ?)`, card1.RoomID, card1.Word, card1.Color, card1.Revealed, card1.Mime)
assert.NoError(t, err)
var count int
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 1, count)
// Test batch card creation with transaction commit
tx, err := db.BeginTxx(ctx, nil)
assert.NoError(t, err)
card2 := &models.WordCard{
RoomID: roomID,
Word: "banana",
Color: models.WordColorBlue,
Revealed: false,
Mime: false,
}
card3 := &models.WordCard{
RoomID: roomID,
Word: "cherry",
Color: models.WordColorBlack,
Revealed: false,
Mime: false,
}
_, err = tx.Exec(`INSERT INTO word_cards (room_id, word, color, revealed, mime_view) VALUES (?, ?, ?, ?, ?)`, card2.RoomID, card2.Word, card2.Color, card2.Revealed, card2.Mime)
assert.NoError(t, err)
_, err = tx.Exec(`INSERT INTO word_cards (room_id, word, color, revealed, mime_view) VALUES (?, ?, ?, ?, ?)`, card3.RoomID, card3.Word, card3.Color, card3.Revealed, card3.Mime)
assert.NoError(t, err)
// Before commit, count should not reflect changes if using a transaction context
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 1, count) // Should still be 1 if not committed
err = tx.Commit()
assert.NoError(t, err)
// After commit, count should reflect changes
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 3, count)
// Test transaction rollback
tx2, err := db.BeginTxx(ctx, nil)
assert.NoError(t, err)
card4 := &models.WordCard{
RoomID: roomID,
Word: "date",
Color: models.WordColorWhite,
Revealed: false,
Mime: false,
}
_, err = tx2.Exec(`INSERT INTO word_cards (room_id, word, color, revealed, mime_view) VALUES (?, ?, ?, ?, ?)`, card4.RoomID, card4.Word, card4.Color, card4.Revealed, card4.Mime)
assert.NoError(t, err)
err = tx2.Rollback()
assert.NoError(t, err)
// After rollback, count should not reflect changes
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 3, count)
}
func TestWordCardsRepo_GetByWordAndRoomID(t *testing.T) {
// Setup temporary file-based SQLite database for this test
tempFile, err := os.CreateTemp("", "test_db_*.db")
assert.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
db, err := sqlx.Connect("sqlite3", tempFile.Name())
assert.NoError(t, err)
defer db.Close()
// Enable foreign key constraints
_, err = db.Exec("PRAGMA foreign_keys = ON;")
assert.NoError(t, err)
// Apply schema
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
creator_name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS word_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id TEXT NOT NULL,
word TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
revealed BOOLEAN NOT NULL DEFAULT FALSE,
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
`
_, err = db.Exec(schema)
assert.NoError(t, err)
repo := &RepoProvider{DB: db}
ctx := context.Background()
roomID := "test_room_3"
_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name) VALUES (?, ?, ?)`, roomID, time.Now(), "test_creator")
assert.NoError(t, err)
card := &models.WordCard{
RoomID: roomID,
Word: "gamma",
Color: models.WordColorRed,
Revealed: false,
Mime: false,
}
err = repo.WordCardsCreate(ctx, card)
assert.NoError(t, err)
retrievedCard, err := repo.WordCardGetByWordAndRoomID(ctx, "gamma", roomID)
assert.NoError(t, err)
assert.NotNil(t, retrievedCard)
assert.Equal(t, "gamma", retrievedCard.Word)
assert.Equal(t, roomID, retrievedCard.RoomID)
// Test non-existent card
_, err = repo.WordCardGetByWordAndRoomID(ctx, "non_existent", roomID)
assert.Error(t, err)
}
func TestWordCardsRepo_Reveal(t *testing.T) {
// Setup temporary file-based SQLite database for this test
tempFile, err := os.CreateTemp("", "test_db_*.db")
assert.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
db, err := sqlx.Connect("sqlite3", tempFile.Name())
assert.NoError(t, err)
defer db.Close()
// Enable foreign key constraints
_, err = db.Exec("PRAGMA foreign_keys = ON;")
assert.NoError(t, err)
// Apply schema
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
creator_name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS word_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id TEXT NOT NULL,
word TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
revealed BOOLEAN NOT NULL DEFAULT FALSE,
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
`
_, err = db.Exec(schema)
assert.NoError(t, err)
repo := &RepoProvider{DB: db}
ctx := context.Background()
roomID := "test_room_4"
_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name) VALUES (?, ?, ?)`, roomID, time.Now(), "test_creator")
assert.NoError(t, err)
card := &models.WordCard{
RoomID: roomID,
Word: "delta",
Color: models.WordColorRed,
Revealed: false,
Mime: false,
}
err = repo.WordCardsCreate(ctx, card)
assert.NoError(t, err)
// Verify initial state
var revealed bool
err = db.Get(&revealed, "SELECT revealed FROM word_cards WHERE word = ? AND room_id = ?", "delta", roomID)
assert.NoError(t, err)
assert.False(t, revealed)
// Reveal the card
err = repo.WordCardReveal(ctx, "delta", roomID)
assert.NoError(t, err)
// Verify revealed state
err = db.Get(&revealed, "SELECT revealed FROM word_cards WHERE word = ? AND room_id = ?", "delta", roomID)
assert.NoError(t, err)
assert.True(t, revealed)
}
func TestWordCardsRepo_RevealAll(t *testing.T) {
// Setup temporary file-based SQLite database for this test
tempFile, err := os.CreateTemp("", "test_db_*.db")
assert.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
db, err := sqlx.Connect("sqlite3", tempFile.Name())
assert.NoError(t, err)
defer db.Close()
// Enable foreign key constraints
_, err = db.Exec("PRAGMA foreign_keys = ON;")
assert.NoError(t, err)
// Apply schema
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
creator_name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS word_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id TEXT NOT NULL,
word TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
revealed BOOLEAN NOT NULL DEFAULT FALSE,
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
`
_, err = db.Exec(schema)
assert.NoError(t, err)
repo := &RepoProvider{DB: db}
ctx := context.Background()
roomID := "test_room_5"
_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name) VALUES (?, ?, ?)`, roomID, time.Now(), "test_creator")
assert.NoError(t, err)
cardsToInsert := []*models.WordCard{
{
RoomID: roomID,
Word: "echo",
Color: models.WordColorRed,
Revealed: false,
Mime: false,
},
{
RoomID: roomID,
Color: models.WordColorBlue,
Revealed: false,
Mime: false,
},
}
for _, card := range cardsToInsert {
err = repo.WordCardsCreate(ctx, card)
assert.NoError(t, err)
}
// Verify initial state
var count int
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ? AND revealed = FALSE", roomID)
assert.NoError(t, err)
assert.Equal(t, 2, count)
// Reveal all cards
err = repo.WordCardsRevealAll(ctx, roomID)
assert.NoError(t, err)
// Verify all cards are revealed
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ? AND revealed = FALSE", roomID)
assert.NoError(t, err)
assert.Equal(t, 0, count)
}
func TestWordCardsRepo_DeleteByRoomID(t *testing.T) {
// Setup temporary file-based SQLite database for this test
tempFile, err := os.CreateTemp("", "test_db_*.db")
assert.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
db, err := sqlx.Connect("sqlite3", tempFile.Name())
assert.NoError(t, err)
defer db.Close()
// Enable foreign key constraints
_, err = db.Exec("PRAGMA foreign_keys = ON;")
assert.NoError(t, err)
// Apply schema
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
creator_name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS word_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id TEXT NOT NULL,
word TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
revealed BOOLEAN NOT NULL DEFAULT FALSE,
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
`
_, err = db.Exec(schema)
assert.NoError(t, err)
repo := &RepoProvider{DB: db}
ctx := context.Background()
roomID := "test_room_6"
_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name) VALUES (?, ?, ?)`, roomID, time.Now(), "test_creator")
assert.NoError(t, err)
cardsToInsert := []*models.WordCard{
{
RoomID: roomID,
Word: "golf",
Color: models.WordColorRed,
Revealed: false,
Mime: false,
},
{
RoomID: roomID,
Word: "hotel",
Color: models.WordColorBlue,
Revealed: false,
Mime: false,
},
}
for _, card := range cardsToInsert {
err = repo.WordCardsCreate(ctx, card)
assert.NoError(t, err)
}
// Verify initial state
var count int
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 2, count)
// Delete cards by room ID
err = repo.WordCardsDeleteByRoomID(ctx, roomID)
assert.NoError(t, err)
// Verify cards are deleted
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 0, count)
}
func TestWordCardsRepo_CascadeDeleteRoom(t *testing.T) {
// Setup temporary file-based SQLite database for this test
tempFile, err := os.CreateTemp("", "test_db_*.db")
assert.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
db, err := sqlx.Connect("sqlite3", tempFile.Name())
assert.NoError(t, err)
defer db.Close()
// Enable foreign key constraints
_, err = db.Exec("PRAGMA foreign_keys = ON;")
assert.NoError(t, err)
// Apply schema
schema := `
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
creator_name TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS word_cards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id TEXT NOT NULL,
word TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '',
revealed BOOLEAN NOT NULL DEFAULT FALSE,
mime_view BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
);
`
_, err = db.Exec(schema)
assert.NoError(t, err)
repo := &RepoProvider{DB: db}
ctx := context.Background()
roomID := "test_room_7"
_, err = db.Exec(`INSERT INTO rooms (id, created_at, creator_name) VALUES (?, ?, ?)`, roomID, time.Now(), "test_creator")
assert.NoError(t, err)
card := &models.WordCard{
RoomID: roomID,
Word: "india",
Color: models.WordColorRed,
Revealed: false,
Mime: false,
}
err = repo.WordCardsCreate(ctx, card)
assert.NoError(t, err)
var count int
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 1, count)
_, err = db.Exec(`DELETE FROM rooms WHERE id = ?`, roomID)
assert.NoError(t, err)
err = db.Get(&count, "SELECT COUNT(*) FROM word_cards WHERE room_id = ?", roomID)
assert.NoError(t, err)
assert.Equal(t, 0, count)
}