1pub mod incoming;
5pub mod outgoing;
6
7pub use self::{
8 incoming::{
9 Exception, IncomingEvent, PlayerUpdate, PlayerUpdateState, Stats, StatsCpu, StatsFrame,
10 StatsMemory, Track, TrackEnd, TrackException, TrackStart, TrackStuck, WebSocketClosed,
11 },
12 outgoing::{
13 Destroy, Equalizer, EqualizerBand, OutgoingEvent, Pause, Play, Seek, Stop,
14 UpdatePlayerTrack, VoiceUpdate, Volume,
15 },
16};
17
18#[cfg(test)]
19mod lavalink_struct_tests {
20 use super::incoming::{Stats, StatsCpu, StatsMemory};
21 use serde_test::Token;
22
23 #[test]
24 fn stats_frames_not_provided() {
25 const LAVALINK_LOAD: f64 = 0.276_119_402_985_074_65;
26 const MEM_ALLOCATED: u64 = 62_914_560;
27 const MEM_FREE: u64 = 27_664_576;
28 const MEM_RESERVABLE: u64 = 4_294_967_296;
29 const MEM_USED: u64 = 35_249_984;
30 const SYSTEM_LOAD: f64 = 0.195_380_536_378_835_9;
31
32 let expected = Stats {
33 cpu: StatsCpu {
34 cores: 4,
35 lavalink_load: LAVALINK_LOAD,
36 system_load: SYSTEM_LOAD,
37 },
38 frame_stats: None,
39 memory: StatsMemory {
40 allocated: MEM_ALLOCATED,
41 free: MEM_FREE,
42 reservable: MEM_RESERVABLE,
43 used: MEM_USED,
44 },
45 players: 0,
46 playing_players: 0,
47 uptime: 18589,
48 };
49
50 serde_test::assert_de_tokens(
51 &expected,
52 &[
53 Token::Struct {
54 name: "Stats",
55 len: 6,
56 },
57 Token::Str("cpu"),
58 Token::Struct {
59 name: "StatsCpu",
60 len: 3,
61 },
62 Token::Str("cores"),
63 Token::U64(4),
64 Token::Str("lavalinkLoad"),
65 Token::F64(LAVALINK_LOAD),
66 Token::Str("systemLoad"),
67 Token::F64(SYSTEM_LOAD),
68 Token::StructEnd,
69 Token::Str("memory"),
70 Token::Struct {
71 name: "StatsMemory",
72 len: 4,
73 },
74 Token::Str("allocated"),
75 Token::U64(MEM_ALLOCATED),
76 Token::Str("free"),
77 Token::U64(MEM_FREE),
78 Token::Str("reservable"),
79 Token::U64(MEM_RESERVABLE),
80 Token::Str("used"),
81 Token::U64(MEM_USED),
82 Token::StructEnd,
83 Token::Str("op"),
84 Token::UnitVariant {
85 name: "Opcode",
86 variant: "stats",
87 },
88 Token::Str("players"),
89 Token::U64(0),
90 Token::Str("playingPlayers"),
91 Token::U64(0),
92 Token::Str("uptime"),
93 Token::U64(18589),
94 Token::StructEnd,
95 ],
96 );
97 }
98}
99
100#[cfg(test)]
101mod lavalink_incoming_model_tests {
102 use crate::model::{TrackEnd, TrackException, TrackStart, TrackStuck};
103 use twilight_model::id::{Id, marker::GuildMarker};
104
105 use super::{
106 WebSocketClosed,
107 incoming::{
108 Event, EventData, EventType, Exception, PlayerUpdate, PlayerUpdateState, Ready,
109 Severity, Stats, StatsCpu, StatsFrame, StatsMemory, Track, TrackEndReason, TrackInfo,
110 },
111 };
112
113 fn compare_json_payload<
115 T: std::fmt::Debug + for<'a> serde::Deserialize<'a> + std::cmp::PartialEq,
116 >(
117 data_struct: &T,
118 json_payload: &str,
119 ) {
120 let deserialized: T = serde_json::from_str(json_payload).unwrap();
122 assert_eq!(deserialized, *data_struct);
123 }
124
125 #[test]
126 fn should_deserialize_a_ready_response() {
127 let ready = Ready {
128 resumed: false,
129 session_id: "la3kfsdf5eafe848".to_string(),
130 };
131 compare_json_payload(
132 &ready,
133 r#"{"op":"ready","resumed":false,"sessionId":"la3kfsdf5eafe848"}"#,
134 );
135 }
136
137 #[test]
138 fn should_deserialize_a_player_update_response() {
139 let update = PlayerUpdate {
140 guild_id: Id::<GuildMarker>::new(987_654_321),
141 state: PlayerUpdateState {
142 time: 1_710_214_147_839,
143 position: 534,
144 connected: true,
145 ping: 0,
146 },
147 };
148 compare_json_payload(
149 &update,
150 r#"{"op":"playerUpdate","guildId":"987654321","state":{"time":1710214147839,"position":534,"connected":true,"ping":0}}"#,
151 );
152 }
153
154 #[test]
155 fn should_deserialize_stat_event() {
156 let stat_event = Stats {
157 players: 0,
158 playing_players: 0,
159 uptime: 1_139_738,
160 cpu: StatsCpu {
161 cores: 16,
162 lavalink_load: 3.497_090_420_769_919E-5,
163 system_load: 0.055_979_978_347_863_06,
164 },
165 frame_stats: None,
166 memory: StatsMemory {
167 allocated: 331_350_016,
168 free: 228_139_904,
169 reservable: 8_396_996_608,
170 used: 103_210_112,
171 },
172 };
173 compare_json_payload(
174 &stat_event.clone(),
175 r#"{"op":"stats","frameStats":null,"players":0,"playingPlayers":0,"uptime":1139738,"memory":{"free":228139904,"used":103210112,"allocated":331350016,"reservable":8396996608},"cpu":{"cores":16,"systemLoad":0.05597997834786306,"lavalinkLoad":3.497090420769919E-5}}"#,
176 );
177 }
178
179 #[test]
180 fn should_deserialize_stat_event_with_frame_stat() {
181 let stat_event = Stats {
182 players: 0,
183 playing_players: 0,
184 uptime: 1_139_738,
185 cpu: StatsCpu {
186 cores: 16,
187 lavalink_load: 3.497_090_420_769_919E-5,
188 system_load: 0.055_979_978_347_863_06,
189 },
190 frame_stats: Some(StatsFrame {
191 sent: 6000,
192 nulled: 10,
193 deficit: -3010,
194 }),
195 memory: StatsMemory {
196 allocated: 331_350_016,
197 free: 228_139_904,
198 reservable: 8_396_996_608,
199 used: 103_210_112,
200 },
201 };
202 compare_json_payload(
203 &stat_event.clone(),
204 r#"{"op":"stats","frameStats":{"sent":6000,"nulled":10,"deficit":-3010},"players":0,"playingPlayers":0,"uptime":1139738,"memory":{"free":228139904,"used":103210112,"allocated":331350016,"reservable":8396996608},"cpu":{"cores":16,"systemLoad":0.05597997834786306,"lavalinkLoad":3.497090420769919E-5}}"#,
205 );
206 }
207
208 #[test]
209 fn should_deserialize_track_start_event() {
210 let track_start_event = Event {
211 r#type: EventType::TrackStartEvent,
212 guild_id: Id::<GuildMarker>::new(987_654_321).to_string(),
213 data: EventData::TrackStartEvent(
214 TrackStart {
215 track: Track {
216 encoded: "QAAAzgMAMUJsZWVkIEl0IE91dCBbT2ZmaWNpYWwgTXVzaWMgVmlkZW9dIC0gTGlua2luIFBhcmsAC0xpbmtpbiBQYXJrAAAAAAAClCgAC09udXVZY3FoekNFAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9T251dVljcWh6Q0UBADRodHRwczovL2kueXRpbWcuY29tL3ZpL09udXVZY3FoekNFL21heHJlc2RlZmF1bHQuanBnAAAHeW91dHViZQAAAAAAAAAA".to_string(),
217 info: TrackInfo {
218 identifier: "OnuuYcqhzCE".to_string(),
219 is_seekable: true,
220 author: "Linkin Park".to_string(),
221 length: 169_000,
222 is_stream: false,
223 position: 0,
224 title: "Bleed It Out [Official Music Video] - Linkin Park".to_string(),
225 uri:Some("https://www.youtube.com/watch?v=OnuuYcqhzCE".to_string()),
226 source_name:"youtube".to_string(),
227 artwork_url:Some("https://i.ytimg.com/vi/OnuuYcqhzCE/maxresdefault.jpg".to_string()),
228 isrc: None
229 }
230 }
231 }
232 )
233
234 };
235 compare_json_payload(
236 &track_start_event.clone(),
237 r#"{"op":"event","guildId":"987654321","type":"TrackStartEvent","track":{"encoded":"QAAAzgMAMUJsZWVkIEl0IE91dCBbT2ZmaWNpYWwgTXVzaWMgVmlkZW9dIC0gTGlua2luIFBhcmsAC0xpbmtpbiBQYXJrAAAAAAAClCgAC09udXVZY3FoekNFAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9T251dVljcWh6Q0UBADRodHRwczovL2kueXRpbWcuY29tL3ZpL09udXVZY3FoekNFL21heHJlc2RlZmF1bHQuanBnAAAHeW91dHViZQAAAAAAAAAA","info":{"identifier":"OnuuYcqhzCE","isSeekable":true,"author":"Linkin Park","length":169000,"isStream":false,"position":0,"title":"Bleed It Out [Official Music Video] - Linkin Park","uri":"https://www.youtube.com/watch?v=OnuuYcqhzCE","artworkUrl":"https://i.ytimg.com/vi/OnuuYcqhzCE/maxresdefault.jpg","isrc":null,"sourceName":"youtube"},"pluginInfo":{},"userData":{}}}"#,
238 );
239 }
240
241 #[test]
242 fn should_deserialize_track_exception_event() {
243 let track_exception_event = Event {
244 r#type: EventType::TrackExceptionEvent,
245 guild_id: Id::<GuildMarker>::new(987_654_321).to_string(),
246 data: EventData::TrackExceptionEvent(
247 TrackException {
248 track: Track {
249 encoded: "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==".to_string(),
250 info: TrackInfo {
251 identifier: "dQw4w9WgXcQ".to_string(),
252 is_seekable: true,
253 author: "RickAstleyVEVO".to_string(),
254 length: 212_000,
255 is_stream: false,
256 position: 0,
257 title: "Rick Astley - Never Gonna Give You Up".to_string(),
258 uri:Some("https://www.youtube.com/watch?v=dQw4w9WgXcQ".to_string()),
259 source_name:"youtube".to_string(),
260 artwork_url:Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string()),
261 isrc: None
262 }
263 },
264 exception: Exception {
265 message: Some(String::new()),
266 severity: Severity::Common,
267 cause: "No video found.".to_string(),
268 cause_stack_trace: String::new(),
269 }
270
271 }
272 )
273
274 };
275 compare_json_payload(
276 &track_exception_event.clone(),
277 r#"{"op":"event","type":"TrackExceptionEvent","guildId":"987654321","track":{"encoded":"QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==","info":{"identifier":"dQw4w9WgXcQ","isSeekable":true,"author":"RickAstleyVEVO","length":212000,"isStream":false,"position":0,"title":"Rick Astley - Never Gonna Give You Up","uri":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","artworkUrl":"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg","isrc":null,"sourceName":"youtube"},"pluginInfo":{}},"exception":{"message":"","severity":"common","cause":"No video found.","causeStackTrace":""}}"#,
278 );
279 }
280
281 #[test]
282 fn should_deserialize_track_stuck_event() {
283 let track_stuck_event = Event {
284 r#type: EventType::TrackStuckEvent,
285 guild_id: Id::<GuildMarker>::new(987_654_321).to_string(),
286 data: EventData::TrackStuckEvent(
287 TrackStuck {
288 track: Track {
289 encoded: "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==".to_string(),
290 info: TrackInfo {
291 identifier: "dQw4w9WgXcQ".to_string(),
292 is_seekable: true,
293 author: "RickAstleyVEVO".to_string(),
294 length: 212_000,
295 is_stream: false,
296 position: 0,
297 title: "Rick Astley - Never Gonna Give You Up".to_string(),
298 uri:Some("https://www.youtube.com/watch?v=dQw4w9WgXcQ".to_string()),
299 source_name:"youtube".to_string(),
300 artwork_url:Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string()),
301 isrc: None
302 }
303 },
304 threshold_ms: 123_456_789,
305
306 }
307 )
308
309 };
310 compare_json_payload(
311 &track_stuck_event.clone(),
312 r#"{"op":"event","type":"TrackStuckEvent","guildId":"987654321","track":{"encoded":"QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==","info":{"identifier":"dQw4w9WgXcQ","isSeekable":true,"author":"RickAstleyVEVO","length":212000,"isStream":false,"position":0,"title":"Rick Astley - Never Gonna Give You Up","uri":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","artworkUrl":"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg","isrc":null,"sourceName":"youtube"},"pluginInfo":{}},"thresholdMs":123456789}"#,
313 );
314 }
315
316 #[test]
317 fn should_deserialize_track_end_event() {
318 let track_stuck_event = Event {
319 r#type: EventType::TrackEndEvent,
320 guild_id: Id::<GuildMarker>::new(987_654_321).to_string(),
321 data: EventData::TrackEndEvent(
322 TrackEnd {
323 track: Track {
324 encoded: "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==".to_string(),
325 info: TrackInfo {
326 identifier: "dQw4w9WgXcQ".to_string(),
327 is_seekable: true,
328 author: "RickAstleyVEVO".to_string(),
329 length: 212_000,
330 is_stream: false,
331 position: 0,
332 title: "Rick Astley - Never Gonna Give You Up".to_string(),
333 uri:Some("https://www.youtube.com/watch?v=dQw4w9WgXcQ".to_string()),
334 source_name:"youtube".to_string(),
335 artwork_url:Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string()),
336 isrc: None
337 }
338 },
339 reason: TrackEndReason::Finished,
340 }
341 )
342
343 };
344 compare_json_payload(
345 &track_stuck_event.clone(),
346 r#"{"op":"event","type":"TrackEndEvent","guildId":"987654321","track":{"encoded":"QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==","info":{"identifier":"dQw4w9WgXcQ","isSeekable":true,"author":"RickAstleyVEVO","length":212000,"isStream":false,"position":0,"title":"Rick Astley - Never Gonna Give You Up","uri":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","artworkUrl":"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg","isrc":null,"sourceName":"youtube"},"pluginInfo":{}},"reason":"finished"}"#,
347 );
348 }
349
350 #[test]
351 fn should_deserialize_websocketclosed_event() {
352 let websocket_closed_event = Event {
353 r#type: EventType::WebSocketClosedEvent,
354 guild_id: Id::<GuildMarker>::new(987_654_321).to_string(),
355 data: EventData::WebSocketClosedEvent(WebSocketClosed {
356 code: 1000,
357 reason: String::new(),
358 by_remote: false,
359 }),
360 };
361 compare_json_payload(
362 &websocket_closed_event.clone(),
363 r#"{"op":"event","type":"WebSocketClosedEvent","guildId":"987654321","code":1000,"reason":"","byRemote":false}"#,
364 );
365 }
366}
367
368#[cfg(test)]
369mod lavalink_outgoing_model_tests {
370 use crate::model::outgoing::TrackOption;
371 use crate::model::{Destroy, Equalizer, Pause, Play, Seek, Stop, Volume};
372
373 use twilight_model::id::{Id, marker::GuildMarker};
374
375 use super::EqualizerBand;
376 use super::outgoing::{OutgoingEvent, UpdatePlayerTrack, Voice, VoiceUpdate};
377
378 fn compare_json_payload<T: serde::Serialize + std::fmt::Debug + std::cmp::PartialEq>(
381 data_struct: &T,
382 json_payload: &str,
383 ) {
384 let serialized = serde_json::to_string(&data_struct).unwrap();
385 let expected_serialized = json_payload;
386 assert_eq!(serialized, expected_serialized);
387 }
388
389 #[test]
390 fn should_serialize_an_outgoing_voice_update() {
391 let voice = VoiceUpdate {
392 guild_id: Id::<GuildMarker>::new(987_654_321),
393 voice: Voice {
394 token: String::from("863ea8ef2ads8ef2"),
395 endpoint: String::from("eu-centra654863.discord.media:443"),
396 session_id: String::from("asdf5w1efa65feaf315e8a8effsa1e5f"),
397 },
398 };
399 compare_json_payload(
400 &voice,
401 r#"{"voice":{"endpoint":"eu-centra654863.discord.media:443","sessionId":"asdf5w1efa65feaf315e8a8effsa1e5f","token":"863ea8ef2ads8ef2"}}"#,
402 );
403 }
404
405 #[test]
406 fn should_serialize_an_outgoing_play() {
407 let play = OutgoingEvent::Play(Play{
408 track: Some(UpdatePlayerTrack {
409 track_string: TrackOption::Encoded(Some("QAAAzgMAMUJsZWVkIEl0IE91dCBbT2ZmaWNpYWwgTXVzaWMgVmlkZW9dIC0gTGlua2luIFBhcmsAC0xpbmtpbiBQYXJrAAAAAAAClCgAC09udXVZY3FoekNFAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9T251dVljcWh6Q0UBADRodHRwczovL2kueXRpbWcuY29tL3ZpL09udXVZY3FoekNFL21heHJlc2RlZmF1bHQuanBnAAAHeW91dHViZQAAAAAAAAAA".to_string())),
410 }),
411 position: None,
412 end_time: Some(None),
413 volume: None,
414 paused: None,
415 guild_id: Id::<GuildMarker>::new(987_654_321),
416 no_replace: true,
417 });
418 compare_json_payload(
419 &play,
420 r#"{"endTime":null,"track":{"encoded":"QAAAzgMAMUJsZWVkIEl0IE91dCBbT2ZmaWNpYWwgTXVzaWMgVmlkZW9dIC0gTGlua2luIFBhcmsAC0xpbmtpbiBQYXJrAAAAAAAClCgAC09udXVZY3FoekNFAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9T251dVljcWh6Q0UBADRodHRwczovL2kueXRpbWcuY29tL3ZpL09udXVZY3FoekNFL21heHJlc2RlZmF1bHQuanBnAAAHeW91dHViZQAAAAAAAAAA"}}"#,
421 );
422 }
423
424 #[test]
425 fn should_serialize_an_outgoing_stop() {
426 let stop = OutgoingEvent::Stop(Stop {
427 track: UpdatePlayerTrack {
428 track_string: TrackOption::Encoded(None),
429 },
430 guild_id: Id::<GuildMarker>::new(987_654_321),
431 });
432 compare_json_payload(&stop, r#"{"track":{"encoded":null}}"#);
433 }
434
435 #[test]
436 fn should_serialize_an_outgoing_pause() {
437 let pause = OutgoingEvent::Pause(Pause {
438 paused: true,
439 guild_id: Id::<GuildMarker>::new(987_654_321),
440 });
441 compare_json_payload(&pause, r#"{"guildId":"987654321","paused":true}"#);
442 }
443
444 #[test]
445 fn should_serialize_an_outgoing_seek() {
446 let seek = OutgoingEvent::Seek(Seek {
447 position: 66000,
448 guild_id: Id::<GuildMarker>::new(987_654_321),
449 });
450 compare_json_payload(&seek, r#"{"position":66000}"#);
451 }
452
453 #[test]
454 fn should_serialize_an_outgoing_volume() {
455 let volume = OutgoingEvent::Volume(Volume {
456 volume: 50,
457 guild_id: Id::<GuildMarker>::new(987_654_321),
458 });
459 compare_json_payload(&volume, r#"{"volume":50}"#);
460 }
461
462 #[test]
463 fn should_serialize_an_outgoing_destroy_aka_leave() {
464 let destroy = OutgoingEvent::Destroy(Destroy {
465 guild_id: Id::<GuildMarker>::new(987_654_321),
466 });
467 compare_json_payload(&destroy, r#"{"guildId":"987654321"}"#);
468 }
469
470 #[test]
471 fn should_serialize_an_outgoing_equalize() {
472 let equalize = OutgoingEvent::Equalizer(Equalizer {
473 equalizer: vec![EqualizerBand::new(5, -0.15)],
474 guild_id: Id::<GuildMarker>::new(987_654_321),
475 });
476 compare_json_payload(&equalize, r#"{"equalizer":[{"band":5,"gain":-0.15}]}"#);
477 }
478}