use chrono::{DateTime, Utc};
use mockito::Matcher;
use std::collections::HashMap;
use uuid::Uuid;
use crate::log::{self, LogLevel};
use crate::{log_debug, log_error, log_fatal, log_information, log_warning};
fn create_config(server_url: String) -> log::ElasticConfig {
log::ElasticConfig {
url: server_url,
username: "test_user".to_string(),
password: "test_password".to_string(),
environment: log::LogEnvironment::Development,
application_name: "relastic-tests".to_string(),
log_to_console: Some(true),
}
}
#[test]
fn test_log_macros() {
log_debug!("Hello world {fish}", "Bass");
log_debug!("Hello world {fish} {snake}", "Bass", "Python");
log_debug!(
"Hello world {fish} {snake} {owl}",
"Bass",
"Python",
"Hoot hoot"
);
log_information!("Hello world {fish}", "Bass");
log_information!("Hello world {fish} {snake}", "Bass", "Python");
log_warning!("Hello world {fish}", "Bass");
log_warning!("Hello world {fish} {snake}", "Bass", "Python");
log_error!("Hello world {fish}", "Bass");
log_error!("Hello world {fish} {snake}", "Bass", "Python");
log_fatal!("Hello world {fish}", "Bass");
log_fatal!("Hello world {fish} {snake}", "Bass", "Python");
}
#[test]
fn test_logging_displayable() {
log_debug!("Hello world {fish}", LogLevel::Information);
log_information!("Hello world {fish}", LogLevel::Information);
log_warning!("Hello world {fish}", LogLevel::Information);
log_error!("Hello world {fish}", LogLevel::Information);
log_fatal!("Hello world {fish}", LogLevel::Information);
}
#[test]
fn it_does_not_panic_on_param_vs_template_mismatch() {
log_debug!("Hello world {fish}", "salmon", "snacks");
log_information!("Hello world {fish}", "salmon", "snacks");
log_warning!("Hello world {fish}", "salmon", "snacks");
log_error!("Hello world {fish}", "salmon", "snacks");
log_fatal!("Hello world {fish}", "salmon", "snacks");
}
#[test]
fn it_does_not_panic_on_template_vs_param_mismatch() {
log_debug!("Hello world {fish}{snacks}", "salmon");
log_information!("Hello world {fish}{snacks}", "salmon");
log_warning!("Hello world {fish}{snacks}", "salmon");
log_error!("Hello world {fish}{snacks}", "salmon");
log_fatal!("Hello world {fish}{snacks}", "salmon");
}
#[test]
fn test_logging() {
let reply = r#"{
"took": 1,
"errors": false,
"items": [
{
"index": {
"_index": "application-production-2021.11",
"_type": "_doc",
"_id": "abc",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1,
"status": 201
}
}
]
}"#;
let my_expected_test_value = Uuid::new_v4();
let mut server = mockito::Server::new();
let api_mock = server
.mock("POST", "/_bulk")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(reply)
.match_body(Matcher::Regex(my_expected_test_value.to_string()))
.create();
let config = create_config(server.url());
log::setup_elastic_log(config, 100);
log::information(
"This is a test {value}",
HashMap::from([("value", my_expected_test_value.to_string())]),
);
log::debug(
"This is a test {value}",
HashMap::from([("value", my_expected_test_value.to_string())]),
);
log::fatal(
"This is a test {value}",
HashMap::from([("value", my_expected_test_value.to_string())]),
);
log::warning(
"This is a test {value}",
HashMap::from([("value", my_expected_test_value.to_string())]),
);
log::flush();
assert!(api_mock.expect(4).matched());
}
#[test]
fn it_returns_error_on_inner_error_replies() {
let reply = r#"{
"took": 3,
"errors": true,
"items": [
{
"index": {
"_index": "application-production-2021.11",
"_type": "_doc",
"_id": "321",
"status": 400,
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse",
"caused_by": {
"type": "json_parse_exception",
"reason": "Unexpected character ('H' (code 72)): was expecting comma to separate Object entries\n at [Source: org.elasticsearch.common.bytes.AbstractBytesReference$MarkSupportingStreamInputWrapper@7bc49eab; line: 1, column: 137]"
}
}
}
},
{
"index": {
"_index": "application-production-2021.11",
"_type": "_doc",
"_id": "1234",
"status": 400,
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse",
"caused_by": {
"type": "json_parse_exception",
"reason": "Unexpected character ('H' (code 72)): was expecting comma to separate Object entries\n at [Source: org.elasticsearch.common.bytes.AbstractBytesReference$MarkSupportingStreamInputWrapper@d19c3fd; line: 1, column: 137]"
}
}
}
},
{
"index": {
"_index": "application-production-2021.11",
"_type": "_doc",
"_id": "123",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1,
"status": 201
}
}
]
}"#;
let mut server = mockito::Server::new();
let api_mock = server
.mock("POST", "/_bulk")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(reply)
.create();
let config = create_config(server.url());
let result = log::test_post_to_elastic(config, "".to_string());
assert!(api_mock.expect(1).matched());
assert!(result.is_err());
let err = result.expect_err("Expected error here");
match err {
log::Error::SomeLogsWereNotAccepted { errors } => {
assert_ne!(errors, "".to_string())
}
_ => {
panic!(
"Did not get the expected error here. Expected 'SomeLogsWereNotAccepted', got {}",
err
)
}
}
}
#[test]
fn it_returns_error_on_api_error_reply() {
let reply = r#"{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "The bulk request must be terminated by a newline [\\n]"
}
],
"type": "illegal_argument_exception",
"reason": "The bulk request must be terminated by a newline [\\n]"
},
"status": 400
}"#;
let mut server = mockito::Server::new();
let api_mock = server
.mock("POST", "/_bulk")
.with_status(400)
.with_header("content-type", "application/json")
.with_body(reply)
.create();
let config = create_config(server.url());
let result = log::test_post_to_elastic(config, "".to_string());
assert!(api_mock.expect(1).matched());
assert!(result.is_err());
let err = result.expect_err("Expected error here");
match err {
log::Error::ApiRejectedLogPayload { errors } => assert_ne!(errors, "".to_string()),
_ => {
panic!(
"Did not get the expected error here. Expected 'ApiRejectedPayload', got: {}",
err
)
}
}
}
#[test]
fn it_does_not_publish_debug_in_production() {
let reply = r#"{
"took": 1,
"errors": false,
"items": [
{
"index": {
"_index": "application-production-2021.11",
"_type": "_doc",
"_id": "abc",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1,
"status": 201
}
}
]
}"#;
let my_expected_test_value = Uuid::new_v4();
let mut server = mockito::Server::new();
let api_mock = server
.mock("POST", "/_bulk")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(reply)
.match_body(Matcher::Regex(my_expected_test_value.to_string()))
.create();
let config = log::ElasticConfig {
url: server.url(),
username: "test_user".to_string(),
password: "test_password".to_string(),
environment: log::LogEnvironment::Production,
application_name: "relastic-tests".to_string(),
log_to_console: Some(true),
};
log::setup_elastic_log(config, 100);
log::debug(
"This is a test {value}",
HashMap::from([("value", my_expected_test_value.to_string())]),
);
log::flush();
assert!(api_mock.expect(0).matched());
}
#[test]
fn it_formats_console_logs_with_timestamp() {
let time = "2014-11-28T21:00:09Z".parse::<DateTime<Utc>>().unwrap();
let log_string = log::create_console_message(&LogLevel::Information, "Hello!", &time);
assert_eq!("[21:00:09 INF]: Hello!", log_string);
}