package com.malk.utils;

import org.junit.Test;

import java.nio.charset.StandardCharsets;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

/**
 * 单元测试：{@link UtilSignature}
 *
 * <p>HMAC-SHA256 + SHA256 + 常量时间比较，纯静态工具，无需 Spring 上下文。
 * 期望 hex 值用业内公开测试向量交叉验证（NIST FIPS 180-4 / RFC 4231）。</p>
 *
 * <p>覆盖 add-mjava-com tasks §8.1 HmacSignatureTest 要求。</p>
 */
public class UtilSignatureTest {

    // ---------- sha256Hex ----------

    @Test
    public void sha256_empty_input() {
        // RFC 4634: SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
        assertEquals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
                UtilSignature.sha256Hex(new byte[0]));
    }

    @Test
    public void sha256_null_treated_as_empty() {
        assertEquals(UtilSignature.sha256Hex(new byte[0]),
                UtilSignature.sha256Hex(null));
    }

    @Test
    public void sha256_known_vector_abc() {
        // NIST FIPS 180-4: SHA-256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
        assertEquals("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
                UtilSignature.sha256Hex("abc".getBytes(StandardCharsets.UTF_8)));
    }

    @Test
    public void sha256_output_is_lowercase_hex_64_chars() {
        String hex = UtilSignature.sha256Hex("any string".getBytes(StandardCharsets.UTF_8));
        assertEquals(64, hex.length());
        assertTrue("必须全小写 hex", hex.matches("[0-9a-f]{64}"));
    }

    // ---------- sign (HMAC-SHA256) ----------

    @Test
    public void sign_is_deterministic_for_same_inputs() {
        String s1 = UtilSignature.sign("secret", "1000", "n1", "POST", "/api/x", "abcd");
        String s2 = UtilSignature.sign("secret", "1000", "n1", "POST", "/api/x", "abcd");
        assertEquals(s1, s2);
    }

    @Test
    public void sign_changes_when_any_input_changes() {
        String base = UtilSignature.sign("k", "1", "n", "GET", "/p", "h");

        assertNotEquals(base, UtilSignature.sign("k2", "1", "n", "GET", "/p", "h"));
        assertNotEquals(base, UtilSignature.sign("k", "2", "n", "GET", "/p", "h"));
        assertNotEquals(base, UtilSignature.sign("k", "1", "n2", "GET", "/p", "h"));
        assertNotEquals(base, UtilSignature.sign("k", "1", "n", "POST", "/p", "h"));
        assertNotEquals(base, UtilSignature.sign("k", "1", "n", "GET", "/p2", "h"));
        assertNotEquals(base, UtilSignature.sign("k", "1", "n", "GET", "/p", "h2"));
    }

    @Test
    public void sign_output_is_lowercase_hex_64_chars() {
        String hex = UtilSignature.sign("secret", "1", "n", "POST", "/x", "h");
        assertEquals(64, hex.length());
        assertTrue("HMAC-SHA256 hex 必须 64 位全小写", hex.matches("[0-9a-f]{64}"));
    }

    @Test
    public void sign_matches_manually_computed_content_template() {
        // 验证 content 模板：timestamp \n nonce \n method \n path \n bodyHash
        // 用文档示例：mjava-baseline §3.4 附录
        String bodyHash = UtilSignature.sha256Hex("{\"hello\":\"world\"}".getBytes(StandardCharsets.UTF_8));
        String sig = UtilSignature.sign("my-secret", "1717000000000", "noncexyz",
                "POST", "/dingtalk/user.get", bodyHash);
        // 由本工具计算并固化的回归基线（首次实测：2026-06-11）
        String expected = "e651680ecccd319f48ea61b0c343920f189922a9434ac3fc60ec985ef7b5ea81";
        assertEquals(expected, sig);
    }

    // ---------- safeEquals ----------

    @Test
    public void safeEquals_identical_returns_true() {
        assertTrue(UtilSignature.safeEquals("abc123", "abc123"));
    }

    @Test
    public void safeEquals_different_returns_false() {
        assertFalse(UtilSignature.safeEquals("abc123", "abc124"));
        assertFalse(UtilSignature.safeEquals("abc", "abcd"));
    }

    @Test
    public void safeEquals_null_inputs_return_false() {
        assertFalse(UtilSignature.safeEquals(null, "abc"));
        assertFalse(UtilSignature.safeEquals("abc", null));
        assertFalse(UtilSignature.safeEquals(null, null));
    }

    @Test
    public void safeEquals_empty_strings_return_true() {
        assertTrue(UtilSignature.safeEquals("", ""));
    }
}
