dApp Docs/测试与CI-CD指南
Development reference. Not independently verified for production.

MSG Chain 测试与 CI/CD 指南 — 完整质量保障体系

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。


目录

  1. 测试体系总览
  2. Rust 单元测试
  3. 集成测试
  4. Go 端测试
  5. 模糊测试与属性测试
  6. CI/CD 管线
  7. 质量门禁
  8. 合约交付工作流
  9. 自动化脚本
  10. 覆盖率与报告
  11. DApp 测试
  12. 附录

1. 测试体系总览

1.1 MSG Chain 测试金字塔

MSG Chain 采用经典测试金字塔架构,从底层到顶层覆盖完整的质量保障体系。类似 Cosmos SDK 的测试架构,但针对 MSG Chain 的后量子密码学(Dilithium-5)、CosmWasm 合约体系、以及 AI Agent 经济体进行了定制化扩展。

                    ┌──────────┐
                    │   E2E    │  ← 端到端测试 (Playwright/Cypress)
                    │  测试     │
                   ┌┴──────────┴┐
                   │  集成测试   │  ← cw-multi-test / Go testnet
                   │ CosmWasm   │  ← 多合约交互 / 跨合约调用
                  ┌┴────────────┴┐
                  │  合约单元测试 │  ← #[cfg(test)] + mock_dependencies
                  │  Rust 测试   │  ← cargo test 每个合约包
                 ┌┴──────────────┴┐
                 │  Go 端测试     │  ← make test (pkg 测试)
                 │  节点测试      │  ← make test-quantum (Dilithium-5 PQ)
                ┌┴────────────────┴┐
                │  模糊测试         │  ← proptest / fuzz testing
                │  属性测试         │  ← 状态不变量检查
               ┌┴──────────────────┴┐
               │  静态分析           │  ← golangci-lint / go vet / gofmt
               │  安全扫描           │  ← gosec / cargo audit
               └────────────────────┘

1.2 测试命令速查表

所有命令均来自 MSG Chain 的 Makefile 和 command_registry.json:

命令 Make 目标 阶段 产出
make deps deps prepare Go 依赖安装完成
make lint lint quality_gate gofmt 检查 + go vet + golangci-lint
make test test quality_gate pkg 测试结果
make test-quantum test-quantum quality_gate pkg/quantum 测试结果
make build-linux build-linux build bin/genesis_node_linux, bin/quantum_node_linux
make ci-contracts ci-contracts contract_validation 所有合约 wasm 构建 + cargo test
make package package artifact_packaging deploy-<version>, deploy-<version>.tar.gz
make test-coverage test-coverage coverage coverage.out, coverage.html
make ci-tests ci-tests CI 带 race detector 和覆盖率
make ci-security ci-security CI gosec + cargo audit

1.3 核心测试原则

┌─────────────────────────────────────────────────────────────┐
│ MSG Chain 测试原则:                                         │
│                                                             │
│ 1. 每个合约包必须包含单元测试 + 集成测试                       │
│ 2. 所有 Dilithium-5 密码学操作必须通过 test-quantum 验证       │
│ 3. CI 中先跑 lint,再跑 test,最后 ci-contracts                │
│ 4. 没有真实 receipt/query/log 时,不得把 CI 通过等同于         │
│    链上上线成功                                                │
│ 5. 生产发布必须经过 3 道人工审批门禁                           │
└─────────────────────────────────────────────────────────────┘

2. Rust 单元测试

2.1 CosmWasm 测试基础

MSG Chain 的 CosmWasm 合约测试遵循标准 CosmWasm 测试模式,使用 cosmwasm-std 提供的 mock 工具。类似 Hardhat 的合约测试框架,但使用 Rust 的 #[cfg(test)] 模式。

每个合约包位于 contracts/cosmwasm/all/<合约名>/,测试可以通过以下命令运行:

# 运行单个合约的全部测试
cd contracts/cosmwasm/all/aidid_did_registry_v1 && cargo test

# 运行特定测试
cd contracts/cosmwasm/all/aidid_did_registry_v1 && cargo test test_create_and_resolve_did

# 构建 WASM(测试前需要确保编译通过)
cd contracts/cosmwasm/all/aidid_did_registry_v1 && cargo build --target wasm32-unknown-unknown --release

2.2 单元测试标准模式

每个 CosmWasm 合约的标准测试结构如下。以 aidid_did_registry_v1 为例,完整的嵌入测试代码:

2.2.1 测试模块结构

#[cfg(test)]
mod tests {
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{from_binary, Addr, Binary, Uint128};

    use crate::contract::{instantiate, execute, query, execute_create_did};
    use crate::msg::{
        InstantiateMsg, ExecuteMsg, QueryMsg, VerificationMethod, ServiceEndpoint,
        ResolveDIDResponse, CheckDIDActiveResponse, ListDIDsByControllerResponse,
    };
    use crate::error::ContractError;
    use crate::state::{Config, CONFIG, DID_DOCUMENTS};
}

2.2.2 辅助函数

    fn create_test_vm(id: &str) -> VerificationMethod {
        VerificationMethod {
            id: id.to_string(),
            controller: "controller".to_string(),
            key_type: "Dilithium5VerificationKey2026".to_string(),
            public_key_multibase: Some("z6Mk".to_string()),
            dilithium5_public_key: Some("dilithium5_pk_example".to_string()),
        }
    }

    fn create_test_service(id: &str) -> ServiceEndpoint {
        ServiceEndpoint {
            id: id.to_string(),
            service_type: "LinkedDomains".to_string(),
            service_endpoint: "https://agent.example.com".to_string(),
            metadata: None,
        }
    }

    fn setup_contract() -> (cosmwasm_std::OwnedDeps<cosmwasm_std::MemoryStorage, cosmwasm_std::testing::MockApi, cosmwasm_std::testing::MockQuerier>, cosmwasm_std::Env) {
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "MSG DID Registry".to_string(),
            },
        )
        .unwrap();

        (deps, env)
    }

2.2.3 Instantiate 测试

    #[test]
    fn proper_initialization() {
        let mut deps = mock_dependencies();
        let env = mock_env();
        let info = mock_info("admin", &[]);

        let msg = InstantiateMsg {
            admin: "admin".to_string(),
            registry_name: "MSG DID Registry".to_string(),
        };
        let res = instantiate(deps.as_mut(), env, info, msg).unwrap();

        // 验证响应属性
        assert_eq!(res.attributes.len(), 3);
        assert_eq!(res.attributes[0].value, "instantiate");
        assert_eq!(res.attributes[1].value, "admin");
        assert_eq!(res.attributes[2].value, "MSG DID Registry");

        // 验证状态
        let config = CONFIG.load(&deps.storage).unwrap();
        assert_eq!(config.admin, "admin");
        assert_eq!(config.registry_name, "MSG DID Registry");
    }

2.2.4 Execute — Create DID 测试

    #[test]
    fn create_and_resolve_did() {
        let mut deps = mock_dependencies();
        let env = mock_env();

        // 实例化
        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "test".to_string(),
            },
        )
        .unwrap();

        // 创建 DID
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: "did:msg:agent:test-1".to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![create_test_vm("vm-1")],
                services: vec![create_test_service("svc-1")],
                metadata: None,
            },
        )
        .unwrap();

        // 查询验证
        let res = query(
            deps.as_ref(),
            env.clone(),
            QueryMsg::ResolveDID {
                did_id: "did:msg:agent:test-1".to_string(),
            },
        )
        .unwrap();
        let response: ResolveDIDResponse = from_binary(&res).unwrap();
        assert_eq!(response.document.did_id, "did:msg:agent:test-1");
        assert!(response.document.active);
        assert_eq!(response.document.verification_methods.len(), 1);
        assert_eq!(response.document.services.len(), 1);
    }

2.2.5 Execute — Deactivate DID 测试

    #[test]
    fn deactivate_did() {
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "test".to_string(),
            },
        )
        .unwrap();

        let did_id = "did:msg:agent:to-deactivate";

        // 创建
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: did_id.to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        // 停用
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::DeactivateDID {
                did_id: did_id.to_string(),
            },
        )
        .unwrap();

        // 验证
        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::CheckDIDActive {
                did_id: did_id.to_string(),
            },
        )
        .unwrap();
        let response: CheckDIDActiveResponse = from_binary(&res).unwrap();
        assert!(!response.active);
    }

2.2.6 错误路径测试

    #[test]
    fn create_duplicate_did_fails() {
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "test".to_string(),
            },
        )
        .unwrap();

        let did_id = "did:msg:agent:dup";

        // 首次创建成功
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: did_id.to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        // 重复创建失败
        let err = execute(
            deps.as_mut(),
            env,
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: did_id.to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap_err();
        assert_eq!(
            err,
            ContractError::DIDAlreadyExists {
                did_id: did_id.to_string()
            }
        );
    }

2.2.7 权限控制测试

    #[test]
    fn only_controller_can_modify_did() {
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "test".to_string(),
            },
        )
        .unwrap();

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: "did:msg:agent:protected".to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        let err = execute(
            deps.as_mut(),
            env,
            mock_info("attacker", &[]),
            ExecuteMsg::UpdateDID {
                did_id: "did:msg:agent:protected".to_string(),
                verification_methods: None,
                services: None,
                metadata: Some(Binary::from(b"evil".as_ref())),
            },
        )
        .unwrap_err();
        assert_eq!(
            err,
            ContractError::NotController {
                did_id: "did:msg:agent:protected".to_string(),
                controller: "attacker".to_string(),
            }
        );
    }

2.2.8 验证方法操作测试

    #[test]
    fn add_and_remove_verification_method() {
        let (mut deps, env) = setup_contract();

        let did_id = "did:msg:agent:vm-test";

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: did_id.to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        // 添加验证方法
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::AddVerificationMethod {
                did_id: did_id.to_string(),
                method: create_test_vm("vm-new"),
            },
        )
        .unwrap();

        let res = query(
            deps.as_ref(),
            env.clone(),
            QueryMsg::ResolveDID {
                did_id: did_id.to_string(),
            },
        )
        .unwrap();
        let response: ResolveDIDResponse = from_binary(&res).unwrap();
        assert_eq!(response.document.verification_methods.len(), 1);

        // 移除验证方法
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::RemoveVerificationMethod {
                did_id: did_id.to_string(),
                method_id: "vm-new".to_string(),
            },
        )
        .unwrap();

        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::ResolveDID {
                did_id: did_id.to_string(),
            },
        )
        .unwrap();
        let response: ResolveDIDResponse = from_binary(&res).unwrap();
        assert_eq!(response.document.verification_methods.len(), 0);
    }

2.2.9 Service 操作测试

    #[test]
    fn add_and_remove_service() {
        let (mut deps, env) = setup_contract();

        let did_id = "did:msg:agent:svc-test";

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: did_id.to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::AddService {
                did_id: did_id.to_string(),
                service: create_test_service("svc-new"),
            },
        )
        .unwrap();

        execute(
            deps.as_mut(),
            env,
            mock_info("controller", &[]),
            ExecuteMsg::RemoveService {
                did_id: did_id.to_string(),
                service_id: "svc-new".to_string(),
            },
        )
        .unwrap();
    }

    #[test]
    fn remove_nonexistent_service_fails() {
        let (mut deps, env) = setup_contract();

        let did_id = "did:msg:agent:no-svc";

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: did_id.to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        let err = execute(
            deps.as_mut(),
            env,
            mock_info("controller", &[]),
            ExecuteMsg::RemoveService {
                did_id: did_id.to_string(),
                service_id: "nonexistent-svc".to_string(),
            },
        )
        .unwrap_err();
        assert_eq!(
            err,
            ContractError::ServiceNotFound {
                did_id: did_id.to_string(),
                service_id: "nonexistent-svc".to_string(),
            }
        );
    }

2.2.10 列表查询测试

    #[test]
    fn list_dids_by_controller() {
        let (mut deps, env) = setup_contract();

        // 创建多个 DID
        for i in 0..5 {
            execute(
                deps.as_mut(),
                env.clone(),
                mock_info("controller", &[]),
                ExecuteMsg::CreateDID {
                    did_id: format!("did:msg:agent:list-{}", i),
                    controller: "controller".to_string(),
                    verification_methods: vec![],
                    services: vec![],
                    metadata: None,
                },
            )
            .unwrap();
        }

        // 创建另一个控制器的 DID
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("other", &[]),
            ExecuteMsg::CreateDID {
                did_id: "did:msg:agent:other-1".to_string(),
                controller: "other".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        // 按 controller 查询
        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::ListDIDsByController {
                controller: "controller".to_string(),
                start_after: None,
                limit: Some(100),
            },
        )
        .unwrap();
        let response: ListDIDsByControllerResponse = from_binary(&res).unwrap();
        assert_eq!(response.dids.len(), 5);
    }

2.3 Agent Registry 合约测试

agent_registry_v1 是 AI Agent 注册合约,使用 IndexedMap 进行多索引查询。

#[cfg(test)]
mod tests {
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{from_binary, Binary};

    use crate::contract::{instantiate, execute, query};
    use crate::msg::{
        Agent, AgentStatus, ExecuteMsg, GetAgentResponse, InstantiateMsg,
        ListAgentsResponse, QueryMsg,
    };
    use crate::error::ContractError;
    use crate::state::{Config, CONFIG};

    fn setup() -> (cosmwasm_std::OwnedDeps<cosmwasm_std::MemoryStorage, cosmwasm_std::testing::MockApi, cosmwasm_std::testing::MockQuerier>, cosmwasm_std::Env) {
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                did_registry_address: "msg1didregistry".to_string(),
            },
        )
        .unwrap();

        (deps, env)
    }

    fn register_test_agent(
        deps: &mut cosmwasm_std::OwnedDeps<cosmwasm_std::MemoryStorage, cosmwasm_std::testing::MockApi, cosmwasm_std::testing::MockQuerier>,
        env: &cosmwasm_std::Env,
        agent_id: &str,
        owner: &str,
    ) {
        execute(
            deps.as_mut(),
            env.clone(),
            mock_info(owner, &[]),
            ExecuteMsg::RegisterAgent {
                agent_id: agent_id.to_string(),
                did_id: format!("did:msg:agent:{}", agent_id),
                owner: owner.to_string(),
                name: format!("Agent {}", agent_id),
                description: Some("Test agent".to_string()),
                endpoint: Some(format!("https://{}.example.com", agent_id)),
                metadata: None,
                agent_type: "worker".to_string(),
                capabilities: vec!["text-generation".to_string(), "code-review".to_string()],
            },
        )
        .unwrap();
    }

    #[test]
    fn proper_initialization() {
        let mut deps = mock_dependencies();
        let env = mock_env();

        let res = instantiate(
            deps.as_mut(),
            env,
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                did_registry_address: "msg1didregistry".to_string(),
            },
        )
        .unwrap();

        assert_eq!(res.attributes.len(), 3);
        assert_eq!(res.attributes[0].value, "instantiate");

        let config = CONFIG.load(&deps.storage).unwrap();
        assert_eq!(config.admin, "admin");
        assert_eq!(config.did_registry_address, "msg1didregistry");
    }

    #[test]
    fn register_and_get_agent() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner");

        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::GetAgent {
                agent_id: "agent-001".to_string(),
            },
        )
        .unwrap();
        let response: GetAgentResponse = from_binary(&res).unwrap();
        assert_eq!(response.agent.agent_id, "agent-001");
        assert_eq!(response.agent.owner, "owner");
        assert_eq!(response.agent.name, "Agent agent-001");
        assert_eq!(response.agent.capabilities.len(), 2);
    }

    #[test]
    fn duplicate_agent_fails() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner");

        let err = execute(
            deps.as_mut(),
            env,
            mock_info("owner", &[]),
            ExecuteMsg::RegisterAgent {
                agent_id: "agent-001".to_string(),
                did_id: "did:msg:agent:dup".to_string(),
                owner: "owner".to_string(),
                name: "Duplicate".to_string(),
                description: None,
                endpoint: None,
                metadata: None,
                agent_type: "worker".to_string(),
                capabilities: vec![],
            },
        )
        .unwrap_err();
        assert_eq!(
            err,
            ContractError::AgentAlreadyExists {
                agent_id: "agent-001".to_string()
            }
        );
    }

    #[test]
    fn update_agent() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner");

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("owner", &[]),
            ExecuteMsg::UpdateAgent {
                agent_id: "agent-001".to_string(),
                name: Some("Updated Agent".to_string()),
                description: Some("Updated description".to_string()),
                endpoint: Some("https://new-endpoint.com".to_string()),
                metadata: None,
                capabilities: Some(vec!["data-analysis".to_string()]),
            },
        )
        .unwrap();

        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::GetAgent {
                agent_id: "agent-001".to_string(),
            },
        )
        .unwrap();
        let response: GetAgentResponse = from_binary(&res).unwrap();
        assert_eq!(response.agent.name, "Updated Agent");
        assert_eq!(response.agent.capabilities, vec!["data-analysis"]);
    }

    #[test]
    fn deregister_agent() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner");

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("owner", &[]),
            ExecuteMsg::DeregisterAgent {
                agent_id: "agent-001".to_string(),
            },
        )
        .unwrap();

        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::GetAgent {
                agent_id: "agent-001".to_string(),
            },
        )
        .unwrap();
        let response: GetAgentResponse = from_binary(&res).unwrap();
        assert_eq!(response.agent.status, AgentStatus::Deregistered);
    }

    #[test]
    fn unauthorized_update_fails() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner");

        let err = execute(
            deps.as_mut(),
            env,
            mock_info("attacker", &[]),
            ExecuteMsg::UpdateAgent {
                agent_id: "agent-001".to_string(),
                name: None,
                description: None,
                endpoint: None,
                metadata: None,
                capabilities: None,
            },
        )
        .unwrap_err();
        assert_eq!(err, ContractError::Unauthorized {});
    }

    #[test]
    fn list_agents() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner-1");
        register_test_agent(&mut deps, &env, "agent-002", "owner-1");
        register_test_agent(&mut deps, &env, "agent-003", "owner-2");

        let res = query(
            deps.as_ref(),
            env.clone(),
            QueryMsg::ListAgents {
                start_after: None,
                limit: None,
            },
        )
        .unwrap();
        let response: ListAgentsResponse = from_binary(&res).unwrap();
        assert_eq!(response.agents.len(), 3);

        // 按 owner 过滤
        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::GetAgentsByOwner {
                owner: "owner-1".to_string(),
                start_after: None,
                limit: None,
            },
        )
        .unwrap();
        let response: ListAgentsResponse = from_binary(&res).unwrap();
        assert_eq!(response.agents.len(), 2);
    }

    #[test]
    fn set_status_by_admin() {
        let (mut deps, env) = setup();

        register_test_agent(&mut deps, &env, "agent-001", "owner");

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            ExecuteMsg::SetStatus {
                agent_id: "agent-001".to_string(),
                status: AgentStatus::Suspended,
            },
        )
        .unwrap();

        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::GetAgent {
                agent_id: "agent-001".to_string(),
            },
        )
        .unwrap();
        let response: GetAgentResponse = from_binary(&res).unwrap();
        assert_eq!(response.agent.status, AgentStatus::Suspended);
    }
}

2.4 Schema 生成测试

每个合约必须包含 schema 生成示例,确保合约接口文档自动生成:

// examples/schema.rs
use cosmwasm_schema::write_api;

use aidid_did_registry::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};

fn main() {
    write_api! {
        instantiate: InstantiateMsg,
        execute: ExecuteMsg,
        query: QueryMsg,
    }
}

运行 schema 生成:

cargo run --example schema
ls -la schema/

3. 集成测试

3.1 cw-multi-test 基础

MSG Chain 使用 cw-multi-test 进行合约集成测试,支持多合约交互场景。每个合约需要 cw-multi-test = "0.18" 作为 dev-dependency。

3.2 DID Registry + Agent Registry 集成测试

以下示例展示 aidid_did_registry_v1 和 agent_registry_v1 的跨合约交互测试。

// tests/integration.rs
use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage};
use cosmwasm_std::{Addr, Coin, Empty, Uint128};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor};

use aidid_did_registry::msg::{
    ExecuteMsg as DIDExecuteMsg, InstantiateMsg as DIDInstantiateMsg,
    QueryMsg as DIDQueryMsg, ResolveDIDResponse, VerificationMethod, ServiceEndpoint,
};
use aidid_did_registry::contract::{
    instantiate as did_instantiate, execute as did_execute, query as did_query,
};

use agent_registry::msg::{
    ExecuteMsg as AgentExecuteMsg, InstantiateMsg as AgentInstantiateMsg,
    QueryMsg as AgentQueryMsg, GetAgentResponse, AgentStatus,
};
use agent_registry::contract::{
    instantiate as agent_instantiate, execute as agent_execute, query as agent_query,
};

fn did_contract() -> Box<dyn Contract<Empty>> {
    let contract = ContractWrapper::new(did_execute, did_instantiate, did_query);
    Box::new(contract)
}

fn agent_contract() -> Box<dyn Contract<Empty>> {
    let contract = ContractWrapper::new(agent_execute, agent_instantiate, agent_query);
    Box::new(contract)
}

fn create_test_vm(id: &str) -> VerificationMethod {
    VerificationMethod {
        id: id.to_string(),
        controller: "controller".to_string(),
        key_type: "Dilithium5VerificationKey2026".to_string(),
        public_key_multibase: Some("z6Mk".to_string()),
        dilithium5_public_key: Some("pk_example".to_string()),
    }
}

fn create_test_service(id: &str) -> ServiceEndpoint {
    ServiceEndpoint {
        id: id.to_string(),
        service_type: "LinkedDomains".to_string(),
        service_endpoint: "https://agent.example.com".to_string(),
        metadata: None,
    }
}

#[test]
fn full_did_and_agent_integration() {
    let owner = Addr::unchecked("owner");
    let admin = Addr::unchecked("admin");
    let controller = Addr::unchecked("controller");
    let alice = Addr::unchecked("alice");

    let mut app = AppBuilder::new().build(|router, _, storage| {
        router
            .bank
            .init_balance(
                storage,
                &owner,
                vec![Coin {
                    denom: "umsg".to_string(),
                    amount: Uint128::new(1_000_000_000_000_000_000u128),
                }],
            )
            .unwrap();
    });

    // 部署 DID Registry 合约
    let did_code_id = app.store_code(did_contract());
    let did_contract_addr = app
        .instantiate_contract(
            did_code_id,
            admin.clone(),
            &DIDInstantiateMsg {
                admin: admin.to_string(),
                registry_name: "MSG DID Registry".to_string(),
            },
            &[],
            "aidid_did_registry_v1",
            None,
        )
        .unwrap();

    // 部署 Agent Registry 合约(引用 DID Registry 地址)
    let agent_code_id = app.store_code(agent_contract());
    let agent_contract_addr = app
        .instantiate_contract(
            agent_code_id,
            admin.clone(),
            &AgentInstantiateMsg {
                admin: admin.to_string(),
                did_registry_address: did_contract_addr.to_string(),
            },
            &[],
            "agent_registry_v1",
            None,
        )
        .unwrap();

    // 1. 创建 DID
    app.execute_contract(
        controller.clone(),
        did_contract_addr.clone(),
        &DIDExecuteMsg::CreateDID {
            did_id: "did:msg:agent:integration-1".to_string(),
            controller: controller.to_string(),
            verification_methods: vec![create_test_vm("vm-key-1")],
            services: vec![create_test_service("svc-endpoint-1")],
            metadata: None,
        },
        &[],
    )
    .unwrap();

    // 2. 验证 DID 已创建
    let did_res: ResolveDIDResponse = app
        .wrap()
        .query_wasm_smart(
            did_contract_addr.clone(),
            &DIDQueryMsg::ResolveDID {
                did_id: "did:msg:agent:integration-1".to_string(),
            },
        )
        .unwrap();
    assert_eq!(did_res.document.did_id, "did:msg:agent:integration-1");
    assert_eq!(did_res.document.controller, controller.to_string());
    assert!(did_res.document.active);

    // 3. 注册 Agent
    app.execute_contract(
        owner.clone(),
        agent_contract_addr.clone(),
        &AgentExecuteMsg::RegisterAgent {
            agent_id: "agent-integration-1".to_string(),
            did_id: "did:msg:agent:integration-1".to_string(),
            owner: owner.to_string(),
            name: "Integration Test Agent".to_string(),
            description: Some("Agent created in integration test".to_string()),
            endpoint: Some("https://integration-agent.example.com".to_string()),
            metadata: None,
            agent_type: "worker".to_string(),
            capabilities: vec!["text-generation".to_string(), "code-review".to_string()],
        },
        &[],
    )
    .unwrap();

    // 4. 验证 Agent
    let agent_res: GetAgentResponse = app
        .wrap()
        .query_wasm_smart(
            agent_contract_addr.clone(),
            &AgentQueryMsg::GetAgent {
                agent_id: "agent-integration-1".to_string(),
            },
        )
        .unwrap();
    assert_eq!(
        agent_res.agent.did_id,
        "did:msg:agent:integration-1"
    );
    assert_eq!(agent_res.agent.owner, owner.to_string());
    assert_eq!(agent_res.agent.status, AgentStatus::Active);

    // 5. 停用 DID — 验证 DID 变为非活跃
    app.execute_contract(
        controller.clone(),
        did_contract_addr.clone(),
        &DIDExecuteMsg::DeactivateDID {
            did_id: "did:msg:agent:integration-1".to_string(),
        },
        &[],
    )
    .unwrap();

    let check_res: cw_multi_test::QuerierResult = app
        .wrap()
        .query_wasm_smart(
            did_contract_addr.clone(),
            &DIDQueryMsg::CheckDIDActive {
                did_id: "did:msg:agent:integration-1".to_string(),
            },
        );
    // 注意: 这里需要根据实际返回类型调整
    println!("DID deactivated, integration complete");

    // 6. 验证非 controller 无法修改
    let err = app
        .execute_contract(
            alice.clone(),
            did_contract_addr,
            &DIDExecuteMsg::UpdateDID {
                did_id: "did:msg:agent:integration-1".to_string(),
                verification_methods: None,
                services: None,
                metadata: None,
            },
            &[],
        )
        .unwrap_err();
    assert!(err
        .root()
        .to_string()
        .contains("does not own DID"));
}

#[test]
fn multiple_agents_same_did_rejected() {
    let admin = Addr::unchecked("admin");
    let owner = Addr::unchecked("owner");

    let mut app = App::default();

    let did_code_id = app.store_code(did_contract());
    let did_contract_addr = app
        .instantiate_contract(
            did_code_id,
            admin.clone(),
            &DIDInstantiateMsg {
                admin: admin.to_string(),
                registry_name: "MSG DID Registry".to_string(),
            },
            &[],
            "aidid_did_registry_v1",
            None,
        )
        .unwrap();

    let agent_code_id = app.store_code(agent_contract());
    let agent_contract_addr = app
        .instantiate_contract(
            agent_code_id,
            admin.clone(),
            &AgentInstantiateMsg {
                admin: admin.to_string(),
                did_registry_address: did_contract_addr.to_string(),
            },
            &[],
            "agent_registry_v1",
            None,
        )
        .unwrap();

    // 注册 agent-001
    app.execute_contract(
        owner.clone(),
        agent_contract_addr.clone(),
        &AgentExecuteMsg::RegisterAgent {
            agent_id: "agent-001".to_string(),
            did_id: "did:msg:agent:test".to_string(),
            owner: owner.to_string(),
            name: "Agent One".to_string(),
            description: None,
            endpoint: None,
            metadata: None,
            agent_type: "worker".to_string(),
            capabilities: vec![],
        },
        &[],
    )
    .unwrap();

    // 重复 agent_id 应被拒绝
    let err = app
        .execute_contract(
            owner.clone(),
            agent_contract_addr,
            &AgentExecuteMsg::RegisterAgent {
                agent_id: "agent-001".to_string(),
                did_id: "did:msg:agent:other".to_string(),
                owner: owner.to_string(),
                name: "Agent One Duplicate".to_string(),
                description: None,
                endpoint: None,
                metadata: None,
                agent_type: "worker".to_string(),
                capabilities: vec![],
            },
            &[],
        )
        .unwrap_err();
    assert!(err.root().to_string().contains("already exists"));
}

3.3 运行集成测试

# 运行所有集成测试
cd contracts/cosmwasm/all/aidid_did_registry_v1 && cargo test --test integration

# 运行特定集成测试
cd contracts/cosmwasm/all/aidid_did_registry_v1 && cargo test full_did_and_agent_integration

# 使用 ci-contracts 运行所有合约的测试
make ci-contracts

4. Go 端测试

4.1 Go pkg 测试

MSG Chain 的 Go 端测试覆盖完整的链基础设施,包括 p2p、共识、存储、密码学等模块。

# 运行所有 Go 测试
make test
# 等价于: go test -v ./pkg/...

# 运行特定包测试
go test -v ./pkg/crypto/...
go test -v ./pkg/consensus/...
go test -v ./pkg/storage/...
go test -v ./pkg/p2p/...

4.2 Dilithium-5 后量子密码学测试

# 运行量子节点模块测试(包含 Dilithium-5 PQ 测试)
make test-quantum
# 等价于: cd pkg/quantum && go test -v

# 运行密码学模块测试
make test-crypto
# 等价于: cd pkg/crypto && go test -v

量子测试的关键验证点:

// pkg/quantum/quantum_test.go
package quantum

import (
    "testing"
    "crypto/rand"
    "github.com/cloudflare/circl/sign/dilithium"
    "github.com/cloudflare/circl/sign/dilithium/mode5"
)

func TestDilithium5KeyGeneration(t *testing.T) {
    pk, sk, err := mode5.GenerateKey(rand.Reader)
    if err != nil {
        t.Fatalf("Dilithium-5 key generation failed: %v", err)
    }
    if pk == nil || sk == nil {
        t.Fatal("Generated key pair is nil")
    }
    t.Logf("Public key size: %d bytes", len(pk.Bytes()))
    t.Logf("Secret key size: %d bytes", len(sk.Bytes()))
}

func TestDilithium5SignVerify(t *testing.T) {
    pk, sk, err := mode5.GenerateKey(rand.Reader)
    if err != nil {
        t.Fatalf("Key generation failed: %v", err)
    }

    message := []byte("MSG Chain test message for Dilithium-5")
    signature, err := sk.Sign(rand.Reader, message, nil)
    if err != nil {
        t.Fatalf("Signing failed: %v", err)
    }

    if !pk.Verify(message, signature) {
        t.Fatal("Signature verification failed")
    }
    t.Logf("Signature size: %d bytes", len(signature))
}

func TestDilithium5TamperedSignature(t *testing.T) {
    pk, sk, err := mode5.GenerateKey(rand.Reader)
    if err != nil {
        t.Fatalf("Key generation failed: %v", err)
    }

    message := []byte("Original message")
    signature, _ := sk.Sign(rand.Reader, message, nil)

    tamperedMessage := []byte("Tampered message")
    if pk.Verify(tamperedMessage, signature) {
        t.Fatal("Tampered message should not verify")
    }
}

func TestDilithium5PublicKeyEncoding(t *testing.T) {
    pk, _, err := mode5.GenerateKey(rand.Reader)
    if err != nil {
        t.Fatalf("Key generation failed: %v", err)
    }

    pkBytes := pk.Bytes()
    pkRestored, err := mode5.PublicKeyFromBytes(pkBytes)
    if err != nil {
        t.Fatalf("Public key deserialization failed: %v", err)
    }

    if !pkRestored.Equals(pk) {
        t.Fatal("Public key round-trip failed")
    }
}

4.3 GitHub Actions 集成测试

# .github/workflows/go-tests.yml
name: Go Tests
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'
          cache: true
      - name: Install dependencies
        run: make deps
      - name: Run Go tests
        run: make test
      - name: Run quantum tests
        run: make test-quantum
      - name: Generate coverage
        run: make test-coverage

4.4 NAT 组网诊断测试

# 运行 NAT 组网诊断矩阵测试
make test-nat-readiness
# 包含: TestNetworkDetectorDetermineBestStrategy
#       TestDefaultConfig_NATBootstrapDependencies
#       TestShouldSendSmartKeepalive
#       TestCheckP2PAndToggleHeartbeat
#       TestGetPublicRelayNodes_EmptyRegistryURLUsesFallback
#       TestGetPublicRelayNodes_RegistryFailureFallsBack
#       TestGetAllBootstrapPeers_FallsBackToOfficialWhenMSGBootstrapEmpty
#       TestCircuitRelayIntegration_BasicRelay

5. 模糊测试与属性测试

5.1 Rust proptest 集成

MSG Chain 使用 proptest 对 CosmWasm 合约进行属性基测试。

# Cargo.toml (dev-dependencies)
[dev-dependencies]
proptest = "1.4"

5.2 合约属性测试示例

#[cfg(test)]
mod property_tests {
    use proptest::prelude::*;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{from_binary, Binary, Addr};

    use crate::contract::{instantiate, execute, query};
    use crate::msg::*;
    use crate::error::ContractError;
    use crate::state::CONFIG;

    proptest! {
        #[test]
        fn instantiate_always_sets_config(
            admin in "[a-z]{5,20}",
            registry_name in "[a-zA-Z0-9 _]{1,50}",
        ) {
            let mut deps = mock_dependencies();
            let env = mock_env();

            let res = instantiate(
                deps.as_mut(),
                env,
                mock_info(&admin, &[]),
                InstantiateMsg {
                    admin: admin.clone(),
                    registry_name: registry_name.clone(),
                },
            );

            prop_assert!(res.is_ok());

            let config = CONFIG.load(&deps.storage).unwrap();
            prop_assert_eq!(config.admin, admin);
            prop_assert_eq!(config.registry_name, registry_name);
        }

        #[test]
        fn create_did_with_valid_key_type_succeeds(
            did_id in "[a-zA-Z0-9:-]{5,30}",
            controller in "[a-z]{5,20}",
        ) {
            let mut deps = mock_dependencies();
            let env = mock_env();

            instantiate(
                deps.as_mut(),
                env.clone(),
                mock_info("admin", &[]),
                InstantiateMsg {
                    admin: "admin".to_string(),
                    registry_name: "test".to_string(),
                },
            ).unwrap();

            let vm = VerificationMethod {
                id: "vm-1".to_string(),
                controller: controller.clone(),
                key_type: "Dilithium5VerificationKey2026".to_string(),
                public_key_multibase: Some("z6Mk".to_string()),
                dilithium5_public_key: Some("pk".to_string()),
            };

            let result = execute(
                deps.as_mut(),
                env,
                mock_info(&controller, &[]),
                ExecuteMsg::CreateDID {
                    did_id,
                    controller,
                    verification_methods: vec![vm],
                    services: vec![],
                    metadata: None,
                },
            );

            prop_assert!(result.is_ok());
        }

        #[test]
        fn deactivated_did_always_shows_inactive(
            did_id in "[a-zA-Z0-9:-]{5,30}",
        ) {
            let mut deps = mock_dependencies();
            let env = mock_env();
            let controller = "controller";

            instantiate(
                deps.as_mut(),
                env.clone(),
                mock_info("admin", &[]),
                InstantiateMsg {
                    admin: "admin".to_string(),
                    registry_name: "test".to_string(),
                },
            ).unwrap();

            execute(
                deps.as_mut(),
                env.clone(),
                mock_info(controller, &[]),
                ExecuteMsg::CreateDID {
                    did_id: did_id.clone(),
                    controller: controller.to_string(),
                    verification_methods: vec![],
                    services: vec![],
                    metadata: None,
                },
            ).unwrap();

            execute(
                deps.as_mut(),
                env.clone(),
                mock_info(controller, &[]),
                ExecuteMsg::DeactivateDID {
                    did_id: did_id.clone(),
                },
            ).unwrap();

            let res = query(
                deps.as_ref(),
                env,
                QueryMsg::CheckDIDActive {
                    did_id,
                },
            ).unwrap();
            let response: CheckDIDActiveResponse = from_binary(&res).unwrap();
            prop_assert!(!response.active);
        }
    }
}

5.3 Go Fuzz Testing

// pkg/crypto/fuzz_test.go
package crypto

import (
    "testing"
    "testing/quick"
    "crypto/rand"
    "github.com/cloudflare/circl/sign/dilithium/mode5"
)

func FuzzDilithium5SignVerify(f *testing.F) {
    pk, sk, err := mode5.GenerateKey(rand.Reader)
    if err != nil {
        f.Fatalf("Key generation failed: %v", err)
    }

    f.Fuzz(func(t *testing.T, message []byte, signature []byte) {
        // 模糊测试:随机消息和签名不应导致 panic
        pk.Verify(message, signature)
    })

    // 也测试合法场景
    f.Add([]byte("test message"), []byte{})
    sk.Sign(rand.Reader, []byte("test message"), nil)
}

func FuzzDIDDocumentSerialization(f *testing.F) {
    f.Fuzz(func(t *testing.T, data []byte) {
        // 模糊测试 DID 文档序列化/反序列化
        var doc DIDDocument
        err := json.Unmarshal(data, &doc)
        if err == nil {
            _, err := json.Marshal(doc)
            if err != nil {
                t.Errorf("round-trip failed: %v", err)
            }
        }
    })
}

// 快速检查属性测试
func TestDIDProperties(t *testing.T) {
    property := func(didID string, controller string) bool {
        if len(didID) == 0 || len(controller) == 0 {
            return true // 跳过空值
        }
        // 验证 DID 格式
        if !strings.HasPrefix(didID, "did:msg:") {
            return false
        }
        return true
    }

    if err := quick.Check(property, nil); err != nil {
        t.Error(err)
    }
}

5.4 状态不变量测试

#[cfg(test)]
mod invariance_tests {
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use crate::contract::{instantiate, execute, query};
    use crate::msg::*;

    #[test]
    fn total_did_count_invariant() {
        // 不变量:创建后创建数增加,停用后总数不变
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "invariant-test".to_string(),
            },
        )
        .unwrap();

        // 初始应无 DID
        let res = query(
            deps.as_ref(),
            env.clone(),
            QueryMsg::ListDIDsByController {
                controller: "controller".to_string(),
                start_after: None,
                limit: Some(100),
            },
        )
        .unwrap();
        let response: ListDIDsByControllerResponse = from_binary(&res).unwrap();
        assert_eq!(response.dids.len(), 0);

        // 创建 3 个
        for i in 0..3 {
            execute(
                deps.as_mut(),
                env.clone(),
                mock_info("controller", &[]),
                ExecuteMsg::CreateDID {
                    did_id: format!("did:msg:agent:inv-{}", i),
                    controller: "controller".to_string(),
                    verification_methods: vec![],
                    services: vec![],
                    metadata: None,
                },
            )
            .unwrap();
        }

        // 应仍有 3 个(不变量:停用不解散)
        let res = query(
            deps.as_ref(),
            env.clone(),
            QueryMsg::ListDIDsByController {
                controller: "controller".to_string(),
                start_after: None,
                limit: Some(100),
            },
        )
        .unwrap();
        let response: ListDIDsByControllerResponse = from_binary(&res).unwrap();
        assert_eq!(response.dids.len(), 3);
    }

    #[test]
    fn create_time_invariant() {
        // 不变量:created <= updated
        let mut deps = mock_dependencies();
        let env = mock_env();

        instantiate(
            deps.as_mut(),
            env.clone(),
            mock_info("admin", &[]),
            InstantiateMsg {
                admin: "admin".to_string(),
                registry_name: "invariant-test".to_string(),
            },
        )
        .unwrap();

        execute(
            deps.as_mut(),
            env.clone(),
            mock_info("controller", &[]),
            ExecuteMsg::CreateDID {
                did_id: "did:msg:agent:time-test".to_string(),
                controller: "controller".to_string(),
                verification_methods: vec![],
                services: vec![],
                metadata: None,
            },
        )
        .unwrap();

        let res = query(
            deps.as_ref(),
            env,
            QueryMsg::ResolveDID {
                did_id: "did:msg:agent:time-test".to_string(),
            },
        )
        .unwrap();
        let response: ResolveDIDResponse = from_binary(&res).unwrap();
        assert!(response.document.created <= response.document.updated);
    }
}

6. CI/CD 管线

6.1 管线架构

                    ┌──────────────┐
                    │  代码提交     │
                    │  git push    │
                    └──────┬───────┘
                           ▼
                    ┌──────────────┐
                    │  Trigger     │
                    │  (push/PR)   │
                    └──────┬───────┘
                           ▼
              ┌────────────────────────┐
              │  Prepare (make deps)   │
              │  安装依赖 + 缓存       │
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Quality Gate (lint)   │
              │  gofmt → go vet → linter│
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Test (make test)      │
              │  pkg tests             │
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  PQ Test (test-quantum)│
              │  Dilithium-5 crypto    │
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Contract CI           │
              │  ci-contracts          │
              │  build + cargo test    │
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Build (build-linux)   │
              │  genesis + quantum     │
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Package (make package)│
              │  deploy-<version>.tar.gz│
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Security Scan         │
              │  gosec + cargo audit   │
              └──────────┬─────────────┘
                         ▼
              ┌────────────────────────┐
              │  Approval Gates        │
              │  3 道审批 → 生产部署    │
              └────────────────────────┘

6.2 GitHub Actions 完整 CI 模板

# .github/workflows/ci.yml
name: MSG Chain CI Pipeline

on:
  push:
    branches: [main, develop, 'release/*', 'feat/*', 'fix/*']
  pull_request:
    branches: [main, develop]

env:
  GO_VERSION: '1.24'
  GO_CACHE_PATH: /tmp/msg-chain-go-build-cache
  GOLANGCI_LINT_CACHE: /tmp/msg-chain-golangci-cache
  RUST_TOOLCHAIN: stable
  WASMVM_LIB_DIR: ./lib

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  prepare:
    name: Prepare Dependencies
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN }}
          target: wasm32-unknown-unknown
      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: |
            ~/go/pkg/mod
            ${{ env.GO_CACHE_PATH }}
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-
      - name: Cache Cargo registry
        uses: actions/cache@v4
        with:
          path: ~/.cargo/registry
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
      - name: Install system dependencies
        run: |
          sudo apt-get update && sudo apt-get install -y \
            build-essential clang wabt binaryen jq
      - name: Install wasm-opt
        run: cargo install wasm-opt || true
      - name: Install Go tools
        run: |
          go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
          go install github.com/securego/gosec/v2/cmd/gosec@latest
      - name: Install Rust tools
        run: |
          cargo install cargo-audit || true
      - name: Install Go dependencies
        run: make deps
      - name: Verify dependencies
        run: go mod verify

  lint:
    name: Code Quality
    needs: [prepare]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true
      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/go/pkg/mod
            ${{ env.GO_CACHE_PATH }}
            ${{ env.GOLANGCI_LINT_CACHE }}
          key: ${{ runner.os }}-lint-${{ hashFiles('**/go.sum') }}
      - name: Run lint (make lint)
        run: make lint
        env:
          GOCACHE: ${{ env.GO_CACHE_PATH }}
          GOLANGCI_LINT_CACHE: ${{ env.GOLANGCI_LINT_CACHE }}

  test:
    name: Go Tests
    needs: [lint]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true
      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/go/pkg/mod
            ${{ env.GO_CACHE_PATH }}
          key: ${{ runner.os }}-test-${{ hashFiles('**/go.sum') }}
      - name: Run tests (make test)
        run: make test
        env:
          GOCACHE: ${{ env.GO_CACHE_PATH }}
      - name: Run quantum tests (make test-quantum)
        run: make test-quantum
        env:
          GOCACHE: ${{ env.GO_CACHE_PATH }}

  contracts:
    name: Contract Build & Test
    needs: [lint]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
          target: wasm32-unknown-unknown
      - name: Cache Cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            contracts/cosmwasm/all/*/target
          key: ${{ runner.os }}-contract-${{ hashFiles('contracts/cosmwasm/all/**/Cargo.lock') }}
      - name: Build & test all contracts (make ci-contracts)
        run: make ci-contracts

  build:
    name: Build Binaries
    needs: [test, contracts]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true
      - name: Cache
        uses: actions/cache@v4
        with:
          path: |
            ~/go/pkg/mod
            ${{ env.GO_CACHE_PATH }}
          key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }}
      - name: Build Linux binaries (make build-linux)
        run: make build-linux
        env:
          GOCACHE: ${{ env.GO_CACHE_PATH }}
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: msg-chain-binaries
          path: |
            bin/genesis_node_linux
            bin/quantum_node_linux
            bin/hashcheck_json
            bin/hashcheck_dir
            bin/hashcheck_http

  package:
    name: Package Deployment
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: msg-chain-binaries
          path: bin/
      - name: Make binaries executable
        run: chmod +x bin/*
      - name: Package (make package)
        run: make package
      - name: Upload deployment package
        uses: actions/upload-artifact@v4
        with:
          name: msg-chain-deploy-package
          path: deploy-*.tar.gz

  security:
    name: Security Audit
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
          cache: true
      - name: Set up Rust
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
      - name: Run security audit
        run: make ci-security

  # # requires_human_approval: true
  # production-deploy:
  #   name: Production Deploy (Manual)
  #   needs: [package, security]
  #   runs-on: ubuntu-latest
  #   environment: production
  #   if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  #   steps:
  #     - name: Deploy to production
  #       run: echo "Manual approval required before production deployment"

6.3 GitLab CI 模板

# .gitlab-ci.yml
stages:
  - prepare
  - quality_gate
  - test
  - contracts
  - build
  - package
  - security
  - deploy

variables:
  GO_VERSION: "1.24"
  GIT_SUBMODULE_STRATEGY: recursive
  CARGO_HOME: "${CI_PROJECT_DIR}/.cargo"

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .cargo/
    - contracts/cosmwasm/all/*/target/
    - ${GO_CACHE_PATH}

before_script:
  - apt-get update && apt-get install -y build-essential clang wabt binaryen jq
  - curl -LO https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
  - tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
  - export PATH=/usr/local/go/bin:$PATH
  - rustup target add wasm32-unknown-unknown

prepare:
  stage: prepare
  script:
    - make deps
  artifacts:
    paths:
      - go.sum

lint:
  stage: quality_gate
  script:
    - make lint

test:
  stage: test
  script:
    - make test
    - make test-quantum

contracts:
  stage: contracts
  script:
    - make ci-contracts

build:
  stage: build
  script:
    - make build-linux
  artifacts:
    paths:
      - bin/
    expire_in: 1 week

package:
  stage: package
  script:
    - make package
  artifacts:
    paths:
      - deploy-*.tar.gz
    expire_in: 1 month

security:
  stage: security
  script:
    - make ci-security

# requires_human_approval: true
production:
  stage: deploy
  script:
    - echo "Production deployment requires manual approval"
  when: manual
  only:
    - main
  environment:
    name: production

6.4 本地 CI 模拟

# 完整本地 CI(提交前运行)
make ci-local
# 等价于: ci-lint → ci-tests → ci-nodes

# 完整 CI(包含合约)
make ci-full
# 等价于: ci-lint → ci-contracts → ci-nodes → ci-tests → ci-security

# 逐步运行
make ci-lint          # 代码检查
make ci-tests         # 带 race detector 的测试
make ci-contracts     # 合约编译 + 测试
make ci-nodes         # 节点编译
make ci-security      # 安全扫描

7. 质量门禁

7.1 门禁架构

MSG Chain 采用四层质量门禁体系:

Layer 1: Lint Gate
  ├── gofmt: 所有 .go 文件必须格式化
  ├── go vet: 静态分析通过
  └── golangci-lint: 零错误

Layer 2: Test Gate
  ├── make test: 全部通过
  ├── make test-quantum: 全部通过
  └── 代码覆盖率 ≥ 60% (建议目标 80%)

Layer 3: Contract Gate
  ├── 所有合约 cargo build --target wasm32-unknown-unknown --release
  ├── 所有合约 cargo test 通过
  └── WASM 二进制 ≤ 800KB

Layer 4: Security Gate
  ├── gosec: 无 High/Critical 漏洞
  ├── cargo audit: 无已知安全漏洞
  └── 依赖无已知 CVE

7.2 Lint Gate 配置

# .golangci.yml
run:
  timeout: 10m
  modules-download-mode: readonly

linters:
  enable:
    - gofmt
    - govet
    - staticcheck
    - gosimple
    - ineffassign
    - misspell
    - unconvert
    - prealloc
  disable:
    - errcheck

issues:
  exclude-use-default: false
  max-issues-per-linter: 0
  max-same-issues: 0

linters-settings:
  staticcheck:
    checks:
      - "all"
  misspell:
    locale: US
// .golangci.json (备用)
{
  "run": {
    "timeout": "10m",
    "modules-download-mode": "readonly"
  },
  "linters": {
    "enable": ["goformat", "govet", "staticcheck", "gosimple"],
    "disable": ["errcheck"]
  },
  "issues": {
    "max-issues-per-linter": 0,
    "max-same-issues": 0
  }
}

7.3 测试覆盖率阈值

# coverage-config.yml
coverage:
  threshold:
    total: 60     # 总体覆盖率 ≥ 60%
    packages:
      pkg/crypto: 80    # 密码学模块 ≥ 80%
      pkg/consensus: 70
      pkg/storage: 65
      pkg/quantum: 75
      pkg/p2p: 60
      pkg/txpool: 60
  exclude:
    - "**/*.pb.go"    # 排除生成的 protobuf 代码
    - "**/mocks/**"

7.4 合约验证门禁

make ci-contracts 门禁包含以下检查:

# 单个合约验证门禁
#!/bin/bash
set -euo pipefail

CONTRACT_DIR=$1
CONTRACT_NAME=$(basename "$CONTRACT_DIR")

echo "[GATE] Validating contract: $CONTRACT_NAME"

# 1. Cargo.toml 存在性检查
if [ ! -f "$CONTRACT_DIR/Cargo.toml" ]; then
    echo "[GATE] ❌ Cargo.toml not found"
    exit 1
fi

# 2. Rust 编译
cd "$CONTRACT_DIR"
CC=/usr/bin/gcc CXX=/usr/bin/g++ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/gcc \
    cargo build --target wasm32-unknown-unknown --release
echo "[GATE] ✅ Build succeeded"

# 3. 测试
cargo test
echo "[GATE] ✅ Tests passed"

# 4. WASM 文件大小检查
WASM_FILE="target/wasm32-unknown-unknown/release/${CONTRACT_NAME//-/_}.wasm"
if [ -f "$WASM_FILE" ]; then
    SIZE=$(stat -c%s "$WASM_FILE" 2>/dev/null || stat -f%z "$WASM_FILE" 2>/dev/null)
    echo "[GATE] WASM size: $SIZE bytes"
    if [ "$SIZE" -gt 819200 ]; then
        echo "[GATE] ❌ WASM exceeds 800KB limit"
        exit 1
    fi
fi

# 5. Schema 生成
if [ -f "examples/schema.rs" ]; then
    cargo run --example schema
    echo "[GATE] ✅ Schema generated"
fi

# 6. wasm-opt 优化
if command -v wasm-opt &> /dev/null && [ -f "$WASM_FILE" ]; then
    wasm-opt -Os "$WASM_FILE" -o "${WASM_FILE}.opt" && mv "${WASM_FILE}.opt" "$WASM_FILE"
    echo "[GATE] ✅ WASM optimized"
fi

echo "[GATE] ✅ Contract $CONTRACT_NAME validated successfully"

7.5 审批门禁

合约交付工作流定义了 3 道必备审批门禁:

门禁名称 阶段 触发条件 审批人 # requires_human_approval
scope_lock Phase 1 范围确定后 PM + Tech Lead true
secret_injection Phase 4 部署计划完成后 Security Engineer true
production_release Phase 5 生产发布前 CTO/VP Eng true

8. 合约交付工作流

8.1 工作流总览

MSG Chain 采用 contract_delivery_guarded_v1 工作流,确保每个合约从设计到上线都经过严格的审核和测试。

workflow_id: contract_delivery_guarded_v1

Phase 1: Scope ──[scope_lock]──→ Phase 2: Design & Codegen
                                          │
                                          ▼
Phase 5: Real Release ←[production_release]── Phase 4: Deploy Plan ←[secret_injection]── Phase 3: Build & Test

8.2 Phase 1: 范围与需求

输入:

活动:

  1. 确定合约功能范围
  2. 定义 ExecuteMsg / QueryMsg 接口
  3. 确定状态存储结构
  4. 安全需求评估

产出:

审批门禁: scope_lock — # requires_human_approval: true

gate:
  id: scope_lock
  phase: 1
  required_approvers:
    - project_manager
    - tech_lead
  artifacts_required:
    - contract_interface_spec
    - state_storage_design
    - security_assessment

8.3 Phase 2: 设计与代码生成

输入:

活动:

  1. 编写合约 Rust 代码
  2. 编写消息类型 (msg.rs)
  3. 编写状态存储 (state.rs)
  4. 编写错误类型 (error.rs)
  5. 编写入口函数 (contract.rs)
  6. 编写 Schema 生成器 (examples/schema.rs)

产出:

关键命令:

# 生成 Schema
cd contracts/cosmwasm/all/<contract_name>/ && cargo run --example schema

8.4 Phase 3: 构建与测试

输入:

活动:

  1. make deps — 安装 Go 依赖
  2. make lint — 静态检查 (gofmt + go vet + golangci-lint)
  3. make test — Go 侧测试
  4. make test-quantum — Dilithium-5 PQ 测试
  5. make ci-contracts — 合约 build + cargo test

产出:

使用真实命令:

# 完整 Phase 3 命令链
make deps && make lint && make test && make test-quantum && make ci-contracts

边界条件:

没有真实 receipt/query/log 时,不得把 CI 通过等同于链上上线成功。

8.5 Phase 4: 部署计划

输入:

活动:

  1. 编写部署脚本
  2. 创建密钥注入计划
  3. 制定回滚方案
  4. 准备 Gas 估算

产出:

审批门禁: secret_injection — # requires_human_approval: true

gate:
  id: secret_injection
  phase: 4
  required_approvers:
    - security_engineer
  artifacts_required:
    - deploy_plan
    - secret_injection_plan
    - rollback_strategy

8.6 Phase 5: 生产发布

输入:

活动:

  1. make build-linux — 构建生产二进制
  2. make package — 打包部署包
  3. StoreCode 上传 WASM
  4. InstantiateContract 实例化
  5. 验证合约状态

产出:

审批门禁: production_release — # requires_human_approval: true

gate:
  id: production_release
  phase: 5
  required_approvers:
    - cto_or_vp_engineering
  artifacts_required:
    - production_binaries
    - deploy_package
    - onchain_contract_address

发布后验证:

# 验证合约已部署
./build/msgd query wasm list-contract-by-code <CODE_ID>

# 验证合约可查询
./build/msgd query wasm contract-state smart <CONTRACT_ADDR> '{"get_config": {}}'

# 验证事件日志
./build/msgd query tx <TX_HASH> | jq '.logs'

9. 自动化脚本

9.1 test-all.sh — 运行全部测试

#!/bin/bash
# test-all.sh — MSG Chain 全部测试运行器
# 运行所有测试层级:lint → Go test → PQ test → contract test → coverage

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

TESTS_PASSED=0
TESTS_FAILED=0
START_TIME=$(date +%s)

print_banner() {
    echo ""
    echo "============================================"
    echo "  MSG Chain Test Runner"
    echo "  $(date)"
    echo "============================================"
    echo ""
}

print_result() {
    local name=$1
    local status=$2
    if [ "$status" -eq 0 ]; then
        echo -e "${GREEN}[PASS]${NC} $name"
        TESTS_PASSED=$((TESTS_PASSED + 1))
    else
        echo -e "${RED}[FAIL]${NC} $name"
        TESTS_FAILED=$((TESTS_FAILED + 1))
    fi
}

run_test() {
    local name=$1
    shift
    echo -e "${BLUE}[RUN]${NC} $name..."
    if "$@" 2>&1 | tail -5; then
        print_result "$name" 0
    else
        print_result "$name" 1
    fi
}

# ============================================
print_banner

# Phase 1: Environment Check
echo -e "${YELLOW}[Phase 1]${NC} Environment Check"
run_test "Go version" go version
run_test "Rust toolchain" rustc --version
run_test "wasm32 target" rustup target list --installed --toolchain stable | grep wasm32-unknown-unknown
run_test "Cargo" cargo --version

# Phase 2: Dependencies
echo ""
echo -e "${YELLOW}[Phase 2]${NC} Dependencies"
run_test "make deps" make deps

# Phase 3: Lint
echo ""
echo -e "${YELLOW}[Phase 3]${NC} Code Quality"
run_test "make lint" make lint

# Phase 4: Go Tests
echo ""
echo -e "${YELLOW}[Phase 4]${NC} Go Tests"
run_test "make test" make test
run_test "make test-quantum" make test-quantum

# Phase 5: Contract Tests
echo ""
echo -e "${YELLOW}[Phase 5]${NC} Contract Tests"
run_test "make ci-contracts" make ci-contracts

# Phase 6: Build
echo ""
echo -e "${YELLOW}[Phase 6]${NC} Build"
run_test "make build-linux" make build-linux

# Phase 7: Coverage
echo ""
echo -e "${YELLOW}[Phase 7]${NC} Coverage"
run_test "make test-coverage" make test-coverage

# ============================================
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))

echo ""
echo "============================================"
echo -e "  Results: ${GREEN}$TESTS_PASSED passed${NC}, ${RED}$TESTS_FAILED failed${NC}"
echo "  Duration: ${DURATION}s"
echo "============================================"

if [ "$TESTS_FAILED" -gt 0 ]; then
    exit 1
fi
exit 0

9.2 ci-pipeline.sh — 完整 CI 管线

#!/bin/bash
# ci-pipeline.sh — MSG Chain 本地 CI 管线模拟
# 执行完整的 CI 流程,匹配 GitHub Actions 行为

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_DIR"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

PIPELINE_LOG="ci-pipeline-$(date +%Y%m%d-%H%M%S).log"
FAILED_STEPS=()

log_step() {
    echo -e "${YELLOW}[CI]${NC} $1" | tee -a "$PIPELINE_LOG"
}

pass_step() {
    echo -e "${GREEN}[CI ✅]${NC} $1" | tee -a "$PIPELINE_LOG"
}

fail_step() {
    echo -e "${RED}[CI ❌]${NC} $1" | tee -a "$PIPELINE_LOG"
    FAILED_STEPS+=("$1")
}

run_step() {
    local step_name=$1
    shift
    log_step "Running: $step_name"
    if "$@" 2>&1 | tee -a "$PIPELINE_LOG"; then
        pass_step "$step_name"
        return 0
    else
        fail_step "$step_name"
        return 1
    fi
}

echo "============================================" | tee "$PIPELINE_LOG"
echo " MSG Chain CI Pipeline" | tee -a "$PIPELINE_LOG"
echo " Started: $(date)" | tee -a "$PIPELINE_LOG"
echo "============================================" | tee -a "$PIPELINE_LOG"

# Step 1: Prepare
run_step "make deps" make deps

# Step 2: Quality Gate
run_step "make lint" make lint

# Step 3: Test Gate
run_step "make test" make test
run_step "make test-quantum" make test-quantum

# Step 4: Contract Validation
run_step "make ci-contracts" make ci-contracts

# Step 5: Build
run_step "make build-linux" make build-linux

# Step 6: Package
run_step "make package" make package

# Step 7: Security
run_step "make ci-security" make ci-security

# Step 8: Coverage
run_step "make test-coverage" make test-coverage

# Summary
echo "" | tee -a "$PIPELINE_LOG"
echo "============================================" | tee -a "$PIPELINE_LOG"
if [ ${#FAILED_STEPS[@]} -eq 0 ]; then
    echo -e "${GREEN}CI Pipeline: ALL PASSED ✅${NC}" | tee -a "$PIPELINE_LOG"
    exit 0
else
    echo -e "${RED}CI Pipeline: FAILED ❌${NC}" | tee -a "$PIPELINE_LOG"
    echo "Failed steps:" | tee -a "$PIPELINE_LOG"
    for step in "${FAILED_STEPS[@]}"; do
        echo "  - $step" | tee -a "$PIPELINE_LOG"
    done
    exit 1
fi

9.3 validate-contract.sh — 单合约验证循环

#!/bin/bash
# validate-contract.sh — 单个 MSG Chain CosmWasm 合约验证脚本
# 用法: ./validate-contract.sh contracts/cosmwasm/all/<contract_name>

set -euo pipefail

CONTRACT_DIR="${1:-}"
if [ -z "$CONTRACT_DIR" ]; then
    echo "用法: $0 <contract-directory>"
    echo "示例: $0 contracts/cosmwasm/all/aidid_did_registry_v1"
    exit 1
fi

if [ ! -d "$CONTRACT_DIR" ]; then
    echo "❌ 目录不存在: $CONTRACT_DIR"
    exit 1
fi

CONTRACT_NAME=$(basename "$CONTRACT_DIR")
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo ""
echo "============================================"
echo "  Validating Contract: $CONTRACT_NAME"
echo "  Directory: $CONTRACT_DIR"
echo "  $(date)"
echo "============================================"

VALIDATION_PASSED=0
VALIDATION_FAILED=0

check_step() {
    local step_name=$1
    shift
    echo -e "${YELLOW}[CHECK]${NC} $step_name..."
    if "$@" 2>&1; then
        echo -e "${GREEN}[PASS]${NC} $step_name"
        VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
    else
        echo -e "${RED}[FAIL]${NC} $step_name"
        VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
    fi
}

# Step 1: 检查项目结构
check_step "Cargo.toml 存在" test -f "$CONTRACT_DIR/Cargo.toml"
check_step "src/lib.rs 存在" test -f "$CONTRACT_DIR/src/lib.rs"
check_step "src/contract.rs 存在" test -f "$CONTRACT_DIR/src/contract.rs"
check_step "src/msg.rs 存在" test -f "$CONTRACT_DIR/src/msg.rs"
check_step "src/state.rs 存在" test -f "$CONTRACT_DIR/src/state.rs"
check_step "src/error.rs 存在" test -f "$CONTRACT_DIR/src/error.rs"

# Step 2: 检查包名
check_step "包名包含 _v1 后缀" grep -q 'name.*_v1' "$CONTRACT_DIR/Cargo.toml"

# Step 3: Rust 编译
echo ""
echo -e "${YELLOW}[BUILD]${NC} 编译 WASM..."
cd "$CONTRACT_DIR"

check_step "cargo build (非 WASM)" \
    CC=/usr/bin/gcc CXX=/usr/bin/g++ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/gcc \
    cargo build

check_step "cargo build (WASM)" \
    CC=/usr/bin/gcc CXX=/usr/bin/g++ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/gcc \
    cargo build --target wasm32-unknown-unknown --release

# Step 4: 测试
echo ""
echo -e "${YELLOW}[TEST]${NC} 运行 cargo test..."
check_step "cargo test" \
    CC=/usr/bin/gcc CXX=/usr/bin/g++ CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/gcc \
    cargo test

# Step 5: WASM 体积
echo ""
echo -e "${YELLOW}[SIZE]${NC} 检查 WASM 体积..."
WASM_FILE="target/wasm32-unknown-unknown/release/${CONTRACT_NAME//-/_}.wasm"
if [ -f "$WASM_FILE" ]; then
    WASM_SIZE=$(stat -c%s "$WASM_FILE" 2>/dev/null || stat -f%z "$WASM_FILE" 2>/dev/null)
    MAX_SIZE=819200
    if [ "$WASM_SIZE" -le "$MAX_SIZE" ]; then
        echo -e "${GREEN}[PASS]${NC} WASM 体积: ${WASM_SIZE} bytes (限制: ${MAX_SIZE})"
        VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
    else
        echo -e "${RED}[FAIL]${NC} WASM 体积: ${WASM_SIZE} bytes (超过 ${MAX_SIZE})"
        VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
    fi
else
    echo -e "${YELLOW}[SKIP]${NC} WASM 文件未找到,跳过体积检查"
fi

# Step 6: Schema 生成
echo ""
echo -e "${YELLOW}[SCHEMA]${NC} 生成 Schema..."
if [ -f "examples/schema.rs" ]; then
    cargo run --example schema && {
        echo -e "${GREEN}[PASS]${NC} Schema 生成成功"
        VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
    } || {
        echo -e "${RED}[FAIL]${NC} Schema 生成失败"
        VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
    }
else
    echo -e "${YELLOW}[SKIP]${NC} examples/schema.rs 不存在"
fi

# 返回项目根目录
cd "$PROJECT_DIR"

# 总结
echo ""
echo "============================================"
echo "  Validation Results for $CONTRACT_NAME"
echo -e "  ${GREEN}Passed: $VALIDATION_PASSED${NC}"
echo -e "  ${RED}Failed: $VALIDATION_FAILED${NC}"
echo "============================================"

if [ "$VALIDATION_FAILED" -gt 0 ]; then
    exit 1
fi
exit 0

9.4 run-all-contracts.sh — 批量合约验证

#!/bin/bash
# run-all-contracts.sh — 批量验证所有 MSG Chain CosmWasm 合约
# 遍历 contracts/cosmwasm/all/ 下所有合约并执行 validate-contract.sh

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
CONTRACTS_DIR="$PROJECT_DIR/contracts/cosmwasm/all"
VALIDATE_SCRIPT="$SCRIPT_DIR/validate-contract.sh"

if [ ! -d "$CONTRACTS_DIR" ]; then
    echo "❌ 合约目录不存在: $CONTRACTS_DIR"
    exit 1
fi

if [ ! -f "$VALIDATE_SCRIPT" ]; then
    echo "❌ validate-contract.sh 未找到"
    exit 1
fi

TOTAL_CONTRACTS=0
PASSED_CONTRACTS=0
FAILED_CONTRACTS=0

echo ""
echo "============================================"
echo "  MSG Chain 批量合约验证"
echo "  $(date)"
echo "============================================"

for contract_dir in "$CONTRACTS_DIR"/*/; do
    if [ -f "${contract_dir}Cargo.toml" ]; then
        TOTAL_CONTRACTS=$((TOTAL_CONTRACTS + 1))
        contract_name=$(basename "$contract_dir")
        echo ""
        echo "────────────────────────────────────────"
        echo "  [${TOTAL_CONTRACTS}] 验证: $contract_name"
        echo "────────────────────────────────────────"

        if bash "$VALIDATE_SCRIPT" "$contract_dir"; then
            PASSED_CONTRACTS=$((PASSED_CONTRACTS + 1))
        else
            FAILED_CONTRACTS=$((FAILED_CONTRACTS + 1))
        fi
    fi
done

echo ""
echo "============================================"
echo "  批量验证完成"
echo "  总计: $TOTAL_CONTRACTS"
echo "  通过: $PASSED_CONTRACTS"
echo "  失败: $FAILED_CONTRACTS"
echo "============================================"

if [ "$FAILED_CONTRACTS" -gt 0 ]; then
    exit 1
fi
exit 0

10. 覆盖率与报告

10.1 Rust 测试覆盖率 (Tarpaulin)

# 安装 tarpaulin
cargo install cargo-tarpaulin

# 运行覆盖率和合约测试
cd contracts/cosmwasm/all/aidid_did_registry_v1
cargo tarpaulin --out Html --output-dir coverage
ls -la coverage/

# 查看覆盖率报告
open coverage/tarpaulin-report.html

# CI 中使用 tarpaulin
cargo tarpaulin --out Xml --output-dir coverage --fail-under 70

10.2 Go 测试覆盖率

# 生成覆盖率报告
make test-coverage
# 产出: coverage.out (原始数据), coverage.html (HTML报告)

# 命令行查看函数级覆盖率
go tool cover -func=coverage.out

# 仅查看未覆盖的代码
go tool cover -func=coverage.out | grep "0%"

# 指定包生成覆盖率
go test -v -coverprofile=pkg_quantum.out ./pkg/quantum/...
go tool cover -html=pkg_quantum.out -o pkg_quantum_coverage.html

10.3 覆盖率聚合

#!/bin/bash
# aggregate-coverage.sh — 聚合 Rust 和 Go 覆盖率

set -euo pipefail

echo "=== MSG Chain 覆盖率聚合 ==="

# Go 覆盖率
go test -coverprofile=coverage.out ./pkg/...
go tool cover -func=coverage.out
go tool cover -html=coverage.out -o coverage_go.html

# Rust 合约覆盖率
if command -v cargo-tarpaulin &> /dev/null; then
    for contract in contracts/cosmwasm/all/*/; do
        if [ -f "$contract/Cargo.toml" ]; then
            name=$(basename "$contract")
            echo "=== Rust Coverage: $name ==="
            cd "$contract"
            cargo tarpaulin --out Html --output-dir coverage --skip-clean
            cd - > /dev/null
        fi
    done
fi

echo ""
echo "覆盖率报告已生成:"
echo "  - coverage_go.html (Go)"
echo "  - contracts/cosmwasm/all/*/coverage/ (Rust)"

10.4 CI 覆盖率徽章

# 在 README.md 中添加覆盖率徽章
# 使用 GitHub Actions 和 codecov 集成

# .github/workflows/coverage.yml
name: Coverage
on:
  push:
    branches: [main]

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.24'
      - name: Generate coverage
        run: |
          go test -v -race -coverprofile=coverage.out -covermode=atomic ./pkg/...
      - name: Upload to Codecov
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.out
          flags: unittests

11. DApp 测试

11.1 CosmJS 客户端测试 (Jest)

// tests/cosmjs-client.test.ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { GasPrice } from "@cosmjs/stargate";
import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx";

// 使用实时测试网 RPC(或 mock)
const TESTNET_RPC = "http://localhost:26657";
const TESTNET_CHAIN_ID = "msg-chain-1";

// Mock 合约地址
const MOCK_DID_REGISTRY = "msg14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmal";
const MOCK_AGENT_REGISTRY = "msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz4";

describe("CosmWasm Contracts", () => {
  let client: SigningCosmWasmClient | null = null;

  beforeAll(async () => {
    // 在测试环境中创建真实或 mock 客户端
    // 这里使用 mock 配置
    client = {} as SigningCosmWasmClient;
  });

  afterAll(async () => {
    if (client) {
      // await client.disconnect();
    }
  });

  describe("DID Registry", () => {
    it("should create a DID document", async () => {
      const createMsg = {
        create_did: {
          did_id: "did:msg:agent:test-agent-001",
          controller: "msg1controller",
          verification_methods: [
            {
              id: "vm-1",
              controller: "msg1controller",
              key_type: "Dilithium5VerificationKey2026",
              public_key_multibase: "z6Mk",
              dilithium5_public_key: "pk_example",
            },
          ],
          services: [],
          metadata: null,
        },
      };

      // 当使用真实客户端:
      // const result = await client!.execute(
      //   senderAddress,
      //   MOCK_DID_REGISTRY,
      //   createMsg,
      //   "auto"
      // );
      // expect(result.code).toBe(0);

      // Mock 测试
      expect(createMsg.create_did.did_id).toContain("did:msg:");
      expect(createMsg.create_did.verification_methods[0].key_type).toBe(
        "Dilithium5VerificationKey2026"
      );
    });

    it("should resolve a DID document", async () => {
      const queryMsg = {
        resolve_did: {
          did_id: "did:msg:agent:test-agent-001",
        },
      };

      // Mock 期望响应
      const mockResponse = {
        document: {
          did_id: "did:msg:agent:test-agent-001",
          controller: "msg1controller",
          active: true,
          verification_methods: [],
          services: [],
        },
      };

      expect(mockResponse.document.did_id).toBe(
        "did:msg:agent:test-agent-001"
      );
      expect(mockResponse.document.active).toBe(true);
    });

    it("should reject deactivated DID modifications", async () => {
      const deactivateMsg = {
        deactivate_did: {
          did_id: "did:msg:agent:test-agent-001",
        },
      };

      expect(deactivateMsg.deactivate_did.did_id).toBeDefined();

      const updateMsg = {
        update_did: {
          did_id: "did:msg:agent:test-agent-001",
          verification_methods: null,
          services: null,
          metadata: null,
        },
      };

      // 验证停用后的 DID 不应允许更新
      // 真实场景下这个 execute 应返回错误
      expect(updateMsg.update_did.did_id).toBe(
        deactivateMsg.deactivate_did.did_id
      );
    });
  });

  describe("Agent Registry", () => {
    it("should register an agent", async () => {
      const registerMsg = {
        register_agent: {
          agent_id: "agent-integration-001",
          did_id: "did:msg:agent:integration-001",
          owner: "msg1owner",
          name: "Integration Test Agent",
          description: "Agent created in integration test",
          endpoint: "https://test-agent.example.com",
          metadata: null,
          agent_type: "worker",
          capabilities: ["text-generation", "code-review"],
        },
      };

      expect(registerMsg.register_agent.agent_id).toBe(
        "agent-integration-001"
      );
      expect(registerMsg.register_agent.capabilities).toContain(
        "text-generation"
      );
    });

    it("should query agents by owner", async () => {
      const queryMsg = {
        get_agents_by_owner: {
          owner: "msg1owner",
          start_after: null,
          limit: 20,
        },
      };

      expect(queryMsg.get_agents_by_owner.owner).toBe("msg1owner");
      expect(queryMsg.get_agents_by_owner.limit).toBeLessThanOrEqual(100);
    });
  });

  describe("Gas Estimation", () => {
    it("should use correct gas price steps", () => {
      const gasPrices = {
        gas_price: "1000000000umsg",
      };

      const gasPrice = GasPrice.fromString(gasPrices.average);
      expect(gasPrice.denom).toBe("umsg");

      // 检查金额解析
      const coin = { denom: "umsg", amount: "1000000000000000000" };
      expect(Number(coin.amount)).toBe(1e18); // 18 decimals
    });
  });
});

11.2 React 组件测试 (React Testing Library)

// tests/components/ContractInteraction.test.tsx
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ContractInteraction } from "../src/components/ContractInteraction";

// Mock CosmJS
jest.mock("@cosmjs/cosmwasm-stargate", () => ({
  SigningCosmWasmClient: {
    connectWithSigner: jest.fn(),
  },
}));

jest.mock("@cosmjs/proto-signing", () => ({
  DirectSecp256k1HdWallet: {
    fromMnemonic: jest.fn(),
  },
}));

describe("ContractInteraction Component", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it("renders contract query form", () => {
    render(<ContractInteraction contractAddress="msg1test" />);
    expect(screen.getByText(/查询合约/i)).toBeInTheDocument();
    expect(screen.getByPlaceholderText(/JSON 查询消息/i)).toBeInTheDocument();
    expect(screen.getByRole("button", { name: /执行查询/i })).toBeInTheDocument();
  });

  it("handles query input and submission", async () => {
    const mockOnQuery = jest.fn();
    render(
      <ContractInteraction
        contractAddress="msg1test"
        onQuery={mockOnQuery}
      />
    );

    const input = screen.getByPlaceholderText(/JSON 查询消息/i);
    await userEvent.type(input, '{"get_count": {}}');

    const submitButton = screen.getByRole("button", { name: /执行查询/i });
    await userEvent.click(submitButton);

    expect(mockOnQuery).toHaveBeenCalledWith({ get_count: {} });
  });

  it("shows error for invalid JSON", async () => {
    render(<ContractInteraction contractAddress="msg1test" />);

    const input = screen.getByPlaceholderText(/JSON 查询消息/i);
    await userEvent.type(input, "invalid json");

    const submitButton = screen.getByRole("button", { name: /执行查询/i });
    await userEvent.click(submitButton);

    expect(screen.getByText(/JSON 格式错误/i)).toBeInTheDocument();
  });

  it("displays query result", async () => {
    const mockResult = { count: 42 };
    const mockExecute = jest.fn().mockResolvedValue(mockResult);

    render(
      <ContractInteraction
        contractAddress="msg1test"
        executeQuery={mockExecute}
      />
    );

    const input = screen.getByPlaceholderText(/JSON 查询消息/i);
    await userEvent.type(input, '{"get_count": {}}');

    const submitButton = screen.getByRole("button", { name: /执行查询/i });
    await userEvent.click(submitButton);

    await waitFor(() => {
      expect(screen.getByText(/count.*42/i)).toBeInTheDocument();
    });
  });
});

11.3 E2E 测试 (Playwright)

// e2e/contract-interaction.spec.ts
import { test, expect } from "@playwright/test";

test.describe("MSG Chain DApp E2E", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("http://localhost:3000");
  });

  test("connect wallet and query contract", async ({ page }) => {
    // 连接钱包
    await page.click('[data-testid="connect-wallet"]');
    await page.waitForSelector('[data-testid="wallet-connected"]');

    // 输入合约地址
    await page.fill(
      '[data-testid="contract-address-input"]',
      "msg14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmal"
    );

    // 输入查询消息
    await page.fill(
      '[data-testid="query-msg-input"]',
      '{"resolve_did": {"did_id": "did:msg:agent:test-1"}}'
    );

    // 执行查询
    await page.click('[data-testid="execute-query"]');

    // 等待响应
    await page.waitForSelector('[data-testid="query-result"]');
    const result = await page.textContent('[data-testid="query-result"]');
    expect(result).toContain("did:msg:agent:test-1");
  });

  test("execute contract and verify transaction", async ({ page }) => {
    // 连接钱包
    await page.click('[data-testid="connect-wallet"]');

    // 发送执行交易
    await page.fill('[data-testid="contract-address-input"]', MOCK_CONTRACT);
    await page.fill(
      '[data-testid="execute-msg-input"]',
      '{"create_did": {"did_id": "did:msg:agent:e2e-test", "controller": "msg1controller", "verification_methods": [], "services": []}}'
    );

    // 设置 gas
    await page.selectOption('[data-testid="gas-price-select"]', "1000000000umsg");

    await page.click('[data-testid="execute-tx"]');

    // 确认交易
    await page.waitForSelector('[data-testid="tx-success"]');
    const txHash = await page.textContent('[data-testid="tx-hash"]');
    expect(txHash).toMatch(/^0x[a-fA-F0-9]{64}$/);
  });

  test("display wallet balance", async ({ page }) => {
    await page.click('[data-testid="connect-wallet"]');

    await page.waitForSelector('[data-testid="wallet-balance"]');
    const balance = await page.textContent('[data-testid="wallet-balance"]');
    expect(balance).toContain("umsg");
  });
});

11.4 Playwright 配置

// playwright.config.ts
import { PlaywrightTestConfig } from "@playwright/test";

const config: PlaywrightTestConfig = {
  testDir: "./e2e",
  timeout: 60000,
  retries: 2,
  use: {
    baseURL: "http://localhost:3000",
    headless: true,
    viewport: { width: 1280, height: 720 },
    screenshot: "only-on-failure",
    video: "retain-on-failure",
  },
  projects: [
    {
      name: "Chrome",
      use: { browserName: "chromium" },
    },
    {
      name: "Firefox",
      use: { browserName: "firefox" },
    },
  ],
  webServer: {
    command: "npm run dev",
    port: 3000,
    timeout: 120000,
    reuseExistingServer: !process.env.CI,
  },
};

export default config;

11.5 前端测试 CI 集成

# .github/workflows/frontend-tests.yml
name: Frontend Tests
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          cache-dependency-path: 'frontend/package-lock.json'
      - name: Install dependencies
        working-directory: frontend
        run: npm ci
      - name: Lint
        working-directory: frontend
        run: npm run lint
      - name: Unit tests
        working-directory: frontend
        run: npm test -- --coverage
      - name: TypeScript check
        working-directory: frontend
        run: npx tsc --noEmit
      - name: Install Playwright
        working-directory: frontend
        run: npx playwright install
      - name: E2E tests
        working-directory: frontend
        run: npx playwright test
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: frontend-coverage
          path: frontend/coverage/

12. 附录

12.1 MSG Chain 46 个合约包列表

以下合约均位于 contracts/cosmwasm/all/ 目录,每个包可使用相同模式验证:

# 列举所有合约
ls -1 contracts/cosmwasm/all/
# 每个合约的标准操作:
#   cd contracts/cosmwasm/all/<name> && cargo test
#   cd contracts/cosmwasm/all/<name> && cargo build --target wasm32-unknown-unknown --release

核心合约包示例:

12.2 故障排查速查

问题 原因 解决方法
cargo test 编译失败 wasm32 target 未安装 rustup target add wasm32-unknown-unknown
make lint 失败 gofmt 格式错误 make fmt
make test 失败 Go 依赖过期 make deps
make test-quantum 失败 Dilithium-5 库缺失 检查 go.sum 中的 cloudflare/circl
make ci-contracts 失败 合约编译错误 单独运行 cargo build --target wasm32-unknown-unknown --release 查看错误
CI 通过但链上上线失败 模拟环境差异 检查真实 receipt/query/log,使用 --trace 调试

12.3 环境变量与配置

# MSG Chain CI/CD 环境变量
export GO_VERSION="1.24"
export GO_CACHE_PATH="/tmp/msg-chain-go-build-cache"
export GOLANGCI_LINT_CACHE="/tmp/msg-chain-golangci-cache"
export RUST_TOOLCHAIN="stable"
export WASMVM_LIB_DIR="$(pwd)/lib"
export LD_LIBRARY_PATH="$WASMVM_LIB_DIR:$LD_LIBRARY_PATH"
export RUST_CC="/usr/bin/gcc"
export RUST_CXX="/usr/bin/g++"
export RUST_LINKER="/usr/bin/gcc"

# CI 参数
export CI_COVERAGE_THRESHOLD="60"
export CONTRACT_WASM_MAX_SIZE="819200"  # 800KB в bytes

12.4 参考文档


本文档是 MSG Chain 开发团队的测试与 CI/CD 标准参考。
所有命令和配置均基于 msg-chain-1 主网参数。
生产发布必须在所有门禁通过后进行,且必须经过人工审批。