Implement collections tests and fix database usage
This commit is contained in:
parent
9fd69fba38
commit
f9fdef9466
1242
internal/collections/collections_test.go
Normal file
1242
internal/collections/collections_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,31 +23,20 @@ func NewDatabaseCollectionManager(db *database.DB) *DatabaseCollectionManager {
|
||||
func (dcm *DatabaseCollectionManager) LoadCollections(ctx context.Context) ([]CollectionData, error) {
|
||||
query := "SELECT `id`, `collection_name`, `collection_category`, `level` FROM `collections`"
|
||||
|
||||
rows, err := dcm.db.QueryContext(ctx, query)
|
||||
var collections []CollectionData
|
||||
err := dcm.db.Query(query, func(row *database.Row) error {
|
||||
var collection CollectionData
|
||||
collection.ID = int32(row.Int(0))
|
||||
collection.Name = row.Text(1)
|
||||
collection.Category = row.Text(2)
|
||||
collection.Level = int8(row.Int(3))
|
||||
collections = append(collections, collection)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query collections: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var collections []CollectionData
|
||||
for rows.Next() {
|
||||
var collection CollectionData
|
||||
err := rows.Scan(
|
||||
&collection.ID,
|
||||
&collection.Name,
|
||||
&collection.Category,
|
||||
&collection.Level,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan collection row: %w", err)
|
||||
}
|
||||
|
||||
collections = append(collections, collection)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating collection rows: %w", err)
|
||||
}
|
||||
|
||||
return collections, nil
|
||||
}
|
||||
@ -59,30 +48,19 @@ func (dcm *DatabaseCollectionManager) LoadCollectionItems(ctx context.Context, c
|
||||
WHERE collection_id = ?
|
||||
ORDER BY item_index ASC`
|
||||
|
||||
rows, err := dcm.db.QueryContext(ctx, query, collectionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query collection items for collection %d: %w", collectionID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var items []CollectionItem
|
||||
for rows.Next() {
|
||||
err := dcm.db.Query(query, func(row *database.Row) error {
|
||||
var item CollectionItem
|
||||
err := rows.Scan(
|
||||
&item.ItemID,
|
||||
&item.Index,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan collection item row: %w", err)
|
||||
}
|
||||
|
||||
item.ItemID = int32(row.Int(0))
|
||||
item.Index = int8(row.Int(1))
|
||||
// Items start as not found
|
||||
item.Found = ItemNotFound
|
||||
items = append(items, item)
|
||||
}
|
||||
return nil
|
||||
}, collectionID)
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating collection item rows: %w", err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query collection items for collection %d: %w", collectionID, err)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
@ -94,31 +72,20 @@ func (dcm *DatabaseCollectionManager) LoadCollectionRewards(ctx context.Context,
|
||||
FROM collection_rewards
|
||||
WHERE collection_id = ?`
|
||||
|
||||
rows, err := dcm.db.QueryContext(ctx, query, collectionID)
|
||||
var rewards []CollectionRewardData
|
||||
err := dcm.db.Query(query, func(row *database.Row) error {
|
||||
var reward CollectionRewardData
|
||||
reward.CollectionID = int32(row.Int(0))
|
||||
reward.RewardType = row.Text(1)
|
||||
reward.RewardValue = row.Text(2)
|
||||
reward.Quantity = int8(row.Int(3))
|
||||
rewards = append(rewards, reward)
|
||||
return nil
|
||||
}, collectionID)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query collection rewards for collection %d: %w", collectionID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var rewards []CollectionRewardData
|
||||
for rows.Next() {
|
||||
var reward CollectionRewardData
|
||||
err := rows.Scan(
|
||||
&reward.CollectionID,
|
||||
&reward.RewardType,
|
||||
&reward.RewardValue,
|
||||
&reward.Quantity,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan collection reward row: %w", err)
|
||||
}
|
||||
|
||||
rewards = append(rewards, reward)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating collection reward rows: %w", err)
|
||||
}
|
||||
|
||||
return rewards, nil
|
||||
}
|
||||
@ -129,32 +96,19 @@ func (dcm *DatabaseCollectionManager) LoadPlayerCollections(ctx context.Context,
|
||||
FROM character_collections
|
||||
WHERE char_id = ?`
|
||||
|
||||
rows, err := dcm.db.QueryContext(ctx, query, characterID)
|
||||
var collections []PlayerCollectionData
|
||||
err := dcm.db.Query(query, func(row *database.Row) error {
|
||||
var collection PlayerCollectionData
|
||||
collection.CharacterID = int32(row.Int(0))
|
||||
collection.CollectionID = int32(row.Int(1))
|
||||
collection.Completed = row.Bool(2)
|
||||
collections = append(collections, collection)
|
||||
return nil
|
||||
}, characterID)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query player collections for character %d: %w", characterID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var collections []PlayerCollectionData
|
||||
for rows.Next() {
|
||||
var collection PlayerCollectionData
|
||||
var completed int
|
||||
err := rows.Scan(
|
||||
&collection.CharacterID,
|
||||
&collection.CollectionID,
|
||||
&completed,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan player collection row: %w", err)
|
||||
}
|
||||
|
||||
collection.Completed = completed == 1
|
||||
collections = append(collections, collection)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating player collection rows: %w", err)
|
||||
}
|
||||
|
||||
return collections, nil
|
||||
}
|
||||
@ -165,26 +119,16 @@ func (dcm *DatabaseCollectionManager) LoadPlayerCollectionItems(ctx context.Cont
|
||||
FROM character_collection_items
|
||||
WHERE char_id = ? AND collection_id = ?`
|
||||
|
||||
rows, err := dcm.db.QueryContext(ctx, query, characterID, collectionID)
|
||||
var itemIDs []int32
|
||||
err := dcm.db.Query(query, func(row *database.Row) error {
|
||||
itemID := int32(row.Int(0))
|
||||
itemIDs = append(itemIDs, itemID)
|
||||
return nil
|
||||
}, characterID, collectionID)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query player collection items for character %d, collection %d: %w", characterID, collectionID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var itemIDs []int32
|
||||
for rows.Next() {
|
||||
var itemID int32
|
||||
err := rows.Scan(&itemID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan player collection item row: %w", err)
|
||||
}
|
||||
|
||||
itemIDs = append(itemIDs, itemID)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating player collection item rows: %w", err)
|
||||
}
|
||||
|
||||
return itemIDs, nil
|
||||
}
|
||||
@ -201,7 +145,7 @@ func (dcm *DatabaseCollectionManager) SavePlayerCollection(ctx context.Context,
|
||||
ON CONFLICT(char_id, collection_id)
|
||||
DO UPDATE SET completed = ?`
|
||||
|
||||
_, err := dcm.db.ExecContext(ctx, query, characterID, collectionID, completedInt, completedInt)
|
||||
err := dcm.db.Exec(query, characterID, collectionID, completedInt, completedInt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save player collection for character %d, collection %d: %w", characterID, collectionID, err)
|
||||
}
|
||||
@ -214,7 +158,7 @@ func (dcm *DatabaseCollectionManager) SavePlayerCollectionItem(ctx context.Conte
|
||||
query := `INSERT OR IGNORE INTO character_collection_items (char_id, collection_id, collection_item_id)
|
||||
VALUES (?, ?, ?)`
|
||||
|
||||
_, err := dcm.db.ExecContext(ctx, query, characterID, collectionID, itemID)
|
||||
err := dcm.db.Exec(query, characterID, collectionID, itemID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save player collection item for character %d, collection %d, item %d: %w", characterID, collectionID, itemID, err)
|
||||
}
|
||||
@ -229,37 +173,34 @@ func (dcm *DatabaseCollectionManager) SavePlayerCollections(ctx context.Context,
|
||||
}
|
||||
|
||||
// Use a transaction for atomic updates
|
||||
tx, err := dcm.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
err := dcm.db.Transaction(func(db *database.DB) error {
|
||||
for _, collection := range collections {
|
||||
if !collection.GetSaveNeeded() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Save collection completion status
|
||||
if err := dcm.savePlayerCollectionTx(ctx, tx, characterID, collection); err != nil {
|
||||
if err := dcm.savePlayerCollectionInTx(db, characterID, collection); err != nil {
|
||||
return fmt.Errorf("failed to save collection %d: %w", collection.GetID(), err)
|
||||
}
|
||||
|
||||
// Save found items
|
||||
if err := dcm.savePlayerCollectionItemsTx(ctx, tx, characterID, collection); err != nil {
|
||||
if err := dcm.savePlayerCollectionItemsInTx(db, characterID, collection); err != nil {
|
||||
return fmt.Errorf("failed to save collection items for collection %d: %w", collection.GetID(), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transaction failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// savePlayerCollectionTx saves a single collection within a transaction
|
||||
func (dcm *DatabaseCollectionManager) savePlayerCollectionTx(ctx context.Context, tx database.Tx, characterID int32, collection *Collection) error {
|
||||
// savePlayerCollectionInTx saves a single collection within a transaction
|
||||
func (dcm *DatabaseCollectionManager) savePlayerCollectionInTx(db *database.DB, characterID int32, collection *Collection) error {
|
||||
completedInt := 0
|
||||
if collection.GetCompleted() {
|
||||
completedInt = 1
|
||||
@ -270,12 +211,12 @@ func (dcm *DatabaseCollectionManager) savePlayerCollectionTx(ctx context.Context
|
||||
ON CONFLICT(char_id, collection_id)
|
||||
DO UPDATE SET completed = ?`
|
||||
|
||||
_, err := tx.ExecContext(ctx, query, characterID, collection.GetID(), completedInt, completedInt)
|
||||
err := db.Exec(query, characterID, collection.GetID(), completedInt, completedInt)
|
||||
return err
|
||||
}
|
||||
|
||||
// savePlayerCollectionItemsTx saves collection items within a transaction
|
||||
func (dcm *DatabaseCollectionManager) savePlayerCollectionItemsTx(ctx context.Context, tx database.Tx, characterID int32, collection *Collection) error {
|
||||
// savePlayerCollectionItemsInTx saves collection items within a transaction
|
||||
func (dcm *DatabaseCollectionManager) savePlayerCollectionItemsInTx(db *database.DB, characterID int32, collection *Collection) error {
|
||||
items := collection.GetCollectionItems()
|
||||
|
||||
for _, item := range items {
|
||||
@ -283,7 +224,7 @@ func (dcm *DatabaseCollectionManager) savePlayerCollectionItemsTx(ctx context.Co
|
||||
query := `INSERT OR IGNORE INTO character_collection_items (char_id, collection_id, collection_item_id)
|
||||
VALUES (?, ?, ?)`
|
||||
|
||||
_, err := tx.ExecContext(ctx, query, characterID, collection.GetID(), item.ItemID)
|
||||
err := db.Exec(query, characterID, collection.GetID(), item.ItemID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save item %d: %w", item.ItemID, err)
|
||||
}
|
||||
@ -339,7 +280,7 @@ func (dcm *DatabaseCollectionManager) EnsureCollectionTables(ctx context.Context
|
||||
}
|
||||
|
||||
for i, query := range queries {
|
||||
_, err := dcm.db.ExecContext(ctx, query)
|
||||
err := dcm.db.Exec(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create collection table %d: %w", i+1, err)
|
||||
}
|
||||
@ -357,7 +298,7 @@ func (dcm *DatabaseCollectionManager) EnsureCollectionTables(ctx context.Context
|
||||
}
|
||||
|
||||
for i, query := range indexes {
|
||||
_, err := dcm.db.ExecContext(ctx, query)
|
||||
err := dcm.db.Exec(query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create collection index %d: %w", i+1, err)
|
||||
}
|
||||
@ -370,52 +311,59 @@ func (dcm *DatabaseCollectionManager) EnsureCollectionTables(ctx context.Context
|
||||
func (dcm *DatabaseCollectionManager) GetCollectionCount(ctx context.Context) (int, error) {
|
||||
query := "SELECT COUNT(*) FROM collections"
|
||||
|
||||
var count int
|
||||
err := dcm.db.QueryRowContext(ctx, query).Scan(&count)
|
||||
row, err := dcm.db.QueryRow(query)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get collection count: %w", err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
return count, nil
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return row.Int(0), nil
|
||||
}
|
||||
|
||||
// GetPlayerCollectionCount returns the number of collections a player has
|
||||
func (dcm *DatabaseCollectionManager) GetPlayerCollectionCount(ctx context.Context, characterID int32) (int, error) {
|
||||
query := "SELECT COUNT(*) FROM character_collections WHERE char_id = ?"
|
||||
|
||||
var count int
|
||||
err := dcm.db.QueryRowContext(ctx, query, characterID).Scan(&count)
|
||||
row, err := dcm.db.QueryRow(query, characterID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get player collection count for character %d: %w", characterID, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
return count, nil
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return row.Int(0), nil
|
||||
}
|
||||
|
||||
// GetCompletedCollectionCount returns the number of completed collections for a player
|
||||
func (dcm *DatabaseCollectionManager) GetCompletedCollectionCount(ctx context.Context, characterID int32) (int, error) {
|
||||
query := "SELECT COUNT(*) FROM character_collections WHERE char_id = ? AND completed = 1"
|
||||
|
||||
var count int
|
||||
err := dcm.db.QueryRowContext(ctx, query, characterID).Scan(&count)
|
||||
row, err := dcm.db.QueryRow(query, characterID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get completed collection count for character %d: %w", characterID, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
return count, nil
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return row.Int(0), nil
|
||||
}
|
||||
|
||||
// DeletePlayerCollection removes a player's collection progress
|
||||
func (dcm *DatabaseCollectionManager) DeletePlayerCollection(ctx context.Context, characterID, collectionID int32) error {
|
||||
// Use a transaction to ensure both tables are updated atomically
|
||||
tx, err := dcm.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
err := dcm.db.Transaction(func(db *database.DB) error {
|
||||
// Delete collection items first due to foreign key constraint
|
||||
_, err = tx.ExecContext(ctx,
|
||||
err := db.Exec(
|
||||
"DELETE FROM character_collection_items WHERE char_id = ? AND collection_id = ?",
|
||||
characterID, collectionID)
|
||||
if err != nil {
|
||||
@ -423,15 +371,18 @@ func (dcm *DatabaseCollectionManager) DeletePlayerCollection(ctx context.Context
|
||||
}
|
||||
|
||||
// Delete collection
|
||||
_, err = tx.ExecContext(ctx,
|
||||
err = db.Exec(
|
||||
"DELETE FROM character_collections WHERE char_id = ? AND collection_id = ?",
|
||||
characterID, collectionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete player collection: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("transaction failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -442,50 +393,78 @@ func (dcm *DatabaseCollectionManager) GetCollectionStatistics(ctx context.Contex
|
||||
var stats CollectionStatistics
|
||||
|
||||
// Total collections
|
||||
err := dcm.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM collections").Scan(&stats.TotalCollections)
|
||||
row, err := dcm.db.QueryRow("SELECT COUNT(*) FROM collections")
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get total collections: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.TotalCollections = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
// Total collection items
|
||||
err = dcm.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM collection_details").Scan(&stats.TotalItems)
|
||||
row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM collection_details")
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get total items: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.TotalItems = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
// Players with collections
|
||||
err = dcm.db.QueryRowContext(ctx, "SELECT COUNT(DISTINCT char_id) FROM character_collections").Scan(&stats.PlayersWithCollections)
|
||||
row, err = dcm.db.QueryRow("SELECT COUNT(DISTINCT char_id) FROM character_collections")
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get players with collections: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.PlayersWithCollections = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
// Completed collections across all players
|
||||
err = dcm.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM character_collections WHERE completed = 1").Scan(&stats.CompletedCollections)
|
||||
row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM character_collections WHERE completed = 1")
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get completed collections: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.CompletedCollections = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
// Active collections (incomplete with at least one item found) across all players
|
||||
query := `SELECT COUNT(DISTINCT cc.char_id, cc.collection_id)
|
||||
query := `SELECT COUNT(DISTINCT cc.char_id || '-' || cc.collection_id)
|
||||
FROM character_collections cc
|
||||
JOIN character_collection_items cci ON cc.char_id = cci.char_id AND cc.collection_id = cci.collection_id
|
||||
WHERE cc.completed = 0`
|
||||
err = dcm.db.QueryRowContext(ctx, query).Scan(&stats.ActiveCollections)
|
||||
row, err = dcm.db.QueryRow(query)
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get active collections: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.ActiveCollections = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
// Found items across all players
|
||||
err = dcm.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM character_collection_items").Scan(&stats.FoundItems)
|
||||
row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM character_collection_items")
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get found items: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.FoundItems = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
// Total rewards
|
||||
err = dcm.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM collection_rewards").Scan(&stats.TotalRewards)
|
||||
row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM collection_rewards")
|
||||
if err != nil {
|
||||
return stats, fmt.Errorf("failed to get total rewards: %w", err)
|
||||
}
|
||||
if row != nil {
|
||||
stats.TotalRewards = row.Int(0)
|
||||
row.Close()
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user