1
0
Fork 0
mirror of https://github.com/IRS-Public/direct-file.git synced 2025-06-28 04:25:52 +00:00

Remove submit and status apps

Simplify onboarding by removing the submit and status apps. We won't use those in this project.
This commit is contained in:
Skippy Williams 2025-06-19 18:32:38 -07:00
parent b8c75c72e7
commit 9a0abfdc8b
238 changed files with 0 additions and 15592 deletions

View file

@ -313,21 +313,8 @@ Most of the project dependencies can be installed using [SDKMAN!](https://sdkman
export LOCAL_WRAPPING_KEY="9mteZFY+gIVfMFywgvpLpyVl+8UIcNoIWpGaHX4jDFU=" export LOCAL_WRAPPING_KEY="9mteZFY+gIVfMFywgvpLpyVl+8UIcNoIWpGaHX4jDFU="
export MEF_SOFTWARE_ID="[mef-software-id]" export MEF_SOFTWARE_ID="[mef-software-id]"
export MEF_SOFTWARE_VERSION_NUM="2023.0.1" export MEF_SOFTWARE_VERSION_NUM="2023.0.1"
export STATUS_ASID="[status-asid]"
export STATUS_EFIN="[status-efin]"
export STATUS_ETIN="[status-etin]"
export SUBMIT_ASID=$STATUS_ASID
export SUBMIT_EFIN=$STATUS_EFIN
export SUBMIT_ETIN=$STATUS_ETIN
export DF_TIN_VALIDATION_ENABLED=false export DF_TIN_VALIDATION_ENABLED=false
export DF_EMAIL_VALIDATION_ENABLED=false export DF_EMAIL_VALIDATION_ENABLED=false
export STATUS_KEYSTOREALIAS="[keystore-alias]"
export STATUS_KEYSTOREBASE64="[base64-encoded-keystore]"
export STATUS_KEYSTOREPASSWORD="[keystore-password]"
export SUBMIT_KEYSTORE_KEYSTOREALIAS=$STATUS_KEYSTOREALIAS
export SUBMIT_KEYSTORE_KEYSTOREBASE64=$STATUS_KEYSTOREBASE64
export SUBMIT_KEYSTORE_KEYSTOREPASSWORD=$STATUS_KEYSTOREPASSWORD
export SUBMIT_ID_VAR_CHARS="zz"
export GIT_COMMIT_HASH="$(cd /path/to/direct-file && git rev-parse --short main)" export GIT_COMMIT_HASH="$(cd /path/to/direct-file && git rev-parse --short main)"
``` ```

View file

@ -1,4 +0,0 @@
**/application-local.*
.env*
.git/
Dockerfile*

View file

@ -1,43 +0,0 @@
HELP.md
target/
!**/src/main/**/target/
!**/src/test/**/target/
*.jar
/src/main/resources/spotbugs/output/spotbugs.xml
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Special ###
/src/main/resources/application-local.*
src/test/resources/private.p12
src/test/resources/private.p12
### Gradle ###
/.gradle/
/build/

View file

@ -1,5 +0,0 @@
changeLogFile=db/changelog.yaml
url=jdbc:postgresql://localhost:32768/directfile-status
username=postgres
password=postgres
changesetAuthor=directfile

View file

@ -1,19 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip

View file

@ -1,56 +0,0 @@
#syntax=docker/dockerfile:1.7-labs
# build factgraph since it will be a dependency in share-libs-builder
FROM sbtscala/scala-sbt:eclipse-temurin-alpine-21.0.2_13_1.9.9_3.3.3 AS factgraph-builder
WORKDIR /build/
COPY --from=factgraph-repo js/src/ js/src/
COPY --from=factgraph-repo jvm/src/ jvm/src/
COPY --from=factgraph-repo project/build.properties project/plugins.sbt project/
COPY --from=factgraph-repo shared/ shared/
COPY --from=factgraph-repo build.sbt .
RUN sbt compile package publishM2
# build shared dependencies
FROM eclipse-temurin:21-jdk-alpine AS shared-dependencies-builder
COPY --from=factgraph-builder /root/.m2/repository/gov/irs/factgraph/fact-graph_3/ /root/.m2/repository/gov/irs/factgraph/fact-graph_3/
ARG MAVEN_OPTS=""
WORKDIR /build/
COPY --from=config . ./config/
COPY --from=boms . ./boms/
WORKDIR /build/libs/
COPY --from=shared-libs .mvn/wrapper/maven-wrapper.properties .mvn/wrapper/
COPY --from=shared-libs mvnw ./
COPY --from=shared-libs --parents **/pom.xml ./
RUN ./mvnw dependency:resolve -P resolve
COPY --from=shared-libs starters/ ./starters/
COPY --from=shared-libs data-models/ ./data-models/
RUN ./mvnw install
# build mef-status
FROM shared-dependencies-builder AS mef-status-builder
ARG MAVEN_OPTS=""
ENV MEF_REPO /mef-client-sdk
ENV A2A_TOOLKIT_HOME /${MEF_REPO}/MeF_Client_SDK/Java/source/
ENV LOCALSTACK_INTEGRATION_TESTS_ENABLED false
COPY --from=mef-sdk-repo MeF_Client_SDK/ /${MEF_REPO}/MeF_Client_SDK/
COPY --from=config . /config/
WORKDIR /build/
COPY --from=scripts install-mef-sdk.sh ./
COPY mvnw pom.xml ./
COPY .mvn/wrapper/maven-wrapper.properties .mvn/wrapper/
RUN ./install-mef-sdk.sh
RUN ./mvnw dependency:resolve
COPY src/ src/
RUN ./mvnw package
FROM eclipse-temurin:21-jre-alpine
ENV LOCAL_WRAPPING_KEY "${LOCAL_WRAPPING_KEY:-oE3Pm+fr1I+YbX2ZxEe/n9INqJjy00KSl7oXXW4p5Xw=}"
COPY --from=mef-status-builder /build/target/mef-status-0.0.1-SNAPSHOT.jar /app.jar
COPY --from=mef-sdk-repo MeF_Client_SDK/Java/source/ /mef-client-sdk-src/
# Run from dir that allows mef-sdk to write it's files to the working directory (next iteration: configure mef-sdk)
RUN adduser --system --no-create-home jar-runner && \
mkdir -p /jar-run && \
chown jar-runner /jar-run
WORKDIR /jar-run
USER jar-runner
CMD ["java", "-jar", "/app.jar"]

View file

@ -1,86 +0,0 @@
# MEF Status
## About MeF Status
The MeF Status application's job is to:
- Regularly poll MeF to learn about the status of each submitted tax return, until we recieve a final status. A final status is one that will not change: Accepted, Rejected, or an error that will not get resolved without intervention.
- Save the final status for each tax return where it can be accessed by the backend application
### How it works
- At the time a tax return is submitted, the mef-submit app creates a submission ID and notifies the status application via message queue that there is a new tax return to begin polling for.
- MeF status periodically queries the status database to find a list of pending tax returns.
- MeF status (this application) then queries MeF (the external API) for batches of those returns. When it receives a final response from MeF (generally "accepted" or "rejected") for any one of those returns, it saves the results to the status database. Returns that remain in pending status will be polled for at the next interval.
## Setup
> [!NOTE]
> For setup please see the most up-to-date instructions which are found in the [Onboarding docs](../../ONBOARDING.md).
### MeF SDK
2. Once you have that variable set, run the
build-project.sh script from the
`submit` folder of the direct_file project.
### Proxy
If you use a proxy, first see `MAVEN_OPTS` in [the project readme](../README.md#important-configuration-variables) and [the OMB Connect readme](../README-omb-connect.md) for information about ensuring your proxy settings are passed to all build steps.
## Docker
Ensure you have the environment variables set (see [Environment](#environment) below).
You may be able to run with only `STATUS_ETIN` set, using the test value from the [submit README](../backend/README.md).
Build the application:
```bash
docker compose build
```
Then run it:
```bash
docker compose up -d mef-status
```
The app probably failed for you due to lack of configuration. You can look at the reason why it failed to start with:
```bash
docker compose logs mef-status
```
## Running locally
### Environment
Set the following environment variables in your local environment which will facilitate running both the applications as well as the docker containers. On macbooks, placing the export statements below in the `.zshrc` (gets run and evaluated everytime a shell instance is started) or `.zprofile` (gets run and evaluated when a user logs in) file will accomplish this. If using the bash shell, placing them in `.bashrc` should do (and effectively behave similar to `.zshrc`).
```
# Get the keystore alias from a fellow developer and replace the value in between quotes with the actual value
export STATUS_KEYSTOREALIAS="[keystore-alias]"
# Get the base64 encoded keystore from a fellow developer and replace the value in between quotes with the actual value
export STATUS_KEYSTOREBASE64="[base64-encoded-keystore]"
# Get the keystore password from a fellow developer and replace the value in between quotes with the actual value
export STATUS_KEYSTOREPASSWORD="[keystore-password]"
# Get the ASID value for the status application from a fellow developer and replace the value in between quotes with the actual value
export STATUS_ASID="[status-asid]"
# Get the EFIN value for the status application from a fellow developer and replace the value in between quotes with the actual value
export STATUS_EFIN="[status-efin]"
# Get the ETIN value for the status application from a fellow developer and replace the value in between quotes with the actual value
export STATUS_ETIN="[status-etin]"
```
You'll also need to set up the `LOCAL_WRAPPING_KEY` following the instructions in the [backend README](../backend/README.md#initial-setup)
```
export LOCAL_WRAPPING_KEY="[local-wrapping-key]"
```
### Static Analysis: Spot Bugs and PMD
For notes and usage on spotbugs see the [Backend API README Spot Bugs section](../submit/README.md#static-analysis)

View file

@ -1,2 +0,0 @@
cd target
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001 -jar mef-status-0.0.1-SNAPSHOT.jar

View file

@ -1,259 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

View file

@ -1,149 +0,0 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

View file

@ -1,174 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>gov.irs.directfile.boot</groupId>
<artifactId>irs-spring-boot-starter-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../boms/irs-spring-boot-starter-parent</relativePath>
</parent>
<groupId>gov.irs.directfile.status</groupId>
<artifactId>mef-status</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MeF status</name>
<description>Status checking system for MeF</description>
<properties>
<!-- overrides for properties defined in irs-spring-boot-starter-parent -->
<config-folder.path>${project.basedir}/../config</config-folder.path>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>gov.irs.directfile</groupId>
<artifactId>data-models</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>amazon-sqs-java-messaging-lib</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>software.amazon.encryption.s3</groupId>
<artifactId>amazon-s3-encryption-client-java</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<!-- Configures mvn site:run to run on port 9898 -->
<configuration>
<port>9898</port>
<tempWebappDirectory>${basedir}/target/site/tempdir</tempWebappDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-XX:+EnableDynamicAgentLoading ${argLine}</argLine>
<excludedEnvironmentVariables>
<!-- exclude active profiles so tests work from cli even if this var is set -->
<excludedEnvironmentVariable>SPRING_PROFILES_ACTIVE</excludedEnvironmentVariable>
</excludedEnvironmentVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,37 +0,0 @@
package gov.irs.directfile.status;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import gov.irs.mef.services.msi.LoginClient;
import gov.irs.mef.services.msi.LogoutClient;
import gov.irs.mef.services.transmitter.mtom.GetAcksMTOMClient;
@SpringBootApplication
@ConfigurationPropertiesScan
public class StatusApplication {
@Bean
@Scope("prototype")
public LoginClient loginClient() {
return new LoginClient();
}
@Bean
@Scope("prototype")
public GetAcksMTOMClient ackClient() {
return new GetAcksMTOMClient();
}
@Bean
@Scope("prototype")
public LogoutClient logoutClient() {
return new LogoutClient();
}
public static void main(String[] args) {
SpringApplication.run(StatusApplication.class, args);
}
}

View file

@ -1,212 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.List;
import java.util.UUID;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import gov.irs.directfile.audit.AuditEventData;
import gov.irs.directfile.audit.AuditLogElement;
import gov.irs.directfile.audit.AuditService;
import gov.irs.directfile.audit.events.Event;
import gov.irs.directfile.audit.events.EventId;
import gov.irs.directfile.audit.events.EventStatus;
import gov.irs.directfile.audit.events.SystemEventPrincipal;
import gov.irs.directfile.models.RejectedStatus;
import gov.irs.directfile.status.acknowledgement.domain.AcknowledgementStatus;
@SuppressFBWarnings(
value = {"CRLF_INJECTION_LOGS"},
justification = "Initial SpotBugs Setup")
@RestController
@Validated
@Slf4j
@RequestMapping("/status")
public class AcknowledgementController {
private static String X_FORWARDED_FOR = "X-Forwarded-For";
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP2"},
justification = "constructor injection")
public AcknowledgementController(AcknowledgementService acknowledgementService) {
this.acknowledgementService = acknowledgementService;
}
private final AcknowledgementService acknowledgementService;
private static final AuditService auditService = new AuditService();
@GetMapping()
public ResponseEntity<AcknowledgementStatus> get(
@RequestParam(name = "id") UUID taxReturnId, HttpServletRequest request) {
AuditEventData eventData = new AuditEventData();
String submissionId = null;
try {
MDC.put(AuditLogElement.taxReturnId.toString(), taxReturnId.toString());
log.info(String.format("Request for taxReturnId %s", taxReturnId));
MDC.clear();
submissionId =
acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(taxReturnId);
AcknowledgementStatus acknowledgementStatus =
acknowledgementService.GetAcknowledgement(taxReturnId, submissionId);
submissionId = setSubmissionIdToUnknownStringIfItIsNullSoThatLogMessagesWillBeMoreClear(submissionId);
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
log.info(String.format("Retrieved submissionId %s", submissionId));
MDC.clear();
if (acknowledgementStatus == null) {
addValuesToEventData(eventData, AuditLogElement.mefSubmissionId, submissionId);
addValuesToEventData(eventData, AuditLogElement.taxReturnId, taxReturnId.toString());
addValuesToEventData(
eventData, AuditLogElement.responseStatusCode, String.valueOf(HttpStatus.NOT_FOUND.value()));
auditService.performLogFromEvent(
Event.builder()
.eventId(EventId.CHECK)
.eventStatus(EventStatus.FAILURE)
.eventPrincipal(new SystemEventPrincipal())
.build(),
eventData);
return new ResponseEntity<AcknowledgementStatus>(HttpStatus.NOT_FOUND);
}
// this will get logged everytime /status is hit, regardless of whether submissionId is found
addValuesToEventData(eventData, AuditLogElement.mefSubmissionId, submissionId);
addValuesToEventData(eventData, AuditLogElement.taxReturnId, taxReturnId.toString());
addValuesToEventData(eventData, AuditLogElement.responseStatusCode, String.valueOf(HttpStatus.OK.value()));
auditService.performLogFromEvent(
Event.builder()
.eventId(EventId.CHECK)
.eventStatus(EventStatus.SUCCESS)
.eventPrincipal(new SystemEventPrincipal())
.build(),
eventData);
return new ResponseEntity<>(acknowledgementStatus, HttpStatus.OK);
} catch (Exception ex) {
eventData.put(AuditLogElement.eventErrorMessage, ex.getClass().getName());
eventData.putDetail("errorMessage", ex.getMessage());
addValuesToEventData(eventData, AuditLogElement.mefSubmissionId, submissionId);
addValuesToEventData(eventData, AuditLogElement.taxReturnId, taxReturnId.toString());
addValuesToEventData(
eventData, AuditLogElement.responseStatusCode, String.valueOf(HttpStatus.BAD_REQUEST.value()));
auditService.performLogFromEvent(
Event.builder()
.eventId(EventId.CHECK)
.eventStatus(EventStatus.FAILURE)
.eventPrincipal(new SystemEventPrincipal())
.build(),
eventData);
MDC.put(AuditLogElement.taxReturnId.toString(), taxReturnId.toString());
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
log.error(String.format("Error handling submission id %s", taxReturnId), ex);
MDC.clear();
return new ResponseEntity<AcknowledgementStatus>(HttpStatus.BAD_REQUEST);
} finally {
MDC.clear();
}
}
@GetMapping("/rejection-codes")
public ResponseEntity<List<RejectedStatus>> getRejectionCodes(
@RequestParam(name = "submissionId") String submissionId, HttpServletRequest request) {
AuditEventData eventData = new AuditEventData();
try {
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
MDC.clear();
List<RejectedStatus> rejectionCodes;
try {
rejectionCodes = acknowledgementService.getRejectionCodesForSubmissionId(submissionId);
} catch (EntityNotFoundException e) {
log.error(String.format("Could not find record for submission ID %s", submissionId), e);
addValuesToEventData(eventData, AuditLogElement.mefSubmissionId, submissionId);
addValuesToEventData(
eventData, AuditLogElement.responseStatusCode, String.valueOf(HttpStatus.NOT_FOUND.value()));
auditService.performLogFromEvent(
Event.builder()
.eventId(EventId.CHECK)
.eventStatus(EventStatus.FAILURE)
.eventPrincipal(new SystemEventPrincipal())
.build(),
eventData);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
MDC.clear();
// this will get logged everytime /status is hit, regardless of whether submissionId is found
addValuesToEventData(eventData, AuditLogElement.mefSubmissionId, submissionId);
addValuesToEventData(eventData, AuditLogElement.responseStatusCode, String.valueOf(HttpStatus.OK.value()));
auditService.performLogFromEvent(
Event.builder()
.eventId(EventId.CHECK)
.eventStatus(EventStatus.SUCCESS)
.eventPrincipal(new SystemEventPrincipal())
.build(),
eventData);
return new ResponseEntity<>(rejectionCodes, HttpStatus.OK);
} catch (Exception ex) {
eventData.put(AuditLogElement.eventErrorMessage, ex.getClass().getName());
eventData.putDetail("errorMessage", ex.getMessage());
addValuesToEventData(eventData, AuditLogElement.mefSubmissionId, submissionId);
addValuesToEventData(
eventData, AuditLogElement.responseStatusCode, String.valueOf(HttpStatus.BAD_REQUEST.value()));
auditService.performLogFromEvent(
Event.builder()
.eventId(EventId.CHECK)
.eventStatus(EventStatus.FAILURE)
.eventPrincipal(new SystemEventPrincipal())
.build(),
eventData);
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
log.error(String.format("Error handling submission id %s", submissionId), ex);
MDC.clear();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} finally {
MDC.clear();
}
}
private void addValuesToEventData(AuditEventData auditEventData, AuditLogElement key, String value) {
if (value != null) {
auditEventData.put(key, value);
}
}
// This is a bit of workaround to make our log messages more clear.
// If submissionId is null, that means the status app could not find a submission associated with the given
// taxreturn in the TaxReturnSubmission table.
// If no submission was found in the TaxReturnSubmission, that likely means that the status app is still waiting for
// a message from the pending submission queue.
// There is currently some logic based on a config property in getAcknowledgementStatus. This logic causes
// GetAcknowledgement to return PENDING if submissionId is null,
// and the statusEndpointReturnsPendingByDefaultEnabled property is true (which it currently is in PROD).
// Without this workaround, submissionId will not appear in our logs, which was causing confusion.
// I am setting submissionId to NOT_YET_KNOWN so that we can have a more clear understanding of what is happening
// when we see our logs.
private String setSubmissionIdToUnknownStringIfItIsNullSoThatLogMessagesWillBeMoreClear(String submissionId) {
if (submissionId == null) {
return "NOT_YET_KNOWN";
} else {
return submissionId;
}
}
}

View file

@ -1,593 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
import com.google.common.math.IntMath;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.EntityNotFoundException;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import gov.irs.mef.exception.ServiceException;
import gov.irs.mef.exception.ToolkitException;
import gov.irs.mef.services.ServiceContext;
import gov.irs.directfile.audit.AuditLogElement;
import gov.irs.directfile.models.RejectedStatus;
import gov.irs.directfile.status.acknowledgement.domain.AcknowledgementStatus;
import gov.irs.directfile.status.acknowledgement.domain.Status;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.*;
import gov.irs.directfile.status.domain.Error;
import gov.irs.directfile.status.error.ErrorRepository;
import gov.irs.directfile.status.error.ToolkitErrorRepository;
import gov.irs.directfile.status.mef.client.MeFAcksMTOMClientService;
import gov.irs.directfile.status.mef.client.MeFLoginClientService;
import gov.irs.directfile.status.mef.client.MeFLogoutClientService;
import gov.irs.directfile.status.repository.PodIdentifierRepository;
import gov.irs.directfile.status.services.StatusChangeMessageService;
@SuppressFBWarnings(
value = {"CRLF_INJECTION_LOGS", "NM_METHOD_NAMING_CONVENTION"},
justification = "Initial SpotBugs Setup")
@Service
@EnableScheduling
@Transactional
@Slf4j
@SuppressWarnings({
"PMD.SimpleDateFormatNeedsLocale",
"PMD.ExcessiveParameterList",
"PMD.AvoidDuplicateLiterals",
"PMD.LiteralsFirstInComparisons",
"PMD.UselessParentheses"
})
public class AcknowledgementService {
private final CompletedAcknowledgementRepository completedRepo;
private final PendingAcknowledgementRepository pendingRepo;
private final TaxReturnSubmissionRepository taxReturnSubmissionRepository;
private final ErrorRepository errorRepo;
private final PodIdentifierRepository podIdentifierRepository;
private final ToolkitErrorRepository toolkitErrorRepo;
private final StatusProperties statusProperties;
private ServiceContext serviceContext;
private final StatusChangeMessageService statusChangeMessageService;
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm:ss");
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP2"},
justification = "constructor injection")
public AcknowledgementService(
CompletedAcknowledgementRepository completedRepo,
PendingAcknowledgementRepository pendingRepo,
TaxReturnSubmissionRepository taxReturnSubmissionRepository,
ErrorRepository errorRepo,
ToolkitErrorRepository toolkitErrorRepo,
StatusProperties statusProperties,
StatusChangeMessageService statusChangeMessageService,
MeFAcksMTOMClientService getAcksClientService,
MeFLoginClientService loginClientService,
MeFLogoutClientService logoutClientService,
PodIdentifierRepository podIdentifierRepository) {
this.completedRepo = completedRepo;
this.pendingRepo = pendingRepo;
this.taxReturnSubmissionRepository = taxReturnSubmissionRepository;
this.errorRepo = errorRepo;
this.toolkitErrorRepo = toolkitErrorRepo;
this.statusProperties = statusProperties;
this.statusChangeMessageService = statusChangeMessageService;
this.getAcksClientService = getAcksClientService;
this.loginClientService = loginClientService;
this.logoutClientService = logoutClientService;
this.podIdentifierRepository = podIdentifierRepository;
}
// changed from protected to public, called by TaxReturnXmlServiceImpl (which would be separated into its own
// microservice later)
public String getLatestSubmissionIdByTaxReturnId(UUID taxReturnId) {
log.info("getLatestSubmissionIdByTaxReturnId for tax-return-id {}", taxReturnId);
Optional<String> submissionId = taxReturnSubmissionRepository.getLatestSubmissionIdByTaxReturnId(taxReturnId);
return submissionId.orElse(null);
}
public String getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(UUID taxReturnId) {
log.info("Getting most relevant submission Id for taxReturnId {}", taxReturnId);
Optional<String> latestAcceptedSubmissionId =
taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId);
if (latestAcceptedSubmissionId.isPresent()) {
String submissionId = latestAcceptedSubmissionId.get();
log.info("Using {} as the most relevant submissionId for taxReturnId {}", submissionId, taxReturnId);
return submissionId;
}
log.info(
"Falling back to retrieving the latest submissionId, regardless of status, for tax return {}",
taxReturnId);
return getLatestSubmissionIdByTaxReturnId(taxReturnId);
}
/**
* Useful for navigating race conditions where a non-accepted submission that is more recent than an accepted
* submission exists, where the accepted submission should be the
*
* @param requestedSubmissionId a submission that (ideally) belongs to a tax return submission
* @return the latest accepted submissionId, if present
*/
public Optional<String> getLatestAcceptedSubmissionIdOfParentTaxReturn(String requestedSubmissionId) {
// Note: Cannot log method name alongside submissionId as it implies status
log.info("checking for more relevant submissionId of tax return with submissionId {}", requestedSubmissionId);
return taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId);
}
public Optional<Completed> getCompletedBySubmissionId(String submissionId) {
return completedRepo.GetCompletedSubmission(submissionId);
}
public List<RejectedStatus> getRejectionCodesForSubmissionId(String submissionId) {
Optional<Completed> optCompleted = getCompletedBySubmissionId(submissionId);
// If submission ID does not have a completed record, throw an exception.
if (optCompleted.isEmpty()) {
throw new EntityNotFoundException("Could not find completed record for submission ID: " + submissionId);
}
// We have a completed submission. Convert to a List<RejectedStatus> and return the list (if it's
// not a rejected submission, the returned list should be empty).
Completed completed = optCompleted.get();
return createRejectedReasonList(completed);
}
public AcknowledgementStatus GetAcknowledgement(UUID taxReturnId) {
String submissionId = getLatestSubmissionIdByTaxReturnId(taxReturnId);
return getAcknowledgementStatus(taxReturnId, submissionId);
}
public AcknowledgementStatus GetAcknowledgement(UUID taxReturnId, String submissionId) {
return getAcknowledgementStatus(taxReturnId, submissionId);
}
private AcknowledgementStatus getAcknowledgementStatus(UUID taxReturnId, String submissionId) {
if (submissionId == null && statusProperties.statusEndpointReturnsPendingByDefaultEnabled) {
// if statusProperties.createPendingUponGetStatus is true, return Pending status as we previously did
// The difference is, we are no longer saving a Pending object in the DB.
log.atInfo()
.setMessage("Unable to find a submission associated with taxReturnId in TaxReturnSubmission table")
.addKeyValue(AuditLogElement.taxReturnId.toString(), taxReturnId)
.log();
return new AcknowledgementStatus(
Status.Pending, CreateTranslationKey("status", "pending"), List.of(), new Date());
}
if (submissionId == null && !statusProperties.statusEndpointReturnsPendingByDefaultEnabled) {
// tax return was likely not submitted to MeF
return null;
}
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
MDC.put(AuditLogElement.taxReturnId.toString(), taxReturnId.toString());
log.info(String.format("Attempting to find submission id %s in completed database", submissionId));
MDC.clear();
Optional<Completed> completed = completedRepo.GetCompletedSubmission(submissionId);
// this is a completed return
if (completed.isPresent()) {
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
MDC.put(AuditLogElement.taxReturnId.toString(), taxReturnId.toString());
log.info("Completed record found");
MDC.clear();
return getCompletedStatus(completed.get());
}
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
MDC.put(AuditLogElement.taxReturnId.toString(), taxReturnId.toString());
log.info(String.format("Did not find %s in completed, checking pending", submissionId));
MDC.clear();
Optional<Pending> pending = pendingRepo.GetPendingSubmission(submissionId);
if (pending.isPresent()) {
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
MDC.put(AuditLogElement.taxReturnId.toString(), taxReturnId.toString());
log.info(String.format("Found %s in pending, will recheck status on next pass", submissionId));
MDC.clear();
return new AcknowledgementStatus(
Status.Pending,
CreateTranslationKey("status", "pending"),
List.of(),
pending.get().getCreatedAt());
}
return null;
}
@Scheduled(fixedRateString = "${status.ack-poll-in-milliseconds}", initialDelay = 1000)
public void ProcessPendingTable() {
if (!statusProperties.isStatusPollingEnabled()) {
log.info("Status polling is disabled in this environment");
return;
}
log.info("Timer called: performing ack check");
LookupSubmissions();
}
protected Iterable<Pending> getAllPending() {
return pendingRepo.findAll();
}
protected Iterable<Error> getAllError() {
return errorRepo.findAll();
}
protected Iterable<Completed> getAllCompleted() {
return completedRepo.findAll();
}
private List<Set<Pending>> batchPendings(Iterable<Pending> pendings) {
List<Set<Pending>> batches = new ArrayList<>();
Set<Pending> current = new HashSet<>();
log.info(String.format("Estimated count: %s", pendings.spliterator().estimateSize()));
// This surprised me, but if you access it from pending every time
// the iterator will never move to next.
Iterator<Pending> iterator = pendings.iterator();
while (iterator.hasNext()) {
Pending pending = iterator.next();
if (current.size() < 100) {
current.add(pending);
} else {
log.info("100 Pendings in current batch, creating new batch");
batches.add(current);
current = new HashSet<>();
current.add(pending);
}
}
batches.add(current);
log.info(String.format("%s batches created", batches.size()));
return batches;
}
protected void LookupSubmissions() {
String podId = statusProperties.getApplicationId();
log.info("Getting all pending submission ids for podId {}", podId);
Iterable<Pending> pendings = pendingRepo.findAllByPodId(podId);
// we will either get the exact size or a 0 here.
if (pendings.spliterator().getExactSizeIfKnown() == 0) {
log.info("No pending submission ids for podId {}", podId);
return;
}
// The MeF system has a limit of 100 submission ids checked per attempt
List<Set<Pending>> batchedPendings = batchPendings(pendings);
log.info("Creating ack client for podId {}", podId);
long startTime = System.currentTimeMillis();
Date date = new Date(startTime);
log.info("Logging in at {} for podId {}", simpleDateFormat.format(date), podId);
batchedPendings.forEach(batch -> {
try {
log.info("Getting acks from MeF for batch for podId {}", podId);
getGetAcksResult(batch);
// It might one day be necessary to purge submissionIds
// It shouldn't be necessary with this service being only available
// to our internal system.
} catch (ToolkitException e) {
// TODO: if a batch fails, find the bad one and report the problem to some other system
log.error("Toolkit error getting ack on poll: {}", e.getMessage(), e);
if (batch.size() == 1) {
createToolkitError(batch.iterator().next(), e);
} else {
partitionBatch(batch);
}
} catch (ServiceException e) {
log.error("Service error getting ack on poll: {}", e.getMessage(), e);
}
});
long endTime = System.currentTimeMillis();
date = new Date(endTime);
log.info(
"Logging out at {}, elapsed time in milliseconds: {}",
simpleDateFormat.format(date),
(endTime - startTime));
}
private void getGetAcksResult(Set<Pending> pendings) throws ToolkitException, ServiceException {
Set<String> submissionIds =
pendings.stream().map(Pending::getSubmissionId).collect(Collectors.toSet());
GetAcksResultWrapper acknowledgements = getAcksClientService.getAcks(serviceContext, submissionIds);
// to enable parallel processing of each batch of 100 submissions, we handle each batch of acknowledgements in a
// separate thread
// once we have fetched them from MeF
new Thread(() -> bulkUpdateRecordsFromAckResultAndEnqueueStatusChangeMessages(acknowledgements, pendings))
.start();
}
void bulkUpdateRecordsFromAckResultAndEnqueueStatusChangeMessages(
GetAcksResultWrapper acksResult, Iterable<Pending> pendings) {
/*
* We start with a list of ackResults and the original Pending records in the database
* All batching logic is handle as a map of a mefSubmissionId key to an arbitrary value
* This allows us to make O(1) look ups between the various batching functions when creating new maps
* There are several steps for map and transform steps before we are ready to save/write database records
* and enqueue the SQS message back to the Backend service
*
* 1. createStatusSubmissionIdMap()
* - initializes a Map of ackStatus (accepted, rejected, pending) to an empty list
* - returns: {"accepted":[],"rejected":[],"pending":[]}
* 2. Loop through the acks and:
* a) populate the submissionIdToValidationErrorMap map submission Id to a list of lists of strings representing each part of the validationErrorGroup
* - returns {"sub_id_1" :[["R0000-904-03","Reject and Stop","Software ID in the Return Header must have passed testing for the form family and TaxYr."],
* ["F1040-525-03","Reject and Stop","If 'PINTypeCd' in the Return Header has ..."]],"sub_id_2":[[...],[...]]}
* b) populate the statusSubmissionIdMap
* - returns: {"accepted":["sub_id_3"],"rejected":["sub_id_2",],"pending":["sub_id_1"]}
*
* 3. createNewCompleteds()
* - Prepare the new Completed entities *but do not save them yet*
*
* 4. Map the Completed and Pending entities to respective maps of submissionId:Completed and submissionId:Pending
*
* 5. bulkUpdateEntities()
* - bulkGetOrCreateErrorsToRejectedAcknowledgements()
* - Create the Error entities based on the submissionIdToValidationErrorMap and save them to the database
* - at this point, the Error entities are *not* related to the Completed entities, because
* - Completed entities haven't been saved
* - returns submissionIdToError(), which is a map of submission Ids to Errors (newly created)
* - addErrorsToRejectedAcknowledgementsAndDeletePendingRecords()
* - pass the three maps (submissionIdToError,submissionIdToCompleted,submissionIdToPending) as args
* - iterate through the submissionIdToCompleted and:
* - if Errors exist, relate them to the completed
* - if the Pending exists, add it to the batch of Pendings to delete
* - in all cases, add the Completed to a batch of Completeds to persist
* - Save all Completeds in the batch to create
* - Delete all Pendings in the batch to delete
* 6. Remove pending submissions from the map of statusSubmissionIds to produce finalStatusSubmissionIds
* 7. Enqueue the finalStatusSubmissionIds to SQS and call statusChangeMessageService.publishStatusChangePayloadV1(finalStatusSubmissionIds)
* */
Map<String, List<String>> statusSubmissionIdMap = createStatusSubmissionIdMap();
Map<String, List<List<String>>> submissionIdToValidationErrorMap = new HashMap<>();
Map<String, Completed> submissionIdToCompletedMap = new HashMap<>();
Map<String, Pending> submissionIdToPendingdMap = new HashMap<>();
acksResult.getAcknowledgementsListWrapper().getAcknowledgements().forEach(acknowledgement -> {
String status = acknowledgement.getAcceptanceStatusTxt().toLowerCase();
String submissionId = acknowledgement.getSubmissionId();
submissionIdToValidationErrorMap.put(submissionId, new ArrayList<>());
if (status.equals("rejected")) {
acknowledgement.getValidationErrorList().forEach(validationErrorGrp -> {
List<String> errorMap = new ArrayList<>();
errorMap.add(validationErrorGrp.getRuleNum());
errorMap.add(validationErrorGrp.getSeverityCd());
errorMap.add(validationErrorGrp.getErrorMessageTxt());
submissionIdToValidationErrorMap.get(submissionId).add(errorMap);
});
}
addToSubmissionIdMap(status, submissionId, statusSubmissionIdMap);
});
Iterable<Completed> completeds = createNewCompleteds(statusSubmissionIdMap);
completeds.forEach(completed -> submissionIdToCompletedMap.put(completed.getSubmissionId(), completed));
pendings.forEach(pending -> submissionIdToPendingdMap.put(pending.getSubmissionId(), pending));
bulkUpdateEntities(submissionIdToValidationErrorMap, submissionIdToCompletedMap, submissionIdToPendingdMap);
Map<String, List<String>> finalStatusSubmissionIdMap =
stripPendingAcknowledgementsFromStatusSubmissionIdMap(statusSubmissionIdMap);
if (!finalStatusSubmissionIdMap.get("rejected").isEmpty()
|| !finalStatusSubmissionIdMap.get("accepted").isEmpty()) {
statusChangeMessageService.publishStatusChangePayloadV1(finalStatusSubmissionIdMap);
}
}
protected Map<String, List<String>> stripPendingAcknowledgementsFromStatusSubmissionIdMap(
Map<String, List<String>> statusSubmssionIdMap) {
statusSubmssionIdMap.remove("pending");
return statusSubmssionIdMap;
}
protected Map<String, List<String>> createStatusSubmissionIdMap() {
Map<String, List<String>> statusSubmissionIdMap = new HashMap<>();
statusSubmissionIdMap.put("accepted", new ArrayList<>());
statusSubmissionIdMap.put("rejected", new ArrayList<>());
statusSubmissionIdMap.put("pending", new ArrayList<>());
return statusSubmissionIdMap;
}
protected Iterable<Completed> createNewCompleteds(Map<String, List<String>> statusSubmissionIdMap) {
List<Completed> completedsToCreate = new ArrayList<>();
statusSubmissionIdMap.forEach((status, submissionIdSet) -> {
if (status.equals("pending")) {
log.info("The following subIds are still pending {}", submissionIdSet.toString());
} else {
submissionIdSet.forEach(submissionId -> {
Completed completed = new Completed();
completed.setSubmissionId(submissionId);
completed.setStatus(status);
completedsToCreate.add(completed);
log.info("{} acknowledgement received from MeF", status);
});
}
});
return completedsToCreate;
}
protected void addToSubmissionIdMap(
String status, String submissionId, Map<String, List<String>> statusSubmissionIdMap) {
MDC.put(AuditLogElement.mefSubmissionId.toString(), submissionId);
if (statusSubmissionIdMap.containsKey(status)) {
log.info(
"Acknowledgement with submissionId {} has status changed, adding to statusSubmissionIdMap",
submissionId);
statusSubmissionIdMap.get(status).add(submissionId);
} else {
// default case where the status is not present or not one we expect
// TODO: handle the exception status case when/if necessary
log.error("Missing a status type: {}", status);
}
MDC.clear();
}
public void bulkUpdateEntities(
Map<String, List<List<String>>> validationErrorMap,
Map<String, Completed> submissionIdToCompleted,
Map<String, Pending> submissionIdToPending) {
Map<String, List<Error>> submissionIdToError =
bulkGetOrCreateErrorsToRejectedAcknowledgements(validationErrorMap);
addErrorsToRejectedAcknowledgementsAndDeletePendingRecords(
submissionIdToError, submissionIdToCompleted, submissionIdToPending);
}
protected Map<String, List<Error>> bulkGetOrCreateErrorsToRejectedAcknowledgements(
Map<String, List<List<String>>> validationErrorMap) {
List<Error> errorList = new ArrayList<>();
Map<String, List<Error>> submissionIdToError = new HashMap<>();
validationErrorMap.forEach((submissionId, validationErrorGrp) -> {
submissionIdToError.put(submissionId, new ArrayList<>());
validationErrorGrp.forEach(validationError -> {
String ruleNum = validationError.get(0);
String severityCd = validationError.get(1);
String errorMessageTxt = validationError.get(2);
var databaseError = errorRepo.findById(ruleNum);
if (databaseError.isPresent()) {
log.info(String.format("Found reject reason %s", ruleNum));
Error existingError = databaseError.get();
errorList.add(existingError);
submissionIdToError.get(submissionId).add(existingError);
} else {
log.warn(String.format("New reject reason found: %s. New translation required", ruleNum));
Error newError = new Error();
newError.setMefErrorCode(ruleNum);
newError.setErrorCodeTranslationKey(CreateTranslationKey("reject", ruleNum));
newError.setMefErrorCategory(severityCd);
newError.setErrorMessage(errorMessageTxt);
log.info(String.format("Saving new reject reason %s", ruleNum));
errorList.add(newError);
submissionIdToError.get(submissionId).add(newError);
}
});
});
errorRepo.saveAll(errorList);
return submissionIdToError;
}
protected void addErrorsToRejectedAcknowledgementsAndDeletePendingRecords(
Map<String, List<Error>> submissionIdToError,
Map<String, Completed> submissionIdToCompleted,
Map<String, Pending> submissionIdToPending) {
List<Completed> completedToSave = new ArrayList<>();
List<Pending> pendingToDelete = new ArrayList<>();
submissionIdToCompleted.forEach((submissionId, completed) -> {
List<Error> errorsToPersist = submissionIdToError.get(submissionId);
Optional<Pending> pending = Optional.ofNullable(submissionIdToPending.get(submissionId));
if (errorsToPersist != null
&& !errorsToPersist.isEmpty()
&& completed.getStatus().toLowerCase().compareTo("rejected") == 0) {
completed.setErrors(errorsToPersist);
}
completedToSave.add(completed);
if (pending.isPresent()) {
Pending p = pending.get();
pendingToDelete.add(p);
}
});
log.info(String.format("Creating %s completed objects", completedToSave.size()));
completedRepo.saveAll(completedToSave);
log.info(String.format("Deleting %s pending objects", pendingToDelete.size()));
pendingRepo.deleteAll(pendingToDelete);
}
protected void createToolkitError(Pending pending, Exception e) {
String submissionId = pending.getSubmissionId();
ToolkitError tke = new ToolkitError();
tke.setSubmissionId(submissionId);
tke.setErrorName(e.getClass().getName());
tke.setErrorMessage(e.toString());
toolkitErrorRepo.save(tke);
// delete the corresponding pending record
try {
deletePendingRecord(submissionId);
} catch (EntityNotFoundException notFoundException) {
log.error(notFoundException.getMessage());
}
}
private void deletePendingRecord(String submissionId) {
Pending p = pendingRepo
.findById(submissionId)
.orElseThrow(() -> new EntityNotFoundException(
String.format("Submission with id [%s] was not found in the database!", submissionId)));
pendingRepo.delete(p);
}
private AcknowledgementStatus getCompletedStatus(Completed c) {
if (c.getStatus().toLowerCase().compareTo("accepted") == 0) {
return new AcknowledgementStatus(
Status.Accepted, CreateTranslationKey("status", "accepted"), List.of(), c.getCreatedAt());
} else if (c.getStatus().toLowerCase().compareTo("rejected") == 0) {
return new AcknowledgementStatus(
Status.Rejected,
CreateTranslationKey("status", "rejected"),
createRejectedReasonList(c),
c.getCreatedAt());
} else {
throw new RuntimeException("Missing status type");
}
}
private List<RejectedStatus> createRejectedReasonList(Completed c) {
List<RejectedStatus> statuses = new ArrayList<>();
if (c.getErrors() != null) {
c.getErrors()
.forEach(x -> statuses.add(new RejectedStatus(
x.getMefErrorCode(), x.getErrorCodeTranslationKey(), x.getErrorMessage())));
}
return statuses;
}
private String CreateTranslationKey(String type, String name) {
return String.join(
statusProperties.getTranslationKeySplitter(), statusProperties.getRootTranslationKey(), type, name);
}
private void partitionBatch(Set<Pending> pBatch) {
Iterable<List<Pending>> partitions =
Iterables.partition(pBatch, IntMath.divide(pBatch.size(), 2, RoundingMode.CEILING));
partitions.forEach(partition -> {
try {
getGetAcksResult(new HashSet<>(partition));
} catch (ToolkitException tke) {
if (partition.size() == 1) {
// report and flag bad data to db
createToolkitError(partition.get(0), tke);
} else {
partitionBatch(new HashSet<>(partition));
}
} catch (ServiceException se) {
if (partition.size() == 1) {
log.warn("ServiceException - " + se);
} else {
partitionBatch(new HashSet<>(partition));
}
}
});
}
}

View file

@ -1,14 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import gov.irs.directfile.status.domain.Completed;
public interface CompletedAcknowledgementRepository extends CrudRepository<Completed, String> {
@Query(value = "SELECT * FROM completed WHERE submission_id = :submissionId LIMIT 1", nativeQuery = true)
Optional<Completed> GetCompletedSubmission(String submissionId);
}

View file

@ -1,18 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import gov.irs.directfile.status.domain.Pending;
public interface PendingAcknowledgementRepository extends CrudRepository<Pending, String> {
@Query(value = "SELECT * FROM pending WHERE submission_id = :submissionId LIMIT 1", nativeQuery = true)
Optional<Pending> GetPendingSubmission(String submissionId);
@Query(value = "SELECT * FROM pending WHERE pod_id = :podId ORDER BY created_at asc", nativeQuery = true)
List<Pending> findAllByPodId(String podId);
}

View file

@ -1,48 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import gov.irs.directfile.status.domain.TaxReturnSubmission;
public interface TaxReturnSubmissionRepository extends CrudRepository<TaxReturnSubmission, Long> {
@Query(
value =
"SELECT submission_id FROM tax_return_submission WHERE tax_return_id = :taxReturnId ORDER BY CREATED_AT DESC LIMIT 1",
nativeQuery = true)
Optional<String> getLatestSubmissionIdByTaxReturnId(UUID taxReturnId);
@Query(
value =
"""
SELECT trs.submission_id
FROM tax_return_submission trs
JOIN completed c ON trs.submission_id = c.submission_id
WHERE trs.tax_return_id = :taxReturnId
AND LOWER(c.status) = 'accepted'
ORDER BY c.created_at DESC LIMIT 1
""",
nativeQuery = true)
Optional<String> getLatestAcceptedSubmissionIdForTaxReturnId(UUID taxReturnId);
@Query(
value =
"""
SELECT trs.submission_id
FROM tax_return_submission trs
JOIN completed c ON trs.submission_id = c.submission_id
WHERE
trs.tax_return_id = (
SELECT tax_return_id
FROM tax_return_submission
WHERE submission_id = :submissionId
)
AND LOWER(c.status) = 'accepted'
ORDER BY c.created_at DESC LIMIT 1
""",
nativeQuery = true)
Optional<String> getLatestAcceptedSubmissionIdOfParentTaxReturn(String submissionId);
}

View file

@ -1,24 +0,0 @@
package gov.irs.directfile.status.acknowledgement.domain;
import java.util.Date;
import java.util.List;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import gov.irs.directfile.models.RejectedStatus;
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"},
justification = "Initial SpotBugs Setup")
@Getter
@Setter
@AllArgsConstructor
public class AcknowledgementStatus {
private Status status;
private String translationKey;
private List<RejectedStatus> rejectionCodes;
private Date createdAt;
}

View file

@ -1,8 +0,0 @@
package gov.irs.directfile.status.acknowledgement.domain;
public enum Status {
Pending,
Accepted,
Rejected,
Error,
}

View file

@ -1,13 +0,0 @@
package gov.irs.directfile.status.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "aws")
@AllArgsConstructor
@Getter
public class AWSClientConfiguration {
private String accessKey;
private String secretKey;
}

View file

@ -1,34 +0,0 @@
package gov.irs.directfile.status.config;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(AWSClientConfiguration.class)
public class AWSCredentialsConfiguration {
private final AWSClientConfiguration awsClientConfiguration;
@Bean
@ConditionalOnProperty(
name = "aws.default-credentials-provider-chain-enabled",
havingValue = "false",
matchIfMissing = true)
public AwsCredentialsProvider staticAWSCredentialsProvider() {
return StaticCredentialsProvider.create(AwsBasicCredentials.create(
awsClientConfiguration.getAccessKey(), awsClientConfiguration.getSecretKey()));
}
@Bean
@ConditionalOnProperty(name = "aws.default-credentials-provider-chain-enabled", havingValue = "true")
public AwsCredentialsProvider defaultAWSCredentialsProvider() {
return DefaultCredentialsProvider.create();
}
}

View file

@ -1,46 +0,0 @@
package gov.irs.directfile.status.config;
import java.net.URI;
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager;
import software.amazon.encryption.s3.materials.KmsKeyring;
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(EncryptionConfiguration.class)
public class EncryptionClientConfiguration {
private final EncryptionConfiguration encryptionConfig;
private final AwsCredentialsProvider awsCredentialsProvider;
public CryptographicMaterialsManager kmsCrypto(@NonNull String kmsWrappingKeyArn) {
return DefaultCryptoMaterialsManager.builder()
.keyring(KmsKeyring.builder()
.kmsClient(regionalKmsClient())
.wrappingKeyId(kmsWrappingKeyArn)
.build())
.build();
}
public SecretKey getLocalAesWrappingKey() {
return new SecretKeySpec(Base64.getDecoder().decode(encryptionConfig.getLocalWrappingKey()), "AES");
}
protected KmsClient regionalKmsClient() {
return KmsClient.builder()
.region(Region.of(encryptionConfig.getRegion()))
.credentialsProvider(awsCredentialsProvider)
.endpointOverride(URI.create(encryptionConfig.getKmsEndpoint()))
.build();
}
}

View file

@ -1,20 +0,0 @@
package gov.irs.directfile.status.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
@AllArgsConstructor
@Getter
@ConfigurationProperties
public class EncryptionConfiguration {
@Value("${aws.kmsEndpoint:#{null}}")
private String kmsEndpoint;
@Value("${aws.region:#{null}}")
private String region;
@Value("${direct-file.local-encryption.local-wrapping-key:#{null}}")
private String localWrappingKey;
}

View file

@ -1,42 +0,0 @@
package gov.irs.directfile.status.config;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hibernate.validator.constraints.URL;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@AllArgsConstructor
@Getter
// See https://stackoverflow.com/a/67994421 for why we use kebab case here (message-queue)
// when we're using camel case (messageQueue) in the source.
@ConfigurationProperties("status.message-queue")
public class MessageQueueConfiguration {
@NotBlank
@URL
private final String endpoint;
@NotBlank
private final String statusChangeQueue;
@NotBlank
private final String pendingSubmissionQueue;
@NotBlank
private final String dlqPendingSubmissionQueue;
@NotBlank
private final String region;
@NotBlank
private final String accessKey;
@NotBlank
private final String secretKey;
private final boolean sqsMessageHandlingEnabled;
private final boolean statusChangePublishEnabled;
}

View file

@ -1,29 +0,0 @@
package gov.irs.directfile.status.config;
import java.net.URI;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
@Configuration
@ConditionalOnProperty(value = "status.sns.status-change-publish-enabled", havingValue = "true")
@EnableConfigurationProperties(SnsConfiguration.class)
@AllArgsConstructor
public class SnsClientConfiguration {
private final AwsCredentialsProvider awsCredentialsProvider;
@Bean
public SnsClient snsClient(SnsConfiguration snsConfiguration) {
return SnsClient.builder()
.region(Region.of(snsConfiguration.getRegion()))
.credentialsProvider(awsCredentialsProvider)
.endpointOverride(URI.create(snsConfiguration.getEndpoint()))
.build();
}
}

View file

@ -1,32 +0,0 @@
package gov.irs.directfile.status.config;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hibernate.validator.constraints.URL;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@AllArgsConstructor
@Getter
@ConfigurationProperties("status.sns")
public class SnsConfiguration {
@NotBlank
@URL
private final String endpoint;
@NotBlank
private final String statusChangeTopicArn;
@NotBlank
private final String region;
@NotBlank
private final String accessKey;
@NotBlank
private final String secretKey;
private final boolean statusChangePublishEnabled;
}

View file

@ -1,27 +0,0 @@
package gov.irs.directfile.status.config;
import java.net.URI;
import lombok.AllArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
@Configuration
@EnableConfigurationProperties(MessageQueueConfiguration.class)
@AllArgsConstructor
public class SqsClientConfiguration {
private final AwsCredentialsProvider awsCredentialsProvider;
@Bean
public SqsClient sqsClient(MessageQueueConfiguration messageQueueConfiguration) {
return SqsClient.builder()
.region(Region.of(messageQueueConfiguration.getRegion()))
.credentialsProvider(awsCredentialsProvider)
.endpointOverride(URI.create(messageQueueConfiguration.getEndpoint()))
.build();
}
}

View file

@ -1,39 +0,0 @@
package gov.irs.directfile.status.config;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@SuppressFBWarnings(
value = {"NM_FIELD_NAMING_CONVENTION"},
justification = "Initial SpotBugs Setup")
@AllArgsConstructor
@Getter
@Validated
@ConfigurationProperties(prefix = "status")
public class StatusProperties {
@NotBlank
private String applicationId;
private String keystoreBase64;
private String keystorePassword;
private String keystoreAlias;
private String etin;
@Setter
private String asid;
private String efin;
private boolean unitTesting;
private boolean prod;
private String rootTranslationKey;
private String translationKeySplitter;
public Long AckPollInMilliseconds;
public boolean statusEndpointReturnsPendingByDefaultEnabled;
private String toolkit;
private boolean statusPollingEnabled;
}

View file

@ -1,51 +0,0 @@
package gov.irs.directfile.status.domain;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import gov.irs.mef.AcknowledgementList;
@Getter
public class AcknowledgementWrapper {
private final String submissionId;
private final String receiptId;
private final String acceptanceStatusTxt;
private final List<ValidationErrorGrpWrapper> validationErrorList;
public AcknowledgementWrapper(String submissionId, String receiptId, String acceptanceStatusTxt) {
this(submissionId, receiptId, acceptanceStatusTxt, List.of());
}
public AcknowledgementWrapper(
String submissionId,
String receiptId,
String acceptanceStatusTxt,
List<ValidationErrorGrpWrapper> validationErrorList) {
this.submissionId = submissionId;
this.receiptId = receiptId;
this.acceptanceStatusTxt = acceptanceStatusTxt;
this.validationErrorList = List.copyOf(validationErrorList);
}
public static AcknowledgementWrapper fromMefAcknowledgement(AcknowledgementList.Acknowledgement acknowledgement) {
if (acknowledgement.getValidationErrorList() == null) {
return new AcknowledgementWrapper(
acknowledgement.getSubmissionId(),
acknowledgement.getReceiptId(),
acknowledgement.getAcceptanceStatusTxt(),
new ArrayList<>());
} else {
List<ValidationErrorGrpWrapper> validationErrors =
acknowledgement.getValidationErrorList().getValidationErrorGrp().stream()
.map(ValidationErrorGrpWrapper::fromMefValidationErrorGrp)
.toList();
return new AcknowledgementWrapper(
acknowledgement.getSubmissionId(),
acknowledgement.getReceiptId(),
acknowledgement.getAcceptanceStatusTxt(),
validationErrors);
}
}
}

View file

@ -1,23 +0,0 @@
package gov.irs.directfile.status.domain;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
import gov.irs.mef.AcknowledgementList;
@Getter
public class AcknowledgementsListWrapper {
private final List<AcknowledgementWrapper> acknowledgements;
public AcknowledgementsListWrapper(AcknowledgementList acknowledgementList) {
this.acknowledgements = List.copyOf(acknowledgementList.getAcknowledgements().stream()
.map(AcknowledgementWrapper::fromMefAcknowledgement)
.collect(Collectors.toList()));
}
public AcknowledgementsListWrapper(List<AcknowledgementWrapper> acknowledgementWrappers) {
this.acknowledgements = List.copyOf(acknowledgementWrappers);
}
}

View file

@ -1,48 +0,0 @@
package gov.irs.directfile.status.domain;
import java.util.Date;
import java.util.List;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"},
justification = "Initial SpotBugs Setup")
@Entity(name = "Completed")
@Getter
@Setter
public class Completed {
@Id
@Column(length = 20)
private String submissionId;
@Column(length = 20)
private String status;
@Getter
@Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "DEFAULT CURRENT_TIMESTAMP")
@CreationTimestamp
private Date createdAt;
@Getter
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "completed-errors",
joinColumns = {
@JoinColumn(
name = "submissionId",
referencedColumnName = "submissionId",
foreignKey = @ForeignKey(name = "completed_error_submission_id_fk"))
},
inverseJoinColumns = {
@JoinColumn(
name = "mefErrorCode",
referencedColumnName = "meferror_code",
foreignKey = @ForeignKey(name = "completed_error_mef_error_code_fk"))
})
private List<Error> errors;
}

View file

@ -1,45 +0,0 @@
package gov.irs.directfile.status.domain;
import java.time.LocalDate;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.CreationTimestamp;
@Entity(name = "Error")
@Getter
@Setter
@ToString(doNotUseGetters = true)
public class Error {
@Id
@Column(name = "meferror_code", length = 25)
private String mefErrorCode;
@Column(name = "error_message", columnDefinition = "TEXT")
private String errorMessage;
@Column(name = "meferror_category")
private String mefErrorCategory;
private String errorCodeTranslationKey;
@Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "DEFAULT CURRENT_TIMESTAMP")
@CreationTimestamp
private LocalDate createdAt;
@ManyToMany(mappedBy = "errors")
@JsonBackReference
private List<Completed> completed;
public List<Completed> getCompleted() {
return List.copyOf(completed);
}
public void setCompleted(List<Completed> completed) {
this.completed = List.copyOf(completed);
}
}

View file

@ -1,18 +0,0 @@
package gov.irs.directfile.status.domain;
import lombok.Getter;
import gov.irs.mef.services.transmitter.mtom.GetAcksResult;
@Getter
public class GetAcksResultWrapper {
private AcknowledgementsListWrapper acknowledgementsListWrapper;
public GetAcksResultWrapper(GetAcksResult getAcksResult) {
this.acknowledgementsListWrapper = new AcknowledgementsListWrapper(getAcksResult.getAcknowledgementList());
}
public GetAcksResultWrapper(AcknowledgementsListWrapper acknowledgementsListWrapper) {
this.acknowledgementsListWrapper = acknowledgementsListWrapper;
}
}

View file

@ -1,43 +0,0 @@
package gov.irs.directfile.status.domain;
import java.util.Date;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2", "NM_FIELD_NAMING_CONVENTION"},
justification = "Initial SpotBugs Setup")
@Entity(name = "Pending")
@Getter
@Setter
public class Pending {
@Id
@Column(length = 20)
private String submissionId;
@Getter
@Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "DEFAULT CURRENT_TIMESTAMP")
@CreationTimestamp
private Date createdAt;
@JdbcTypeCode(SqlTypes.VARCHAR)
@Column(columnDefinition = "varchar", name = "pod_id", length = 255)
private String podId;
public Pending() {}
public Pending(final String submissionId) {
this.submissionId = submissionId;
}
public Pending(String submissionId, String podId) {
this.submissionId = submissionId;
this.podId = podId;
}
}

View file

@ -1,30 +0,0 @@
package gov.irs.directfile.status.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@Getter
@Setter
@Entity(name = "PodIdentifier")
@Table(name = "pod_identifier")
public class PodIdentifier {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JdbcTypeCode(SqlTypes.VARCHAR)
@Column(columnDefinition = "varchar", name = "asid", length = 50)
private String asid;
@JdbcTypeCode(SqlTypes.VARCHAR)
@Column(columnDefinition = "varchar", name = "region", length = 50)
private String region;
@JdbcTypeCode(SqlTypes.VARCHAR)
@Column(columnDefinition = "varchar", name = "pod_id", length = 255)
private String podId;
}

View file

@ -1,40 +0,0 @@
package gov.irs.directfile.status.domain;
import java.util.Date;
import java.util.UUID;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"},
justification = "Initial SpotBugs Setup")
@Entity(name = "TaxReturnSubmission")
@Table(name = "tax_return_submission")
@Getter
@Setter
public class TaxReturnSubmission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UUID taxReturnId;
@Column(length = 20)
private String submissionId;
@Getter
@Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "DEFAULT CURRENT_TIMESTAMP")
@CreationTimestamp
private Date createdAt;
public TaxReturnSubmission(UUID taxReturnId, String submissionId) {
this.taxReturnId = taxReturnId;
this.submissionId = submissionId;
}
public TaxReturnSubmission() {}
}

View file

@ -1,30 +0,0 @@
package gov.irs.directfile.status.domain;
import java.util.Date;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
@SuppressFBWarnings(
value = {"NM_FIELD_NAMING_CONVENTION", "EI_EXPOSE_REP", "EI_EXPOSE_REP2"},
justification = "Initial SpotBugs Setup")
@Entity
@Getter
@Setter
public class ToolkitError {
@Id
private String submissionId;
@NotNull private String errorMessage;
@NotNull private String errorName;
@Getter
@Column(name = "created_at", nullable = false, updatable = false, columnDefinition = "DEFAULT CURRENT_TIMESTAMP")
@CreationTimestamp
private Date createdAt;
}

View file

@ -1,22 +0,0 @@
package gov.irs.directfile.status.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import gov.irs.efile.ValidationErrorListType;
@Getter
@AllArgsConstructor
public class ValidationErrorGrpWrapper {
private String ruleNum;
private String severityCd;
private String errorMessageTxt;
public static ValidationErrorGrpWrapper fromMefValidationErrorGrp(
ValidationErrorListType.ValidationErrorGrp validationErrorGrp) {
return new ValidationErrorGrpWrapper(
validationErrorGrp.getRuleNum(),
validationErrorGrp.getSeverityCd(),
validationErrorGrp.getErrorMessageTxt());
}
}

View file

@ -1,7 +0,0 @@
package gov.irs.directfile.status.error;
import org.springframework.data.repository.CrudRepository;
import gov.irs.directfile.status.domain.Error;
public interface ErrorRepository extends CrudRepository<Error, String> {}

View file

@ -1,9 +0,0 @@
package gov.irs.directfile.status.error;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import gov.irs.directfile.status.domain.ToolkitError;
@Repository
public interface ToolkitErrorRepository extends CrudRepository<ToolkitError, String> {}

View file

@ -1,18 +0,0 @@
package gov.irs.directfile.status.mef.client;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import gov.irs.mef.services.ServiceContext;
@Getter
@Setter
@AllArgsConstructor
@SuppressFBWarnings(
value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"},
justification = "The Getter and Setter are auto-generated by Lombox.")
public class ServiceContextWrapper {
private final ServiceContext serviceContext;
}

View file

@ -1,14 +0,0 @@
package gov.irs.directfile.status.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import gov.irs.directfile.status.domain.PodIdentifier;
public interface PodIdentifierRepository extends CrudRepository<PodIdentifier, Long> {
@Query(value = "SELECT asid from pod_identifier WHERE pod_id = :pod_id", nativeQuery = true)
Optional<String> findAsidByPodId(String pod_id);
}

View file

@ -1,71 +0,0 @@
package gov.irs.directfile.status.services;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.jms.Message;
import jakarta.jms.MessageListener;
import jakarta.jms.TextMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
import gov.irs.directfile.status.config.MessageQueueConfiguration;
@Slf4j
@Service
@ConditionalOnProperty(value = "status.messageQueue.sqs-message-handling-enabled", havingValue = "true")
@EnableConfigurationProperties(MessageQueueConfiguration.class)
public class MessageQueueListenerService implements MessageListener {
private final String pendingSubmissionQueue;
private final PendingSubmissionMessageRouter pendingSubmissionMessageRouter;
private final SubmissionConfirmationMessageRouter submissionConfirmationMessageRouter;
private final ObjectMapper objectMapper = new ObjectMapper();
MessageQueueListenerService(
MessageQueueConfiguration messageQueueConfiguration,
PendingSubmissionMessageRouter pendingSubmissionMessageRouter,
SubmissionConfirmationMessageRouter submissionConfirmationMessageRouter) {
this.pendingSubmissionQueue = messageQueueConfiguration.getPendingSubmissionQueue();
this.pendingSubmissionMessageRouter = pendingSubmissionMessageRouter;
this.submissionConfirmationMessageRouter = submissionConfirmationMessageRouter;
}
@Override
public void onMessage(Message message) {
log.info("onMessage called ({})", pendingSubmissionQueue);
String rawText = "";
try {
rawText = ((TextMessage) message).getText();
try {
// First try to deserialize as a VersionedPendingSubmissionMessage (what SQS would send)
log.info("Trying to deserialize message to VersionedPendingSubmissionMessage");
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> versionedPendingSubmissionMessage =
objectMapper.readValue(rawText, new TypeReference<>() {});
pendingSubmissionMessageRouter.handlePendingSubmissionMessage(versionedPendingSubmissionMessage);
} catch (JsonProcessingException e) {
// Otherwise, try to deserialize as a VersionedSubmissionConfirmationMessage (what SNS would send)
log.info("Trying to deserialize message to VersionedSubmissionConfirmationMessage");
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload>
versionedSubmissionConfirmationMessage =
objectMapper.readValue(rawText, new TypeReference<>() {});
submissionConfirmationMessageRouter.handleSubmissionConfirmationMessage(
versionedSubmissionConfirmationMessage);
}
message.acknowledge();
} catch (Exception e) {
log.error(
"Error saving Pending objects in database. Re-queueing list of submissionIds: {}. Error: {}",
rawText,
e.getMessage());
}
}
}

View file

@ -1,60 +0,0 @@
package gov.irs.directfile.status.services;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.exception.UnsupportedVersionException;
import gov.irs.directfile.models.message.pending.PendingSubmissionMessageVersion;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
import gov.irs.directfile.status.services.handlers.pending.PendingSubmissionHandler;
import gov.irs.directfile.status.services.handlers.pending.PendingSubmissionV1Handler;
import gov.irs.directfile.status.services.handlers.pending.UnsupportedMessageVersionHandler;
@Service
public class PendingSubmissionMessageRouter {
private final Map<PendingSubmissionMessageVersion, PendingSubmissionHandler> handlers = new HashMap<>();
private final UnsupportedMessageVersionHandler unsupportedMessageVersionHandler;
public PendingSubmissionMessageRouter(
UnsupportedMessageVersionHandler unsupportedMessageVersionHandler,
PendingSubmissionV1Handler pendingSubmissionV1Handler) {
this.unsupportedMessageVersionHandler = unsupportedMessageVersionHandler;
this.handlers.put(PendingSubmissionMessageVersion.V1, pendingSubmissionV1Handler);
}
public void handlePendingSubmissionMessage(
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> message) {
QueueMessageHeaders headers = message.getHeaders();
Optional<String> versionOptional = headers.getAttribute(MessageHeaderAttribute.VERSION);
// We can assume a version number is present here since a VersionedPendingSubmissionMessage
// mandates that on construction.
// Get enum for this version (may throw UnsupportedVersionException which we should handle and
// rethrow to get message back on queue/DLQ).
PendingSubmissionMessageVersion version;
try {
version = PendingSubmissionMessageVersion.getEnum(versionOptional.get());
} catch (UnsupportedVersionException e) {
unsupportedMessageVersionHandler.handlePendingSubmissionMessage(message);
throw e;
}
// Get handler. If not found, we have an unsupported version so handle and throw exception.
PendingSubmissionHandler handler = handlers.get(version);
if (handler == null) {
unsupportedMessageVersionHandler.handlePendingSubmissionMessage(message);
throw new UnsupportedVersionException(
String.format("No handler found for PendingSubmissionMessageVersion (%s)", version.getVersion()));
}
// We should have a good handler, so handle the message.
handler.handlePendingSubmissionMessage(message);
}
}

View file

@ -1,67 +0,0 @@
package gov.irs.directfile.status.services;
import com.amazon.sqs.javamessaging.ProviderConfiguration;
import com.amazon.sqs.javamessaging.SQSConnection;
import com.amazon.sqs.javamessaging.SQSConnectionFactory;
import com.amazon.sqs.javamessaging.SQSSession;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.PreDestroy;
import jakarta.jms.JMSException;
import jakarta.jms.MessageConsumer;
import jakarta.jms.Queue;
import jakarta.jms.Session;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.sqs.SqsClient;
import gov.irs.directfile.status.config.MessageQueueConfiguration;
@Service
@ConditionalOnProperty(value = "status.messageQueue.sqs-message-handling-enabled", havingValue = "true")
@Slf4j
@SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Java 21 update")
@SuppressWarnings({"PMD.CloseResource", "PMD.UnusedPrivateMethod"})
@EnableConfigurationProperties(MessageQueueConfiguration.class)
public class SqsConnectionSetupService {
private final SqsClient sqsClient;
private final String pendingSubmissionQueue;
private final MessageQueueListenerService messageQueueListenerService;
private SQSConnection connection;
SqsConnectionSetupService(
SqsClient sqsClient,
MessageQueueConfiguration messageQueueConfiguration,
MessageQueueListenerService messageQueueListenerService)
throws JMSException {
this.sqsClient = sqsClient;
this.pendingSubmissionQueue = messageQueueConfiguration.getPendingSubmissionQueue();
this.messageQueueListenerService = messageQueueListenerService;
initializeSQSConnection();
}
private void initializeSQSConnection() throws JMSException {
SQSConnectionFactory connectionFactory = new SQSConnectionFactory(new ProviderConfiguration(), sqsClient);
connection = connectionFactory.createConnection();
Session session = connection.createSession(false, SQSSession.UNORDERED_ACKNOWLEDGE);
Queue queue = session.createQueue(pendingSubmissionQueue);
MessageConsumer consumer = session.createConsumer(queue);
consumer.setMessageListener(messageQueueListenerService);
connection.start();
log.info("CONNECTED TO SQS");
}
@PreDestroy
private void cleanup() throws JMSException {
if (connection != null) {
connection.stop();
log.info("SQS connection stopped");
connection.close();
log.info("SQS connection closed");
}
}
}

View file

@ -1,70 +0,0 @@
package gov.irs.directfile.status.services;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.PublisherException;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.status.StatusChangeMessageVersion;
import gov.irs.directfile.models.message.status.VersionedStatusChangeMessage;
import gov.irs.directfile.models.message.status.payload.AbstractStatusChangePayload;
import gov.irs.directfile.models.message.status.payload.StatusChangePayloadV1;
@Service
@Slf4j
public class StatusChangeMessageService {
private final List<StatusChangePublisher> publishers;
private final ObjectMapper mapper;
public StatusChangeMessageService(List<StatusChangePublisher> publishers, ObjectMapper mapper) {
this.publishers = publishers;
this.mapper = mapper;
}
public void publishStatusChangePayloadV1(Map<String, List<String>> statusSubmissionIdMap) {
AbstractStatusChangePayload payload = new StatusChangePayloadV1(statusSubmissionIdMap);
publishStatusChangePayload(payload, StatusChangeMessageVersion.V1);
}
private void publishStatusChangePayload(AbstractStatusChangePayload payload, StatusChangeMessageVersion version) {
if (publishers == null || publishers.isEmpty()) {
return;
}
VersionedStatusChangeMessage<AbstractStatusChangePayload> message = new VersionedStatusChangeMessage<>(
payload, new QueueMessageHeaders().addHeader(MessageHeaderAttribute.VERSION, version.getVersion()));
String jsonString;
try {
jsonString = mapper.writeValueAsString(message);
} catch (JsonProcessingException e) {
String errorMessage = "Exception calling writeValueAsString";
log.error(errorMessage, e);
throw new PublisherException(errorMessage, e);
}
List<String> errors = new ArrayList<>();
for (StatusChangePublisher publisher : publishers) {
try {
publisher.publish(jsonString);
} catch (Exception e) {
log.error("Exception calling publish", e);
errors.add(e.getMessage() + " (" + publisher.getClass().getSimpleName() + ")");
}
}
if (!errors.isEmpty()) {
String errorMessage = StringUtils.join(errors, "; ");
log.error(errorMessage);
throw new PublisherException(errorMessage);
}
}
}

View file

@ -1,7 +0,0 @@
package gov.irs.directfile.status.services;
import gov.irs.directfile.models.message.Publisher;
public interface StatusChangePublisher extends Publisher {
// Just a marker interface to allow Spring to inject all the publishers for this message type
}

View file

@ -1,18 +0,0 @@
package gov.irs.directfile.status.services;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.sns.SnsClient;
import gov.irs.directfile.models.message.SnsPublisher;
import gov.irs.directfile.status.config.SnsConfiguration;
@Service
@ConditionalOnProperty(value = "status.sns.status-change-publish-enabled", havingValue = "true")
@EnableConfigurationProperties(SnsConfiguration.class)
public class StatusChangeSnsPublisher extends SnsPublisher implements StatusChangePublisher {
public StatusChangeSnsPublisher(SnsClient snsClient, SnsConfiguration snsConfiguration) {
super(snsClient, snsConfiguration.getStatusChangeTopicArn());
}
}

View file

@ -1,18 +0,0 @@
package gov.irs.directfile.status.services;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.sqs.SqsClient;
import gov.irs.directfile.models.message.SqsPublisher;
import gov.irs.directfile.status.config.MessageQueueConfiguration;
@Service
@ConditionalOnProperty(value = "status.messageQueue.status-change-publish-enabled", havingValue = "true")
@EnableConfigurationProperties(MessageQueueConfiguration.class)
public class StatusChangeSqsPublisher extends SqsPublisher implements StatusChangePublisher {
public StatusChangeSqsPublisher(SqsClient sqsClient, MessageQueueConfiguration messageQueueConfiguration) {
super(sqsClient, messageQueueConfiguration.getStatusChangeQueue());
}
}

View file

@ -1,63 +0,0 @@
package gov.irs.directfile.status.services;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.confirmation.SubmissionConfirmationMessageVersion;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.exception.UnsupportedVersionException;
import gov.irs.directfile.status.services.handlers.confirmation.SubmissionConfirmationHandler;
import gov.irs.directfile.status.services.handlers.confirmation.SubmissionConfirmationV1Handler;
import gov.irs.directfile.status.services.handlers.confirmation.SubmissionConfirmationV2Handler;
import gov.irs.directfile.status.services.handlers.confirmation.UnsupportedMessageVersionHandler;
@Service
public class SubmissionConfirmationMessageRouter {
private final Map<SubmissionConfirmationMessageVersion, SubmissionConfirmationHandler> handlers = new HashMap<>();
private final UnsupportedMessageVersionHandler unsupportedMessageVersionHandler;
public SubmissionConfirmationMessageRouter(
UnsupportedMessageVersionHandler unsupportedMessageVersionHandler,
SubmissionConfirmationV1Handler submissionConfirmationV1Handler,
SubmissionConfirmationV2Handler submissionConfirmationV2Handler) {
this.unsupportedMessageVersionHandler = unsupportedMessageVersionHandler;
this.handlers.put(SubmissionConfirmationMessageVersion.V1, submissionConfirmationV1Handler);
this.handlers.put(SubmissionConfirmationMessageVersion.V2, submissionConfirmationV2Handler);
}
public void handleSubmissionConfirmationMessage(
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> message) {
QueueMessageHeaders headers = message.getHeaders();
Optional<String> versionOptional = headers.getAttribute(MessageHeaderAttribute.VERSION);
// We can assume a version number is present here since a VersionedSubmissionConfirmationMessage
// mandates that on construction.
// Get enum for this version (may throw UnsupportedVersionException which we should handle and
// rethrow to get message back on queue/DLQ).
SubmissionConfirmationMessageVersion version;
try {
version = SubmissionConfirmationMessageVersion.getEnum(versionOptional.get());
} catch (UnsupportedVersionException e) {
unsupportedMessageVersionHandler.handleSubmissionConfirmationMessage(message);
throw e;
}
// Get handler. If not found, we have an unsupported version so handle and throw exception.
SubmissionConfirmationHandler handler = handlers.get(version);
if (handler == null) {
unsupportedMessageVersionHandler.handleSubmissionConfirmationMessage(message);
throw new UnsupportedVersionException(String.format(
"No handler found for SubmissionConfirmationMessageVersion (%s)", version.getVersion()));
}
// We should have a good handler, so handle the message.
handler.handleSubmissionConfirmationMessage(message);
}
}

View file

@ -1,9 +0,0 @@
package gov.irs.directfile.status.services.handlers.confirmation;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
public interface SubmissionConfirmationHandler {
void handleSubmissionConfirmationMessage(
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> message);
}

View file

@ -1,65 +0,0 @@
package gov.irs.directfile.status.services.handlers.confirmation;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.TaxReturnSubmissionReceipt;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV1;
import gov.irs.directfile.status.acknowledgement.PendingAcknowledgementRepository;
import gov.irs.directfile.status.acknowledgement.TaxReturnSubmissionRepository;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.Pending;
import gov.irs.directfile.status.domain.TaxReturnSubmission;
/**
* @deprecated V1 messages are no longer sent, but keeping handlers until we are sure queues are V1-free
*/
@Deprecated
@Service
@Slf4j
public class SubmissionConfirmationV1Handler implements SubmissionConfirmationHandler {
private final PendingAcknowledgementRepository pendingRepo;
private final TaxReturnSubmissionRepository taxReturnSubmissionRepo;
private final StatusProperties statusProperties;
public SubmissionConfirmationV1Handler(
PendingAcknowledgementRepository pendingRepo,
TaxReturnSubmissionRepository taxReturnSubmissionRepo,
StatusProperties statusProperties) {
this.pendingRepo = pendingRepo;
this.taxReturnSubmissionRepo = taxReturnSubmissionRepo;
this.statusProperties = statusProperties;
}
@Override
public void handleSubmissionConfirmationMessage(
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> message) {
SubmissionConfirmationPayloadV1 payload = (SubmissionConfirmationPayloadV1) message.getPayload();
List<TaxReturnSubmissionReceipt> taxReturnSubmissionReceipts = payload.getReceipts();
StringBuilder sb = new StringBuilder();
sb.append("Received submission confirmation V1 message for tax return ids:");
taxReturnSubmissionReceipts.forEach(
taxReturnSubmissionReceipt -> sb.append(" ").append(taxReturnSubmissionReceipt.getTaxReturnId()));
log.info(sb.toString());
List<Pending> pendings = taxReturnSubmissionReceipts.stream()
.map(taxReturnSubmissionReceipt ->
new Pending(taxReturnSubmissionReceipt.getSubmissionId(), statusProperties.getApplicationId()))
.toList();
List<TaxReturnSubmission> taxReturnSubmissions = taxReturnSubmissionReceipts.stream()
.map(taxReturnSubmissionReceipt -> new TaxReturnSubmission(
taxReturnSubmissionReceipt.getTaxReturnId(), taxReturnSubmissionReceipt.getSubmissionId()))
.toList();
log.info(String.format("Saving %s pendings", pendings.size()));
pendingRepo.saveAll(pendings);
taxReturnSubmissionRepo.saveAll(taxReturnSubmissions);
}
}

View file

@ -1,68 +0,0 @@
package gov.irs.directfile.status.services.handlers.confirmation;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.TaxReturnSubmissionReceipt;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV2;
import gov.irs.directfile.models.message.event.SubmissionEventTypeEnum;
import gov.irs.directfile.status.acknowledgement.PendingAcknowledgementRepository;
import gov.irs.directfile.status.acknowledgement.TaxReturnSubmissionRepository;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.Pending;
import gov.irs.directfile.status.domain.TaxReturnSubmission;
@Service
@Slf4j
@AllArgsConstructor
public class SubmissionConfirmationV2Handler implements SubmissionConfirmationHandler {
private final PendingAcknowledgementRepository pendingRepo;
private final TaxReturnSubmissionRepository taxReturnSubmissionRepo;
private final StatusProperties statusProperties;
@Override
public void handleSubmissionConfirmationMessage(
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> message) {
SubmissionConfirmationPayloadV2 payload = (SubmissionConfirmationPayloadV2) message.getPayload();
// Note that for the pending submission queue, we only need to process SUBMITTED tax return
// events. FAILED events are also sent via the submission confirmation SNS topic subscription,
// but can be ignored here since they aren't considered pending submissions.
List<TaxReturnSubmissionReceipt> submittedTaxReturnSubmissionReceipts = new ArrayList<>();
payload.getEntries().forEach(entry -> {
if (SubmissionEventTypeEnum.SUBMITTED.equals(entry.getEventType())) {
submittedTaxReturnSubmissionReceipts.add(entry.getTaxReturnSubmissionReceipt());
}
});
// If we did not get any SUBMITTED returns, then we're done.
if (submittedTaxReturnSubmissionReceipts.isEmpty()) return;
// Otherwise, save the database records for the SUBMITTED returns.
StringBuilder sb = new StringBuilder();
sb.append("Received SUBMITTED submission confirmation V2 message for tax return ids:");
submittedTaxReturnSubmissionReceipts.forEach(
taxReturnSubmissionReceipt -> sb.append(" ").append(taxReturnSubmissionReceipt.getTaxReturnId()));
log.info(sb.toString());
List<Pending> pendings = submittedTaxReturnSubmissionReceipts.stream()
.map(taxReturnSubmissionReceipt ->
new Pending(taxReturnSubmissionReceipt.getSubmissionId(), statusProperties.getApplicationId()))
.toList();
List<TaxReturnSubmission> taxReturnSubmissions = submittedTaxReturnSubmissionReceipts.stream()
.map(taxReturnSubmissionReceipt -> new TaxReturnSubmission(
taxReturnSubmissionReceipt.getTaxReturnId(), taxReturnSubmissionReceipt.getSubmissionId()))
.toList();
log.info(String.format("Saving %s pendings", pendings.size()));
pendingRepo.saveAll(pendings);
taxReturnSubmissionRepo.saveAll(taxReturnSubmissions);
}
}

View file

@ -1,17 +0,0 @@
package gov.irs.directfile.status.services.handlers.confirmation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
@Service("SubmissionConfirmationUnsupportedMessageVersionHandler")
@Slf4j
public class UnsupportedMessageVersionHandler implements SubmissionConfirmationHandler {
@Override
public void handleSubmissionConfirmationMessage(
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> payload) {
log.error("Unable to process Submission Confirmation Message. Headers: {} ", payload.getHeaders());
}
}

View file

@ -1,8 +0,0 @@
package gov.irs.directfile.status.services.handlers.pending;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
public interface PendingSubmissionHandler {
void handlePendingSubmissionMessage(VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> message);
}

View file

@ -1,64 +0,0 @@
package gov.irs.directfile.status.services.handlers.pending;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.TaxReturnIdAndSubmissionId;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
import gov.irs.directfile.models.message.pending.payload.PendingSubmissionPayloadV1;
import gov.irs.directfile.status.acknowledgement.PendingAcknowledgementRepository;
import gov.irs.directfile.status.acknowledgement.TaxReturnSubmissionRepository;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.Pending;
import gov.irs.directfile.status.domain.TaxReturnSubmission;
@Service
@Slf4j
public class PendingSubmissionV1Handler implements PendingSubmissionHandler {
private final PendingAcknowledgementRepository pendingRepo;
private final TaxReturnSubmissionRepository taxReturnSubmissionRepo;
private final StatusProperties statusProperties;
public PendingSubmissionV1Handler(
PendingAcknowledgementRepository pendingRepo,
TaxReturnSubmissionRepository taxReturnSubmissionRepo,
StatusProperties statusProperties) {
this.pendingRepo = pendingRepo;
this.taxReturnSubmissionRepo = taxReturnSubmissionRepo;
this.statusProperties = statusProperties;
}
@Override
public void handlePendingSubmissionMessage(
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> message) {
PendingSubmissionPayloadV1 payload = (PendingSubmissionPayloadV1) message.getPayload();
List<TaxReturnIdAndSubmissionId> taxReturnIdAndSubmissionIds = payload.getPendings();
StringBuilder sb = new StringBuilder();
sb.append("Received pending submission V1 message for tax return ids:");
taxReturnIdAndSubmissionIds.forEach(
taxReturnIdAndSubmissionId -> sb.append(" ").append(taxReturnIdAndSubmissionId.getTaxReturnId()));
log.info(sb.toString());
List<Pending> pendings = taxReturnIdAndSubmissionIds.stream()
.map(taxReturnIdAndSubmissionId -> {
Pending pending = new Pending();
pending.setSubmissionId(taxReturnIdAndSubmissionId.getSubmissionId());
pending.setPodId(statusProperties.getApplicationId());
return pending;
})
.toList();
List<TaxReturnSubmission> taxReturnSubmissions = taxReturnIdAndSubmissionIds.stream()
.map(taxReturnIdAndSubmissionId -> new TaxReturnSubmission(
taxReturnIdAndSubmissionId.getTaxReturnId(), taxReturnIdAndSubmissionId.getSubmissionId()))
.toList();
log.info(String.format("Saving %s pendings", pendings.size()));
pendingRepo.saveAll(pendings);
taxReturnSubmissionRepo.saveAll(taxReturnSubmissions);
}
}

View file

@ -1,17 +0,0 @@
package gov.irs.directfile.status.services.handlers.pending;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
@Service
@Slf4j
public class UnsupportedMessageVersionHandler implements PendingSubmissionHandler {
@Override
public void handlePendingSubmissionMessage(
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> payload) {
log.error("Unable to process Pending Submission Message. Headers: {} ", payload.getHeaders());
}
}

View file

@ -1,11 +0,0 @@
status:
status-endpoint-returns-pending-by-default-enabled: false
messageQueue:
sqs-message-handling-enabled: true
direct-file:
local-encryption:
local-wrapping-key: ${LOCAL_WRAPPING_KEY}
server:
port: 8082

View file

@ -1,19 +0,0 @@
spring:
datasource:
url: jdbc:postgresql://mef-apps-db:5432/directfile-status
status:
toolkit: /mef-client-sdk-src
status-endpoint-returns-pending-by-default-enabled: false
messageQueue:
endpoint: http://localstack:4566
sqs-message-handling-enabled: true
sns:
endpoint: http://localstack:4566
documentstore:
endpoint: http://localstack:4566/
# these defaults are to match the backend (update/remove once we're fully onto sqs)
direct-file:
local-encryption:
local-wrapping-key: ${LOCAL_WRAPPING_KEY}

View file

@ -1,98 +0,0 @@
spring:
datasource:
username: postgres
password: postgres
url: jdbc:postgresql://localhost:${MEF_APPS_DB_PORT:32768}/directfile-status
jpa:
hibernate:
ddl-auto: none
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
# Batching insert/update/delete
# https://docs.jboss.org/hibernate/orm/6.4/userguide/html_single/Hibernate_User_Guide.html#batch
jdbc:
batch_size: 30 # Hibernate docs recommend a value between 10 and 50
# Not turning on order_inserts or order_updates since we already generally save as a group
# Hibernate disables batching for entities having an identity PK, so tax_return_submission won't batch
liquibase:
change-log: classpath:db/changelog.yaml
url: ${spring.datasource.url}
user: ${spring.datasource.username}
password: ${spring.datasource.password}
hikari:
maximum-pool-size: 50
max-lifetime: 450000
minimum-idle: 30
connection-timeout: 15000
server:
shutdown: graceful
status:
# base64-encode the DER-formatted keystore and pass as `keystore-base64`
keystore-base64: MISSING
keystore-password: MISSING
keystore-alias: MISSING
application-id: ${POD_NAME:dfsys-mef-status-deployment-0}-${DF_AWS_REGION:us-gov-east-1}
etin: ${STATUS_ETIN:99999}
asid: 000
efin: 000
prod: false
unit-testing: false
root-translation-key: status
translation-key-splitter: .
ack-poll-in-milliseconds: 60000
# this field controls whether a Pending Response is returned from the /status endpoint when no submisionId is found.
# we will want to disable this in the future (likely when SQS communication is enabled).
status-endpoint-returns-pending-by-default-enabled: true
status-polling-enabled: true
messageQueue:
endpoint: http://localhost:4566
status-change-queue: status-change-queue
pending-submission-queue: pending-submission-queue
dlq-pending-submission-queue: dlq-pending-submission-queue
region: us-west-2
accessKey: accessKey
secretKey: secretKey
sqs-message-handling-enabled: false
status-change-publish-enabled: false
sns:
endpoint: http://localhost:4566
status-change-topic-arn: arn:aws:sns:us-west-2:000000000000:status-change-topic
region: us-west-2
accessKey: accessKey
secretKey: secretKey
status-change-publish-enabled: true
documentstore:
region: us-west-2
accessKey: accessKey
secretKey: secretKey
assume-role-arn: assume-role-arn-placeholder
assume-role-duration-seconds: 1800
assume-role-session-name: "df-status"
endpoint: http://localhost:4566/
bucket: direct-file-taxreturns
prefix:
aws:
enabled: false
default-credentials-provider-chain-enabled: false
access-key: accessKey
secret-key: secretKey
direct-file:
local-encryption:
local-wrapping-key: ${LOCAL_WRAPPING_KEY:-}
management:
endpoint:
health:
enabled: true
endpoints:
enabled-by-default: false
web:
discovery:
enabled: false
exposure:
include: health

View file

@ -1,32 +0,0 @@
@@@@
(..@ ( @ .@@@@ @@@@/*@#@@@. .@@(@&@@/&@ * * * .
. @ @@@@@@@@@ &@@@(@@@@@@ .@/@@@@@@./@@@*@(@@@. ,
. @& &@&/@%@@@ ,&&@@&#@@@@ &%@@&. (@@@@(@@#@@(&
@@&@@#%& /@@@&@(@@% @@@@@@@@( @@@@@ @/&@@ #@@@@&&@
@%@#%%@@@ @@@&&@@(&#%% . %&#(@& @@@@@( @@&@@
@@&%@@#@@ @@%%@@ / # . %##@@@ @&&@@ %@@&@ &%@#@
#&@%/@@%@ #&@ . @@@&@(#@&#@@@ @&@&#@@&&#(@&& #/@&@ ,@@@(#
@(@#(@@@ @& %@/*&( ,&%(@*@ #@%@@* @@@@(@*@@@( @@&@&
((, @ @@&@ @@@@@@ @&(&/@&&@@@@/ , @&&% @&(@& @@@%@@@@%/ %@@(@
,@@@ @@ #@@@@@#@@ @@&@. , .*@. @/@#@ .@@@@@@@@@ (#@*@ (/@@@
@@(@&/ @@%@&/@%@@* #@@@%@@/*#@&@@ @@#@@, @@/@@ @/# @@@@@,@@@@@
@*@(@@@% @@@(@@@@(@ . &@@@ @ @@(@@. @.&@&* *#@& &(@@@ #@@*@
@@@/@/@@*@@@#@@@@ /@@@ &@@@* %@&@#. @@&@,,&@@@& @&(@
@(@&@@@@@@@&@/ @ @ @@@@, * @@*/&%@@,@@( *@@/@@ @@@@@ @@@@
&@@@@%@&%@ % . (@@# *@&*@(@&@@@ * @@@&@ &&#&@ &@@@
@@@&@@@ &@@&@ ( &(@% * @#@#@@&%@ &@@@%, .@@@@@ #@@@
*@@% ##@%@@ * @#@% ( / #@%&@&@ (#@#%@ #@&@@@ %@@@@
@%& @ &@&@@@&@ @%@@&@@@&%@@%@&#@%@@%(&@ @@%@&@ (@@@@@% %#&&%# /#@@@@
@#@@@ #@@&#(@@&( @&@#&#@(&&&@@@%%(#@@&&#%%@ @ ( &#%&% @@%@@@ %@@%@
@@@(&&( @@&@@/@@@ @(@@@@@@ @@&@ @#@&@@@& &@%&/% &@@(@, @(*@*
(@#@@@@@@@@@@(@@ ,@&(@@.#@@&& &@@@ @#@@@@@@@& , @@@@( /@/@#( @@@@&
*@%@@@@@@&@&@@. @@#%@ @@@,@ ,@@#% @(%,@ . @@@@, @/,@(@ @@@@*
.(&&@/@%/@&@ @ @@@@@ @&@@@ (. @*@@ #@@@& ,(@@@, &@@@@# @@(@@
&*@@@@@@@@# &@@@# . &@@%@ @*@@( .@@*@@ *@@@@@ @*@@( @#@@@
/@@@&&@#& #@@&@%( @@@@* %@@@@# @@%@@ @@@@@& ,/@
@@@@, /@@*@( /(&&@, &@@@@ @ @@&@( , @@@@@ .
. &(@%@@@@&@@&&&@@@&*@@@@@@*@@&&@@&#/@@@@@@ @&@/ .
( &#@%#@#@%%# #@@@@#@&&#@ #
${application.title} (${application.version})
Spring Boot ${spring-boot.version}

View file

@ -1,7 +0,0 @@
databaseChangeLog:
- preConditions:
onFail: HALT
onError: HALT
- includeAll:
path: migrations/
relativeToChangelogFile: true

View file

@ -1,196 +0,0 @@
databaseChangeLog:
- changeSet:
id: 1704409188607-1
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: completed
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: completed_pkey
name: submission_id
type: VARCHAR(20)
- column:
name: status
type: VARCHAR(20)
- column:
name: tax_return_id
type: VARCHAR(255)
tableName: completed
- changeSet:
id: 1704409188607-2
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: completed-errors
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
name: submission_id
type: VARCHAR(20)
- column:
constraints:
nullable: false
name: mef_error_code
type: VARCHAR(25)
tableName: completed-errors
- changeSet:
id: 1704409188607-3
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: error
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: error_pkey
name: meferror_code
type: VARCHAR(25)
- column:
name: error_code_translation_key
type: VARCHAR(255)
- column:
name: meferror_category
type: VARCHAR(255)
- column:
name: meferror_message
type: BLOB
tableName: error
- changeSet:
id: 1704409188607-4
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: pending
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: pending_pkey
name: submission_id
type: VARCHAR(20)
- column:
name: error_message
type: VARCHAR(255)
- column:
name: status
type: VARCHAR(20)
tableName: pending
- changeSet:
id: 1704409188607-5
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: tax_return_identifier
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: tax_return_identifier_pkey
name: tax_return_id
type: UUID
- column:
name: submission_id
type: VARCHAR(20)
tableName: tax_return_identifier
- changeSet:
id: 1704409188607-6
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: toolkit_error
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: toolkit_error_pkey
name: submission_id
type: VARCHAR(255)
- column:
constraints:
nullable: false
name: error_message
type: VARCHAR(255)
- column:
constraints:
nullable: false
name: error_name
type: VARCHAR(255)
tableName: toolkit_error
- changeSet:
id: 1704409188607-7
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: completed
- tableExists:
- tableName: completed-errors
changes:
- addForeignKeyConstraint:
baseColumnNames: submission_id
baseTableName: completed-errors
constraintName: fkd6wcrt7yp7rypmyf365wlrely
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: submission_id
referencedTableName: completed
validate: true
- changeSet:
id: 1704409188607-8
author: irs-123 (generated)
preConditions:
- onFail: MARK_RAN
- not:
- tableExists:
- tableName: error
- tableExists:
- tableName: completed-errors
changes:
- addForeignKeyConstraint:
baseColumnNames: mef_error_code
baseTableName: completed-errors
constraintName: fkhq7kqsddr2k6uauh9gg47bfis
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: meferror_code
referencedTableName: error
validate: true

View file

@ -1,77 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-created_at
author: irs-123
comment: add createdAt columns for various tables
changes:
- addColumn:
tableName: pending
columns:
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
- addColumn:
tableName: completed
columns:
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
- addColumn:
tableName: toolkit_error
columns:
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
- addColumn:
tableName: error
columns:
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
- addColumn:
tableName: tax_return_identifier
columns:
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
rollback:
- dropColumn:
tableName: error
columns:
- column:
name: created_at
- dropColumn:
tableName: completed
columns:
- column:
name: created_at
- dropColumn:
tableName: pending
columns:
- column:
name: created_at
- dropColumn:
tableName: toolkit_error
columns:
- column:
name: created_at
- dropColumn:
tableName: tax_return_identifier
columns:
- column:
name: created_at

View file

@ -1,18 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-error-message
author: irs-123
comment: add error_message field to error table
changes:
- addColumn:
tableName: error
columns:
- column:
name: error_message
type: TEXT
rollback:
- dropColumn:
tableName: error
columns:
- column:
name: error_message

View file

@ -1,18 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-error-message
author: irs-123
comment: delete meferror_message field from error table
changes:
- dropColumn:
tableName: error
columns:
- column:
name: meferror_message
rollback:
- addColumn:
tableName: error
columns:
- column:
name: meferror_message
type: BLOB

View file

@ -1,31 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-taxreturn-submission-table
author: irs-123
comment: create tax_return_submission join table
changes:
- createTable:
columns:
- column:
name: id
type: serial
constraints:
nullable: false
primaryKey: true
primaryKeyName: tax_return_id_submission_id_pkey
- column:
name: tax_return_id
type: UUID
- column:
name: submission_id
type: VARCHAR(20)
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
tableName: tax_return_submission
rollback:
- dropTable:
tableName: tax_return_submission

View file

@ -1,40 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-indices-status-app
author: irs-123
comment: add indices to status app
changes:
- createIndex:
indexName: completed_tax_return_id_idx
tableName: completed
columns:
- column:
name: tax_return_id
- createIndex:
indexName: tax_return_submission_tax_return_id_idx
tableName: tax_return_submission
columns:
- column:
name: tax_return_id
- createIndex:
indexName: completed_errors_submission_id_idx
tableName: completed-errors
columns:
- column:
name: submission_id
- createIndex:
indexName: completed_errors_mef_error_code_idx
tableName: completed-errors
columns:
- column:
name: mef_error_code
rollback:
- dropIndex:
indexName: completed_tax_return_id_idx
- dropIndex:
indexName: tax_return_submission_tax_return_id_idx
- dropIndex:
indexName: completed_error_submission_id_idx
- dropIndex:
indexName: completed_error_mef_error_code_idx

View file

@ -1,36 +0,0 @@
databaseChangeLog:
- changeSet:
id: fk-completed-error
author: irs-123
comment: add foreign key constraint on m2m join between completed and error tables
changes:
changes:
- addForeignKeyConstraint:
baseColumnNames: submission_id
baseTableName: completed-errors
constraintName: completed_error_submission_id_fk
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: submission_id
referencedTableName: completed
validate: true
- addForeignKeyConstraint:
baseColumnNames: mef_error_code
baseTableName: completed-errors
constraintName: completed_error_mef_error_code_fk
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: meferror_code
referencedTableName: error
validate: true
rollback:
- dropForeignKeyConstraint:
baseTableName: completed-errors
constraintName: completed_error_submission_id_fk
- dropForeignKeyConstraint:
baseTableName: completed-errors
constraintName: completed_error_mef_error_code_fk

View file

@ -1,63 +0,0 @@
databaseChangeLog:
- changeSet:
id: drop-unused-columns-from-completed-and-pending-tables-and-drop-tax-return-identifier-table
author: irs-123
comment: drop tax_return_id column from completed, drop error_message and status columns from pending, drop tax_return_identifier table
changes:
- dropIndex:
indexName: completed_tax_return_id_idx
- dropColumn:
tableName: completed
columns:
- column:
name: tax_return_id
- dropColumn:
tableName: pending
columns:
- column:
name: error_message
- column:
name: status
- dropTable:
tableName: tax_return_identifier
rollback:
- addColumn:
tableName: completed
columns:
- column:
name: tax_return_id
type: VARCHAR(255)
- createIndex:
indexName: completed_tax_return_id_idx
tableName: completed
columns:
- column:
name: tax_return_id
- addColumn:
tableName: pending
columns:
- column:
name: error_message
type: TEXT
- column:
name: status
type: TEXT
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: tax_return_identifier_pkey
name: tax_return_id
type: UUID
- column:
name: submission_id
type: VARCHAR(20)
- column:
name: created_at
type: TIMESTAMP WITHOUT TIME ZONE
defaultValueComputed: CURRENT_TIMESTAMP
constraints:
nullable: false
tableName: tax_return_identifier

View file

@ -1,29 +0,0 @@
databaseChangeLog:
- changeSet:
id: pod-identifier-table
author: 123 (generated)
comment: create pod_identifier table
changes:
- createTable:
columns:
- column:
name: id
type: serial
constraints:
nullable: false
primaryKey: true
primaryKeyName: pod_identifier_id_pkey
- column:
name: asid
type: VARCHAR(50)
- column:
name: region
type: VARCHAR(50)
- column:
name: pod_id
type: VARCHAR(255)
tableName: pod_identifier
rollback:
- dropTable:
tableName: pod_identifier

View file

@ -1,18 +0,0 @@
databaseChangeLog:
- changeSet:
id: add-pod-id-column-pending
author: 123 (generated)
comment: add pod_id column to pending table
changes:
- addColumn:
tableName: pending
columns:
- column:
name: pod_id
type: VARCHAR(255)
rollback:
- dropColumn:
tableName: pending
columns:
- column:
name: pod_id

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration>
<appender name="ConsoleJSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<fieldNames>
<timestamp>timestamp</timestamp>
<thread>[ignore]</thread>
<version>[ignore]</version>
<levelValue>[ignore]</levelValue>
</fieldNames>
<timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</timestampPattern>
<timeZone>UTC</timeZone>
<customFields>{"system":"DIRECTFILE","eventType":"STATUS","version":"${GIT_COMMIT_HASH}"}}</customFields>
<throwableConverter class="gov.irs.directfile.audit.NoMessageStackTraceConverter" />
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="ConsoleJSON" />
</root>
</configuration>

View file

@ -1,423 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
import ch.qos.logback.classic.Level;
import jakarta.persistence.EntityNotFoundException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import gov.irs.directfile.audit.AuditLogElement;
import gov.irs.directfile.audit.AuditService;
import gov.irs.directfile.audit.events.*;
import gov.irs.directfile.models.RejectedStatus;
import gov.irs.directfile.status.acknowledgement.domain.AcknowledgementStatus;
import gov.irs.directfile.status.acknowledgement.domain.Status;
import gov.irs.directfile.status.config.SnsClientTestConfiguration;
import gov.irs.directfile.status.extension.LoggerExtension;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {"status.xml-headers-to-be-removed="})
@Import(SnsClientTestConfiguration.class)
class AcknowledgementControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AcknowledgementService acknowledgementService;
@RegisterExtension
public static LoggerExtension logVerifier = new LoggerExtension(Level.INFO, AuditService.class.getName());
private static String X_FORWARDED_FOR = "X-Forwarded-For";
private static final String TEST_IP_ADDR1 = "10.1.2.1";
private static final String TEST_IP_ADDR2 = "10.1.2.2";
private static final String REMOTE_IP_ADDR = "10.1.2.3";
@Test
public void status_whenAcknowledgementServiceReturnsPendingStatus_endpointReturnsOkResponse() throws Exception {
AcknowledgementStatus acknowledgementStatus =
new AcknowledgementStatus(Status.Pending, "", List.of(), new Date());
UUID taxReturnId = UUID.randomUUID();
when(acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(eq(taxReturnId)))
.thenReturn("1234567890");
when(acknowledgementService.GetAcknowledgement(eq(taxReturnId), eq("1234567890")))
.thenReturn(acknowledgementStatus);
this.mockMvc
.perform(get("/status")
.with(r -> {
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("id", taxReturnId.toString()))
.andExpect(status().isOk());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.SUCCESS)
.mefSubmissionId("1234567890")
.build(),
Map.of(
AuditLogElement.taxReturnId,
taxReturnId.toString(),
AuditLogElement.responseStatusCode,
HttpStatus.OK.value(),
AuditLogElement.remoteAddress,
REMOTE_IP_ADDR,
AuditLogElement.cyberOnly,
true));
}
@Test
public void status_whenAcknowledgementServiceReturnsNull_endpointReturnsNotFoundResponse() throws Exception {
UUID taxReturnId = UUID.randomUUID();
when(acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(eq(taxReturnId)))
.thenReturn("1234567890");
when(acknowledgementService.GetAcknowledgement(eq(taxReturnId), eq("1234567890")))
.thenReturn(null);
this.mockMvc
.perform(get("/status")
.with(r -> {
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("id", taxReturnId.toString()))
.andExpect(status().isNotFound());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.FAILURE)
.build(),
Map.of(
AuditLogElement.taxReturnId,
taxReturnId.toString(),
AuditLogElement.responseStatusCode,
HttpStatus.NOT_FOUND.value(),
AuditLogElement.remoteAddress,
REMOTE_IP_ADDR,
AuditLogElement.cyberOnly,
true));
}
@Test
public void status_whenAcknowledgementServiceThrowsException_endpointReturnsBadRequestResponse() throws Exception {
UUID taxReturnId = UUID.randomUUID();
when(acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(eq(taxReturnId)))
.thenReturn("1234567890");
when(acknowledgementService.GetAcknowledgement(eq(taxReturnId), eq("1234567890")))
.thenThrow(new NullPointerException("Error occurred"));
this.mockMvc
.perform(get("/status")
.with(r -> {
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("id", taxReturnId.toString()))
.andExpect(status().isBadRequest());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{errorMessage=Error occurred}")
.eventErrorMessage("java.lang.NullPointerException")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.FAILURE)
.build(),
Map.of(
AuditLogElement.taxReturnId,
taxReturnId.toString(),
AuditLogElement.responseStatusCode,
HttpStatus.BAD_REQUEST.value(),
AuditLogElement.remoteAddress,
REMOTE_IP_ADDR,
AuditLogElement.cyberOnly,
true));
}
@Test
public void status_endpointHitWithInvalidUUID_endpointReturnsBadRequestResponse() throws Exception {
String invalidUUIDString = "12323123231321";
this.mockMvc.perform(get("/status").param("id", invalidUUIDString)).andExpect(status().isBadRequest());
}
@Test
public void testRemoteIpAddress_OneIpInX_Forwarded_For() throws Exception {
AcknowledgementStatus acknowledgementStatus =
new AcknowledgementStatus(Status.Pending, "", List.of(), new Date());
UUID taxReturnId = UUID.randomUUID();
when(acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(eq(taxReturnId)))
.thenReturn("1234567890");
when(acknowledgementService.GetAcknowledgement(eq(taxReturnId), eq("1234567890")))
.thenReturn(acknowledgementStatus);
// 1 ip address in x-forwarded-for
this.mockMvc
.perform(get("/status")
.with(r -> {
r.addHeader(X_FORWARDED_FOR, TEST_IP_ADDR1);
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("id", taxReturnId.toString()))
.andExpect(status().isOk());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.SUCCESS)
.mefSubmissionId("1234567890")
.build(),
Map.of(
AuditLogElement.taxReturnId,
taxReturnId.toString(),
AuditLogElement.responseStatusCode,
HttpStatus.OK.value(),
AuditLogElement.remoteAddress,
TEST_IP_ADDR1,
AuditLogElement.cyberOnly,
true));
}
@Test
public void testRemoteIpAddress_TwoIpInX_Forwarded_For() throws Exception {
AcknowledgementStatus acknowledgementStatus =
new AcknowledgementStatus(Status.Pending, "", List.of(), new Date());
UUID taxReturnId = UUID.randomUUID();
when(acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(eq(taxReturnId)))
.thenReturn("1234567890");
when(acknowledgementService.GetAcknowledgement(eq(taxReturnId), eq("1234567890")))
.thenReturn(acknowledgementStatus);
// 2 ip addresses in x-forwarded-for
this.mockMvc
.perform(get("/status")
.with(r -> {
r.addHeader(X_FORWARDED_FOR, TEST_IP_ADDR2 + "," + TEST_IP_ADDR1);
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("id", taxReturnId.toString()))
.andExpect(status().isOk());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.SUCCESS)
.mefSubmissionId("1234567890")
.build(),
Map.of(
AuditLogElement.taxReturnId,
taxReturnId.toString(),
AuditLogElement.responseStatusCode,
HttpStatus.OK.value(),
AuditLogElement.remoteAddress,
TEST_IP_ADDR2,
AuditLogElement.cyberOnly,
true));
}
@Test
public void rejectionCodes_whenAcknowledgementServiceReturnsRejectionCodes_endpointReturnsOkResponse()
throws Exception {
List<RejectedStatus> rejectionCodes = List.of(
new RejectedStatus("code1", "key1", "description1"),
new RejectedStatus("code2", "key2", "description2"));
String submissionId = UUID.randomUUID().toString();
when(acknowledgementService.getRejectionCodesForSubmissionId(submissionId))
.thenReturn(rejectionCodes);
this.mockMvc
.perform(get("/status/rejection-codes")
.with(r -> {
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("submissionId", submissionId))
.andExpect(status().isOk());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.SUCCESS)
.mefSubmissionId(submissionId)
.build(),
Map.of(
AuditLogElement.mefSubmissionId,
submissionId,
AuditLogElement.responseStatusCode,
HttpStatus.OK.value(),
AuditLogElement.remoteAddress,
REMOTE_IP_ADDR,
AuditLogElement.cyberOnly,
true));
}
@Test
public void rejectionCodes_whenAcknowledgementServiceThrowsNotFoundException_endpointReturnsNotFoundResponse()
throws Exception {
String submissionId = UUID.randomUUID().toString();
when(acknowledgementService.getRejectionCodesForSubmissionId(submissionId))
.thenThrow(EntityNotFoundException.class);
this.mockMvc
.perform(get("/status/rejection-codes")
.with(r -> {
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("submissionId", submissionId))
.andExpect(status().isNotFound());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.FAILURE)
.mefSubmissionId(submissionId)
.build(),
Map.of(
AuditLogElement.mefSubmissionId,
submissionId,
AuditLogElement.responseStatusCode,
HttpStatus.NOT_FOUND.value(),
AuditLogElement.remoteAddress,
REMOTE_IP_ADDR,
AuditLogElement.cyberOnly,
true));
}
@Test
public void rejectionCodes_whenAcknowledgementServiceThrowsException_endpointReturnsBadRequestResponse()
throws Exception {
String submissionId = UUID.randomUUID().toString();
when(acknowledgementService.getRejectionCodesForSubmissionId(submissionId))
.thenThrow(new NullPointerException("Error occurred"));
this.mockMvc
.perform(get("/status/rejection-codes")
.with(r -> {
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("submissionId", submissionId))
.andExpect(status().isBadRequest());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{errorMessage=Error occurred}")
.eventErrorMessage("java.lang.NullPointerException")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.FAILURE)
.mefSubmissionId(submissionId)
.build(),
Map.of(
AuditLogElement.mefSubmissionId,
submissionId,
AuditLogElement.responseStatusCode,
HttpStatus.BAD_REQUEST.value(),
AuditLogElement.remoteAddress,
REMOTE_IP_ADDR,
AuditLogElement.cyberOnly,
true));
}
private static Stream<Arguments> getRejectionCodesXForwardedForParameters() {
return Stream.of(
Arguments.of(TEST_IP_ADDR1, TEST_IP_ADDR1),
Arguments.of(TEST_IP_ADDR2 + "," + TEST_IP_ADDR1, TEST_IP_ADDR2));
}
@ParameterizedTest
@MethodSource("getRejectionCodesXForwardedForParameters")
public void rejectionCodes_whenXForwardedFor_endpointReturnsOkResponse(
String xForwardedForValue, String expectedRemoteAddress) throws Exception {
List<RejectedStatus> rejectionCodes = List.of(
new RejectedStatus("code1", "key1", "description1"),
new RejectedStatus("code2", "key2", "description2"));
String submissionId = UUID.randomUUID().toString();
when(acknowledgementService.getRejectionCodesForSubmissionId(submissionId))
.thenReturn(rejectionCodes);
this.mockMvc
.perform(get("/status/rejection-codes")
.with(r -> {
r.addHeader(X_FORWARDED_FOR, xForwardedForValue);
r.setRemoteAddr(REMOTE_IP_ADDR);
return r;
})
.param("submissionId", submissionId))
.andExpect(status().isOk());
// verify audit log message
logVerifier.verifyLogEvent(
Event.builder()
.detail("{}")
.eventId(EventId.CHECK)
.eventPrincipal(new SystemEventPrincipal())
.eventStatus(EventStatus.SUCCESS)
.mefSubmissionId(submissionId)
.build(),
Map.of(
AuditLogElement.mefSubmissionId,
submissionId,
AuditLogElement.responseStatusCode,
HttpStatus.OK.value(),
AuditLogElement.remoteAddress,
expectedRemoteAddress,
AuditLogElement.cyberOnly,
true));
}
}

View file

@ -1,859 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import jakarta.persistence.EntityNotFoundException;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import gov.irs.a2a.mef.mefheader.TestCdType;
import gov.irs.mef.exception.ServiceException;
import gov.irs.mef.exception.ToolkitException;
import gov.irs.mef.services.ServiceContext;
import gov.irs.mef.services.transmitter.mtom.GetAcksResult;
import gov.irs.directfile.models.RejectedStatus;
import gov.irs.directfile.status.acknowledgement.domain.Status;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.*;
import gov.irs.directfile.status.domain.Error;
import gov.irs.directfile.status.error.ErrorRepository;
import gov.irs.directfile.status.error.ToolkitErrorRepository;
import gov.irs.directfile.status.mef.client.MeFAcksMTOMClientService;
import gov.irs.directfile.status.mef.client.MeFLoginClientService;
import gov.irs.directfile.status.mef.client.MeFLogoutClientService;
import gov.irs.directfile.status.mef.client.ServiceContextWrapper;
import gov.irs.directfile.status.repository.PodIdentifierRepository;
import gov.irs.directfile.status.services.StatusChangeMessageService;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.*;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@EnableConfigurationProperties(StatusProperties.class)
@ImportAutoConfiguration(classes = SecurityAutoConfiguration.class)
@DataJpaTest
@ExtendWith(MockitoExtension.class)
class AcknowledgementServiceTest {
@BeforeEach
void setUp() throws ServiceException, ToolkitException {
when(mockLoginClientService.loginWithDefaultAsid()).thenReturn(new ServiceContextWrapper(null));
acknowledgementService = new AcknowledgementService(
completedRepo,
pendingRepo,
taxReturnSubmissionRepository,
errorRepository,
toolkitErrorRepo,
statusProperties,
statusChangeMessageService,
mockGetAcksClientService,
mockLoginClientService,
mockLogoutClientService,
podIdentifierRepository);
}
@BeforeAll
public static void setupSystemProperties() {
String userDirectory = System.getProperty("user.dir");
System.setProperty("A2A_TOOLKIT_HOME", userDirectory + "/src/test/resources/");
}
@AfterAll
public static void cleanupSystemProperties() {
System.clearProperty("A2A_TOOLKIT_HOME");
}
String ASID = "asid1235";
@Autowired
CompletedAcknowledgementRepository completedRepo;
@Autowired
ErrorRepository errorRepository;
@Autowired
PendingAcknowledgementRepository pendingRepo;
@Autowired
TaxReturnSubmissionRepository taxReturnSubmissionRepository;
@Autowired
ToolkitErrorRepository toolkitErrorRepo;
@Autowired
PodIdentifierRepository podIdentifierRepository;
@Autowired
StatusProperties statusProperties;
@MockBean
private StatusChangeMessageService statusChangeMessageService;
@MockBean
MeFAcksMTOMClientService mockGetAcksClientService;
@MockBean
MeFLoginClientService mockLoginClientService;
@MockBean
MeFLogoutClientService mockLogoutClientService;
AcknowledgementService acknowledgementService;
public PodIdentifier createPodIdentifer(String region, String asid, int index) {
PodIdentifier p = new PodIdentifier();
p.setAsid(asid);
p.setRegion(region);
p.setPodId("dfsys-mef-status-deployment-" + index + "-" + region);
return p;
}
@Test
void GetAcknowledgementWithACompleted() {
String submissionId = "1234562023021500001";
UUID taxReturnId = UUID.randomUUID();
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, submissionId));
Completed c = new Completed();
c.setSubmissionId(submissionId);
c.setStatus("Accepted");
completedRepo.save(c);
var accepted = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Accepted, accepted.getStatus());
}
@Test
void GetAcknowledgementWithARejection() {
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation.reject.XML-123-4567-006");
e.setMefErrorCategory("Reject and Stop");
c.setErrors(List.of(e));
errorRepository.save(e);
completedRepo.save(c);
String submissionId = "12345620230215000001";
UUID taxReturnId = UUID.randomUUID();
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, submissionId));
var rejected = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Rejected, rejected.getStatus());
assertEquals(1, rejected.getRejectionCodes().size());
assertEquals("XML-123-4567-006", rejected.getRejectionCodes().getFirst().MeFErrorCode);
assertEquals("You messed up!", rejected.getRejectionCodes().getFirst().MeFDescription);
}
@Test
void GetAcknowledgementWithMultipleRejections() {
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject");
Error e2 = new Error();
e2.setMefErrorCode("REJC-00001");
e2.setErrorMessage("This was a huge problem");
e2.setErrorCodeTranslationKey("translation/reject/REJC-00001");
e2.setMefErrorCategory("Reject");
c.setErrors(List.of(e, e2));
errorRepository.saveAll(List.of(e, e2));
completedRepo.save(c);
String submissionId = "12345620230215000001";
UUID taxReturnId = UUID.randomUUID();
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, submissionId));
var rejected = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Rejected, rejected.getStatus());
assertEquals(2, rejected.getRejectionCodes().size());
assertEquals("XML-123-4567-006", rejected.getRejectionCodes().get(0).MeFErrorCode);
assertEquals("You messed up!", rejected.getRejectionCodes().get(0).MeFDescription);
assertEquals("REJC-00001", rejected.getRejectionCodes().get(1).MeFErrorCode);
assertEquals("This was a huge problem", rejected.getRejectionCodes().get(1).MeFDescription);
}
@Test
@Disabled("This test has been flaking")
void GetAcknowledgementWithRejectionAndAcceptedReturnsLatestCompleted() {
UUID taxReturnId = UUID.randomUUID();
String submissionId = "1234562023021500001";
Completed c = new Completed();
c.setSubmissionId(submissionId);
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation.reject.XML-123-4567-006");
e.setMefErrorCategory("Reject and Stop");
c.setErrors(List.of(e));
errorRepository.save(e);
completedRepo.save(c);
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, submissionId));
submissionId = "1234562023021500002";
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, submissionId));
Completed c1 = new Completed();
c1.setSubmissionId(submissionId);
c1.setStatus("Accepted");
completedRepo.save(c1);
var accepted = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Accepted, accepted.getStatus());
}
@Test
void
GetAcknowledgementWithNoTaxReturnSubmissionReturnsPendingIfStatusEndpointReturnsPendingByDefaultEnabledIsTrue() {
UUID taxReturnId = UUID.randomUUID();
String submissionId = "1234562023021500001";
Completed c = new Completed();
c.setSubmissionId(submissionId);
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation.reject.XML-123-4567-006");
e.setMefErrorCategory("Reject and Stop");
c.setErrors(List.of(e));
errorRepository.save(e);
completedRepo.save(c);
var result = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Pending, result.getStatus());
}
@Test
void GetAcknowledgementWithPending() {
UUID taxReturnId = UUID.randomUUID();
Pending p = new Pending();
p.setSubmissionId("12345620230215000001");
pendingRepo.save(p);
var pending = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Pending, pending.getStatus());
}
@Test
void
GetAcknowledgementWithNoPreviousLookup_ifCreatePendingUponLookupIsTrue_ReturnsPendingStatusButDoesNotSaveObjectInDB() {
UUID taxReturnId = UUID.fromString("e111c2c4-5de7-465d-8a00-b0e80caeedbc");
var nothing = acknowledgementService.GetAcknowledgement(taxReturnId);
assertEquals(Status.Pending, nothing.getStatus());
}
List<Set<String>> createBatchSubmissionIds(List<Pending> argument)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = AcknowledgementService.class.getDeclaredMethod("batchPendings", Iterable.class);
method.setAccessible(true);
return (List<Set<String>>) method.invoke(acknowledgementService, argument);
}
@Test
void batchSubmissionIdsReturnsASingleBatchForOneSubmissionId() {
try {
Pending p = new Pending();
p.setSubmissionId("12345620230215000001");
var test = createBatchSubmissionIds(List.of(p));
assertEquals(1, test.size());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Test
void batchSubmissionIdsReturns10BatchesFor1000Ids() {
try {
List<Pending> pendings = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Pending p = new Pending();
p.setSubmissionId("12345620230215000" + i);
pendings.add(p);
}
var test = createBatchSubmissionIds(pendings);
assertEquals(10, test.size());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Test
void batchSubmissionIdsReturns2BatchesFor101Ids() {
try {
List<Pending> pendings = new ArrayList<>();
for (int i = 0; i < 101; i++) {
Pending p = new Pending();
p.setSubmissionId("12345620230215000" + i);
pendings.add(p);
}
var test = createBatchSubmissionIds(pendings);
assertEquals(2, test.size());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Test
void batchSubmissionIdsReturns1BatchFor0() {
try {
List<Pending> pendings = new ArrayList<>();
var test = createBatchSubmissionIds(pendings);
assertEquals(1, test.size());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Test
void CreateToolkitErrorDeletesPendingRecordForSubmissionId() {
String submissionId = "12345678900987654321";
Pending p = new Pending();
p.setSubmissionId(submissionId);
pendingRepo.save(p);
ToolkitException tke = new ToolkitException("invalid request parameters");
acknowledgementService.createToolkitError(p, tke);
Optional<Pending> deletedPendingRecord = pendingRepo.findById(submissionId);
assertEquals(Optional.empty(), deletedPendingRecord);
}
@Test
void CreateToolkitErrorCreatesTheRecordWithTheSubmissionIdAndError() {
String submissionId = "21423423423424423234";
Pending p = new Pending();
p.setSubmissionId(submissionId);
p.setPodId(getDefaultPodIdentifierFromProperties(0, "us-gov-east-1"));
pendingRepo.save(p);
ToolkitException tke = new ToolkitException("invalid request parameters");
acknowledgementService.createToolkitError(p, tke);
Optional<ToolkitError> toolkitErrorRecord = toolkitErrorRepo.findById(submissionId);
assertEquals(submissionId, toolkitErrorRecord.get().getSubmissionId());
assertEquals(
"gov.irs.mef.exception.ToolkitException",
toolkitErrorRecord.get().getErrorName());
assertEquals(
"gov.irs.mef.exception.ToolkitException: invalid request parameters",
toolkitErrorRecord.get().getErrorMessage());
}
@Test
@Disabled
void lookupSubmissionsAddsToolkitErrorRecordsOnToolkitExceptions() throws ServiceException, ToolkitException {
MockitoAnnotations.openMocks(this);
String meFClientErrorString = "MeFClientSDK000039: Invalid request parameters";
pendingRepo.save(new Pending("12345"));
ToolkitException tke = new ToolkitException(meFClientErrorString);
when(mockGetAcksClientService.getAcks(isNull(), anySet())).thenThrow(tke);
acknowledgementService.LookupSubmissions();
Optional<Pending> pendingRecord = pendingRepo.findById("12345");
Optional<ToolkitError> toolkitError = toolkitErrorRepo.findById("12345");
assertEquals(Optional.empty(), pendingRecord);
assertEquals(
"gov.irs.mef.exception.ToolkitException", toolkitError.get().getErrorName());
assertEquals(
"gov.irs.mef.exception.ToolkitException: " + meFClientErrorString,
toolkitError.get().getErrorMessage());
}
@Test
@Disabled
void lookupSubmissionsHandlesToolkitExceptions() throws ServiceException, ToolkitException {
MockitoAnnotations.openMocks(this);
for (int i = 0; i < 100; i++) {
String genString = RandomStringUtils.random(20, true, true);
Pending p = new Pending(genString);
pendingRepo.save(p);
}
assertEquals(100, pendingRepo.count());
ToolkitException tke = new ToolkitException("Invalid request parameters");
GetAcksResult acksResult = mock(GetAcksResult.class);
// We do this to mimic the expected behavior of the MeF client SDK on batch calls that have an issue with one
// piece of data in the batch.
// throw ToolkitException 7 times on a batch of size 100
when(mockGetAcksClientService.getAcks(isNull(), anySet()))
.thenThrow(tke)
.thenThrow(tke)
.thenThrow(tke)
.thenThrow(tke)
.thenThrow(tke)
.thenThrow(tke)
.thenThrow(tke)
.thenThrow(tke)
.thenReturn(new GetAcksResultWrapper(new AcknowledgementsListWrapper(new ArrayList<>())));
acknowledgementService.LookupSubmissions();
// This is to test the expected binary search pattern
verify(mockGetAcksClientService, times(15)).getAcks(isNull(), anySet());
// since we threw ToolkitException 7 times in a batch of size 100, we only expect one pending record to be
// deleted and then created as a toolkit error record in the database,
// because we use binary search pattern to divide the set until we find the single cause of the exception (100/2
// 50/2 25/2 13/2 7/2 3/2 2/2 1/1)
assertEquals(1, toolkitErrorRepo.count());
assertEquals(99, pendingRepo.count());
}
private String getDefaultPodIdentifierFromProperties(int index, String region) {
return "dfsys-mef-status-deployment-" + index + "-" + region;
}
Map<String, List<String>> getStatusSubmissionIdMap() {
PodIdentifier pi = createPodIdentifer("us-gov-east-1", statusProperties.getAsid(), 0);
podIdentifierRepository.save(pi);
Map<String, List<String>> statusSubmissionIdMap = acknowledgementService.createStatusSubmissionIdMap();
List<String> statuses = new ArrayList<>();
statuses.add("accepted");
statuses.add("rejected");
statuses.add("pending");
statuses.forEach(status -> {
String submissionId = UUID.randomUUID().toString().substring(0, 20);
if (Objects.equals(status, "pending")) {
Pending p = new Pending();
p.setSubmissionId(submissionId);
p.setPodId(pi.getPodId());
pendingRepo.save(p);
}
acknowledgementService.addToSubmissionIdMap(status, submissionId, statusSubmissionIdMap);
});
return statusSubmissionIdMap;
}
private ValidationMapCompleted getValidationErrorMap() {
Map<String, List<String>> statusSubmissionIdMap = getStatusSubmissionIdMap();
Map<String, List<List<String>>> validationErrorMap = new HashMap<>();
statusSubmissionIdMap.forEach((status, submissionIdList) -> {
if (status.equals("rejected")) {
String submissionId = submissionIdList.get(0);
validationErrorMap.put(submissionId, new ArrayList<>());
List<String> errorList1 = new ArrayList<>();
List<String> errorList2 = new ArrayList<>();
String ruleNum = "R0000-904-03";
String severityCd = "Reject and Stop";
String errorMessageTxt =
"Software ID in the Return Header must have passed testing for the form family and TaxYr.";
errorList1.add(ruleNum);
errorList1.add(severityCd);
errorList1.add(errorMessageTxt);
validationErrorMap.get(submissionId).add(errorList1);
ruleNum = "F1040-525-03";
severityCd = "Reject and Stop";
errorMessageTxt =
"If 'PINTypeCd' in the Return Header has the value \"Self-Select On-Line\" and the filing status of the return is married filing jointly and Form 1040, [ 'SpecialProcessingLiteralCd' and 'CombatZoneCd' and 'SpecialProcessingCodeTxt' and 'PrimaryDeathDt' ] do not have values, then 'PrimaryBirthDt' in the Return Header must match the e-File database.";
errorList2.add(ruleNum);
errorList2.add(severityCd);
errorList2.add(errorMessageTxt);
validationErrorMap.get(submissionId).add(errorList2);
}
});
Iterable<Completed> completeds = acknowledgementService.createNewCompleteds(statusSubmissionIdMap);
return new ValidationMapCompleted(validationErrorMap, completeds);
}
@Test
void stripPendingAcknowledgementsFromStatusSubmissionIdMap() {
Map<String, List<String>> statusSubmissionIdMap = getStatusSubmissionIdMap();
Map<String, List<String>> newMap =
acknowledgementService.stripPendingAcknowledgementsFromStatusSubmissionIdMap(statusSubmissionIdMap);
assertNull(newMap.get("pending"));
assertEquals(newMap.get("rejected").size(), 1);
assertEquals(newMap.get("accepted").size(), 1);
}
@Test
void addToSubmissionIdMapErrorDoesNotMutateSubmissionIdMap() {
Map<String, List<String>> statusSubmissionIdMap = getStatusSubmissionIdMap();
String status = "error";
String submissionId = UUID.randomUUID().toString().substring(0, 20);
acknowledgementService.addToSubmissionIdMap(status, submissionId, statusSubmissionIdMap);
assertNull(statusSubmissionIdMap.get("error"));
assertEquals(statusSubmissionIdMap.get("rejected").size(), 1);
assertEquals(statusSubmissionIdMap.get("accepted").size(), 1);
assertEquals(statusSubmissionIdMap.get("pending").size(), 1);
}
@Test
void createNewCompletedsCreatesExpectedEntities() {
assertEquals(acknowledgementService.getAllPending().spliterator().getExactSizeIfKnown(), 0);
assertEquals(acknowledgementService.getAllCompleted().spliterator().getExactSizeIfKnown(), 0);
Map<String, List<String>> statusSubmssionIdMap = getStatusSubmissionIdMap();
Iterable<Completed> completeds = acknowledgementService.createNewCompleteds(statusSubmssionIdMap);
completedRepo.saveAll(completeds);
// one pending is created, two completed (rejected and accepted)
assertEquals(acknowledgementService.getAllPending().spliterator().getExactSizeIfKnown(), 1);
assertEquals(acknowledgementService.getAllCompleted().spliterator().getExactSizeIfKnown(), 2);
}
@Test
void bulkUpdateEntitiesWithNewErrors() {
assertEquals(acknowledgementService.getAllPending().spliterator().getExactSizeIfKnown(), 0);
assertEquals(acknowledgementService.getAllError().spliterator().getExactSizeIfKnown(), 0);
assertEquals(acknowledgementService.getAllCompleted().spliterator().getExactSizeIfKnown(), 0);
ValidationMapCompleted validationMapCompleted = getValidationErrorMap();
Map<String, List<List<String>>> validationErrorMap = validationMapCompleted.validationErrorMap;
Iterable<Completed> c = validationMapCompleted.completeds;
Map<String, Completed> cmap = new HashMap<>();
Map<String, Pending> pmap = new HashMap<>();
c.forEach(completed -> cmap.put(completed.getSubmissionId(), completed));
String podId = getDefaultPodIdentifierFromProperties(0, "us-gov-east-1");
Iterable<Pending> p = pendingRepo.findAllByPodId(podId);
p.forEach(pending -> pmap.put(pending.getSubmissionId(), pending));
acknowledgementService.bulkUpdateEntities(validationErrorMap, cmap, pmap);
String rejectedSubmissionId =
validationErrorMap.entrySet().iterator().next().getKey();
Completed rejectedAck = acknowledgementService
.getCompletedBySubmissionId(rejectedSubmissionId)
.get();
Pending pending = acknowledgementService.getAllPending().iterator().next();
Iterator<Completed> completeds =
acknowledgementService.getAllCompleted().iterator();
assertEquals(acknowledgementService.getAllCompleted().spliterator().getExactSizeIfKnown(), 2);
assertEquals(acknowledgementService.getAllError().spliterator().getExactSizeIfKnown(), 2);
assertEquals(rejectedAck.getSubmissionId(), rejectedSubmissionId);
assertNotNull(rejectedAck.getErrors());
// the pending object that corresponded to the rejected completed is deleted, the one that remains
// is still pending from the standpoint of MeF so it was not deleted
assertEquals(acknowledgementService.getAllPending().spliterator().getExactSizeIfKnown(), 1);
completeds.forEachRemaining(completed -> {
assertNotEquals(pending.getSubmissionId(), completed.getSubmissionId());
});
}
@Test
void bulkUpdateEntitiesWithPreexistingErrors() {
ValidationMapCompleted validationMapCompleted = getValidationErrorMap();
Map<String, List<List<String>>> validationErrorMap1 = validationMapCompleted.validationErrorMap;
Iterable<Completed> c = validationMapCompleted.completeds;
Map<String, Completed> cmap = new HashMap<>();
Map<String, Pending> pmap = new HashMap<>();
c.forEach(completed -> cmap.put(completed.getSubmissionId(), completed));
String podId = getDefaultPodIdentifierFromProperties(0, "us-gov-east-1");
Iterable<Pending> p = pendingRepo.findAllByPodId(podId);
p.forEach(pending -> pmap.put(pending.getSubmissionId(), pending));
acknowledgementService.bulkUpdateEntities(validationErrorMap1, cmap, pmap);
String rejectedSubmissionId =
validationErrorMap1.entrySet().iterator().next().getKey();
Completed rejectedAck = acknowledgementService
.getCompletedBySubmissionId(rejectedSubmissionId)
.get();
Pending pending = acknowledgementService.getAllPending().iterator().next();
Iterator<Completed> completeds =
acknowledgementService.getAllCompleted().iterator();
assertEquals(acknowledgementService.getAllCompleted().spliterator().getExactSizeIfKnown(), 2);
assertEquals(acknowledgementService.getAllError().spliterator().getExactSizeIfKnown(), 2);
assertEquals(rejectedAck.getSubmissionId(), rejectedSubmissionId);
assertNotNull(rejectedAck.getErrors());
assertEquals(acknowledgementService.getAllPending().spliterator().getExactSizeIfKnown(), 1);
completeds.forEachRemaining(completed -> {
assertNotEquals(pending.getSubmissionId(), completed.getSubmissionId());
});
// mimic polling MeF again for the same errors but different submissions
// we would expect the rejected ack in the second batch to still have associated errors
ValidationMapCompleted validationMapCompleted2 = getValidationErrorMap();
Map<String, List<List<String>>> validationErrorMap2 = validationMapCompleted2.validationErrorMap;
Iterable<Completed> c2 = validationMapCompleted2.completeds;
Map<String, Completed> cmap2 = new HashMap<>();
c2.forEach(completed -> cmap2.put(completed.getSubmissionId(), completed));
acknowledgementService.bulkUpdateEntities(validationErrorMap2, cmap2, pmap);
String rejectedSubmissionId2 =
validationErrorMap2.entrySet().iterator().next().getKey();
Completed rejectedAck2 = acknowledgementService
.getCompletedBySubmissionId(rejectedSubmissionId2)
.get();
Pending updatedPendings =
acknowledgementService.getAllPending().iterator().next();
Iterator<Completed> updatedCompleteds =
acknowledgementService.getAllCompleted().iterator();
// added another rejected and accepted submission object on top of the 2 in the first batch
assertEquals(acknowledgementService.getAllCompleted().spliterator().getExactSizeIfKnown(), 4);
// error count is the same because each error in the db is unique
assertEquals(acknowledgementService.getAllError().spliterator().getExactSizeIfKnown(), 2);
assertEquals(rejectedAck2.getSubmissionId(), rejectedSubmissionId2);
// errors are still persisted
assertNotNull(rejectedAck2.getErrors());
assertEquals(acknowledgementService.getAllPending().spliterator().getExactSizeIfKnown(), 2);
updatedCompleteds.forEachRemaining(completed -> {
assertNotEquals(updatedPendings.getSubmissionId(), completed.getSubmissionId());
});
assertEquals(
rejectedAck2.getErrors().getFirst().getErrorMessage(),
rejectedAck.getErrors().getFirst().getErrorMessage());
assertEquals(
rejectedAck2.getErrors().getLast().getErrorMessage(),
rejectedAck.getErrors().getLast().getErrorMessage());
}
@Test
void getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission_returnsAcceptedSubmissionEvenIfNotTheLatest() {
UUID taxReturnId = UUID.randomUUID();
String olderAcceptedSubmissionId = "accepted";
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, olderAcceptedSubmissionId));
String latestSubmissionId = "latest";
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, latestSubmissionId));
Completed acceptedCompleted = new Completed();
acceptedCompleted.setSubmissionId(olderAcceptedSubmissionId);
acceptedCompleted.setStatus("accepted");
completedRepo.save(acceptedCompleted);
Completed latestCompleted = new Completed();
latestCompleted.setSubmissionId(latestSubmissionId);
latestCompleted.setStatus("rejected");
completedRepo.save(latestCompleted);
var submissionId =
acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(taxReturnId);
assertEquals(olderAcceptedSubmissionId, submissionId);
}
@Test
void
getLatestAcceptedSubmissionIdOfParentTaxReturn_getsTheLatestAcceptedSubmissionIdOfTheParentTaxReturnEvenIfNotTheLatest() {
UUID taxReturnId = UUID.randomUUID();
String olderAcceptedSubmissionId = "accepted";
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, olderAcceptedSubmissionId));
String latestSubmissionId = "latest";
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, latestSubmissionId));
Completed acceptedCompleted = new Completed();
acceptedCompleted.setSubmissionId(olderAcceptedSubmissionId);
acceptedCompleted.setStatus("accepted");
completedRepo.save(acceptedCompleted);
Completed latestCompleted = new Completed();
latestCompleted.setSubmissionId(latestSubmissionId);
latestCompleted.setStatus("rejected");
completedRepo.save(latestCompleted);
var submissionId =
acknowledgementService.getLatestAcceptedSubmissionIdOfParentTaxReturn(olderAcceptedSubmissionId);
assertTrue(submissionId.isPresent());
assertEquals(olderAcceptedSubmissionId, submissionId.get());
}
@Test
void getRejectionCodesForSubmissionId_returnsRejectionCodes() {
Error error1 = new Error();
error1.setMefErrorCode("code1");
error1.setErrorCodeTranslationKey("key1");
error1.setMefErrorCategory("category1");
error1.setErrorMessage("message1");
error1 = errorRepository.save(error1);
Completed completedRejection = new Completed();
String submissionId = "rejected";
completedRejection.setSubmissionId(submissionId);
completedRejection.setStatus("rejected");
completedRejection.setErrors(List.of(error1));
completedRepo.save(completedRejection);
List<RejectedStatus> rejectionCodes = acknowledgementService.getRejectionCodesForSubmissionId(submissionId);
assertEquals(1, rejectionCodes.size());
RejectedStatus rejectedStatus = rejectionCodes.getFirst();
assertEquals(error1.getMefErrorCode(), rejectedStatus.MeFErrorCode);
assertEquals(error1.getErrorCodeTranslationKey(), rejectedStatus.TranslationKey);
assertEquals(error1.getErrorMessage(), rejectedStatus.MeFDescription);
}
@Test
void getRejectionCodesForSubmissionId_returnsEmptyListWhenCompletedIsNotRejected() {
Completed completedAccepted = new Completed();
String submissionId = "accepted";
completedAccepted.setSubmissionId(submissionId);
completedAccepted.setStatus("accepted");
completedRepo.save(completedAccepted);
List<RejectedStatus> rejectionCodes = acknowledgementService.getRejectionCodesForSubmissionId(submissionId);
assertEquals(0, rejectionCodes.size());
}
@Test
void getRejectionCodesForSubmissionId_throwsExceptionWhenNoCompletedRecord() {
String submissionId = "missing";
EntityNotFoundException thrown = assertThrows(
EntityNotFoundException.class,
() -> acknowledgementService.getRejectionCodesForSubmissionId(submissionId));
assertEquals("Could not find completed record for submission ID: " + submissionId, thrown.getMessage());
}
@Test
void whenCreateServiceContextWrapper_WithPodIdentifiers_ThenReturnsAsidFromDatabase() {
PodIdentifier p = createPodIdentifer("us-gov-east-1", statusProperties.getAsid(), 0);
podIdentifierRepository.save(p);
String asid = podIdentifierRepository.findAsidByPodId(p.getPodId()).get();
ServiceContextWrapper serviceContextWrapper = acknowledgementService.createServiceContextWrapper();
ServiceContext serviceContext = serviceContextWrapper.getServiceContext();
assertNotNull(serviceContextWrapper);
assertNotNull(serviceContextWrapper.getServiceContext());
assertEquals(serviceContext.getAppSysID(), statusProperties.getAsid());
assertEquals(serviceContext.getAppSysID(), asid);
assertEquals(serviceContext.getEtin().toString(), statusProperties.getEtin());
assertEquals(serviceContext.getTestCdType(), TestCdType.T);
}
@Test
void whenCreateServiceContextWrapper_WithMultiplePodIdentifiers_ThenReturnsCorrectAsid() {
PodIdentifier p1 = createPodIdentifer("us-gov-east-1", ASID, 2);
PodIdentifier p2 = createPodIdentifer("us-gov-east-1", ASID + "2", 1);
PodIdentifier p3 = createPodIdentifer("us-gov-east-1", statusProperties.getAsid(), 0);
podIdentifierRepository.save(p1);
podIdentifierRepository.save(p2);
podIdentifierRepository.save(p3);
String correctAsid =
podIdentifierRepository.findAsidByPodId(p3.getPodId()).get();
ServiceContextWrapper serviceContextWrapper = acknowledgementService.createServiceContextWrapper();
ServiceContext serviceContext = serviceContextWrapper.getServiceContext();
assertNotNull(serviceContextWrapper);
assertNotNull(serviceContextWrapper.getServiceContext());
assertEquals(serviceContext.getAppSysID(), statusProperties.getAsid());
assertEquals(serviceContext.getAppSysID(), correctAsid);
assertEquals(serviceContext.getEtin().toString(), statusProperties.getEtin());
assertEquals(serviceContext.getTestCdType(), TestCdType.T);
}
private record ValidationMapCompleted(
Map<String, List<List<String>>> validationErrorMap, Iterable<Completed> completeds) {}
@Test
void whenRejectedStatusChangeMessages_DoPublish() {
List<AcknowledgementWrapper> acksList = new ArrayList<>();
PodIdentifier pi = createPodIdentifer("us-gov-east-1", statusProperties.getAsid(), 0);
podIdentifierRepository.save(pi);
Pending pending = new Pending("submissionId", pi.getPodId());
pendingRepo.save(pending);
acksList.add(new AcknowledgementWrapper("submissionId", "reeee", "rejected"));
GetAcksResultWrapper acksResult = new GetAcksResultWrapper(new AcknowledgementsListWrapper(acksList));
acknowledgementService.bulkUpdateRecordsFromAckResultAndEnqueueStatusChangeMessages(
acksResult, List.of(pending));
Map<String, List<String>> statusSubmissionIdMap = new HashMap<>();
statusSubmissionIdMap.put("accepted", List.of());
statusSubmissionIdMap.put("rejected", List.of("submissionId"));
verify(statusChangeMessageService, times(1)).publishStatusChangePayloadV1(statusSubmissionIdMap);
}
@Test
void whenAcceptedStatusChangeMessages_DoPublish() {
List<AcknowledgementWrapper> acksList = new ArrayList<>();
PodIdentifier pi = createPodIdentifer("us-gov-east-1", statusProperties.getAsid(), 0);
podIdentifierRepository.save(pi);
Pending pending = new Pending("submissionId3", pi.getPodId());
pendingRepo.save(pending);
acksList.add(new AcknowledgementWrapper("submissionId3", "beeee", "accepted"));
GetAcksResultWrapper acksResult = new GetAcksResultWrapper(new AcknowledgementsListWrapper(acksList));
acknowledgementService.bulkUpdateRecordsFromAckResultAndEnqueueStatusChangeMessages(
acksResult, List.of(pending));
Map<String, List<String>> statusSubmissionIdMap = new HashMap<>();
statusSubmissionIdMap.put("accepted", List.of("submissionId3"));
statusSubmissionIdMap.put("rejected", List.of());
verify(statusChangeMessageService, times(1)).publishStatusChangePayloadV1(statusSubmissionIdMap);
}
@Test
void whenAcceptedandRejectedStatusChangeMessages_DoPublish() {
List<AcknowledgementWrapper> acksList = new ArrayList<>();
PodIdentifier pi = createPodIdentifer("us-gov-east-1", statusProperties.getAsid(), 0);
podIdentifierRepository.save(pi);
Pending pending = new Pending("subId3", pi.getPodId());
Pending pending2 = new Pending("subId", pi.getPodId());
pendingRepo.save(pending);
pendingRepo.save(pending2);
acksList.add(new AcknowledgementWrapper("subId3", "yerp", "accepted"));
acksList.add(new AcknowledgementWrapper("subId", "nerp", "rejected"));
GetAcksResultWrapper acksResult = new GetAcksResultWrapper(new AcknowledgementsListWrapper(acksList));
acknowledgementService.bulkUpdateRecordsFromAckResultAndEnqueueStatusChangeMessages(
acksResult, List.of(pending, pending2));
Map<String, List<String>> statusSubmissionIdMap = new HashMap<>();
statusSubmissionIdMap.put("accepted", List.of("subId3"));
statusSubmissionIdMap.put("rejected", List.of("subId"));
verify(statusChangeMessageService, times(1)).publishStatusChangePayloadV1(statusSubmissionIdMap);
}
@Test
void whenNoStatusChangeMessages_NoPublish() {
List<AcknowledgementWrapper> acksList = new ArrayList<>();
GetAcksResultWrapper acksResult = new GetAcksResultWrapper(new AcknowledgementsListWrapper(acksList));
acknowledgementService.bulkUpdateRecordsFromAckResultAndEnqueueStatusChangeMessages(
acksResult, new ArrayList<>());
Map<String, List<String>> statusSubmissionIdMap = new HashMap<>();
statusSubmissionIdMap.put("accepted", List.of());
statusSubmissionIdMap.put("rejected", List.of());
verify(statusChangeMessageService, times(0)).publishStatusChangePayloadV1(statusSubmissionIdMap);
}
}

View file

@ -1,130 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.times;
// AcknowledgementServiceTest is a @SpringBootTest
// This test uses mocks for lighter weight tests that don't need to test integration details as heavily.
@ExtendWith(MockitoExtension.class)
public class AcknowledgementServiceWithMocksTest {
@Mock
private TaxReturnSubmissionRepository taxReturnSubmissionRepository;
@Mock
private CompletedAcknowledgementRepository completedRepo;
@InjectMocks
AcknowledgementService acknowledgementService;
@Test
void getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission_returnsNullIfNoSubmissionIdsExist() {
// Given
var taxReturnId = UUID.randomUUID();
when(taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId))
.thenReturn(Optional.empty());
when(taxReturnSubmissionRepository.getLatestSubmissionIdByTaxReturnId(taxReturnId))
.thenReturn(Optional.empty());
// When
String submissionId =
acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(taxReturnId);
// Then
verify(taxReturnSubmissionRepository, times(1)).getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId);
verify(taxReturnSubmissionRepository, times(1)).getLatestSubmissionIdByTaxReturnId(taxReturnId);
assertNull(submissionId);
}
@Test
void
getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission_justGetsTheLatestSubmissionIdIfNoAcceptedSubmissionIsFound() {
// Given
var taxReturnId = UUID.randomUUID();
var latestSubmissionId = "theLatestSubmissionId";
when(taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId))
.thenReturn(Optional.empty());
when(taxReturnSubmissionRepository.getLatestSubmissionIdByTaxReturnId(taxReturnId))
.thenReturn(Optional.of(latestSubmissionId));
// When
String submissionId =
acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(taxReturnId);
// Then
verify(taxReturnSubmissionRepository, times(1)).getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId);
verify(taxReturnSubmissionRepository, times(1)).getLatestSubmissionIdByTaxReturnId(taxReturnId);
assertEquals(latestSubmissionId, submissionId);
}
@Test
void
getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission_getsTheLatestAcceptedSubmissionIdEvenIfItIsNotTheLatest() {
// Given
var taxReturnId = UUID.randomUUID();
var latestAcceptedSubmissionId = "theLatestAcceptedSubmissionId";
when(taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId))
.thenReturn(Optional.of(latestAcceptedSubmissionId));
// When
String submissionId =
acknowledgementService.getLatestSubmissionIdByTaxReturnIdPreferringAcceptedSubmission(taxReturnId);
// Then
verify(taxReturnSubmissionRepository, times(1)).getLatestAcceptedSubmissionIdForTaxReturnId(taxReturnId);
verify(taxReturnSubmissionRepository, times(0)).getLatestSubmissionIdByTaxReturnId(taxReturnId);
assertEquals(latestAcceptedSubmissionId, submissionId);
}
@Test
void getLatestAcceptedSubmissionIdOfParentTaxReturn_returnsEmptyIfNoAcceptedSubmissionFound() {
// Given
var requestedSubmissionId = "requestedSubmissionId";
when(taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId))
.thenReturn(Optional.empty());
// When
Optional<String> latestAcceptedSubmissionId =
acknowledgementService.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId);
// Then
verify(taxReturnSubmissionRepository, times(1))
.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId);
assertTrue(latestAcceptedSubmissionId.isEmpty());
}
@Test
void getLatestAcceptedSubmissionIdOfParentTaxReturn_returnsTheAcceptedSubmissionId() {
// Given
var requestedSubmissionId = "requestedSubmissionId";
var acceptedSubmissionId = "acceptedSubmissionId";
when(taxReturnSubmissionRepository.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId))
.thenReturn(Optional.of(acceptedSubmissionId));
// When
Optional<String> latestAcceptedSubmissionId =
acknowledgementService.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId);
// Then
verify(taxReturnSubmissionRepository, times(1))
.getLatestAcceptedSubmissionIdOfParentTaxReturn(requestedSubmissionId);
assertTrue(latestAcceptedSubmissionId.isPresent());
assertEquals(acceptedSubmissionId, latestAcceptedSubmissionId.get());
}
}

View file

@ -1,156 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.List;
import ch.qos.logback.classic.Level;
import org.hibernate.engine.jdbc.batch.JdbcBatchLogging;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import gov.irs.directfile.status.domain.Completed;
import gov.irs.directfile.status.domain.Error;
import gov.irs.directfile.status.error.ErrorRepository;
import gov.irs.directfile.status.extension.BatchUtil;
import gov.irs.directfile.status.extension.LoggerExtension;
import static org.junit.jupiter.api.Assertions.*;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(classes = SecurityAutoConfiguration.class)
@DataJpaTest(properties = "spring.main.web-application-type=servlet")
class CompletedAcknowledgementRepositoryTest {
@Autowired
CompletedAcknowledgementRepository completedRepo;
@Autowired
ErrorRepository errorRepository;
@RegisterExtension
private static final LoggerExtension batchLogVerifier = new LoggerExtension(Level.TRACE, JdbcBatchLogging.NAME);
@Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
private int batchSize;
@Test
public void CanAddAnAccepted() {
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Accepted");
completedRepo.save(c);
var completed =
completedRepo.GetCompletedSubmission("12345620230215000001").get();
assertEquals("Accepted", completed.getStatus());
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Completed.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CanAddARejected() {
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject and Stop");
c.setErrors(List.of(e));
// must add error to database first!
errorRepository.save(e);
completedRepo.save(c);
var completed =
completedRepo.GetCompletedSubmission("12345620230215000001").get();
assertEquals("Rejected", completed.getStatus());
assertEquals("XML-123-4567-006", completed.getErrors().get(0).getMefErrorCode());
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Error.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Completed.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(BatchUtil.buildBatchMessage(
1, batchSize, Completed.class.getName() + ".errors", BatchUtil.BatchType.INSERT));
}
@Test
public void CanAddARejectedWithMultipleErrors() {
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject");
Error e2 = new Error();
e2.setMefErrorCode("REJC-00001");
e2.setErrorMessage("This was a huge problem");
e2.setErrorCodeTranslationKey("translation/reject/REJC-00001");
e2.setMefErrorCategory("Reject");
c.setErrors(List.of(e, e2));
errorRepository.saveAll(List.of(e, e2));
completedRepo.save(c);
var completed =
completedRepo.GetCompletedSubmission("12345620230215000001").get();
assertEquals("Rejected", completed.getStatus());
assertEquals("XML-123-4567-006", completed.getErrors().get(0).getMefErrorCode());
assertEquals("REJC-00001", completed.getErrors().get(1).getMefErrorCode());
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(2, batchSize, Error.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Completed.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(BatchUtil.buildBatchMessage(
2, batchSize, Completed.class.getName() + ".errors", BatchUtil.BatchType.INSERT));
}
@Test
public void CanQueryForANonExistentSubmissionId() {
var completed = completedRepo.GetCompletedSubmission("12345620230215000001");
assertTrue(completed.isEmpty());
}
@Test
public void CanAddTheSameErrorToTwoDifferentCompletedRecords() {
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject and Stop");
errorRepository.save(e);
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Rejected");
c.setErrors(List.of(e));
completedRepo.save(c);
Completed c2 = new Completed();
c2.setSubmissionId("12345620230215000002");
c2.setStatus("Rejected");
c2.setErrors(List.of(e));
completedRepo.save(c2);
assertEquals(1, errorRepository.findAll().spliterator().getExactSizeIfKnown());
assertEquals(2, completedRepo.findAll().spliterator().getExactSizeIfKnown());
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Error.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(2, batchSize, Completed.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(BatchUtil.buildBatchMessage(
2, batchSize, Completed.class.getName() + ".errors", BatchUtil.BatchType.INSERT));
}
}

View file

@ -1,143 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.List;
import ch.qos.logback.classic.Level;
import org.hibernate.engine.jdbc.batch.JdbcBatchLogging;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import gov.irs.directfile.status.domain.Pending;
import gov.irs.directfile.status.extension.BatchUtil;
import gov.irs.directfile.status.extension.LoggerExtension;
import static org.junit.jupiter.api.Assertions.*;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(classes = SecurityAutoConfiguration.class)
@DataJpaTest(properties = "spring.main.web-application-type=servlet")
public class PendingAcknowledgementRepositoryTest {
@Autowired
PendingAcknowledgementRepository pendingRepo;
@Autowired
private TestEntityManager testEntityManager;
@RegisterExtension
private static final LoggerExtension batchLogVerifier = new LoggerExtension(Level.TRACE, JdbcBatchLogging.NAME);
@Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
private int batchSize;
@Test
public void CanAddASubmissionIdToTheTable() {
Pending p = new Pending();
p.setSubmissionId("12345620230215000010");
pendingRepo.save(p);
// Verify JDBC batching
testEntityManager.flush();
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Pending.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CanAddMultipleSubmissionsToTheTable() {
Pending p = new Pending();
p.setSubmissionId("12345620230215000001");
pendingRepo.save(p);
Pending p2 = new Pending();
p2.setSubmissionId("12345620230215000002");
pendingRepo.save(p2);
Pending p3 = new Pending();
Pending p4 = new Pending();
p3.setSubmissionId("12345620230215000003");
p4.setSubmissionId("12345620230215000004");
var multiple = List.of(p3, p4);
pendingRepo.saveAll(multiple);
// Verify JDBC batching
testEntityManager.flush();
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(4, batchSize, Pending.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CanRetrieveFromPendingTable() {
Pending p = new Pending();
p.setSubmissionId("12345620230215000001");
pendingRepo.save(p);
var pending = pendingRepo.GetPendingSubmission("12345620230215000001");
assertEquals(p.getSubmissionId(), pending.get().getSubmissionId());
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Pending.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CanDeleteFromPendingTable() {
Pending p = new Pending();
p.setSubmissionId("12345620230215000001");
pendingRepo.save(p);
Pending p2 = new Pending();
p2.setSubmissionId("12345620230215000001");
pendingRepo.delete(p2);
var option = pendingRepo.GetPendingSubmission("12345620230215000001");
assertTrue(option.isEmpty());
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Pending.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Pending.class.getName(), BatchUtil.BatchType.DELETE));
}
@Test
public void CannotEnterTwoOfTheSameSubmissionIdsIntoThePendingTable() {
Pending p = new Pending();
p.setSubmissionId("12345620230215000001");
pendingRepo.save(p);
Pending p2 = new Pending();
p2.setSubmissionId("12345620230215000001");
pendingRepo.save(p2);
var all = pendingRepo.findAll();
var size = all.spliterator().getExactSizeIfKnown();
assertEquals(1, size);
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Pending.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CanQueryForANonExistentPendingWithNoResults() {
var empty = pendingRepo.GetPendingSubmission("12345620230215000001");
assertTrue(empty.isEmpty());
}
@Test
public void CannotEnterASubmissionIdThatIsTooLong() {
Pending p = new Pending();
p.setSubmissionId("123456202302150000001");
assertEquals(0, pendingRepo.findAll().spliterator().getExactSizeIfKnown());
// I wish I knew why this saves
// TODO: make this throw...
pendingRepo.save(p);
assertThrows(org.springframework.dao.DataIntegrityViolationException.class, () -> {
assertEquals(0, pendingRepo.findAll().spliterator().getExactSizeIfKnown());
});
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Pending.class.getName(), BatchUtil.BatchType.INSERT));
}
}

View file

@ -1,42 +0,0 @@
package gov.irs.directfile.status.acknowledgement;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import gov.irs.directfile.status.domain.TaxReturnSubmission;
import static org.junit.jupiter.api.Assertions.*;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
class TaxReturnSubmissionRepositoryTest {
@Autowired
TaxReturnSubmissionRepository taxReturnSubmissionRepository;
@Test
public void canSaveAndRetrieveTaxReturnSubmissions() {
UUID taxReturnId = UUID.randomUUID();
String submissionId = "12345678901234567890";
taxReturnSubmissionRepository.save(new TaxReturnSubmission(taxReturnId, submissionId));
Optional<String> result = taxReturnSubmissionRepository.getLatestSubmissionIdByTaxReturnId(taxReturnId);
assertTrue(result.isPresent());
assertEquals(result.get(), submissionId);
// Note: Hibernate disables batching for entities having an identity PK, so no batching to test for here
}
@Test
public void returnsEmptyOptionalWhenTryingToFindSubmissionIdOfTaxReturnThatDoesNotExist() {
UUID taxReturnId = UUID.randomUUID();
Optional<String> result = taxReturnSubmissionRepository.getLatestSubmissionIdByTaxReturnId(taxReturnId);
assertTrue(result.isEmpty());
}
}

View file

@ -1,51 +0,0 @@
package gov.irs.directfile.status.config;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import static org.assertj.core.api.Assertions.assertThat;
class AWSCredentialsConfigurationTest {
@Test
void staticCredentialProviderCreatedWhenApplicablePropertySetToFalse() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues(
"aws.accessKey=test",
"aws.secretKey=test",
"aws.default-credentials-provider-chain-enabled=false")
.withUserConfiguration(AWSCredentialsConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isNotNull();
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(StaticCredentialsProvider.class);
});
}
@Test
void staticCredentialProviderCreatedWhenApplicablePropertyIsNotSet() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues("aws.accessKey=test", "aws.secretKey=test")
.withUserConfiguration(AWSCredentialsConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isNotNull();
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(StaticCredentialsProvider.class);
});
}
@Test
void defaultCredentialProviderCreatedWhenApplicablePropertySetToTrue() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues(
"aws.accessKey=test",
"aws.secretKey=test",
"aws.default-credentials-provider-chain-enabled=true")
.withUserConfiguration(AWSCredentialsConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isNotNull();
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(DefaultCredentialsProvider.class);
});
}
}

View file

@ -1,32 +0,0 @@
package gov.irs.directfile.status.config;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.encryption.s3.materials.CryptographicMaterialsManager;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(
classes = {EncryptionClientConfiguration.class, AWSCredentialsConfiguration.class},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
class EncryptionClientConfigurationTest {
@Autowired
EncryptionClientConfiguration encryptionClientConfiguration;
@Test
void regionalKmsClientIsNotNull() {
KmsClient kmsClient = encryptionClientConfiguration.regionalKmsClient();
assertNotNull(kmsClient);
}
@Test
void kmsCryptoIsNotNull() {
String testKmsKeyArn = "test-kms-arn";
CryptographicMaterialsManager cryptographicMaterialsManager =
encryptionClientConfiguration.kmsCrypto(testKmsKeyArn);
assertNotNull(cryptographicMaterialsManager);
}
}

View file

@ -1,51 +0,0 @@
package gov.irs.directfile.status.config;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class SnsClientConfigurationTest {
@Test
void snsClientBeanCreatedWhenStaticCredentialProviderEnabled() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues(
"aws.accessKey=test",
"aws.secretKey=test",
"status.sns.endpoint=http://directfile.test",
"status.sns.status-change-topic-arn=test-topic-arn",
"status.sns.region=us-west-2",
"status.sns.accessKey=test",
"status.sns.secretKey=test",
"status.sns.status-change-publish-enabled=true",
"aws.default-credentials-provider-chain-enabled=false")
.withUserConfiguration(AWSCredentialsConfiguration.class, SnsClientConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(StaticCredentialsProvider.class);
assertThat(context.getBean(SnsClientConfiguration.class)).isNotNull();
});
}
@Test
void snsClientBeanCreatedWhenDefaultCredentialProviderEnabled() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues(
"status.sns.endpoint=http://directfile.test",
"status.sns.status-change-topic-arn=test-topic-arn",
"status.sns.region=us-west-2",
"status.sns.accessKey=test",
"status.sns.secretKey=test",
"status.sns.status-change-publish-enabled=true",
"aws.default-credentials-provider-chain-enabled=true")
.withUserConfiguration(AWSCredentialsConfiguration.class, SnsClientConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(DefaultCredentialsProvider.class);
assertThat(context.getBean(SnsClientConfiguration.class)).isNotNull();
});
}
}

View file

@ -1,34 +0,0 @@
package gov.irs.directfile.status.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.ListSubscriptionsByTopicRequest;
import software.amazon.awssdk.services.sns.model.ListSubscriptionsByTopicResponse;
import software.amazon.awssdk.services.sns.model.Subscription;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@TestConfiguration
public class SnsClientTestConfiguration {
@Bean
@Primary
public SnsClient testSnsClient() {
ListSubscriptionsByTopicResponse listSubscriptionsByTopicResponse =
mock(ListSubscriptionsByTopicResponse.class);
List<Subscription> subscriptions = new ArrayList<>();
subscriptions.add(mock(Subscription.class));
when(listSubscriptionsByTopicResponse.subscriptions()).thenReturn(subscriptions);
SnsClient snsClient = mock(SnsClient.class);
when(snsClient.listSubscriptionsByTopic(any(ListSubscriptionsByTopicRequest.class)))
.thenReturn(listSubscriptionsByTopicResponse);
return snsClient;
}
}

View file

@ -1,54 +0,0 @@
package gov.irs.directfile.status.config;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.services.sqs.SqsClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class SqsClientConfigurationTest {
@Test
void sqsClientBeanCreatedWhenStaticCredentialProviderEnabled() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues(
"aws.accessKey=test",
"aws.secretKey=test",
"status.message-queue.endpoint=http://directfile.test",
"status.message-queue.status-change-queue=test-queue-01",
"status.message-queue.pending-submission-queue=test-queue-02",
"status.message-queue.dlq-pending-submission-queue=test-queue-03",
"status.message-queue.region=us-west-2",
"status.message-queue.accessKey=test",
"status.message-queue.secretKey=test",
"aws.default-credentials-provider-chain-enabled=false")
.withUserConfiguration(AWSCredentialsConfiguration.class, SqsClientConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(StaticCredentialsProvider.class);
assertThat(context.getBean(SqsClient.class)).isNotNull();
});
}
@Test
void sqsClientBeanCreatedWhenDefaultCredentialProviderEnabled() {
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
.withPropertyValues(
"status.message-queue.endpoint=http://directfile.test",
"status.message-queue.status-change-queue=test-queue-01",
"status.message-queue.pending-submission-queue=test-queue-02",
"status.message-queue.dlq-pending-submission-queue=test-queue-03",
"status.message-queue.region=us-west-2",
"status.message-queue.accessKey=test",
"status.message-queue.secretKey=test",
"aws.default-credentials-provider-chain-enabled=true")
.withUserConfiguration(AWSCredentialsConfiguration.class, SqsClientConfiguration.class);
applicationContextRunner.run((context) -> {
assertThat(context.getBean(AwsCredentialsProvider.class)).isInstanceOf(DefaultCredentialsProvider.class);
assertThat(context.getBean(SqsClient.class)).isNotNull();
});
}
}

View file

@ -1,122 +0,0 @@
package gov.irs.directfile.status.error;
import java.util.List;
import ch.qos.logback.classic.Level;
import org.hibernate.engine.jdbc.batch.JdbcBatchLogging;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import gov.irs.directfile.status.acknowledgement.CompletedAcknowledgementRepository;
import gov.irs.directfile.status.domain.Completed;
import gov.irs.directfile.status.domain.Error;
import gov.irs.directfile.status.extension.BatchUtil;
import gov.irs.directfile.status.extension.LoggerExtension;
import static org.junit.jupiter.api.Assertions.*;
// For right now, Errors only exist as information on
// the Reject. It doesn't need a direct access right now.
// It may/will in the future.
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(classes = SecurityAutoConfiguration.class)
@DataJpaTest(properties = "spring.main.web-application-type=servlet")
class ErrorRepositoryTest {
@Autowired
CompletedAcknowledgementRepository completedRepo;
@Autowired
ErrorRepository errorRepository;
@Autowired
private TestEntityManager testEntityManager;
@RegisterExtension
private static final LoggerExtension batchLogVerifier = new LoggerExtension(Level.TRACE, JdbcBatchLogging.NAME);
@Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
private int batchSize;
@Test
public void CanAddABasicError() {
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject");
errorRepository.save(e);
Error retrieved = errorRepository.findById("XML-123-4567-006").get();
assertEquals(e.getErrorCodeTranslationKey(), retrieved.getErrorCodeTranslationKey());
assertEquals(e.getMefErrorCode(), retrieved.getMefErrorCode());
assertEquals(e.getErrorMessage(), retrieved.getErrorMessage());
assertEquals(e.getMefErrorCategory(), retrieved.getMefErrorCategory());
// Verify JDBC batching
testEntityManager.flush();
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Error.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CanAddAnErrorToACompletedRecord() {
// you should never do it this way!
// Errors should be created first
Completed c = new Completed();
c.setSubmissionId("12345620230215000001");
c.setStatus("Rejected");
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject");
e.setCompleted(List.of(c));
completedRepo.save(c);
errorRepository.save(e);
Error retrieved = errorRepository.findById("XML-123-4567-006").get();
assertEquals("12345620230215000001", retrieved.getCompleted().get(0).getSubmissionId());
// Verify JDBC batching
testEntityManager.flush();
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Completed.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Error.class.getName(), BatchUtil.BatchType.INSERT));
}
@Test
public void CallingSaveOnAKnownIDWithNewInformationUpdatesTheRecord() {
Error e = new Error();
e.setMefErrorCode("XML-123-4567-006");
e.setErrorMessage("You messed up!");
e.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e.setMefErrorCategory("Reject");
errorRepository.save(e);
Error e2 = new Error();
e2.setMefErrorCode("XML-123-4567-006");
e2.setErrorMessage("This is bad");
e2.setErrorCodeTranslationKey("translation/reject/XML-123-4567-006");
e2.setMefErrorCategory("Reject");
errorRepository.save(e2);
Error retrieved = errorRepository.findById("XML-123-4567-006").get();
assertEquals(e.getErrorCodeTranslationKey(), retrieved.getErrorCodeTranslationKey());
assertEquals(e.getMefErrorCode(), retrieved.getMefErrorCode());
assertEquals(e2.getErrorMessage(), retrieved.getErrorMessage());
assertEquals(e.getMefErrorCategory(), retrieved.getMefErrorCategory());
// Verify JDBC batching
testEntityManager.flush();
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Error.class.getName(), BatchUtil.BatchType.INSERT));
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, Error.class.getName(), BatchUtil.BatchType.UPDATE));
}
}

View file

@ -1,78 +0,0 @@
package gov.irs.directfile.status.error;
import ch.qos.logback.classic.Level;
import jakarta.validation.ConstraintViolationException;
import org.hibernate.engine.jdbc.batch.JdbcBatchLogging;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import gov.irs.directfile.status.domain.ToolkitError;
import gov.irs.directfile.status.extension.BatchUtil;
import gov.irs.directfile.status.extension.LoggerExtension;
import static org.junit.jupiter.api.Assertions.*;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ImportAutoConfiguration(classes = SecurityAutoConfiguration.class)
@Transactional(propagation = Propagation.NOT_SUPPORTED) // Allows CrudRepository Transactional scope to work as intended
@DataJpaTest
public class ToolkitErrorRepositoryTest {
@Autowired
ToolkitErrorRepository toolkitErrorRepo;
@RegisterExtension
private static final LoggerExtension batchLogVerifier = new LoggerExtension(Level.TRACE, JdbcBatchLogging.NAME);
@Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
private int batchSize;
@Test
public void DoesNotAllowNullErrorName() {
ToolkitError tke = new ToolkitError();
tke.setSubmissionId("12345620230215000010");
tke.setErrorMessage("something");
TransactionSystemException tse = assertThrows(TransactionSystemException.class, () -> {
toolkitErrorRepo.save(tke);
});
assertEquals(
ConstraintViolationException.class, tse.getMostSpecificCause().getClass());
}
@Test
public void DoesNotAllowNullErrorMessage() {
ToolkitError tke = new ToolkitError();
tke.setSubmissionId("12345678900987654321");
tke.setErrorName("ToolkitException");
TransactionSystemException tse = assertThrows(TransactionSystemException.class, () -> {
toolkitErrorRepo.save(tke);
});
assertEquals(
ConstraintViolationException.class, tse.getMostSpecificCause().getClass());
}
@Test
public void SavesWithValidFields() {
ToolkitError tke = new ToolkitError();
tke.setSubmissionId("12345678900987654321");
tke.setErrorName("ToolkitException");
tke.setErrorMessage("Something went wrong!");
toolkitErrorRepo.save(tke);
assert (toolkitErrorRepo.findById(tke.getSubmissionId())).isPresent();
// Verify JDBC batching
batchLogVerifier.verifyLogContainsMessage(
BatchUtil.buildBatchMessage(1, batchSize, ToolkitError.class.getName(), BatchUtil.BatchType.INSERT));
}
}

View file

@ -1,17 +0,0 @@
package gov.irs.directfile.status.extension;
public final class BatchUtil {
private BatchUtil() {
// no instantiation allowed
}
public enum BatchType {
INSERT,
UPDATE,
DELETE
}
public static String buildBatchMessage(int count, int batchSize, String entityName, BatchType type) {
return String.format("Executing JDBC batch (%d / %d) - `%s#%s`", count, batchSize, entityName, type);
}
}

View file

@ -1,105 +0,0 @@
package gov.irs.directfile.status.extension;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.LoggerFactory;
import org.slf4j.event.KeyValuePair;
import gov.irs.directfile.audit.AuditLogElement;
import gov.irs.directfile.audit.events.Event;
import static org.junit.jupiter.api.Assertions.*;
public class LoggerExtension implements BeforeEachCallback, AfterEachCallback {
private Logger logger;
private ListAppender<ILoggingEvent> appender;
private final String loggerName;
private final Level level;
public LoggerExtension(Level level, String loggerName) {
this.loggerName = loggerName;
this.level = level;
}
@Override
public void beforeEach(ExtensionContext context) {
logger = (Logger) LoggerFactory.getLogger(loggerName);
appender = new ListAppender<>();
appender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
logger.setLevel(level);
logger.addAppender(appender);
appender.start();
}
@Override
public void afterEach(ExtensionContext context) {
logger.detachAppender(appender);
}
public void verifyLogEvent(Event event) {
verifyLogEvent(event, 0, null);
}
public void verifyLogEvent(Event event, Map<AuditLogElement, Object> logPropertiesToAssert) {
verifyLogEvent(event, 0, logPropertiesToAssert);
}
public void verifyLogEvent(Event event, int index) {
verifyLogEvent(event, index, null);
}
public void verifyLogEvent(Event event, int index, Map<AuditLogElement, Object> logPropertiesToAssert) {
ILoggingEvent loggingEvent = appender.list.get(index);
List<KeyValuePair> keyValuePairs = loggingEvent.getKeyValuePairs();
Map<String, String> mdcPropertyMap = loggingEvent.getMDCPropertyMap();
HashMap<String, String> combinedMap = new HashMap<>(mdcPropertyMap);
ListIterator<KeyValuePair> keyValuePairListIterator = keyValuePairs.listIterator();
while (keyValuePairListIterator.hasNext()) {
KeyValuePair next = keyValuePairListIterator.next();
// a collision here causes logback to silently exclude all keyValuePairs from json output
assertFalse(combinedMap.containsKey(next.key));
combinedMap.put(next.key, next.value == null ? null : next.value.toString());
}
assertEquals(combinedMap.get(AuditLogElement.cyberOnly.toString()), "true", "cyberOnly not set (XXXX flag)");
assertEquals(event.getEventStatus().toString(), combinedMap.get(AuditLogElement.eventStatus.toString()));
assertEquals(event.getEventId().toString(), combinedMap.get(AuditLogElement.eventId.toString()));
assertEquals(
event.getEventPrincipal().getUserType().toString(),
combinedMap.get(AuditLogElement.userType.toString()));
assertEquals(event.getEventErrorMessage(), combinedMap.get(AuditLogElement.eventErrorMessage.toString()));
assertEquals(event.getDetail(), combinedMap.get(AuditLogElement.detail.toString()));
if (logPropertiesToAssert != null) {
for (Map.Entry<AuditLogElement, Object> entry : logPropertiesToAssert.entrySet()) {
assertEquals(
entry.getValue().toString(),
combinedMap.get(entry.getKey().toString()));
}
}
}
public void verifyLogContainsMessage(String message) {
boolean foundMessage = false;
for (ILoggingEvent loggingEvent : appender.list) {
if (loggingEvent.getMessage().equals(message)) {
foundMessage = true;
break;
}
}
assertTrue(foundMessage, String.format("log message not found: %s", message));
}
}

View file

@ -1,133 +0,0 @@
package gov.irs.directfile.status.services;
import com.amazon.sqs.javamessaging.message.SQSTextMessage;
import jakarta.jms.JMSException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import gov.irs.directfile.status.config.MessageQueueConfiguration;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class MessageQueueListenerServiceTest {
@Mock
private MessageQueueConfiguration messageQueueConfiguration;
@Mock
private PendingSubmissionMessageRouter pendingSubmissionMessageRouter;
@Mock
private SubmissionConfirmationMessageRouter submissionConfirmationMessageRouter;
private MessageQueueListenerService messageQueueListenerService;
String pendingSubmissionMessageJson =
"""
{
"payload": {
"@type": "PendingSubmissionPayloadV1",
"pendings": [
{
"taxReturnId": "00000000-0000-1111-1111-000000000000",
"submissionId": "11111111"
},
{
"taxReturnId": "00000000-0000-2222-2222-000000000000",
"submissionId": "22222222"
}
]
},
"headers": {
"headers": {
"VERSION": "1.0"
}
}
}
""";
String submissionConfirmationMessageJson =
"""
{
"payload": {
"@type": "SubmissionConfirmationPayloadV1",
"receipts": [
{
"taxReturnId": "f6cdd8d9-2606-4acc-a331-28554f9bc72b",
"submissionId": "submissionId1",
"receiptId": "receiptId1",
"submissionReceivedAt": 1721754334033
},
{
"taxReturnId": "248fcc48-b362-497e-8927-e3c88b653009",
"submissionId": "submissionId2",
"receiptId": "receiptId2",
"submissionReceivedAt": 1721754334033
}
]
},
"headers": {
"headers": {
"VERSION": "1.0"
}
}
}
""";
@BeforeEach
public void setup() {
messageQueueListenerService = new MessageQueueListenerService(
messageQueueConfiguration, pendingSubmissionMessageRouter, submissionConfirmationMessageRouter);
}
@Test
public void onMessage_success_pendingSubmissionMessage() throws JMSException {
SQSTextMessage mockMessage = mock(SQSTextMessage.class);
when(mockMessage.getText()).thenReturn(pendingSubmissionMessageJson);
assertDoesNotThrow(() -> {
messageQueueListenerService.onMessage(mockMessage);
// Verify that only the pending submission handler is called and message acknowledged
verify(pendingSubmissionMessageRouter, times(1)).handlePendingSubmissionMessage(any());
verify(submissionConfirmationMessageRouter, never()).handleSubmissionConfirmationMessage(any());
verify(mockMessage, times(1)).acknowledge();
});
}
@Test
public void onMessage_success_submissionConfirmationMessage() throws JMSException {
SQSTextMessage mockMessage = mock(SQSTextMessage.class);
when(mockMessage.getText()).thenReturn(submissionConfirmationMessageJson);
assertDoesNotThrow(() -> {
messageQueueListenerService.onMessage(mockMessage);
// Verify that only the submission confirmation handler is called and message acknowledged
verify(pendingSubmissionMessageRouter, never()).handlePendingSubmissionMessage(any());
verify(submissionConfirmationMessageRouter, times(1)).handleSubmissionConfirmationMessage(any());
verify(mockMessage, times(1)).acknowledge();
});
}
@Test
public void onMessage_exceptionThrown() throws JMSException {
SQSTextMessage mockMessage = mock(SQSTextMessage.class);
when(mockMessage.getText())
.thenReturn(
"""
{"some_key":"some_val_without_a_closing_string}\s
""");
messageQueueListenerService.onMessage(mockMessage);
// Verify that handlers and message.acknowledge() are not called
verify(pendingSubmissionMessageRouter, never()).handlePendingSubmissionMessage(any());
verify(submissionConfirmationMessageRouter, never()).handleSubmissionConfirmationMessage(any());
verify(mockMessage, never()).acknowledge();
}
}

View file

@ -1,84 +0,0 @@
package gov.irs.directfile.status.services;
import java.util.ArrayList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.exception.UnsupportedVersionException;
import gov.irs.directfile.models.message.pending.PendingSubmissionMessageVersion;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
import gov.irs.directfile.models.message.pending.payload.PendingSubmissionPayloadV1;
import gov.irs.directfile.status.services.handlers.pending.PendingSubmissionV1Handler;
import gov.irs.directfile.status.services.handlers.pending.UnsupportedMessageVersionHandler;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class PendingSubmissionMessageRouterTest {
@Mock
public PendingSubmissionV1Handler pendingSubmissionV1Handler;
@Mock
public UnsupportedMessageVersionHandler unsupportedMessageVersionHandler;
private PendingSubmissionMessageRouter pendingSubmissionMessageRouter;
@BeforeEach
public void setup() {
pendingSubmissionMessageRouter =
new PendingSubmissionMessageRouter(unsupportedMessageVersionHandler, pendingSubmissionV1Handler);
}
@Test
public void itGetsHandlerWhenProvidedAValidPendingSubmissionMessageVersion() {
// Arrange: Create a VersionedPendingSubmissionMessage, with a header specifying V1
AbstractPendingSubmissionPayload payload = new PendingSubmissionPayloadV1(new ArrayList<>());
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> queueMessage =
new VersionedPendingSubmissionMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
PendingSubmissionMessageVersion.V1.getVersion()));
// Act: Call handlePendingSubmissionMessage()
assertDoesNotThrow(() -> {
pendingSubmissionMessageRouter.handlePendingSubmissionMessage(queueMessage);
// Assert: Expect that an exception was not thrown, and that the
// PendingSubmissionV1Handler.handlePendingSubmissionMessage() was called
verify(pendingSubmissionV1Handler, times(1)).handlePendingSubmissionMessage(any());
});
}
@Test
public void itHandlesUnsupportedVersions() {
// Arrange: Create a VersionedPendingSubmissionMessage, with a header specifying an unsupported version
AbstractPendingSubmissionPayload payload = new PendingSubmissionPayloadV1(new ArrayList<>());
String unsupportedVersion = "9.0.EGG";
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> messageWithUnsupportedVersion =
new VersionedPendingSubmissionMessage<>(
payload,
new QueueMessageHeaders().addHeader(MessageHeaderAttribute.VERSION, unsupportedVersion));
// Act: call handlePendingSubmissionMessage()
assertThrows(UnsupportedVersionException.class, () -> {
pendingSubmissionMessageRouter.handlePendingSubmissionMessage(messageWithUnsupportedVersion);
// Assert: Check that we called the UnsupportedVersionHandler
verify(unsupportedMessageVersionHandler, times(1)).handlePendingSubmissionMessage(any());
});
}
}

View file

@ -1,108 +0,0 @@
package gov.irs.directfile.status.services;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.PublisherException;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.status.StatusChangeMessageVersion;
import gov.irs.directfile.models.message.status.VersionedStatusChangeMessage;
import gov.irs.directfile.models.message.status.payload.AbstractStatusChangePayload;
import gov.irs.directfile.models.message.status.payload.StatusChangePayloadV1;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.doThrow;
@ExtendWith(MockitoExtension.class)
class StatusChangeMessageServiceTest {
private StatusChangeMessageService statusChangeMessageService;
@Mock
private StatusChangeSqsPublisher sqsPublisher;
@Mock
private StatusChangeSnsPublisher snsPublisher;
private static final Map<String, List<String>> v1Object;
private static final String v1Json;
static {
ObjectMapper objectMapper = new ObjectMapper();
v1Object = Map.of(
"accepted", List.of("11111111", "33333333"),
"rejected", List.of("22222222", "44444444"));
VersionedStatusChangeMessage<AbstractStatusChangePayload> v1VersionedObject =
new VersionedStatusChangeMessage<>(
new StatusChangePayloadV1(v1Object),
new QueueMessageHeaders()
.addHeader(MessageHeaderAttribute.VERSION, StatusChangeMessageVersion.V1.getVersion()));
try {
v1Json = objectMapper.writeValueAsString(v1VersionedObject);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@BeforeEach
public void setup() {
statusChangeMessageService =
new StatusChangeMessageService(List.of(sqsPublisher, snsPublisher), new ObjectMapper());
}
@Test
public void publishStatusChangePayloadV1_success() {
statusChangeMessageService.publishStatusChangePayloadV1(v1Object);
ArgumentCaptor<String> sqsPublishArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(sqsPublisher, times(1)).publish(sqsPublishArgumentCaptor.capture());
assertEquals(v1Json, sqsPublishArgumentCaptor.getValue());
ArgumentCaptor<String> snsPublishArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(snsPublisher, times(1)).publish(snsPublishArgumentCaptor.capture());
assertEquals(v1Json, snsPublishArgumentCaptor.getValue());
}
@ParameterizedTest
@NullAndEmptySource
public void publishStatusChangePayloadV1_nullOrEmptyPublishers(List<StatusChangePublisher> publishers) {
StatusChangeMessageService noPublishersService = new StatusChangeMessageService(publishers, new ObjectMapper());
noPublishersService.publishStatusChangePayloadV1(v1Object);
verify(sqsPublisher, never()).publish(any());
verify(snsPublisher, never()).publish(any());
}
@Test
public void publishSubmissionConfirmationPayloadV1_writeValueAsStringFails() throws JsonProcessingException {
ObjectMapper mockMapper = Mockito.mock(ObjectMapper.class);
StatusChangeMessageService testService =
new StatusChangeMessageService(List.of(sqsPublisher, snsPublisher), mockMapper);
doThrow(new JsonProcessingException("bad json") {}).when(mockMapper).writeValueAsString(any());
assertThrows(PublisherException.class, () -> testService.publishStatusChangePayloadV1(v1Object));
}
@Test
public void publishStatusChangePayloadV1_publishFails() {
doThrow(new PublisherException("could not publish")).when(snsPublisher).publish(any());
assertThrows(PublisherException.class, () -> statusChangeMessageService.publishStatusChangePayloadV1(v1Object));
}
}

View file

@ -1,112 +0,0 @@
package gov.irs.directfile.status.services;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.confirmation.SubmissionConfirmationMessageVersion;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV1;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV2;
import gov.irs.directfile.models.message.exception.UnsupportedVersionException;
import gov.irs.directfile.status.services.handlers.confirmation.SubmissionConfirmationV1Handler;
import gov.irs.directfile.status.services.handlers.confirmation.SubmissionConfirmationV2Handler;
import gov.irs.directfile.status.services.handlers.confirmation.UnsupportedMessageVersionHandler;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class SubmissionConfirmationMessageRouterTest {
@Mock
public SubmissionConfirmationV1Handler submissionConfirmationV1Handler;
@Mock
public SubmissionConfirmationV2Handler submissionConfirmationV2Handler;
@Mock
public UnsupportedMessageVersionHandler unsupportedMessageVersionHandler;
private SubmissionConfirmationMessageRouter submissionConfirmationMessageRouter;
@BeforeEach
public void setup() {
submissionConfirmationMessageRouter = new SubmissionConfirmationMessageRouter(
unsupportedMessageVersionHandler, submissionConfirmationV1Handler, submissionConfirmationV2Handler);
}
@Test
public void itGetsHandlerWhenProvidedAValidConfirmationMessageVersion() {
// Arrange: Create a VersionedSubmissionConfirmationMessage, with a header specifying V1
AbstractSubmissionConfirmationPayload payload = new SubmissionConfirmationPayloadV1(List.of());
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> queueMessage =
new VersionedSubmissionConfirmationMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
SubmissionConfirmationMessageVersion.V1.getVersion()));
// Act: Call handleSubmissionConfirmationMessage()
assertDoesNotThrow(() -> {
submissionConfirmationMessageRouter.handleSubmissionConfirmationMessage(queueMessage);
// Assert: Expect that an exception was not thrown, and that the
// SubmissionConfirmationV1Handler.handleSubmissionConfirmationMessage() was called
verify(submissionConfirmationV1Handler, times(1)).handleSubmissionConfirmationMessage(any());
});
}
@Test
public void givenConfirmationMessageV2_whenHandleSubmissionConfirmationMessage_thenCorrectHandlerChosen() {
// Arrange: Create a VersionedSubmissionConfirmationMessage, with a header specifying V2
AbstractSubmissionConfirmationPayload payload = new SubmissionConfirmationPayloadV2(List.of());
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> queueMessage =
new VersionedSubmissionConfirmationMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
SubmissionConfirmationMessageVersion.V2.getVersion()));
// Act: Call handleSubmissionConfirmationMessage()
assertDoesNotThrow(() -> {
submissionConfirmationMessageRouter.handleSubmissionConfirmationMessage(queueMessage);
// Assert: Expect that an exception was not thrown, and that the
// SubmissionConfirmationV2Handler.handleSubmissionConfirmationMessage() was called
verify(submissionConfirmationV2Handler, times(1)).handleSubmissionConfirmationMessage(any());
});
}
@Test
public void itHandlesUnsupportedVersions() {
// Arrange: Create a VersionedSubmissionMessage, with a header specifying an unsupported version
AbstractSubmissionConfirmationPayload payload = new SubmissionConfirmationPayloadV2(List.of());
String unsupportedVersion = "9.0.EGG";
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> messageWithUnsupportedVersion =
new VersionedSubmissionConfirmationMessage<>(
payload,
new QueueMessageHeaders().addHeader(MessageHeaderAttribute.VERSION, unsupportedVersion));
// Act: call handleSubmissionConfirmationMessage()
assertThrows(UnsupportedVersionException.class, () -> {
submissionConfirmationMessageRouter.handleSubmissionConfirmationMessage(messageWithUnsupportedVersion);
// Assert: Check that we called the UnsupportedVersionHandler
verify(unsupportedMessageVersionHandler, times(1)).handleSubmissionConfirmationMessage(any());
});
}
}

View file

@ -1,84 +0,0 @@
package gov.irs.directfile.status.services.handlers.confirmation;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import gov.irs.directfile.models.TaxReturnSubmissionReceipt;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.confirmation.SubmissionConfirmationMessageVersion;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV1;
import gov.irs.directfile.status.acknowledgement.PendingAcknowledgementRepository;
import gov.irs.directfile.status.acknowledgement.TaxReturnSubmissionRepository;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.Pending;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@EnableConfigurationProperties(StatusProperties.class)
@DataJpaTest
class SubmissionConfirmationV1HandlerTest {
@Autowired
PendingAcknowledgementRepository pendingRepo;
@Autowired
TaxReturnSubmissionRepository taxReturnSubmissionRepo;
@Autowired
private StatusProperties statusProperties;
@Test
public void handleSubmissionConfirmationMessage() {
SubmissionConfirmationV1Handler handler =
new SubmissionConfirmationV1Handler(pendingRepo, taxReturnSubmissionRepo, statusProperties);
TaxReturnSubmissionReceipt taxReturnSubmissionReceipt1 =
new TaxReturnSubmissionReceipt(UUID.randomUUID(), "submissionId1", "receiptId1", new Date());
TaxReturnSubmissionReceipt taxReturnSubmissionReceipt2 =
new TaxReturnSubmissionReceipt(UUID.randomUUID(), "submissionId2", "receiptId2", new Date());
List<TaxReturnSubmissionReceipt> taxReturnSubmissionReceipts =
List.of(taxReturnSubmissionReceipt1, taxReturnSubmissionReceipt2);
AbstractSubmissionConfirmationPayload payload =
new SubmissionConfirmationPayloadV1(taxReturnSubmissionReceipts);
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> queueMessage =
new VersionedSubmissionConfirmationMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
SubmissionConfirmationMessageVersion.V1.getVersion()));
handler.handleSubmissionConfirmationMessage(queueMessage);
assertEquals(2, pendingRepo.count());
Optional<Pending> pending1 = pendingRepo.GetPendingSubmission(taxReturnSubmissionReceipt1.getSubmissionId());
assertTrue(pending1.isPresent()
&& pending1.get().getSubmissionId().equals(taxReturnSubmissionReceipt1.getSubmissionId()));
Optional<Pending> pending2 = pendingRepo.GetPendingSubmission(taxReturnSubmissionReceipt2.getSubmissionId());
assertTrue(pending2.isPresent()
&& pending2.get().getSubmissionId().equals(taxReturnSubmissionReceipt2.getSubmissionId()));
assertEquals(2, taxReturnSubmissionRepo.count());
Optional<String> submissionId1 = taxReturnSubmissionRepo.getLatestSubmissionIdByTaxReturnId(
taxReturnSubmissionReceipt1.getTaxReturnId());
assertTrue(
submissionId1.isPresent() && submissionId1.get().equals(taxReturnSubmissionReceipt1.getSubmissionId()));
Optional<String> submissionId2 = taxReturnSubmissionRepo.getLatestSubmissionIdByTaxReturnId(
taxReturnSubmissionReceipt2.getTaxReturnId());
assertTrue(
submissionId2.isPresent() && submissionId2.get().equals(taxReturnSubmissionReceipt2.getSubmissionId()));
}
}

View file

@ -1,117 +0,0 @@
package gov.irs.directfile.status.services.handlers.confirmation;
import java.util.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import gov.irs.directfile.models.TaxReturnSubmissionReceipt;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.confirmation.SubmissionConfirmationMessageVersion;
import gov.irs.directfile.models.message.confirmation.VersionedSubmissionConfirmationMessage;
import gov.irs.directfile.models.message.confirmation.payload.AbstractSubmissionConfirmationPayload;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV2;
import gov.irs.directfile.models.message.confirmation.payload.SubmissionConfirmationPayloadV2Entry;
import gov.irs.directfile.models.message.event.SubmissionEventTypeEnum;
import gov.irs.directfile.status.acknowledgement.PendingAcknowledgementRepository;
import gov.irs.directfile.status.acknowledgement.TaxReturnSubmissionRepository;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.Pending;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@EnableConfigurationProperties(StatusProperties.class)
@DataJpaTest
class SubmissionConfirmationV2HandlerTest {
@Autowired
PendingAcknowledgementRepository pendingRepo;
@Autowired
TaxReturnSubmissionRepository taxReturnSubmissionRepo;
@Autowired
StatusProperties statusProperties;
@Test
public void handleSubmissionConfirmationMessage() {
SubmissionConfirmationV2Handler handler =
new SubmissionConfirmationV2Handler(pendingRepo, taxReturnSubmissionRepo, statusProperties);
TaxReturnSubmissionReceipt taxReturnSubmissionReceipt1 =
new TaxReturnSubmissionReceipt(UUID.randomUUID(), "submissionId1", "receiptId1", new Date());
SubmissionConfirmationPayloadV2Entry entry1 = new SubmissionConfirmationPayloadV2Entry(
taxReturnSubmissionReceipt1, SubmissionEventTypeEnum.SUBMITTED, Map.of());
TaxReturnSubmissionReceipt taxReturnSubmissionReceipt2 =
new TaxReturnSubmissionReceipt(UUID.randomUUID(), "submissionId2", "receiptId2", new Date());
SubmissionConfirmationPayloadV2Entry entry2 = new SubmissionConfirmationPayloadV2Entry(
taxReturnSubmissionReceipt2, SubmissionEventTypeEnum.FAILED, Map.of());
TaxReturnSubmissionReceipt taxReturnSubmissionReceipt3 =
new TaxReturnSubmissionReceipt(UUID.randomUUID(), "submissionId3", "receiptId3", new Date());
SubmissionConfirmationPayloadV2Entry entry3 = new SubmissionConfirmationPayloadV2Entry(
taxReturnSubmissionReceipt3, SubmissionEventTypeEnum.SUBMITTED, Map.of());
List<SubmissionConfirmationPayloadV2Entry> entries = List.of(entry1, entry2, entry3);
AbstractSubmissionConfirmationPayload payload = new SubmissionConfirmationPayloadV2(entries);
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> queueMessage =
new VersionedSubmissionConfirmationMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
SubmissionConfirmationMessageVersion.V2.getVersion()));
handler.handleSubmissionConfirmationMessage(queueMessage);
assertEquals(2, pendingRepo.count());
Optional<Pending> pending1 = pendingRepo.GetPendingSubmission(taxReturnSubmissionReceipt1.getSubmissionId());
assertTrue(pending1.isPresent()
&& pending1.get().getSubmissionId().equals(taxReturnSubmissionReceipt1.getSubmissionId()));
Optional<Pending> pending2 = pendingRepo.GetPendingSubmission(taxReturnSubmissionReceipt3.getSubmissionId());
assertTrue(pending2.isPresent()
&& pending2.get().getSubmissionId().equals(taxReturnSubmissionReceipt3.getSubmissionId()));
assertEquals(2, taxReturnSubmissionRepo.count());
Optional<String> submissionId1 = taxReturnSubmissionRepo.getLatestSubmissionIdByTaxReturnId(
taxReturnSubmissionReceipt1.getTaxReturnId());
assertTrue(
submissionId1.isPresent() && submissionId1.get().equals(taxReturnSubmissionReceipt1.getSubmissionId()));
Optional<String> submissionId2 = taxReturnSubmissionRepo.getLatestSubmissionIdByTaxReturnId(
taxReturnSubmissionReceipt3.getTaxReturnId());
assertTrue(
submissionId2.isPresent() && submissionId2.get().equals(taxReturnSubmissionReceipt3.getSubmissionId()));
}
@Test
public void handleSubmissionConfirmationMessage_noSubmittedReturns() {
SubmissionConfirmationV2Handler handler =
new SubmissionConfirmationV2Handler(pendingRepo, taxReturnSubmissionRepo, statusProperties);
TaxReturnSubmissionReceipt taxReturnSubmissionReceipt1 =
new TaxReturnSubmissionReceipt(UUID.randomUUID(), "submissionId1", "receiptId1", new Date());
SubmissionConfirmationPayloadV2Entry entry1 = new SubmissionConfirmationPayloadV2Entry(
taxReturnSubmissionReceipt1, SubmissionEventTypeEnum.FAILED, Map.of());
List<SubmissionConfirmationPayloadV2Entry> entries = List.of(entry1);
AbstractSubmissionConfirmationPayload payload = new SubmissionConfirmationPayloadV2(entries);
VersionedSubmissionConfirmationMessage<AbstractSubmissionConfirmationPayload> queueMessage =
new VersionedSubmissionConfirmationMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
SubmissionConfirmationMessageVersion.V2.getVersion()));
handler.handleSubmissionConfirmationMessage(queueMessage);
assertEquals(0, pendingRepo.count());
assertEquals(0, taxReturnSubmissionRepo.count());
}
}

View file

@ -1,82 +0,0 @@
package gov.irs.directfile.status.services.handlers.pending;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import gov.irs.directfile.models.TaxReturnIdAndSubmissionId;
import gov.irs.directfile.models.message.MessageHeaderAttribute;
import gov.irs.directfile.models.message.QueueMessageHeaders;
import gov.irs.directfile.models.message.pending.PendingSubmissionMessageVersion;
import gov.irs.directfile.models.message.pending.VersionedPendingSubmissionMessage;
import gov.irs.directfile.models.message.pending.payload.AbstractPendingSubmissionPayload;
import gov.irs.directfile.models.message.pending.payload.PendingSubmissionPayloadV1;
import gov.irs.directfile.status.acknowledgement.PendingAcknowledgementRepository;
import gov.irs.directfile.status.acknowledgement.TaxReturnSubmissionRepository;
import gov.irs.directfile.status.config.StatusProperties;
import gov.irs.directfile.status.domain.Pending;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@EnableConfigurationProperties(StatusProperties.class)
@DataJpaTest
class PendingSubmissionV1HandlerTest {
@Autowired
PendingAcknowledgementRepository pendingRepo;
@Autowired
TaxReturnSubmissionRepository taxReturnSubmissionRepo;
@Autowired
StatusProperties statusProperties;
@Test
public void handlePendingSubmissionMessage() {
PendingSubmissionV1Handler handler =
new PendingSubmissionV1Handler(pendingRepo, taxReturnSubmissionRepo, statusProperties);
TaxReturnIdAndSubmissionId taxReturnIdAndSubmissionId1 =
new TaxReturnIdAndSubmissionId(UUID.randomUUID(), "submissionId1");
TaxReturnIdAndSubmissionId taxReturnIdAndSubmissionId2 =
new TaxReturnIdAndSubmissionId(UUID.randomUUID(), "submissionId2");
List<TaxReturnIdAndSubmissionId> taxReturnIdAndSubmissionIds =
List.of(taxReturnIdAndSubmissionId1, taxReturnIdAndSubmissionId2);
AbstractPendingSubmissionPayload payload = new PendingSubmissionPayloadV1(taxReturnIdAndSubmissionIds);
VersionedPendingSubmissionMessage<AbstractPendingSubmissionPayload> queueMessage =
new VersionedPendingSubmissionMessage<>(
payload,
new QueueMessageHeaders()
.addHeader(
MessageHeaderAttribute.VERSION,
PendingSubmissionMessageVersion.V1.getVersion()));
handler.handlePendingSubmissionMessage(queueMessage);
assertEquals(2, pendingRepo.count());
Optional<Pending> pending1 = pendingRepo.GetPendingSubmission(taxReturnIdAndSubmissionId1.getSubmissionId());
assertTrue(pending1.isPresent()
&& pending1.get().getSubmissionId().equals(taxReturnIdAndSubmissionId1.getSubmissionId()));
Optional<Pending> pending2 = pendingRepo.GetPendingSubmission(taxReturnIdAndSubmissionId2.getSubmissionId());
assertTrue(pending2.isPresent()
&& pending2.get().getSubmissionId().equals(taxReturnIdAndSubmissionId2.getSubmissionId()));
assertEquals(2, taxReturnSubmissionRepo.count());
Optional<String> submissionId1 = taxReturnSubmissionRepo.getLatestSubmissionIdByTaxReturnId(
taxReturnIdAndSubmissionId1.getTaxReturnId());
assertTrue(
submissionId1.isPresent() && submissionId1.get().equals(taxReturnIdAndSubmissionId1.getSubmissionId()));
Optional<String> submissionId2 = taxReturnSubmissionRepo.getLatestSubmissionIdByTaxReturnId(
taxReturnIdAndSubmissionId2.getTaxReturnId());
assertTrue(
submissionId2.isPresent() && submissionId2.get().equals(taxReturnIdAndSubmissionId2.getSubmissionId()));
}
}

View file

@ -1,38 +0,0 @@
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect #Override PostgreSQLDialect defined in src/main/resources/application.yaml, SpringBoot v3.3.0 enforced using dialect
status:
private-key: src/test/resources/private.p12
private-key-password: 000
unit-testing: true
etin: ${STATUS_ETIN:99999}
asid: 000
efin: 00
prod: false
toolkit: 000
root-translation-key: status
translation-key-splitter: .
ack-poll-in-milliseconds: 1200000
messageQueue:
url: http://localhost:4577/000000000000/pending-submission-queue
region: us-west-2
accessKey: accessKey
secretKey: secretKey
sqs-message-handling-enabled: false
direct-file:
local-encryption:
local-wrapping-key: lYIIKutUatfMwdEGB8qtUpQc3wMNtT5pfM+zW57qrv4=
aws:
enabled: false
default-credentials-provider-chain-enabled: false
access-key: test
secret-key: test
region: us-west-2
kmsEndpoint: http://directfile.test

View file

@ -1,2 +0,0 @@
# This is an empty logging properties file because the MeF SDK requires one
# Because we use some MeF Java classes in our code, it requires us to have this.

View file

@ -1,5 +0,0 @@
**/application-local.*
/src/main/java/gov/irs/directfile/submit/xml
.env*
.git/
Dockerfile*

View file

@ -1,45 +0,0 @@
HELP.md
target/
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
sample_runner.sh
/test/
/src/main/java/resources/test
*.jar
/target
/.secret/
/src/main/resources/application-local.*
/src/main/resources/test/run
audit_log.txt
### Spot Bugs ###
/src/main/resources/spotbugs/output/spotbugs.xml

View file

@ -1,5 +0,0 @@
changeLogFile=db/changelog.yaml
url=jdbc:postgresql://localhost:32768/directfile-submit
username=postgres
password=postgres
changesetAuthor=directfile

Some files were not shown because too many files have changed in this diff Show more