chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
//! Integration tests for the `jsonlgrep` command.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use jsonlgrep::{Query, parse_query};
|
||||
use predicates::prelude::*;
|
||||
|
||||
fn cargo_command() -> Command {
|
||||
Command::cargo_bin("jsonlgrep").expect("binary")
|
||||
}
|
||||
|
||||
fn cargo_binary() -> PathBuf {
|
||||
assert_cmd::cargo::cargo_bin("jsonlgrep")
|
||||
}
|
||||
|
||||
fn powershell_command(script: String) -> Command {
|
||||
let mut command = Command::new("pwsh");
|
||||
command.args(["-NoProfile", "-Command"]).arg(script);
|
||||
command
|
||||
}
|
||||
|
||||
fn ps_quote(value: impl std::fmt::Display) -> String {
|
||||
format!("'{}'", value.to_string().replace('\'', "''"))
|
||||
}
|
||||
|
||||
fn fixture(path: &str) -> PathBuf {
|
||||
let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("..")
|
||||
.join("fixtures")
|
||||
.join(path);
|
||||
assert!(
|
||||
fixture.exists(),
|
||||
"missing fixture `{path}` at {}",
|
||||
fixture.display()
|
||||
);
|
||||
fixture
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_field_queries() {
|
||||
assert_eq!(
|
||||
parse_query("level=error").expect("exact query"),
|
||||
Query::FieldEquals {
|
||||
field: "level".into(),
|
||||
value: "error".into(),
|
||||
}
|
||||
);
|
||||
assert!(matches!(
|
||||
parse_query("msg~=fail").expect("regex query"),
|
||||
Query::FieldRegex { field, .. } if field == "msg"
|
||||
));
|
||||
assert_eq!(
|
||||
parse_query("panic").expect("text query"),
|
||||
Query::Contains("panic".into())
|
||||
);
|
||||
assert!(matches!(
|
||||
parse_query("level!=info").expect("negative exact query"),
|
||||
Query::FieldNotEquals { field, value } if field == "level" && value == "info"
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_query("msg!~=login").expect("negative regex query"),
|
||||
Query::FieldNotRegex { field, .. } if field == "msg"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_and_projects_jsonl_records() {
|
||||
cargo_command()
|
||||
.arg("level=error")
|
||||
.arg(fixture("jsonl/events.jsonl"))
|
||||
.arg("--pick")
|
||||
.arg("ts,msg")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains(
|
||||
"ts=2026-04-20T12:01:00Z msg=failed login",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_and_json_projections_survive_conditional_raw_retention() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let path = temp.path().join("spaced.jsonl");
|
||||
std::fs::write(
|
||||
&path,
|
||||
"{ \"level\" : \"error\" , \"msg\" : \"failed login\" , \"ts\" : \"2026-04-20T12:01:00Z\" }\n",
|
||||
)
|
||||
.expect("fixture");
|
||||
|
||||
let mut text = cargo_command();
|
||||
text.arg("level=error")
|
||||
.arg(&path)
|
||||
.arg("--pick")
|
||||
.arg("ts,msg")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("ts=2026-04-20T12:01:00Z msg=failed login\n");
|
||||
|
||||
let mut json = cargo_command();
|
||||
json.arg("level=error")
|
||||
.arg(&path)
|
||||
.arg("--pick")
|
||||
.arg("ts,msg")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"ts\":\"2026-04-20T12:01:00Z\""))
|
||||
.stdout(predicate::str::contains("\"msg\":\"failed login\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counts_matches_as_json() {
|
||||
cargo_command()
|
||||
.arg("msg~=login")
|
||||
.arg(fixture("jsonl/events.jsonl"))
|
||||
.arg("--count")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("{\"count\":2}\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_powershell_pipeline() {
|
||||
let binary = cargo_binary();
|
||||
let input = fixture("jsonl/events.jsonl");
|
||||
let script = format!(
|
||||
"[System.IO.File]::ReadLines({}) | & {} 'level=warn'",
|
||||
ps_quote(input.display()),
|
||||
ps_quote(binary.display())
|
||||
);
|
||||
|
||||
powershell_command(script)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("cache warmup"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_nested_fields_and_negative_predicates() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let path = temp.path().join("nested.jsonl");
|
||||
std::fs::write(
|
||||
&path,
|
||||
concat!(
|
||||
"{\"event\":{\"user\":{\"name\":\"alice\"}},\"status\":500,\"ok\":false}\n",
|
||||
"{\"event\":{\"user\":{\"name\":\"bob\"}},\"status\":200,\"ok\":true}\n",
|
||||
"{\"event\":{\"user\":{\"name\":\"carol\"}},\"status\":404,\"ok\":false}\n"
|
||||
),
|
||||
)
|
||||
.expect("fixture");
|
||||
|
||||
let mut nested = cargo_command();
|
||||
nested
|
||||
.arg("event.user.name=alice")
|
||||
.arg(&path)
|
||||
.arg("--pick")
|
||||
.arg("event.user.name,status")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("event.user.name=alice status=500"));
|
||||
|
||||
let mut negative_exact = cargo_command();
|
||||
negative_exact
|
||||
.arg("status!=200")
|
||||
.arg(&path)
|
||||
.arg("--pick")
|
||||
.arg("event.user.name,status")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("alice"))
|
||||
.stdout(predicate::str::contains("carol"))
|
||||
.stdout(predicate::str::contains("bob").not());
|
||||
|
||||
let mut negative_regex = cargo_command();
|
||||
negative_regex
|
||||
.arg("event.user.name!~=^(alice|bob)$")
|
||||
.arg(&path)
|
||||
.arg("--pick")
|
||||
.arg("event.user.name")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("event.user.name=carol\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_includes_examples_for_nested_and_powershell_usage() {
|
||||
cargo_command()
|
||||
.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("event.user.name=alice"))
|
||||
.stdout(predicate::str::contains("field!=value"))
|
||||
.stdout(predicate::str::contains("ConvertFrom-Json"))
|
||||
.stdout(predicate::str::contains("--toon"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mistyped_regex_operator_reports_the_fix() {
|
||||
let error = parse_query("level~warn|error").expect_err("missing equals should fail");
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("did you mean 'level~=warn|error'"),
|
||||
"unexpected query error: {error}"
|
||||
);
|
||||
|
||||
cargo_command()
|
||||
.arg("level~warn|error")
|
||||
.arg(fixture("jsonl/events.jsonl"))
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("did you mean 'level~=warn|error'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stops_after_limit_before_parsing_invalid_trailing_jsonl() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let path = temp.path().join("limited.jsonl");
|
||||
std::fs::write(
|
||||
&path,
|
||||
concat!(
|
||||
"{\"level\":\"error\",\"msg\":\"first\"}\n",
|
||||
"{not valid json}\n",
|
||||
"{\"level\":\"error\",\"msg\":\"third\"}\n"
|
||||
),
|
||||
)
|
||||
.expect("fixture");
|
||||
|
||||
cargo_command()
|
||||
.arg("level=error")
|
||||
.arg(&path)
|
||||
.arg("--input-format")
|
||||
.arg("jsonl")
|
||||
.arg("--count")
|
||||
.arg("--limit")
|
||||
.arg("1")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("1\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_limit() {
|
||||
cargo_command()
|
||||
.arg("level=error")
|
||||
.arg(fixture("jsonl/events.jsonl"))
|
||||
.arg("--limit")
|
||||
.arg("0")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(2)
|
||||
.stderr(predicate::str::contains("--limit must be greater than 0"));
|
||||
}
|
||||
Reference in New Issue
Block a user