Files
dusk/test/item/test_inventory.c
T
YourWishesandClaude Sonnet 5 109ddc9e1e Switch item definitions to a JSON-driven runtime registry
Replaces the build-time item.json/tools.item.py code generator with a
single item_t ITEM registry populated at runtime by parsing
assets/items.jsonc, so items/types can grow without a rebuild. Adds
itemGetDefById/itemGetTypeById/itemGetIdByName/itemTypeGetOrCreate,
updates inventory/backpack to size off the new *_DEF_COUNT_MAX caps
and look up types dynamically, and reorders engine init so rpgInit
(which loads items) runs before uiInit (which needs the item type
count to size the backpack's tabs).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-13 11:36:50 -05:00

433 lines
14 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "dusktest.h"
#include "rpg/item/inventory.h"
#include "util/memory.h"
static itemid_t ITEM_ID_POTION;
static itemid_t ITEM_ID_POTATO;
static itemid_t ITEM_ID_APPLE;
// Registers three test items (POTION as MEDICINE, POTATO/APPLE as FOOD)
// directly into the global ITEM registry -- these tests exercise the
// inventory/backpack layer only, not the JSON-driven itemInit() pipeline.
static int_t itemFixtureSetup(void **state) {
memoryZero(&ITEM, sizeof(ITEM));
ITEM.typeCount = ITEM_TYPE_NULL + 1;
ITEM.defCount = ITEM_ID_NULL + 1;
const itemtype_t medicine = itemTypeGetOrCreate("MEDICINE");
const itemtype_t food = itemTypeGetOrCreate("FOOD");
ITEM_ID_POTION = ITEM.defCount++;
ITEM.defs[ITEM_ID_POTION].type = medicine;
ITEM_ID_POTATO = ITEM.defCount++;
ITEM.defs[ITEM_ID_POTATO].type = food;
ITEM_ID_APPLE = ITEM.defCount++;
ITEM.defs[ITEM_ID_APPLE].type = food;
return 0;
}
static void test_inventoryInit(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Should setup struct
assert_non_null(inventory.storage);
assert_int_equal(inventory.storageSize, 5);
// Should zero the item ids.
for(size_t i = 0; i < inventory.storageSize; i++) {
assert_int_equal(inventory.storage[i].item, ITEM_ID_NULL);
assert_int_equal(inventory.storage[i].quantity, 0);
}
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryInit(NULL, storage, 5));
// Should fail when given NULL storage pointer
expect_assert_failure(inventoryInit(&inventory, NULL, 5));
// Should fail when given zero storage size
expect_assert_failure(inventoryInit(&inventory, storage, 0));
}
static void test_inventoryItemExists(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Initially should not exist
assert_false(inventoryItemExists(&inventory, ITEM_ID_POTION));
// Add item
inventorySet(&inventory, ITEM_ID_POTION, 3);
assert_true(inventoryItemExists(&inventory, ITEM_ID_POTION));
// Remove item
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_false(inventoryItemExists(&inventory, ITEM_ID_POTION));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryItemExists(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_NULL));
// Should fail if item has zero quantity somehow
inventorySet(&inventory, ITEM_ID_POTION, 3);
inventory.storage[0].quantity = 0;
expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION));
}
static void test_inventorySet(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Set item quantity
inventorySet(&inventory, ITEM_ID_POTION, 4);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 4);
// Update item quantity
inventorySet(&inventory, ITEM_ID_POTION, 2);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 2);
// Remove item by setting quantity to 0
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL);
// Setting multiple items should not cause issues
inventorySet(&inventory, ITEM_ID_POTION, 1);
inventorySet(&inventory, ITEM_ID_POTATO, 5);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 1);
assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO);
assert_int_equal(inventory.storage[1].quantity, 5);
assert_int_equal(inventory.storage[2].item, ITEM_ID_NULL);
// Setting early item to 0 should shift others down
inventorySet(&inventory, ITEM_ID_POTION, 0);// Item 0 removed
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTATO);
assert_int_equal(inventory.storage[0].quantity, 5);
assert_int_equal(inventory.storage[1].item, ITEM_ID_NULL);
// Setting non-existing item should add it
inventorySet(&inventory, ITEM_ID_POTION, 3);
assert_int_equal(inventory.storage[1].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[1].quantity, 3);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventorySet(NULL, ITEM_ID_POTION, 3));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventorySet(&inventory, ITEM_ID_POTION, 3));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventorySet(&inventory, ITEM_ID_POTION, 3));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventorySet(&inventory, ITEM_ID_NULL, 3));
}
static void test_inventoryAdd(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Add item quantity
inventoryAdd(&inventory, ITEM_ID_POTION, 4);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 4);
// Add more to existing item
inventoryAdd(&inventory, ITEM_ID_POTION, 3);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].quantity, 7);
// Adding item that would overflow should assert
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 250));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryAdd(NULL, ITEM_ID_POTION, 3));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 3));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 3));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_NULL, 3));
}
static void test_inventoryRemove(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Add item and then remove it
inventorySet(&inventory, ITEM_ID_POTION, 5);
inventoryRemove(&inventory, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL);
// Removing non-existing item should do nothing
inventoryRemove(&inventory, ITEM_ID_POTION);
assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryRemove(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_POTION));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_NULL));
}
static void test_inventoryGetCount(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Initially should be zero
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 0);
// Add item and check count
inventorySet(&inventory, ITEM_ID_POTION, 4);
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 4);
// Update item quantity and check count
inventorySet(&inventory, ITEM_ID_POTION, 2);
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 2);
// Remove item and check count
inventorySet(&inventory, ITEM_ID_POTION, 0);
assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 0);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryGetCount(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_POTION));
inventory.storageSize = 5;
// Should fail when given ITEM_ID_NULL
expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_NULL));
}
static void test_inventoryIsFull(void **state) {
inventorystack_t storage[2];
inventory_t inventory;
inventoryInit(&inventory, storage, 2);
// Initially should not be full
assert_false(inventoryIsFull(&inventory));
// Fill inventory
inventorySet(&inventory, ITEM_ID_POTION, 1);
inventorySet(&inventory, ITEM_ID_POTATO, 1);
assert_true(inventoryIsFull(&inventory));
// Remove one item
inventoryRemove(&inventory, ITEM_ID_POTION);
assert_false(inventoryIsFull(&inventory));
// Add one item back
inventorySet(&inventory, ITEM_ID_POTION, 1);
assert_true(inventoryIsFull(&inventory));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryIsFull(NULL));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryIsFull(&inventory));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryIsFull(&inventory));
inventory.storageSize = 2;
}
static void test_inventoryItemFull(void **state) {
inventorystack_t storage[2];
inventory_t inventory;
inventoryInit(&inventory, storage, 2);
// Initially should not be full
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Add some potions, but not too many.
inventorySet(&inventory, ITEM_ID_POTION, 100);
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Fill with potions
inventorySet(&inventory, ITEM_ID_POTION, ITEM_STACK_QUANTITY_MAX);
assert_true(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Add potatoes, should not affect potions
inventorySet(&inventory, ITEM_ID_POTATO, 50);
assert_true(inventoryItemFull(&inventory, ITEM_ID_POTION));
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTATO));
// Remove some potions
inventorySet(&inventory, ITEM_ID_POTION, 200);
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
// Fill with potatoes
inventorySet(&inventory, ITEM_ID_POTATO, ITEM_STACK_QUANTITY_MAX);
assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION));
assert_true(inventoryItemFull(&inventory, ITEM_ID_POTATO));
// Should fail when given NULL inventory pointer
expect_assert_failure(inventoryItemFull(NULL, ITEM_ID_POTION));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventoryItemFull(&inventory, ITEM_ID_POTION));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventoryItemFull(&inventory, ITEM_ID_POTION));
inventory.storageSize = 2;
}
static void test_inventorySort(void **state) {
inventorystack_t storage[5];
inventory_t inventory;
inventoryInit(&inventory, storage, 5);
// Add items out of order
inventorySet(&inventory, ITEM_ID_POTATO, 3);
inventorySet(&inventory, ITEM_ID_APPLE, 5);
inventorySet(&inventory, ITEM_ID_POTION, 2);
// Sort by ID
inventorySort(&inventory, INVENTORY_SORT_BY_ID, false);
// Check order
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO);
assert_int_equal(inventory.storage[2].item, ITEM_ID_APPLE);
// Sort by ID reverse
inventorySort(&inventory, INVENTORY_SORT_BY_ID, true);
assert_int_equal(inventory.storage[0].item, ITEM_ID_APPLE);
assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO);
assert_int_equal(inventory.storage[2].item, ITEM_ID_POTION);
// Sort by type ascending - POTION (MEDICINE) sorts before POTATO/APPLE
// (both FOOD). The two FOOD items compare equal, so their relative order
// isn't guaranteed (qsort is not stable) - only the type grouping is.
inventorySort(&inventory, INVENTORY_SORT_BY_TYPE, false);
assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION);
assert_true(
(inventory.storage[1].item == ITEM_ID_POTATO &&
inventory.storage[2].item == ITEM_ID_APPLE) ||
(inventory.storage[1].item == ITEM_ID_APPLE &&
inventory.storage[2].item == ITEM_ID_POTATO)
);
// Sort by type reverse - FOOD items first (order unspecified), POTION last.
inventorySort(&inventory, INVENTORY_SORT_BY_TYPE, true);
assert_int_equal(inventory.storage[2].item, ITEM_ID_POTION);
assert_true(
(inventory.storage[0].item == ITEM_ID_POTATO &&
inventory.storage[1].item == ITEM_ID_APPLE) ||
(inventory.storage[0].item == ITEM_ID_APPLE &&
inventory.storage[1].item == ITEM_ID_POTATO)
);
// Should fail when given NULL inventory pointer
expect_assert_failure(inventorySort(NULL, INVENTORY_SORT_BY_ID, false));
// Should fail when given NULL storage pointer
inventory.storage = NULL;
expect_assert_failure(inventorySort(&inventory, INVENTORY_SORT_BY_ID, false));
inventory.storage = storage;
// Should fail when given zero storage size
inventory.storageSize = 0;
expect_assert_failure(inventorySort(&inventory, INVENTORY_SORT_BY_ID, false));
inventory.storageSize = 5;
}
int main(int argc, char** argv) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_inventoryInit),
cmocka_unit_test(test_inventoryItemExists),
cmocka_unit_test(test_inventorySet),
cmocka_unit_test(test_inventoryAdd),
cmocka_unit_test(test_inventoryRemove),
cmocka_unit_test(test_inventoryGetCount),
cmocka_unit_test(test_inventoryIsFull),
cmocka_unit_test(test_inventoryItemFull),
cmocka_unit_test(test_inventorySort),
};
return cmocka_run_group_tests(tests, itemFixtureSetup, NULL);
}