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) { public void isInvalidHttpCode(OriginalResponse response) {
if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) { if (response.getStatusCode() < HTTP_OK || response.getStatusCode() >= HTTP_MULT_CHOICE) {
throw new ValidationException(String.format("校验失败, HttpStatusCode=%s, Request-Id=%s, HttpResponseBody=%s", 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) { public void validateTimestamp(OriginalResponse response) {
String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP); String timestamp = response.getHttpHeaders().getHeader(TIMESTAMP);
if (Strings.isEmpty(timestamp)) { if (Strings.isEmpty(timestamp)) {
throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP, throw new TimestampException(String.format("校验失败, 时间戳[%s]不存在, Request-Id=%s, HttpResponseBody=%s",
response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); TIMESTAMP, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
} }
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期请求 // 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) { if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s", TIMESTAMP, throw new TimestampException(String.format("校验失败, 时间戳[%s=%s]已过期, Request-Id=%s, HttpResponseBody=%s",
timestamp, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); 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 message = profile.getSignature().getCredential().buildResponseMessage(response, channel);
String signature = response.getHttpHeaders().getHeader(SIGNATURE); String signature = response.getHttpHeaders().getHeader(SIGNATURE);
if (signature == null || signature.isEmpty()) { if (Strings.isEmpty(signature)) {
throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s", SIGNATURE, throw new ValidationException(String.format("校验失败, 签名[%s]不存在, Request-Id=%s, HttpResponseBody=%s",
response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); SIGNATURE, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
} }
// 签名证书编号 // 签名证书编号
String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL); String serialNumber = response.getHttpHeaders().getHeader(BANK_CERTIFICATE_SERIAL);
if (!verifier.verify(serialNumber, message, signature)) { if (!verifier.verify(serialNumber, message, signature)) {
throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s", SIGNATURE, throw new ValidationException(String.format("校验失败, 签名[%s=%s]校验未通过, Request-Id=%s, HttpResponseBody=%s",
signature, response.getHttpHeaders().getHeader(REQUEST_ID), plainBody(response))); SIGNATURE, signature, response.getHttpHeaders().getHeader(REQUEST_ID), decryptResponseBodyIfNecessary(response)));
} }
} }
private String plainBody(OriginalResponse originalResponse) { private String decryptResponseBodyIfNecessary(OriginalResponse originalResponse) {
byte[] body = originalResponse.getBody(); // 原始应答报文
byte[] responseBody = originalResponse.getBody();
// 判断是否有body加密密钥 // 判断是否有body加密密钥
if (!originalResponse.getHttpHeaders().hasHeader(SECRET_KEY)) { if (!originalResponse.getHttpHeaders().hasHeader(SECRET_KEY)) {
return Strings.toStr(body); // 明文直接返回
return Strings.toStr(responseBody);
} }
// 解密密钥 // SecretKey
String secretKey = originalResponse.getHttpHeaders().getHeader(SECRET_KEY); String secretKey = originalResponse.getHttpHeaders().getHeader(SECRET_KEY);
// 解密密钥
String decryptSecretKey = getProfile().getPrivacy().getDecryptor().decrypt(secretKey); String decryptSecretKey = getProfile().getPrivacy().getDecryptor().decrypt(secretKey);
// 解密body // 解密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.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
@ -23,6 +24,7 @@ import java.util.Map;
import static com.czcb.scfs.api.core.Constants.TIMESTAMP; import static com.czcb.scfs.api.core.Constants.TIMESTAMP;
class DefaultValidatorTest { class DefaultValidatorTest {
// 创建配置文件
Profile buildProfile() { Profile buildProfile() {
PrivateKey privateKey = KeyText.loadTestPrivateKeyRSA(); PrivateKey privateKey = KeyText.loadTestPrivateKeyRSA();
X509Certificate certificate = KeyText.loadTestRSA(); X509Certificate certificate = KeyText.loadTestRSA();
@ -83,9 +85,51 @@ class DefaultValidatorTest {
.statusCode(500) .statusCode(500)
.build(); .build();
Assertions.assertThrows(ValidationException.class, () -> { ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.isInvalidHttpCode(response));
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 @Test
@ -108,8 +152,9 @@ class DefaultValidatorTest {
defaultValidator.validateTimestamp(response); defaultValidator.validateTimestamp(response);
} }
// 没有时间戳案例
@Test @Test
void validateTimestampError() { void validateTimestampNoneError() {
DefaultValidator defaultValidator = new DefaultValidator(buildProfile()); DefaultValidator defaultValidator = new DefaultValidator(buildProfile());
Map<String, String> headers = new HashMap<>(); Map<String, String> headers = new HashMap<>();
@ -124,9 +169,30 @@ class DefaultValidatorTest {
.statusCode(500) .statusCode(500)
.build(); .build();
Assertions.assertThrows(TimestampException.class, () -> { TimestampException validationException = Assertions.assertThrows(TimestampException.class, () -> defaultValidator.validateTimestamp(response));
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 @Test
@ -142,14 +208,77 @@ class DefaultValidatorTest {
.build()) .build())
.body("123") .body("123")
.headers(headers) .headers(headers)
.statusCode(500) .statusCode(200)
.build(); .build();
Assertions.assertThrows(ValidationException.class, () -> { ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder()
defaultValidator.validateResponseSignature(response, new DefaultChannel.Builder() .channelNo("1010")
.channelNo("1010") .appNo("1111")
.appNo("1111") .build()));
.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());
} }
} }