Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions gateway/src/codec/auction/subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,6 @@ crate::stdb::relays::declare_relays! {
insert(Live) => |context, _event, row| auction_notice_appeared(&context.view, row);
}
}

#[cfg(test)]
mod packet_tests;
91 changes: 91 additions & 0 deletions gateway/src/codec/auction/subscriptions/packet_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use super::*;
use crate::stdb::bindings::AuctionNotice;
use crate::world::Outbound;
use wow_world_messages::vanilla::opcodes::ServerOpcodeMessage;

/// Auction Notice kind decoding (`cm:AuctionHouseHandler.cpp`/`AuctionHouseMgr.cpp`): Outbid
/// and Won go out on `SMSG_AUCTION_BIDDER_NOTIFICATION`, Sold, Expired and New bid go out on
/// `SMSG_AUCTION_OWNER_NOTIFICATION`, Removed goes out on `SMSG_AUCTION_REMOVED_NOTIFICATION`,
/// and an unrecognized kind or an unimported house both drop the packet. The Auction Mail the
/// same transaction wrote still reaches the inbox.
#[test]
fn auction_notice_kinds_decode_to_their_own_packet_and_an_unknown_one_drops() {
use lyracore_shared::auction::auction_notice::{EXPIRED, NEW_BID, OUTBID, REMOVED, SOLD, WON};
use wow_world_messages::vanilla::AuctionHouse;

let notice = |kind: u8, house: u32| AuctionNotice {
id: 1,
recipient_guid: 7,
kind,
house,
auction_id: 41,
item_entry: 25,
random_property_id: 117,
bid: 500,
out_bid: 25,
bidder_guid: 9,
created_at: spacetimedb_sdk::Timestamp::UNIX_EPOCH,
};

for kind in [OUTBID, WON] {
let out = auction_notice_outbound(&notice(kind, 1));
assert_eq!(
out.len(),
1,
"kind {kind} must decode to exactly one packet"
);
match &out[0] {
Outbound::One(ServerOpcodeMessage::SMSG_AUCTION_BIDDER_NOTIFICATION(packet)) => {
assert_eq!(packet.auction_house, AuctionHouse::Stormwind);
assert_eq!(packet.bidder.guid(), 9);
}
Outbound::One(other) => {
panic!("kind {kind}: expected SMSG_AUCTION_BIDDER_NOTIFICATION, got {other}")
}
_ => {
panic!("kind {kind}: expected a single SMSG_AUCTION_BIDDER_NOTIFICATION packet")
}
}
}

for kind in [SOLD, EXPIRED, NEW_BID] {
let out = auction_notice_outbound(&notice(kind, 1));
assert_eq!(
out.len(),
1,
"kind {kind} must decode to exactly one packet"
);
match &out[0] {
Outbound::One(ServerOpcodeMessage::SMSG_AUCTION_OWNER_NOTIFICATION(packet)) => {
assert_eq!(packet.auction_id, 41);
}
Outbound::One(other) => {
panic!("kind {kind}: expected SMSG_AUCTION_OWNER_NOTIFICATION, got {other}")
}
_ => {
panic!("kind {kind}: expected a single SMSG_AUCTION_OWNER_NOTIFICATION packet")
}
}
}

let out = auction_notice_outbound(&notice(REMOVED, 1));
assert!(
matches!(
out.as_slice(),
[Outbound::One(ServerOpcodeMessage::SMSG_AUCTION_REMOVED_NOTIFICATION(packet))]
if packet.item == 41
&& packet.item_template == 25
&& packet.random_property_id == 117
),
"Removed goes out on SMSG_AUCTION_REMOVED_NOTIFICATION"
);

assert!(
auction_notice_outbound(&notice(200, 1)).is_empty(),
"an unknown kind must drop, not guess a packet"
);
assert!(
auction_notice_outbound(&notice(OUTBID, 99)).is_empty(),
"a house outside 1-7 must drop rather than send an unresolved house"
);
}
3 changes: 3 additions & 0 deletions gateway/src/codec/channel/subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ crate::stdb::relays::declare_relays! {
insert(Live) => |context, _event, row| channel_notice_appeared(&context.view, row);
}
}

#[cfg(test)]
mod audience_tests;
62 changes: 62 additions & 0 deletions gateway/src/codec/channel/subscriptions/audience_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use super::*;
use crate::codec::social::subscriptions::contact_changed;
use crate::stdb::world_view::test_support::{
contact_entry as ignore_row, registered_viewer as listener,
};
use std::sync::mpsc::Receiver;

const SPEAKER: u64 = 10;

/// Every raw packet queued for one viewer, jobs run in order, as `(opcode, body)`.
fn received_raw(rx: &Receiver<Outbound>) -> Vec<(u16, Vec<u8>)> {
let mut packets = Vec::new();
while let Ok(outbound) = rx.try_recv() {
let Outbound::Job(job) = outbound else {
panic!("the Relay must enqueue packet work as a writer job");
};
for packet in job() {
let Outbound::Raw { opcode, body } = packet else {
panic!("a Channel Notice is one raw packet");
};
packets.push((opcode, body));
}
}
packets
}

/// JOINED naming `SPEAKER` in "Tx": notice 0x00, the name, then the guid (cm:Channel.cpp:760-764).
#[test]
fn a_channel_notice_reaches_each_recipient_on_any_shard_and_nobody_else() {
let view = WorldView::new(true);
let (_, first_rx) = listener(&view, 0, 20);
let (_, second_rx) = listener(&view, 1, 30);
let (_, bystander_rx) = listener(&view, 0, 40);
contact_changed(&view, 0, &ignore_row(20, SPEAKER, true), true);

channel_notice_appeared(
&view,
&ChatChannelNoticeEvent {
id: 1,
notice: 0x00,
channel_name: "Tx".to_string(),
subject_guid: SPEAKER,
actor_guid: 0,
old_flags: 0,
new_flags: 0,
channel_flags: 0,
text: String::new(),
recipients: vec![20, 30, 99],
created_at: spacetimedb_sdk::Timestamp::UNIX_EPOCH,
},
);

let mut joined = vec![0x00, b'T', b'x', 0];
joined.extend(SPEAKER.to_le_bytes());
assert_eq!(
received_raw(&first_rx),
[(0x0099, joined.clone())],
"notices skip the ignore list"
);
assert_eq!(received_raw(&second_rx), [(0x0099, joined)]);
assert!(received_raw(&bystander_rx).is_empty());
}
3 changes: 3 additions & 0 deletions gateway/src/codec/combat/subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1353,3 +1353,6 @@ pub(crate) mod tests {
assert_eq!(*packet, codec::build_force_run_speed(7, 0.7));
}
}

#[cfg(test)]
mod packet_tests;
Loading
Loading