├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE
│ ├── bug.md
│ └── feature.md
└── workflows
│ ├── format-java-code.yml
│ ├── reusable-workflow-to-run-tests.yml
│ ├── trigger-health-check-on-a-schedule.yml
│ ├── trigger-new-updated-and-unit-tests-on-pull-request.yml
│ ├── trigger-smoke-tests-on-merge-to-main.yml
│ ├── trigger-specific-targetted-tests-on-an-external-event.yml
│ └── trigger-tests-manually.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── README.md
├── docker-compose.yml
├── docs
├── README-CODE-FORMATTING.md
└── README-GIT-CRYPT.md
├── drawings
├── api-test-framework-design.drawio
├── end-to-end-test-workflow.drawio
└── github-pr-workflow.drawio
├── git-crypt-key-restpro
├── images
├── api-test-framework-design.png
├── end-to-end-test-workflow.png
└── github-pr-workflow.png
├── pom.xml
├── restpro.iml
└── src
├── main
├── java
│ └── org
│ │ └── powertester
│ │ ├── annotations
│ │ ├── CsvTest.java
│ │ ├── FailingTest.java
│ │ ├── FlakyTest.java
│ │ ├── HealthCheckTest.java
│ │ ├── RegressionTest.java
│ │ ├── SmokeTest.java
│ │ └── UnitTest.java
│ │ ├── auth
│ │ ├── AuthBody.java
│ │ ├── Scope.java
│ │ └── TokenFactory.java
│ │ ├── basespec
│ │ └── SpecFactory.java
│ │ ├── booking
│ │ ├── Booking.java
│ │ ├── BookingAPI.java
│ │ ├── BookingResponse.java
│ │ └── entitites
│ │ │ └── Bookingdates.java
│ │ ├── config
│ │ ├── TestConfig.java
│ │ └── TestEnv.java
│ │ ├── data
│ │ └── TestData.java
│ │ ├── database
│ │ └── DBConnection.java
│ │ ├── extensions
│ │ ├── LoggingExtension.java
│ │ ├── ReportingExtension.java
│ │ ├── TestRunExtension.java
│ │ ├── TimingExtension.java
│ │ └── report
│ │ │ ├── ElasticLowLevelRestClientFactory.java
│ │ │ ├── ElasticServerChoices.java
│ │ │ ├── PublishResults.java
│ │ │ └── TestRunMetaData.java
│ │ ├── healthcheck
│ │ └── HealthCheckAPI.java
│ │ └── utils
│ │ └── DateUtils.java
└── resources
│ ├── META-INF
│ └── services
│ │ └── org.junit.jupiter.api.extension.Extension
│ ├── application.conf
│ ├── choices.conf
│ ├── common
│ └── secrets.conf
│ ├── develop
│ ├── secrets.conf
│ ├── test-data.conf
│ └── user-info.conf
│ ├── junit-platform.properties
│ ├── localhost
│ ├── secrets.conf
│ ├── test-data.conf
│ └── user-info.conf
│ ├── logback.xml
│ ├── schemas
│ ├── create-booking-schema.json
│ └── read-update-booking-schema.json
│ └── staging
│ ├── secrets.conf
│ ├── test-data.conf
│ └── user-info.conf
└── test
├── java
├── DoNotUseRestAssuredOnlyTests.java
├── asserts
│ ├── ValidateDB.java
│ └── VerifyResponse.java
├── booking
│ ├── BookingTests.java
│ ├── VerifyBookingResponse.java
│ └── explore-booking-api.http
├── env
│ └── http-client.env.json
├── healthcheck
│ └── HealthCheckTests.java
└── unittests
│ └── CSVUnitTests.java
└── resources
└── testdata
└── data.csv
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Uncomment the following line to enable git-crypt (if you wish to encrypt secrets)
2 | # secrets.conf filter=git-crypt diff=git-crypt
3 |
4 | # Prevent encrypting .gitattributes file :
5 | .gitattributes !filter !diff
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | # Title
2 | Replace this text with title.
3 |
4 | # Description
5 | Replace this text with description.
6 |
7 | # Steps to reproduce
8 | 1. Step 1:
9 | 2. Step 2:
10 | 3. so on..
11 |
12 | # Result (Expected vs Actual)
13 | Replace this text with what was expected vs what was found.
14 |
15 | # Attachment
16 | Add attachments to the ticket.
17 |
18 | # Ticket ID
19 | - [Bug_ID](Replace this text with bug ticket url.)
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | # Title
2 | Replace this text with title.
3 |
4 | # Description
5 | Replace this text with description.
6 |
7 | # Ticket ID
8 | - [Ticket_ID](Replace this text with url.)
9 |
--------------------------------------------------------------------------------
/.github/workflows/format-java-code.yml:
--------------------------------------------------------------------------------
1 | name: Format Java Code as per Google Java Format program
2 |
3 | on:
4 | pull_request:
5 | branches: [ main ]
6 |
7 | jobs:
8 |
9 | formatting:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3 # v2 minimum required
13 | - uses: axel-op/googlejavaformat-action@v3
14 | with:
15 | args: "--skip-sorting-imports --replace"
16 | skip-commit: true
17 |
18 | - name: Print diffs
19 | run: |
20 | echo "Please read the README.md file and install pre-commit hooks to properly format the files and resolve this error"
21 | git --no-pager diff --exit-code
22 |
--------------------------------------------------------------------------------
/.github/workflows/reusable-workflow-to-run-tests.yml:
--------------------------------------------------------------------------------
1 | name: reusable-workflow-to-run-tests
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | test-environment:
7 | required: false
8 | type: string
9 | default: "STAGING"
10 |
11 | tags-of-tests-to-include:
12 | required: true
13 | type: string
14 |
15 | tags-of-tests-to-exclude:
16 | required: false
17 | type: string
18 | default: "flaky, failing"
19 |
20 | test-files-to-include:
21 | required: false
22 | type: string
23 | default: ""
24 |
25 | run-name:
26 | required: false
27 | type: string
28 | default: "CI"
29 |
30 | jobs:
31 | run-tests:
32 |
33 | runs-on: ubuntu-latest
34 |
35 | steps:
36 | - uses: actions/checkout@v3
37 |
38 | - name: Set up JDK 17
39 | uses: actions/setup-java@v3
40 | with:
41 | java-version: '17'
42 | distribution: 'temurin'
43 | cache: maven
44 |
45 | # Uncomment the following lines if you are using git-crypt to encrypt secrets
46 | # - name: Unlock secrets
47 | # uses: sliteteam/github-action-git-crypt-unlock@1.2.0
48 | # env:
49 | # GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }}
50 |
51 | - name: Test with Maven
52 | run: >
53 | mvn
54 | -DTEST_ENV="${{ inputs.test-environment }}"
55 | -Dgroups="${{ inputs.tags-of-tests-to-include }}"
56 | -Dtest="${{ inputs.test-files-to-include }}"
57 | -DexcludedGroups="${{ inputs.tags-of-tests-to-exclude }}"
58 | -DTRIGGERED_BY="${{ github.event_name }}"
59 | -DRUN_NAME="${{ inputs.run-name }}"
60 | -B package --file pom.xml
61 |
--------------------------------------------------------------------------------
/.github/workflows/trigger-health-check-on-a-schedule.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: trigger-tests-on-a-schedule
5 |
6 | on:
7 | schedule:
8 | # * is a special character in YAML so you have to quote this string
9 | # To set a new schedule use this: https://crontab.guru/
10 | # To get the right time for your timezone use : https://www.worldtimebuddy.com/
11 | - cron: "0 * * * MON-FRI" # “At every hour on every week day. ”
12 |
13 | jobs:
14 | trigger-tests-on-a-schedule:
15 | uses: ./.github/workflows/reusable-workflow-to-run-tests.yml
16 | with:
17 | tags-of-tests-to-include: "healthcheck" # -Dgroups="" means execute all test cases
18 | tags-of-tests-to-exclude: "" # -DexcludedGroups="" means don't exclude any test case
19 | secrets: inherit
20 |
--------------------------------------------------------------------------------
/.github/workflows/trigger-new-updated-and-unit-tests-on-pull-request.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: trigger-tests-on-a-pull-request
5 |
6 | on:
7 | pull_request:
8 | branches: [ main ]
9 |
10 | # Allows us to run this workflow manually as well from the Actions tab
11 | workflow_dispatch:
12 |
13 | jobs:
14 | touched-test-files:
15 | runs-on: ubuntu-latest # windows-latest || macos-latest
16 | name: Touched test files
17 | permissions:
18 | pull-requests: read
19 | outputs:
20 | test_files_to_run: ${{ steps.list_touched_files.outputs.test_files }}
21 | steps:
22 | - name: Fetch changed files
23 | id: changed-files
24 | uses: tj-actions/changed-files@v44
25 | with:
26 | # Avoid using single or double quotes for multiline patterns
27 | files: src/test/**/*.java
28 |
29 | - name: List changed test files
30 | id: list_touched_files
31 | env:
32 | ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_and_modified_files }}
33 | run: |
34 | tests="" # Initialize the empty variable. If passed empty, this will have no effect on the command.
35 | for file in ${ALL_CHANGED_FILES}; do
36 | filename=$(basename "$file")
37 | echo "$filename was changed"
38 | if [ -z "$tests" ]; then
39 | tests="$filename"
40 | else
41 | tests="$tests,$filename"
42 | fi
43 | done
44 | echo "--grep input to send: $tests"
45 | echo "test_files=$tests" >> "$GITHUB_OUTPUT"
46 |
47 | trigger-tests-on-a-pull-request:
48 | needs: touched-test-files
49 | uses: ./.github/workflows/reusable-workflow-to-run-tests.yml
50 | with:
51 | tags-of-tests-to-include: "unit"
52 | test-files-to-include: "${{needs.touched-test-files.outputs.test_files_to_run}}"
53 | tags-of-tests-to-exclude: ""
54 | run-name: "${{ github.event.pull_request.title }}"
55 | secrets: inherit
56 |
--------------------------------------------------------------------------------
/.github/workflows/trigger-smoke-tests-on-merge-to-main.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: trigger-tests-on-merge-to-main
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 |
10 | jobs:
11 | trigger-tests-on-merge-to-main:
12 | uses: ./.github/workflows/reusable-workflow-to-run-tests.yml
13 | with:
14 | tags-of-tests-to-include: "smoke"
15 | secrets: inherit
16 |
--------------------------------------------------------------------------------
/.github/workflows/trigger-specific-targetted-tests-on-an-external-event.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: trigger-tests-on-an-external-event
5 |
6 | on:
7 | repository_dispatch:
8 | types: [deployment-completed-notification-event]
9 |
10 | jobs:
11 | trigger-tests-on-an-external-event:
12 | uses: ./.github/workflows/reusable-workflow-to-run-tests.yml
13 | with:
14 | tags-of-tests-to-include: ${{ github.event.client_payload.TAG }} # -Dgroups="" means execute all test cases
15 | tags-of-tests-to-exclude: "" # -DexcludedGroups="" means don't exclude any test case
16 | test-environment: ${{ github.event.client_payload.TEST_ENV }}
17 | secrets: inherit
18 |
--------------------------------------------------------------------------------
/.github/workflows/trigger-tests-manually.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: trigger-tests-manually
5 |
6 | on:
7 | workflow_dispatch:
8 | inputs:
9 | tags-of-tests-to-include:
10 | description: 'Tag of tests to include'
11 | required: false
12 | default: ''
13 | type: choice
14 | options:
15 | - '' # -Dgroups="" means execute all test cases
16 | - 'smoke'
17 | - 'flaky'
18 | - 'failing'
19 | - 'flaky, failing'
20 |
21 | tags-of-tests-to-exclude:
22 | description: 'Tag of tests to exclude'
23 | required: false
24 | default: ''
25 | type: choice
26 | options:
27 | - '' # -DexcludedGroups="" means don't exclude any test case
28 | - 'smoke'
29 | - 'flaky'
30 | - 'failing'
31 | - 'flaky, failing'
32 |
33 | test-environment:
34 | description: 'Test environment'
35 | required: false
36 | default: 'STAGING'
37 | type: choice
38 | options:
39 | - 'DEVELOP'
40 | - 'STAGING'
41 |
42 | jobs:
43 | trigger-tests-manually:
44 | uses: ./.github/workflows/reusable-workflow-to-run-tests.yml
45 | with:
46 | tags-of-tests-to-include: ${{ inputs.tags-of-tests-to-include }}
47 | tags-of-tests-to-exclude: ${{ inputs.tags-of-tests-to-exclude }}
48 | test-environment: ${{ inputs.test-environment }}
49 | secrets: inherit
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore all .idea files
2 | .idea
3 |
4 | # Ignore all uninterested files
5 | notes.txt
6 |
7 | # Ignore target
8 | target
9 |
10 | # Ignore git crypt key (in a real production world scenario).
11 | # I am not ingoring it here since its an open source project and anyone who wants to clone the project would need this
12 | # key to work with. Ideally, if you were working in a company, this key would be preserved in a password manager such
13 | # as 1password from where everyone could download and decrypt files.
14 | # git-crypt-key-restpro
15 |
16 | # Ignore mac file
17 | .DS_Store
18 |
19 | # Ignore .cache files
20 | .cache
21 |
22 | # Ignore secrets file
23 | **/http-client.private.env.json
24 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v4.4.0
4 | hooks:
5 | - id: check-yaml
6 | - id: check-json
7 | - id: check-xml
8 | - id: mixed-line-ending
9 | - id: end-of-file-fixer
10 | - id: trailing-whitespace
11 |
12 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
13 | rev: 54f7f7c5cb9501110b928f2a98faa8024142f8d7
14 | hooks:
15 | - id: pretty-format-java
16 | args: [--autofix]
17 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "Appium",
4 | "dockerignore",
5 | "dockerized",
6 | "powertester",
7 | "Qube"
8 | ],
9 | "java.configuration.updateBuildConfiguration": "automatic",
10 | "java.compile.nullAnalysis.mode": "automatic"
11 | }
12 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | pramodyadav027@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🦾 restpro
2 |
3 | Test RestAPIS like a PRO.
4 |
5 | 
6 | 
7 | 
8 | 
9 | 
10 | 
11 | 
12 |
13 | > NOTE:
14 | > 1. I found that a few users who tried to use this project struggled to use git-crypt properly to decrypt encrypted secrets and thus were not able to move forward with rest of learnings too.
15 | > 2. To make the entry barrier minimum for new users, I am removing encryption from [secrets.conf](src/main/resources/develop/secrets.conf) files and storing them as plain text for training purpose.
16 | > 3. That said, in a real project you should use the instructions mentioned here in this readme file [**git-crypt**](docs/README-GIT-CRYPT.md) to encrypt/decrypt secrets and MUST not store your secrets in plain text.
17 |
18 | ## Application under test
19 |
20 | [Restful booker](https://restful-booker.herokuapp.com/) - An API playground created by [Mark Winteringham](https://www.mwtestconsultancy.co.uk/) for those wanting to learn more about API testing and tools.
21 |
22 | ## 🔢 Requiring (one time) manual setup by user
23 |
24 | 1. [**JDK 11**](https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html) - as language of choice
25 | for writing this test framework.
26 | 2. [**Maven 3.8.6+**](https://maven.apache.org/) - for project dependency management and running tests in CI.
27 | 3. [**pre-commit**](docs/README-CODE-FORMATTING.md) - To have code automatically and uniformly formatted (JAVA, JSON,
28 | XML, YAML).
29 |
30 | ## 🚀 Core features
31 |
32 | - [x] Allow to put both exploratory tests (postman style http requests) and automated tests (RestAssured) side-by-side
33 | in the same framework. Usually due to Postman not being git compatible, these two tests live in two separate
34 | places/repositories.
35 | - [x] Shows how to create a decoupled test design (that minimises maintenance efforts by reducing code
36 | duplication and increases code readability by separating test intentions from implementation details).
37 | - [x] Allows users to write fluent assertions for asserting both status and response body, without any code duplication.
38 | - [x] Creates APIs that are scope/role agnostic. This allows you to use the same APIs and data to write tests for
39 | different user roles and scopes.
40 | - [x] Shows how to provide different token types for different role/scopes using a TokenFactory pattern.
41 | - [x] Shows how to reuse the same auth token in all the tests for same role/scope using a Singleton pattern.
42 | - [ ] Shows how to use health checks in the test CI to have efficient pipelines.
43 | - [ ] Shows how to insert test data dynamically in each test.
44 |
45 | > A Note on Postman style (HTTP Request) tests from AQUA:
46 | > - These tests are git compatible (unlike Postman that requires you to take a paid
47 | membership for properly version controlling its collections). In these HTTP request tests:
48 | > - User can define different environments (such as localhost, develop, staging). For each environment, user can
49 | specify env specific configuration such as hostURLs.
50 | > - Provides a way to separate secret information (such as userid/password) from other generic config information.
51 | > - Provides a way to run pre-request and post-request scripts that can be used to set variables such as auth-tokens.
52 | > - Until AQUA IDE is closedThese auth-tokens/variables are then available to any other http request that needs to use
53 | these tokens/variables.
54 | > - Provides a way to run post-request scripts that can be used to set variables, log values and write some basic tests.
55 |
56 | ## 🎯 Standard features
57 |
58 | > From RestAssured testing and [Core test framework: zero](https://github.com/PramodKumarYadav/zero) are as below:
59 |
60 | - [ ] Shows how to integrate your tests in CI (GitHub Actions).
61 | - [ ] Shows how to log your test results into a test monitoring system (such as Elastic/Kibana or DataDog)
62 | - [x] Shows how to do JSON Schema validation.
63 | - [x] Shows how to separate config from code.
64 | - [x] Shows how to deal with Secrets in local and in CI. Also on how to skip logging secret information on console.
65 | - [ ] More to be added...
66 |
67 | ## ⚙ Tool Set
68 |
69 | Key tools to be used in this core framework are:
70 |
71 | - [x] **Java** (As the core programming language)
72 | - [x] **Maven** (for automatic dependency management)
73 | - [x] **Junit 5** (for assertions)
74 | - [x] **RestAssured** (library for Rest API automation)
75 | - [x] **Slf4J/Log4J** (for logging interface and as a logging library)
76 | - [x] **Typesafe** (for application configuration for multiple test environments)
77 | - [x] **Git crypt** (for managing secrets)
78 | - [x] **Surefire** (for xml reports in CI)
79 | - [x] **Surefire Site plugin** (for html reports in CI)
80 | - [x] **GitHub** (for version control)
81 | - [x] **GitHub actions** (for continuous integration)
82 | - [x] **Faker library** (for generating random test data for different locales - germany, france, netherlands, english)
83 | - [x] **Slack integration** (for giving notifications on pull requests)
84 | - [x] **Elastic and Kibana** (for test monitoring)
85 | - [ ] **Docker** (for automating test framework's environment)
86 | - [ ] **Powershell or bash Script** (for automating building test environment)
87 | - [ ] **SonarQube/SonarLint** (for keeping your code clean and safe)
88 | - [x] **Badges** (for a quick view on your project meta and build status)
89 |
90 | ## 🧪 api-test-design
91 |
92 | 
93 |
94 | ## 🔚 end-to-end-test-workflow
95 |
96 | 
97 |
98 | ## ℹ References
99 |
100 | - Rest-assured
101 | - [Application under test (restful-booker)](https://restful-booker.herokuapp.com/apidoc/index.html)
102 | - [Info on HTTP Client](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html)
103 | - [Exploring-http-syntax](https://www.jetbrains.com/help/idea/exploring-http-syntax.html)
104 | - [http-response-handling-api-reference](https://www.jetbrains.com/help/idea/http-response-handling-api-reference.html)
105 |
106 | ## 🔌 Plugins
107 |
108 | - [HOCON](https://plugins.jetbrains.com/plugin/10481-hocon) - For config and secrets files syntax highlight.
109 | - [.ignore](https://plugins.jetbrains.com/plugin/7495--ignore) - For (dockerignore, gitignore) files syntax highlight.
110 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2.2'
2 | services:
3 | es01:
4 | image: docker.elastic.co/elasticsearch/elasticsearch:7.17.8
5 | container_name: restpro-es01
6 | environment:
7 | - node.name=es01
8 | - cluster.name=es-docker-cluster
9 | - discovery.seed_hosts=es02,es03
10 | - cluster.initial_master_nodes=es01,es02,es03
11 | - bootstrap.memory_lock=true
12 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
13 | ulimits:
14 | memlock:
15 | soft: -1
16 | hard: -1
17 | volumes:
18 | - data01:/usr/share/elasticsearch/data
19 | ports:
20 | - 9200:9200
21 | networks:
22 | - elastic
23 | es02:
24 | image: docker.elastic.co/elasticsearch/elasticsearch:7.17.8
25 | container_name: restpro-es02
26 | environment:
27 | - node.name=es02
28 | - cluster.name=es-docker-cluster
29 | - discovery.seed_hosts=es01,es03
30 | - cluster.initial_master_nodes=es01,es02,es03
31 | - bootstrap.memory_lock=true
32 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
33 | ulimits:
34 | memlock:
35 | soft: -1
36 | hard: -1
37 | volumes:
38 | - data02:/usr/share/elasticsearch/data
39 | networks:
40 | - elastic
41 | es03:
42 | image: docker.elastic.co/elasticsearch/elasticsearch:7.17.8
43 | container_name: restpro-es03
44 | environment:
45 | - node.name=es03
46 | - cluster.name=es-docker-cluster
47 | - discovery.seed_hosts=es01,es02
48 | - cluster.initial_master_nodes=es01,es02,es03
49 | - bootstrap.memory_lock=true
50 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
51 | ulimits:
52 | memlock:
53 | soft: -1
54 | hard: -1
55 | volumes:
56 | - data03:/usr/share/elasticsearch/data
57 | networks:
58 | - elastic
59 |
60 | kib01:
61 | image: docker.elastic.co/kibana/kibana:7.17.8
62 | container_name: restpro-kib01
63 | ports:
64 | - 5601:5601
65 | environment:
66 | ELASTICSEARCH_URL: http://es01:9200
67 | ELASTICSEARCH_HOSTS: '["http://es01:9200","http://es02:9200","http://es03:9200"]'
68 | networks:
69 | - elastic
70 |
71 | volumes:
72 | data01:
73 | driver: local
74 | data02:
75 | driver: local
76 | data03:
77 | driver: local
78 |
79 | networks:
80 | elastic:
81 | driver: bridge
82 |
--------------------------------------------------------------------------------
/docs/README-CODE-FORMATTING.md:
--------------------------------------------------------------------------------
1 | # Code formatting Setup (one time)
2 |
3 | Reference: https://pre-commit.com/
4 |
5 | Install pre-commit to have code automatically and uniformly formatted (JAVA, JSON, XML, YAML).
6 |
7 | ## Short version
8 |
9 | To install pre-commit do below steps (as a one time activity).
10 | - Open terminal
11 | - Install [pre-commit](https://pre-commit.com/) (a hooks package manager).
12 | - If on mac, install using: `brew install pre-commit`
13 | - If on windows, install using pip (python package manager).
14 | - Install python and pip first if not intalled already.
15 | - Then run `pip install pre-commit`
16 | - Check pre-commit version by running: `pre-commit --version`
17 | - cd to your project repository.
18 | - Run `pre-commit install`
19 | - That's it! From now on if you try to push any unformatted code to GitHub, pre-commit hook will both format the code
20 | and show the changed file for you to stage and commit.
21 |
22 | ## Long version
23 |
24 | ### Step 1
25 |
26 | - Install [pre-commit](https://pre-commit.com/) which is a package manager for installing pre-commit hooks.
27 |
28 | ### FYI Step: (fyi only - already done)
29 |
30 | > NOTE: This particular step is already done and is only here for your information. No action is required from new users,
31 | since Hook file is already copied in the project. I am adding this step here since it is good to know how this was done and so that if needed in future you can add (new) hooks here.
32 |
33 | - Search for the pre commit hook you are interested in from the list of [available hooks](https://pre-commit.com/hooks.html).
34 | - If you search with word "java" in the search window of above webpage, you will find (at least) 3 different pre commit hooks.
35 | - All of these hooks use [Google Java Format program](https://github.com/google/google-java-format) that reformats Java source code to comply with [Google's Java codestyle](https://google.github.io/styleguide/javaguide.html).
36 | - The hook we have chosen for our project is named `google-style-java` from user [matltz - on Github](https://github.com/maltzj/google-style-precommit-hook).
37 | - Click on the above GitHub URL to go to see how to use this hook. You will see an example as below
38 |
39 | ``` repos:
40 | - repo: https://github.com/maltzj/google-style-precommit-hook
41 | sha: b7e9e7fcba4a5aea463e72fe9964c14877bd8130
42 | hooks:
43 | - id: google-style-java
44 | ```
45 |
46 | - Add a file in root of this repository named `.pre-commit-config.yaml` and paste above content in it (already done. Listed for fyi only).
47 | - Change tag `sha` to `rev` since that is how it used in latest versions (already done).
48 | - Add any other hooks you are interested in here (as you will see in file `.pre-commit-config.yaml`).
49 |
50 | #### Step 2
51 |
52 | - Each user need to run below step (Only one time ever for a project repo):
53 | - To install the git hook script on your local git repository:
54 | - run `pre-commit install` from the root of this repository.
55 | - Should give result as below if successful.
56 |
57 | ```bash
58 | $ pre-commit install
59 | pre-commit installed at .git/hooks/pre-commit
60 | ```
61 |
62 | #### Step 3
63 |
64 | - That's it you are now all set!
65 | - Pre-commit will now run on every commit that you make and your code will be auto-magically formatted as per [Google's Java codestyle](https://google.github.io/styleguide/javaguide.html)
66 | - If there are any formatting violations, which they will be almost "All-of-the-time" (as below). Not only it will tell you what the failures are
67 | but it will also fix it for you (of course you would need to add those changes to stage and commit)
68 |
69 | ```bash
70 | 2023-02-25 20:40:19.553 [info] Google Java Code Style for Java......................(no files to check)Skipped
71 | Trim Trailing Whitespace.................................................Failed
72 | hook id: trailing-whitespace
73 | exit code: 1
74 | files were modified by this hook
75 |
76 | Fixing README.md
77 | ```bash
78 | - You can see failures here:
79 | - `Trim Trailing Whitespace.................................................Failed`
80 | - Here it tells you that the hook is fixing these issues for you:
81 | - ```
82 | files were modified by this hook
83 |
84 | Fixing README.md
85 | ```
86 | - Now all you need to do is stage and add those changes and that's it!
87 | - Once you stage and commit, you should see now a message as below where everything should pass.
88 | - ```
89 | 2023-02-25 20:42:57.360 [info] Google Java Code Style for Java......................(no files to check)Skipped
90 | Trim Trailing Whitespace.................................................Passed
91 | Check Yaml...........................................(no files to check)Skipped
92 | Check JSON...........................................(no files to check)Skipped
93 | Pretty format JSON...................................(no files to check)Skipped
94 | Check Xml............................................(no files to check)Skipped
95 | Fix End of Files.........................................................Passed
96 | Don't commit to branch...................................................Passed
97 | ```
98 |
99 | #### Step 4 [One time action for the whole project by any user]
100 |
101 | When pre-commit is first introduced in a project it is advised to format all the files at least
102 | one time to avoid seeing formatting changes going forward. The way you can do this is by running
103 | the below command in a powershell or a git terminal
104 |
105 | > Note: It would not work from intellij or normal terminals.
106 |
107 | `java -jar "C:\Users\Pramod Yadav\.cache\pre-commit\google-java-formatter1.16.0.jar" --replace $(git ls-files *.java)`
108 |
109 | > Here you to replace the "Pramod Yadav" user with your user directory where this jar file was downloaded.
110 | > Once formatted push all the changes in a PR and you can be assured that you dont have to see any formatting related
111 | > issues anymore in your project PRs.
112 | >
113 | [Back to readme.md](./readme.md)
114 |
--------------------------------------------------------------------------------
/docs/README-GIT-CRYPT.md:
--------------------------------------------------------------------------------
1 | # Setting up git-crypt to encrypt your secrets
2 |
3 | Our secret keys are saved in `secrets.*` files and encrypted with [`git-crypt`](https://github.com/AGWA/git-crypt#readme).
4 |
5 | > A user who is not added to the project, will not be able to use the secrets from our project and thus this is a mandatory
6 | > step to complete to be able to run the tests.
7 |
8 | ## Install Git-Crypt
9 |
10 | Reference: https://dev.to/heroku/how-to-manage-your-secrets-with-git-crypt-56ih
11 |
12 | ### Step1: Verify if you have git crypt installed on your system
13 |
14 | - macOS/Linux (run from terminal): `git-crypt --version`
15 | - Windows (run from gitbash or powershell): `git-crypt --version`
16 |
17 | ### Step2: Install git crypt, if you haven`t already
18 |
19 | > If you already see git-crypt installed in the previous step, skip.
20 |
21 | Install `git-crypt` on your system:
22 |
23 | - macOS (with homebrew) `brew install git-crypt`
24 | - Windows - [Download git-crypt.exe and place it here: C:\Program Files\Git\cmd\git-crypt.exe](https://github.com/oholovko/git-crypt-windows).
25 | - Linux `sudo apt install git-crypt`
26 | - [Manual installation](https://github.com/AGWA/git-crypt/blob/master/INSTALL.md)
27 |
28 | ## Encrypt Project
29 |
30 | > One time activity, to be done by the very first user of this project.
31 |
32 | > Note if the project is already git crypt-ed by another user, skip this section and go to the next section.
33 |
34 | Below are the steps that needs to be done only one time for the project by the very first user, who tries to
35 | set up git -crypt in the project repository. Run below commands to git crypt the project.
36 |
37 | > Install git crypt if not already installed.
38 |
39 | 1. `cd repo`
40 | 2. `git-crypt init`
41 | 3. `git-crypt export-key ./git-crypt-key-restpro`
42 | - Save this in a central password manager - like `1password`.
43 | 4. define which files to encrypt in `.gitattributes` files.
44 | - Ex: `secrets.conf filter=git-crypt diff=git-crypt`
45 | 5. Check before committing.
46 | `git-crypt status`
47 | 6. Ignore the key `git-crypt-key-restpro` from version control by adding it to the `.gitignore` file.
48 | > Ignore git crypt key (in a real production world scenario).
49 | > I am not ignoring it here since its an open source project and anyone who wants to clone the project would need this
50 | > key to work with.
51 |
52 | > Ideally, if you were working in a company, this key would be preserved in a password manager such
53 | > as 1password from where everyone could download this key and decrypt files.
54 |
55 | 7. Push files to github
56 | 8. Check if files are encrypted on github by clicking on any secrets file in Github and by verifying that
57 | text is not readable.
58 |
59 | ## Decrypt Project (in local)
60 |
61 | > One time activity, to be done by every new user of this project.
62 |
63 | Now once a user has initialized a project with git crypt
64 |
65 | 1. other new users can simply ask for the key from the first user
66 | or download it from a central password manager tool (recommended) - such as `1password` or any other password manager
67 | tool.
68 | 2. They have to copy/paste this file in their cloned projects root directory.
69 | 3. Then run (only one time) below command to see the decrypted files.
70 | - `git-crypt unlock git-crypt-key-restpro`
71 |
72 | ## Decrypt Project (in CI)
73 |
74 | Refer information [here](https://github.com/sliteteam/github-action-git-crypt-unlock), to see how this was done.
75 |
76 | > NOTE: If you are making a copy of this project and pushing it to your own GitHub repository,
77 | > remember to run the below command from say (gitbash terminal)
78 |
79 | > `git-crypt export-key ./git-crypt-key-restpro && cat ./git-crypt-key-restpro | base64`
80 |
81 | > to get the secret and add it to GitHub secret named: GIT_CRYPT_KEY.
82 | > Since this is a demo project, the key is already present in the root repository and I do not
83 | > mind exposing this secret for you below. In a real life project, this will not be part of version
84 | > control.
85 |
86 | ```commandline
87 | Pramod Yadav@DESKTOP-GPU5LFR MINGW64 ~/restpro (main)
88 | $ git-crypt export-key ./git-crypt-key-restpro && cat ./git-crypt-key-restpro | base64
89 | AEdJVENSWVBUS0VZAAAAAgAAAAAAAAABAAAABAAAAAAAAAADAAAAIODz1YHHA96CZubMzshhXpKh
90 | SIuNpeEPbQmvIcBT8UTuAAAABQAAAEAfT0bYmgWbxK+RI/mKsJXtCq9Th77lSR0D1G/5WGfspccv
91 | o/0VHDfAHi88Q6LmCL45TixGqFnLi5XmqzFwBjgdAAAAAA==
92 |
93 | ```
94 |
95 | ## Reference
96 |
97 | - [git-crypt official GitHub repo and readme file](https://github.com/AGWA/git-crypt)
98 | - [A great article on this topic by Michael Bogan for Heroku](https://dev.to/heroku/how-to-manage-your-secrets-with-git-crypt-56ih)
99 |
--------------------------------------------------------------------------------
/drawings/api-test-framework-design.drawio:
--------------------------------------------------------------------------------
1 | 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
2 |
--------------------------------------------------------------------------------
/git-crypt-key-restpro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PramodKumarYadav/restpro/717ba2126d92abe23c0c908667c31c0aa9514098/git-crypt-key-restpro
--------------------------------------------------------------------------------
/images/api-test-framework-design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PramodKumarYadav/restpro/717ba2126d92abe23c0c908667c31c0aa9514098/images/api-test-framework-design.png
--------------------------------------------------------------------------------
/images/end-to-end-test-workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PramodKumarYadav/restpro/717ba2126d92abe23c0c908667c31c0aa9514098/images/end-to-end-test-workflow.png
--------------------------------------------------------------------------------
/images/github-pr-workflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PramodKumarYadav/restpro/717ba2126d92abe23c0c908667c31c0aa9514098/images/github-pr-workflow.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.powertester
8 | restpro
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 17
13 | ${java.version}
14 | ${java.version}
15 | 3.10.1
16 | 3.0.0-M5
17 | 3.11.0
18 | 5.10.1
19 | 1.18.30
20 | 1.4.2
21 | 8.6.1
22 | 2.14.1
23 | 2.0.1
24 | 5.3.2
25 | 3.24.2
26 | 5.1.0
27 | 4.2.0
28 | 2.0.9
29 | 1.4.13
30 | 2.0.2
31 |
32 |
33 |
34 |
35 | io.rest-assured
36 | rest-assured
37 | ${rest-assured.version}
38 |
39 |
40 | io.rest-assured
41 | json-schema-validator
42 | ${rest-assured.version}
43 | test
44 |
45 |
46 |
47 | org.junit.jupiter
48 | junit-jupiter
49 | ${junit.jupiter.version}
50 | test
51 |
52 |
53 |
54 | org.assertj
55 | assertj-core
56 | ${assertj-core.version}
57 | test
58 |
59 |
60 |
61 | org.projectlombok
62 | lombok
63 | ${lombok.version}
64 |
65 |
66 | org.junit.platform
67 | junit-platform-console-standalone
68 | 1.9.0-M1
69 |
70 |
71 |
72 | com.typesafe
73 | config
74 | ${typesafe.version}
75 |
76 |
77 | co.elastic.clients
78 | elasticsearch-java
79 | ${elasticsearch-java.version}
80 |
81 |
82 |
83 | com.fasterxml.jackson.core
84 | jackson-databind
85 | ${jackson-databind.version}
86 |
87 |
88 | jakarta.json
89 | jakarta.json-api
90 | ${jakarta.json-api.version}
91 |
92 |
93 |
94 | net.datafaker
95 | datafaker
96 | ${datafaker.version}
97 |
98 |
99 |
100 | com.zaxxer
101 | HikariCP
102 | ${HikariCP.version}
103 |
104 |
105 |
106 | org.awaitility
107 | awaitility
108 | ${awaitility.version}
109 |
110 |
111 |
112 | org.slf4j
113 | slf4j-api
114 | ${slf4j.version}
115 |
116 |
117 |
118 | ch.qos.logback
119 | logback-classic
120 | ${logback-classic.version}
121 |
122 |
123 |
124 |
125 |
126 |
127 | org.apache.maven.plugins
128 | maven-compiler-plugin
129 | ${maven.compiler.plugin}
130 |
131 | UTF-8
132 |
133 |
134 |
135 |
136 | org.apache.maven.plugins
137 | maven-surefire-plugin
138 | ${maven.surefire.plugin}
139 |
140 |
141 |
142 | org.apache.maven.plugins
143 | maven-site-plugin
144 | ${maven.site.plugin}
145 |
146 |
147 | org.apache.maven.plugins
148 | maven-jar-plugin
149 | 3.2.2
150 |
151 |
152 |
153 | test-jar
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | org.apache.maven.plugins
165 | maven-surefire-report-plugin
166 | ${maven.surefire.plugin}
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/restpro.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/CsvTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.params.ParameterizedTest;
8 |
9 | @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @ParameterizedTest(name = "[{index}] {0}")
12 | public @interface CsvTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/FailingTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.api.Tag;
8 |
9 | @Target({ElementType.METHOD, ElementType.TYPE})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Tag("failing")
12 | public @interface FailingTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/FlakyTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.api.Tag;
8 |
9 | @Target({ElementType.METHOD, ElementType.TYPE})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Tag("flaky")
12 | public @interface FlakyTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/HealthCheckTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.api.Tag;
8 |
9 | @Target({ElementType.METHOD, ElementType.TYPE})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Tag("healthcheck")
12 | public @interface HealthCheckTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/RegressionTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.api.Tag;
8 |
9 | @Target({ElementType.METHOD, ElementType.TYPE})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Tag("regression")
12 | public @interface RegressionTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/SmokeTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.api.Tag;
8 |
9 | @Target({ElementType.METHOD, ElementType.TYPE})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Tag("smoke")
12 | public @interface SmokeTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/annotations/UnitTest.java:
--------------------------------------------------------------------------------
1 | package org.powertester.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 | import org.junit.jupiter.api.Tag;
8 |
9 | @Target({ElementType.METHOD, ElementType.TYPE})
10 | @Retention(RetentionPolicy.RUNTIME)
11 | @Tag("unit")
12 | public @interface UnitTest {}
13 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/auth/AuthBody.java:
--------------------------------------------------------------------------------
1 | package org.powertester.auth;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Builder;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 | import lombok.experimental.Accessors;
9 | import lombok.extern.slf4j.Slf4j;
10 |
11 | @Slf4j
12 | @JsonInclude(JsonInclude.Include.NON_NULL)
13 | @Data
14 | @Builder(setterPrefix = "set")
15 | @Accessors(chain = true)
16 | @AllArgsConstructor
17 | @NoArgsConstructor
18 | public class AuthBody {
19 | private String username;
20 | private String password;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/auth/Scope.java:
--------------------------------------------------------------------------------
1 | package org.powertester.auth;
2 |
3 | public enum Scope {
4 | GUEST("read"),
5 | MAINTAINER("write"),
6 | ADMIN("delete");
7 |
8 | private String value;
9 |
10 | Scope(String value) {
11 | this.value = value;
12 | }
13 |
14 | public String getValue() {
15 | return value;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/auth/TokenFactory.java:
--------------------------------------------------------------------------------
1 | package org.powertester.auth;
2 |
3 | import static io.restassured.RestAssured.given;
4 |
5 | import com.typesafe.config.Config;
6 | import io.restassured.response.Response;
7 | import java.util.Arrays;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.powertester.config.TestConfig;
10 |
11 | @Slf4j
12 | public class TokenFactory {
13 | private static final Config CONFIG = TestConfig.getInstance().getConfig();
14 |
15 | private final String maintainerToken;
16 | private final String adminToken;
17 |
18 | private static final TokenFactory UNIQUE_INSTANCE = new TokenFactory();
19 |
20 | private TokenFactory() {
21 | AuthBody authBodyMaintainer =
22 | AuthBody.builder()
23 | .setUsername(CONFIG.getString("MAINTAINER_USERNAME"))
24 | .setPassword(CONFIG.getString("MAINTAINER_PASSWORD"))
25 | .build();
26 | this.maintainerToken = getToken(authBodyMaintainer);
27 |
28 | AuthBody authBodyAdmin =
29 | AuthBody.builder()
30 | .setUsername(CONFIG.getString("ADMIN_USERNAME"))
31 | .setPassword(CONFIG.getString("ADMIN_PASSWORD"))
32 | .build();
33 | this.adminToken = getToken(authBodyAdmin);
34 | }
35 |
36 | public static TokenFactory getInstance() {
37 | return UNIQUE_INSTANCE;
38 | }
39 |
40 | public String getTokenFor(Scope scope) {
41 | switch (scope) {
42 | case GUEST:
43 | return "";
44 | case MAINTAINER:
45 | return maintainerToken;
46 | case ADMIN:
47 | return adminToken;
48 | default:
49 | throw new IllegalStateException(
50 | "Not a valid scope. Pick a scope from " + Arrays.toString(Scope.values()));
51 | }
52 | }
53 |
54 | private String getToken(AuthBody authBody) {
55 | Response response =
56 | given()
57 | .header("Content-Type", "application/json")
58 | .baseUri(CONFIG.getString("BASE_URL"))
59 | .body(authBody)
60 | .log()
61 | .ifValidationFails()
62 | .when()
63 | .post(CONFIG.getString("AUTH_ENDPOINT"))
64 | .then()
65 | .log()
66 | .ifError()
67 | .extract()
68 | .response();
69 |
70 | return response.body().jsonPath().getString("token");
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/basespec/SpecFactory.java:
--------------------------------------------------------------------------------
1 | package org.powertester.basespec;
2 |
3 | import static org.powertester.auth.Scope.ADMIN;
4 | import static org.powertester.auth.Scope.MAINTAINER;
5 |
6 | import com.typesafe.config.Config;
7 | import io.restassured.builder.RequestSpecBuilder;
8 | import io.restassured.config.LogConfig;
9 | import io.restassured.config.RestAssuredConfig;
10 | import java.util.Arrays;
11 | import java.util.List;
12 | import org.powertester.auth.Scope;
13 | import org.powertester.auth.TokenFactory;
14 | import org.powertester.config.TestConfig;
15 |
16 | public class SpecFactory {
17 | private static final Config CONFIG = TestConfig.getInstance().getConfig();
18 |
19 | public static RequestSpecBuilder getSpecFor(Scope scope) {
20 | switch (scope) {
21 | case GUEST:
22 | return get();
23 | case MAINTAINER:
24 | return get(TokenFactory.getInstance().getTokenFor(MAINTAINER));
25 | case ADMIN:
26 | return get(TokenFactory.getInstance().getTokenFor(ADMIN));
27 | default:
28 | throw new IllegalStateException(
29 | "Not a valid scope. Pick a scope from " + Arrays.toString(Scope.values()));
30 | }
31 | }
32 |
33 | private static RequestSpecBuilder get() {
34 | return new RequestSpecBuilder()
35 | .addHeader("Content-Type", "application/json")
36 | .addHeader("Accept", "application/json")
37 | .setBaseUri(CONFIG.getString("BASE_URL"));
38 | }
39 |
40 | private static RequestSpecBuilder get(String token) {
41 | return get()
42 | .addHeader("Cookie", "token=" + token)
43 | .addHeader("Authorization", "some-value")
44 | .setConfig(
45 | RestAssuredConfig.config()
46 | .logConfig(
47 | LogConfig.logConfig().blacklistHeaders(List.of("Cookie", "Authorization"))));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/booking/Booking.java:
--------------------------------------------------------------------------------
1 | package org.powertester.booking;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import java.util.concurrent.TimeUnit;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Builder;
7 | import lombok.Data;
8 | import lombok.NoArgsConstructor;
9 | import lombok.experimental.Accessors;
10 | import lombok.extern.slf4j.Slf4j;
11 | import net.datafaker.Faker;
12 | import org.powertester.booking.entitites.Bookingdates;
13 |
14 | @Slf4j
15 | @JsonInclude(JsonInclude.Include.NON_NULL)
16 | @Data
17 | @Builder(setterPrefix = "set")
18 | @Accessors(chain = true)
19 | @AllArgsConstructor
20 | @NoArgsConstructor
21 | public class Booking {
22 | private String firstname;
23 | private String lastname;
24 | private long totalprice;
25 | private boolean depositpaid;
26 | private Bookingdates bookingdates;
27 | private String additionalneeds;
28 |
29 | public static Booking getInstance() {
30 | Bookingdates bookingdates =
31 | Bookingdates.builder()
32 | .setCheckin(new Faker().date().future(1, TimeUnit.DAYS, "YYYY-MM-dd"))
33 | .setCheckout(new Faker().date().future(6, TimeUnit.DAYS, "YYYY-MM-dd"))
34 | .build();
35 |
36 | Booking booking =
37 | Booking.builder()
38 | .setFirstname(new Faker().name().firstName())
39 | .setLastname(new Faker().name().lastName())
40 | .setTotalprice(new Faker().number().numberBetween(1, 1000))
41 | .setDepositpaid(true)
42 | .setBookingdates(bookingdates)
43 | .setAdditionalneeds(new Faker().food().dish())
44 | .build();
45 |
46 | log.info("bookingBody: {}", booking);
47 | return booking;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/booking/BookingAPI.java:
--------------------------------------------------------------------------------
1 | package org.powertester.booking;
2 |
3 | import static io.restassured.RestAssured.given;
4 |
5 | import com.typesafe.config.Config;
6 | import io.restassured.RestAssured;
7 | import io.restassured.response.Response;
8 | import org.powertester.auth.Scope;
9 | import org.powertester.basespec.SpecFactory;
10 | import org.powertester.config.TestConfig;
11 |
12 | public class BookingAPI {
13 | private static final Config CONFIG = TestConfig.getInstance().getConfig();
14 | private Scope scope;
15 |
16 | private BookingAPI(Scope scope) {
17 | this.scope = scope;
18 | }
19 |
20 | public static BookingAPI useAs(Scope scope) {
21 | return new BookingAPI(scope);
22 | }
23 |
24 | public Response newBooking(Booking booking) {
25 | return RestAssured.given()
26 | .spec(SpecFactory.getSpecFor(scope).build())
27 | .body(booking)
28 | .log()
29 | .ifValidationFails()
30 | .when()
31 | .post(CONFIG.getString("BOOKING_ENDPOINT"))
32 | .then()
33 | .log()
34 | .ifError()
35 | .extract()
36 | .response();
37 | }
38 |
39 | public Response getBooking(Long bookingId) {
40 | return RestAssured.given()
41 | .spec(SpecFactory.getSpecFor(scope).build())
42 | .log()
43 | .ifValidationFails()
44 | .when()
45 | .get(CONFIG.getString("BOOKING_ID_ENDPOINT"), bookingId)
46 | .then()
47 | .log()
48 | .ifError()
49 | .extract()
50 | .response();
51 | }
52 |
53 | public Response updateBooking(Booking booking, Long bookingId) {
54 | // Cookie: token={{auth_token}}
55 | return given()
56 | .spec(SpecFactory.getSpecFor(scope).build())
57 | .body(booking)
58 | .log()
59 | .ifValidationFails()
60 | .when()
61 | .put(CONFIG.getString("BOOKING_ID_ENDPOINT"), bookingId)
62 | .then()
63 | .log()
64 | .ifError()
65 | .extract()
66 | .response();
67 | }
68 |
69 | public Response patchBooking(Booking booking, Long bookingId) {
70 | // Cookie: token={{auth_token}}
71 | return given()
72 | .spec(SpecFactory.getSpecFor(scope).build())
73 | .body(booking)
74 | .log()
75 | .ifValidationFails()
76 | .when()
77 | .patch(CONFIG.getString("BOOKING_ID_ENDPOINT"), bookingId)
78 | .then()
79 | .log()
80 | .ifError()
81 | .extract()
82 | .response();
83 | }
84 |
85 | public Response deleteBooking(Long bookingId) {
86 | // Cookie: token={{auth_token}}
87 | return given()
88 | .spec(SpecFactory.getSpecFor(scope).build())
89 | .log()
90 | .ifValidationFails()
91 | .when()
92 | .delete(CONFIG.getString("BOOKING_ID_ENDPOINT"), bookingId)
93 | .then()
94 | .log()
95 | .ifError()
96 | .extract()
97 | .response();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/booking/BookingResponse.java:
--------------------------------------------------------------------------------
1 | package org.powertester.booking;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 | import lombok.Data;
5 |
6 | @JsonIgnoreProperties(ignoreUnknown = true)
7 | @Data
8 | public class BookingResponse {
9 | private long bookingid;
10 | private Booking booking;
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/booking/entitites/Bookingdates.java:
--------------------------------------------------------------------------------
1 | package org.powertester.booking.entitites;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 | import lombok.extern.slf4j.Slf4j;
8 |
9 | @Slf4j
10 | @Data
11 | @NoArgsConstructor
12 | @Builder(setterPrefix = "set")
13 | @AllArgsConstructor
14 | public class Bookingdates {
15 | private String checkin;
16 | private String checkout;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/config/TestConfig.java:
--------------------------------------------------------------------------------
1 | package org.powertester.config;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 | import java.io.File;
6 | import java.nio.file.Paths;
7 | import java.util.Objects;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.slf4j.MDC;
10 |
11 | /**
12 | * Env configuration once loaded, is to remain constant for all classes using it. Thus we will
13 | * follow Singleton design pattern here. For future reference on this topic:
14 | * https://github.com/lightbend/config
15 | */
16 | @Slf4j
17 | public class TestConfig {
18 | /**
19 | * With this approach, we are relying on JVM to create the unique instance of TestEnvFactory when
20 | * the class is loaded. The JVM guarantees that the instance will be created before any thread
21 | * accesses the static uniqueInstance variable. This code is thus guaranteed to be thread safe.
22 | */
23 | private static final TestConfig UNIQUE_INSTANCE = new TestConfig();
24 |
25 | private Config config;
26 |
27 | private TestConfig() {
28 | MDC.put("testContext", "Set TestEnv Config");
29 | config = setConfig();
30 | MDC.clear();
31 | }
32 |
33 | public static TestConfig getInstance() {
34 | return UNIQUE_INSTANCE;
35 | }
36 |
37 | public Config getConfig() {
38 | return config;
39 | }
40 |
41 | private Config setConfig() {
42 | log.info("Call setConfig only once for the whole test run!");
43 |
44 | // Standard config load behavior (loads common config from application.conf file)
45 | // https://github.com/lightbend/config#standard-behavior
46 | config = ConfigFactory.load();
47 |
48 | Config choicesConfig = ConfigFactory.load("choices");
49 | config = config.withFallback(choicesConfig);
50 |
51 | config = getAllConfigFromFilesInTheResourcePath("common");
52 |
53 | TestEnv testEnv = TestEnv.getEnumByValue(config.getString("TEST_ENV"));
54 | return getAllConfigFromFilesInTheResourcePath(testEnv.getValue());
55 | }
56 |
57 | private Config getAllConfigFromFilesInTheResourcePath(String resourceBasePath) {
58 | try {
59 | for (File file :
60 | Objects.requireNonNull(
61 | Paths.get("src/main/resources/" + resourceBasePath).toFile().listFiles())) {
62 | log.info("file path: {}", file);
63 |
64 | Config childConfig =
65 | ConfigFactory.load(String.format("%s/%s", resourceBasePath, file.getName()));
66 | config = config.withFallback(childConfig);
67 | }
68 |
69 | return config;
70 | } catch (Exception exception) {
71 | exception.printStackTrace();
72 | throw new IllegalStateException("Could not parse config. Got issues in parsing File path.");
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/config/TestEnv.java:
--------------------------------------------------------------------------------
1 | package org.powertester.config;
2 |
3 | public enum TestEnv {
4 | LOCALHOST("localhost"),
5 | DEVELOP("develop"),
6 | STAGING("staging");
7 |
8 | TestEnv(String value) {
9 | this.value = value;
10 | }
11 |
12 | private String value;
13 |
14 | public String getValue() {
15 | return value;
16 | }
17 |
18 | public static TestEnv getEnumByValue(String value) {
19 | for (TestEnv testEnv : TestEnv.values()) {
20 | if (testEnv.getValue().equalsIgnoreCase(value)) {
21 | return testEnv;
22 | }
23 | }
24 |
25 | throw new IllegalStateException("No enum constant with value: " + value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/data/TestData.java:
--------------------------------------------------------------------------------
1 | package org.powertester.data;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.Set;
6 | import java.util.stream.Collectors;
7 |
8 | public class TestData {
9 | private Map keyValueMap; // Ex: amount: 100, 200, 300
10 | private Map keyTypeMap; // Ex: amount: output, input, io
11 |
12 | public TestData(Map keyTypeMap, Map keyValueMap) {
13 | this.keyTypeMap = keyTypeMap;
14 | this.keyValueMap = keyValueMap;
15 | }
16 |
17 | public TestData() {
18 | this(new HashMap<>(), new HashMap<>());
19 | }
20 |
21 | public void setKey(String key, String value, String type) {
22 | keyValueMap.put(key, value);
23 | keyTypeMap.put(key, type);
24 | }
25 |
26 | public void setKey(String key, String value) {
27 | setKey(key, value, "default");
28 | }
29 |
30 | // Get trimmed keys
31 | public Set getKeys() {
32 | return keyValueMap.keySet().stream().map(String::trim).collect(Collectors.toSet());
33 | }
34 |
35 | public Map getKeyTypeMap() {
36 | return keyTypeMap;
37 | }
38 |
39 | public Map getKeyValueMap() {
40 | return keyValueMap;
41 | }
42 |
43 | public void setKeyValueMap(Map keyValueMap) {
44 | this.keyValueMap = keyValueMap;
45 | }
46 |
47 | public void setKeyTypeMap(Map keyTypeMap) {
48 | this.keyTypeMap = keyTypeMap;
49 | }
50 |
51 | public String getValue(String key) {
52 | return keyValueMap.get(key);
53 | }
54 |
55 | public String getType(String key) {
56 | return keyTypeMap.get(key);
57 | }
58 |
59 | public String toString() {
60 | return "keyValueMap: " + keyValueMap + "\nkeyTypeMap: " + keyTypeMap;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/org/powertester/database/DBConnection.java:
--------------------------------------------------------------------------------
1 | package org.powertester.database;
2 |
3 | import static org.junit.Assert.fail;
4 |
5 | import com.typesafe.config.Config;
6 | import com.zaxxer.hikari.HikariDataSource;
7 | import java.sql.CallableStatement;
8 | import java.sql.Connection;
9 | import java.sql.PreparedStatement;
10 | import java.sql.ResultSet;
11 | import java.sql.ResultSetMetaData;
12 | import java.sql.SQLException;
13 | import java.sql.Statement;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import lombok.extern.slf4j.Slf4j;
19 | import org.powertester.config.TestConfig;
20 |
21 | @Slf4j
22 | public class DBConnection {
23 | private static final Config CONFIG = TestConfig.getInstance().getConfig();
24 | private static final String DB_URL = CONFIG.getString("DB_URL");
25 | private static final String DB_USER = CONFIG.getString("DB_USER");
26 | private static final String DB_PASSWORD = CONFIG.getString("DB_PASSWORD");
27 | private static final DBConnection INSTANCE = new DBConnection();
28 |
29 | private HikariDataSource dataSource;
30 |
31 | private DBConnection() {
32 | dataSource = getDataSource();
33 | }
34 |
35 | public static DBConnection getInstance() {
36 | return INSTANCE;
37 | }
38 |
39 | private HikariDataSource getDataSource() {
40 | try {
41 | if (dataSource == null) {
42 | dataSource = new HikariDataSource();
43 | dataSource.setJdbcUrl(DB_URL);
44 | dataSource.setUsername(DB_USER);
45 | dataSource.setPassword(DB_PASSWORD);
46 | dataSource.setMaximumPoolSize(20); // 20 connections
47 | dataSource.setMinimumIdle(10); // 10 connections
48 | dataSource.setConnectionTimeout(30000); // 30 seconds
49 | dataSource.setIdleTimeout(30000); // 30 seconds
50 | dataSource.setMaxLifetime(1800000); // 30 minutes
51 | dataSource.setLeakDetectionThreshold(30000); // 30 seconds
52 | dataSource.setPoolName("PowerTester");
53 | }
54 | } catch (Exception e) {
55 | log.error("Error initializing Hikari datasource", e);
56 | log.error("⚠ Cancelling test run since tests depend on Database Connection");
57 | System.exit(1);
58 | }
59 |
60 | log.info("Hikari datasource initialized");
61 | return dataSource;
62 | }
63 |
64 | public Connection getConnection() throws SQLException {
65 | Connection connection = dataSource.getConnection();
66 | try (Statement statement = connection.createStatement()) {
67 | statement.execute(CONFIG.getString("QUERY_TO_SET_SCHEMA_USER"));
68 | statement.execute(CONFIG.getString("QUERY_TO_SET_DATE_FORMAT"));
69 | } catch (Exception e) {
70 | throw new IllegalStateException("Error setting schema and date format", e);
71 | }
72 | return connection;
73 | }
74 |
75 | // Execute update query
76 | public void executeUpdate(String sql) {
77 | try (Connection connection = getConnection();
78 | Statement statement = connection.createStatement()) {
79 | statement.executeUpdate(sql);
80 | } catch (Exception e) {
81 | throw new IllegalStateException("Error executing update query" + sql, e);
82 | }
83 | }
84 |
85 | // Preferred option 1: Execute a prepared statement and return the resultSet data as a list of map
86 | // of column name and value
87 | public List