#![cfg(feature = "integration-tests")]
use std::{
    fs::File,
    fs::{read_to_string, OpenOptions},
    io::{Seek, SeekFrom, Write},
    process::Command,
    time::Duration,
};

use assert_cmd::prelude::*;
use httpmock::{Method::*, MockServer};
use indoc::indoc;
use predicate::str::contains;
use predicates::prelude::*;
use serde_json::json;
use tempfile::{tempdir, tempfile};

fn get_base_command() -> Command {
    let mut cmd = Command::cargo_bin("xh").expect("binary should be present");
    cmd.env("HOME", "");
    #[cfg(target_os = "windows")]
    cmd.env("XH_TEST_MODE_WIN_HOME_DIR", "");
    cmd
}

/// Sensible default command to test with. use [`get_base_command`] if this
/// setup doesn't apply.
fn get_command() -> Command {
    let mut cmd = get_base_command();
    cmd.env("XH_TEST_MODE", "1");
    cmd.env("XH_TEST_MODE_TERM", "1");
    cmd
}

/// Do not pretend the output goes to a terminal.
fn redirecting_command() -> Command {
    let mut cmd = get_base_command();
    cmd.env("XH_TEST_MODE", "1");
    cmd
}

/// Color output (with ANSI colors) by default.
fn color_command() -> Command {
    let mut cmd = get_command();
    cmd.env("XH_TEST_MODE_COLOR", "1");
    cmd
}

#[test]
fn basic_json_post() {
    let server = MockServer::start();
    let mock = server.mock(|when, then| {
        when.method(POST)
            .header("Content-Type", "application/json")
            .json_body(json!({"name": "ali"}));
        then.header("Content-Type", "application/json")
            .json_body(json!({"got": "name", "status": "ok"}));
    });

    get_command()
        .arg("--print=b")
        .arg("--pretty=format")
        .arg("post")
        .arg(server.base_url())
        .arg("name=ali")
        .assert()
        .stdout(indoc! {r#"
        {
            "got": "name",
            "status": "ok"
        }


        "#});
    mock.assert();
}

#[test]
fn basic_get() {
    let server = MockServer::start();
    let mock = server.mock(|when, then| {
        when.method(GET);
        then.body("foobar\n");
    });

    get_command()
        .arg("--print=b")
        .arg("get")
        .arg(server.base_url())
        .assert()
        .stdout("foobar\n\n");
    mock.assert();
}

#[test]
fn basic_head() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.method(HEAD);
    });

    get_command().arg("head").arg(server.base_url()).assert();
    mock.assert();
}

#[test]
fn basic_options() {
    let server = MockServer::start();
    let mock = server.mock(|when, then| {
        when.method(OPTIONS);
        then.header("Allow", "GET, HEAD, OPTIONS");
    });

    get_command()
        .arg("-h")
        .arg("options")
        .arg(server.base_url())
        .assert()
        .stdout(contains("HTTP/1.1 200 OK"))
        .stdout(contains("allow:"));
    mock.assert();
}

#[test]
fn multiline_value() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.method(POST).body("foo=bar%0Abaz");
    });

    get_command()
        .arg("--form")
        .arg("post")
        .arg(server.base_url())
        .arg("foo=bar\nbaz")
        .assert();
    mock.assert();
}

#[test]
fn header() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.header("X-Foo", "Bar");
    });
    get_command()
        .arg(server.base_url())
        .arg("x-foo:Bar")
        .assert();
    mock.assert();
}

#[test]
fn query_param() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.query_param("foo", "bar");
    });
    get_command()
        .arg(server.base_url())
        .arg("foo==bar")
        .assert();
    mock.assert();
}

#[test]
fn json_param() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.json_body(json!({"foo": [1, 2, 3]}));
    });
    get_command()
        .arg(server.base_url())
        .arg("foo:=[1,2,3]")
        .assert();
    mock.assert();
}

#[test]
fn verbose() {
    let server = MockServer::start();
    let mock = server.mock(|when, then| {
        when.header("Connection", "keep-alive")
            .header("Content-Type", "application/json")
            .header("Content-Length", "9")
            .header("User-Agent", "xh/0.0.0 (test mode)")
            .json_body(json!({"x": "y"}));
        then.body("a body")
            .header("date", "N/A")
            .header("X-Foo", "Bar");
    });
    get_command()
        .arg("--verbose")
        .arg(server.base_url())
        .arg("x=y")
        .assert()
        .stdout(indoc! {r#"
        POST / HTTP/1.1
        accept: application/json, */*;q=0.5
        accept-encoding: gzip, br
        connection: keep-alive
        content-length: 9
        content-type: application/json
        host: http.mock
        user-agent: xh/0.0.0 (test mode)

        {
            "x": "y"
        }



        HTTP/1.1 200 OK
        content-length: 6
        date: N/A
        x-foo: Bar

        a body
        "#});
    mock.assert();
}

#[test]
fn download() {
    let dir = tempdir().unwrap();
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.body("file contents\n");
    });

    let outfile = dir.path().join("outfile");
    get_command()
        .arg("--download")
        .arg("--output")
        .arg(&outfile)
        .arg(server.base_url())
        .assert();
    mock.assert();
    assert_eq!(read_to_string(&outfile).unwrap(), "file contents\n");
}

fn get_proxy_command(
    protocol_to_request: &str,
    protocol_to_proxy: &str,
    proxy_url: &str,
) -> Command {
    let mut cmd = get_command();
    cmd.arg("--pretty=format")
        .arg("--check-status")
        .arg(format!("--proxy={}:{}", protocol_to_proxy, proxy_url))
        .arg("GET")
        .arg(format!("{}://example.test/get", protocol_to_request));
    cmd
}

#[test]
fn proxy_http_proxy() {
    let server = MockServer::start();

    let mock = server.mock(|when, then| {
        when.method(GET).header("host", "example.test");
        then.status(200);
    });

    get_proxy_command("http", "http", &server.base_url())
        .assert()
        .success();

    mock.assert();
}

#[test]
fn proxy_https_proxy() {
    let server = MockServer::start();

    let mock = server.mock(|when, then| {
        when.method(CONNECT);
        then.status(502);
    });

    get_proxy_command("https", "https", &server.base_url())
        .assert()
        .stderr(predicate::str::contains("unsuccessful tunnel"))
        .failure();
    mock.assert();
}

#[test]
fn download_generated_filename() {
    let dir = tempdir().unwrap();
    let server = MockServer::start();
    server.mock(|_when, then| {
        then.header("Content-Type", "application/json").body("file");
    });

    get_command()
        .arg("--download")
        .arg(server.url("/foo/bar/"))
        .current_dir(&dir)
        .assert();

    get_command()
        .arg("--download")
        .arg(server.url("/foo/bar/"))
        .current_dir(&dir)
        .assert();

    assert_eq!(read_to_string(dir.path().join("bar.json")).unwrap(), "file");
    assert_eq!(
        read_to_string(dir.path().join("bar.json-1")).unwrap(),
        "file"
    );
}

#[test]
fn download_supplied_filename() {
    let dir = tempdir().unwrap();
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("Content-Disposition", r#"attachment; filename="foo.bar""#)
            .body("file");
    });

    get_command()
        .arg("--download")
        .arg(server.base_url())
        .current_dir(&dir)
        .assert();
    mock.assert();
    assert_eq!(read_to_string(dir.path().join("foo.bar")).unwrap(), "file");
}

#[test]
fn download_supplied_unquoted_filename() {
    let dir = tempdir().unwrap();
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("Content-Disposition", r#"attachment; filename=foo bar baz"#)
            .body("file");
    });

    get_command()
        .arg("--download")
        .arg(server.base_url())
        .current_dir(&dir)
        .assert();
    mock.assert();
    assert_eq!(
        read_to_string(dir.path().join("foo bar baz")).unwrap(),
        "file"
    );
}

#[test]
fn decode() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("Content-Type", "text/plain; charset=latin1")
            .body(b"\xe9");
    });

    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout("é\n");
    mock.assert();
}

#[test]
fn proxy_all_proxy() {
    let server = MockServer::start();

    let mock = server.mock(|when, then| {
        when.method(CONNECT);
        then.status(502);
    });

    get_proxy_command("https", "all", &server.base_url())
        .assert()
        .stderr(predicate::str::contains("unsuccessful tunnel"))
        .failure();
    mock.assert();

    get_proxy_command("http", "all", &server.base_url())
        .assert()
        .failure();
    mock.assert();
}

#[test]
fn streaming_decode() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("Content-Type", "text/plain; charset=latin1")
            .body(b"\xe9");
    });

    get_command()
        .arg("--print=b")
        .arg("--stream")
        .arg(server.base_url())
        .assert()
        .stdout("é\n");
    mock.assert();
}

#[test]
fn only_decode_for_terminal() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("Content-Type", "text/plain; charset=latin1")
            .body(b"\xe9");
    });

    let output = redirecting_command()
        .arg(server.base_url())
        .assert()
        .get_output()
        .stdout
        .clone();
    assert_eq!(&output, b"\xe9"); // .stdout() doesn't support byte slices
    mock.assert();
}

#[test]
fn do_decode_if_formatted() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("Content-Type", "text/plain; charset=latin1")
            .body(b"\xe9");
    });

    redirecting_command()
        .arg("--pretty=all")
        .arg(server.base_url())
        .assert()
        .stdout("é");
    mock.assert();
}

#[test]
fn never_decode_if_binary() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        // this mimetype with a charset may actually be incoherent
        then.header("Content-Type", "application/octet-stream; charset=latin1")
            .body(b"\xe9");
    });

    let output = redirecting_command()
        .arg("--pretty=all")
        .arg(server.base_url())
        .assert()
        .get_output()
        .stdout
        .clone();
    assert_eq!(&output, b"\xe9");
    mock.assert();
}

#[test]
fn binary_detection() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.body(b"foo\0bar");
    });

    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        +-----------------------------------------+
        | NOTE: binary data not shown in terminal |
        +-----------------------------------------+

        "#});
    mock.assert();
}

#[test]
fn streaming_binary_detection() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.body(b"foo\0bar");
    });

    get_command()
        .arg("--print=b")
        .arg("--stream")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        +-----------------------------------------+
        | NOTE: binary data not shown in terminal |
        +-----------------------------------------+

        "#});
    mock.assert();
}

#[test]
fn request_binary_detection() {
    let mut binary_file = tempfile().unwrap();
    binary_file.write_all(b"foo\0bar").unwrap();
    binary_file.seek(SeekFrom::Start(0)).unwrap();
    redirecting_command()
        .arg("--print=B")
        .arg("--offline")
        .arg(":")
        .stdin(binary_file)
        .assert()
        .stdout(indoc! {r#"
        +-----------------------------------------+
        | NOTE: binary data not shown in terminal |
        +-----------------------------------------+


        "#});
}

#[test]
fn timeout() {
    let server = MockServer::start();
    let mock = server.mock(|_, then| {
        then.status(200).delay(Duration::from_secs_f32(0.5));
    });

    get_command()
        .arg("--timeout=0.1")
        .arg(server.base_url())
        .assert()
        .failure()
        .stderr(predicates::str::contains("operation timed out"));

    mock.assert();
}

#[test]
fn timeout_no_limit() {
    let server = MockServer::start();
    let mock = server.mock(|_, then| {
        then.status(200).delay(Duration::from_secs_f32(0.5));
    });

    get_command()
        .arg("--timeout=0")
        .arg(server.base_url())
        .assert()
        .success();

    mock.assert();
}

#[test]
fn timeout_invalid() {
    get_command()
        .arg("--timeout=-0.01")
        .arg("--offline")
        .arg(":")
        .assert()
        .failure()
        .stderr(predicates::str::contains(
            "Invalid seconds as connection timeout",
        ));
}

#[test]
fn last_supplied_proxy_wins() {
    let first_server = MockServer::start();
    let first_mock = first_server.mock(|when, then| {
        when.method(GET).header("host", "example.test");
        then.status(500);
    });
    let second_server = MockServer::start();
    let second_mock = second_server.mock(|when, then| {
        when.method(GET).header("host", "example.test");
        then.status(200);
    });

    let mut cmd = get_command();
    cmd.args(&[
        format!("--proxy=http:{}", first_server.base_url()).as_str(),
        format!("--proxy=http:{}", second_server.base_url()).as_str(),
        "GET",
        "http://example.test",
    ])
    .assert()
    .success();

    first_mock.assert_hits(0);
    second_mock.assert();
}

#[test]
fn proxy_multiple_valid_proxies() {
    let mut cmd = get_command();
    cmd.arg("--offline")
        .arg("--pretty=format")
        .arg("--proxy=http:https://127.0.0.1:8000")
        .arg("--proxy=https:socks5://127.0.0.1:8000")
        .arg("--proxy=all:http://127.0.0.1:8000")
        .arg("GET")
        .arg("http://httpbin.org/get");

    cmd.assert().success();
}

#[test]
fn check_status() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.status(404);
    });

    get_command()
        .arg("--check-status")
        .arg(server.base_url())
        .assert()
        .code(4)
        .stderr("");
    mock.assert();
}

#[test]
fn user_password_auth() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.header("Authorization", "Basic dXNlcjpwYXNz");
    });

    get_command()
        .arg("--auth=user:pass")
        .arg(server.base_url())
        .assert();
    mock.assert();
}

#[test]
fn netrc_env_user_password_auth() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.header("Authorization", "Basic dXNlcjpwYXNz");
    });

    let mut netrc = tempfile::NamedTempFile::new().unwrap();
    writeln!(
        netrc,
        "machine {}\nlogin user\npassword pass",
        server.host()
    )
    .unwrap();

    get_command()
        .env("NETRC", netrc.path())
        .arg(server.base_url())
        .assert();
    mock.assert();
}

#[test]
fn netrc_file_user_password_auth() {
    for netrc_file in [".netrc", "_netrc"].iter() {
        let server = MockServer::start();
        let mock = server.mock(|when, _then| {
            when.header("Authorization", "Basic dXNlcjpwYXNz");
        });

        let homedir = tempfile::TempDir::new().unwrap();
        let netrc_path = homedir.path().join(netrc_file);
        let mut netrc = File::create(&netrc_path).unwrap();
        writeln!(
            netrc,
            "machine {}\nlogin user\npassword pass",
            server.host()
        )
        .unwrap();

        netrc.flush().unwrap();

        get_command()
            .env("HOME", homedir.path())
            .env("XH_TEST_MODE_WIN_HOME_DIR", homedir.path())
            .arg(server.base_url())
            .assert();

        mock.assert();

        drop(netrc);
        homedir.close().unwrap();
    }
}

#[test]
fn check_status_warning() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.status(501);
    });

    redirecting_command()
        .arg("--check-status")
        .arg(server.base_url())
        .assert()
        .code(5)
        .stderr("\nxh: warning: HTTP 501 Not Implemented\n\n");
    mock.assert();
}

#[test]
fn user_auth() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.header("Authorization", "Basic dXNlcjo=");
    });

    get_command()
        .arg("--auth=user:")
        .arg(server.base_url())
        .assert();
    mock.assert();
}

#[test]
fn bearer_auth() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.header("Authorization", "Bearer SomeToken");
    });

    get_command()
        .arg("--bearer=SomeToken")
        .arg(server.base_url())
        .assert();
    mock.assert();
}

// TODO: test implicit download filenames
// For this we have to pretend the output is a tty
// This intersects with both #41 and #59

#[test]
fn verify_default_yes() {
    get_command()
        .arg("-v")
        .arg("--pretty=format")
        .arg("get")
        .arg("https://self-signed.badssl.com")
        .assert()
        .failure()
        .stdout(predicates::str::contains("GET / HTTP/1.1"))
        .stderr(predicates::str::contains("UnknownIssuer"));
}

#[test]
fn verify_explicit_yes() {
    get_command()
        .arg("-v")
        .arg("--pretty=format")
        .arg("--verify=yes")
        .arg("get")
        .arg("https://self-signed.badssl.com")
        .assert()
        .failure()
        .stdout(predicates::str::contains("GET / HTTP/1.1"))
        .stderr(predicates::str::contains("UnknownIssuer"));
}

#[test]
fn verify_no() {
    get_command()
        .arg("-v")
        .arg("--pretty=format")
        .arg("--verify=no")
        .arg("get")
        .arg("https://self-signed.badssl.com")
        .assert()
        .stdout(predicates::str::contains("GET / HTTP/1.1"))
        .stdout(predicates::str::contains("HTTP/1.1 200 OK"))
        .stderr(predicates::str::is_empty());
}

#[test]
fn verify_valid_file() {
    get_command()
        .arg("-v")
        .arg("--pretty=format")
        .arg("--verify=tests/fixtures/certs/wildcard-self-signed.pem")
        .arg("get")
        .arg("https://self-signed.badssl.com")
        .assert()
        .stdout(predicates::str::contains("GET / HTTP/1.1"))
        .stdout(predicates::str::contains("HTTP/1.1 200 OK"))
        .stderr(predicates::str::is_empty());
}

#[test]
fn cert_without_key() {
    get_command()
        .arg("-v")
        .arg("--pretty=format")
        .arg("get")
        .arg("https://client.badssl.com")
        .assert()
        .stdout(predicates::str::contains(
            "400 No required SSL certificate was sent",
        ))
        .stderr(predicates::str::is_empty());
}

#[test]
fn cert_with_key() {
    get_command()
        .arg("-v")
        .arg("--pretty=format")
        .arg("--cert=tests/fixtures/certs/client.badssl.com.crt")
        .arg("--cert-key=tests/fixtures/certs/client.badssl.com.key")
        .arg("get")
        .arg("https://client.badssl.com")
        .assert()
        .stdout(predicates::str::contains("HTTP/1.1 200 OK"))
        .stdout(predicates::str::contains("client-authenticated"))
        .stderr(predicates::str::is_empty());
}

#[test]
fn forced_json() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.method(GET)
            .header("content-type", "application/json")
            .header("accept", "application/json, */*;q=0.5");
    });
    get_command()
        .arg("--json")
        .arg(server.base_url())
        .assert()
        .success();
    mock.assert();
}

#[test]
fn forced_form() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.method(GET)
            .header("content-type", "application/x-www-form-urlencoded");
    });
    get_command()
        .arg("--form")
        .arg(server.base_url())
        .assert()
        .success();
    mock.assert();
}

#[test]
fn forced_multipart() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.method(POST).header_exists("content-type").body("");
    });
    get_command()
        .arg("--multipart")
        .arg(server.base_url())
        .assert()
        .success();
    mock.assert();
}

#[test]
fn formatted_json_output() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("content-type", "application/json")
            .body(r#"{"":0}"#);
    });
    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        {
            "": 0
        }


        "#});
    mock.assert();
}

#[test]
fn inferred_json_output() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("content-type", "text/plain").body(r#"{"":0}"#);
    });
    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        {
            "": 0
        }


        "#});
    mock.assert();
}

#[test]
fn inferred_json_javascript_output() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        then.header("content-type", "application/javascript")
            .body(r#"{"":0}"#);
    });
    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        {
            "": 0
        }


        "#});
    mock.assert();
}

#[test]
fn inferred_nonjson_output() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        // Trailing comma makes it invalid JSON, though formatting would still work
        then.header("content-type", "text/plain").body(r#"{"":0,}"#);
    });
    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        {"":0,}
        "#});
    mock.assert();
}

#[test]
fn noninferred_json_output() {
    let server = MockServer::start();
    let mock = server.mock(|_when, then| {
        // Valid JSON, but not declared as text
        then.header("content-type", "application/octet-stream")
            .body(r#"{"":0}"#);
    });
    get_command()
        .arg("--print=b")
        .arg(server.base_url())
        .assert()
        .stdout(indoc! {r#"
        {"":0}
        "#});
    mock.assert();
}

#[test]
fn mixed_stdin_request_items() {
    let input_file = tempfile().unwrap();
    redirecting_command()
        .arg("--offline")
        .arg(":")
        .arg("x=3")
        .stdin(input_file)
        .assert()
        .failure()
        .stderr(predicate::str::contains(
            "Request body (from stdin) and request data (key=value) cannot be mixed",
        ));
}

#[test]
fn multipart_stdin() {
    let input_file = tempfile().unwrap();
    redirecting_command()
        .arg("--offline")
        .arg("--multipart")
        .arg(":")
        .stdin(input_file)
        .assert()
        .failure()
        .stderr(predicate::str::contains(
            "Cannot build a multipart request body from stdin",
        ));
}

#[test]
fn default_json_for_raw_body() {
    let server = MockServer::start();
    let mock = server.mock(|when, _then| {
        when.header("content-type", "application/json");
    });
    let input_file = tempfile().unwrap();
    redirecting_command()
        .arg(server.base_url())
        .stdin(input_file)
        .assert()
        .success();
    mock.assert();
}

#[test]
fn body_from_file() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.header("content-type", "text/plain")
            .body("Hello world\n");
    });

    let dir = tempfile::tempdir().unwrap();
    let filename = dir.path().join("input.txt");
    OpenOptions::new()
        .create(true)
        .write(true)
        .open(&filename)
        .unwrap()
        .write_all(b"Hello world\n")
        .unwrap();

    get_command()
        .arg(server.base_url())
        .arg(format!("@{}", filename.to_string_lossy()))
        .assert()
        .success();

    mock.assert();
}

#[test]
fn body_from_file_with_explicit_mimetype() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.header("content-type", "image/png")
            .body("Hello world\n");
    });

    let dir = tempfile::tempdir().unwrap();
    let filename = dir.path().join("input.txt");
    OpenOptions::new()
        .create(true)
        .write(true)
        .open(&filename)
        .unwrap()
        .write_all(b"Hello world\n")
        .unwrap();

    get_command()
        .arg(server.base_url())
        .arg(format!("@{};type=image/png", filename.to_string_lossy()))
        .assert()
        .success();

    mock.assert();
}

#[test]
fn body_from_file_with_fallback_mimetype() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.header("content-type", "application/json")
            .body("Hello world\n");
    });

    let dir = tempfile::tempdir().unwrap();
    let filename = dir.path().join("input");
    OpenOptions::new()
        .create(true)
        .write(true)
        .open(&filename)
        .unwrap()
        .write_all(b"Hello world\n")
        .unwrap();

    get_command()
        .arg(server.base_url())
        .arg(format!("@{}", filename.to_string_lossy()))
        .assert()
        .success();

    mock.assert();
}

#[test]
fn no_double_file_body() {
    get_command()
        .arg(":")
        .arg("@foo")
        .arg("@bar")
        .assert()
        .failure()
        .stderr(predicate::str::contains(
            "Can't read request from multiple files",
        ));
}

#[test]
fn print_body_from_file() {
    let dir = tempfile::tempdir().unwrap();
    let filename = dir.path().join("input");
    OpenOptions::new()
        .create(true)
        .write(true)
        .open(&filename)
        .unwrap()
        .write_all(b"Hello world\n")
        .unwrap();

    get_command()
        .arg("--offline")
        .arg(":")
        .arg(format!("@{}", filename.to_string_lossy()))
        .assert()
        .success()
        .stdout(predicate::str::contains("Hello world"));
}

#[test]
fn colored_headers() {
    color_command()
        .arg("--offline")
        .arg(":")
        .assert()
        .success()
        // Color
        .stdout(predicate::str::contains("\x1b[4m"))
        // Reset
        .stdout(predicate::str::contains("\x1b[0m"));
}

#[test]
fn colored_body() {
    color_command()
        .arg("--offline")
        .arg(":")
        .arg("x:=3")
        .assert()
        .success()
        .stdout(predicate::str::contains("\x1b[34m3\x1b[0m"));
}

#[test]
fn force_color_pipe() {
    redirecting_command()
        .arg("--ignore-stdin")
        .arg("--offline")
        .arg("--pretty=colors")
        .arg(":")
        .arg("x:=3")
        .assert()
        .success()
        .stdout(predicate::str::contains("\x1b[34m3\x1b[0m"));
}

#[test]
fn request_json_keys_order_is_preserved() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.body(r#"{"name":"ali","age":24}"#);
    });

    get_command()
        .arg("get")
        .arg(server.base_url())
        .arg("name=ali")
        .arg("age:=24")
        .assert();
    mock.assert();
}

#[test]
fn data_field_from_file() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.body(r#"{"ids":"[1,2,3]"}"#);
    });

    let mut text_file = tempfile::NamedTempFile::new().unwrap();
    write!(text_file, "[1,2,3]").unwrap();

    get_command()
        .arg(server.base_url())
        .arg(format!("ids=@{}", text_file.path().to_string_lossy()))
        .assert();
    mock.assert();
}

#[test]
fn data_field_from_file_in_form_mode() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.body(r#"message=hello+world"#);
    });

    let mut text_file = tempfile::NamedTempFile::new().unwrap();
    write!(text_file, "hello world").unwrap();

    get_command()
        .arg(server.base_url())
        .arg("--form")
        .arg(format!("message=@{}", text_file.path().to_string_lossy()))
        .assert();
    mock.assert();
}

#[test]
fn json_field_from_file() {
    let server = MockServer::start();
    let mock = server.mock(|when, _| {
        when.body(r#"{"ids":[1,2,3]}"#);
    });

    let mut json_file = tempfile::NamedTempFile::new().unwrap();
    writeln!(json_file, "[1,2,3]").unwrap();

    get_command()
        .arg(server.base_url())
        .arg(format!("ids:=@{}", json_file.path().to_string_lossy()))
        .assert();
    mock.assert();
}

#[test]
fn accept_encoding_not_modifiable_in_download_mode() {
    let server = MockServer::start();
    let mock = server.mock(|when, then| {
        when.header("accept-encoding", "identity");
        then.body(r#"{"ids":[1,2,3]}"#);
    });

    let dir = tempdir().unwrap();
    get_command()
        .current_dir(&dir)
        .arg(server.base_url())
        .arg("--download")
        .arg("accept-encoding:gzip")
        .assert();
    mock.assert();
}
