Update public project selection boundary

This commit is contained in:
Michel Paulissen 2026-07-04 09:58:48 +02:00
parent ca5e6dddc8
commit 298fbcca72
3 changed files with 162 additions and 5 deletions

View file

@ -1128,7 +1128,6 @@ fn project_init_report(args: ProjectInitArgs, cwd: PathBuf) -> Result<Value> {
config_file.display()
);
}
write_project_config(&cwd, &config)?;
let coordinator_response = if let Some(coordinator) = &args.scope.coordinator {
let mut session = JsonLineSession::connect(coordinator)?;
Some(session.request(json!({
@ -1141,8 +1140,12 @@ fn project_init_report(args: ProjectInitArgs, cwd: PathBuf) -> Result<Value> {
} else {
None
};
write_project_config(&cwd, &config)?;
Ok(json!({
"command": "project init",
"source": if args.scope.coordinator.is_some() { "public_coordinator_api" } else { "local_project_config" },
"private_website_required": false,
"project_config_written": true,
"project_config": config,
"config_file": config_file,
"coordinator_response": coordinator_response,
@ -1208,17 +1211,32 @@ fn project_list_report(args: ProjectListArgs, cwd: PathBuf) -> Result<Value> {
"tenant": args.scope.tenant,
"actor_user": args.scope.user,
}))?;
let projects = response
.get("projects")
.cloned()
.unwrap_or_else(|| json!([]));
let project_count = projects.as_array().map(Vec::len).unwrap_or(0);
return Ok(json!({
"command": "project list",
"source": "public_coordinator_api",
"coordinator": coordinator,
"tenant": args.scope.tenant,
"user": args.scope.user,
"projects": projects,
"project_count": project_count,
"private_website_required": false,
"response": response,
"coordinator_session_requests": session.requests(),
}));
}
let projects = read_project_config(&cwd)?.into_iter().collect::<Vec<_>>();
let project_count = projects.len();
Ok(json!({
"command": "project list",
"source": "local_project_config",
"projects": read_project_config(&cwd)?.into_iter().collect::<Vec<_>>(),
"projects": projects,
"project_count": project_count,
"private_website_required": false,
}))
}
@ -1229,7 +1247,6 @@ fn project_select_report(args: ProjectSelectArgs, cwd: PathBuf) -> Result<Value>
user: args.scope.user.clone(),
coordinator: args.scope.coordinator.clone(),
};
write_project_config(&cwd, &config)?;
let coordinator_response = if let Some(coordinator) = &args.scope.coordinator {
let mut session = JsonLineSession::connect(coordinator)?;
Some(session.request(json!({
@ -1241,8 +1258,24 @@ fn project_select_report(args: ProjectSelectArgs, cwd: PathBuf) -> Result<Value>
} else {
None
};
write_project_config(&cwd, &config)?;
let selected_project = coordinator_response
.as_ref()
.and_then(|response| response.get("project"))
.cloned()
.unwrap_or_else(|| {
json!({
"id": config.project.clone(),
"tenant": config.tenant.clone(),
"name": config.project.clone(),
})
});
Ok(json!({
"command": "project select",
"source": if args.scope.coordinator.is_some() { "public_coordinator_api" } else { "local_project_config" },
"selected_project": selected_project,
"project_config_written": true,
"private_website_required": false,
"project_config": config,
"coordinator_response": coordinator_response,
}))
@ -6766,6 +6799,119 @@ mod tests {
assert_eq!(status["attached_nodes"]["checked"], false);
}
#[test]
fn project_list_and_select_use_public_api_without_website() {
let temp = tempfile::tempdir().unwrap();
write_project_config(
temp.path(),
&ProjectConfig {
tenant: "tenant-live".to_owned(),
project: "project-original".to_owned(),
user: "user-live".to_owned(),
coordinator: None,
},
)
.unwrap();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap().to_string();
let server = std::thread::spawn(move || {
for index in 0..3 {
let (mut stream, _) = listener.accept().unwrap();
let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut line = String::new();
reader.read_line(&mut line).unwrap();
assert!(line.contains(r#""tenant":"tenant-live""#));
assert!(line.contains(r#""actor_user":"user-live""#));
match index {
0 => {
assert!(line.contains(r#""type":"list_projects""#));
stream
.write_all(
br#"{"type":"projects","projects":[{"id":"project-a","tenant":"tenant-live","name":"Project A"}],"actor":"user-live"}"#,
)
.unwrap();
}
1 => {
assert!(line.contains(r#""type":"select_project""#));
assert!(line.contains(r#""project":"project-a""#));
stream
.write_all(
br#"{"type":"project_selected","project":{"id":"project-a","tenant":"tenant-live","name":"Project A"},"actor":"user-live"}"#,
)
.unwrap();
}
2 => {
assert!(line.contains(r#""type":"select_project""#));
assert!(line.contains(r#""project":"project-b""#));
stream
.write_all(
br#"{"type":"error","message":"project is outside the signed-in tenant scope"}"#,
)
.unwrap();
}
_ => unreachable!(),
}
stream.write_all(b"\n").unwrap();
}
});
let scope = CliScopeArgs {
coordinator: Some(addr),
tenant: "tenant-live".to_owned(),
project: "project".to_owned(),
user: "user-live".to_owned(),
json: false,
};
let list = project_list_report(
ProjectListArgs {
scope: scope.clone(),
},
temp.path().to_path_buf(),
)
.unwrap();
assert_eq!(list["command"], "project list");
assert_eq!(list["source"], "public_coordinator_api");
assert_eq!(list["project_count"], 1);
assert_eq!(list["projects"][0]["id"], "project-a");
assert_eq!(list["private_website_required"], false);
assert_eq!(list["coordinator_session_requests"], 1);
let selected = project_select_report(
ProjectSelectArgs {
scope: scope.clone(),
selected_project: "project-a".to_owned(),
},
temp.path().to_path_buf(),
)
.unwrap();
assert_eq!(selected["command"], "project select");
assert_eq!(selected["source"], "public_coordinator_api");
assert_eq!(selected["selected_project"]["id"], "project-a");
assert_eq!(selected["project_config_written"], true);
assert_eq!(selected["private_website_required"], false);
assert_eq!(
read_project_config(temp.path()).unwrap().unwrap().project,
"project-a"
);
let rejected = project_select_report(
ProjectSelectArgs {
scope,
selected_project: "project-b".to_owned(),
},
temp.path().to_path_buf(),
)
.unwrap_err();
server.join().unwrap();
assert!(rejected.to_string().contains("tenant scope"));
assert_eq!(
read_project_config(temp.path()).unwrap().unwrap().project,
"project-a"
);
}
#[test]
fn project_status_queries_public_coordinator_state() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();