create items package
This commit is contained in:
parent
58248ec339
commit
0210e7dd28
@ -1,3 +1,11 @@
|
|||||||
|
// package install is the home of the install command
|
||||||
|
//
|
||||||
|
// Its purpose is to set up the intial database structure and data,
|
||||||
|
// then create a "demo" user to act as the initial admin account.
|
||||||
|
//
|
||||||
|
// At the moment, it simply creates a static structure and admin user;
|
||||||
|
// in the future I'd like to add migrations and prompt for account
|
||||||
|
// creation.
|
||||||
package install
|
package install
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -16,24 +24,20 @@ func Run() error {
|
|||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Open database connection
|
|
||||||
db, err := database.Open(dbPath)
|
db, err := database.Open(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
// Create tables
|
|
||||||
if err := createTables(db); err != nil {
|
if err := createTables(db); err != nil {
|
||||||
return fmt.Errorf("failed to create tables: %w", err)
|
return fmt.Errorf("failed to create tables: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate initial data
|
|
||||||
if err := populateData(db); err != nil {
|
if err := populateData(db); err != nil {
|
||||||
return fmt.Errorf("failed to populate data: %w", err)
|
return fmt.Errorf("failed to populate data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create demo user
|
|
||||||
if err := createDemoUser(db); err != nil {
|
if err := createDemoUser(db); err != nil {
|
||||||
return fmt.Errorf("failed to create demo user: %w", err)
|
return fmt.Errorf("failed to create demo user: %w", err)
|
||||||
}
|
}
|
||||||
@ -197,7 +201,7 @@ func populateData(db *database.DB) error {
|
|||||||
if err := db.Exec("INSERT INTO control VALUES (1, 250, 1, '', 'Mage', 'Warrior', 'Paladin')"); err != nil {
|
if err := db.Exec("INSERT INTO control VALUES (1, 250, 1, '', 'Mage', 'Warrior', 'Paladin')"); err != nil {
|
||||||
return fmt.Errorf("failed to populate control table: %w", err)
|
return fmt.Errorf("failed to populate control table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ Control table populated")
|
fmt.Println("✓ control table populated")
|
||||||
|
|
||||||
dropsSQL := `INSERT INTO drops VALUES
|
dropsSQL := `INSERT INTO drops VALUES
|
||||||
(1, 'Life Pebble', 1, 1, 'maxhp,10', ''),
|
(1, 'Life Pebble', 1, 1, 'maxhp,10', ''),
|
||||||
@ -235,7 +239,7 @@ func populateData(db *database.DB) error {
|
|||||||
if err := db.Exec(dropsSQL); err != nil {
|
if err := db.Exec(dropsSQL); err != nil {
|
||||||
return fmt.Errorf("failed to populate drops table: %w", err)
|
return fmt.Errorf("failed to populate drops table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ Drops table populated")
|
fmt.Println("✓ drops table populated")
|
||||||
|
|
||||||
itemsSQL := `INSERT INTO items VALUES
|
itemsSQL := `INSERT INTO items VALUES
|
||||||
(1, 1, 'Stick', 10, 2, ''),
|
(1, 1, 'Stick', 10, 2, ''),
|
||||||
@ -274,7 +278,7 @@ func populateData(db *database.DB) error {
|
|||||||
if err := db.Exec(itemsSQL); err != nil {
|
if err := db.Exec(itemsSQL); err != nil {
|
||||||
return fmt.Errorf("failed to populate items table: %w", err)
|
return fmt.Errorf("failed to populate items table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ Items table populated")
|
fmt.Println("✓ items table populated")
|
||||||
|
|
||||||
monstersSQL := `INSERT INTO monsters VALUES
|
monstersSQL := `INSERT INTO monsters VALUES
|
||||||
(1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0),
|
(1, 'Blue Slime', 4, 3, 1, 1, 1, 1, 0),
|
||||||
@ -431,15 +435,13 @@ func populateData(db *database.DB) error {
|
|||||||
if err := db.Exec(monstersSQL); err != nil {
|
if err := db.Exec(monstersSQL); err != nil {
|
||||||
return fmt.Errorf("failed to populate monsters table: %w", err)
|
return fmt.Errorf("failed to populate monsters table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ Monsters table populated (sample data)")
|
fmt.Println("✓ monsters table populated")
|
||||||
|
|
||||||
// News table
|
|
||||||
if err := db.Exec("INSERT INTO news (author, content) VALUES (1, 'Welcome to Dragon Knight! This is your first news post.')"); err != nil {
|
if err := db.Exec("INSERT INTO news (author, content) VALUES (1, 'Welcome to Dragon Knight! This is your first news post.')"); err != nil {
|
||||||
return fmt.Errorf("failed to populate news table: %w", err)
|
return fmt.Errorf("failed to populate news table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ News table populated")
|
fmt.Println("✓ news table populated")
|
||||||
|
|
||||||
// Spells table
|
|
||||||
spellsSQL := `INSERT INTO spells VALUES
|
spellsSQL := `INSERT INTO spells VALUES
|
||||||
(1, 'Heal', 5, 10, 1),
|
(1, 'Heal', 5, 10, 1),
|
||||||
(2, 'Revive', 10, 25, 1),
|
(2, 'Revive', 10, 25, 1),
|
||||||
@ -463,9 +465,8 @@ func populateData(db *database.DB) error {
|
|||||||
if err := db.Exec(spellsSQL); err != nil {
|
if err := db.Exec(spellsSQL); err != nil {
|
||||||
return fmt.Errorf("failed to populate spells table: %w", err)
|
return fmt.Errorf("failed to populate spells table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ Spells table populated")
|
fmt.Println("✓ spells table populated")
|
||||||
|
|
||||||
// Towns table
|
|
||||||
townsSQL := `INSERT INTO towns VALUES
|
townsSQL := `INSERT INTO towns VALUES
|
||||||
(1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'),
|
(1, 'Midworld', 0, 0, 5, 0, 0, '1,2,3,17,18,19,28,29'),
|
||||||
(2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'),
|
(2, 'Roma', 30, 30, 10, 25, 5, '2,3,4,18,19,29'),
|
||||||
@ -478,20 +479,19 @@ func populateData(db *database.DB) error {
|
|||||||
if err := db.Exec(townsSQL); err != nil {
|
if err := db.Exec(townsSQL); err != nil {
|
||||||
return fmt.Errorf("failed to populate towns table: %w", err)
|
return fmt.Errorf("failed to populate towns table: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("✓ Towns table populated")
|
fmt.Println("✓ towns table populated")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDemoUser(db *database.DB) error {
|
func createDemoUser(db *database.DB) error {
|
||||||
// Hash the password using argon2id
|
|
||||||
hashedPassword, err := password.Hash("Demo123!")
|
hashedPassword, err := password.Hash("Demo123!")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to hash password: %w", err)
|
return fmt.Errorf("failed to hash password: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt := `INSERT INTO users (username, password, email, verified, class_id, auth)
|
stmt := `INSERT INTO users (username, password, email, verified, class_id, auth)
|
||||||
VALUES (?, ?, ?, 1, 1, 1)`
|
VALUES (?, ?, ?, 1, 1, 4)`
|
||||||
|
|
||||||
if err := db.Exec(stmt, "demo", hashedPassword, "demo@demo.com"); err != nil {
|
if err := db.Exec(stmt, "demo", hashedPassword, "demo@demo.com"); err != nil {
|
||||||
return fmt.Errorf("failed to create demo user: %w", err)
|
return fmt.Errorf("failed to create demo user: %w", err)
|
||||||
|
123
internal/items/doc.go
Normal file
123
internal/items/doc.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
Package items is the active record implementation for items in the game.
|
||||||
|
|
||||||
|
# Basic Usage
|
||||||
|
|
||||||
|
To retrieve an item by ID:
|
||||||
|
|
||||||
|
item, err := items.Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Found item: %s (value: %d)\n", item.Name, item.Value)
|
||||||
|
|
||||||
|
To get all items:
|
||||||
|
|
||||||
|
allItems, err := items.All(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, item := range allItems {
|
||||||
|
fmt.Printf("Item: %s\n", item.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
To filter items by type:
|
||||||
|
|
||||||
|
weapons, err := items.ByType(db, items.TypeWeapon)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Creating Items with Builder Pattern
|
||||||
|
|
||||||
|
The package provides a fluent builder interface for creating new items:
|
||||||
|
|
||||||
|
item, err := items.NewBuilder(db).
|
||||||
|
WithType(items.TypeWeapon).
|
||||||
|
WithName("Excalibur").
|
||||||
|
WithValue(5000).
|
||||||
|
WithAtt(100).
|
||||||
|
WithSpecial("strength,25").
|
||||||
|
Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Created item with ID: %d\n", item.ID)
|
||||||
|
|
||||||
|
# Updating Items
|
||||||
|
|
||||||
|
Items can be modified and saved back to the database:
|
||||||
|
|
||||||
|
item, _ := items.Find(db, 1)
|
||||||
|
item.Name = "Enhanced Sword"
|
||||||
|
item.Value += 100
|
||||||
|
|
||||||
|
err := item.Save()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deleting Items
|
||||||
|
|
||||||
|
Items can be removed from the database:
|
||||||
|
|
||||||
|
item, _ := items.Find(db, 1)
|
||||||
|
err := item.Delete()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Item Types
|
||||||
|
|
||||||
|
The package defines three item types as constants:
|
||||||
|
|
||||||
|
items.TypeWeapon = 1 // Swords, axes, etc.
|
||||||
|
items.TypeArmor = 2 // Protective gear
|
||||||
|
items.TypeShield = 3 // Shields and bucklers
|
||||||
|
|
||||||
|
Helper methods are available to check item types:
|
||||||
|
|
||||||
|
if item.IsWeapon() {
|
||||||
|
fmt.Println("This is a weapon")
|
||||||
|
}
|
||||||
|
fmt.Printf("Item type: %s\n", item.TypeName())
|
||||||
|
|
||||||
|
# Database Schema
|
||||||
|
|
||||||
|
The items table has the following structure:
|
||||||
|
|
||||||
|
CREATE TABLE items (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
att INTEGER NOT NULL DEFAULT 0,
|
||||||
|
special TEXT NOT NULL DEFAULT ''
|
||||||
|
)
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- id: Unique identifier
|
||||||
|
- type: Item type (1=weapon, 2=armor, 3=shield)
|
||||||
|
- name: Display name of the item
|
||||||
|
- value: Gold value/cost
|
||||||
|
- att: Attack or defense attribute bonus
|
||||||
|
- special: Special attributes in "key,value" format
|
||||||
|
|
||||||
|
# Special Attributes
|
||||||
|
|
||||||
|
The special field contains comma-separated key-value pairs for item bonuses:
|
||||||
|
|
||||||
|
"strength,10" // +10 strength
|
||||||
|
"maxhp,25" // +25 max health
|
||||||
|
"expbonus,5" // +5% experience bonus
|
||||||
|
"maxhp,50,strength,25" // Multiple bonuses
|
||||||
|
|
||||||
|
# Error Handling
|
||||||
|
|
||||||
|
All functions return appropriate errors for common failure cases:
|
||||||
|
- Item not found (Find returns error for non-existent IDs)
|
||||||
|
- Database connection issues
|
||||||
|
- Invalid operations (e.g., saving/deleting items without IDs)
|
||||||
|
*/
|
||||||
|
package items
|
241
internal/items/items.go
Normal file
241
internal/items/items.go
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
package items
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"dk/internal/database"
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Item represents an item in the database
|
||||||
|
type Item struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value int `json:"value"`
|
||||||
|
Att int `json:"att"`
|
||||||
|
Special string `json:"special"`
|
||||||
|
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// ItemType constants for item types
|
||||||
|
const (
|
||||||
|
TypeWeapon = 1
|
||||||
|
TypeArmor = 2
|
||||||
|
TypeShield = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find retrieves an item by ID
|
||||||
|
func Find(db *database.DB, id int) (*Item, error) {
|
||||||
|
item := &Item{db: db}
|
||||||
|
|
||||||
|
query := "SELECT id, type, name, value, att, special FROM items WHERE id = ?"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
item.ID = stmt.ColumnInt(0)
|
||||||
|
item.Type = stmt.ColumnInt(1)
|
||||||
|
item.Name = stmt.ColumnText(2)
|
||||||
|
item.Value = stmt.ColumnInt(3)
|
||||||
|
item.Att = stmt.ColumnInt(4)
|
||||||
|
item.Special = stmt.ColumnText(5)
|
||||||
|
return nil
|
||||||
|
}, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find item: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ID == 0 {
|
||||||
|
return nil, fmt.Errorf("item with ID %d not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// All retrieves all items
|
||||||
|
func All(db *database.DB) ([]*Item, error) {
|
||||||
|
var items []*Item
|
||||||
|
|
||||||
|
query := "SELECT id, type, name, value, att, special FROM items ORDER BY id"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
item := &Item{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Type: stmt.ColumnInt(1),
|
||||||
|
Name: stmt.ColumnText(2),
|
||||||
|
Value: stmt.ColumnInt(3),
|
||||||
|
Att: stmt.ColumnInt(4),
|
||||||
|
Special: stmt.ColumnText(5),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve all items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByType retrieves items by type
|
||||||
|
func ByType(db *database.DB, itemType int) ([]*Item, error) {
|
||||||
|
var items []*Item
|
||||||
|
|
||||||
|
query := "SELECT id, type, name, value, att, special FROM items WHERE type = ? ORDER BY id"
|
||||||
|
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||||
|
item := &Item{
|
||||||
|
ID: stmt.ColumnInt(0),
|
||||||
|
Type: stmt.ColumnInt(1),
|
||||||
|
Name: stmt.ColumnText(2),
|
||||||
|
Value: stmt.ColumnInt(3),
|
||||||
|
Att: stmt.ColumnInt(4),
|
||||||
|
Special: stmt.ColumnText(5),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
return nil
|
||||||
|
}, itemType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve items by type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder provides a fluent interface for creating items
|
||||||
|
type Builder struct {
|
||||||
|
item *Item
|
||||||
|
db *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new item builder
|
||||||
|
func NewBuilder(db *database.DB) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
item: &Item{db: db},
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithType sets the item type
|
||||||
|
func (b *Builder) WithType(itemType int) *Builder {
|
||||||
|
b.item.Type = itemType
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName sets the item name
|
||||||
|
func (b *Builder) WithName(name string) *Builder {
|
||||||
|
b.item.Name = name
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue sets the item value
|
||||||
|
func (b *Builder) WithValue(value int) *Builder {
|
||||||
|
b.item.Value = value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAtt sets the item attack/defense value
|
||||||
|
func (b *Builder) WithAtt(att int) *Builder {
|
||||||
|
b.item.Att = att
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSpecial sets the item special attributes
|
||||||
|
func (b *Builder) WithSpecial(special string) *Builder {
|
||||||
|
b.item.Special = special
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create saves the item to the database and returns it
|
||||||
|
func (b *Builder) Create() (*Item, error) {
|
||||||
|
// Use a transaction to ensure we can get the ID
|
||||||
|
var item *Item
|
||||||
|
err := b.db.Transaction(func(tx *database.Tx) error {
|
||||||
|
query := `INSERT INTO items (type, name, value, att, special)
|
||||||
|
VALUES (?, ?, ?, ?, ?)`
|
||||||
|
|
||||||
|
if err := tx.Exec(query, b.item.Type, b.item.Name, b.item.Value, b.item.Att, b.item.Special); err != nil {
|
||||||
|
return fmt.Errorf("failed to insert item: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last inserted ID within the same transaction
|
||||||
|
var lastID int
|
||||||
|
err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error {
|
||||||
|
lastID = stmt.ColumnInt(0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get last insert ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the item with the ID
|
||||||
|
item = &Item{
|
||||||
|
ID: lastID,
|
||||||
|
Type: b.item.Type,
|
||||||
|
Name: b.item.Name,
|
||||||
|
Value: b.item.Value,
|
||||||
|
Att: b.item.Att,
|
||||||
|
Special: b.item.Special,
|
||||||
|
db: b.db,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create item: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save updates an existing item in the database
|
||||||
|
func (i *Item) Save() error {
|
||||||
|
if i.ID == 0 {
|
||||||
|
return fmt.Errorf("cannot save item without ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `UPDATE items SET type = ?, name = ?, value = ?, att = ?, special = ? WHERE id = ?`
|
||||||
|
return i.db.Exec(query, i.Type, i.Name, i.Value, i.Att, i.Special, i.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the item from the database
|
||||||
|
func (i *Item) Delete() error {
|
||||||
|
if i.ID == 0 {
|
||||||
|
return fmt.Errorf("cannot delete item without ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "DELETE FROM items WHERE id = ?"
|
||||||
|
return i.db.Exec(query, i.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWeapon returns true if the item is a weapon
|
||||||
|
func (i *Item) IsWeapon() bool {
|
||||||
|
return i.Type == TypeWeapon
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsArmor returns true if the item is armor
|
||||||
|
func (i *Item) IsArmor() bool {
|
||||||
|
return i.Type == TypeArmor
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsShield returns true if the item is a shield
|
||||||
|
func (i *Item) IsShield() bool {
|
||||||
|
return i.Type == TypeShield
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeName returns the string representation of the item type
|
||||||
|
func (i *Item) TypeName() string {
|
||||||
|
switch i.Type {
|
||||||
|
case TypeWeapon:
|
||||||
|
return "Weapon"
|
||||||
|
case TypeArmor:
|
||||||
|
return "Armor"
|
||||||
|
case TypeShield:
|
||||||
|
return "Shield"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
272
internal/items/items_test.go
Normal file
272
internal/items/items_test.go
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
package items
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dk/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestDB(t *testing.T) *database.DB {
|
||||||
|
testDB := "test_items.db"
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove(testDB)
|
||||||
|
})
|
||||||
|
|
||||||
|
db, err := database.Open(testDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to open test database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createTable := `CREATE TABLE items (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
att INTEGER NOT NULL DEFAULT 0,
|
||||||
|
special TEXT NOT NULL DEFAULT ''
|
||||||
|
)`
|
||||||
|
|
||||||
|
if err := db.Exec(createTable); err != nil {
|
||||||
|
t.Fatalf("Failed to create items table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testItems := `INSERT INTO items (type, name, value, att, special) VALUES
|
||||||
|
(1, 'Test Sword', 100, 10, 'strength,5'),
|
||||||
|
(2, 'Test Armor', 200, 15, 'maxhp,25'),
|
||||||
|
(3, 'Test Shield', 150, 8, '')`
|
||||||
|
|
||||||
|
if err := db.Exec(testItems); err != nil {
|
||||||
|
t.Fatalf("Failed to insert test items: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFind(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Test finding existing item
|
||||||
|
item, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ID != 1 {
|
||||||
|
t.Errorf("Expected ID 1, got %d", item.ID)
|
||||||
|
}
|
||||||
|
if item.Name != "Test Sword" {
|
||||||
|
t.Errorf("Expected name 'Test Sword', got '%s'", item.Name)
|
||||||
|
}
|
||||||
|
if item.Type != TypeWeapon {
|
||||||
|
t.Errorf("Expected type %d, got %d", TypeWeapon, item.Type)
|
||||||
|
}
|
||||||
|
if item.Value != 100 {
|
||||||
|
t.Errorf("Expected value 100, got %d", item.Value)
|
||||||
|
}
|
||||||
|
if item.Att != 10 {
|
||||||
|
t.Errorf("Expected att 10, got %d", item.Att)
|
||||||
|
}
|
||||||
|
if item.Special != "strength,5" {
|
||||||
|
t.Errorf("Expected special 'strength,5', got '%s'", item.Special)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test finding non-existent item
|
||||||
|
_, err = Find(db, 999)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when finding non-existent item")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
items, err := All(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get all items: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) != 3 {
|
||||||
|
t.Errorf("Expected 3 items, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check first item
|
||||||
|
if items[0].Name != "Test Sword" {
|
||||||
|
t.Errorf("Expected first item to be 'Test Sword', got '%s'", items[0].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestByType(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
weapons, err := ByType(db, TypeWeapon)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get weapons: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(weapons) != 1 {
|
||||||
|
t.Errorf("Expected 1 weapon, got %d", len(weapons))
|
||||||
|
}
|
||||||
|
|
||||||
|
if weapons[0].Name != "Test Sword" {
|
||||||
|
t.Errorf("Expected weapon to be 'Test Sword', got '%s'", weapons[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
armor, err := ByType(db, TypeArmor)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get armor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(armor) != 1 {
|
||||||
|
t.Errorf("Expected 1 armor, got %d", len(armor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Create new item using builder
|
||||||
|
item, err := NewBuilder(db).
|
||||||
|
WithType(TypeWeapon).
|
||||||
|
WithName("Builder Sword").
|
||||||
|
WithValue(500).
|
||||||
|
WithAtt(25).
|
||||||
|
WithSpecial("dexterity,10").
|
||||||
|
Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create item with builder: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ID == 0 {
|
||||||
|
t.Error("Expected non-zero ID after creation")
|
||||||
|
}
|
||||||
|
if item.Name != "Builder Sword" {
|
||||||
|
t.Errorf("Expected name 'Builder Sword', got '%s'", item.Name)
|
||||||
|
}
|
||||||
|
if item.Type != TypeWeapon {
|
||||||
|
t.Errorf("Expected type %d, got %d", TypeWeapon, item.Type)
|
||||||
|
}
|
||||||
|
if item.Value != 500 {
|
||||||
|
t.Errorf("Expected value 500, got %d", item.Value)
|
||||||
|
}
|
||||||
|
if item.Att != 25 {
|
||||||
|
t.Errorf("Expected att 25, got %d", item.Att)
|
||||||
|
}
|
||||||
|
if item.Special != "dexterity,10" {
|
||||||
|
t.Errorf("Expected special 'dexterity,10', got '%s'", item.Special)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it was saved to database
|
||||||
|
foundItem, err := Find(db, item.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find created item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundItem.Name != "Builder Sword" {
|
||||||
|
t.Errorf("Created item not found in database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSave(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
item, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify item
|
||||||
|
item.Name = "Updated Sword"
|
||||||
|
item.Value = 150
|
||||||
|
|
||||||
|
// Save changes
|
||||||
|
err = item.Save()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify changes were saved
|
||||||
|
updatedItem, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find updated item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedItem.Name != "Updated Sword" {
|
||||||
|
t.Errorf("Expected updated name 'Updated Sword', got '%s'", updatedItem.Name)
|
||||||
|
}
|
||||||
|
if updatedItem.Value != 150 {
|
||||||
|
t.Errorf("Expected updated value 150, got %d", updatedItem.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
item, err := Find(db, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to find item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete item
|
||||||
|
err = item.Delete()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to delete item: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify item was deleted
|
||||||
|
_, err = Find(db, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when finding deleted item")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestItemTypeMethods(t *testing.T) {
|
||||||
|
db := setupTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sword, _ := Find(db, 1)
|
||||||
|
armor, _ := Find(db, 2)
|
||||||
|
shield, _ := Find(db, 3)
|
||||||
|
|
||||||
|
// Test IsWeapon
|
||||||
|
if !sword.IsWeapon() {
|
||||||
|
t.Error("Expected sword to be weapon")
|
||||||
|
}
|
||||||
|
if armor.IsWeapon() {
|
||||||
|
t.Error("Expected armor not to be weapon")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test IsArmor
|
||||||
|
if !armor.IsArmor() {
|
||||||
|
t.Error("Expected armor to be armor")
|
||||||
|
}
|
||||||
|
if sword.IsArmor() {
|
||||||
|
t.Error("Expected sword not to be armor")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test IsShield
|
||||||
|
if !shield.IsShield() {
|
||||||
|
t.Error("Expected shield to be shield")
|
||||||
|
}
|
||||||
|
if sword.IsShield() {
|
||||||
|
t.Error("Expected sword not to be shield")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test TypeName
|
||||||
|
if sword.TypeName() != "Weapon" {
|
||||||
|
t.Errorf("Expected sword type name 'Weapon', got '%s'", sword.TypeName())
|
||||||
|
}
|
||||||
|
if armor.TypeName() != "Armor" {
|
||||||
|
t.Errorf("Expected armor type name 'Armor', got '%s'", armor.TypeName())
|
||||||
|
}
|
||||||
|
if shield.TypeName() != "Shield" {
|
||||||
|
t.Errorf("Expected shield type name 'Shield', got '%s'", shield.TypeName())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user