Commit f447934c authored by xie.qin's avatar xie.qin

java-sdk supported.

parent 9b3bb3cd
English, please click [here](./HELP.md)
# 一款实用的API和SDK自动化测试框架
# 一款实用的API和Java SDK自动化测试框架
## 1. API自动化测试
```text
......@@ -119,7 +119,7 @@ RPC形式的API,又以 gRPC 和 json-RPC 两种实现方式居多。
- 需要生成service类(option java_generic_services = true;)
2. 通过 gradle 插件命令生成java class。可携带参数chainVer,表示proto文件版本。
- gradlew generateProto -DchainVer=v1.6.6
3. 编写 gRPC 客户端请求数据文件([参考链接](/src/test/resources/testdatacollection/v2.2.0/runtime/OpsInChain/sendTransaction.json))
3. 编写 gRPC 客户端请求数据文件([参考链接](/src/test/resources/testdatacollection/v2.2.0/runtime/chain_ops/sendTransaction.json))
3. 由于 gRPC 接口对负载净荷数据没有统一规范要求,所以只能手工实现请求数据的发送和响应数据的验证:
- 编写 gRPC 客户端发送程序代码(主要是把步骤3中的数据,序列化成2进制码流。[参考链接](/src/main/java/com/fuzamei/autotest/rpc/grpc/GrpcClientTransaction.java))
- 编写 gRPC 服务端响应数据验证代码(主要是和 proto 文件中定义的 service.returns 进行结果比对)。
......
......@@ -39,14 +39,14 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'io.qameta.allure:allure-okhttp3:2.14.0'
implementation 'com.github.briandilley.jsonrpc4j:jsonrpc4j:1.6'
implementation 'com.google.protobuf:protobuf-java:3.10.0'
implementation 'com.google.protobuf:protobuf-java-util:3.10.0'
implementation 'io.grpc:grpc-netty:1.39.0'
implementation 'com.google.protobuf:protobuf-java:3.17.3'
implementation 'com.google.protobuf:protobuf-java-util:3.17.3'
//implementation 'io.grpc:grpc-netty:1.39.0'
implementation 'net.devh:grpc-client-spring-boot-starter:2.12.0.RELEASE' exclude group: 'io.grpc', module: 'grpc-netty-shaded'
implementation 'com.alibaba:fastjson:1.2.47'
implementation 'org.bitcoinj:bitcoinj-core:0.14.7'
implementation 'org.slf4j:slf4j-log4j12:1.7.30'
//implementation 'org.slf4j:slf4j-log4j12:1.7.30'
implementation 'org.bouncycastle:bcprov-jdk15on:1.67'
implementation 'net.vrallev.ecc:ecc-25519-java:1.0.3'
implementation 'io.grpc:grpc-all:1.39.0'
......
......@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "service.backend")
@PropertySource(value = "classpath:application.properties", encoding = "UTF-8")
@PropertySource(value = "classpath:application.yml", encoding = "UTF-8")
@Setter
@Getter
public class BackendServiceProperties {
......
package com.fuzamei.autotest.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "service.chain33")
@PropertySource(value = "classpath:application.yml", encoding = "UTF-8")
@Setter
@Getter
public class Chain33Properties {
private String host;
private Integer jrpcPort;
private Integer grpcPort;
}
package com.fuzamei.autotest.sdk;
import java.io.IOException;
import java.util.Arrays;
import cn.chain33.javasdk.client.Account;
import cn.chain33.javasdk.client.RpcClient;
import cn.chain33.javasdk.model.AccountInfo;
import cn.chain33.javasdk.model.gm.SM3Util;
import cn.chain33.javasdk.model.gm.SM4Util;
import cn.chain33.javasdk.model.rpcresult.QueryTransactionResult;
import cn.chain33.javasdk.utils.AesUtil;
import cn.chain33.javasdk.utils.HexUtil;
import cn.chain33.javasdk.utils.TransactionUtil;
/**
* 数据存证上链(sha256,sm3, aes, sm4几种方式上链)
* @author fkeit
*
*/
public class DemoSample {
private static RpcClient client = new RpcClient("区块链节点IP地址", 8801);
// 智能合约的名称
private static String execer = "";
// 合约地址
private static String contractAddress = "";
// 存证内容,一个json字符串
String content = "{\"档案编号\":\"ID0000001\",\"企业代码\":\"QY0000001\",\"业务标识\":\"DA000001\",\"来源系统\":\"OA\", \"文档摘要\",\"0x93689a705ac0bb4612824883060d73d02534f8ba758f5ca21a343beab2bf7b47\"}";
/**
* 用户私钥
*/
private String privateKey = "";
/**
* 用户地址
*/
private String address = "";
/**
*
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception {
DemoSample demoSample = new DemoSample();
// 存证智能合约的名称(简单存证,固定就用这个名称)
execer = "user.write";
// 合约地址
contractAddress = client.convertExectoAddr(execer);
// 创建区块链地址和私钥
demoSample.createAccount();
System.out.println("Create account success.");
// sha256哈希上链存储
demoSample.sha256Store();
System.out.println("sha256 store success.");
// sm3哈希上链存储
demoSample.sm3Store();
System.out.println("sm3 store success.");
// aes加密上链存储
demoSample.aesStore();
System.out.println("aes store success.");
// sm4加密上链存储
demoSample.sm4Store();
System.out.println("sm4 store success.");
}
/**
* 创建区块链地址和私钥
*/
private void createAccount() {
Account account = new Account();
// 获取签名用的私钥
AccountInfo accountInfo = account.newAccountLocal();
privateKey = accountInfo.getPrivateKey();
address = accountInfo.getAddress();
System.out.println("用户地址:" + address);
System.out.println("用户私钥:" + privateKey);
}
/**
* sha256哈希后上链
*/
private void sha256Store() throws IOException {
byte[] contentHash = TransactionUtil.Sha256(content.getBytes());
String txEncode = TransactionUtil.createTransferTx(privateKey, contractAddress, execer, contentHash);
String hash = client.submitTransaction(txEncode);
System.out.println("sha256存证has: " + hash);
}
/**
* sm3哈希后上链
* @throws IOException
*/
private void sm3Store() throws IOException {
byte[] contentHash = SM3Util.hash(content.getBytes());
String txEncode = TransactionUtil.createTransferTx(privateKey, contractAddress, execer, contentHash);
String hash = client.submitTransaction(txEncode);
System.out.println("sm3存证hash:" + hash);
}
/**
* aes对称加密后上链
* @throws Exception
*/
private void aesStore() throws Exception {
// 生成AES加密KEY
String aesKeyHex = "ba940eabdf09ee0f37f8766841eee763";
//可用该方法生成 AesUtil.generateDesKey(128);
byte[] key = HexUtil.fromHexString(aesKeyHex);
// 生成iv
byte[] iv = AesUtil.generateIv();
// 对明文进行加密
byte[] encrypt = AesUtil.encrypt(content, key, iv);
String txEncode = TransactionUtil.createTransferTx(privateKey, contractAddress, execer, encrypt);
String hash = client.submitTransaction(txEncode);
System.out.println("AES加密存证hash:" + hash);
Thread.sleep(10000);
// 查询结果
queryAesStorage(hash);
}
/**
* sm4对称加密后上链
* @throws Exception
*/
private void sm4Store() throws Exception {
// 生成SM4加密KEY
String sm4KeyHex = "ba940eabdf09ee0f37f8766841eee763";
//可用该方法生成 AesUtil.generateDesKey(128);
byte[] key = HexUtil.fromHexString(sm4KeyHex);
// 对明文进行加密
byte[] cipherText = SM4Util.encryptECB(key, content.getBytes());
String txEncode = TransactionUtil.createTransferTx(privateKey, contractAddress, execer, cipherText);
String hash = client.submitTransaction(txEncode);
System.out.println("SM4加密上链hash:" + hash);
Thread.sleep(5000);
// 查询结果
querySM4Storage(hash);
}
/**
* 查询AES
* @throws Exception
*/
public void queryAesStorage(String hash) throws Exception {
QueryTransactionResult queryTransaction;
queryTransaction = client.queryTransaction(hash);
//隐私存证
String desKey = "ba940eabdf09ee0f37f8766841eee763";
String result = queryTransaction.getTx().getRawpayload();
if(result.startsWith("0x")) {
result = result.substring(2);
}
byte[] fromHexString = HexUtil.hexStringToBytes(result);
System.out.println(Arrays.toString(fromHexString));
String decrypt = AesUtil.decrypt(fromHexString, desKey);
System.out.println("AES解密结果:" + decrypt);
}
/**
* 查询SM4
* @throws Exception
*/
public void querySM4Storage(String hash) throws Exception {
QueryTransactionResult queryTransaction;
queryTransaction = client.queryTransaction(hash);
// 生成SM4加密KEY
String sm4KeyHex = "ba940eabdf09ee0f37f8766841eee763";
//可用该方法生成 AesUtil.generateDesKey(128);
byte[] key = HexUtil.fromHexString(sm4KeyHex);
String result = queryTransaction.getTx().getRawpayload();
if(result.startsWith("0x")) {
result = result.substring(2);
}
//隐私存证
byte[] fromHexString = HexUtil.hexStringToBytes(result);
byte[] decryptedData = SM4Util.decryptECB(key, fromHexString);
System.out.println("SM4解密结果:" + new String(decryptedData));
}
}
package com.fuzamei.autotest.sdk.chain33;
import cn.chain33.javasdk.client.Account;
import cn.chain33.javasdk.model.AccountInfo;
import cn.chain33.javasdk.utils.TransactionUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fuzamei.autotest.properties.GlobalProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
@Slf4j
@Component
public class Chain33SdkCaller {
@Autowired
Chain33SessionContext chain33SessionContext;
@Autowired
GlobalProperties globalProperties;
public void createAccount() throws Throwable {
Account account = new Account();
// 获取签名用的私钥
AccountInfo accountInfo = account.newAccountLocal();
chain33SessionContext.setAccountPrivateKey(accountInfo.getPrivateKey());
chain33SessionContext.setAccountPublicKey(accountInfo.getPublicKey());
chain33SessionContext.setAccountAddr(accountInfo.getAddress());
log.debug("generated account private key is: {}", chain33SessionContext.getAccountPrivateKey());
assertThat("Generated account private key is invalid!", chain33SessionContext.getAccountPrivateKey(), notNullValue());
assertThat("Generated account private key is invalid!", chain33SessionContext.getAccountPrivateKey().length(), is(not(0)));
log.debug("generated account public key is: {}", chain33SessionContext.getAccountPublicKey());
assertThat("Generated account private key is invalid!", chain33SessionContext.getAccountPublicKey(), notNullValue());
assertThat("Generated account private key is invalid!", chain33SessionContext.getAccountPublicKey().length(), is(not(0)));
log.debug("generated account address is: {}", chain33SessionContext.getAccountAddr());
assertThat("Generated account private key is invalid!", chain33SessionContext.getAccountAddr(), notNullValue());
assertThat("Generated account private key is invalid!", chain33SessionContext.getAccountAddr().length(), is(not(0)));
}
public void sha256Store(String dataNum) {
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode rootNode = objectMapper.readTree(new File(globalProperties.getTestdatapathintesting()));
String content = rootNode.get(dataNum).asText();
byte[] contentHash = TransactionUtil.Sha256(content.getBytes());
String execer = "user.write";
String txEncode = TransactionUtil.createTransferTx(chain33SessionContext.getAccountPrivateKey(), chain33SessionContext.getContracts().get(execer), execer, contentHash);
String hash = chain33SessionContext.getJrpcClient().submitTransaction(txEncode);
log.info("store process finished, get the returned hash code from chain33 is: {}", hash);
}
catch (IOException e){
log.error("parsing testing data error. the error is: {}", e);
e.printStackTrace();
assertThat("Store data process failed as exception!", e, equalTo(null));
}
}
}
package com.fuzamei.autotest.sdk.chain33;
import cn.chain33.javasdk.client.GrpcClient;
import cn.chain33.javasdk.client.RpcClient;
import lombok.*;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@Getter
@Setter
public class Chain33SessionContext {
private RpcClient jrpcClient;
private GrpcClient grpcClient;
private String accountPrivateKey;
private String accountPublicKey;
private String accountAddr;
private String contractAddress; //合约地址
private String execer; //智能合约的名称
private Map<String, String> contracts;
}
......@@ -24,20 +24,6 @@ logging.level.com.fuzamei.autotest=DEBUG
logging.config=classpath:logback-spring.xml
spring.jackson.default-property-inclusion=non_null
service.backend.https=false
service.backend.http2.enabled=false
service.backend.host=172.22.18.152
service.backend.port=2345
service.backend.openapidefinitionpath=/
service.mysql.host=172.22.18.152
service.mysql.database=baas
spring.datasource.username=baas
spring.datasource.password=baas
spring.datasource.url=jdbc:mysql://${service.mysql.host}:3306/${service.mysql.database}?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.fuzamei.autotest.testdata.entity
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
......
......@@ -4,4 +4,26 @@ grpc:
address: static://172.22.19.2:8802
enableKeepAlive: true
keepAliveWithoutCalls: true
negotiationType: plaintext
\ No newline at end of file
negotiationType: plaintext
service:
backend:
host: 172.22.18.152
port: 2345
openapidefinitionpath: /
https: false
http2:
enabled: false
mysql:
host: 172.22.18.152
database: baas
chain33:
host: 172.22.19.7
jrpcPort: 8801
grpcPort: 8802
spring:
datasource:
username: baas
password: baas
url: jdbc:mysql://${service.mysql.host}:3306/${service.mysql.database}?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
package com.fuzamei.autotest.steps.sdk;
import cn.chain33.javasdk.client.GrpcClient;
import cn.chain33.javasdk.client.RpcClient;
import com.fuzamei.autotest.properties.Chain33Properties;
import com.fuzamei.autotest.properties.GlobalProperties;
import com.fuzamei.autotest.sdk.chain33.Chain33SdkCaller;
import com.fuzamei.autotest.sdk.chain33.Chain33SessionContext;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.File;
import java.util.*;
@Slf4j
public class Chain33SdkVerification {
@Autowired
Chain33SdkCaller chain33SdkCaller;
@Autowired
Chain33SessionContext chain33SessionContext;
@Autowired
Chain33Properties chain33Properties;
@Autowired
GlobalProperties globalProperties;
@Given("^Key pair used by block chain was created$")
public void createAccount() throws Throwable {
chain33SdkCaller.createAccount();
}
@Then("^Store data (.*?) into block chain after hash calculation with (.*?)$")
public void store(String dataNum, String calcType) throws Throwable {
switch (calcType.toLowerCase()){
case "sha256":
chain33SdkCaller.sha256Store(dataNum);
break;
}
}
@Then("^Initialize chain33 session context$")
public void initializeChainSessionContext() throws Throwable {
if (chain33SessionContext.getJrpcClient() == null) {
RpcClient jrpcClient = new RpcClient(chain33Properties.getHost(), chain33Properties.getJrpcPort());
chain33SessionContext.setJrpcClient(jrpcClient);
}
if (chain33SessionContext.getGrpcClient() == null) {
GrpcClient grpcClient = new GrpcClient(chain33Properties.getHost(), chain33Properties.getGrpcPort());
chain33SessionContext.setGrpcClient(grpcClient);
}
if (chain33SessionContext.getContracts()== null || !chain33SessionContext.getContracts().containsKey("user.write")) { //存证智能合约的名称
String contractAddr = chain33SessionContext.getJrpcClient().convertExectoAddr("user.write");
Map<String, String> contract = new HashMap<String, String>();
contract.put("user.write", contractAddr);
chain33SessionContext.setContracts(contract);
}
}
}
......@@ -2,5 +2,5 @@
Feature: Account Management Contract Test
Scenario: Account registration by RPC interface
Given The testing RUNTIME data OpsInChain.sendTransaction is ready
Given The testing RUNTIME data chain_ops.sendTransaction is ready
Then Register an account in chain33 with dataNum sendTransaction.001 by gRPC interface
\ No newline at end of file
@sdk
Feature: JDK demo for storage service
Background: Initialize chain33 session context
Then Initialize chain33 session context
Scenario: demo scenario for storage service
Given Key pair used by block chain was created
Given The testing RUNTIME data chain_ops.store is ready
Then Store data digest.001 into block chain after hash calculation with SHA256
\ No newline at end of file
{
"digest.001": {
"档案编号":"ID0000001",
"企业代码":"QY0000001",
"业务标识":"DA000001",
"来源系统":"OA",
"文档摘要":"0x93689a705ac0bb4612824883060d73d02534f8ba758f5ca21a343beab2bf7b47"
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment