blob: fcc2b799bb106e3917406af0c32a9bb8eb548a95 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::*;
use maka_fs_tools::workspace::MARKER_FILE;
async fn projects(fixture: &Fixture) -> Value {
fixture
.query("projects", json!({"kind":"list","cursor":null}))
.await
.unwrap()
}
async fn change(fixture: &Fixture, value: Value) -> Value {
fixture
.mutation(2, "project", value, Uuid::new_v4())
.await
.unwrap()
}
#[tokio::test]
async fn projects_register_create_rename_archive_restore_and_relocate_original_sessions() {
let fixture = Fixture::new("http://127.0.0.1:1/v1", None).await;
let roots = fixture
.query("directories", json!({"kind":"roots"}))
.await
.unwrap();
let source = roots["roots"][0]["selection"].clone();
let destination = roots["roots"][1]["selection"].clone();
assert_eq!(projects(&fixture).await["projects"], json!([]));
let preview = fixture
.query("projects", json!({"kind":"directory","selection":source}))
.await
.unwrap();
assert!(preview["location"]["project"].is_null());
assert!(
!fixture.workspace.join(MARKER_FILE).exists(),
"queries do not create workspace markers"
);
let registered = change(
&fixture,
json!({"kind":"register","selection":source,"expected":null}),
)
.await;
assert_eq!(registered["kind"], "committed");
assert!(!fixture.workspace.join(MARKER_FILE).exists());
let selected = registered["project"]["reference"].clone();
let created = fixture.mutation(2,"create",json!({"title":"Project conversation","labels":[],"workspace":{"kind":"project","project":selected,"selection":source}}),Uuid::new_v4()).await.unwrap();
assert_eq!(created["kind"], "created");
let session = created["session"]["id"].as_str().unwrap();
assert_eq!(created["session"]["workspace"]["projectId"], selected["id"]);
assert!(fixture.workspace.join(MARKER_FILE).exists());
let current = projects(&fixture).await["projects"][0]["reference"].clone();
let name = format!("Research 项目{}", "完整".repeat(9000));
let renamed = change(
&fixture,
json!({"kind":"rename","project":current,"name":name}),
)
.await;
assert_eq!(
renamed["project"]["name"],
format!("Research 项目{}", "完整".repeat(9000))
);
let listed = projects(&fixture).await;
assert_eq!(listed["projects"][0], renamed["project"]);
let directory = fixture
.query("projects", json!({"kind":"directory","selection":source}))
.await
.unwrap();
assert_eq!(directory["location"]["project"], renamed["project"]);
assert_eq!(
change(&fixture, json!({"kind":"archive","project":current})).await["kind"],
"changed"
);
let archived = change(
&fixture,
json!({"kind":"archive","project":renamed["project"]["reference"]}),
)
.await;
assert_eq!(archived["project"]["archived"], true);
let denied = fixture.mutation(2,"create",json!({"title":"Archived","labels":[],"workspace":{"kind":"project","project":archived["project"]["reference"],"selection":source}}),Uuid::new_v4()).await.unwrap();
assert_eq!(denied["kind"], "rejected");
let denied = fixture.mutation(2,"create",json!({"title":"Archived session shortcut","labels":[],"workspace":{"kind":"session","sessionId":session}}),Uuid::new_v4()).await.unwrap();
assert_eq!(denied["kind"], "rejected");
let restored = change(
&fixture,
json!({"kind":"restore","project":archived["project"]["reference"]}),
)
.await;
assert_eq!(restored["project"]["archived"], false);
let before = fixture.owner.session(session).await.unwrap();
fixture
.store
.create_session(
"foreign",
"foreign",
&json!({"different":"configuration"}),
1,
)
.await
.unwrap();
let relocated = change(&fixture,json!({"kind":"relocate","project":restored["project"]["reference"],"selection":destination,"destination":null})).await;
assert_eq!(relocated["kind"], "committed");
assert_eq!(relocated["project"]["reference"]["id"], selected["id"]);
let after = fixture.owner.session(session).await.unwrap();
assert_eq!(
after.configuration.workspace.path,
roots["roots"][1]["path"].as_str().unwrap()
);
assert_eq!(
after.configuration.boundary_revision,
before.configuration.boundary_revision + 1
);
assert_eq!(after.updated_at, before.updated_at);
assert_eq!(
after.configuration.permissions,
before.configuration.permissions
);
assert_eq!(
fixture
.store
.get_session::<Value>("foreign")
.await
.unwrap()
.unwrap()
.configuration,
json!({"different":"configuration"})
);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
}
#[tokio::test]
async fn projects_are_owner_scoped_and_stale_pages_and_directory_replacements_are_rejected() {
let fixture = Fixture::new("http://127.0.0.1:1/v1", None).await;
let hidden = fixture
.store
.register_project(
maka_event_log::projects::ProjectRegistration {
identity: "another-owner:folder:private".into(),
name: "Private".into(),
path: "/private-unpublished".into(),
is_worktree: false,
},
true,
1,
)
.await
.unwrap();
assert_eq!(projects(&fixture).await["projects"], json!([]));
let rejected = change(&fixture,json!({"kind":"rename","project":{"id":hidden.id,"revision":"0".repeat(64)},"name":"Stolen"})).await;
assert_eq!(rejected["kind"], "unavailable");
for index in 0..17 {
std::fs::create_dir(fixture.workspace.join(format!("project-{index:02}"))).unwrap();
}
let roots = fixture
.query("directories", json!({"kind":"roots"}))
.await
.unwrap();
let children = fixture
.query(
"directories",
json!({"kind":"children","selection":roots["roots"][0]["selection"],"after":null}),
)
.await
.unwrap();
for child in children["children"].as_array().unwrap() {
assert_eq!(
change(
&fixture,
json!({"kind":"register","selection":child["selection"],"expected":null})
)
.await["kind"],
"committed"
);
}
let page = projects(&fixture).await;
assert_eq!(page["projects"].as_array().unwrap().len(), 16);
assert!(!page["next"].is_null());
let last = fixture
.query("projects", json!({"kind":"list","cursor":page["next"]}))
.await
.unwrap();
assert_eq!(last["projects"].as_array().unwrap().len(), 1);
change(
&fixture,
json!({"kind":"rename","project":page["projects"][0]["reference"],"name":"Changed"}),
)
.await;
assert_eq!(
fixture
.query("projects", json!({"kind":"list","cursor":page["next"]}))
.await
.unwrap()["kind"],
"changed"
);
let child = &children["children"][0];
let observed_project = fixture
.query(
"projects",
json!({"kind":"directory","selection":child["selection"]}),
)
.await
.unwrap()["location"]["project"]
.clone();
let path = std::path::PathBuf::from(child["path"].as_str().unwrap());
std::fs::rename(&path, path.with_extension("retired")).unwrap();
std::fs::create_dir(&path).unwrap();
let replaced = fixture.mutation(2, "create", json!({"title":"Replaced project", "labels":[], "workspace":{"kind":"project","project":observed_project["reference"],"selection":observed_project["selection"]}}), Uuid::new_v4()).await.unwrap();
assert_eq!(replaced["kind"], "rejected");
assert!(!path.join(MARKER_FILE).exists());
assert_eq!(
fixture
.query(
"projects",
json!({"kind":"directory","selection":child["selection"]})
)
.await
.unwrap()["kind"],
"unavailable"
);
assert_eq!(
change(
&fixture,
json!({"kind":"register","selection":child["selection"],"expected":null})
)
.await["kind"],
"unavailable"
);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
}
#[tokio::test]
async fn projects_relocation_rejects_running_and_queued_work_then_merges_without_rewriting_destination_authority()
{
tokio::time::timeout(Duration::from_secs(40), async {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let base = format!("http://{}/v1", listener.local_addr().unwrap());
let fixture = Fixture::new(&base, None).await;
let source_session = fixture.create("Source").await;
let roots = fixture.query("directories", json!({"kind":"roots"})).await.unwrap();
let destination = roots["roots"][1]["selection"].clone();
let target = fixture.mutation(2, "create", json!({"title":"Destination", "labels":[], "workspace":{"kind":"directory","selection":destination}}), Uuid::new_v4()).await.unwrap();
let target_id = target["session"]["id"].as_str().unwrap();
let original = fixture.owner.session(&source_session).await.unwrap();
let target_original = fixture.owner.session(target_id).await.unwrap();
let project_id = original.configuration.workspace.project_id.unwrap().to_string();
let catalog = projects(&fixture).await;
let source = catalog["projects"].as_array().unwrap().iter().find(|p| p["reference"]["id"] == project_id).unwrap()["reference"].clone();
let preview = fixture.query("projects", json!({"kind":"directory","selection":destination})).await.unwrap();
let relocation = json!({"kind":"relocate","project":source,"selection":destination,"destination":preview["location"]["project"]["reference"]});
fixture.send(&source_session).await.unwrap();
let (mut socket, _) = listener.accept().await.unwrap();
request(&mut socket).await;
assert_eq!(fixture.send(&source_session).await.unwrap()["kind"], "queued");
assert_eq!(change(&fixture, relocation.clone()).await["kind"], "busy");
assert_eq!(fixture.owner.session(&source_session).await.unwrap().configuration.workspace, original.configuration.workspace);
assert_eq!(fixture.store.pending_messages(&source_session).await.unwrap().len(), 1);
response(&mut socket).await;
let (mut next, _) = listener.accept().await.unwrap();
request(&mut next).await;
assert_eq!(change(&fixture, relocation.clone()).await["kind"], "busy");
response(&mut next).await;
fixture.drained().await;
let merged = change(&fixture, relocation).await;
assert_eq!(merged["kind"], "committed");
assert_eq!(projects(&fixture).await["projects"].as_array().unwrap().len(), 1);
let moved = fixture.owner.session(&source_session).await.unwrap();
assert_eq!(moved.configuration.workspace.path, target_original.configuration.workspace.path);
assert_eq!(moved.configuration.boundary_revision, original.configuration.boundary_revision + 1);
let retained = fixture.owner.session(target_id).await.unwrap();
assert_eq!(retained.configuration.workspace.project_id, moved.configuration.workspace.project_id);
assert_eq!(retained.configuration.workspace.path, target_original.configuration.workspace.path);
assert_eq!(retained.configuration.workspace.identity, target_original.configuration.workspace.identity);
assert_eq!(retained.configuration.boundary_revision, target_original.configuration.boundary_revision);
assert_eq!(retained.updated_at, target_original.updated_at);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
}).await.unwrap();
}
#[tokio::test]
async fn projects_original_receipt_survives_reactivation_without_repeating_the_change() {
let mut fixture = Fixture::new("http://127.0.0.1:1/v1", None).await;
let session = fixture.create("Original project").await;
let selected = projects(&fixture).await["projects"][0]["reference"].clone();
let operation = Uuid::new_v4();
let result = fixture
.mutation(
2,
"project",
json!({"kind":"rename","project":selected,"name":"Persisted project"}),
operation,
)
.await
.unwrap();
let source = fixture.app.source().unwrap().clone();
let reference = fixture
.store
.application_intents(source.deployment, id(2), 0, 16)
.await
.unwrap()
.into_iter()
.find(|entry| entry.reference.operation == operation)
.unwrap()
.reference;
let before = fixture
.store
.get_project(selected["id"].as_str().unwrap())
.await
.unwrap()
.unwrap();
let roots = fixture.owner.settings.workspace_roots.clone();
let policy = Arc::new(Grant(fixture.issuer.clone(), None));
workspaces::publish_with_policy(&mut fixture, roots, policy).await;
let client = fixture.app.client();
let recovered = client
.recover(
client.activation(&id(2)).unwrap(),
reference,
scope(&fixture.issuer, true),
)
.await
.unwrap()
.unwrap();
assert_eq!(
recovered.outcome,
OperationOutcome::Returned { value: result }
);
assert_eq!(
fixture
.store
.get_project(&before.id)
.await
.unwrap()
.unwrap(),
before
);
assert_eq!(
projects(&fixture).await["projects"][0]["name"],
"Persisted project"
);
assert_eq!(
fixture
.query("observe", json!({"sessionId":session}))
.await
.unwrap()["session"]["workspace"]["projectId"],
before.id
);
assert_eq!(
fixture
.store
.application_intents(source.deployment, id(2), 0, 16)
.await
.unwrap()
.len(),
2
);
fixture.app.shutdown().await.unwrap();
fixture.config.shutdown().await.unwrap();
}