mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-12 01:45:14 +02:00
The operator config gains servers: (name -> url; never a token). A remote command whose URL prefix-matches an operator server resolves its bearer token through the keyed chain first — OMNIGRAPH_TOKEN_<NAME> env, then the [<name>] section of ~/.omnigraph/credentials (created 0600 via temp+rename, #139 finding 7; group/world-readable files refused loudly) — falling through to the legacy chain unchanged. URL keying makes §D5 rule 3 structural: a token is only ever sent to the server it is keyed to. Longest-prefix matching with a path-boundary check (http://h:8080 never matches http://h:8080-evil). Inserting the keyed hop above the legacy chain is safe by construction — no existing setup can have servers: defined. omnigraph login <name> stores/rotates one section (token from --token or one stdin line — the pipe flow keeps secrets out of shell history); omnigraph logout removes it, idempotently; logging in before declaring the server warns instead of failing (the gh model). Coverage: URL-match/no-substring-trap, credentials round-trip preserving sibling sections, 0600 write + over-permissive refusal, env-name mapping; the legacy resolve test is now hermetic against a real ~/.omnigraph and asserts byte-identical legacy behavior with no servers defined; one spawned-binary e2e walks the whole lifecycle against an authed server: refusal -> wrong-token login (stdin) -> rotate (--token) -> authorized read -> env-beats-file -> non-matching-URL negative -> logout revokes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
690 lines
18 KiB
Rust
690 lines
18 KiB
Rust
#![allow(dead_code)]
|
|
|
|
use std::fs;
|
|
use std::net::TcpListener;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Child, Command as StdCommand, Output, Stdio};
|
|
use std::thread::sleep;
|
|
use std::time::Duration;
|
|
|
|
use assert_cmd::Command;
|
|
use reqwest::blocking::Client;
|
|
use serde_json::Value;
|
|
use tempfile::{TempDir, tempdir};
|
|
|
|
/// Hermetic default: point OMNIGRAPH_HOME at a path that exists on no
|
|
/// machine, so spawned binaries never read the developer's real
|
|
/// ~/.omnigraph/ (an absent operator config is an empty layer). Tests
|
|
/// exercising the operator layer override the var explicitly.
|
|
pub const HERMETIC_OPERATOR_HOME: &str = "/nonexistent/omnigraph-test-home";
|
|
|
|
pub fn cli() -> Command {
|
|
let mut command = Command::cargo_bin("omnigraph").unwrap();
|
|
command.env("OMNIGRAPH_HOME", HERMETIC_OPERATOR_HOME);
|
|
command.env_remove("OMNIGRAPH_CONFIG");
|
|
command
|
|
}
|
|
|
|
pub fn cli_process() -> StdCommand {
|
|
let mut command = StdCommand::new(assert_cmd::cargo::cargo_bin("omnigraph"));
|
|
command.env("OMNIGRAPH_HOME", HERMETIC_OPERATOR_HOME);
|
|
command.env_remove("OMNIGRAPH_CONFIG");
|
|
command
|
|
}
|
|
|
|
fn server_process() -> StdCommand {
|
|
if let Some(path) = std::env::var_os("CARGO_BIN_EXE_omnigraph-server") {
|
|
StdCommand::new(path)
|
|
} else if let Some(path) = built_server_binary() {
|
|
StdCommand::new(path)
|
|
} else {
|
|
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
|
|
let mut cmd = StdCommand::new(cargo);
|
|
cmd.arg("run")
|
|
.arg("--quiet")
|
|
.arg("-p")
|
|
.arg("omnigraph-server")
|
|
.arg("--");
|
|
cmd
|
|
}
|
|
}
|
|
|
|
fn built_server_binary() -> Option<PathBuf> {
|
|
let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
|
|
let candidate = workspace_root
|
|
.join("target")
|
|
.join("debug")
|
|
.join(format!("omnigraph-server{}", std::env::consts::EXE_SUFFIX));
|
|
candidate.exists().then_some(candidate)
|
|
}
|
|
|
|
pub fn fixture(name: &str) -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../omnigraph/tests/fixtures")
|
|
.join(name)
|
|
}
|
|
|
|
pub fn graph_path(root: &Path) -> PathBuf {
|
|
root.join("demo.omni")
|
|
}
|
|
|
|
pub fn output_success(cmd: &mut Command) -> Output {
|
|
let output = cmd.output().unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"command failed\nstdout:\n{}\nstderr:\n{}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
output
|
|
}
|
|
|
|
pub fn output_failure(cmd: &mut Command) -> Output {
|
|
let output = cmd.output().unwrap();
|
|
assert!(
|
|
!output.status.success(),
|
|
"command unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}",
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
output
|
|
}
|
|
|
|
pub fn stdout_string(output: &Output) -> String {
|
|
String::from_utf8(output.stdout.clone()).unwrap()
|
|
}
|
|
|
|
pub fn parse_stdout_json(output: &Output) -> Value {
|
|
serde_json::from_slice(&output.stdout).unwrap()
|
|
}
|
|
|
|
pub fn init_graph(graph: &Path) {
|
|
let schema = fixture("test.pg");
|
|
output_success(cli().arg("init").arg("--schema").arg(&schema).arg(graph));
|
|
}
|
|
|
|
pub fn load_fixture(graph: &Path) {
|
|
let data = fixture("test.jsonl");
|
|
output_success(
|
|
cli()
|
|
.arg("load")
|
|
.arg("--mode")
|
|
.arg("overwrite")
|
|
.arg("--data")
|
|
.arg(&data)
|
|
.arg(graph),
|
|
);
|
|
}
|
|
|
|
pub fn write_jsonl(path: &Path, rows: &str) {
|
|
fs::write(path, rows).unwrap();
|
|
}
|
|
|
|
pub fn write_query_file(path: &Path, source: &str) {
|
|
fs::write(path, source).unwrap();
|
|
}
|
|
|
|
pub fn write_config(path: &Path, source: &str) {
|
|
fs::write(path, source).unwrap();
|
|
}
|
|
|
|
pub fn write_file(path: &Path, source: &str) {
|
|
fs::write(path, source).unwrap();
|
|
}
|
|
|
|
fn yaml_string(value: &str) -> String {
|
|
format!("'{}'", value.replace('\'', "''"))
|
|
}
|
|
|
|
pub fn local_yaml_config(graph: &Path) -> String {
|
|
format!(
|
|
"\
|
|
graphs:
|
|
local:
|
|
uri: {}
|
|
cli:
|
|
graph: local
|
|
branch: main
|
|
query:
|
|
roots:
|
|
- .
|
|
policy: {{}}
|
|
",
|
|
yaml_string(&graph.to_string_lossy())
|
|
)
|
|
}
|
|
|
|
pub fn remote_yaml_config(url: &str) -> String {
|
|
format!(
|
|
"\
|
|
graphs:
|
|
dev:
|
|
uri: {}
|
|
cli:
|
|
graph: dev
|
|
branch: main
|
|
query:
|
|
roots:
|
|
- .
|
|
policy: {{}}
|
|
",
|
|
yaml_string(url)
|
|
)
|
|
}
|
|
|
|
pub struct TestServer {
|
|
child: Child,
|
|
pub base_url: String,
|
|
}
|
|
|
|
impl Drop for TestServer {
|
|
fn drop(&mut self) {
|
|
let _ = self.child.kill();
|
|
let _ = self.child.wait();
|
|
}
|
|
}
|
|
|
|
fn free_port() -> u16 {
|
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
let port = listener.local_addr().unwrap().port();
|
|
drop(listener);
|
|
port
|
|
}
|
|
|
|
fn spawn_server_process(mut command: StdCommand) -> TestServer {
|
|
let port = free_port();
|
|
let bind = format!("127.0.0.1:{}", port);
|
|
let mut child = command
|
|
.arg("--bind")
|
|
.arg(&bind)
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.spawn()
|
|
.unwrap();
|
|
let base_url = format!("http://{}", bind);
|
|
let client = Client::new();
|
|
for _ in 0..300 {
|
|
if client
|
|
.get(format!("{}/healthz", base_url))
|
|
.send()
|
|
.map(|response| response.status().is_success())
|
|
.unwrap_or(false)
|
|
{
|
|
return TestServer { child, base_url };
|
|
}
|
|
if let Some(status) = child.try_wait().unwrap() {
|
|
panic!("server exited before becoming healthy: {status}");
|
|
}
|
|
sleep(Duration::from_millis(100));
|
|
}
|
|
panic!("server did not become healthy");
|
|
}
|
|
|
|
pub fn spawn_server(graph: &Path) -> TestServer {
|
|
let mut command = server_process();
|
|
command.arg(graph);
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
pub fn spawn_server_with_config(config: &Path) -> TestServer {
|
|
let mut command = server_process();
|
|
command.arg("--config").arg(config);
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
pub fn spawn_server_with_cluster(cluster_dir: &Path) -> TestServer {
|
|
let mut command = server_process();
|
|
command.arg("--cluster").arg(cluster_dir).arg("--unauthenticated");
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
/// Cluster boot with the server process's cwd set explicitly — used to prove
|
|
/// rule 0 never touches the cwd omnigraph.yaml search.
|
|
pub fn spawn_server_with_cluster_in(cluster_dir: &Path, cwd: &Path) -> TestServer {
|
|
let mut command = server_process();
|
|
command
|
|
.arg("--cluster")
|
|
.arg(cluster_dir)
|
|
.arg("--unauthenticated")
|
|
.current_dir(cwd);
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
pub fn spawn_server_with_cluster_env(cluster_dir: &Path, envs: &[(&str, &str)]) -> TestServer {
|
|
let mut command = server_process();
|
|
command.arg("--cluster").arg(cluster_dir);
|
|
for (name, value) in envs {
|
|
command.env(name, value);
|
|
}
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
pub fn spawn_server_with_env(graph: &Path, envs: &[(&str, &str)]) -> TestServer {
|
|
let mut command = server_process();
|
|
command.arg(graph);
|
|
for (name, value) in envs {
|
|
command.env(name, value);
|
|
}
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
pub fn spawn_server_with_config_env(config: &Path, envs: &[(&str, &str)]) -> TestServer {
|
|
let mut command = server_process();
|
|
command.arg("--config").arg(config);
|
|
for (name, value) in envs {
|
|
command.env(name, value);
|
|
}
|
|
spawn_server_process(command)
|
|
}
|
|
|
|
pub struct SystemGraph {
|
|
_temp: TempDir,
|
|
graph: PathBuf,
|
|
}
|
|
|
|
impl SystemGraph {
|
|
pub fn initialized() -> Self {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
init_graph(&graph);
|
|
Self { _temp: temp, graph }
|
|
}
|
|
|
|
pub fn loaded() -> Self {
|
|
let temp = tempdir().unwrap();
|
|
let graph = graph_path(temp.path());
|
|
init_graph(&graph);
|
|
load_fixture(&graph);
|
|
Self { _temp: temp, graph }
|
|
}
|
|
|
|
pub fn path(&self) -> &Path {
|
|
&self.graph
|
|
}
|
|
|
|
pub fn write_query(&self, name: &str, source: &str) -> PathBuf {
|
|
let path = self.graph.parent().unwrap().join(name);
|
|
write_query_file(&path, source);
|
|
path
|
|
}
|
|
|
|
pub fn write_jsonl(&self, name: &str, rows: &str) -> PathBuf {
|
|
let path = self.graph.parent().unwrap().join(name);
|
|
write_jsonl(&path, rows);
|
|
path
|
|
}
|
|
|
|
pub fn write_config(&self, name: &str, source: &str) -> PathBuf {
|
|
let path = self.graph.parent().unwrap().join(name);
|
|
write_config(&path, source);
|
|
path
|
|
}
|
|
|
|
pub fn write_file(&self, name: &str, source: &str) -> PathBuf {
|
|
let path = self.graph.parent().unwrap().join(name);
|
|
write_file(&path, source);
|
|
path
|
|
}
|
|
|
|
pub fn spawn_server(&self) -> TestServer {
|
|
spawn_server(&self.graph)
|
|
}
|
|
|
|
pub fn spawn_server_with_config(&self, config: &Path) -> TestServer {
|
|
spawn_server_with_config(config)
|
|
}
|
|
|
|
pub fn spawn_server_with_config_env(&self, config: &Path, envs: &[(&str, &str)]) -> TestServer {
|
|
spawn_server_with_config_env(config, envs)
|
|
}
|
|
}
|
|
|
|
// ---- helpers moved from the monolithic tests/cli.rs ----
|
|
#[allow(unused_imports)]
|
|
use lance::Dataset;
|
|
#[allow(unused_imports)]
|
|
use lance::index::DatasetIndexExt;
|
|
#[allow(unused_imports)]
|
|
use omnigraph::db::{Omnigraph, ReadTarget};
|
|
|
|
pub const POLICY_YAML: &str = r#"
|
|
version: 1
|
|
groups:
|
|
team: [act-andrew, act-bruno]
|
|
admins: [act-andrew]
|
|
protected_branches: [main]
|
|
rules:
|
|
- id: team-read
|
|
allow:
|
|
actors: { group: team }
|
|
actions: [read]
|
|
branch_scope: any
|
|
- id: team-write
|
|
allow:
|
|
actors: { group: team }
|
|
actions: [change]
|
|
branch_scope: unprotected
|
|
- id: admins-promote
|
|
allow:
|
|
actors: { group: admins }
|
|
actions: [branch_merge]
|
|
target_branch_scope: protected
|
|
"#;
|
|
|
|
pub const POLICY_TESTS_YAML: &str = r#"
|
|
version: 1
|
|
cases:
|
|
- id: allow-feature-write
|
|
actor: act-andrew
|
|
action: change
|
|
branch: feature
|
|
expect: allow
|
|
- id: deny-main-write
|
|
actor: act-bruno
|
|
action: change
|
|
branch: main
|
|
expect: deny
|
|
"#;
|
|
|
|
pub fn manifest_dataset_version(graph: &std::path::Path) -> u64 {
|
|
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
Omnigraph::open(graph.to_string_lossy().as_ref())
|
|
.await
|
|
.unwrap()
|
|
.snapshot_of(ReadTarget::branch("main"))
|
|
.await
|
|
.unwrap()
|
|
.version()
|
|
})
|
|
}
|
|
|
|
pub fn forge_person_delete_drift(graph: &std::path::Path) -> (u64, u64) {
|
|
tokio::runtime::Runtime::new().unwrap().block_on(async {
|
|
let uri = graph.to_string_lossy();
|
|
let db = Omnigraph::open(uri.as_ref()).await.unwrap();
|
|
let snap = db
|
|
.snapshot_of(ReadTarget::branch("main"))
|
|
.await
|
|
.unwrap();
|
|
let entry = snap.entry("node:Person").unwrap();
|
|
let full_path = format!("{}/{}", uri.trim_end_matches('/'), entry.table_path);
|
|
let mut ds = Dataset::open(&full_path).await.unwrap();
|
|
let deleted = ds.delete("name = 'Alice'").await.unwrap();
|
|
assert_eq!(deleted.num_deleted_rows, 1);
|
|
let head = deleted.new_dataset.version().version;
|
|
assert!(head > entry.table_version);
|
|
(entry.table_version, head)
|
|
})
|
|
}
|
|
|
|
pub fn write_policy_config_fixture(root: &std::path::Path) -> (std::path::PathBuf, std::path::PathBuf) {
|
|
let config = root.join("omnigraph.yaml");
|
|
let policy = root.join("policy.yaml");
|
|
fs::write(
|
|
&config,
|
|
r#"
|
|
project:
|
|
name: policy-test-graph
|
|
policy:
|
|
file: ./policy.yaml
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
fs::write(&policy, POLICY_YAML).unwrap();
|
|
fs::write(root.join("policy.tests.yaml"), POLICY_TESTS_YAML).unwrap();
|
|
(config, policy)
|
|
}
|
|
|
|
pub fn write_cluster_config_fixture(root: &std::path::Path) {
|
|
fs::write(
|
|
root.join("people.pg"),
|
|
r#"
|
|
node Person {
|
|
name: String @key
|
|
age: I32?
|
|
}
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
fs::write(
|
|
root.join("people.gq"),
|
|
r#"
|
|
query find_person($name: String) {
|
|
match { $p: Person { name: $name } }
|
|
return { $p.name, $p.age }
|
|
}
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
fs::write(root.join("base.policy.yaml"), "rules: []\n").unwrap();
|
|
fs::write(
|
|
root.join("cluster.yaml"),
|
|
r#"
|
|
version: 1
|
|
metadata:
|
|
name: company-brain
|
|
state:
|
|
backend: cluster
|
|
lock: true
|
|
graphs:
|
|
knowledge:
|
|
schema: ./people.pg
|
|
queries:
|
|
find_person:
|
|
file: ./people.gq
|
|
policies:
|
|
base:
|
|
file: ./base.policy.yaml
|
|
applies_to: [knowledge]
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
pub fn init_cluster_derived_graph(root: &std::path::Path) {
|
|
init_named_cluster_graph(root, "knowledge", "people.pg");
|
|
}
|
|
|
|
pub fn init_named_cluster_graph(root: &std::path::Path, graph_id: &str, schema_file: &str) {
|
|
let graph_dir = root.join("graphs");
|
|
fs::create_dir_all(&graph_dir).unwrap();
|
|
output_success(
|
|
cli()
|
|
.arg("init")
|
|
.arg("--schema")
|
|
.arg(root.join(schema_file))
|
|
.arg(graph_dir.join(format!("{graph_id}.omni"))),
|
|
);
|
|
}
|
|
|
|
pub fn write_cluster_lock(root: &std::path::Path, lock_id: &str, operation: &str) {
|
|
let state_dir = root.join("__cluster");
|
|
fs::create_dir_all(&state_dir).unwrap();
|
|
fs::write(
|
|
state_dir.join("lock.json"),
|
|
format!(
|
|
r#"{{"version":1,"lock_id":"{lock_id}","operation":"{operation}","created_at":"1970-01-01T00:00:00Z","pid":123}}"#
|
|
),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
pub fn write_cluster_applyable_state(root: &std::path::Path) -> serde_json::Value {
|
|
let validate = parse_stdout_json(&output_success(
|
|
cli()
|
|
.arg("cluster")
|
|
.arg("validate")
|
|
.arg("--config")
|
|
.arg(root)
|
|
.arg("--json"),
|
|
));
|
|
let schema_digest = validate["resource_digests"]["schema.knowledge"]
|
|
.as_str()
|
|
.unwrap()
|
|
.to_string();
|
|
let state_dir = root.join("__cluster");
|
|
fs::create_dir_all(&state_dir).unwrap();
|
|
fs::write(
|
|
state_dir.join("state.json"),
|
|
format!(
|
|
r#"{{
|
|
"version": 1,
|
|
"state_revision": 1,
|
|
"applied_revision": {{
|
|
"resources": {{
|
|
"graph.knowledge": {{ "digest": "seed" }},
|
|
"schema.knowledge": {{ "digest": "{schema_digest}" }}
|
|
}}
|
|
}}
|
|
}}
|
|
"#
|
|
),
|
|
)
|
|
.unwrap();
|
|
validate
|
|
}
|
|
|
|
pub fn cluster_json(root: &std::path::Path, command: &str) -> serde_json::Value {
|
|
parse_stdout_json(&output_success(
|
|
cli()
|
|
.arg("cluster")
|
|
.arg(command)
|
|
.arg("--config")
|
|
.arg(root)
|
|
.arg("--json"),
|
|
))
|
|
}
|
|
|
|
pub fn write_multi_graph_cluster_fixture(root: &std::path::Path) {
|
|
write_cluster_config_fixture(root);
|
|
fs::write(
|
|
root.join("services.pg"),
|
|
r#"
|
|
node Service {
|
|
name: String @key
|
|
}
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
fs::write(
|
|
root.join("services.gq"),
|
|
r#"
|
|
query find_service($name: String) {
|
|
match { $s: Service { name: $name } }
|
|
return { $s.name }
|
|
}
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
fs::write(root.join("cluster_wide.policy.yaml"), "rules: []\n").unwrap();
|
|
fs::write(root.join("shared.policy.yaml"), "rules: []\n").unwrap();
|
|
fs::write(
|
|
root.join("cluster.yaml"),
|
|
r#"
|
|
version: 1
|
|
metadata:
|
|
name: company-brain
|
|
state:
|
|
backend: cluster
|
|
lock: true
|
|
graphs:
|
|
knowledge:
|
|
schema: ./people.pg
|
|
queries:
|
|
find_person:
|
|
file: ./people.gq
|
|
engineering:
|
|
schema: ./services.pg
|
|
queries:
|
|
find_service:
|
|
file: ./services.gq
|
|
policies:
|
|
shared:
|
|
file: ./shared.policy.yaml
|
|
applies_to: [knowledge, engineering]
|
|
cluster_wide:
|
|
file: ./cluster_wide.policy.yaml
|
|
applies_to: [cluster]
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
pub fn change_for<'j>(json: &'j serde_json::Value, resource: &str) -> &'j serde_json::Value {
|
|
json["changes"]
|
|
.as_array()
|
|
.unwrap()
|
|
.iter()
|
|
.find(|change| change["resource"] == resource)
|
|
.unwrap_or_else(|| panic!("missing change for {resource}: {json}"))
|
|
}
|
|
|
|
pub fn write_seed_fixture(root: &std::path::Path) -> std::path::PathBuf {
|
|
fs::create_dir_all(root.join("data")).unwrap();
|
|
fs::create_dir_all(root.join("build")).unwrap();
|
|
let raw_seed = root.join("data/seed.jsonl");
|
|
let seed = root.join("seed.yaml");
|
|
|
|
fs::write(
|
|
&raw_seed,
|
|
concat!(
|
|
"{\"type\":\"Decision\",\"data\":{\"slug\":\"dec-alpha\",\"intent\":\"Alpha ship\"}}\n",
|
|
"{\"type\":\"Decision\",\"data\":{\"slug\":\"dec-beta\",\"intent\":\"Beta ship\",\"embedding\":[0.1,0.2]}}\n"
|
|
),
|
|
)
|
|
.unwrap();
|
|
|
|
fs::write(
|
|
&seed,
|
|
concat!(
|
|
"graph:\n",
|
|
" slug: mr-context-graph\n",
|
|
"sources:\n",
|
|
" raw_seed: ./data/seed.jsonl\n",
|
|
"artifacts:\n",
|
|
" embedded_seed: ./build/seed.embedded.jsonl\n",
|
|
"embeddings:\n",
|
|
" model: gemini-embedding-2-preview\n",
|
|
" dimension: 4\n",
|
|
" types:\n",
|
|
" Decision:\n",
|
|
" target: embedding\n",
|
|
" fields: [slug, intent]\n"
|
|
),
|
|
)
|
|
.unwrap();
|
|
|
|
seed
|
|
}
|
|
|
|
pub fn write_seed_fixture_with_edge(root: &std::path::Path) -> std::path::PathBuf {
|
|
let seed = write_seed_fixture(root);
|
|
let raw_seed = root.join("data/seed.jsonl");
|
|
fs::write(
|
|
&raw_seed,
|
|
concat!(
|
|
"{\"type\":\"Decision\",\"data\":{\"slug\":\"dec-alpha\",\"intent\":\"Alpha ship\"}}\n",
|
|
"{\"type\":\"Decision\",\"data\":{\"slug\":\"dec-beta\",\"intent\":\"Beta ship\",\"embedding\":[0.1,0.2]}}\n",
|
|
"{\"edge\":\"Triggered\",\"from\":\"sig-alpha\",\"to\":\"dec-alpha\"}\n"
|
|
),
|
|
)
|
|
.unwrap();
|
|
seed
|
|
}
|
|
|
|
pub fn read_embedded_rows(path: std::path::PathBuf) -> Vec<Value> {
|
|
fs::read_to_string(path)
|
|
.unwrap()
|
|
.lines()
|
|
.filter(|line| !line.trim().is_empty())
|
|
.map(|line| serde_json::from_str(line).unwrap())
|
|
.collect()
|
|
}
|
|
|
|
pub fn queries_test_config(graph_uri: &str, entry: &str, gq_file: &str) -> String {
|
|
format!(
|
|
"graphs:\n local:\n uri: '{}'\n queries:\n {entry}:\n file: ./{gq_file}\n\
|
|
cli:\n graph: local\npolicy: {{}}\n",
|
|
graph_uri.replace('\'', "''")
|
|
)
|
|
}
|