test: 单元测试

main
13009 2024-05-29 10:04:46 +08:00
parent 03e6ac3f9f
commit 33baa0436d
3 changed files with 190 additions and 29 deletions

View File

@ -49,22 +49,22 @@ public final class DefaultValidator implements Validator {
public void isInvalidHttpCode(OriginalResponse response) {
if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) {
throw new ValidationException(String.format("校验失败, HttpStatusCode=%s, Request-Id=%s, HttpResponseBody=%s",
response.getStatusCode(), response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
response.getStatusCode(), response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
}
}
public void validateTimestamp(OriginalResponse response) {
String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP);
if (Strings.isEmpty(timestamp)) {
throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s",
TIMESTAMP, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
}
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP,
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s",
TIMESTAMP, timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
}
}
@ -72,31 +72,34 @@ public final class DefaultValidator implements Validator {
// 待签名串
String message = profile.getSignature().getCredential().buildResponseMessage(response, channel);
String signature = response.getHttpHeaders().getHeader(SIGNATURE);
if (signature == null || signature.isEmpty()) {
throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
if (Strings.isEmpty(signature)) {
throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s",
SIGNATURE, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
}
// 签名证书编号
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
if (!verifier.verify(serialNumber, message, signature)) {
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE,
signature, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response)));
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s",
SIGNATURE, signature, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
}
}
private String plainBody(OriginalResponse originalResponse) {
byte[] body = originalResponse.getBody();
private String decryptResponseBodyIfNecessary(OriginalResponse originalResponse) {
// 原始应答报文
byte[] responseBody = originalResponse.getBody();
// 判断是否有body加密密钥
if (!originalResponse.getHttpHeaders().hasHeader(SECRET_KEY)) {
return Strings.toStr(body);
// 明文直接返回
return Strings.toStr(responseBody);
}
// 解密密钥
// SecretKey
String secretKey = originalResponse.getHttpHeaders().getHeader(SECRET_KEY);
// 解密密钥
String decryptSecretKey = getProfile().getPrivacy().getDecryptor().decrypt(secretKey);
// 解密body
return getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(decryptSecretKey), body);
return getProfile().getPrivacy().getSecretCipher().decrypt(Strings.toBytes(decryptSecretKey), responseBody);
}
}

View File

@ -0,0 +1,29 @@
package com.czcb.scfs.api.core;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class ConstantsTest {
@Test
void test() {
Assertions.assertEquals("X-SCFS-Channel-Serial", Constants.CHANNEL_CERTIFICATE_SERIAL);
Assertions.assertEquals("X-SCFS-Channel-No", Constants.CHANNEL_NO);
Assertions.assertEquals("X-SCFS-App-No", Constants.APP_NO);
Assertions.assertEquals("X-SCFS-Signature", Constants.SIGNATURE);
Assertions.assertEquals("X-SCFS-Api-Version", Constants.API_VERSION);
Assertions.assertEquals("X-SCFS-Secret-Key", Constants.SECRET_KEY);
Assertions.assertEquals("X-SCFS-Nonce", Constants.NONCE);
Assertions.assertEquals("X-SCFS-Timestamp", Constants.TIMESTAMP);
Assertions.assertEquals("X-SCFS-Request-Id", Constants.REQUEST_ID);
Assertions.assertEquals("Content-Type", Constants.CONTENT_TYPE);
Assertions.assertEquals("Authorization", Constants.AUTHORIZATION);
Assertions.assertEquals("Accept-Encoding", Constants.ACCEPT_ENCODING);
Assertions.assertEquals("gzip", Constants.GZIP_ENCODING);
Assertions.assertEquals("Content-Encoding", Constants.CONTENT_ENCODING);
Assertions.assertEquals("User-Agent", Constants.USER_AGENT);
Assertions.assertEquals(16, Constants.HEX);
}
}

View File

@ -13,6 +13,7 @@ import com.czcb.scfs.api.core.http.client.TestVerifier;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
@ -23,6 +24,7 @@ import java.util.Map;
import static com.czcb.scfs.api.core.Constants.TIMESTAMP;
class DefaultValidatorTest {
// 创建配置文件
Profile buildProfile() {
PrivateKey privateKey = KeyText.loadTestPrivateKeyRSA();
X509Certificate certificate = KeyText.loadTestRSA();
@ -83,9 +85,51 @@ class DefaultValidatorTest {
.statusCode(500)
.build();
Assertions.assertThrows(ValidationException.class, () -> {
defaultValidator.isInvalidHttpCode(response);
});
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response));
Assertions.assertEquals("校验失败, HttpStatusCode=500, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
@Test
void isInvalidHttpCodeError1xx() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Map<String, String> headers = new HashMap<>();
headers.put("X-SCFS-Request-Id", "123456789");
OriginalResponse response = new OriginalResponse.Builder()
.request(new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url("http://demo")
.build())
.body("123")
.headers(headers)
.statusCode(100)
.build();
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response));
Assertions.assertEquals("校验失败, HttpStatusCode=100, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
@Test
void isInvalidHttpCodeError1xxBody() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
String plain = "0987654321123456";
String secretKey = defaultValidator.getProfile().getPrivacy().getEncryptor().encrypt(plain);
Map<String, String> headers = new HashMap<>();
headers.put("X-SCFS-Request-Id", "123456789");
headers.put("X-SCFS-Secret-Key", secretKey);
OriginalResponse response = new OriginalResponse.Builder()
.request(new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url("http://demo")
.build())
.body(defaultValidator.getProfile().getPrivacy().getSecretCipher().encrypt(plain.getBytes(StandardCharsets.UTF_8), "123".getBytes(StandardCharsets.UTF_8)))
.headers(headers)
.statusCode(100)
.build();
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response));
Assertions.assertEquals("校验失败, HttpStatusCode=100, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
@Test
@ -108,8 +152,9 @@ class DefaultValidatorTest {
defaultValidator.validateTimestamp(response);
}
// 没有时间戳案例
@Test
void validateTimestampError() {
void validateTimestampNoneError() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Map<String, String> headers = new HashMap<>();
@ -124,9 +169,30 @@ class DefaultValidatorTest {
.statusCode(500)
.build();
Assertions.assertThrows(TimestampException.class, () -> {
defaultValidator.validateTimestamp(response);
});
TimestampException validationException = Assertions.assertThrows(TimestampException.class, () -> defaultValidator.validateTimestamp(response));
Assertions.assertEquals("校验失败, 时间戳[X-SCFS-Timestamp]不存在, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
// 时间戳错误案例
@Test
void validateTimestampError() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Map<String, String> headers = new HashMap<>();
headers.put("X-SCFS-Request-Id", "123456789");
headers.put("X-SCFS-Timestamp", "1716941850");
OriginalResponse response = new OriginalResponse.Builder()
.request(new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url("http://demo")
.build())
.body("123")
.headers(headers)
.statusCode(200)
.build();
TimestampException validationException = Assertions.assertThrows(TimestampException.class, () -> defaultValidator.validateTimestamp(response));
Assertions.assertEquals("校验失败, 时间戳[X-SCFS-Timestamp=1716941850]已过期, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
@Test
@ -142,14 +208,77 @@ class DefaultValidatorTest {
.build())
.body("123")
.headers(headers)
.statusCode(500)
.statusCode(200)
.build();
Assertions.assertThrows(ValidationException.class, () -> {
defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder()
.channelNo("1010")
.appNo("1111")
.build());
});
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder()
.channelNo("1010")
.appNo("1111")
.build()));
Assertions.assertEquals("校验失败, 签名[X-SCFS-Signature]不存在, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
@Test
void validateResponseSignatureError() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Map<String, String> headers = new HashMap<>();
headers.put("X-SCFS-Request-Id", "123456789");
headers.put("X-SCFS-Signature", "MTIzNDU2Nzg5");
headers.put("X-SCFS-Serial", "6CDDAA92CAD75998325027647847330C1756291");
OriginalResponse response = new OriginalResponse.Builder()
.request(new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url("http://demo")
.build())
.body("123")
.headers(headers)
.statusCode(200)
.build();
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder()
.channelNo("1010")
.appNo("1111")
.build()));
Assertions.assertEquals("校验失败, 签名[X-SCFS-Signature=MTIzNDU2Nzg5]校验未通过, Request-Id=123456789, HttpResponseBody=123", validationException.getLocalizedMessage());
}
@Test
void validate() {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String message = String.format("X-SCFS-Nonce=3333333333,X-SCFS-Timestamp=%s,X-SCFS-Serial=6CDDAA92CAD75998325027647847330C1756291,X-SCFS-Channel-Serial=6CDDAA92CAD75998325027647847330C1756291,X-SCFS-Secret-Key=ddd\n" +
"123\n", timestamp);
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Map<String, String> headers = new HashMap<>();
headers.put("X-SCFS-Request-Id", "123456789");
headers.put("X-SCFS-Timestamp", timestamp);
headers.put("X-SCFS-Nonce", "3333333333");
headers.put("X-SCFS-Secret-Key", "ddd");
headers.put("X-SCFS-Signature", defaultValidator.getProfile().getSignature().getSigner().sign(message).getSignature());
headers.put("X-SCFS-Serial", "6CDDAA92CAD75998325027647847330C1756291");
headers.put("X-SCFS-Channel-Serial", "6CDDAA92CAD75998325027647847330C1756291");
OriginalResponse response = new OriginalResponse.Builder()
.request(new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url("http://demo")
.build())
.body("123")
.headers(headers)
.statusCode(200)
.build();
Assertions.assertDoesNotThrow(() -> defaultValidator.validate(null, response, new DefaultChannel.Builder()
.channelNo("1010")
.appNo("1111")
.build()));
}
@Test
void getProfile() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Assertions.assertNotNull(defaultValidator.getProfile());
Assertions.assertEquals("000000", defaultValidator.getProfile().getChannel().getChannelNo());
}
}