DNF90 服务端与启动器完整源码(含 GM 工具功能增强)

服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
This commit is contained in:
hanfuit committed 2026-11-04 14:31:55 +08:00
commit 645b64fe21
4388 files changed
+1405457

No files matched your search

@@ -0,0 +1,461 @@
package dnfbridge
import (
"context"
"encoding/binary"
"errors"
"fmt"
"os"
"strconv"
"testing"
"longheng.io/server/internal/modules/dnf/dnfenum"
dnfpvf "longheng.io/server/internal/modules/dnf/pvf"
dnfrepo "longheng.io/server/internal/modules/dnf/repository"
dnfrepomemory "longheng.io/server/internal/modules/dnf/repository/memory"
platformpvf "longheng.io/server/internal/platform/pvf"
)
func TestCurrentCargoTransportRequestIsExactNineByteWire(t *testing.T) {
body := make([]byte, 9)
binary.LittleEndian.PutUint16(body[0:2], 71)
binary.LittleEndian.PutUint16(body[2:4], 29)
body[4] = 2
binary.LittleEndian.PutUint32(body[5:9], 4)
request, err := decodeCurrentCargoTransportRequest(body)
if err != nil {
t.Fatal(err)
}
if request.StoneSlot != 71 || request.TargetSlot != 29 || request.Mode != 2 || request.RecipientSelect != 4 {
t.Fatalf("request=%+v", request)
}
for _, invalid := range [][]byte{body[:8], append(append([]byte(nil), body...), 0)} {
if _, err := decodeCurrentCargoTransportRequest(invalid); err == nil {
t.Fatalf("accepted length=%d", len(invalid))
}
}
}
func TestCurrentTransportStoneDefinitionAcceptsDirectAndActionTypeLayouts(t *testing.T) {
tests := []struct {
name string
text string
cargo bool
cargoType int64
creature bool
creatureType int64
}{
{name: "current cargo action", text: "[action type]\n`[cargo transport stone]` 8\n[/action type]\n", cargo: true, cargoType: 8},
{name: "current creature action", text: "[action type]\n`[creature transport stone]` 2\n[/action type]\n", creature: true, creatureType: 2},
{name: "legacy cargo section", text: "[cargo transport stone]\n5\n", cargo: true, cargoType: 5},
{name: "unrelated action fails closed", text: "[action type]\n`[cargo something else]` 8\n[/action type]\n"},
{name: "missing action integer fails closed", text: "[action type]\n`[cargo transport stone]`\n[/action type]\n"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
document, err := dnfpvf.Parse("stackable/test.stk", test.text)
if err != nil {
t.Fatal(err)
}
definition := dungeonDropItemDefinition{}
parseCurrentTransportStoneDefinition(document, &definition)
if definition.CargoTransportStone != test.cargo || definition.CargoTransportStoneType != test.cargoType ||
definition.CreatureTransportStone != test.creature || definition.CreatureTransportStoneType != test.creatureType {
t.Fatalf("definition=%+v", definition)
}
})
}
}
func TestCurrentCargoTransportDirectStoneMovesEquipmentToAccountCargo(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
mustSaveCargoTransportAccount(t, ctx, repositories, "account-1", 8)
if err := repositories.AccountInventory.Save(ctx, dnfrepo.AccountInventoryRecord{AccountID: "account-1", Slots: map[string]dnfrepo.ItemStack{}}); err != nil {
t.Fatal(err)
}
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "1", Slots: map[string]dnfrepo.ItemStack{
"0:70": {ItemID: 9001, Count: 2},
"0:12": {ItemID: 1001, Count: 1},
}}); err != nil {
t.Fatal(err)
}
catalog := &pvfDungeonDropCatalog{source: finishBridgePVFSource{}, itemCache: map[uint32]dungeonDropItemDefinition{
9001: {ItemID: 9001, Kind: dungeonDropItemStackable, CargoTransportStone: true, CargoTransportStoneType: 3},
1001: {ItemID: 1001, Kind: dungeonDropItemEquipment, EquipmentType: "[weapon]", MinimumLevel: 85, Rarity: 4},
}}
config := currentCargoTransportConfig{Rules: map[int64]currentCargoTransportRule{
3: {StoneType: 3, GradeWeights: []currentPVFRangeWeight{{Minimum: 1, Maximum: 90, Weight: 1}}, RarityWeights: map[int64]int64{4: 1}, EquipmentTypes: map[string]bool{"weapon": true}, ExcludeItemIDs: map[uint32]bool{}, IncludeItemIDs: map[uint32]bool{}},
}}
service := &Service{}
if err := service.commitCurrentStoneToAccountCargo(ctx, repositories, catalog, config, "account-1", "1", currentCargoTransportRequest{StoneSlot: 70, TargetSlot: 12}); err != nil {
t.Fatal(err)
}
inventory, _, _ := repositories.Inventory.Load(ctx, "1")
if inventory.Slots["0:70"].Count != 1 {
t.Fatalf("stone=%+v", inventory.Slots["0:70"])
}
if _, found := inventory.Slots["0:12"]; found {
t.Fatal("target remained in character inventory")
}
cargo, found, err := repositories.AccountInventory.Load(ctx, "account-1")
if err != nil || !found || cargo.Slots["12:0"].ItemID != 1001 {
t.Fatalf("cargo found=%t err=%v slots=%+v", found, err, cargo.Slots)
}
}
func TestCurrentGrandiTransportConsumesExactPVFCostsAtomically(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
for _, row := range []dnfrepo.CharacterRecord{
{CharacterID: "1", AccountID: "account-1", Slot: 0},
{CharacterID: "2", AccountID: "account-1", Slot: 4},
} {
if err := repositories.Character.Save(ctx, row); err != nil {
t.Fatal(err)
}
}
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "1", Slots: map[string]dnfrepo.ItemStack{
"0:12": {ItemID: 100240061, Count: 1},
"0:70": {ItemID: 3167, Count: 500},
}}); err != nil {
t.Fatal(err)
}
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "2", Slots: map[string]dnfrepo.ItemStack{}}); err != nil {
t.Fatal(err)
}
if err := repositories.AccountInventory.Save(ctx, dnfrepo.AccountInventoryRecord{AccountID: "account-1", Slots: map[string]dnfrepo.ItemStack{
"0:364": {ItemID: 10158124, Count: 200},
}}); err != nil {
t.Fatal(err)
}
if err := repositories.EpicBook.Save(ctx, dnfrepo.EpicBookRecord{AccountID: "account-1", Balances: map[uint32]uint32{100240061: 200}}); err != nil {
t.Fatal(err)
}
catalog := &pvfDungeonDropCatalog{source: finishBridgePVFSource{}, itemCache: map[uint32]dungeonDropItemDefinition{
100240061: {ItemID: 100240061, Kind: dungeonDropItemEquipment, EquipmentType: "[coat]", MinimumLevel: 85, Rarity: 4},
}}
rule := currentCargoTransportRule{
StoneType: 7, GradeWeights: []currentPVFRangeWeight{{Minimum: 85, Maximum: 85, Weight: 5}},
RarityWeights: map[int64]int64{4: 9}, EquipmentTypes: map[string]bool{"coat": true}, IncludeItemIDs: map[uint32]bool{}, ExcludeItemIDs: map[uint32]bool{},
SoulItemIDs: map[int64]uint32{4: 10158124}, SoulWeights: map[int64][]int64{4: {3, 3, 3, 3, 3}},
ElementItemID: 3167, ElementWeights: map[int64][]int64{4: {10, 10, 10, 10}}, EpicPieceWeight: []int64{3, 3, 3, 3, 3},
}
service := &Service{}
// RecipientSelect is the roster index, not the slot: character "2" sits
// in slot 4 but is the second row of the account roster, so the client
// sends 1. This is exactly the gap the slot reading used to get wrong.
if err := service.commitCurrentGrandiEquipmentTransport(ctx, repositories, catalog, currentCargoTransportConfig{Rules: map[int64]currentCargoTransportRule{7: rule}}, "account-1", "1", currentCargoTransportRequest{TargetSlot: 12, Mode: 2, RecipientSelect: 1}); err != nil {
t.Fatal(err)
}
source, _, _ := repositories.Inventory.Load(ctx, "1")
destination, _, _ := repositories.Inventory.Load(ctx, "2")
account, _, _ := repositories.AccountInventory.Load(ctx, "account-1")
book, _, _ := repositories.EpicBook.Load(ctx, "account-1")
if _, found := source.Slots["0:12"]; found {
t.Fatalf("transferred equipment stayed in the sender bag: %+v", source.Slots)
}
// The recipient must be reached by mail, never straight into the bag.
if len(destination.Slots) != 0 {
t.Fatalf("equipment went into the recipient backpack: %+v", destination.Slots)
}
mailbox, found, err := repositories.Mailbox.Load(ctx, "2")
if err != nil || !found || len(mailbox.Mails) != 1 {
t.Fatalf("recipient mailbox found=%t err=%v mails=%d", found, err, len(mailbox.Mails))
}
for _, mail := range mailbox.Mails {
if len(mail.Attachments) != 1 || mail.Attachments[0].ItemID != 100240061 || mail.Attachments[0].Count != 1 {
t.Fatalf("mail attachments=%+v", mail.Attachments)
}
if mail.Attachments[0].Extra["source"] != "grandi_equipment_transport" {
t.Fatalf("mail attachment extra=%+v", mail.Attachments[0].Extra)
}
}
if source.Slots["0:70"].Count != 50 || account.Slots["0:364"].Count != 65 || book.Balances[100240061] != 65 {
t.Fatalf("element=%d soul=%d fragments=%d", source.Slots["0:70"].Count, account.Slots["0:364"].Count, book.Balances[100240061])
}
}
// A rejection must reach the client as one of the seven codes the op557
// handler sub_1E785B0 recognises. An unlisted code leaves its dstr register
// zeroed and the client pops dstr 0 -- in this build the unrelated
// "已超出机器人放置的个数上限".
func TestCurrentCargoTransportFailureCodesAreClientRecognised(t *testing.T) {
// code -> the dstr text docs/client-dstr-zh.txt gives for it. Every code
// this server can send must be in here; anything else renders dstr 0.
recognised := map[byte]string{
0x04: "帐号金库空间不足",
0x07: "无法转移该道具",
0x11: "该道具不存在",
0x13: "帐号金库未激活",
0x15: "无法使用。",
0x16: "所需材料不足",
0xD6: "在物品已锁定状态下",
}
for _, code := range []byte{
currentCargoTransportErrorNotTransferable,
currentCargoTransportErrorItemMissing,
currentCargoTransportErrorCargoInactive,
currentCargoTransportErrorCargoFull,
currentCargoTransportErrorMaterial,
} {
if _, ok := recognised[code]; !ok {
t.Fatalf("code %#x is not in the current-EXE op557 table", code)
}
}
// The material message is the one the player sees most, so pin the code
// that carries it: 0x16, not ServerS4A21's "source missing" reading.
if currentCargoTransportErrorMaterial != 0x16 {
t.Fatalf("material code=%#x want 0x16 (所需材料不足)", currentCargoTransportErrorMaterial)
}
for _, testCase := range []struct {
name string
err error
want byte
}{
{name: "target missing", err: currentCargoTransportReject(currentCargoTransportErrorItemMissing, "x"), want: currentCargoTransportErrorItemMissing},
{name: "material insufficient", err: currentCargoTransportReject(currentCargoTransportErrorMaterial, "x"), want: currentCargoTransportErrorMaterial},
{name: "wrapped", err: fmt.Errorf("commit: %w", currentCargoTransportReject(currentCargoTransportErrorCargoFull, "x")), want: currentCargoTransportErrorCargoFull},
{name: "uncoded repository fault", err: errors.New("boom"), want: currentCargoTransportErrorNotTransferable},
} {
t.Run(testCase.name, func(t *testing.T) {
if got := currentCargoTransportFailureCode(testCase.err); got != testCase.want {
t.Fatalf("code=%#x want %#x", got, testCase.want)
}
})
}
}
// The success payload is the four u16 plus mode byte sub_1E785B0 reads at
// 0x1E78621. Mode 2 makes it close the 装备跨界 window (UI 0x435) and return
// before touching any item row, which is why the count and parameter are 0.
func TestCurrentCargoTransportSuccessBodyIsFourWordsAndAModeByte(t *testing.T) {
body := buildCurrentCargoTransportSuccessBody(0, 20, 0, 0, currentCargoTransportGrandiMode)
if len(body) != 9 {
t.Fatalf("body=% X", body)
}
if binary.LittleEndian.Uint16(body[0:2]) != 0 ||
binary.LittleEndian.Uint16(body[2:4]) != 20 ||
binary.LittleEndian.Uint16(body[4:6]) != 0 ||
binary.LittleEndian.Uint16(body[6:8]) != 0 ||
body[8] != 2 {
t.Fatalf("body=% X", body)
}
if got := upperSuccessBody(body); got[0] != 1 || len(got) != 10 {
t.Fatalf("upper body=% X", got)
}
}
// The dropdown value is a roster position. The recipient must be picked by
// that position over the roster ordered by slot then character id, and the
// sender's own row must never resolve.
func TestCurrentCargoRecipientIsResolvedByRosterIndexNotSlot(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
for _, row := range []dnfrepo.CharacterRecord{
{CharacterID: "1", AccountID: "account-1", Slot: 0},
{CharacterID: "9", AccountID: "account-1", Slot: 7},
{CharacterID: "5", AccountID: "account-1", Slot: 13},
} {
if err := repositories.Character.Save(ctx, row); err != nil {
t.Fatal(err)
}
}
service := &Service{}
for _, testCase := range []struct {
selection uint32
want string
}{{selection: 1, want: "9"}, {selection: 2, want: "5"}} {
picked, err := service.currentCargoRecipientBySelection(ctx, repositories.Character, "account-1", "1", testCase.selection)
if err != nil || picked.CharacterID != testCase.want {
t.Fatalf("selection=%d picked=%q err=%v want %q", testCase.selection, picked.CharacterID, err, testCase.want)
}
}
if _, err := service.currentCargoRecipientBySelection(ctx, repositories.Character, "account-1", "1", 0); err == nil {
t.Fatal("the sender's own roster position must not resolve as a recipient")
}
if _, err := service.currentCargoRecipientBySelection(ctx, repositories.Character, "account-1", "1", 7); err == nil {
t.Fatal("a slot number past the roster length must not resolve")
}
}
func TestCurrentPetTransportMovesDurableCreatureState(t *testing.T) {
ctx := context.Background()
repositories := dnfrepomemory.NewMemoryGroup()
for _, row := range []dnfrepo.CharacterRecord{{CharacterID: "1", AccountID: "account-1", Slot: 0}, {CharacterID: "2", AccountID: "account-1", Slot: 1}} {
if err := repositories.Character.Save(ctx, row); err != nil {
t.Fatal(err)
}
}
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "1", Slots: map[string]dnfrepo.ItemStack{
"0:70": {ItemID: 8001, Count: 1}, "7:4": {ItemID: 63000, Count: 1, Extra: map[string]string{"creature_key": "77"}},
}}); err != nil {
t.Fatal(err)
}
if err := repositories.Inventory.Save(ctx, dnfrepo.InventoryRecord{CharacterID: "2", Slots: map[string]dnfrepo.ItemStack{}}); err != nil {
t.Fatal(err)
}
if err := repositories.Pet.Save(ctx, dnfrepo.PetRecord{CharacterID: "1", Entries: map[string]dnfrepo.PetEntry{
"77": {PetKey: "77", ItemID: 63000, SourceListType: 7, SourceSlotIndex: 4},
}}); err != nil {
t.Fatal(err)
}
if err := repositories.Pet.Save(ctx, dnfrepo.PetRecord{CharacterID: "2", Entries: map[string]dnfrepo.PetEntry{}}); err != nil {
t.Fatal(err)
}
catalog := &pvfDungeonDropCatalog{source: finishBridgePVFSource{}, itemCache: map[uint32]dungeonDropItemDefinition{
8001: {ItemID: 8001, Kind: dungeonDropItemStackable, CreatureTransportStone: true, CreatureTransportStoneType: 1},
63000: {ItemID: 63000, Kind: dungeonDropItemEquipment, EquipmentType: "[creature]"},
}}
service := &Service{}
if err := service.commitCurrentPetTransport(ctx, repositories, catalog, "account-1", "1", currentCargoTransportRequest{StoneSlot: 70, TargetSlot: 4, Mode: 1, RecipientSelect: 1}); err != nil {
t.Fatal(err)
}
source, _, _ := repositories.Inventory.Load(ctx, "1")
destination, _, _ := repositories.Inventory.Load(ctx, "2")
sourcePets, _, _ := repositories.Pet.Load(ctx, "1")
destinationPets, _, _ := repositories.Pet.Load(ctx, "2")
if len(source.Slots) != 0 || destination.Slots["7:0"].ItemID != 63000 || len(sourcePets.Entries) != 0 || destinationPets.Entries["77"].SourceSlotIndex != 0 {
t.Fatalf("source=%+v destination=%+v sourcePets=%+v destinationPets=%+v", source.Slots, destination.Slots, sourcePets, destinationPets)
}
}
func TestCurrentResealCostAndWaxKinds(t *testing.T) {
config := currentResealConfig{
GradeWeights: []currentPVFRangeWeight{{Minimum: 85, Maximum: 85, Weight: 5}},
RarityWeights: map[int64]float64{4: 9}, ExtendedWeights: map[int64]float64{4: 1}, SoulItemIDs: map[int64]uint32{4: 10158124},
SoulCostWeights: map[int64]float64{4: 3}, ResealConstants: map[int64]float64{4: 1.5}, ResealCountLimits: []currentPVFRangeWeight{{Minimum: 1, Maximum: 90, Weight: 7}},
}
definition := dungeonDropItemDefinition{Kind: dungeonDropItemEquipment, MinimumLevel: 85, Rarity: 4}
if item, cost, ok := config.materialAndCost(definition, 0); !ok || item != 10158124 || cost != 135 {
t.Fatalf("soul item=%d cost=%d ok=%t", item, cost, ok)
}
if cost, ok := config.waxCost(definition, 0); !ok || cost != 45 {
t.Fatalf("wax cost=%d ok=%t", cost, ok)
}
for _, test := range []struct {
definition dungeonDropItemDefinition
oneShot bool
valid bool
}{
{dungeonDropItemDefinition{PVFPath: "stackable/cash/candle_goldwax.stk"}, false, true},
{dungeonDropItemDefinition{PVFPath: "stackable/cash/candle_goldwax_oneshot.stk"}, true, true},
{dungeonDropItemDefinition{PVFPath: "stackable/event/chn_candle_goldwax_oneshot_490000345.stk"}, true, true},
{dungeonDropItemDefinition{PVFPath: "stackable/other.stk"}, false, false},
} {
oneShot, valid := currentPVFWaxKind(test.definition)
if oneShot != test.oneShot || valid != test.valid {
t.Fatalf("definition=%+v oneShot=%t valid=%t", test.definition, oneShot, valid)
}
}
}
func TestCargoTransportAndPetSealModulesAreRegistered(t *testing.T) {
for _, opcode := range []uint16{uint16(dnfenum.CmdPacketCargoTransportItem), uint16(dnfenum.CmdPacketSealCreature)} {
if _, found := currentGameplayModules.legacyHandlers[opcode]; !found {
t.Fatalf("legacy opcode=%d missing", opcode)
}
if _, found := currentGameplayModules.upperHandlers[opcode]; !found {
t.Fatalf("upper opcode=%d missing", opcode)
}
}
}
func TestRealPVFCargoTransportAndResealRules(t *testing.T) {
pvfPath := os.Getenv("DNFBRIDGE_REAL_PVF_SMOKE")
if pvfPath == "" {
t.Skip("set DNFBRIDGE_REAL_PVF_SMOKE to verify cargo transport and reseal rules")
}
archive, err := platformpvf.Open(pvfPath)
if err != nil {
t.Fatal(err)
}
catalog, err := newPVFDungeonDropCatalog(archive)
if err != nil {
t.Fatal(err)
}
transport, err := loadCurrentCargoTransportConfig(catalog)
if err != nil {
t.Fatal(err)
}
if _, found := transport.Rules[7]; !found {
t.Fatal("runtime PVF has no Grandi stone type 7")
}
for _, direct := range []struct {
stoneID uint32
stoneType int64
targetID uint32
}{
{stoneID: 10304349, stoneType: 8, targetID: 100140069},
{stoneID: 490002430, stoneType: 4016, targetID: 100060476},
} {
stone, resolveErr := catalog.ResolveItem(direct.stoneID)
if resolveErr != nil {
t.Fatal(resolveErr)
}
if !stone.CargoTransportStone || stone.CargoTransportStoneType != direct.stoneType {
t.Fatalf("runtime direct cargo stone=%d not parsed: %+v", direct.stoneID, stone)
}
directRule, ruleFound := transport.Rules[stone.CargoTransportStoneType]
if !ruleFound {
t.Fatalf("runtime direct cargo stone=%d type=%d has no rule", direct.stoneID, stone.CargoTransportStoneType)
}
directTarget, resolveErr := catalog.ResolveItem(direct.targetID)
if resolveErr != nil {
t.Fatal(resolveErr)
}
if !currentCargoRuleAllows(directRule, directTarget, dnfrepo.ItemStack{ItemID: int64(directTarget.ItemID), Count: 1}) {
t.Fatalf("runtime screenshot cargo target rejected stone=%+v target=%+v rule=%+v", stone, directTarget, directRule)
}
}
creatureStone, err := catalog.ResolveItem(490700208)
if err != nil {
t.Fatal(err)
}
if !creatureStone.CreatureTransportStone || creatureStone.CreatureTransportStoneType != 16 {
t.Fatalf("runtime creature transport stone not parsed: %+v", creatureStone)
}
rule := transport.Rules[7]
target, err := catalog.ResolveItem(100240061)
if err != nil {
t.Fatal(err)
}
category := currentCargoEquipmentCategory(target.EquipmentType)
base := rangeWeightFor(rule.GradeWeights, target.MinimumLevel) * rule.RarityWeights[target.Rarity]
if len(rule.SoulWeights[target.Rarity]) <= category || len(rule.ElementWeights[target.Rarity]) <= category || len(rule.EpicPieceWeight) <= category {
t.Fatalf("runtime Grandi cost vectors missing target=%+v category=%d base=%d rule=%+v", target, category, base, rule)
}
if category != 1 || base != 45 || rule.SoulItemIDs[target.Rarity] != 10158124 || rule.SoulWeights[target.Rarity][category]*base != 135 || rule.ElementWeights[target.Rarity][category]*base != 450 || rule.EpicPieceWeight[category]*base != 135 {
t.Fatalf("runtime Grandi rule target=%+v category=%d base=%d rule=%+v", target, category, base, rule)
}
reseal, err := loadCurrentResealConfig(catalog)
if err != nil {
t.Fatal(err)
}
resealable := dungeonDropItemDefinition{Kind: dungeonDropItemEquipment, MinimumLevel: 85, Rarity: 3}
if item, cost, ok := reseal.materialAndCost(resealable, 0); !ok || item != 10099774 || cost != 80 {
t.Fatalf("runtime Grandi reseal item=%d cost=%d ok=%t config=%+v", item, cost, ok, reseal)
}
if cost, ok := reseal.waxCost(resealable, 0); !ok || cost != 8 {
t.Fatalf("runtime golden wax cost=%d ok=%t", cost, ok)
}
for _, itemID := range []uint32{14, 2660238, 10098656, 490000345, 10157922, 10164812} {
definition, resolveErr := catalog.ResolveItem(itemID)
if resolveErr != nil {
t.Fatal(resolveErr)
}
oneShot, valid := currentPVFWaxKind(definition)
if !valid || (itemID == 14 && oneShot) || (itemID != 14 && !oneShot) {
t.Fatalf("runtime wax item=%d one_shot=%t valid=%t definition=%+v", itemID, oneShot, valid, definition)
}
}
}
func mustSaveCargoTransportAccount(t *testing.T, ctx context.Context, repositories dnfrepo.Group, accountID string, capacity int) {
t.Helper()
if err := repositories.Account.Save(ctx, dnfrepo.AccountRecord{AccountID: accountID, Metadata: map[string]string{
"account_cargo_created": "true", "account_cargo_level": strconv.Itoa(capacity),
}}); err != nil {
t.Fatal(err)
}
}