From 3d4ca8417ae03ed020efdba5ea6041d65b3bde51 Mon Sep 17 00:00:00 2001 From: tequ Date: Mon, 24 Nov 2025 15:06:30 +0900 Subject: [PATCH] add slot tests --- src/test/app/HookAPI_test.cpp | 368 +++++++++++++++++++++++++++++++++- 1 file changed, 358 insertions(+), 10 deletions(-) diff --git a/src/test/app/HookAPI_test.cpp b/src/test/app/HookAPI_test.cpp index 4108968e7..c9da6e3b9 100644 --- a/src/test/app/HookAPI_test.cpp +++ b/src/test/app/HookAPI_test.cpp @@ -1914,7 +1914,7 @@ public: ASSERT_FLOAT_EQUAL( api, api.float_sum(std::get<0>(test), std::get<1>(test)).value(), - std::get<1>(test)); + std::get<2>(test)); } } @@ -2682,7 +2682,46 @@ public: { testcase("Test slot"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing slot + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot(1).error() == DOESNT_EXIST); + } + + // Present slot pointing to an STAmount field + { + printf("test\n"); + STObject obj(sfGeneric); + obj.setFieldAmount(sfAmount, drops(1)); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = hook::SlotEntry{.storage = storage, .entry = 0}; + stubCtx.slot[1].entry = &(*stubCtx.slot[1].storage); + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + auto const result = api.slot(1); + BEAST_EXPECT(result.has_value()); + + Serializer s; + (*result)->add(s); + + STObject resultObj{s.slice(), sfGeneric}; + printf( + "resultObj: %s\n", + resultObj.getJson(JsonOptions::none).toStyledString().c_str()); + } } void @@ -2690,7 +2729,36 @@ public: { testcase("Test slot_clear"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing slot + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_clear(1).error() == DOESNT_EXIST); + } + + // Clear existing slot and push to free queue + { + STObject obj(sfGeneric); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[2] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_clear(2).has_value()); + BEAST_EXPECT(hookCtx.slot.find(2) == hookCtx.slot.end()); + BEAST_EXPECT(!hookCtx.slot_free.empty()); + } } void @@ -2698,7 +2766,51 @@ public: { testcase("Test slot_count"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Nonexistent + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_count(1).error() == DOESNT_EXIST); + } + + // Not an array + { + STObject obj(sfGeneric); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_count(1).error() == NOT_AN_ARRAY); + } + + // Array count + { + STArray arr{sfGeneric}; + arr.emplace_back(STObject(sfGeneric)); + arr.emplace_back(STObject(sfGeneric)); + auto storage = std::make_shared(arr); + StubHookContext stubCtx{}; + stubCtx.slot[3] = { + .storage = + std::reinterpret_pointer_cast(storage), + .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_count(3).value() == 2); + } } void @@ -2706,7 +2818,50 @@ public: { testcase("Test slot_float"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing slot + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_float(1).error() == DOESNT_EXIST); + } + + // Not an amount + { + STObject obj(sfGeneric); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_float(1).error() == NOT_AN_AMOUNT); + } + + // Native amount + { + STObject obj(sfGeneric); + obj.setFieldAmount(sfAmount, drops(10)); + auto storage = std::make_shared(obj); + auto* amtPtr = &storage->getFieldAmount(sfAmount); + StubHookContext stubCtx{}; + stubCtx.slot[2] = {.storage = storage, .entry = amtPtr}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + auto const result = api.slot_float(2); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() != 0); + } } void @@ -2714,7 +2869,26 @@ public: { testcase("Test slot_set"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + + // Invalid argument (wrong size) + BEAST_EXPECT( + api.slot_set(Bytes{1, 2, 3}, 0).error() == INVALID_ARGUMENT); + // Invalid argument (slot_no beyond max) + BEAST_EXPECT( + api.slot_set(Bytes(32, 0), hook_api::max_slots + 1).error() == + INVALID_ARGUMENT); } void @@ -2722,7 +2896,35 @@ public: { testcase("Test slot_size"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing slot + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_size(1).error() == DOESNT_EXIST); + } + + // Size of STObject + { + STObject obj(sfGeneric); + obj.setFieldAmount(sfAmount, drops(1)); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_size(1).value() > 0); + } } void @@ -2730,7 +2932,42 @@ public: { testcase("Test slot_subarray"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing parent + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_subarray(1, 0, 0).error() == DOESNT_EXIST); + } + + // Valid subarray extraction + { + STArray arr{sfGeneric}; + arr.emplace_back(STObject(sfGeneric)); + arr.emplace_back(STObject(sfGeneric)); + auto storage = std::make_shared(arr); + StubHookContext stubCtx{}; + stubCtx.slot[1] = { + .storage = + std::reinterpret_pointer_cast(storage), + .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + auto const result = api.slot_subarray(1, 1, 0); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() != 0); + BEAST_EXPECT(hookCtx.slot[result.value()].entry != nullptr); + } } void @@ -2738,7 +2975,74 @@ public: { testcase("Test slot_subfield"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing parent + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT( + api.slot_subfield(1, sfAccount.getCode(), 0).error() == + DOESNT_EXIST); + } + + // No free slots + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + for (int i = 0; i < hook_api::max_slots; i++) + hookCtx.slot[i] = hook::SlotEntry{}; + hook::HookAPI api(hookCtx); + BEAST_EXPECT( + api.slot_subfield(1, sfAccount.getCode(), 0).error() == + NO_FREE_SLOTS); + BEAST_EXPECT( + api.slot_subfield( + 1, sfAccount.getCode(), hook_api::max_slots + 1) + .error() == INVALID_ARGUMENT); + } + + // Invalid field + { + STObject obj(sfGeneric); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + BEAST_EXPECT( + api.slot_subfield(1, ((99U << 16U) + 99U), 0).error() == + INVALID_FIELD); + BEAST_EXPECT( + api.slot_subfield(1, sfAccount.getCode(), 0).error() == + DOESNT_EXIST); + } + + // Valid field + { + STObject obj(sfGeneric); + obj.setFieldAmount(sfAmount, drops(1)); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + auto const result = api.slot_subfield(1, sfAmount.getCode(), 0); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() != 0); + BEAST_EXPECT(hookCtx.slot[result.value()].entry != nullptr); + // test the amount bytes + } } void @@ -2746,7 +3050,51 @@ public: { testcase("Test slot_type"); - BEAST_EXPECT(true); + using namespace jtx; + using namespace hook_api; + + auto const alice = Account{"alice"}; + Env env{*this, features}; + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + // Missing slot + { + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + hook::HookAPI api(hookCtx); + BEAST_EXPECT(api.slot_type(1, 0).error() == DOESNT_EXIST); + } + + // Generic object + { + STObject obj(sfGeneric); + obj.setFieldAmount(sfAmount, drops(5)); + auto storage = std::make_shared(obj); + StubHookContext stubCtx{}; + stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + auto const result = api.slot_type(1, 0); + BEAST_EXPECT(result.has_value()); + } + + // Amount with flag 1 + { + STObject obj(sfGeneric); + obj.setFieldAmount(sfAmount, drops(7)); + auto storage = std::make_shared(obj); + auto* amtPtr = &storage->getFieldAmount(sfAmount); + StubHookContext stubCtx{}; + stubCtx.slot[2] = {.storage = storage, .entry = amtPtr}; + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx); + hook::HookAPI api(hookCtx); + auto const result = api.slot_type(2, 1); + BEAST_EXPECT(result.has_value()); + } } void