mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
N4/E1: T4 Estate vault — 10-slot protected item storage
The Tier-4 "Established" home now unlocks a vault: `!adventure vault [store|take] <item>` moves items in and out of a 10-slot pool that shelters them from `!sell all`, crafting, and combat consumption. It is also E2 gifting's prerequisite plumbing. Implemented as a single `in_vault` flag on adventure_inventory rather than a parallel table: a stowed item keeps its identity (id, temper, everything) and loadAdvInventory filters it out, so a vaulted item drops out of every play surface with one flag change and comes back untouched. DEFAULT 0 = "in play", correct for every pre-existing row, so no bootstrap backfill. Golden byte-identical; go test ./... green. Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
This commit is contained in:
138
internal/plugin/adventure_vault_test.go
Normal file
138
internal/plugin/adventure_vault_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gogobee/internal/db"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
func vaultTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
db.Close()
|
||||
if err := db.Init(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(db.Close)
|
||||
}
|
||||
|
||||
func seedVaultItem(t *testing.T, user id.UserID, name string, value int64) {
|
||||
t.Helper()
|
||||
if err := addAdvInventoryItem(user, AdvItem{Name: name, Type: "treasure", Tier: 3, Value: value}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVaultStoreTakeRoundTrip: an item vaulted leaves the active inventory and
|
||||
// joins the vault; taken back, it reverses exactly.
|
||||
func TestVaultStoreTakeRoundTrip(t *testing.T) {
|
||||
vaultTestDB(t)
|
||||
user := id.UserID("@vault:test.invalid")
|
||||
seedVaultItem(t, user, "Jeweled Crown", 5000)
|
||||
|
||||
if got := vaultStoreItem(user, "crown"); !strings.Contains(got, "Stowed") {
|
||||
t.Fatalf("store reply = %q, want a stow confirmation", got)
|
||||
}
|
||||
|
||||
inv, _ := loadAdvInventory(user)
|
||||
if len(inv) != 0 {
|
||||
t.Fatalf("active inventory = %d items, want 0 (item is vaulted)", len(inv))
|
||||
}
|
||||
vault, _ := loadAdvVault(user)
|
||||
if len(vault) != 1 || vault[0].Name != "Jeweled Crown" {
|
||||
t.Fatalf("vault = %+v, want the crown", vault)
|
||||
}
|
||||
|
||||
if got := vaultTakeItem(user, "crown"); !strings.Contains(got, "back into your pack") {
|
||||
t.Fatalf("take reply = %q, want a retrieval confirmation", got)
|
||||
}
|
||||
inv, _ = loadAdvInventory(user)
|
||||
if len(inv) != 1 {
|
||||
t.Fatalf("active inventory = %d items, want 1 after take", len(inv))
|
||||
}
|
||||
if n := advVaultCount(user); n != 0 {
|
||||
t.Fatalf("vault count = %d, want 0", n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVaultCapacity: the 10th item stores, the 11th is refused, and the refusal
|
||||
// leaves the item in the active inventory.
|
||||
func TestVaultCapacity(t *testing.T) {
|
||||
vaultTestDB(t)
|
||||
user := id.UserID("@vaultcap:test.invalid")
|
||||
for i := 0; i < vaultCapacity; i++ {
|
||||
seedVaultItem(t, user, "Gem", 100)
|
||||
if got := vaultStoreItem(user, "Gem"); !strings.Contains(got, "Stowed") {
|
||||
t.Fatalf("store %d reply = %q, want success", i, got)
|
||||
}
|
||||
}
|
||||
if n := advVaultCount(user); n != vaultCapacity {
|
||||
t.Fatalf("vault count = %d, want %d", n, vaultCapacity)
|
||||
}
|
||||
|
||||
seedVaultItem(t, user, "Overflow Gem", 100)
|
||||
got := vaultStoreItem(user, "Overflow Gem")
|
||||
if !strings.Contains(got, "full") {
|
||||
t.Fatalf("11th store reply = %q, want a full-vault refusal", got)
|
||||
}
|
||||
// The refused item is still in play.
|
||||
inv, _ := loadAdvInventory(user)
|
||||
if len(inv) != 1 || inv[0].Name != "Overflow Gem" {
|
||||
t.Fatalf("active inventory = %+v, want the overflow gem left in play", inv)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVaultShieldsFromSellAll: `!sell all` liquidates loadAdvInventory, which a
|
||||
// vaulted item is no longer part of — the whole point of the vault.
|
||||
func TestVaultShieldsFromSellAll(t *testing.T) {
|
||||
vaultTestDB(t)
|
||||
user := id.UserID("@vaultsell:test.invalid")
|
||||
seedVaultItem(t, user, "Heirloom", 9000)
|
||||
seedVaultItem(t, user, "Junk", 5)
|
||||
|
||||
if got := vaultStoreItem(user, "Heirloom"); !strings.Contains(got, "Stowed") {
|
||||
t.Fatalf("store reply = %q", got)
|
||||
}
|
||||
// What sell-all would consume:
|
||||
sellable, _ := loadAdvInventory(user)
|
||||
if len(sellable) != 1 || sellable[0].Name != "Junk" {
|
||||
t.Fatalf("sellable inventory = %+v, want only Junk (heirloom sheltered)", sellable)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVaultGate: below Tier 4 the vault is locked; at Tier 4 it opens.
|
||||
func TestVaultGate(t *testing.T) {
|
||||
locked := &AdventureCharacter{HouseTier: 3}
|
||||
if ok, msg := vaultUnlocked(locked); ok || msg == "" {
|
||||
t.Fatalf("tier 3: ok=%v msg=%q, want locked with a refusal", ok, msg)
|
||||
}
|
||||
open := &AdventureCharacter{HouseTier: 4}
|
||||
if ok, msg := vaultUnlocked(open); !ok || msg != "" {
|
||||
t.Fatalf("tier 4: ok=%v msg=%q, want unlocked", ok, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetItemVaultedIsOwnerScoped: another user's id cannot be flipped.
|
||||
func TestSetItemVaultedIsOwnerScoped(t *testing.T) {
|
||||
vaultTestDB(t)
|
||||
owner := id.UserID("@owner:test.invalid")
|
||||
intruder := id.UserID("@intruder:test.invalid")
|
||||
seedVaultItem(t, owner, "Relic", 1000)
|
||||
items, _ := loadAdvInventory(owner)
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("setup: owner inventory = %d, want 1", len(items))
|
||||
}
|
||||
moved, err := setItemVaulted(intruder, items[0].ID, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if moved {
|
||||
t.Fatalf("intruder moved the owner's item; want no-op")
|
||||
}
|
||||
if advVaultCount(owner) != 0 {
|
||||
t.Fatalf("owner's item was vaulted by an intruder")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user