mod support;
use support::TestWorkspace;
fn assert_verify_failure(
source: &str,
expected_message: &str,
expected_location: &str,
expected_excerpt: &str,
) {
let workspace = TestWorkspace::new();
workspace.write_file("collection.hen", source);
let output = workspace.run_hen(["verify", "collection.hen"]);
assert_eq!(output.status_code, 1, "stderr: {}", output.stderr);
assert!(output.stdout.is_empty(), "stdout: {}", output.stdout);
assert!(
output.stderr.contains(expected_message),
"stderr: {}",
output.stderr
);
assert!(
output.stderr.contains(expected_location),
"stderr: {}",
output.stderr
);
assert!(
output.stderr.contains(expected_excerpt),
"stderr: {}",
output.stderr
);
}
#[test]
fn verify_reports_requests_and_required_inputs() {
let workspace = TestWorkspace::new();
workspace.write_file(
"fragments/imported.hen",
r#"Imported request
POST https://example.com/imported/[[ token ]]
"#,
);
workspace.write_file(
"collection.hen",
r#"Verify Fixture
Verifies imported requests.
? region = [[ region = us-east-1 ]]
---
Root request
GET https://example.com/root
---
<< fragments/imported.hen
"#,
);
let output = workspace.run_hen(["verify", "collection.hen"]);
assert_eq!(output.status_code, 0, "stderr: {}", output.stderr);
assert!(
output.stdout.contains("Verification passed"),
"stdout: {}",
output.stdout
);
assert!(
output.stdout.contains("[0] GET https://example.com/root"),
"stdout: {}",
output.stdout
);
assert!(
output
.stdout
.contains("[1] POST https://example.com/imported/[[ token ]]"),
"stdout: {}",
output.stdout
);
assert!(
output.stdout.contains("Required inputs"),
"stdout: {}",
output.stdout
);
assert!(output.stdout.contains("token"), "stdout: {}", output.stdout);
assert!(
output.stdout.contains("region (default: us-east-1)"),
"stdout: {}",
output.stdout
);
assert!(output.stderr.is_empty(), "stderr: {}", output.stderr);
}
#[test]
fn verify_does_not_execute_shell_substitutions() {
let workspace = TestWorkspace::new();
workspace.write_file(
"collection.hen",
r#"Shell Verify
$ shell_value = $(touch side-effect.txt)
---
Shell request
GET https://example.com/{{ shell_value }}
"#,
);
let output = workspace.run_hen(["verify", "collection.hen"]);
assert_eq!(output.status_code, 0, "stderr: {}", output.stderr);
assert!(
!workspace.root().join("side-effect.txt").exists(),
"verify executed a shell substitution"
);
}
#[test]
fn verify_accepts_websocket_authoring_slice() {
let workspace = TestWorkspace::new();
workspace.write_file(
"collection.hen",
r#"WebSocket Verify
---
Open socket
protocol = ws
session = chat
GET wss://example.com/chat
---
Send hello
session = chat
~~~json
{"type":"hello"}
~~~
---
Receive reply
session = chat
receive
within = 2s
"#,
);
let output = workspace.run_hen(["verify", "collection.hen"]);
assert_eq!(output.status_code, 0, "stderr: {}", output.stderr);
assert!(
output.stdout.contains("[0] GET wss://example.com/chat"),
"stdout: {}",
output.stdout
);
assert!(
output.stdout.contains("[1] GET wss://example.com/chat"),
"stdout: {}",
output.stdout
);
assert!(
output.stdout.contains("[2] GET wss://example.com/chat"),
"stdout: {}",
output.stdout
);
}
#[test]
fn verify_accepts_websocket_exchange_authoring_slice() {
let workspace = TestWorkspace::new();
workspace.write_file(
"collection.hen",
r#"WebSocket Exchange Verify
---
Open socket
protocol = ws
session = chat
GET wss://example.com/chat
---
Send hello
session = chat
within = 2s
~~~json
{"type":"hello"}
~~~
"#,
);
let output = workspace.run_hen(["verify", "collection.hen"]);
assert_eq!(output.status_code, 0, "stderr: {}", output.stderr);
assert!(
output.stdout.contains("[0] GET wss://example.com/chat"),
"stdout: {}",
output.stdout
);
assert!(
output.stdout.contains("[1] GET wss://example.com/chat"),
"stdout: {}",
output.stdout
);
}
#[test]
fn verify_reports_parse_failures_without_running_requests() {
let workspace = TestWorkspace::new();
workspace.write_file(
"collection.hen",
r#"Broken Verify
---
Broken assertion
GET https://example.com
^ & body =~ /foo/
"#,
);
let output = workspace.run_hen(["verify", "collection.hen"]);
assert_eq!(output.status_code, 1, "stderr: {}", output.stderr);
assert!(output.stdout.is_empty(), "stdout: {}", output.stdout);
assert!(output
.stderr
.contains("No valid operator found in assertion"));
}
#[test]
fn verify_reports_unknown_schema_reference_at_declaration_span() {
assert_verify_failure(
r#"name = Verify Unknown Reference
description = Ensure unknown schema references point at the owning declaration.
schema User {
profile: Profile
}
---
Get user
GET https://example.com/users/1
"#,
"User references unknown validation target Profile",
"--> 3:1",
"schema User {",
);
}
#[test]
fn verify_reports_invalid_scalar_base_reference_at_declaration_span() {
assert_verify_failure(
r#"name = Verify Invalid Scalar Base
description = Ensure schema-backed scalar bases point at the scalar declaration.
schema User {
id: UUID
}
scalar UserAlias = User
---
Get user
GET https://example.com/users/1
"#,
"scalar UserAlias cannot use schema User as a scalar base",
"--> 6:1",
"scalar UserAlias = User",
);
}
#[test]
fn verify_reports_unknown_schema_assertion_target_at_assertion_span() {
assert_verify_failure(
r#"name = Verify Unknown Assertion Target
description = Ensure schema assertions are validated during verify.
---
Get user
GET https://example.com/users/1
^ & body === MissingUser
"#,
"Unknown schema validation target 'MissingUser'",
"--> 6:1",
"^ & body === MissingUser",
);
}
#[test]
fn verify_reports_duplicate_declaration_names_at_second_declaration() {
assert_verify_failure(
r#"name = Verify Duplicate Declaration
description = Ensure duplicate declaration names point at the second definition.
schema User {
id: UUID
}
scalar User = string
---
Get user
GET https://example.com/users/1
"#,
"User is already defined",
"--> 6:1",
"scalar User = string",
);
}
#[test]
fn verify_reports_reserved_name_redefinitions_at_declaration_span() {
assert_verify_failure(
r#"name = Verify Reserved Name
description = Ensure built-in names cannot be redefined.
scalar UUID = string
---
Get user
GET https://example.com/users/1
"#,
"UUID is reserved and cannot be redefined",
"--> 3:1",
"scalar UUID = string",
);
}
#[test]
fn verify_reports_invalid_scalar_predicate_combinations_at_declaration_span() {
assert_verify_failure(
r#"name = Verify Invalid Scalar Expression
description = Ensure invalid scalar base combinations point at the declaration.
scalar Broken = string & integer
---
Get user
GET https://example.com/users/1
"#,
"scalar expressions can only declare one base type",
"--> 3:17",
"scalar Broken = string & integer",
);
}
#[test]
fn verify_reports_misplaced_declarations_after_requests_begin() {
assert_verify_failure(
r#"name = Verify Misplaced Declaration
description = Ensure declarations after requests begin get a targeted error.
---
Get user
GET https://example.com/users/1
schema User {
id: UUID
}
"#,
"schema and scalar declarations must appear before the first ---",
"--> 6:1",
"schema User {",
);
}