regression_tests.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #include <array>
  2. #include <cstddef>
  3. #include <cstdint>
  4. #include <cstdio>
  5. #include <cstring>
  6. #include <span>
  7. #include "middleware/content/packages/tables/definition_index_table.h"
  8. #include "middleware/content/packages/tables/items.h"
  9. #include "middleware/datagen/character_record/appearance/internal.h"
  10. #include "middleware/queuez/queuez_update.h"
  11. #include "middleware/web_service/messages/opcode1901.h"
  12. #include "server/bap/encrypted/queuez/queuez_state_validation.h"
  13. #include "server/web_service/web_service_runtime.h"
  14. #include "state/build_data/inventory/buckets/inventory_bucket_catalog.h"
  15. #include "state/build_data/table.h"
  16. namespace {
  17. int failures{};
  18. void expect(bool value, const char* label) {
  19. if (!value) {
  20. std::fprintf(stderr, "FAIL %s\n", label);
  21. ++failures;
  22. }
  23. }
  24. template <typename Value>
  25. void write_at(std::span<std::byte> bytes, std::size_t offset, Value value) {
  26. std::memcpy(bytes.data() + offset, &value, sizeof value);
  27. }
  28. void test_opcode1901_fixture() {
  29. using namespace sunrise::middleware::web_service;
  30. using namespace sunrise::middleware::web_service::messages;
  31. constexpr std::array<std::uint8_t, 24> wire{
  32. 0x19, 0xA8, 0x28, 0x56, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,
  33. 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
  34. };
  35. Message message{
  36. opcode1901::kOpcode, 7, {reinterpret_cast<const std::byte*>(wire.data()), wire.size()}};
  37. opcode1901::Request decoded{};
  38. expect(opcode1901::parse_request(message, decoded), "opcode1901 captured fixture parses");
  39. expect(decoded.plugDefinitionIndex == 6786, "opcode1901 plug index");
  40. expect(decoded.canonicalSocketKind == 5 && decoded.modelSocketKind == 0,
  41. "opcode1901 socket kinds");
  42. expect(decoded.socketIndex == 5 && decoded.auxiliary == 0, "opcode1901 socket descriptor");
  43. expect(decoded.equipmentSelector == 8, "opcode1901 equipment selector");
  44. auto malformed = wire;
  45. malformed[0] = 0;
  46. message.payload = {reinterpret_cast<const std::byte*>(malformed.data()), malformed.size()};
  47. expect(!opcode1901::parse_request(message, decoded), "opcode1901 rejects wrong count");
  48. expect(decoded.plugDefinitionIndex == 0 && decoded.equipmentSelector == 0,
  49. "opcode1901 clears rejected output");
  50. }
  51. void test_class_art_selection() {
  52. namespace appearance = sunrise::middleware::datagen::character_record::appearance;
  53. namespace details = sunrise::state::build_data::items::details;
  54. details::Definition definition{};
  55. definition.artArrangementIndices = {100, 101, details::kUnavailableArtIndex, 103};
  56. expect(appearance::select_art_arrangement(definition, sunrise::state::CharacterClass::titan)
  57. == 101,
  58. "Titan art selects class row");
  59. expect(appearance::select_art_arrangement(definition, sunrise::state::CharacterClass::hunter)
  60. == 100,
  61. "missing Hunter art falls back to generic");
  62. expect(appearance::select_art_arrangement(definition, sunrise::state::CharacterClass::warlock)
  63. == 103,
  64. "Warlock art selects class row");
  65. }
  66. void test_class_art_extraction() {
  67. namespace tables = sunrise::middleware::content::packages::tables;
  68. namespace items = tables::items;
  69. std::array<std::byte, 384> blob{};
  70. constexpr std::size_t art = 192;
  71. constexpr std::size_t header = 304;
  72. constexpr std::size_t data = header + 16;
  73. write_at(
  74. blob, tables::kArtBlockOffset, static_cast<std::int64_t>(art - tables::kArtBlockOffset));
  75. write_at(blob, art, std::uint64_t{3});
  76. write_at(blob, art + 8, static_cast<std::int64_t>(header - (art + 8)));
  77. write_at(blob, header - 4, std::uint32_t{0x80800000});
  78. write_at(blob, header, std::uint64_t{3});
  79. write_at(blob, header + 8, tables::kArtRowClass);
  80. write_at(blob, art + 88, std::uint16_t{77});
  81. write_at(blob, data, std::int8_t{-1});
  82. write_at(blob, data + 2, std::uint16_t{100});
  83. write_at(blob, data + 4, std::int8_t{0});
  84. write_at(blob, data + 6, std::uint16_t{101});
  85. write_at(blob, data + 8, std::int8_t{2});
  86. write_at(blob, data + 10, std::uint16_t{103});
  87. items::Row row{};
  88. items::read_appearance(blob, row);
  89. expect(row.gearArtIndex == 77, "appearance extraction reads gear art");
  90. expect(row.artArrangementIndices[0] == 100 && row.artArrangementIndices[1] == 101
  91. && row.artArrangementIndices[2] == items::kUnavailableArtIndex
  92. && row.artArrangementIndices[3] == 103,
  93. "appearance extraction keeps generic and class rows");
  94. }
  95. void test_table_replacement() {
  96. struct Row {
  97. std::uint32_t value{};
  98. };
  99. sunrise::state::build_data::Table<Row, 8> table{};
  100. constexpr std::array<Row, 4> first{{{1}, {2}, {3}, {4}}};
  101. constexpr std::array<Row, 2> second{{{9}, {8}}};
  102. expect(table.replace(first), "table accepts first replacement");
  103. expect(table.replace(second), "table accepts shorter replacement");
  104. std::array<Row, 8> snapshot{};
  105. std::size_t count = 0;
  106. expect(table.snapshot(snapshot, count) && count == second.size(), "table publishes new count");
  107. expect(snapshot[0].value == second[0].value && snapshot[1].value == second[1].value,
  108. "table publishes new prefix");
  109. expect(table.reset(3).size() == 3 && table.count() == 3, "table reset resizes safely");
  110. table.clear();
  111. expect(table.count() == 0, "table clear publishes empty state");
  112. }
  113. void test_bucket_equipment_mapping_validation() {
  114. namespace buckets = sunrise::state::build_data::inventory::buckets;
  115. constexpr std::array<buckets::Descriptor, 2> mapped{{
  116. {0, buckets::ArraySelector::character, 0, 10, 7, 0},
  117. {13, buckets::ArraySelector::profile, 75, 50, buckets::kUnavailableEquipmentSlot, 0},
  118. }};
  119. expect(buckets::valid(mapped), "bucket table accepts a unique extracted equipment slot");
  120. auto unmapped = mapped;
  121. unmapped[0].equipmentSlot = buckets::kUnavailableEquipmentSlot;
  122. expect(!buckets::valid(unmapped), "bucket table rejects an all-unmapped cache domain");
  123. auto duplicate = mapped;
  124. duplicate[1].equipmentSlot = mapped[0].equipmentSlot;
  125. expect(!buckets::valid(duplicate), "bucket table rejects duplicate equipment slots");
  126. }
  127. void test_family4_resync() {
  128. namespace queuez = sunrise::server::bap::encrypted::queuez;
  129. namespace wire = sunrise::middleware::queuez;
  130. queuez::SessionState before{};
  131. before.family4RootSoid = 0x100;
  132. before.family4Version = 3;
  133. before.family4ResidentCount = 2;
  134. before.family4Active = true;
  135. before.family4Residents[0] = {0x100, 10};
  136. before.family4Residents[1] = {0x101, 11};
  137. expect(queuez::valid(before), "Family4 before state is valid");
  138. constexpr std::array<wire::Object, 3> objects{{
  139. {10, 0x100, wire::Encoding::oodle, {}},
  140. {11, 0x101, wire::Encoding::oodle, {}},
  141. {12, 0x202, wire::Encoding::oodle, {}},
  142. }};
  143. const wire::Family refresh{
  144. queuez::kAccountFamilyType, 0x100, 4, wire::kFullSnapshotFlag, objects};
  145. queuez::SessionState after{};
  146. expect(queuez::stage_family4_refresh(before, refresh, after),
  147. "Family4 next-version refresh stages");
  148. expect(after.family4Version == 4 && after.family4ResidentCount == 3,
  149. "Family4 refresh advances version and manifest");
  150. expect(after.family4Residents[2].objectSoid == 0x202, "Family4 refresh records added resident");
  151. wire::Family stale = refresh;
  152. stale.version = before.family4Version;
  153. queuez::SessionState rejected{};
  154. expect(!queuez::stage_family4_refresh(before, stale, rejected),
  155. "Family4 rejects stale refresh version");
  156. expect(rejected.family4Version == before.family4Version
  157. && rejected.family4ResidentCount == before.family4ResidentCount,
  158. "rejected refresh leaves exact before image");
  159. auto duplicateObjects = objects;
  160. duplicateObjects[2].version = duplicateObjects[1].version;
  161. wire::Family duplicate = refresh;
  162. duplicate.objects = duplicateObjects;
  163. expect(!queuez::stage_family4_refresh(before, duplicate, rejected),
  164. "Family4 rejects duplicate resident identity");
  165. }
  166. void test_outcome_exclusivity() {
  167. using namespace sunrise;
  168. server::web_service::Outcome outcome{};
  169. state::PendingItemState itemState{};
  170. itemState.prepared = true;
  171. outcome.mutation = itemState;
  172. expect(server::web_service::mutation_if<state::PendingItemState>(outcome) != nullptr,
  173. "Outcome stores item-state transaction");
  174. expect(server::web_service::mutation_if<state::PendingSocketPlug>(outcome) == nullptr,
  175. "Outcome exposes only one transaction alternative");
  176. state::PendingSocketPlug socket{};
  177. socket.prepared = true;
  178. outcome.mutation = socket;
  179. expect(server::web_service::mutation_if<state::PendingItemState>(outcome) == nullptr
  180. && server::web_service::mutation_if<state::PendingSocketPlug>(outcome) != nullptr,
  181. "Outcome replacement cannot retain a second mutation");
  182. }
  183. } // namespace
  184. int main() {
  185. test_opcode1901_fixture();
  186. test_class_art_selection();
  187. test_class_art_extraction();
  188. test_table_replacement();
  189. test_bucket_equipment_mapping_validation();
  190. test_family4_resync();
  191. test_outcome_exclusivity();
  192. if (failures != 0) {
  193. std::fprintf(stderr, "%d regression test(s) failed\n", failures);
  194. return 1;
  195. }
  196. std::puts("All Sunrise regression tests passed");
  197. return 0;
  198. }