commit dbddb3a3f2e945268fb41cb951a7d7fb8aaaa14f
parent 213332ee712b57ecdc5f0bb5e97d530c1ba8f434
Author: kernelkind <kernelkind@gmail.com>
Date: Mon, 9 Dec 2024 21:28:57 -0500
add columns.json -> DecksCache migration
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
1 file changed, 695 insertions(+), 0 deletions(-)
diff --git a/src/storage/migration.rs b/src/storage/migration.rs
@@ -0,0 +1,695 @@
+use enostr::{NoteId, Pubkey};
+use nostrdb::Ndb;
+use serde::{Deserialize, Deserializer};
+use tracing::error;
+
+use crate::{
+ accounts::AccountsRoute,
+ column::{Columns, IntermediaryRoute},
+ route::Route,
+ timeline::{kind::ListKind, PubkeySource, Timeline, TimelineId, TimelineKind, TimelineRoute},
+ ui::add_column::AddColumnRoute,
+ Error,
+};
+
+use super::{DataPath, DataPathType, Directory};
+
+pub static COLUMNS_FILE: &str = "columns.json";
+
+fn columns_json(path: &DataPath) -> Option<String> {
+ let data_path = path.path(DataPathType::Setting);
+ Directory::new(data_path)
+ .get_file(COLUMNS_FILE.to_string())
+ .ok()
+}
+
+#[derive(Deserialize, Debug, PartialEq)]
+enum MigrationTimelineRoute {
+ Timeline(u32),
+ Thread(String),
+ Profile(String),
+ Reply(String),
+ Quote(String),
+}
+
+impl MigrationTimelineRoute {
+ fn timeline_route(self) -> Option<TimelineRoute> {
+ match self {
+ MigrationTimelineRoute::Timeline(id) => {
+ Some(TimelineRoute::Timeline(TimelineId::new(id)))
+ }
+ MigrationTimelineRoute::Thread(note_id_hex) => {
+ Some(TimelineRoute::Thread(NoteId::from_hex(¬e_id_hex).ok()?))
+ }
+ MigrationTimelineRoute::Profile(pubkey_hex) => {
+ Some(TimelineRoute::Profile(Pubkey::from_hex(&pubkey_hex).ok()?))
+ }
+ MigrationTimelineRoute::Reply(note_id_hex) => {
+ Some(TimelineRoute::Reply(NoteId::from_hex(¬e_id_hex).ok()?))
+ }
+ MigrationTimelineRoute::Quote(note_id_hex) => {
+ Some(TimelineRoute::Quote(NoteId::from_hex(¬e_id_hex).ok()?))
+ }
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, PartialEq)]
+enum MigrationRoute {
+ Timeline(MigrationTimelineRoute),
+ Accounts(MigrationAccountsRoute),
+ Relays,
+ ComposeNote,
+ AddColumn(MigrationAddColumnRoute),
+ Support,
+}
+
+impl MigrationRoute {
+ fn route(self) -> Option<Route> {
+ match self {
+ MigrationRoute::Timeline(migration_timeline_route) => {
+ Some(Route::Timeline(migration_timeline_route.timeline_route()?))
+ }
+ MigrationRoute::Accounts(migration_accounts_route) => {
+ Some(Route::Accounts(migration_accounts_route.accounts_route()))
+ }
+ MigrationRoute::Relays => Some(Route::Relays),
+ MigrationRoute::ComposeNote => Some(Route::ComposeNote),
+ MigrationRoute::AddColumn(migration_add_column_route) => Some(Route::AddColumn(
+ migration_add_column_route.add_column_route(),
+ )),
+ MigrationRoute::Support => Some(Route::Support),
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, PartialEq)]
+enum MigrationAccountsRoute {
+ Accounts,
+ AddAccount,
+}
+
+impl MigrationAccountsRoute {
+ fn accounts_route(self) -> AccountsRoute {
+ match self {
+ MigrationAccountsRoute::Accounts => AccountsRoute::Accounts,
+ MigrationAccountsRoute::AddAccount => AccountsRoute::AddAccount,
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, PartialEq)]
+enum MigrationAddColumnRoute {
+ Base,
+ UndecidedNotification,
+ ExternalNotification,
+ Hashtag,
+}
+
+impl MigrationAddColumnRoute {
+ fn add_column_route(self) -> AddColumnRoute {
+ match self {
+ MigrationAddColumnRoute::Base => AddColumnRoute::Base,
+ MigrationAddColumnRoute::UndecidedNotification => AddColumnRoute::UndecidedNotification,
+ MigrationAddColumnRoute::ExternalNotification => AddColumnRoute::ExternalNotification,
+ MigrationAddColumnRoute::Hashtag => AddColumnRoute::Hashtag,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+struct MigrationColumn {
+ routes: Vec<MigrationRoute>,
+}
+
+impl<'de> Deserialize<'de> for MigrationColumn {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let routes = Vec::<MigrationRoute>::deserialize(deserializer)?;
+
+ Ok(MigrationColumn { routes })
+ }
+}
+
+#[derive(Deserialize, Debug)]
+struct MigrationColumns {
+ columns: Vec<MigrationColumn>,
+ timelines: Vec<MigrationTimeline>,
+}
+
+#[derive(Deserialize, Debug, Clone, PartialEq)]
+struct MigrationTimeline {
+ id: u32,
+ kind: MigrationTimelineKind,
+}
+
+impl MigrationTimeline {
+ fn into_timeline(self, ndb: &Ndb, deck_user_pubkey: Option<&[u8; 32]>) -> Option<Timeline> {
+ self.kind
+ .into_timeline_kind()?
+ .into_timeline(ndb, deck_user_pubkey)
+ }
+}
+
+#[derive(Deserialize, Clone, Debug, PartialEq)]
+enum MigrationListKind {
+ Contact(MigrationPubkeySource),
+}
+
+impl MigrationListKind {
+ fn list_kind(self) -> Option<ListKind> {
+ match self {
+ MigrationListKind::Contact(migration_pubkey_source) => {
+ Some(ListKind::Contact(migration_pubkey_source.pubkey_source()?))
+ }
+ }
+ }
+}
+
+#[derive(Deserialize, Clone, Debug, PartialEq)]
+enum MigrationPubkeySource {
+ Explicit(String),
+ DeckAuthor,
+}
+
+impl MigrationPubkeySource {
+ fn pubkey_source(self) -> Option<PubkeySource> {
+ match self {
+ MigrationPubkeySource::Explicit(hex) => {
+ Some(PubkeySource::Explicit(Pubkey::from_hex(hex.as_str()).ok()?))
+ }
+ MigrationPubkeySource::DeckAuthor => Some(PubkeySource::DeckAuthor),
+ }
+ }
+}
+
+#[derive(Deserialize, Clone, Debug, PartialEq)]
+enum MigrationTimelineKind {
+ List(MigrationListKind),
+ Notifications(MigrationPubkeySource),
+ Profile(MigrationPubkeySource),
+ Universe,
+ Generic,
+ Hashtag(String),
+}
+
+impl MigrationTimelineKind {
+ fn into_timeline_kind(self) -> Option<TimelineKind> {
+ match self {
+ MigrationTimelineKind::List(migration_list_kind) => {
+ Some(TimelineKind::List(migration_list_kind.list_kind()?))
+ }
+ MigrationTimelineKind::Notifications(migration_pubkey_source) => Some(
+ TimelineKind::Notifications(migration_pubkey_source.pubkey_source()?),
+ ),
+ MigrationTimelineKind::Profile(migration_pubkey_source) => Some(TimelineKind::Profile(
+ migration_pubkey_source.pubkey_source()?,
+ )),
+ MigrationTimelineKind::Universe => Some(TimelineKind::Universe),
+ MigrationTimelineKind::Generic => Some(TimelineKind::Generic),
+ MigrationTimelineKind::Hashtag(hashtag) => Some(TimelineKind::Hashtag(hashtag)),
+ }
+ }
+}
+
+impl MigrationColumns {
+ fn into_columns(self, ndb: &Ndb, deck_pubkey: Option<&[u8; 32]>) -> Columns {
+ let mut columns = Columns::default();
+
+ for column in self.columns {
+ let mut cur_routes = Vec::new();
+ for route in column.routes {
+ match route {
+ MigrationRoute::Timeline(MigrationTimelineRoute::Timeline(timeline_id)) => {
+ if let Some(migration_tl) =
+ self.timelines.iter().find(|tl| tl.id == timeline_id)
+ {
+ let tl = migration_tl.clone().into_timeline(ndb, deck_pubkey);
+ if let Some(tl) = tl {
+ cur_routes.push(IntermediaryRoute::Timeline(tl));
+ } else {
+ error!("Problem deserializing timeline {:?}", migration_tl);
+ }
+ }
+ }
+ MigrationRoute::Timeline(MigrationTimelineRoute::Thread(_thread)) => {}
+ _ => {
+ if let Some(route) = route.route() {
+ cur_routes.push(IntermediaryRoute::Route(route));
+ }
+ }
+ }
+ }
+ if !cur_routes.is_empty() {
+ columns.insert_intermediary_routes(cur_routes);
+ }
+ }
+ columns
+ }
+}
+
+fn string_to_columns(
+ serialized_columns: String,
+ ndb: &Ndb,
+ user: Option<&[u8; 32]>,
+) -> Option<Columns> {
+ Some(
+ deserialize_columns_string(serialized_columns)
+ .ok()?
+ .into_columns(ndb, user),
+ )
+}
+
+pub fn deserialize_columns(path: &DataPath, ndb: &Ndb, user: Option<&[u8; 32]>) -> Option<Columns> {
+ string_to_columns(columns_json(path)?, ndb, user)
+}
+
+fn deserialize_columns_string(serialized_columns: String) -> Result<MigrationColumns, Error> {
+ serde_json::from_str::<MigrationColumns>(&serialized_columns)
+ .map_err(|e| Error::Generic(e.to_string()))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::storage::migration::{
+ MigrationColumn, MigrationListKind, MigrationPubkeySource, MigrationRoute,
+ MigrationTimeline, MigrationTimelineKind, MigrationTimelineRoute,
+ };
+
+ impl MigrationColumn {
+ fn from_route(route: MigrationRoute) -> Self {
+ Self {
+ routes: vec![route],
+ }
+ }
+
+ fn from_routes(routes: Vec<MigrationRoute>) -> Self {
+ Self { routes }
+ }
+ }
+
+ impl MigrationTimeline {
+ fn new(id: u32, kind: MigrationTimelineKind) -> Self {
+ Self { id, kind }
+ }
+ }
+
+ use super::*;
+
+ #[test]
+ fn multi_column() {
+ 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
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+
+ assert_eq!(migration_cols.columns.len(), 3);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(2)
+ ))
+ );
+
+ assert_eq!(
+ *migration_cols.columns.get(1).unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(0)
+ ))
+ );
+
+ assert_eq!(
+ *migration_cols.columns.get(2).unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(1)
+ ))
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 3);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(
+ 0,
+ MigrationTimelineKind::List(MigrationListKind::Contact(
+ MigrationPubkeySource::Explicit(
+ "aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe"
+ .to_owned()
+ )
+ ))
+ )
+ );
+ assert_eq!(
+ *migration_cols.timelines.get(1).unwrap(),
+ MigrationTimeline::new(
+ 1,
+ MigrationTimelineKind::Hashtag("introductions".to_owned())
+ )
+ );
+
+ assert_eq!(
+ *migration_cols.timelines.get(2).unwrap(),
+ MigrationTimeline::new(2, MigrationTimelineKind::Universe)
+ )
+ }
+
+ #[test]
+ fn base() {
+ let route = r#"{"columns":[[{"AddColumn":"Base"}]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::AddColumn(MigrationAddColumnRoute::Base))
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+
+ #[test]
+ fn universe() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":0}}]],"timelines":[{"id":0,"kind":"Universe"}]}"#;
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(0)
+ ))
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(0, MigrationTimelineKind::Universe)
+ )
+ }
+
+ #[test]
+ fn home() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":2}}]],"timelines":[{"id":2,"kind":{"List":{"Contact":{"Explicit":"aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe"}}}}]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(2)
+ ))
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(
+ 2,
+ MigrationTimelineKind::List(MigrationListKind::Contact(
+ MigrationPubkeySource::Explicit(
+ "aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe"
+ .to_owned()
+ )
+ ))
+ )
+ )
+ }
+
+ #[test]
+ fn thread() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":7}},{"Timeline":{"Thread":"fb9b0c62bc91bbe28ca428fc85e310ae38795b94fb910e0f4e12962ced971f25"}}]],"timelines":[{"id":7,"kind":{"List":{"Contact":{"Explicit":"4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"}}}}]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::Timeline(MigrationTimelineRoute::Timeline(7),),
+ MigrationRoute::Timeline(MigrationTimelineRoute::Thread(
+ "fb9b0c62bc91bbe28ca428fc85e310ae38795b94fb910e0f4e12962ced971f25".to_owned()
+ )),
+ ])
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(
+ 7,
+ MigrationTimelineKind::List(MigrationListKind::Contact(
+ MigrationPubkeySource::Explicit(
+ "4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"
+ .to_owned()
+ )
+ ))
+ )
+ )
+ }
+
+ #[test]
+ fn profile() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":7}},{"Timeline":{"Profile":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"}}]],"timelines":[{"id":7,"kind":{"List":{"Contact":{"Explicit":"4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"}}}}]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::Timeline(MigrationTimelineRoute::Timeline(7),),
+ MigrationRoute::Timeline(MigrationTimelineRoute::Profile(
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_owned()
+ )),
+ ])
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(
+ 7,
+ MigrationTimelineKind::List(MigrationListKind::Contact(
+ MigrationPubkeySource::Explicit(
+ "4a0510f26880d40e432f4865cb5714d9d3c200ca6ebb16b418ae6c555f574967"
+ .to_owned()
+ )
+ ))
+ )
+ )
+ }
+
+ #[test]
+ fn your_notifs() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":5}}]],"timelines":[{"id":5,"kind":{"Notifications":"DeckAuthor"}}]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(5)
+ ))
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(
+ 5,
+ MigrationTimelineKind::Notifications(MigrationPubkeySource::DeckAuthor)
+ )
+ )
+ }
+
+ #[test]
+ fn undecided_notifs() {
+ let route = r#"{"columns":[[{"AddColumn":"Base"},{"AddColumn":"UndecidedNotification"}]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::Base),
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::UndecidedNotification),
+ ])
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+
+ #[test]
+ fn extern_notifs() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":4}}]],"timelines":[{"id":4,"kind":{"Notifications":{"Explicit":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"}}}]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(4)
+ ))
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(
+ 4,
+ MigrationTimelineKind::Notifications(MigrationPubkeySource::Explicit(
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245".to_owned()
+ ))
+ )
+ )
+ }
+
+ #[test]
+ fn hashtag() {
+ let route = r#"{"columns":[[{"Timeline":{"Timeline":6}}]],"timelines":[{"id":6,"kind":{"Hashtag":"notedeck"}}]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_route(MigrationRoute::Timeline(
+ MigrationTimelineRoute::Timeline(6)
+ ))
+ );
+
+ assert_eq!(migration_cols.timelines.len(), 1);
+ assert_eq!(
+ *migration_cols.timelines.first().unwrap(),
+ MigrationTimeline::new(6, MigrationTimelineKind::Hashtag("notedeck".to_owned()))
+ )
+ }
+
+ #[test]
+ fn support() {
+ let route = r#"{"columns":[[{"AddColumn":"Base"},"Support"]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::Base),
+ MigrationRoute::Support
+ ])
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+
+ #[test]
+ fn post() {
+ let route = r#"{"columns":[[{"AddColumn":"Base"},"ComposeNote"]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::Base),
+ MigrationRoute::ComposeNote
+ ])
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+
+ #[test]
+ fn relay() {
+ let route = r#"{"columns":[[{"AddColumn":"Base"},"Relays"]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::Base),
+ MigrationRoute::Relays
+ ])
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+
+ #[test]
+ fn accounts() {
+ let route =
+ r#"{"columns":[[{"AddColumn":"Base"},{"Accounts":"Accounts"}]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::Base),
+ MigrationRoute::Accounts(MigrationAccountsRoute::Accounts),
+ ])
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+
+ #[test]
+ fn login() {
+ let route = r#"{"columns":[[{"AddColumn":"Base"},{"Accounts":"Accounts"},{"Accounts":"AddAccount"}]],"timelines":[]}"#;
+
+ let deserialized_columns = deserialize_columns_string(route.to_string());
+ assert!(deserialized_columns.is_ok());
+
+ let migration_cols = deserialized_columns.unwrap();
+ assert_eq!(migration_cols.columns.len(), 1);
+ assert_eq!(
+ *migration_cols.columns.first().unwrap(),
+ MigrationColumn::from_routes(vec![
+ MigrationRoute::AddColumn(MigrationAddColumnRoute::Base),
+ MigrationRoute::Accounts(MigrationAccountsRoute::Accounts),
+ MigrationRoute::Accounts(MigrationAccountsRoute::AddAccount),
+ ])
+ );
+
+ assert!(migration_cols.timelines.is_empty());
+ }
+}