fix(desktop): detach hosted destination for invalid local source
This commit is contained in:
@@ -3064,6 +3064,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
// Installing a local repair workspace changes its destination,
|
||||||
|
// even while the last valid graphical model stays visible. A
|
||||||
|
// failed/cancelled Open that installs nothing retains its binding.
|
||||||
|
self.hosted.leave_document();
|
||||||
self.canvas_document = Id::new_v4();
|
self.canvas_document = Id::new_v4();
|
||||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||||
self.path = Some(path);
|
self.path = Some(path);
|
||||||
|
|||||||
@@ -443,3 +443,118 @@ async fn hosted_actual_app_tasks_publish_edit_conflict_correct_credentials_reloa
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn hosted_invalid_local_open_repair_saves_locally_and_never_replaces_old_resource() {
|
||||||
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
let store = Arc::new(oa_server::store::Store::open(directory.path()).unwrap());
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let base = format!("http://{}", listener.local_addr().unwrap());
|
||||||
|
let (stop, done) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
let server = tokio::spawn(async move {
|
||||||
|
axum::serve(listener, oa_server::app(store, None))
|
||||||
|
.with_graceful_shutdown(async {
|
||||||
|
let _ = done.await;
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
let mut app = App::with_project(Project::empty());
|
||||||
|
app.hosted.base = base.clone();
|
||||||
|
dispatch(&mut app, Message::Hosted(Event::Publish)).await;
|
||||||
|
let session = app.hosted.session.clone().unwrap();
|
||||||
|
let accepted = app.document.snapshot();
|
||||||
|
let original_identity = app.canvas_document;
|
||||||
|
let external = Connection::new(&base, Secret::default()).unwrap();
|
||||||
|
let old = external.open(session.resource_id).await.unwrap();
|
||||||
|
|
||||||
|
let local = tempfile::tempdir().unwrap();
|
||||||
|
let path = local.path().join("repair.arcadia");
|
||||||
|
let invalid = "arcadia 5; ? // unfinished local work";
|
||||||
|
std::fs::write(&path, invalid).unwrap();
|
||||||
|
let path = path.canonicalize().unwrap();
|
||||||
|
dispatch(
|
||||||
|
&mut app,
|
||||||
|
Message::Loaded(Ok(Some(read_snapshot(path.clone()).unwrap()))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_ne!(app.canvas_document, original_identity);
|
||||||
|
assert_eq!(app.path.as_ref(), Some(&path));
|
||||||
|
assert!(!app.source_workspace.applied);
|
||||||
|
assert_eq!(app.source_editor.text(), invalid);
|
||||||
|
assert!(
|
||||||
|
Arc::ptr_eq(&accepted, &app.document.snapshot()),
|
||||||
|
"A repair workspace must preserve the last valid graphical model"
|
||||||
|
);
|
||||||
|
assert!(!app.source_diagnostics.is_empty());
|
||||||
|
assert!(
|
||||||
|
app.hosted.session.is_none(),
|
||||||
|
"The installed local repair workspace must own its save destination"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut repaired = Project::empty();
|
||||||
|
repaired.name = "Independent local repair".into();
|
||||||
|
let repaired_source = language::format(&repaired);
|
||||||
|
dispatch(
|
||||||
|
&mut app,
|
||||||
|
Message::SourceEdit(text_editor::Action::SelectAll),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
dispatch(
|
||||||
|
&mut app,
|
||||||
|
Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Paste(
|
||||||
|
Arc::new(repaired_source),
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
dispatch(&mut app, Message::ApplySource).await;
|
||||||
|
assert_eq!(app.project(), &repaired);
|
||||||
|
assert!(!app.source_dirty());
|
||||||
|
dispatch(&mut app, Message::Save(false)).await;
|
||||||
|
|
||||||
|
let remote_after = external.open(session.resource_id).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
remote_after.etag, old.etag,
|
||||||
|
"Saving a repaired local workspace must never target the old hosted resource"
|
||||||
|
);
|
||||||
|
assert_eq!(remote_after.compiled.bundle, old.compiled.bundle);
|
||||||
|
assert!(app.hosted.session.is_none());
|
||||||
|
assert!(app.hosted.review.is_none());
|
||||||
|
assert!(app.hosted.uncertain.is_none());
|
||||||
|
assert!(app.hosted.request.is_none());
|
||||||
|
assert_eq!(app.path.as_ref(), Some(&path));
|
||||||
|
assert!(!app.dirty());
|
||||||
|
let saved = source_files::load_project(&path, LIMITS).unwrap();
|
||||||
|
assert_eq!(saved.reachable_bundle(), *app.document.bundle());
|
||||||
|
assert_eq!(
|
||||||
|
app.hosted.base, base,
|
||||||
|
"Connection settings remain available in memory"
|
||||||
|
);
|
||||||
|
drop(external);
|
||||||
|
drop(app);
|
||||||
|
let _ = stop.send(());
|
||||||
|
tokio::time::timeout(std::time::Duration::from_secs(5), server)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hosted_failed_or_cancelled_local_open_keeps_the_existing_document_and_destination() {
|
||||||
|
let mut app = App::with_project(Project::empty());
|
||||||
|
let resource = bind(&mut app);
|
||||||
|
let accepted = app.document.snapshot();
|
||||||
|
let identity = app.canvas_document;
|
||||||
|
let source = app.source_workspace.bundle();
|
||||||
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
let unreadable = directory.path().join("missing.json");
|
||||||
|
let error = read_snapshot(unreadable).unwrap_err();
|
||||||
|
for result in [Ok(None), Err(error)] {
|
||||||
|
let _ = app.update(Message::Loaded(result));
|
||||||
|
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||||
|
assert_eq!(app.canvas_document, identity);
|
||||||
|
assert_eq!(app.source_workspace.bundle(), source);
|
||||||
|
assert!(app.path.is_none());
|
||||||
|
assert_eq!(app.hosted.session.as_ref().unwrap().resource_id, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user