migration.rs (24477B)
1 use enostr::{NoteId, Pubkey}; 2 use nostrdb::Ndb; 3 use serde::{Deserialize, Deserializer}; 4 use tracing::error; 5 6 use crate::{ 7 accounts::AccountsRoute, 8 column::{Columns, IntermediaryRoute}, 9 route::Route, 10 timeline::{kind::ListKind, PubkeySource, Timeline, TimelineId, TimelineKind, TimelineRoute}, 11 ui::add_column::AddColumnRoute, 12 Result, 13 }; 14 15 use notedeck::{DataPath, DataPathType, Directory}; 16 17 pub static COLUMNS_FILE: &str = "columns.json"; 18 19 fn columns_json(path: &DataPath) -> Option<String> { 20 let data_path = path.path(DataPathType::Setting); 21 Directory::new(data_path) 22 .get_file(COLUMNS_FILE.to_string()) 23 .ok() 24 } 25 26 #[derive(Deserialize, Debug, PartialEq)] 27 enum MigrationTimelineRoute { 28 Timeline(u32), 29 Thread(String), 30 Profile(String), 31 Reply(String), 32 Quote(String), 33 } 34 35 impl MigrationTimelineRoute { 36 fn timeline_route(self) -> Option<TimelineRoute> { 37 match self { 38 MigrationTimelineRoute::Timeline(id) => { 39 Some(TimelineRoute::Timeline(TimelineId::new(id))) 40 } 41 MigrationTimelineRoute::Thread(note_id_hex) => { 42 Some(TimelineRoute::Thread(NoteId::from_hex(¬e_id_hex).ok()?)) 43 } 44 MigrationTimelineRoute::Profile(pubkey_hex) => { 45 Some(TimelineRoute::Profile(Pubkey::from_hex(&pubkey_hex).ok()?)) 46 } 47 MigrationTimelineRoute::Reply(note_id_hex) => { 48 Some(TimelineRoute::Reply(NoteId::from_hex(¬e_id_hex).ok()?)) 49 } 50 MigrationTimelineRoute::Quote(note_id_hex) => { 51 Some(TimelineRoute::Quote(NoteId::from_hex(¬e_id_hex).ok()?)) 52 } 53 } 54 } 55 } 56 57 #[derive(Deserialize, Debug, PartialEq)] 58 enum MigrationRoute { 59 Timeline(MigrationTimelineRoute), 60 Accounts(MigrationAccountsRoute), 61 Relays, 62 ComposeNote, 63 AddColumn(MigrationAddColumnRoute), 64 Support, 65 } 66 67 impl MigrationRoute { 68 fn route(self) -> Option<Route> { 69 match self { 70 MigrationRoute::Timeline(migration_timeline_route) => { 71 Some(Route::Timeline(migration_timeline_route.timeline_route()?)) 72 } 73 MigrationRoute::Accounts(migration_accounts_route) => { 74 Some(Route::Accounts(migration_accounts_route.accounts_route())) 75 } 76 MigrationRoute::Relays => Some(Route::Relays), 77 MigrationRoute::ComposeNote => Some(Route::ComposeNote), 78 MigrationRoute::AddColumn(migration_add_column_route) => Some(Route::AddColumn( 79 migration_add_column_route.add_column_route(), 80 )), 81 MigrationRoute::Support => Some(Route::Support), 82 } 83 } 84 } 85 86 #[derive(Deserialize, Debug, PartialEq)] 87 enum MigrationAccountsRoute { 88 Accounts, 89 AddAccount, 90 } 91 92 impl MigrationAccountsRoute { 93 fn accounts_route(self) -> AccountsRoute { 94 match self { 95 MigrationAccountsRoute::Accounts => AccountsRoute::Accounts, 96 MigrationAccountsRoute::AddAccount => AccountsRoute::AddAccount, 97 } 98 } 99 } 100 101 #[derive(Deserialize, Debug, PartialEq)] 102 enum MigrationAddColumnRoute { 103 Base, 104 UndecidedNotification, 105 ExternalNotification, 106 Hashtag, 107 } 108 109 impl MigrationAddColumnRoute { 110 fn add_column_route(self) -> AddColumnRoute { 111 match self { 112 MigrationAddColumnRoute::Base => AddColumnRoute::Base, 113 MigrationAddColumnRoute::UndecidedNotification => AddColumnRoute::UndecidedNotification, 114 MigrationAddColumnRoute::ExternalNotification => AddColumnRoute::ExternalNotification, 115 MigrationAddColumnRoute::Hashtag => AddColumnRoute::Hashtag, 116 } 117 } 118 } 119 120 #[derive(Debug, PartialEq)] 121 struct MigrationColumn { 122 routes: Vec<MigrationRoute>, 123 } 124 125 impl<'de> Deserialize<'de> for MigrationColumn { 126 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> 127 where 128 D: Deserializer<'de>, 129 { 130 let routes = Vec::<MigrationRoute>::deserialize(deserializer)?; 131 132 Ok(MigrationColumn { routes }) 133 } 134 } 135 136 #[derive(Deserialize, Debug)] 137 struct MigrationColumns { 138 columns: Vec<MigrationColumn>, 139 timelines: Vec<MigrationTimeline>, 140 } 141 142 #[derive(Deserialize, Debug, Clone, PartialEq)] 143 struct MigrationTimeline { 144 id: u32, 145 kind: MigrationTimelineKind, 146 } 147 148 impl MigrationTimeline { 149 fn into_timeline(self, ndb: &Ndb, deck_user_pubkey: Option<&[u8; 32]>) -> Option<Timeline> { 150 self.kind 151 .into_timeline_kind()? 152 .into_timeline(ndb, deck_user_pubkey) 153 } 154 } 155 156 #[derive(Deserialize, Clone, Debug, PartialEq)] 157 enum MigrationListKind { 158 Contact(MigrationPubkeySource), 159 } 160 161 impl MigrationListKind { 162 fn list_kind(self) -> Option<ListKind> { 163 match self { 164 MigrationListKind::Contact(migration_pubkey_source) => { 165 Some(ListKind::Contact(migration_pubkey_source.pubkey_source()?)) 166 } 167 } 168 } 169 } 170 171 #[derive(Deserialize, Clone, Debug, PartialEq)] 172 enum MigrationPubkeySource { 173 Explicit(String), 174 DeckAuthor, 175 } 176 177 impl MigrationPubkeySource { 178 fn pubkey_source(self) -> Option<PubkeySource> { 179 match self { 180 MigrationPubkeySource::Explicit(hex) => { 181 Some(PubkeySource::Explicit(Pubkey::from_hex(hex.as_str()).ok()?)) 182 } 183 MigrationPubkeySource::DeckAuthor => Some(PubkeySource::DeckAuthor), 184 } 185 } 186 } 187 188 #[derive(Deserialize, Clone, Debug, PartialEq)] 189 enum MigrationTimelineKind { 190 List(MigrationListKind), 191 Notifications(MigrationPubkeySource), 192 Profile(MigrationPubkeySource), 193 Universe, 194 Generic, 195 Hashtag(String), 196 } 197 198 impl MigrationTimelineKind { 199 fn into_timeline_kind(self) -> Option<TimelineKind> { 200 match self { 201 MigrationTimelineKind::List(migration_list_kind) => { 202 Some(TimelineKind::List(migration_list_kind.list_kind()?)) 203 } 204 MigrationTimelineKind::Notifications(migration_pubkey_source) => Some( 205 TimelineKind::Notifications(migration_pubkey_source.pubkey_source()?), 206 ), 207 MigrationTimelineKind::Profile(migration_pubkey_source) => Some(TimelineKind::Profile( 208 migration_pubkey_source.pubkey_source()?, 209 )), 210 MigrationTimelineKind::Universe => Some(TimelineKind::Universe), 211 MigrationTimelineKind::Generic => Some(TimelineKind::Generic), 212 MigrationTimelineKind::Hashtag(hashtag) => Some(TimelineKind::Hashtag(hashtag)), 213 } 214 } 215 } 216 217 impl MigrationColumns { 218 fn into_columns(self, ndb: &Ndb, deck_pubkey: Option<&[u8; 32]>) -> Columns { 219 let mut columns = Columns::default(); 220 221 for column in self.columns { 222 let mut cur_routes = Vec::new(); 223 for route in column.routes { 224 match route { 225 MigrationRoute::Timeline(MigrationTimelineRoute::Timeline(timeline_id)) => { 226 if let Some(migration_tl) = 227 self.timelines.iter().find(|tl| tl.id == timeline_id) 228 { 229 let tl = migration_tl.clone().into_timeline(ndb, deck_pubkey); 230 if let Some(tl) = tl { 231 cur_routes.push(IntermediaryRoute::Timeline(tl)); 232 } else { 233 error!("Problem deserializing timeline {:?}", migration_tl); 234 } 235 } 236 } 237 MigrationRoute::Timeline(MigrationTimelineRoute::Thread(_thread)) => {} 238 _ => { 239 if let Some(route) = route.route() { 240 cur_routes.push(IntermediaryRoute::Route(route)); 241 } 242 } 243 } 244 } 245 if !cur_routes.is_empty() { 246 columns.insert_intermediary_routes(cur_routes); 247 } 248 } 249 columns 250 } 251 } 252 253 fn string_to_columns( 254 serialized_columns: String, 255 ndb: &Ndb, 256 user: Option<&[u8; 32]>, 257 ) -> Option<Columns> { 258 Some( 259 deserialize_columns_string(serialized_columns) 260 .ok()? 261 .into_columns(ndb, user), 262 ) 263 } 264 265 pub fn deserialize_columns(path: &DataPath, ndb: &Ndb, user: Option<&[u8; 32]>) -> Option<Columns> { 266 string_to_columns(columns_json(path)?, ndb, user) 267 } 268 269 fn deserialize_columns_string(serialized_columns: String) -> Result<MigrationColumns> { 270 Ok( 271 serde_json::from_str::<MigrationColumns>(&serialized_columns) 272 .map_err(notedeck::Error::Json)?, 273 ) 274 } 275 276 #[cfg(test)] 277 mod tests { 278 use crate::storage::migration::{ 279 MigrationColumn, MigrationListKind, MigrationPubkeySource, MigrationRoute, 280 MigrationTimeline, MigrationTimelineKind, MigrationTimelineRoute, 281 }; 282 283 impl MigrationColumn { 284 fn from_route(route: MigrationRoute) -> Self { 285 Self { 286 routes: vec![route], 287 } 288 } 289 290 fn from_routes(routes: Vec<MigrationRoute>) -> Self { 291 Self { routes } 292 } 293 } 294 295 impl MigrationTimeline { 296 fn new(id: u32, kind: MigrationTimelineKind) -> Self { 297 Self { id, kind } 298 } 299 } 300 301 use super::*; 302 303 #[test] 304 fn multi_column() { 305 let route = r#"{"columns":[[{"Timeline":{"Timeline":2}}],[{"Timeline":{"Timeline":0}}],[{"Timeline":{"Timeline":1}}]],"timelines":[{"id":0,"kind":{"List":{"Contact":{"Explicit":"aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe"}}}},{"id":1,"kind":{"Hashtag":"introductions"}},{"id":2,"kind":"Universe"}]}"#; // Multi-column 306 307 let deserialized_columns = deserialize_columns_string(route.to_string()); 308 assert!(deserialized_columns.is_ok()); 309 310 let migration_cols = deserialized_columns.unwrap(); 311 312 assert_eq!(migration_cols.columns.len(), 3); 313 assert_eq!( 314 *migration_cols.columns.first().unwrap(), 315 MigrationColumn::from_route(MigrationRoute::Timeline( 316 MigrationTimelineRoute::Timeline(2) 317 )) 318 ); 319 320 assert_eq!( 321 *migration_cols.columns.get(1).unwrap(), 322 MigrationColumn::from_route(MigrationRoute::Timeline( 323 MigrationTimelineRoute::Timeline(0) 324 )) 325 ); 326 327 assert_eq!( 328 *migration_cols.columns.get(2).unwrap(), 329 MigrationColumn::from_route(MigrationRoute::Timeline( 330 MigrationTimelineRoute::Timeline(1) 331 )) 332 ); 333 334 assert_eq!(migration_cols.timelines.len(), 3); 335 assert_eq!( 336 *migration_cols.timelines.first().unwrap(), 337 MigrationTimeline::new( 338 0, 339 MigrationTimelineKind::List(MigrationListKind::Contact( 340 MigrationPubkeySource::Explicit( 341 "aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe" 342 .to_owned() 343 ) 344 )) 345 ) 346 ); 347 assert_eq!( 348 *migration_cols.timelines.get(1).unwrap(), 349 MigrationTimeline::new( 350 1, 351 MigrationTimelineKind::Hashtag("introductions".to_owned()) 352 ) 353 ); 354 355 assert_eq!( 356 *migration_cols.timelines.get(2).unwrap(), 357 MigrationTimeline::new(2, MigrationTimelineKind::Universe) 358 ) 359 } 360 361 #[test] 362 fn base() { 363 let route = r#"{"columns":[[{"AddColumn":"Base"}]],"timelines":[]}"#; 364 365 let deserialized_columns = deserialize_columns_string(route.to_string()); 366 assert!(deserialized_columns.is_ok()); 367 368 let migration_cols = deserialized_columns.unwrap(); 369 assert_eq!(migration_cols.columns.len(), 1); 370 assert_eq!( 371 *migration_cols.columns.first().unwrap(), 372 MigrationColumn::from_route(MigrationRoute::AddColumn(MigrationAddColumnRoute::Base)) 373 ); 374 375 assert!(migration_cols.timelines.is_empty()); 376 } 377 378 #[test] 379 fn universe() { 380 let route = r#"{"columns":[[{"Timeline":{"Timeline":0}}]],"timelines":[{"id":0,"kind":"Universe"}]}"#; 381 let deserialized_columns = deserialize_columns_string(route.to_string()); 382 assert!(deserialized_columns.is_ok()); 383 384 let migration_cols = deserialized_columns.unwrap(); 385 assert_eq!(migration_cols.columns.len(), 1); 386 assert_eq!( 387 *migration_cols.columns.first().unwrap(), 388 MigrationColumn::from_route(MigrationRoute::Timeline( 389 MigrationTimelineRoute::Timeline(0) 390 )) 391 ); 392 393 assert_eq!(migration_cols.timelines.len(), 1); 394 assert_eq!( 395 *migration_cols.timelines.first().unwrap(), 396 MigrationTimeline::new(0, MigrationTimelineKind::Universe) 397 ) 398 } 399 400 #[test] 401 fn home() { 402 let route = r#"{"columns":[[{"Timeline":{"Timeline":2}}]],"timelines":[{"id":2,"kind":{"List":{"Contact":{"Explicit":"aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe"}}}}]}"#; 403 404 let deserialized_columns = deserialize_columns_string(route.to_string()); 405 assert!(deserialized_columns.is_ok()); 406 407 let migration_cols = deserialized_columns.unwrap(); 408 assert_eq!(migration_cols.columns.len(), 1); 409 assert_eq!( 410 *migration_cols.columns.first().unwrap(), 411 MigrationColumn::from_route(MigrationRoute::Timeline( 412 MigrationTimelineRoute::Timeline(2) 413 )) 414 ); 415 416 assert_eq!(migration_cols.timelines.len(), 1); 417 assert_eq!( 418 *migration_cols.timelines.first().unwrap(), 419 MigrationTimeline::new( 420 2, 421 MigrationTimelineKind::List(MigrationListKind::Contact( 422 MigrationPubkeySource::Explicit( 423 "aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe" 424 .to_owned() 425 ) 426 )) 427 ) 428 ) 429 } 430 431 #[test] 432 fn thread() { 433 let route = r#"{"columns":[[{"Timeline":{"Timeline":7}},{"Timeline":{"Thread":"fb9b0c62bc91bbe28ca428fc85e310ae38795b94fb910e0f4e12962ced971f25"}}]],"timelines":[{"id":7,"kind":{"List":{"Contact":{"Explicit":"4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"}}}}]}"#; 434 435 let deserialized_columns = deserialize_columns_string(route.to_string()); 436 assert!(deserialized_columns.is_ok()); 437 438 let migration_cols = deserialized_columns.unwrap(); 439 assert_eq!(migration_cols.columns.len(), 1); 440 assert_eq!( 441 *migration_cols.columns.first().unwrap(), 442 MigrationColumn::from_routes(vec![ 443 MigrationRoute::Timeline(MigrationTimelineRoute::Timeline(7),), 444 MigrationRoute::Timeline(MigrationTimelineRoute::Thread( 445 "fb9b0c62bc91bbe28ca428fc85e310ae38795b94fb910e0f4e12962ced971f25".to_owned() 446 )), 447 ]) 448 ); 449 450 assert_eq!(migration_cols.timelines.len(), 1); 451 assert_eq!( 452 *migration_cols.timelines.first().unwrap(), 453 MigrationTimeline::new( 454 7, 455 MigrationTimelineKind::List(MigrationListKind::Contact( 456 MigrationPubkeySource::Explicit( 457 "4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967" 458 .to_owned() 459 ) 460 )) 461 ) 462 ) 463 } 464 465 #[test] 466 fn profile() { 467 let route = r#"{"columns":[[{"Timeline":{"Timeline":7}},{"Timeline":{"Profile":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"}}]],"timelines":[{"id":7,"kind":{"List":{"Contact":{"Explicit":"4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"}}}}]}"#; 468 469 let deserialized_columns = deserialize_columns_string(route.to_string()); 470 assert!(deserialized_columns.is_ok()); 471 472 let migration_cols = deserialized_columns.unwrap(); 473 assert_eq!(migration_cols.columns.len(), 1); 474 assert_eq!( 475 *migration_cols.columns.first().unwrap(), 476 MigrationColumn::from_routes(vec![ 477 MigrationRoute::Timeline(MigrationTimelineRoute::Timeline(7),), 478 MigrationRoute::Timeline(MigrationTimelineRoute::Profile( 479 "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_owned() 480 )), 481 ]) 482 ); 483 484 assert_eq!(migration_cols.timelines.len(), 1); 485 assert_eq!( 486 *migration_cols.timelines.first().unwrap(), 487 MigrationTimeline::new( 488 7, 489 MigrationTimelineKind::List(MigrationListKind::Contact( 490 MigrationPubkeySource::Explicit( 491 "4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967" 492 .to_owned() 493 ) 494 )) 495 ) 496 ) 497 } 498 499 #[test] 500 fn your_notifs() { 501 let route = r#"{"columns":[[{"Timeline":{"Timeline":5}}]],"timelines":[{"id":5,"kind":{"Notifications":"DeckAuthor"}}]}"#; 502 503 let deserialized_columns = deserialize_columns_string(route.to_string()); 504 assert!(deserialized_columns.is_ok()); 505 506 let migration_cols = deserialized_columns.unwrap(); 507 assert_eq!(migration_cols.columns.len(), 1); 508 assert_eq!( 509 *migration_cols.columns.first().unwrap(), 510 MigrationColumn::from_route(MigrationRoute::Timeline( 511 MigrationTimelineRoute::Timeline(5) 512 )) 513 ); 514 515 assert_eq!(migration_cols.timelines.len(), 1); 516 assert_eq!( 517 *migration_cols.timelines.first().unwrap(), 518 MigrationTimeline::new( 519 5, 520 MigrationTimelineKind::Notifications(MigrationPubkeySource::DeckAuthor) 521 ) 522 ) 523 } 524 525 #[test] 526 fn undecided_notifs() { 527 let route = r#"{"columns":[[{"AddColumn":"Base"},{"AddColumn":"UndecidedNotification"}]],"timelines":[]}"#; 528 529 let deserialized_columns = deserialize_columns_string(route.to_string()); 530 assert!(deserialized_columns.is_ok()); 531 532 let migration_cols = deserialized_columns.unwrap(); 533 assert_eq!(migration_cols.columns.len(), 1); 534 assert_eq!( 535 *migration_cols.columns.first().unwrap(), 536 MigrationColumn::from_routes(vec![ 537 MigrationRoute::AddColumn(MigrationAddColumnRoute::Base), 538 MigrationRoute::AddColumn(MigrationAddColumnRoute::UndecidedNotification), 539 ]) 540 ); 541 542 assert!(migration_cols.timelines.is_empty()); 543 } 544 545 #[test] 546 fn extern_notifs() { 547 let route = r#"{"columns":[[{"Timeline":{"Timeline":4}}]],"timelines":[{"id":4,"kind":{"Notifications":{"Explicit":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"}}}]}"#; 548 549 let deserialized_columns = deserialize_columns_string(route.to_string()); 550 assert!(deserialized_columns.is_ok()); 551 552 let migration_cols = deserialized_columns.unwrap(); 553 assert_eq!(migration_cols.columns.len(), 1); 554 assert_eq!( 555 *migration_cols.columns.first().unwrap(), 556 MigrationColumn::from_route(MigrationRoute::Timeline( 557 MigrationTimelineRoute::Timeline(4) 558 )) 559 ); 560 561 assert_eq!(migration_cols.timelines.len(), 1); 562 assert_eq!( 563 *migration_cols.timelines.first().unwrap(), 564 MigrationTimeline::new( 565 4, 566 MigrationTimelineKind::Notifications(MigrationPubkeySource::Explicit( 567 "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_owned() 568 )) 569 ) 570 ) 571 } 572 573 #[test] 574 fn hashtag() { 575 let route = r#"{"columns":[[{"Timeline":{"Timeline":6}}]],"timelines":[{"id":6,"kind":{"Hashtag":"notedeck"}}]}"#; 576 577 let deserialized_columns = deserialize_columns_string(route.to_string()); 578 assert!(deserialized_columns.is_ok()); 579 580 let migration_cols = deserialized_columns.unwrap(); 581 assert_eq!(migration_cols.columns.len(), 1); 582 assert_eq!( 583 *migration_cols.columns.first().unwrap(), 584 MigrationColumn::from_route(MigrationRoute::Timeline( 585 MigrationTimelineRoute::Timeline(6) 586 )) 587 ); 588 589 assert_eq!(migration_cols.timelines.len(), 1); 590 assert_eq!( 591 *migration_cols.timelines.first().unwrap(), 592 MigrationTimeline::new(6, MigrationTimelineKind::Hashtag("notedeck".to_owned())) 593 ) 594 } 595 596 #[test] 597 fn support() { 598 let route = r#"{"columns":[[{"AddColumn":"Base"},"Support"]],"timelines":[]}"#; 599 600 let deserialized_columns = deserialize_columns_string(route.to_string()); 601 assert!(deserialized_columns.is_ok()); 602 603 let migration_cols = deserialized_columns.unwrap(); 604 assert_eq!(migration_cols.columns.len(), 1); 605 assert_eq!( 606 *migration_cols.columns.first().unwrap(), 607 MigrationColumn::from_routes(vec![ 608 MigrationRoute::AddColumn(MigrationAddColumnRoute::Base), 609 MigrationRoute::Support 610 ]) 611 ); 612 613 assert!(migration_cols.timelines.is_empty()); 614 } 615 616 #[test] 617 fn post() { 618 let route = r#"{"columns":[[{"AddColumn":"Base"},"ComposeNote"]],"timelines":[]}"#; 619 620 let deserialized_columns = deserialize_columns_string(route.to_string()); 621 assert!(deserialized_columns.is_ok()); 622 623 let migration_cols = deserialized_columns.unwrap(); 624 assert_eq!(migration_cols.columns.len(), 1); 625 assert_eq!( 626 *migration_cols.columns.first().unwrap(), 627 MigrationColumn::from_routes(vec![ 628 MigrationRoute::AddColumn(MigrationAddColumnRoute::Base), 629 MigrationRoute::ComposeNote 630 ]) 631 ); 632 633 assert!(migration_cols.timelines.is_empty()); 634 } 635 636 #[test] 637 fn relay() { 638 let route = r#"{"columns":[[{"AddColumn":"Base"},"Relays"]],"timelines":[]}"#; 639 640 let deserialized_columns = deserialize_columns_string(route.to_string()); 641 assert!(deserialized_columns.is_ok()); 642 643 let migration_cols = deserialized_columns.unwrap(); 644 assert_eq!(migration_cols.columns.len(), 1); 645 assert_eq!( 646 *migration_cols.columns.first().unwrap(), 647 MigrationColumn::from_routes(vec![ 648 MigrationRoute::AddColumn(MigrationAddColumnRoute::Base), 649 MigrationRoute::Relays 650 ]) 651 ); 652 653 assert!(migration_cols.timelines.is_empty()); 654 } 655 656 #[test] 657 fn accounts() { 658 let route = 659 r#"{"columns":[[{"AddColumn":"Base"},{"Accounts":"Accounts"}]],"timelines":[]}"#; 660 661 let deserialized_columns = deserialize_columns_string(route.to_string()); 662 assert!(deserialized_columns.is_ok()); 663 664 let migration_cols = deserialized_columns.unwrap(); 665 assert_eq!(migration_cols.columns.len(), 1); 666 assert_eq!( 667 *migration_cols.columns.first().unwrap(), 668 MigrationColumn::from_routes(vec![ 669 MigrationRoute::AddColumn(MigrationAddColumnRoute::Base), 670 MigrationRoute::Accounts(MigrationAccountsRoute::Accounts), 671 ]) 672 ); 673 674 assert!(migration_cols.timelines.is_empty()); 675 } 676 677 #[test] 678 fn login() { 679 let route = r#"{"columns":[[{"AddColumn":"Base"},{"Accounts":"Accounts"},{"Accounts":"AddAccount"}]],"timelines":[]}"#; 680 681 let deserialized_columns = deserialize_columns_string(route.to_string()); 682 assert!(deserialized_columns.is_ok()); 683 684 let migration_cols = deserialized_columns.unwrap(); 685 assert_eq!(migration_cols.columns.len(), 1); 686 assert_eq!( 687 *migration_cols.columns.first().unwrap(), 688 MigrationColumn::from_routes(vec![ 689 MigrationRoute::AddColumn(MigrationAddColumnRoute::Base), 690 MigrationRoute::Accounts(MigrationAccountsRoute::Accounts), 691 MigrationRoute::Accounts(MigrationAccountsRoute::AddAccount), 692 ]) 693 ); 694 695 assert!(migration_cols.timelines.is_empty()); 696 } 697 }