fix(desktop): identify stale hosted catalog results

This commit is contained in:
2026-09-06 08:15:31 +03:00
parent 843635ed26
commit 00ac6eb86c
4 changed files with 232 additions and 9 deletions
+171
View File
@@ -329,7 +329,35 @@ async fn hosted_actual_app_tasks_publish_edit_conflict_correct_credentials_reloa
app.sync();
app.hosted.base = base.clone();
app.hosted.token = Secret(token.into());
dispatch(&mut app, Message::Hosted(Event::Connect)).await;
assert!(!app.hosted.catalog_stale);
assert!(app.hosted.catalog.is_empty());
assert!(
app.hosted
.catalog_message()
.unwrap()
.starts_with("No models found")
);
dispatch(&mut app, Message::Hosted(Event::Publish)).await;
assert!(app.hosted.catalog_stale);
assert!(
!app.hosted
.catalog_message()
.unwrap()
.starts_with("No models found")
);
dispatch(&mut app, Message::Hosted(Event::Close)).await;
dispatch(&mut app, Message::Hosted(Event::Show)).await;
assert!(app.hosted.catalog_stale);
assert!(
app.hosted.request.is_none(),
"Showing the dialog does not poll"
);
dispatch(&mut app, Message::Hosted(Event::Connect)).await;
assert!(!app.hosted.catalog_stale);
assert_eq!(app.hosted.catalog.len(), 1);
assert_eq!(app.hosted.catalog[0].revision, 1);
dispatch(&mut app, Message::Hosted(Event::Close)).await;
let session = app
.hosted
.session
@@ -347,6 +375,15 @@ async fn hosted_actual_app_tasks_publish_edit_conflict_correct_credentials_reloa
)
.await;
dispatch(&mut app, Message::Save(false)).await;
assert!(app.hosted.catalog_stale);
assert_eq!(
app.hosted.catalog[0].revision, 1,
"Old results stay identified as a snapshot"
);
dispatch(&mut app, Message::Hosted(Event::Connect)).await;
assert!(!app.hosted.catalog_stale);
assert_eq!(app.hosted.catalog[0].revision, 2);
assert_eq!(app.hosted.catalog[0].name, "Graphical update");
let external = Connection::new(&base, Secret(token.into())).unwrap();
let remote = external.open(session.resource_id).await.unwrap();
assert_eq!(remote.compiled.decoded.project.name, "Graphical update");
@@ -558,3 +595,137 @@ fn hosted_failed_or_cancelled_local_open_keeps_the_existing_document_and_destina
assert_eq!(app.hosted.session.as_ref().unwrap().resource_id, resource);
}
}
fn catalog_result(app: &App, resource: Id, revision: u64) -> Outcome {
Outcome::Catalog(crate::hosted::client::Catalog {
items: vec![crate::hosted::client::Summary {
resource_id: resource,
revision,
model_id: app.project().id,
name: format!("Catalog snapshot {revision}"),
elements: app.project().elements.len(),
relations: app.project().relations.len(),
}],
next_after: Some(resource),
})
}
#[test]
fn hosted_catalog_search_credentials_and_failed_refresh_never_mix_result_scopes() {
let mut app = App::with_project(Project::empty());
let first = Id::new_v4();
send(&mut app, Event::Connect);
let catalog = catalog_result(&app, first, 1);
complete(&mut app, Ok(catalog));
assert!(!app.hosted.catalog_stale);
send(&mut app, Event::More);
assert_eq!(
app.hosted.request.as_ref().unwrap().operation,
Operation::Catalog(true)
);
let next_page = catalog_result(&app, Id::new_v4(), 1);
complete(&mut app, Ok(next_page));
assert_eq!(app.hosted.catalog.len(), 2);
send(&mut app, Event::Select(first));
send(&mut app, Event::Query("different search".into()));
assert!(app.hosted.catalog_stale);
assert!(app.hosted.next.is_none());
assert!(app.hosted.selected.is_none());
assert_eq!(app.hosted.catalog.len(), 2);
assert!(
app.hosted
.catalog_message()
.unwrap()
.contains("Connect / refresh")
);
send(&mut app, Event::More);
assert!(app.hosted.request.is_none());
send(&mut app, Event::Connect);
complete(&mut app, Err(failure(None, false)));
assert!(app.hosted.catalog_stale);
assert_eq!(app.hosted.catalog[0].resource_id, first);
send(&mut app, Event::Connect);
let second = Id::new_v4();
let catalog = catalog_result(&app, second, 1);
complete(&mut app, Ok(catalog));
assert!(!app.hosted.catalog_stale);
assert_eq!(app.hosted.catalog.len(), 1);
assert_eq!(app.hosted.catalog[0].resource_id, second);
send(&mut app, Event::Query("different search".into()));
assert!(
!app.hosted.catalog_stale,
"Unchanged query preserves the snapshot"
);
send(
&mut app,
Event::Token(Secret("different-credential".into())),
);
assert!(app.hosted.catalog_stale);
assert!(app.hosted.catalog.is_empty());
assert!(app.hosted.connection.is_none());
assert!(app.hosted.next.is_none());
assert!(app.hosted.selected.is_none());
assert!(
!app.hosted
.catalog_message()
.unwrap()
.starts_with("No models found")
);
send(&mut app, Event::Connect);
let catalog = catalog_result(&app, second, 2);
complete(&mut app, Ok(catalog));
send(&mut app, Event::Base("http://127.0.0.1:9876".into()));
assert!(app.hosted.catalog.is_empty());
assert!(app.hosted.connection.is_none());
assert!(app.hosted.catalog_stale);
assert!(app.hosted.request.is_none());
}
#[test]
fn hosted_catalog_invalidation_follows_service_writes_and_observed_revision_changes() {
let mut app = App::with_project(Project::empty());
let resource = bind(&mut app);
let other = Id::new_v4();
send(&mut app, Event::Base("http://127.0.0.1:9876".into()));
send(&mut app, Event::Connect);
let catalog = catalog_result(&app, other, 7);
complete(&mut app, Ok(catalog));
rename(&mut app, "Saved to original service");
send(&mut app, Event::Save);
let saved = remote(app.document.snapshot(), resource, 2);
complete(&mut app, Ok(Outcome::Remote(saved)));
assert!(
!app.hosted.catalog_stale,
"A write to A cannot invalidate B's catalog"
);
assert_eq!(app.hosted.next, Some(other));
assert_eq!(app.hosted.catalog[0].resource_id, other);
send(&mut app, Event::Base("http://127.0.0.1:8787".into()));
send(&mut app, Event::Connect);
let catalog = catalog_result(&app, resource, 2);
complete(&mut app, Ok(catalog));
send(&mut app, Event::Check);
let changed = remote(app.document.snapshot(), resource, 3);
complete(&mut app, Ok(Outcome::Remote(changed)));
assert!(
app.hosted.catalog_stale,
"A known newer server revision invalidates old metadata"
);
assert!(app.hosted.next.is_none());
send(&mut app, Event::Connect);
let catalog = catalog_result(&app, resource, 3);
complete(&mut app, Ok(catalog));
send(&mut app, Event::Reload);
rename(&mut app, "Write with uncertain outcome");
send(&mut app, Event::Save);
complete(&mut app, Err(failure(None, true)));
assert!(app.hosted.catalog_stale);
assert!(app.hosted.next.is_none());
assert_eq!(app.hosted.catalog[0].revision, 3);
app.hosted.leave_document();
assert!(
app.hosted.catalog_stale,
"Leaving the hosted destination cannot make old results fresh"
);
}