diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb08ff7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# Created by .ignore support plugin (hsz.mobi) +.eunit +deps +*.o +*.beam +*.plt +erl_crash.dump +ebin/*.beam +rel/example_project +.concrete/DEV_MODE +.rebar +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/ +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +*.iws +*.ipr +*.iml + + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +env.list diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ca5a761 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "build_utils"] + path = build_utils + url = git@github.com:rbkmoney/build_utils.git + branch = master diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..b1dda7d --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,15 @@ +#!groovy +build('adapter-bank-payout-spring-boot-starter', 'docker-host') { + + checkoutRepo() + loadBuildUtils() + + def javaLibPipeline + runStage('load JavaLib pipeline') { + javaLibPipeline = load("build_utils/jenkins_lib/pipeJavaLib.groovy") + } + + def buildImageTag = "fcf116dd775cc2e91bffb6a36835754e3f2d5321" + javaLibPipeline(buildImageTag) + +} \ No newline at end of file diff --git a/README.md b/README.md index 6710598..8c31e40 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# adapter-bank-payout-spring-boot-starter \ No newline at end of file +# adapter-bank-payout-spring-boot-starter + +For new payout: + +1. implement RemoteClient +2. implement ResultProcessor and config ChainProcessor: + + ``` + @Bean + @Autowired + public ResultProcessor responseProcessorChain(ErrorMapping errorMapping) { + SuccessProcessor successProcessor = new SuccessProcessor(null); + return new ErrorProcessor(errorMapping, successProcessor); + } + ``` +3. implement WithdrawalConverter +4. implement GenericServlet for woody: + + ``` + @WebServlet("/adapter/{app-path}/payout") + public class PayoutServlet extends GenericServlet { + + @Autowired + private AdapterSrv.Iface payoutAdapterServiceLogDecorator; + + private Servlet servlet; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + servlet = new THServiceBuilder().build(AdapterSrv.Iface.class, payoutAdapterServiceLogDecorator); + } + + @Override + public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { + servlet.service(request, response); + } + + } + ``` \ No newline at end of file diff --git a/build_utils b/build_utils new file mode 160000 index 0000000..ea4aa04 --- /dev/null +++ b/build_utils @@ -0,0 +1 @@ +Subproject commit ea4aa042f482551d624fd49a570d28488f479e93 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e050c9e --- /dev/null +++ b/pom.xml @@ -0,0 +1,179 @@ + + + + 4.0.0 + com.rbkmoney + adapter-bank-payout-spring-boot-starter + 0.0.1-SNAPSHOT + jar + + Adapter bank payout spring boot starter + + + UTF-8 + 8 + 2.1.0.RELEASE + 2.1.9 + 1.256-2afe121 + 0.6.7 + 2.1.9 + + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + org.slf4j + slf4j-api + 1.7.25 + provided + + + org.projectlombok + lombok + 1.18.4 + provided + + + + + org.springframework.boot + spring-boot + ${spring-boot.version} + provided + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + true + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + provided + + + + + com.rbkmoney + error-mapping-java + 1.0.0 + provided + + + com.rbkmoney.woody + woody-thrift + 1.1.15 + provided + + + com.rbkmoney + damsel + ${damsel.version} + provided + + + com.rbkmoney + damsel-utils + ${damsel-utils.version} + + + com.rbkmoney.geck + serializer + ${serializer.version} + + + + com.rbkmoney.proxy-libs + hellgate-adapter-client + ${hellgate-adapter-client.version} + + + + + junit + junit + 4.12 + test + + + + http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/groups/public + + false + releases + RBKmoney releases repository + http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/repositories/releases + default + + + true + snapshots + RBKmoney snapshots repository + http://java-nexus.msk1.rbkmoney.net:8081/nexus/content/repositories/snapshots + default + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.0 + + + attach-sources + + jar + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.2 + + ${sonar.jacoco.reportPath} + true + + + + agent + + prepare-agent + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + ${java.version} + + + + + + + diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/client/RemoteClient.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/client/RemoteClient.java new file mode 100644 index 0000000..b10c541 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/client/RemoteClient.java @@ -0,0 +1,7 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.client; + +public interface RemoteClient { + + R payout(T request); + +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/ErrorMappingConfiguration.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/ErrorMappingConfiguration.java new file mode 100644 index 0000000..5368636 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/ErrorMappingConfiguration.java @@ -0,0 +1,32 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.config; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rbkmoney.error.mapping.ErrorMapping; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import java.io.IOException; + +@Configuration +public class ErrorMappingConfiguration { + + @Value("${error-mapping.file}") + private Resource filePath; + + @Value("${error-mapping.patternReason:\"'%s' - '%s'\"}") + private String patternReason; + + @Bean + ErrorMapping errorMapping() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); + + ErrorMapping errorMapping = new ErrorMapping(filePath.getInputStream(), patternReason, mapper); + errorMapping.validateMappingFormat(); + return errorMapping; + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/FilterConfiguration.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/FilterConfiguration.java new file mode 100644 index 0000000..5c44539 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/FilterConfiguration.java @@ -0,0 +1,87 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.config; + +import com.rbkmoney.woody.api.flow.WFlow; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Configuration +public class FilterConfiguration { + + public static final String HEALTH = "/actuator/health"; + + @Value("${server.rest.port}") + private int restPort; + + @Value("/${server.rest.endpoint}/") + private String restEndpoint; + + @Bean + public FilterRegistrationBean externalPortRestrictingFilter() { + Filter filter = new OncePerRequestFilter() { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String servletPath = request.getServletPath(); + if ((request.getLocalPort() == restPort) + && !(servletPath.startsWith(restEndpoint) || servletPath.startsWith(HEALTH))) { + response.sendError(404, "Unknown address"); + return; + } + filterChain.doFilter(request, response); + } + }; + + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(filter); + filterRegistrationBean.setOrder(-100); + filterRegistrationBean.setName("httpPortFilter"); + filterRegistrationBean.addUrlPatterns("/*"); + return filterRegistrationBean; + } + + @Bean + public FilterRegistrationBean woodyFilter() { + WFlow wFlow = new WFlow(); + Filter filter = new OncePerRequestFilter() { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + if ((request.getLocalPort() == restPort) + && request.getServletPath().startsWith(restEndpoint)) { + wFlow.createServiceFork(() -> { + try { + filterChain.doFilter(request, response); + } catch (IOException | ServletException e) { + sneakyThrow(e); + } + }).run(); + return; + } + filterChain.doFilter(request, response); + } + + private T sneakyThrow(Throwable t) throws E { + throw (E) t; + } + }; + + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(filter); + filterRegistrationBean.setOrder(-50); + filterRegistrationBean.setName("woodyFilter"); + filterRegistrationBean.addUrlPatterns(restEndpoint + "*"); + return filterRegistrationBean; + } +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/properties/AdapterProperties.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/properties/AdapterProperties.java new file mode 100644 index 0000000..0caf375 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/properties/AdapterProperties.java @@ -0,0 +1,21 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotEmpty; + +@Getter +@Setter +@Configuration +@ConfigurationProperties("adapter") +@Validated +public class AdapterProperties { + + @NotEmpty + private String url; + +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/properties/TimerProperties.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/properties/TimerProperties.java new file mode 100644 index 0000000..7fbdaed --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/config/properties/TimerProperties.java @@ -0,0 +1,27 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +@Configuration +@ConfigurationProperties("time.config") +@Validated +@Getter +@Setter +public class TimerProperties { + + @NotNull + private int redirectTimeout; + + @NotNull + private int maxTimePolling; + + @NotNull + private int pollingDelay; + +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/converter/WithdrawalConverter.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/converter/WithdrawalConverter.java new file mode 100644 index 0000000..88c2184 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/converter/WithdrawalConverter.java @@ -0,0 +1,12 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.converter; + +import com.rbkmoney.damsel.msgpack.Value; +import com.rbkmoney.damsel.withdrawals.provider_adapter.Withdrawal; + +import java.util.Map; + +public interface WithdrawalConverter { + + T convert(Withdrawal withdrawal, Value state, Map options); + +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/processor/ResultProcessor.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/processor/ResultProcessor.java new file mode 100644 index 0000000..f05505b --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/processor/ResultProcessor.java @@ -0,0 +1,7 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.processor; + +public interface ResultProcessor { + + R process(T t); + +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/service/PayoutAdapterService.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/service/PayoutAdapterService.java new file mode 100644 index 0000000..91136a6 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/service/PayoutAdapterService.java @@ -0,0 +1,32 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.service; + + +import com.rbkmoney.adapter.bank.payout.spring.boot.starter.client.RemoteClient; +import com.rbkmoney.adapter.bank.payout.spring.boot.starter.converter.WithdrawalConverter; +import com.rbkmoney.adapter.bank.payout.spring.boot.starter.processor.ResultProcessor; +import com.rbkmoney.damsel.msgpack.Value; +import com.rbkmoney.damsel.withdrawals.provider_adapter.AdapterSrv; +import com.rbkmoney.damsel.withdrawals.provider_adapter.ProcessResult; +import com.rbkmoney.damsel.withdrawals.provider_adapter.Withdrawal; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PayoutAdapterService implements AdapterSrv.Iface { + + private final WithdrawalConverter converter; + private final RemoteClient client; + private final ResultProcessor resultProcessorChain; + + @Override + public ProcessResult processWithdrawal(Withdrawal withdrawal, Value state, Map options) { + T request = converter.convert(withdrawal, state, options); + R response = client.payout(request); + return resultProcessorChain.process(response); + } +} \ No newline at end of file diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/service/PayoutAdapterServiceLogDecorator.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/service/PayoutAdapterServiceLogDecorator.java new file mode 100644 index 0000000..0684c84 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/service/PayoutAdapterServiceLogDecorator.java @@ -0,0 +1,41 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.service; + +import com.rbkmoney.damsel.msgpack.Value; +import com.rbkmoney.damsel.withdrawals.provider_adapter.AdapterSrv; +import com.rbkmoney.damsel.withdrawals.provider_adapter.ProcessResult; +import com.rbkmoney.damsel.withdrawals.provider_adapter.Withdrawal; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.rbkmoney.java.damsel.utils.verification.ProxyProviderVerification.isUndefinedResultOrUnavailable; + +@Slf4j +@RequiredArgsConstructor +public class PayoutAdapterServiceLogDecorator implements AdapterSrv.Iface { + + private final AdapterSrv.Iface payoutAdapterService; + + @Override + public ProcessResult processWithdrawal(Withdrawal withdrawal, Value state, Map options) throws TException { + String withdrawalId = withdrawal.getId(); + log.info("processWithdrawal: start with withdrawalId {}", withdrawalId); + try { + ProcessResult processResult = payoutAdapterService.processWithdrawal(withdrawal, state, options); + log.info("processWithdrawal: finish {} with withdrawalId {}", processResult, withdrawalId); + return processResult; + } catch (Exception ex) { + String message = "Exception in processPayment with withdrawalId " + withdrawalId; + if (isUndefinedResultOrUnavailable(ex)) { + log.warn(message, ex); + } else { + log.error(message, ex); + } + throw ex; + } + } +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/utils/CardDateConverter.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/utils/CardDateConverter.java new file mode 100644 index 0000000..b4fe756 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/utils/CardDateConverter.java @@ -0,0 +1,26 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.utils; + +import com.rbkmoney.damsel.cds.ExpDate; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CardDateConverter { + + public static String getYearFromExpDate(ExpDate expDate) { + return String.format("%1$02d", expDate.getYear() % 100); + } + + public static String getYearFromShort(short year) { + return String.format("%1$02d", year % 100); + } + + public static String getMonthFromExpDate(ExpDate expDate) { + return String.format("%02d", expDate.getMonth()); + } + + public static String getMonthFromByte(byte month) { + return String.format("%02d", month); + } + +} diff --git a/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/utils/SignGenerator.java b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/utils/SignGenerator.java new file mode 100644 index 0000000..d25fcb2 --- /dev/null +++ b/src/main/java/com/rbkmoney/adapter/bank/payout/spring/boot/starter/utils/SignGenerator.java @@ -0,0 +1,56 @@ +package com.rbkmoney.adapter.bank.payout.spring.boot.starter.utils; + +import org.apache.commons.codec.binary.Hex; +import org.springframework.util.MultiValueMap; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +public class SignGenerator { + + public static String prepareDataForHmac(String[] fields, MultiValueMap params) { + StringBuilder dataHmac = new StringBuilder(); + Arrays.asList(fields) + .forEach(field -> { + List fieldParameters = params.get(field); + if (fieldParameters != null + && !fieldParameters.isEmpty() + && fieldParameters.get(0) != null + && !fieldParameters.get(0).isEmpty() + ) { + dataHmac.append(fieldParameters.get(0).length()); + dataHmac.append(fieldParameters.get(0)); + } else { + dataHmac.append("-"); + } + } + ); + return dataHmac.toString(); + } + + public static String sign(String[] fieldsForSign, MultiValueMap params, String key, String algorithm) { + String dataHmac = prepareDataForHmac(fieldsForSign, params); + String pSign = sign(dataHmac, key, algorithm); + return pSign.toUpperCase(); + } + + public static String sign(String data, String key, String algorithm) { + try { + byte[] decodedKey = Hex.decodeHex(key.toCharArray()); + SecretKeySpec keySpec = new SecretKeySpec(decodedKey, algorithm); + Mac mac = Mac.getInstance(algorithm); + mac.init(keySpec); + + byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); + byte[] signatureBytes = mac.doFinal(dataBytes); + + return new String(new Hex().encode(signatureBytes)); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + } +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..5ca6528 --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1,7 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.ErrorMappingConfiguration,\ +com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties.TimerProperties,\ +com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.properties.AdapterProperties,\ +com.rbkmoney.adapter.bank.payout.spring.boot.starter.service.PayoutAdapterService,\ +com.rbkmoney.adapter.bank.payout.spring.boot.starter.config.FilterConfiguration + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..d291cc1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,9 @@ +--- +time.config: + redirectTimeout: 600 + maxTimePolling: 600 + pollingDelay: 10 +--- +error-mapping: + file: classpath:fixture/errors.json + patternReason: "'%s' - '%s'" # 'code' - 'description' \ No newline at end of file diff --git a/src/main/resources/fixture/errors.json b/src/main/resources/fixture/errors.json new file mode 100644 index 0000000..9cabeef --- /dev/null +++ b/src/main/resources/fixture/errors.json @@ -0,0 +1,8 @@ +[ + { + "code":"unknown", + "description":"unknown", + "regexp":".*", + "mapping":"authorization_failed:unknown" + } +] diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..e1f57ed --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,10 @@ + + + + + + + + + +