Update public project selection boundary
This commit is contained in:
parent
ca5e6dddc8
commit
298fbcca72
3 changed files with 162 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"kind": "disasmer-filtered-public-tree",
|
"kind": "disasmer-filtered-public-tree",
|
||||||
"source_commit": "ef0246f094d8e779e21138dda21f7ee1c4734b72",
|
"source_commit": "57e56a24e60a3990d91c199548b96b6c1f0fe962",
|
||||||
"release_name": "dryrun-ef0246f094d8",
|
"release_name": "dryrun-57e56a24e60a",
|
||||||
"filtered_out": [
|
"filtered_out": [
|
||||||
"private/**",
|
"private/**",
|
||||||
"experiments/**",
|
"experiments/**",
|
||||||
|
|
|
||||||
|
|
@ -1128,7 +1128,6 @@ fn project_init_report(args: ProjectInitArgs, cwd: PathBuf) -> Result<Value> {
|
||||||
config_file.display()
|
config_file.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
write_project_config(&cwd, &config)?;
|
|
||||||
let coordinator_response = if let Some(coordinator) = &args.scope.coordinator {
|
let coordinator_response = if let Some(coordinator) = &args.scope.coordinator {
|
||||||
let mut session = JsonLineSession::connect(coordinator)?;
|
let mut session = JsonLineSession::connect(coordinator)?;
|
||||||
Some(session.request(json!({
|
Some(session.request(json!({
|
||||||
|
|
@ -1141,8 +1140,12 @@ fn project_init_report(args: ProjectInitArgs, cwd: PathBuf) -> Result<Value> {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
write_project_config(&cwd, &config)?;
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"command": "project init",
|
"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,
|
"project_config": config,
|
||||||
"config_file": config_file,
|
"config_file": config_file,
|
||||||
"coordinator_response": coordinator_response,
|
"coordinator_response": coordinator_response,
|
||||||
|
|
@ -1208,17 +1211,32 @@ fn project_list_report(args: ProjectListArgs, cwd: PathBuf) -> Result<Value> {
|
||||||
"tenant": args.scope.tenant,
|
"tenant": args.scope.tenant,
|
||||||
"actor_user": args.scope.user,
|
"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!({
|
return Ok(json!({
|
||||||
"command": "project list",
|
"command": "project list",
|
||||||
|
"source": "public_coordinator_api",
|
||||||
"coordinator": coordinator,
|
"coordinator": coordinator,
|
||||||
|
"tenant": args.scope.tenant,
|
||||||
|
"user": args.scope.user,
|
||||||
|
"projects": projects,
|
||||||
|
"project_count": project_count,
|
||||||
|
"private_website_required": false,
|
||||||
"response": response,
|
"response": response,
|
||||||
"coordinator_session_requests": session.requests(),
|
"coordinator_session_requests": session.requests(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
let projects = read_project_config(&cwd)?.into_iter().collect::<Vec<_>>();
|
||||||
|
let project_count = projects.len();
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"command": "project list",
|
"command": "project list",
|
||||||
"source": "local_project_config",
|
"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(),
|
user: args.scope.user.clone(),
|
||||||
coordinator: args.scope.coordinator.clone(),
|
coordinator: args.scope.coordinator.clone(),
|
||||||
};
|
};
|
||||||
write_project_config(&cwd, &config)?;
|
|
||||||
let coordinator_response = if let Some(coordinator) = &args.scope.coordinator {
|
let coordinator_response = if let Some(coordinator) = &args.scope.coordinator {
|
||||||
let mut session = JsonLineSession::connect(coordinator)?;
|
let mut session = JsonLineSession::connect(coordinator)?;
|
||||||
Some(session.request(json!({
|
Some(session.request(json!({
|
||||||
|
|
@ -1241,8 +1258,24 @@ fn project_select_report(args: ProjectSelectArgs, cwd: PathBuf) -> Result<Value>
|
||||||
} else {
|
} else {
|
||||||
None
|
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!({
|
Ok(json!({
|
||||||
"command": "project select",
|
"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,
|
"project_config": config,
|
||||||
"coordinator_response": coordinator_response,
|
"coordinator_response": coordinator_response,
|
||||||
}))
|
}))
|
||||||
|
|
@ -6766,6 +6799,119 @@ mod tests {
|
||||||
assert_eq!(status["attached_nodes"]["checked"], false);
|
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]
|
#[test]
|
||||||
fn project_status_queries_public_coordinator_state() {
|
fn project_status_queries_public_coordinator_state() {
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ for (const [name, pattern] of [
|
||||||
["doctor unchecked reachability coverage", /fn doctor_reports_unchecked_coordinator_reachability_without_config\(\)/],
|
["doctor unchecked reachability coverage", /fn doctor_reports_unchecked_coordinator_reachability_without_config\(\)/],
|
||||||
["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/],
|
["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/],
|
||||||
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
||||||
|
["project public API list/select coverage", /fn project_list_and_select_use_public_api_without_website\(\)/],
|
||||||
["project coordinator status coverage", /fn project_status_queries_public_coordinator_state\(\)/],
|
["project coordinator status coverage", /fn project_status_queries_public_coordinator_state\(\)/],
|
||||||
["run coordinator active-process coverage", /fn run_contacts_configured_coordinator_and_reports_active_process_conflicts\(\)/],
|
["run coordinator active-process coverage", /fn run_contacts_configured_coordinator_and_reports_active_process_conflicts\(\)/],
|
||||||
["CLI error classifier coverage", /fn cli_error_classifier_distinguishes_mvp_failure_categories\(\)/],
|
["CLI error classifier coverage", /fn cli_error_classifier_distinguishes_mvp_failure_categories\(\)/],
|
||||||
|
|
@ -309,6 +310,16 @@ expect(
|
||||||
"CLI exposes task placement reasons",
|
"CLI exposes task placement reasons",
|
||||||
/fn task_summaries[\s\S]*node_placement[\s\S]*reasons[\s\S]*explanation_available/
|
/fn task_summaries[\s\S]*node_placement[\s\S]*reasons[\s\S]*explanation_available/
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
cli,
|
||||||
|
"CLI project list/select report public API boundary",
|
||||||
|
/fn project_list_report[\s\S]*public_coordinator_api[\s\S]*private_website_required[\s\S]*fn project_select_report[\s\S]*project_config_written[\s\S]*private_website_required/
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
cli,
|
||||||
|
"CLI project select writes config after coordinator response",
|
||||||
|
/let coordinator_response = if let Some\(coordinator\)[\s\S]*session\.request\(json!\(\{[\s\S]*"type": "select_project"[\s\S]*\}\)\)\?[\s\S]*write_project_config\(&cwd, &config\)\?/
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
cli,
|
cli,
|
||||||
"CLI renders task placement reasons",
|
"CLI renders task placement reasons",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue