use hurl_core::ast::SourceInfo;
use hurl_core::error::{DisplaySourceError, OutputFormat};
use crate::report::html::Testcase;
use crate::runner::RunnerError;
use crate::util::redacted::Redact;
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Tab {
Timeline,
Run,
Source,
}
impl Testcase {
pub fn get_nav_html(&self, content: &str, tab: Tab, secrets: &[&str]) -> String {
let status = get_status_html(self.success, &self.id);
let errors = self.get_errors_html(content, secrets);
let errors_count = if !self.errors.is_empty() {
self.errors.len().to_string()
} else {
"-".to_string()
};
format!(
include_str!("resources/nav.html"),
duration = self.time_in_ms,
errors = errors,
errors_count = errors_count,
filename = self.filename,
href_run = self.run_filename(),
href_source = self.source_filename(),
href_timeline = self.timeline_filename(),
run_selected = tab == Tab::Run,
source_selected = tab == Tab::Source,
status = status,
timeline_selected = tab == Tab::Timeline,
)
}
fn get_errors_html(&self, content: &str, secrets: &[&str]) -> String {
self.errors
.iter()
.map(|(error, entry_src_info)| {
let error = error_to_html(
error,
*entry_src_info,
content,
&self.filename,
&self.source_filename(),
secrets,
);
format!("<div class=\"error\"><div class=\"error-desc\">{error}</div></div>")
})
.collect::<Vec<_>>()
.join("")
}
}
fn get_status_html(success: bool, id: &str) -> String {
let class = if success { "success" } else { "failure" };
let label = if success { "success" } else { "failure" };
format!("<span class=\"{class}\"><a href=\"{id}-timeline.html\">{label}</a></span>")
}
fn error_to_html(
error: &RunnerError,
entry_src_info: SourceInfo,
content: &str,
filename: &str,
source_filename: &str,
secrets: &[&str],
) -> String {
let line = error.source_info.start.line;
let column = error.source_info.start.column;
let message = error.render(
filename,
content,
Some(entry_src_info),
OutputFormat::Terminal(false),
);
let message = message.redact(secrets);
let message = html_escape(&message);
let old = format!("{filename}:{line}:{column}");
let href = source_filename;
let new = format!("<a href=\"{href}#l{line}\">{filename}:{line}:{column}</a>");
let message = message.replace(&old, &new);
format!("<pre><code>{message}</code></pre>")
}
fn html_escape(text: &str) -> String {
text.replace('<', "<").replace('>', ">")
}
#[cfg(test)]
mod tests {
use hurl_core::ast::SourceInfo;
use hurl_core::reader::Pos;
use crate::report::html::nav::error_to_html;
use crate::runner::{RunnerError, RunnerErrorKind};
#[test]
fn test_error_html() {
let entry_src_info = SourceInfo::new(Pos::new(1, 1), Pos::new(1, 39));
let error = RunnerError::new(
SourceInfo::new(Pos::new(4, 1), Pos::new(4, 9)),
RunnerErrorKind::AssertFailure {
actual: "<script>alert('Hi')</script>".to_string(),
expected: "Hello world".to_string(),
type_mismatch: false,
},
true,
);
let content = "GET http://localhost:8000/inline-script\n\
HTTP 200\n\
[Asserts]\n\
`Hello World`\n\
";
let filename = "a/b/c/foo.hurl";
let source_filename = "abc-source.hurl";
let html = error_to_html(
&error,
entry_src_info,
content,
filename,
source_filename,
&[],
);
assert_eq!(
html,
r##"<pre><code>Assert failure
--> <a href="abc-source.hurl#l4">a/b/c/foo.hurl:4:1</a>
|
| GET http://localhost:8000/inline-script
| ...
4 | `Hello World`
| actual: <script>alert('Hi')</script>
| expected: Hello world
|</code></pre>"##
);
}
}