├── .circleci └── config.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── auto_assign.yml ├── dependabot.yml ├── no-response.yml ├── stale.yml └── workflows │ └── greetings.yml ├── .gitignore ├── .spotless └── alexander.zagniotov-apache-2.0-license.txt ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYRIGHT.md ├── LICENSE ├── README.md ├── assets ├── stubby-logo-2.svg └── stubby-logo-duke-hiding.svg ├── build.gradle ├── codecov.yml ├── conf └── gradle │ ├── artifacts.gradle │ ├── dependencies.gradle │ ├── ide.gradle │ ├── jacoco.gradle │ ├── sonatype.gradle │ ├── spotless.gradle │ └── tests.gradle ├── docker ├── jdk11 │ ├── .dockerignore │ └── Dockerfile.arm64 ├── jdk16 │ ├── .dockerignore │ └── Dockerfile ├── jdk17 │ ├── .dockerignore │ └── Dockerfile.arm64 ├── jdk21 │ ├── .dockerignore │ └── Dockerfile.arm64 ├── jdk8 │ ├── .dockerignore │ └── Dockerfile.arm64 ├── log4j2-for-docker.xml └── smoke-test │ ├── docker-compose.yml │ └── yaml │ ├── include-all-test-stubs.yaml │ └── smoke-tests-stubs.yaml ├── docs └── DOCKERHUB.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── functionalTest ├── java │ └── io │ │ └── github │ │ └── azagniotov │ │ └── stubby4j │ │ ├── AdminPortalTest.java │ │ ├── ClientWebSocketHelper.java │ │ ├── HttpClientUtils.java │ │ ├── HttpUtils.java │ │ ├── ProxyConfigWithStubsTest.java │ │ ├── StubsAdminPortalsTest.java │ │ ├── StubsPortalHttp11OverTlsTests.java │ │ ├── StubsPortalHttp11WebSocketOverTlsTests.java │ │ ├── StubsPortalHttp11WebSocketSequencedResponsesTests.java │ │ ├── StubsPortalHttp11WebSocketTests.java │ │ ├── StubsPortalHttp20ClearTextTests.java │ │ ├── StubsPortalHttp20OverTlsWithAlpnProtocolTests.java │ │ ├── StubsPortalRaisedIssueTests.java │ │ ├── StubsPortalTest.java │ │ └── client │ │ ├── StubbyClientTest.java │ │ └── StubbyClientYamlessTest.java └── resources │ ├── binary │ └── hello-world.pdf │ ├── html │ └── file.html │ ├── json │ ├── request │ │ ├── json_payload_1.json │ │ ├── json_payload_10.json │ │ ├── json_payload_11.json │ │ ├── json_payload_12.json │ │ ├── json_payload_13.json │ │ ├── json_payload_14.json │ │ ├── json_payload_2.json │ │ ├── json_payload_3.json │ │ ├── json_payload_4.json │ │ ├── json_payload_5.json │ │ ├── json_payload_6.json │ │ ├── json_payload_7.json │ │ ├── json_payload_8.json │ │ └── json_payload_9.json │ └── response │ │ ├── json_response_1.json │ │ ├── json_response_2.json │ │ ├── json_response_3.json │ │ ├── json_response_4.json │ │ ├── json_response_5.json │ │ ├── json_response_6.json │ │ └── json_response_7.json │ ├── xml │ ├── request │ │ ├── xml_payload_1.xml │ │ ├── xml_payload_2.xml │ │ ├── xml_payload_3.xml │ │ ├── xml_request_issue_29_payload.xml │ │ ├── xml_request_issue_29_stub_template.xml │ │ ├── xml_request_issue_399_payload.xml │ │ ├── xml_request_issue_399_payload_2.xml │ │ ├── xml_request_issue_399_vanilla_regex_stub_template.xml │ │ └── xml_request_issue_399_xmlunit_matcher_stub_template.xml │ └── response │ │ ├── xml_response_1.xml │ │ ├── xml_response_2.xml │ │ ├── xml_response_issue_29_body.xml │ │ ├── xml_response_issue_29_stub_template.xml │ │ ├── xml_response_issue_399_body.xml │ │ └── xml_response_issue_399_stub_template.xml │ └── yaml │ ├── include-all-test-stubs.yaml │ ├── include-proxy-config.yaml │ ├── include-record-and-play-and-redirect-response-stubs.yaml │ ├── include-regex-dynamic-tokens-templated-stubs.yaml │ ├── include-reported-issues-test-stubs.yaml │ ├── include-sequenced-response-stubs.yaml │ ├── include-web-socket-config.yaml │ ├── include-web-socket-sequenced-response-config.yaml │ ├── main-test-stubs-with-proxy-config.yaml │ ├── main-test-stubs-with-web-socket-config.yaml │ ├── main-test-stubs.yaml │ └── standalone-stub.yaml ├── integrationTest ├── java │ └── io │ │ └── github │ │ └── azagniotov │ │ └── stubby4j │ │ ├── cli │ │ └── CommandLineInterpreterTest.java │ │ ├── parser │ │ └── json │ │ │ └── JSONAssertTest.java │ │ ├── server │ │ └── ssl │ │ │ ├── CustomHostnameVerifierTest.java │ │ │ └── SslUtilsTest.java │ │ ├── stubs │ │ └── StubRepositoryTest.java │ │ ├── utils │ │ ├── ConsoleUtilsTest.java │ │ ├── HandlerUtilsTest.java │ │ └── JarUtilsTest.java │ │ └── yaml │ │ ├── YamlParserLoadTest.java │ │ └── YamlParserTest.java └── resources │ ├── json │ ├── array.1.json │ ├── array.2.json │ ├── graph.1.json │ ├── graph.2.json │ ├── request.external.file.json │ ├── response.1.external.file.json │ ├── response.2.external.file.json │ ├── response.3.external.file.json │ ├── response.4.external.file.json │ └── response.5.external.file.json │ ├── ui │ └── html │ │ └── test-template.html │ └── yaml │ ├── duplicated.uuid.stub.yaml │ ├── feature.stub.yaml │ ├── multi-include-main.yaml │ ├── multi-included-service-1.yaml │ ├── multi-included-service-2.yaml │ ├── multi-included-service-3.yaml │ ├── one.external.files.yaml │ ├── proxy-config-duplicate-uuid.yaml │ ├── proxy-config-invalid-config.yaml │ ├── proxy-config-valid-config-with-stubs.yaml │ ├── proxy-config-valid-config.yaml │ ├── proxy-config-without-default-config.yaml │ ├── request.null.external.files.yaml │ ├── response.null.external.files.yaml │ ├── same.external.files.yaml │ ├── two.cycles.with.multiple.responses.yaml │ ├── two.external.files.yaml │ ├── web-socket-invalid-config-with-duplicate-client-request-body-bytes.yaml │ ├── web-socket-invalid-config-with-duplicate-client-request-body-text.yaml │ ├── web-socket-invalid-config-with-duplicate-url.yaml │ ├── web-socket-invalid-config-with-invalid-policy-name.yaml │ ├── web-socket-valid-config-with-no-on-open-no-on-message.yaml │ ├── web-socket-valid-config-with-no-on-open.yaml │ ├── web-socket-valid-config-with-sequenced-responses.yaml │ ├── web-socket-valid-config-with-uuid-and-description-on-top.yaml │ ├── web-socket-valid-config-without-on-message.yaml │ └── web-socket-valid-config.yaml ├── loadTest ├── java │ └── io │ │ └── github │ │ └── azagniotov │ │ └── stubby4j │ │ ├── HttpUtils.java │ │ ├── PortTestUtils.java │ │ ├── SpringSocketUtils.java │ │ └── StubsPortalLoadTest.java └── resources │ └── yaml │ ├── 10k_stubs_load_test.yaml │ └── 25k_stubs_load_test.yaml ├── main ├── java │ └── io │ │ └── github │ │ └── azagniotov │ │ └── stubby4j │ │ ├── Main.java │ │ ├── annotations │ │ ├── GeneratedCodeClassCoverageExclusion.java │ │ ├── GeneratedCodeMethodCoverageExclusion.java │ │ ├── PotentiallyFlaky.java │ │ └── VisibleForTesting.java │ │ ├── caching │ │ ├── Cache.java │ │ ├── NoOpStubHttpLifecycleCache.java │ │ ├── RegexPatternCache.java │ │ └── StubHttpLifecycleCache.java │ │ ├── cli │ │ ├── ANSITerminal.java │ │ ├── CommandLineInterpreter.java │ │ └── EmptyLogger.java │ │ ├── client │ │ ├── Authorization.java │ │ ├── StubbyClient.java │ │ ├── StubbyRequest.java │ │ └── StubbyResponse.java │ │ ├── common │ │ └── Common.java │ │ ├── filesystem │ │ ├── ExternalFilesScanner.java │ │ ├── MainIncludedYamlScanner.java │ │ └── MainYamlScanner.java │ │ ├── handlers │ │ ├── AbstractHandlerExtension.java │ │ ├── AdminPortalHandler.java │ │ ├── AjaxEndpointStatsHandler.java │ │ ├── AjaxResourceContentHandler.java │ │ ├── FaviconHandler.java │ │ ├── JsonErrorHandler.java │ │ ├── StatusPageHandler.java │ │ ├── StubDataRefreshActionHandler.java │ │ ├── StubsPortalHandler.java │ │ └── strategy │ │ │ ├── admin │ │ │ ├── AdminResponseHandlingStrategy.java │ │ │ ├── AdminResponseHandlingStrategyFactory.java │ │ │ ├── DeleteHandlingStrategy.java │ │ │ ├── GetHandlingStrategy.java │ │ │ ├── HttpVerbsEnum.java │ │ │ ├── NullHandlingStrategy.java │ │ │ ├── PostHandlingStrategy.java │ │ │ └── PutHandlingStrategy.java │ │ │ └── stubs │ │ │ ├── DefaultResponseHandlingStrategy.java │ │ │ ├── NotFoundResponseHandlingStrategy.java │ │ │ ├── RedirectResponseHandlingStrategy.java │ │ │ ├── StubResponseHandlingStrategy.java │ │ │ ├── StubsResponseHandlingStrategyFactory.java │ │ │ └── UnauthorizedResponseHandlingStrategy.java │ │ ├── http │ │ ├── HttpMethodExtended.java │ │ └── StubbyHttpTransport.java │ │ ├── server │ │ ├── JettyContext.java │ │ ├── JettyFactory.java │ │ ├── StubbyManager.java │ │ ├── StubbyManagerFactory.java │ │ ├── ssl │ │ │ ├── CustomHostnameVerifier.java │ │ │ ├── DefaultExtendedX509TrustManager.java │ │ │ ├── LanIPv4Validator.java │ │ │ └── SslUtils.java │ │ └── websocket │ │ │ ├── StubsServerWebSocket.java │ │ │ └── StubsWebSocketCreator.java │ │ ├── stubs │ │ ├── AbstractBuilder.java │ │ ├── ReflectableStub.java │ │ ├── RegexParser.java │ │ ├── StubHttpLifecycle.java │ │ ├── StubMatcher.java │ │ ├── StubRepository.java │ │ ├── StubRequest.java │ │ ├── StubResponse.java │ │ ├── StubSearchResult.java │ │ ├── StubTypes.java │ │ ├── StubbableAuthorizationType.java │ │ ├── matching │ │ │ ├── Stubby4jMatchesRegexPlaceholderHandler.java │ │ │ └── Stubby4jXmlUnitPlaceholderDifferenceEvaluator.java │ │ ├── proxy │ │ │ ├── StubProxyConfig.java │ │ │ └── StubProxyStrategy.java │ │ └── websocket │ │ │ ├── StubWebSocketClientRequest.java │ │ │ ├── StubWebSocketConfig.java │ │ │ ├── StubWebSocketMessageType.java │ │ │ ├── StubWebSocketOnMessageLifeCycle.java │ │ │ ├── StubWebSocketServerResponse.java │ │ │ └── StubWebSocketServerResponsePolicy.java │ │ ├── utils │ │ ├── CollectionUtils.java │ │ ├── ConsoleUtils.java │ │ ├── DateTimeUtils.java │ │ ├── FileUtils.java │ │ ├── HandlerUtils.java │ │ ├── HttpRequestUtils.java │ │ ├── JarUtils.java │ │ ├── NetworkPortUtils.java │ │ ├── ObjectUtils.java │ │ ├── ReflectionUtils.java │ │ ├── SpringSocketUtils.java │ │ └── StringUtils.java │ │ └── yaml │ │ ├── ConfigurableYAMLProperty.java │ │ ├── SnakeYaml.java │ │ ├── YamlBuilder.java │ │ ├── YamlParseResultSet.java │ │ └── YamlParser.java └── resources │ ├── META-INF │ └── services │ │ └── org.eclipse.jetty.http.HttpFieldPreEncoder │ ├── jetty-logging.properties │ ├── ssl │ ├── openssl.downloaded.stubby4j.self.signed.v3.jks │ ├── openssl.downloaded.stubby4j.self.signed.v3.pem │ ├── openssl.downloaded.stubby4j.self.signed.v3.pkcs12 │ ├── stubby4j.self.signed.v3.commands.txt │ ├── stubby4j.self.signed.v3.conf │ └── stubby4j.self.signed.v3.pkcs12 │ ├── ui │ ├── css │ │ └── main.css │ ├── html │ │ ├── _popup_generic.html │ │ ├── _popup_proxy_config.html │ │ ├── _popup_stats.html │ │ ├── _table.html │ │ ├── default404.html │ │ └── status.html │ ├── images │ │ ├── favicon.ico │ │ └── loading.gif │ └── js │ │ ├── d3 │ │ └── d3.v3.min.js │ │ ├── highlight │ │ ├── LICENSE │ │ ├── highlight.pack.js │ │ └── styles │ │ │ └── solarized_light.min.css │ │ ├── main.js │ │ └── minified │ │ └── minified.js │ └── yaml │ └── empty-stub.yaml ├── smoke-test ├── shell │ ├── make_request_for_sequenced_responses_using_websocat.sh │ ├── make_request_using_curl.sh │ └── make_request_using_websocat.sh └── yaml │ ├── include-all-test-stubs.yaml │ ├── include-web-socket-config.yaml │ └── smoke-tests-stubs.yaml └── test ├── java └── io │ └── github │ └── azagniotov │ └── stubby4j │ ├── caching │ ├── CacheTest.java │ └── NoOpStubHttpLifecycleCacheTest.java │ ├── cli │ └── CommandLineInterpreterTest.java │ ├── handlers │ ├── AbstractHandlerExtensionTest.java │ ├── AjaxResourceContentHandlerTest.java │ ├── StubsPortalHandlerTest.java │ └── strategy │ │ ├── DefaultResponseHandlingStrategyTest.java │ │ ├── HandlingStrategyFactoryTest.java │ │ ├── RedirectResponseHandlingStrategyTest.java │ │ └── StubsResponseHandlingStrategyFactoryTest.java │ ├── server │ ├── ssl │ │ └── LanIPv4ValidatorTest.java │ └── websocket │ │ └── StubsServerWebSocketTest.java │ ├── stubs │ ├── RegexParserTest.java │ ├── StubHttpLifecycleBuilderTest.java │ ├── StubMatcherTest.java │ ├── StubProxyConfigBuilderTest.java │ ├── StubRepositoryTest.java │ ├── StubRequestBuilderTest.java │ ├── StubResponseBuilderTest.java │ └── websocket │ │ ├── StubWebSocketClientRequestTest.java │ │ ├── StubWebSocketMessageTypeTest.java │ │ ├── StubWebSocketServerResponsePolicyTest.java │ │ └── StubWebSocketServerResponseTest.java │ ├── utils │ ├── CollectionUtilsTest.java │ ├── DateTimeUtilsTest.java │ ├── FileUtilsTest.java │ ├── HandlerUtilsTest.java │ ├── ReflectionUtilsTest.java │ └── StringUtilsTest.java │ └── yaml │ ├── YamlBuilderTest.java │ └── YamlParserTest.java └── resources └── stubbed.request.response.yaml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | # https://help.github.com/articles/about-codeowners/ 4 | # 5 | # Order is important; the last matching pattern takes the most 6 | # precedence. 7 | 8 | * @azagniotov 9 | *.md @azagniotov 10 | .github/CODEOWNERS @azagniotov 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve stubby4j 4 | title: "[BUG] ..." 5 | labels: bug 6 | assignees: azagniotov 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Please provide some basic information about you environment** 14 | - What is your OS: Windows/Mac/*Nix: 15 | - What is stubby4j version you are running: 16 | - Are you running stubby4j in Docker container or as a standalone JAR: 17 | - If Docker container, what is the image tag: 18 | 19 | **YAML config** 20 | Paste here YAML config used or provide an example: 21 | ``` 22 | Paste YAML here 23 | ``` 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea/feature for stubby4j. 4 | title: "[FEATURE REQUEST] ..." 5 | labels: feature-request 6 | assignees: azagniotov 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 about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about stubby4j. 4 | title: "[QUESTION] ..." 5 | labels: question 6 | assignees: azagniotov 7 | 8 | --- 9 | 10 | **Please provide some basic information about you environment** 11 | - What is your OS: Windows/Mac/*Nix: 12 | - What is stubby4j version you are running: 13 | - Are you running stubby4j in Docker container or as a standalone JAR: 14 | - If Docker container, what is the image tag: 15 | 16 | **Is your question request related to a problem? Please describe.** 17 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 18 | 19 | **Provide details about your question** 20 | A clear and concise description of what you want to happen. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the question here. 24 | -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # Set to true to add reviewers to pull requests 2 | addReviewers: true 3 | 4 | # Set to true to add assignees to pull requests 5 | addAssignees: author 6 | 7 | # A list of reviewers to be added to pull requests (GitHub user name) 8 | reviewers: 9 | - azagniotov 10 | 11 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 12 | skipKeywords: 13 | - wip 14 | - work in progress 15 | - work-in-progress 16 | 17 | # A number of reviewers added to the pull request 18 | # Set 0 to add all the reviewers (default: 0) 19 | numberOfReviewers: 1 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an Issue is closed for lack of response 2 | daysUntilClose: 14 3 | # Label requiring a response 4 | responseRequiredLabel: more-information-needed 5 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 6 | closeComment: > 7 | This issue has been automatically closed because there has been no response 8 | to our request for more information from the original author. With only the 9 | information that is currently in the issue, we don't have enough information 10 | to take action. Please reach out if you have or find the answers we need so 11 | that we can investigate further. 12 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | # Configuration options apply to both Issues and Pull Requests. 3 | # We configure those individually to match our workflow (see `pulls:` and `issues:`) 4 | 5 | pulls: 6 | # Number of days of inactivity before a Pull Request becomes stale 7 | daysUntilStale: 60 8 | 9 | # Number of days of inactivity before a Pull Request with the stale label is closed. 10 | # Set to false to disable. If disabled, Pull Request still need to be closed manually, but will remain marked as stale. 11 | daysUntilClose: 21 12 | 13 | # Comment to post when marking as stale. Set to `false` to disable 14 | markComment: > 15 | This pull request has been automatically marked as stale because it has not had recent activity. 16 | Given the limited bandwidth of the project maintainers, it will be closed if no further activity occurs. 17 | If you intend to work on this pull request, please reopen the PR. Thank you for your contributions. 18 | # Comment to post when closing a stale Pull Request. 19 | closeComment: > 20 | This pull request has been automatically closed due to inactivity. 21 | If you are still interested in contributing this, please ensure that 22 | it is rebased against the latest branch (usually `master`), all review 23 | comments have been addressed and the build is passing. 24 | 25 | issues: 26 | daysUntilStale: 365 27 | 28 | # Number of days of inactivity before an Issue with the stale label is closed. 29 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 30 | daysUntilClose: 21 31 | 32 | # Comment to post when marking as stale. Set to `false` to disable 33 | markComment: > 34 | This issue has been automatically marked as stale because it has not had recent activity. 35 | Given the limited bandwidth of the team, it will be automatically closed if no further 36 | activity occurs. Thank you for your contribution. 37 | # Comment to post when closing a stale Issue. 38 | closeComment: > 39 | This issue has been automatically closed due to inactivity. If you can reproduce this on a 40 | recent version of `stubby4j` or if you have a good use case for this feature, please feel free 41 | to reopen the issue with steps to reproduce, a quick explanation of your use case or a 42 | high-quality pull request. 43 | 44 | exemptLabels: 45 | - pinned 46 | - security 47 | - work-in-progress 48 | - epic 49 | - backlog 50 | 51 | # Label to use when marking an issue as stale 52 | staleLabel: wontfix 53 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Thanks for opening your first issue. Pull requests are always welcome too! :)' 13 | pr-message: 'Thank you for your contribution to stubby4j. We will be reviewing your PR shortly! In the meantime, please make sure all status checks have passed.' 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | **/logs/* 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | 26 | *.swp 27 | .gradle/* 28 | target/* 29 | **/target/* 30 | build/* 31 | .classpath 32 | build/* 33 | out/* 34 | **/out/* 35 | .idea/* 36 | .DS_Store 37 | Thumbs.db 38 | *.jar 39 | *.war 40 | *.ear 41 | .classpath 42 | .project 43 | .settings 44 | *.iml 45 | *.ipr 46 | *.iws 47 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 48 | !gradle-wrapper.jar 49 | 50 | 51 | # Ignore Gradle project-specific cache directory 52 | .gradle 53 | 54 | # Ignore Gradle build output directory 55 | build 56 | 57 | -------------------------------------------------------------------------------- /.spotless/alexander.zagniotov-apache-2.0-license.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) $YEAR Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the approval of one of project committers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | -------------------------------------------------------------------------------- /COPYRIGHT.md: -------------------------------------------------------------------------------- 1 | ## COPYRIGHT 2 | 3 | This product includes software developed by Alexander Zagniotov, Isa Goksu and Eric Mrak under MIT LICENSE. 4 | Copyright © 2012-2022 Alexander Zagniotov and Contributors. See [MIT LICENSE](LICENSE) for details. 5 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | project.ext.isReleaseVersion = !"${stubbyProjectVersion}".endsWith("SNAPSHOT") 2 | 3 | buildscript { 4 | repositories { 5 | mavenLocal() 6 | repositories { 7 | maven { 8 | url "https://plugins.gradle.org/m2/" 9 | } 10 | } 11 | } 12 | dependencies { 13 | // From v6.14.0 (inc.) Java 8 support is dropped 14 | classpath("com.diffplug.spotless:spotless-plugin-gradle:6.13.0") 15 | } 16 | } 17 | 18 | apply plugin: "java-library" 19 | apply plugin: "maven-publish" 20 | apply plugin: "signing" 21 | apply plugin: "idea" 22 | apply plugin: "eclipse" 23 | apply plugin: "jacoco" 24 | apply plugin: "jvm-test-suite" 25 | 26 | defaultTasks 'clean', 'test', 'integrationTest', 'functionalTest', 'build' 27 | description = 'Gradle configuration for stubby4j' 28 | 29 | compileJava { 30 | options.encoding = 'UTF-8' 31 | options.compilerArgs << '-parameters' << '-Xlint:deprecation' 32 | } 33 | 34 | compileTestJava { 35 | options.encoding = 'UTF-8' 36 | } 37 | 38 | javadoc { 39 | if (JavaVersion.current().isJava8Compatible()) { 40 | options.addStringOption('Xdoclint:none', '-quiet') 41 | } 42 | } 43 | 44 | repositories { 45 | mavenLocal() 46 | mavenCentral() 47 | } 48 | 49 | apply from: "$rootDir/conf/gradle/dependencies.gradle" 50 | apply from: "$rootDir/conf/gradle/ide.gradle" 51 | apply from: "$rootDir/conf/gradle/tests.gradle" 52 | apply from: "$rootDir/conf/gradle/jacoco.gradle" 53 | apply from: "$rootDir/conf/gradle/artifacts.gradle" 54 | apply from: "$rootDir/conf/gradle/sonatype.gradle" 55 | 56 | // Disabling the Spotless plugin on Ci due to its v6.13.0 incompatability with JDK 21 57 | if (!project.hasProperty("ciRun")) { 58 | apply from: "$rootDir/conf/gradle/spotless.gradle" 59 | } 60 | 61 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: 70..100 9 | status: 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 5% 14 | project: 15 | default: 16 | target: auto 17 | threshold: 3% 18 | 19 | comment: 20 | layout: diff, files 21 | behavior: default 22 | -------------------------------------------------------------------------------- /conf/gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | if (project.hasProperty("useNativeJdkAlpnProcessor")) { 3 | println "> Building with native JDK ALPN processor in default dependencies" 4 | api "org.eclipse.jetty:jetty-alpn-java-server:${jettyVersion}" 5 | } else { 6 | println "> Building with Jetty JDK ALPN OpenJDK 1.8 processor in default dependencies" 7 | api "org.eclipse.jetty:jetty-alpn-openjdk8-server:${jettyVersion}" 8 | } 9 | 10 | api "org.eclipse.jetty.websocket:websocket-api:${jettyVersion}" 11 | api "org.eclipse.jetty.websocket:websocket-server:${jettyVersion}" 12 | api "org.eclipse.jetty.websocket:websocket-client:${jettyVersion}" 13 | 14 | // https://github.com/eclipse/jetty.project/issues/1894 15 | // https://github.com/eclipse/jetty.project/issues/2950 16 | api "org.eclipse.jetty.http2:http2-server:${jettyVersion}" 17 | api "org.eclipse.jetty:jetty-servlets:${jettyVersion}" 18 | api "org.ehcache:ehcache:3.9.1" 19 | api "commons-cli:commons-cli:1.4" 20 | api "org.yaml:snakeyaml:${snakeYamlVersion}" 21 | api "org.skyscreamer:jsonassert:1.5.0" 22 | api "org.xmlunit:xmlunit-core:${xmlUnitVersion}" 23 | api "org.xmlunit:xmlunit-placeholders:${xmlUnitVersion}" 24 | api "io.github.azagniotov:collection-type-safe-converter:1.0.1" 25 | 26 | if (project.hasProperty("log4j")) { 27 | var becauseMsg = "CVE-2021-44228, CVE-2021-45046, CVE-2021-45105, CVE-2021-44832: Log4j vulnerable to remote code execution and other critical security vulnerabilities" 28 | println "> Building with log4j included in default dependencies" 29 | api "org.apache.logging.log4j:log4j-slf4j-impl:${log4j2Version}" 30 | api "org.apache.logging.log4j:log4j-api:${log4j2Version}" 31 | api "org.apache.logging.log4j:log4j-core:${log4j2Version}" 32 | 33 | } else { 34 | println "> Building with default dependencies" 35 | api "org.slf4j:slf4j-api:1.7.30" 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /conf/gradle/ide.gradle: -------------------------------------------------------------------------------- 1 | idea { 2 | module { 3 | downloadSources = true 4 | downloadJavadoc = true 5 | } 6 | 7 | project { 8 | jdkName = "1.8" 9 | languageLevel = "1.8" 10 | vcs = "Git" 11 | wildcards += [ 12 | '?*.gradle', '?*.properties', '?*.xml', '?*.gif', 13 | '?*.png', '?*.jpeg', '?*.jpg', '?*.html', '?*.dtd', 14 | '?*.tld', '?*.ftl', '?*.yaml', '?*.json', '?*.jks' 15 | ] 16 | } 17 | } 18 | 19 | eclipse { 20 | classpath { 21 | downloadSources = true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /conf/gradle/jacoco.gradle: -------------------------------------------------------------------------------- 1 | jacoco { 2 | toolVersion = "0.8.11" 3 | } 4 | 5 | def getMainExcludes() { 6 | return fileTree(dir: "build/classes/java/main").filter({ file -> 7 | !file.name.contains('Main') && !file.name.contains('$1') && !file.name.contains('$2') 8 | }) 9 | } 10 | 11 | jacocoTestCoverageVerification { 12 | classDirectories.setFrom(getMainExcludes()) 13 | } 14 | 15 | jacocoTestReport { 16 | // tests are required to run before generating the report 17 | dependsOn test, integrationTest, functionalTest 18 | executionData.from = fileTree(buildDir).include("/jacoco/*.exec") 19 | reports { 20 | csv.required = false 21 | 22 | xml.required = true 23 | xml.outputLocation = layout.buildDirectory.file("${buildDir}/reports/jacoco/xml/jacocoCoverage.xml") 24 | 25 | html.required = true 26 | html.outputLocation = layout.buildDirectory.dir("${buildDir}/reports/jacoco/html") 27 | 28 | } 29 | 30 | classDirectories.setFrom(getMainExcludes()) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /conf/gradle/spotless.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.diffplug.spotless" 2 | 3 | spotless { 4 | java { 5 | // ratchetFrom "origin/master" // only format files which have changed since origin/master 6 | 7 | target "src/**/*.java" 8 | 9 | importOrder() 10 | removeUnusedImports() 11 | palantirJavaFormat() 12 | 13 | licenseHeaderFile(rootProject.file(".spotless/alexander.zagniotov-apache-2.0-license.txt")). 14 | named("alexander.zagniotov"). 15 | updateYearWithLatest(true) 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /docker/jdk11/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /docker/jdk16/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /docker/jdk17/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /docker/jdk21/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /docker/jdk8/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | -------------------------------------------------------------------------------- /docker/log4j2-for-docker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{ISO8601_OFFSET_DATE_TIME_HHCMM} %p %c{1.} %m%n 7 | 8 | data/logs 9 | stubby4j 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docker/smoke-test/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This compose file adds stubby4j https://hub.docker.com/r/azagniotov/stubby4j to your stack 2 | # 3 | # See "Environment variables" section at https://hub.docker.com/r/azagniotov/stubby4j 4 | version: '3.5' 5 | services: 6 | stubby4j-jre11: 7 | # 'root' - so that stubby4j can write 'logs' into host machine's directory mapped to container volume 8 | user: root 9 | image: azagniotov/stubby4j:latest-jre11 10 | volumes: 11 | - "./yaml:/home/stubby4j/data" 12 | container_name: stubby4j_jre11 13 | ports: 14 | - 8884:8884 15 | - 8891:8891 16 | - 7445:7445 17 | environment: 18 | YAML_CONFIG: smoke-tests-stubs.yaml 19 | LOCATION: 0.0.0.0 20 | STUBS_PORT: 8884 21 | ADMIN_PORT: 8891 22 | STUBS_TLS_PORT: 7445 23 | # https://stubby4j.com/#command-line-switches 24 | WITH_ARGS: "--enable_tls_with_alpn_and_http_2 --debug --watch" 25 | -------------------------------------------------------------------------------- /docker/smoke-test/yaml/include-all-test-stubs.yaml: -------------------------------------------------------------------------------- 1 | - uuid: 9136d8b7-f7a7-478d-97a5-53292484aaf6 2 | description: stubby4j sanity smoke test when running in Docker 3 | request: 4 | method: GET 5 | url: /hello 6 | 7 | response: 8 | headers: 9 | content-type: application/json 10 | status: 200 11 | body: > 12 | world! 13 | -------------------------------------------------------------------------------- /docker/smoke-test/yaml/smoke-tests-stubs.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - include-all-test-stubs.yaml 3 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.parallel=true 2 | configureondemand=true 3 | org.gradle.vfs.watch=true 4 | 5 | log4j2Version=2.17.1 6 | xmlUnitVersion=2.8.4 7 | jettyVersion=9.4.53.v20231009 8 | snakeYamlVersion=2.2 9 | googleHttpClient=1.38.1 10 | mockitoVersion=4.11.0 11 | googleTruthVersion=1.1.5 12 | junitVersion=4.13.2 13 | 14 | # Explicitly adding a recent Byte Buddy to Mockito for the compatibility with JDK 21 and Mockito 4.x.x 15 | byteBuddyVersion=1.14.3 16 | 17 | stubbyProjectName=stubby4j 18 | stubbyProjectGroup=io.github.azagniotov 19 | stubbyProjectVersion=7.6.2-SNAPSHOT 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'stubby4j' 2 | -------------------------------------------------------------------------------- /src/functionalTest/java/io/github/azagniotov/stubby4j/HttpUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j; 18 | 19 | import static io.github.azagniotov.stubby4j.HttpClientUtils.buildHttpClient; 20 | import static io.github.azagniotov.stubby4j.server.ssl.SslUtils.TLS_v1_2; 21 | 22 | import com.google.api.client.http.ByteArrayContent; 23 | import com.google.api.client.http.GenericUrl; 24 | import com.google.api.client.http.HttpRequest; 25 | import com.google.api.client.http.HttpRequestFactory; 26 | import com.google.api.client.http.apache.v2.ApacheHttpTransport; 27 | import io.github.azagniotov.stubby4j.utils.StringUtils; 28 | import java.io.IOException; 29 | import org.apache.http.impl.client.CloseableHttpClient; 30 | 31 | final class HttpUtils { 32 | 33 | private static final HttpRequestFactory WEB_CLIENT; 34 | 35 | static { 36 | CloseableHttpClient apacheHttpClient = null; 37 | try { 38 | apacheHttpClient = buildHttpClient(TLS_v1_2); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | 43 | WEB_CLIENT = new ApacheHttpTransport(apacheHttpClient, false).createRequestFactory(request -> { 44 | // This is dumb. Google client throws exception if response code is not 200 45 | request.setThrowExceptionOnExecuteError(false); 46 | }); 47 | } 48 | 49 | private HttpUtils() {} 50 | 51 | static HttpRequest constructHttpRequest(final String method, final String targetUrl) throws IOException { 52 | return WEB_CLIENT.buildRequest(method, new GenericUrl(targetUrl), null); 53 | } 54 | 55 | static HttpRequest constructHttpRequest(final String method, final String targetUrl, final String content) 56 | throws IOException { 57 | return WEB_CLIENT.buildRequest( 58 | method, new GenericUrl(targetUrl), new ByteArrayContent(null, StringUtils.getBytesUtf8(content))); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/functionalTest/java/io/github/azagniotov/stubby4j/client/StubbyClientYamlessTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.client; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import io.github.azagniotov.stubby4j.utils.NetworkPortUtils; 22 | import io.github.azagniotov.stubby4j.utils.StringUtils; 23 | import java.io.InputStream; 24 | import org.eclipse.jetty.http.HttpStatus; 25 | import org.junit.AfterClass; 26 | import org.junit.BeforeClass; 27 | import org.junit.Test; 28 | 29 | public class StubbyClientYamlessTest { 30 | 31 | private static final String ADDRESS_TO_BIND = "127.0.0.1"; 32 | private static final int STUBS_PORT = NetworkPortUtils.findAvailableTcpPort(); 33 | private static final int STUBS_TLS_PORT = NetworkPortUtils.findAvailableTcpPort(); 34 | private static final int ADMIN_PORT = NetworkPortUtils.findAvailableTcpPort(); 35 | 36 | private static final StubbyClient STUBBY_CLIENT = new StubbyClient(); 37 | 38 | @BeforeClass 39 | public static void beforeClass() throws Exception { 40 | 41 | // For example, passing additional command line args. But, they are not needed for this specific test 42 | final String[] additionalFlags = new String[] {"--debug"}; 43 | 44 | final InputStream resourceAsStream = 45 | StubbyClientYamlessTest.class.getResourceAsStream("/yaml/standalone-stub.yaml"); 46 | final String stubsYamlConfigurationData = StringUtils.inputStreamToString(resourceAsStream); 47 | 48 | STUBBY_CLIENT.startJettyYamless( 49 | stubsYamlConfigurationData, STUBS_PORT, STUBS_TLS_PORT, ADMIN_PORT, ADDRESS_TO_BIND, additionalFlags); 50 | } 51 | 52 | @AfterClass 53 | public static void afterClass() throws Exception { 54 | STUBBY_CLIENT.stopJetty(); 55 | } 56 | 57 | @Test 58 | public void shouldStartStubby4jUsingStubbyClientByCallingYamlessAPI() throws Exception { 59 | final String uri = "/standalone/stub/uri"; 60 | final StubbyResponse stubbyResponse = STUBBY_CLIENT.doGetOverSsl(ADDRESS_TO_BIND, uri, STUBS_TLS_PORT); 61 | 62 | assertThat(stubbyResponse.body()).isEqualTo("This is working!"); 63 | assertThat(stubbyResponse.statusCode()).isEqualTo(HttpStatus.OK_200); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/functionalTest/resources/binary/hello-world.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/src/functionalTest/resources/binary/hello-world.pdf -------------------------------------------------------------------------------- /src/functionalTest/resources/html/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fileserver in stubby4j is working. 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "articles", 5 | "id": "1", 6 | "attributes": { 7 | "title": "JSON API paints my bikeshed!" 8 | }, 9 | "links": { 10 | "self": "http://example.com/articles/1" 11 | }, 12 | "relationships": { 13 | "author": { 14 | "links": { 15 | "self": "http://example.com/articles/1/relationships/author", 16 | "related": "http://example.com/articles/1/author" 17 | }, 18 | "data": { 19 | "type": "people", 20 | "id": "9" 21 | } 22 | }, 23 | "comments": { 24 | "links": { 25 | "self": "http://example.com/articles/1/relationships/comments", 26 | "related": "http://example.com/articles/1/comments" 27 | }, 28 | "data": [ 29 | { 30 | "type": "comments", 31 | "id": "5" 32 | }, 33 | { 34 | "type": "comments", 35 | "id": "12" 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | ], 42 | "included": [ 43 | { 44 | "type": "people", 45 | "id": "9", 46 | "attributes": { 47 | "first-name": "Dan", 48 | "last-name": "Gebhardt", 49 | "twitter": "dgeb" 50 | }, 51 | "links": { 52 | "self": "http://example.com/people/9" 53 | } 54 | }, 55 | { 56 | "type": "comments", 57 | "id": "5", 58 | "attributes": { 59 | "body": "First!" 60 | }, 61 | "relationships": { 62 | "author": { 63 | "data": { 64 | "type": "people", 65 | "id": "2" 66 | } 67 | } 68 | }, 69 | "links": { 70 | "self": "http://example.com/comments/5" 71 | } 72 | }, 73 | { 74 | "type": "comments", 75 | "id": "12", 76 | "attributes": { 77 | "body": "I like XML better" 78 | }, 79 | "relationships": { 80 | "author": { 81 | "data": { 82 | "type": "people", 83 | "id": "9" 84 | } 85 | } 86 | }, 87 | "links": { 88 | "self": "http://example.com/comments/12" 89 | } 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_10.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": { 3 | "identifierTyp": "IDENTIFIER", 4 | "identifierVal": "1234567890" 5 | }, 6 | "permissions": { 7 | "PERMISSION.PERMISSION1": { 8 | "value": "ABC" 9 | } 10 | }, 11 | "extra": { 12 | "extraId": "54" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_11.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "proxy-config": { 4 | "uuid": "some-unique-name", 5 | "strategy": "additive", 6 | "headers": { 7 | "x-custom-header": "custom/value" 8 | }, 9 | "properties": { 10 | "endpoint": "https://UPDATED.com" 11 | } 12 | } 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_12.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "/resources/something/new", 5 | "query": { 6 | "someKey": "someValue" 7 | }, 8 | "method": [ 9 | "GET" 10 | ] 11 | }, 12 | "response": { 13 | "body": "OK", 14 | "headers": { 15 | "content-type": "application/xml" 16 | }, 17 | "status": 201 18 | } 19 | }, 20 | { 21 | "proxy-config": { 22 | "description": "this would be the default proxy config", 23 | "strategy": "as-is", 24 | "properties": { 25 | "endpoint": "https://google.com" 26 | } 27 | } 28 | }, 29 | { 30 | "proxy-config": { 31 | "uuid": "some-unique-name-1", 32 | "strategy": "as-is", 33 | "properties": { 34 | "endpoint": "https://yahoo.com" 35 | } 36 | } 37 | }, 38 | { 39 | "proxy-config": { 40 | "description": "this would be the 2nd description", 41 | "uuid": "some-unique-name-2", 42 | "strategy": "as-is", 43 | "properties": { 44 | "endpoint": "https://microsoft.com" 45 | } 46 | } 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_13.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "proxy-config": { 4 | "description": "this should be the catch-all config", 5 | "strategy": "as-is", 6 | "properties": { 7 | "endpoint": "https://google.com" 8 | } 9 | } 10 | }, 11 | { 12 | "proxy-config": { 13 | "description": "but this one is missing uuid key, which also makes it a catch-all???", 14 | "strategy": "as-is", 15 | "properties": { 16 | "endpoint": "https://yahoo.com" 17 | } 18 | } 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_14.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "uuid": "SAME-SAME-SAME-SAME-SAME-SAME", 4 | "request": { 5 | "url": "/resources/current", 6 | "query": { 7 | "someKey": "someValue" 8 | }, 9 | "method": [ 10 | "GET" 11 | ] 12 | }, 13 | "response": { 14 | "body": "OK", 15 | "headers": { 16 | "content-type": "application/xml" 17 | }, 18 | "status": 201 19 | } 20 | }, 21 | { 22 | "uuid": "SAME-SAME-SAME-SAME-SAME-SAME", 23 | "request": { 24 | "url": "/resources/something/new", 25 | "query": { 26 | "someKey": "someValue" 27 | }, 28 | "method": [ 29 | "GET" 30 | ] 31 | }, 32 | "response": { 33 | "body": "MAYBE", 34 | "headers": { 35 | "content-type": "application/xml" 36 | }, 37 | "status": 200 38 | } 39 | } 40 | ] -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": "articles", 5 | "id": "(.*)", 6 | "attributes": { 7 | "title": "JSON API paints my bikeshed!" 8 | }, 9 | "links": { 10 | "self": "http://example.com/articles/1" 11 | }, 12 | "relationships": { 13 | "author": { 14 | "links": { 15 | "self": "http://example.com/articles/1/relationships/author", 16 | "related": "http://example.com/articles/1/author" 17 | }, 18 | "data": { 19 | "type": "people", 20 | "id": "(.*)" 21 | } 22 | }, 23 | "comments": { 24 | "links": { 25 | "self": "http://example.com/articles/1/relationships/comments", 26 | "related": "http://example.com/articles/1/comments" 27 | }, 28 | "data": [ 29 | { 30 | "type": "comments", 31 | "id": "(.*)" 32 | }, 33 | { 34 | "type": "comments", 35 | "id": "(.*)" 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | ], 42 | "included": [ 43 | { 44 | "type": "people", 45 | "id": "(.*)", 46 | "attributes": { 47 | "first-name": "Dan", 48 | "last-name": "Gebhardt", 49 | "twitter": "dgeb" 50 | }, 51 | "links": { 52 | "self": "http://example.com/people/9" 53 | } 54 | }, 55 | { 56 | "type": "comments", 57 | "id": "(.*)", 58 | "attributes": { 59 | "body": "First!" 60 | }, 61 | "relationships": { 62 | "author": { 63 | "data": { 64 | "type": "people", 65 | "id": "(.*)" 66 | } 67 | } 68 | }, 69 | "links": { 70 | "self": "http://example.com/comments/5" 71 | } 72 | }, 73 | { 74 | "type": "comments", 75 | "id": "(.*)", 76 | "attributes": { 77 | "body": "I like XML better" 78 | }, 79 | "relationships": { 80 | "author": { 81 | "data": { 82 | "type": "people", 83 | "id": "(.*)" 84 | } 85 | } 86 | }, 87 | "links": { 88 | "self": "http://example.com/comments/12" 89 | } 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "first": "John", 3 | "last": "Doe", 4 | "age": 39, 5 | "sex": "M", 6 | "salary": 70000, 7 | "registered": true, 8 | "interests": [ 9 | "Reading", 10 | "Mountain Biking", 11 | "Hacking" 12 | ], 13 | "favorites": { 14 | "color": "Blue", 15 | "sport": "Soccer", 16 | "food": "Spaghetti" 17 | }, 18 | "skills": [ 19 | { 20 | "category": "JavaScript", 21 | "tests": [ 22 | { 23 | "name": "One", 24 | "score": 90 25 | }, 26 | { 27 | "name": "Two", 28 | "score": 96 29 | } 30 | ] 31 | }, 32 | { 33 | "category": "CouchDB", 34 | "tests": [ 35 | { 36 | "name": "One", 37 | "score": 79 38 | }, 39 | { 40 | "name": "Two", 41 | "score": 84 42 | } 43 | ] 44 | }, 45 | { 46 | "category": "Node.js", 47 | "tests": [ 48 | { 49 | "name": "One", 50 | "score": 97 51 | }, 52 | { 53 | "name": "Two", 54 | "score": 93 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "first": "John", 3 | "last": "Doe", 4 | "age": 39, 5 | "favorites": { 6 | "color": "Blue", 7 | "sport": "Soccer", 8 | "food": "Spaghetti" 9 | }, 10 | "sex": "M", 11 | "salary": 70000, 12 | "registered": true, 13 | "interests": [ 14 | "Reading", 15 | "Mountain Biking", 16 | "Hacking" 17 | ], 18 | "skills": [ 19 | { 20 | "category": "CouchDB", 21 | "tests": [ 22 | { 23 | "name": "One", 24 | "score": 79 25 | }, 26 | { 27 | "name": "Two", 28 | "score": 84 29 | } 30 | ] 31 | }, 32 | { 33 | "category": "JavaScript", 34 | "tests": [ 35 | { 36 | "name": "One", 37 | "score": 90 38 | }, 39 | { 40 | "name": "Two", 41 | "score": 96 42 | } 43 | ] 44 | }, 45 | { 46 | "category": "Node.js", 47 | "tests": [ 48 | { 49 | "name": "Two", 50 | "score": 93 51 | }, 52 | { 53 | "name": "One", 54 | "score": 97 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_5.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "^\/resources\/something\/new", 5 | "query": { 6 | "someKey": "someValue" 7 | }, 8 | "method": [ 9 | "GET" 10 | ] 11 | }, 12 | "response": { 13 | "body": "OK", 14 | "headers": { 15 | "content-type": "application/xml" 16 | }, 17 | "status": 201 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_6.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "uuid": "9136d8b7-f7a7-478d-97a5-53292484aaf6", 4 | "request": { 5 | "url": "/with/UPDATED/uuid/property", 6 | "query": { 7 | "someKey": "someValue" 8 | }, 9 | "method": [ 10 | "GET" 11 | ] 12 | }, 13 | "response": { 14 | "body": "OK", 15 | "headers": { 16 | "content-type": "application/xml" 17 | }, 18 | "status": 201 19 | } 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_7.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": ^\/resources\/something\/new, 5 | "query": { 6 | "someKey": "someValue" 7 | }, 8 | "method": [ 9 | "GET" 10 | ] 11 | }, 12 | "response": { 13 | "body": "OK", 14 | "headers": { 15 | "content-type": "application/xml" 16 | }, 17 | "status": 201 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_8.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "^/resources/something/new", 5 | "query": { 6 | "someKey": "someValue" 7 | }, 8 | "method": [ 9 | "GET" 10 | ] 11 | }, 12 | "response": { 13 | "body": "OK", 14 | "headers": { 15 | "content-type": "application/xml" 16 | }, 17 | "status": 201 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /src/functionalTest/resources/json/request/json_payload_9.json: -------------------------------------------------------------------------------- 1 | { 2 | "included": [ 3 | { 4 | "type": "people", 5 | "id": "9", 6 | "attributes": { 7 | "first-name": "Dan", 8 | "last-name": "Gebhardt", 9 | "twitter": "dgeb" 10 | }, 11 | "links": { 12 | "self": "http://example.com/people/9" 13 | } 14 | }, 15 | { 16 | "type": "comments", 17 | "id": "5", 18 | "attributes": { 19 | "body": "First!" 20 | }, 21 | "relationships": { 22 | "author": { 23 | "data": { 24 | "type": "people", 25 | "id": "2" 26 | } 27 | } 28 | }, 29 | "links": { 30 | "self": "http://example.com/comments/5" 31 | } 32 | }, 33 | { 34 | "type": "comments", 35 | "id": "12", 36 | "attributes": { 37 | "body": "I like XML better" 38 | }, 39 | "relationships": { 40 | "author": { 41 | "data": { 42 | "type": "people", 43 | "id": "9" 44 | } 45 | } 46 | }, 47 | "links": { 48 | "self": "http://example.com/comments/12" 49 | } 50 | } 51 | ], 52 | "data": [ 53 | { 54 | "type": "articles", 55 | "id": "1", 56 | "attributes": { 57 | "title": "JSON API paints my bikeshed!" 58 | }, 59 | "links": { 60 | "self": "http://example.com/articles/1" 61 | }, 62 | "relationships": { 63 | "author": { 64 | "links": { 65 | "self": "http://example.com/articles/1/relationships/author", 66 | "related": "http://example.com/articles/1/author" 67 | }, 68 | "data": { 69 | "type": "people", 70 | "id": "9" 71 | } 72 | }, 73 | "comments": { 74 | "links": { 75 | "self": "http://example.com/articles/1/relationships/comments", 76 | "related": "http://example.com/articles/1/comments" 77 | }, 78 | "data": [ 79 | { 80 | "type": "comments", 81 | "id": "5" 82 | }, 83 | { 84 | "type": "comments", 85 | "id": "12" 86 | } 87 | ] 88 | } 89 | } 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": [ 3 | { 4 | "id": "123", 5 | "name": "milk", 6 | "description": "full", 7 | "status": "active" 8 | }, 9 | { 10 | "id": "789", 11 | "name": "bread", 12 | "description": "wheat", 13 | "status": "active" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_2.json: -------------------------------------------------------------------------------- 1 | OK -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_3.json: -------------------------------------------------------------------------------- 1 | Still going strong! -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_4.json: -------------------------------------------------------------------------------- 1 | OMFG!!! -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_5.json: -------------------------------------------------------------------------------- 1 | Returned invoice number# <% url.1 %> in category '<% url.2 %>' on the date '<% query.date.1%>' -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_6.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Queued", 3 | "transactionId": "123-456-789" 4 | } -------------------------------------------------------------------------------- /src/functionalTest/resources/json/response/json_response_7.json: -------------------------------------------------------------------------------- 1 | fruit-2a,fruit-2b,fruit-2c,fruit-2d,fruit-2e 2 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_payload_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PIPS 4 | pid 5 | pid:// 6 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_request_issue_29_payload.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | vaccine 5 | astra-zeneca 6 | capsule 7 | strong 8 | 200 9 | 10 | Monkey 11 | 7 12 | 20 | 21 | 123.456 22 | 456.123 23 | Melbourne 24 | 25 | One Value 26 | Two Value 27 | Three Value 28 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_request_issue_29_stub_template.xml: -------------------------------------------------------------------------------- 1 | <\?xml version="1.0" encoding="UTF-8"\?> 2 | 3 | 4 | (.*) 5 | (.*) 6 | (.*) 7 | (.*) 8 | (.*) 9 | 10 | (.*) 11 | (.*) 12 | 20 | 21 | (.*) 22 | (.*) 23 | (.*) 24 | 25 | (.*) 26 | (.*) 27 | (.*) 28 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_request_issue_399_payload.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | banana 6 | apple 7 | strawberry 8 | melon 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_request_issue_399_payload_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PIPS 4 | pid 5 | pid:// 6 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_request_issue_399_vanilla_regex_stub_template.xml: -------------------------------------------------------------------------------- 1 | <\?xml version="1.0" encoding="utf-8"\?> 2 | 3 | 4 | 5 | (.*) 6 | (.*) 7 | (.*) 8 | (.*) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/request/xml_request_issue_399_xmlunit_matcher_stub_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${xmlunit.matchesRegex(.*)} 6 | ${xmlunit.matchesRegex(.*)} 7 | ${xmlunit.matchesRegex(.*)} 8 | ${xmlunit.matchesRegex(.*)} 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/response/xml_response_1.xml: -------------------------------------------------------------------------------- 1 | 2 | PIPS 3 | pid 4 | pid:// 5 | 6 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/response/xml_response_2.xml: -------------------------------------------------------------------------------- 1 | 2 | WildCardMatched 3 | WildCardMatched 4 | WildCardMatched 5 | 6 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/response/xml_response_issue_29_body.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0652642.107340 4 | vaccine 5 | astra-zeneca 6 | capsule 7 | strong 8 | 200 9 | Monkey 10 | 7 11 | Hello World! 12 | great_world_ 13 | 123.456 14 | 456.123 15 | Melbourne 16 | one_attribute_name 17 | One Value 18 | two_attribute_name 19 | Two Value 20 | two_attribute_name 21 | three 22 | Three Value 23 | 24 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/response/xml_response_issue_29_stub_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% file.1 %> 4 | <% file.2 %> 5 | <% file.3 %> 6 | <% file.4 %> 7 | <% file.5 %> 8 | <% file.6 %> 9 | <% file.7 %> 10 | <% file.8 %> 11 | <% file.9 %> 12 | <% file.10 %> 13 | <% file.11 %> 14 | <% file.12 %> 15 | <% file.13 %> 16 | <% file.14 %> 17 | <% file.15 %> 18 | <% file.16 %> 19 | <% file.17 %> 20 | <% file.16 %> 21 | <% file.19 %> 22 | <% file.20 %> 23 | 24 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/response/xml_response_issue_399_body.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SUCCESS 7 | 8 | 9 | A Key 10 | Some text 11 | 12 | 13 | 14 | 15 | banana 16 | apple 17 | strawberry 18 | melon 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/functionalTest/resources/xml/response/xml_response_issue_399_stub_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SUCCESS 7 | 8 | 9 | A Key 10 | Some text 11 | 12 | 13 | 14 | 15 | <% file.1 %> 16 | <% file.2 %> 17 | <% file.3 %> 18 | <% file.4 %> 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/include-proxy-config.yaml: -------------------------------------------------------------------------------- 1 | - proxy-config: 2 | description: this is a catch-all proxy config 3 | strategy: as-is 4 | properties: 5 | endpoint: https://jsonplaceholder.typicode.com 6 | 7 | - proxy-config: 8 | uuid: some-unique-name 9 | strategy: additive 10 | headers: 11 | x-original-stubby4j-custom-header: custom/value 12 | properties: 13 | endpoint: https://jsonplaceholder.typicode.com 14 | 15 | - proxy-config: 16 | description: description-2 17 | uuid: some-unique-name-two 18 | strategy: additive 19 | properties: 20 | endpoint: https://google.com 21 | 22 | - proxy-config: 23 | description: description-3 24 | uuid: some-unique-name-three 25 | strategy: additive 26 | properties: 27 | endpoint: https://google.com 28 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/include-regex-dynamic-tokens-templated-stubs.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: [GET] 3 | url: ^/v\d/identity/authorize 4 | query: 5 | redirect_uri: "https://(.*)/app.*" 6 | 7 | response: 8 | headers: 9 | location: https://<% query.redirect_uri.1 %>/auth 10 | status: 302 11 | 12 | 13 | - request: 14 | method: POST 15 | url: /post-body-as-json 16 | headers: 17 | content-type: application/json 18 | post: > 19 | {"userId":"19","requestId":"(.*)","transactionDate":"(.*)","transactionTime":"(.*)"} 20 | 21 | response: 22 | headers: 23 | content-type: application/json 24 | status: 200 25 | body: > 26 | {"requestId": "<%post.1%>", "transactionDate": "<%post.2%>", "transactionTime": "<%post.3%>"} 27 | 28 | 29 | - request: 30 | method: POST 31 | url: /post-body-as-json-2 32 | headers: 33 | content-type: application/json 34 | post: > 35 | {"objects": [{"key": "value"}, {"key": "value"}, {"key": {"key": "(.*)"}}]} 36 | 37 | response: 38 | headers: 39 | content-type: application/json 40 | status: 200 41 | body: > 42 | {"internalKey": "<%post.1%>"} 43 | 44 | 45 | - request: 46 | method: POST 47 | url: /jsonapi-json-regex 48 | headers: 49 | content-type: application/json 50 | file: ../json/request/json_payload_2.json 51 | 52 | response: 53 | headers: 54 | content-type: application/json 55 | status: 200 56 | body: > 57 | {"people#id": "<% file.2 %>"} 58 | 59 | 60 | - request: 61 | method: [GET] 62 | url: ^/resources/invoices/(\d{5})/category/([a-zA-Z]+) 63 | 64 | response: 65 | status: 200 66 | body: Returned invoice number# <%url.1%> in category '<%url.2%>' 67 | 68 | 69 | - request: 70 | method: [GET] 71 | url: ^/resources/invoices/(\d{5}) 72 | 73 | response: 74 | status: 200 75 | body: Returned invoice number# <%url.1%> in category '<%url.2%>' 76 | 77 | 78 | - request: 79 | method: [GET] 80 | url: ^/account/(\d{5})/category/([a-zA-Z]+) 81 | query: 82 | date: "([a-zA-Z]+)" 83 | 84 | response: 85 | status: 200 86 | file: ../json/response/json_response_5.json 87 | 88 | 89 | - request: 90 | method: [GET] 91 | url: ^/no/explicit/groups/\d{5} 92 | query: 93 | param: "[a-zA-Z]+" 94 | headers: 95 | custom-header: "[a-zA-Z]+" 96 | 97 | response: 98 | status: 200 99 | body: Returned content with URL <% url.0 %>, query param <% query.param.0 %> and custom-header <% headers.custom-header.0 %> 100 | 101 | 102 | - request: 103 | method: [GET] 104 | url: ^/groups/with/sub/groups/(([a-z]{3})-([0-9]{3}))$ 105 | 106 | response: 107 | status: 200 108 | body: Returned content with URL <% url.0 %>, parent group <% url.1 %> and two sub-groups <% url.2 %> & <% url.3 %> 109 | 110 | 111 | - request: 112 | method: [GET] 113 | url: ^/regex-fileserver/([a-z]+).html$ 114 | 115 | response: 116 | status: 200 117 | file: ../html/<% url.1 %>.html 118 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/include-sequenced-response-stubs.yaml: -------------------------------------------------------------------------------- 1 | 2 | - request: 3 | method: [GET] 4 | url: /uri/with/sequenced/responses 5 | 6 | response: 7 | - status: 201 8 | headers: 9 | content-type: application/json 10 | body: OK 11 | 12 | - status: 201 13 | headers: 14 | content-type: application/json 15 | body: Still going strong! 16 | 17 | - status: 500 18 | headers: 19 | content-type: application/json 20 | body: OMFG!!! 21 | 22 | 23 | - request: 24 | method: [GET] 25 | url: /uri/with/sequenced/responses/infile 26 | 27 | response: 28 | - status: 201 29 | headers: 30 | content-type: application/json 31 | file: ../json/response/json_response_2.json 32 | 33 | - status: 201 34 | headers: 35 | content-type: application/json 36 | file: ../json/response/json_response_3.json 37 | 38 | - status: 500 39 | headers: 40 | content-type: application/json 41 | file: ../json/response/json_response_4.json 42 | 43 | 44 | - request: 45 | method: [GET] 46 | url: /uri/with/sequenced/responses/infile/withbadurls 47 | 48 | response: 49 | - status: 201 50 | headers: 51 | content-type: application/json 52 | file: ../json/this.file.does.not.exist.json 53 | 54 | - status: 201 55 | headers: 56 | content-type: application/json 57 | file: ../json/response/json_response_3.json 58 | 59 | - status: 500 60 | headers: 61 | content-type: application/json 62 | file: ../json/this.file.does.not.exist.either.json 63 | 64 | 65 | - request: 66 | method: [GET] 67 | url: /uri/with/single/sequenced/response 68 | 69 | response: 70 | - status: 201 71 | headers: 72 | content-type: application/json 73 | body: Still going strong! 74 | 75 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/include-web-socket-sequenced-response-config.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /demo/hello/8 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-open: 7 | policy: once 8 | message-type: text 9 | body: You have been successfully connected 10 | 11 | on-message: 12 | - client-request: 13 | message-type: text 14 | body: Hey, server, give me fruits 15 | 16 | server-response: 17 | - policy: once 18 | message-type: text 19 | body: fruit-0 20 | 21 | - policy: once 22 | message-type: text 23 | body: fruit-1 24 | 25 | - policy: fragmentation 26 | message-type: binary 27 | body: fruit-2a,fruit-2b,fruit-2c,fruit-2d,fruit-2e 28 | 29 | - policy: once 30 | message-type: text 31 | body: fruit-3 32 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/main-test-stubs-with-proxy-config.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - include-all-test-stubs.yaml 3 | - include-proxy-config.yaml 4 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/main-test-stubs-with-web-socket-config.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - include-all-test-stubs.yaml 3 | - include-web-socket-config.yaml 4 | - include-web-socket-sequenced-response-config.yaml 5 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/main-test-stubs.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - include-all-test-stubs.yaml 3 | - include-record-and-play-and-redirect-response-stubs.yaml 4 | - include-sequenced-response-stubs.yaml 5 | - include-regex-dynamic-tokens-templated-stubs.yaml 6 | - include-reported-issues-test-stubs.yaml 7 | -------------------------------------------------------------------------------- /src/functionalTest/resources/yaml/standalone-stub.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /standalone/stub/uri 4 | 5 | response: 6 | status: 200 7 | body: This is working! 8 | headers: 9 | content-type: application/json 10 | -------------------------------------------------------------------------------- /src/integrationTest/java/io/github/azagniotov/stubby4j/parser/json/JSONAssertTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.parser.json; 18 | 19 | import io.github.azagniotov.stubby4j.utils.StringUtils; 20 | import org.junit.Test; 21 | import org.skyscreamer.jsonassert.JSONAssert; 22 | import org.skyscreamer.jsonassert.JSONCompareMode; 23 | 24 | public class JSONAssertTest { 25 | 26 | @Test 27 | public void shouldCompareTwoJsonArraysWithDifferentContentOrder() throws Exception { 28 | JSONAssert.assertEquals( 29 | StringUtils.inputStreamToString(JSONAssertTest.class.getResourceAsStream("/json/array.1.json")), 30 | StringUtils.inputStreamToString(JSONAssertTest.class.getResourceAsStream("/json/array.2.json")), 31 | JSONCompareMode.NON_EXTENSIBLE); 32 | } 33 | 34 | @Test 35 | public void shouldCompareTwoJsonComplexGraphsWithDifferentContentOrder() throws Exception { 36 | JSONAssert.assertEquals( 37 | StringUtils.inputStreamToString(JSONAssertTest.class.getResourceAsStream("/json/graph.1.json")), 38 | StringUtils.inputStreamToString(JSONAssertTest.class.getResourceAsStream("/json/graph.2.json")), 39 | JSONCompareMode.NON_EXTENSIBLE); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/integrationTest/java/io/github/azagniotov/stubby4j/server/ssl/SslUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.server.ssl; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import java.security.cert.X509Certificate; 22 | import java.util.ArrayList; 23 | import java.util.Set; 24 | import org.junit.BeforeClass; 25 | import org.junit.Test; 26 | 27 | public class SslUtilsTest { 28 | 29 | private static X509Certificate stubbySelfSignedCert; 30 | private static CustomHostnameVerifier customHostnameVerifier; 31 | 32 | @BeforeClass 33 | public static void beforeClass() throws Exception { 34 | final Set selfSignedX509 = SslUtils.keyStoreAsX509Certificates(); 35 | stubbySelfSignedCert = new ArrayList<>(selfSignedX509).get(0); 36 | customHostnameVerifier = new CustomHostnameVerifier(stubbySelfSignedCert); 37 | } 38 | 39 | @Test 40 | public void certificateShouldBeVersion3() throws Exception { 41 | assertThat(stubbySelfSignedCert.getVersion()).isEqualTo(3); 42 | } 43 | 44 | @Test 45 | public void certificateSigAlgNameShouldBeSHA256withRSA() throws Exception { 46 | assertThat(stubbySelfSignedCert.getSigAlgName()).isEqualTo("SHA256withRSA"); 47 | } 48 | 49 | @Test 50 | public void certificateShouldContainExpectedSubjectAltNames() throws Exception { 51 | final Set subjectAltNames = customHostnameVerifier.getSubjectAltNames(2); 52 | 53 | assertThat(subjectAltNames.size()).isEqualTo(2); 54 | assertThat(subjectAltNames.contains("localhost")).isTrue(); 55 | assertThat(subjectAltNames.contains("::1")).isTrue(); 56 | } 57 | 58 | @Test 59 | public void certificateShouldContainExpectedSubjectAltIps() throws Exception { 60 | final Set subjectAltNameIps = customHostnameVerifier.getSubjectAltNames(7); 61 | 62 | assertThat(subjectAltNameIps.size()).isEqualTo(23); 63 | assertThat(subjectAltNameIps.contains("0.0.0.0")).isTrue(); 64 | assertThat(subjectAltNameIps.contains("127.0.0.1")).isTrue(); 65 | 66 | final int totalIpsPerClass = 7; 67 | for (int idx = 1; idx <= totalIpsPerClass; idx++) { 68 | assertThat(subjectAltNameIps.contains("10.0.0." + idx)).isTrue(); 69 | assertThat(subjectAltNameIps.contains("192.168.0." + idx)).isTrue(); 70 | assertThat(subjectAltNameIps.contains("172.16.0." + idx)).isTrue(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/integrationTest/java/io/github/azagniotov/stubby4j/utils/HandlerUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import java.io.IOException; 22 | import org.junit.Rule; 23 | import org.junit.Test; 24 | import org.junit.rules.ExpectedException; 25 | 26 | public class HandlerUtilsTest { 27 | 28 | @Rule 29 | public ExpectedException expectedException = ExpectedException.none(); 30 | 31 | @Test 32 | public void shouldGetHtmlResourceByName() throws Exception { 33 | final String templateContent = HandlerUtils.getHtmlResourceByName("test-template"); 34 | assertThat("%s").isEqualTo(templateContent); 35 | } 36 | 37 | @Test 38 | public void shouldPopulateHtmlTemplate() throws Exception { 39 | final String templateContent = HandlerUtils.populateHtmlTemplate("test-template", "cheburashka"); 40 | assertThat("cheburashka").isEqualTo(templateContent); 41 | } 42 | 43 | @Test 44 | public void shouldNotPopulateNonExistentHtmlTemplate() throws Exception { 45 | 46 | expectedException.expect(IOException.class); 47 | 48 | HandlerUtils.populateHtmlTemplate("non-existent-template", "cheburashka"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/integrationTest/java/io/github/azagniotov/stubby4j/utils/JarUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | 23 | public class JarUtilsTest { 24 | 25 | @Test 26 | public void shouldReadManifestImplementationVersion() throws Exception { 27 | final String result = JarUtils.readManifestImplementationVersion(); 28 | 29 | assertThat(result).isNotEqualTo("x.x.xx"); 30 | } 31 | 32 | @Test 33 | public void shouldReadManifestBuiltDate() throws Exception { 34 | final String result = JarUtils.readManifestBuiltDate(); 35 | 36 | assertThat(result).contains("20"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/integrationTest/resources/json/array.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "colorsArray": [ 3 | { 4 | "red": "#f00", 5 | "green": "#0f0", 6 | "blue": "#00f", 7 | "cyan": "#0ff", 8 | "magenta": "#f0f", 9 | "yellow": "#ff0", 10 | "black": "#000" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/json/array.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "colorsArray": [ 3 | { 4 | "red": "#f00", 5 | "yellow": "#ff0", 6 | "green": "#0f0", 7 | "cyan": "#0ff", 8 | "magenta": "#f0f", 9 | "black": "#000", 10 | "blue": "#00f" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/json/graph.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "first": "John", 3 | "last": "Doe", 4 | "age": 39, 5 | "sex": "M", 6 | "salary": 70000, 7 | "registered": true, 8 | "interests": [ 9 | "Reading", 10 | "Mountain Biking", 11 | "Hacking" 12 | ], 13 | "favorites": { 14 | "color": "Blue", 15 | "sport": "Soccer", 16 | "food": "Spaghetti" 17 | }, 18 | "skills": [ 19 | { 20 | "category": "JavaScript", 21 | "tests": [ 22 | { 23 | "name": "One", 24 | "score": 90 25 | }, 26 | { 27 | "name": "Two", 28 | "score": 96 29 | } 30 | ] 31 | }, 32 | { 33 | "category": "CouchDB", 34 | "tests": [ 35 | { 36 | "name": "One", 37 | "score": 79 38 | }, 39 | { 40 | "name": "Two", 41 | "score": 84 42 | } 43 | ] 44 | }, 45 | { 46 | "category": "Node.js", 47 | "tests": [ 48 | { 49 | "name": "One", 50 | "score": 97 51 | }, 52 | { 53 | "name": "Two", 54 | "score": 93 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/json/graph.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "first": "John", 3 | "last": "Doe", 4 | "age": 39, 5 | "favorites": { 6 | "color": "Blue", 7 | "sport": "Soccer", 8 | "food": "Spaghetti" 9 | }, 10 | "sex": "M", 11 | "salary": 70000, 12 | "registered": true, 13 | "interests": [ 14 | "Reading", 15 | "Mountain Biking", 16 | "Hacking" 17 | ], 18 | "skills": [ 19 | { 20 | "category": "CouchDB", 21 | "tests": [ 22 | { 23 | "name": "One", 24 | "score": 79 25 | }, 26 | { 27 | "name": "Two", 28 | "score": 84 29 | } 30 | ] 31 | }, 32 | { 33 | "category": "JavaScript", 34 | "tests": [ 35 | { 36 | "name": "One", 37 | "score": 90 38 | }, 39 | { 40 | "name": "Two", 41 | "score": 96 42 | } 43 | ] 44 | }, 45 | { 46 | "category": "Node.js", 47 | "tests": [ 48 | { 49 | "name": "Two", 50 | "score": 93 51 | }, 52 | { 53 | "name": "One", 54 | "score": 97 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/json/request.external.file.json: -------------------------------------------------------------------------------- 1 | This is request content -------------------------------------------------------------------------------- /src/integrationTest/resources/json/response.1.external.file.json: -------------------------------------------------------------------------------- 1 | This is response 1 content -------------------------------------------------------------------------------- /src/integrationTest/resources/json/response.2.external.file.json: -------------------------------------------------------------------------------- 1 | This is response 2 content -------------------------------------------------------------------------------- /src/integrationTest/resources/json/response.3.external.file.json: -------------------------------------------------------------------------------- 1 | This is response 3 content -------------------------------------------------------------------------------- /src/integrationTest/resources/json/response.4.external.file.json: -------------------------------------------------------------------------------- 1 | This is response 4 content -------------------------------------------------------------------------------- /src/integrationTest/resources/json/response.5.external.file.json: -------------------------------------------------------------------------------- 1 | mango 2 | orange 3 | lemon 4 | watermelon 5 | honeydew 6 | -------------------------------------------------------------------------------- /src/integrationTest/resources/ui/html/test-template.html: -------------------------------------------------------------------------------- 1 | %s -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/duplicated.uuid.stub.yaml: -------------------------------------------------------------------------------- 1 | - description: Stub one 2 | uuid: 9136d8b7-f7a7-478d-97a5-53292484aaf6 3 | request: 4 | url: ^/one$ 5 | method: GET 6 | 7 | response: 8 | status: 200 9 | body: 'One!' 10 | 11 | 12 | - description: Stub two 13 | uuid: 9136d8b7-f7a7-478d-97a5-53292484aaf6 14 | request: 15 | url: ^/two 16 | method: GET 17 | 18 | response: 19 | status: 200 20 | body: 'Two!' 21 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/feature.stub.yaml: -------------------------------------------------------------------------------- 1 | - description: Stub one 2 | uuid: 9136d8b7-f7a7-478d-97a5-53292484aaf6 3 | request: 4 | url: ^/one$ 5 | method: GET 6 | 7 | response: 8 | status: 200 9 | latency: 100 10 | body: 'One!' 11 | 12 | - description: Stub two 13 | uuid: 888975hn-f7a7-8888-97a5-53292484aaf6 14 | request: 15 | url: ^/two 16 | method: GET 17 | 18 | response: 19 | status: 200 20 | body: 'Two!' -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/multi-include-main.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - multi-included-service-1.yaml 3 | - multi-included-service-2.yaml 4 | - multi-included-service-3.yaml -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/multi-included-service-1.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: 3 | - GET 4 | - POST 5 | - PUT 6 | url: ^/resources/asn/.*$ 7 | 8 | response: 9 | status: 200 10 | body: > 11 | {"status": "ASN found!"} 12 | headers: 13 | content-type: application/json 14 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/multi-included-service-2.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: 3 | - GET 4 | - POST 5 | - PUT 6 | url: /this/stub/should/always/be/second/in/this/file 7 | 8 | response: 9 | status: 200 10 | body: OK 11 | headers: 12 | content-type: application/json 13 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/multi-included-service-3.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | url: /individuals/.*/address$ 3 | method: PUT 4 | post: > 5 | {"type": "MOBILE"} 6 | 7 | response: 8 | status: 400 9 | body: > 10 | {"type": "BAD_REQUEST"} 11 | 12 | 13 | - request: 14 | url: /individuals/.*/address$ 15 | method: PUT 16 | post: > 17 | {"type": "HOME"} 18 | 19 | response: 20 | body: OK 21 | status: 200 22 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/one.external.files.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /invoice 4 | query: 5 | status: active 6 | type: full 7 | 8 | response: 9 | headers: 10 | content-type: application/json 11 | pragma: no-cache 12 | status: 200 13 | file: ../json/response.1.external.file.json -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/proxy-config-duplicate-uuid.yaml: -------------------------------------------------------------------------------- 1 | - proxy-config: 2 | uuid: some-unique-name 3 | strategy: as-is 4 | properties: 5 | endpoint: https://jsonplaceholder.typicode.com 6 | 7 | - proxy-config: 8 | uuid: some-unique-name 9 | strategy: additive 10 | properties: 11 | endpoint: https://jsonplaceholder.typicode.com 12 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/proxy-config-invalid-config.yaml: -------------------------------------------------------------------------------- 1 | - proxy-config: 2 | strategy: invalid-strategy-name 3 | properties: 4 | endpoint: https://jsonplaceholder.typicode.com 5 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/proxy-config-valid-config-with-stubs.yaml: -------------------------------------------------------------------------------- 1 | - proxy-config: 2 | strategy: as-is 3 | properties: 4 | endpoint: https://jsonplaceholder.typicode.com 5 | 6 | - proxy-config: 7 | uuid: some-unique-name 8 | strategy: additive 9 | properties: 10 | endpoint: https://jsonplaceholder.typicode.com 11 | 12 | - request: 13 | method: 14 | - GET 15 | - POST 16 | - PUT 17 | url: ^/resources/asn/.*$ 18 | 19 | response: 20 | status: 200 21 | body: > 22 | {"status": "ASN found!"} 23 | headers: 24 | content-type: application/json 25 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/proxy-config-valid-config.yaml: -------------------------------------------------------------------------------- 1 | - proxy-config: 2 | description: this is a default catch-all config 3 | strategy: as-is 4 | properties: 5 | endpoint: https://jsonplaceholder.typicode.com 6 | headers: 7 | headerKeyOne: headerValueOne 8 | headerKeyTwo: headerValueTwo 9 | 10 | - proxy-config: 11 | description: woah! this is a unique proxy-config 12 | uuid: some-unique-name 13 | strategy: additive 14 | properties: 15 | endpoint: https://jsonplaceholder.typicode.com 16 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/proxy-config-without-default-config.yaml: -------------------------------------------------------------------------------- 1 | - proxy-config: 2 | uuid: some-unique-name 3 | strategy: as-is 4 | properties: 5 | endpoint: https://jsonplaceholder.typicode.com 6 | 7 | - proxy-config: 8 | uuid: another-unique-name 9 | strategy: additive 10 | properties: 11 | endpoint: https://jsonplaceholder.typicode.com 12 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/request.null.external.files.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /invoice 4 | file: ../somewhere/over/the/rainbow/file.json 5 | query: 6 | status: active 7 | type: full 8 | 9 | response: 10 | headers: 11 | content-type: application/json 12 | pragma: no-cache 13 | status: 200 14 | file: ../json/response.1.external.file.json -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/response.null.external.files.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /invoice 4 | file: ../json/request.external.file.json 5 | query: 6 | status: active 7 | type: full 8 | 9 | response: 10 | headers: 11 | content-type: application/json 12 | pragma: no-cache 13 | status: 200 14 | file: ../somewhere/over/the/rainbow/file.json -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/same.external.files.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /invoice 4 | query: 5 | status: active 6 | type: full 7 | file: ../json/request.external.file.json 8 | 9 | response: 10 | headers: 11 | content-type: application/json 12 | pragma: no-cache 13 | status: 200 14 | file: ../json/request.external.file.json -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/two.cycles.with.multiple.responses.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /cycle/1 4 | 5 | response: 6 | - headers: 7 | content-type: application/json 8 | status: 200 9 | file: ../json/response.1.external.file.json 10 | 11 | - headers: 12 | content-type: application/json 13 | status: 200 14 | file: ../json/response.2.external.file.json 15 | 16 | 17 | - request: 18 | method: GET 19 | url: /cycle/2 20 | 21 | response: 22 | - headers: 23 | content-type: application/json 24 | status: 200 25 | file: ../json/response.3.external.file.json 26 | 27 | - headers: 28 | content-type: application/json 29 | status: 200 30 | file: ../json/response.4.external.file.json 31 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/two.external.files.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: GET 3 | url: /invoice 4 | query: 5 | status: active 6 | type: full 7 | file: ../json/request.external.file.json 8 | 9 | response: 10 | headers: 11 | content-type: application/json 12 | pragma: no-cache 13 | status: 200 14 | file: ../json/response.1.external.file.json -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-invalid-config-with-duplicate-client-request-body-bytes.yaml: -------------------------------------------------------------------------------- 1 | - uuid: 123-567-90 2 | description: hello, web socket 3 | web-socket: 4 | url: /this/is/uri/path 5 | sub-protocols: echo 6 | 7 | on-open: 8 | policy: push 9 | message-type: text 10 | body: You have been successfully connected 11 | delay: 2000 12 | 13 | - description: hello, another web socket 14 | web-socket: 15 | url: /this/is/another/uri/path 16 | sub-protocols: mamba 17 | 18 | on-open: 19 | policy: once 20 | message-type: text 21 | body: You have been successfully connected 22 | delay: 100 23 | 24 | on-message: 25 | - client-request: 26 | message-type: binary 27 | file: ../json/response.1.external.file.json 28 | server-response: 29 | policy: disconnect 30 | message-type: text 31 | body: bye 32 | 33 | - client-request: 34 | message-type: binary 35 | file: ../json/response.1.external.file.json 36 | server-response: 37 | policy: disconnect 38 | message-type: text 39 | body: bye 40 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-invalid-config-with-duplicate-client-request-body-text.yaml: -------------------------------------------------------------------------------- 1 | - uuid: 123-567-90 2 | description: hello, web socket 3 | web-socket: 4 | url: /this/is/uri/path 5 | sub-protocols: echo 6 | 7 | on-open: 8 | policy: push 9 | message-type: text 10 | body: You have been successfully connected 11 | delay: 2000 12 | 13 | - description: hello, another web socket 14 | web-socket: 15 | url: /this/is/another/uri/path 16 | sub-protocols: mamba 17 | 18 | on-open: 19 | policy: once 20 | message-type: text 21 | body: You have been successfully connected 22 | delay: 100 23 | 24 | on-message: 25 | - client-request: 26 | message-type: text 27 | body: SAME 28 | server-response: 29 | policy: push 30 | message-type: text 31 | body: apple 32 | delay: 500 33 | 34 | - client-request: 35 | message-type: text 36 | body: SAME 37 | server-response: 38 | policy: push 39 | message-type: text 40 | body: no files for you 41 | delay: 250 42 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-invalid-config-with-duplicate-url.yaml: -------------------------------------------------------------------------------- 1 | - uuid: 123-567-90 2 | description: hello, web socket 3 | web-socket: 4 | url: /this/is/duplicate/uri/path 5 | sub-protocols: echo 6 | 7 | on-open: 8 | policy: push 9 | message-type: text 10 | body: You have been successfully connected 11 | delay: 2000 12 | 13 | - description: hello, another web socket 14 | web-socket: 15 | url: /this/is/duplicate/uri/path 16 | sub-protocols: mamba 17 | 18 | on-open: 19 | policy: once 20 | message-type: text 21 | body: You have been successfully connected 22 | delay: 100 -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-invalid-config-with-invalid-policy-name.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /items/furniture 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-open: 7 | policy: invalid-policy-name 8 | message-type: text 9 | body: You have been successfully connected 10 | delay: 2000 11 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-valid-config-with-no-on-open-no-on-message.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /items/furniture 4 | sub-protocols: echo, mamba, zumba 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-valid-config-with-no-on-open.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /items/furniture 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-message: 7 | - client-request: 8 | message-type: text 9 | body: Hey, server, say apple 10 | server-response: 11 | policy: push 12 | message-type: text 13 | body: apple 14 | delay: 500 15 | 16 | - client-request: 17 | message-type: text 18 | body: JSON file 19 | server-response: 20 | policy: push 21 | message-type: text 22 | body: no files for you 23 | delay: 250 24 | 25 | - client-request: 26 | message-type: text 27 | body: JSON file, please 28 | server-response: 29 | policy: disconnect 30 | message-type: text 31 | file: ../json/response.1.external.file.json 32 | 33 | - web-socket: 34 | url: /items/furniture/8 35 | sub-protocols: echo 36 | 37 | on-message: 38 | - client-request: 39 | message-type: text 40 | body: Hey, server, send me a huge JSON file 41 | server-response: 42 | policy: disconnect 43 | message-type: text 44 | file: ../json/response.1.external.file.json 45 | delay: 250 46 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-valid-config-with-sequenced-responses.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /items/fruits 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-open: 7 | policy: once 8 | message-type: text 9 | body: You have been successfully connected 10 | delay: 2000 11 | 12 | on-message: 13 | - client-request: 14 | message-type: text 15 | body: Hey, server, give me fruits 16 | 17 | server-response: 18 | - policy: push 19 | message-type: text 20 | body: apple 21 | delay: 500 22 | - policy: push 23 | message-type: text 24 | body: banana 25 | delay: 250 26 | - policy: fragmentation 27 | message-type: binary 28 | file: ../json/response.5.external.file.json 29 | delay: 250 30 | - policy: push 31 | message-type: text 32 | body: grapes 33 | delay: 500 34 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-valid-config-with-uuid-and-description-on-top.yaml: -------------------------------------------------------------------------------- 1 | - uuid: 123-567-90 2 | description: hello, web socket 3 | web-socket: 4 | url: /items/furniture 5 | sub-protocols: echo, mamba, zumba 6 | 7 | on-open: 8 | policy: once 9 | message-type: text 10 | body: You have been successfully connected 11 | delay: 2000 12 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-valid-config-without-on-message.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /items/furniture 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-open: 7 | policy: once 8 | message-type: text 9 | body: You have been successfully connected 10 | delay: 2000 11 | 12 | -------------------------------------------------------------------------------- /src/integrationTest/resources/yaml/web-socket-valid-config.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /items/furniture 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-open: 7 | policy: once 8 | message-type: text 9 | body: You have been successfully connected 10 | delay: 2000 11 | 12 | on-message: 13 | - client-request: 14 | message-type: text 15 | body: Hey, server, say apple 16 | server-response: 17 | policy: push 18 | message-type: text 19 | body: apple 20 | delay: 500 21 | 22 | - client-request: 23 | message-type: text 24 | body: JSON file 25 | server-response: 26 | policy: push 27 | message-type: text 28 | body: no files for you 29 | delay: 250 30 | 31 | - client-request: 32 | message-type: text 33 | body: JSON file, please 34 | server-response: 35 | policy: disconnect 36 | message-type: text 37 | file: ../json/response.1.external.file.json 38 | 39 | - web-socket: 40 | url: /items/furniture/8 41 | sub-protocols: echo 42 | 43 | on-open: 44 | policy: push 45 | message-type: text 46 | body: You have been successfully connected 47 | 48 | on-message: 49 | - client-request: 50 | message-type: text 51 | body: Hey, server, send me a huge JSON file 52 | server-response: 53 | policy: disconnect 54 | message-type: text 55 | file: ../json/response.1.external.file.json 56 | delay: 250 57 | 58 | - web-socket: 59 | url: /items/furniture/10 60 | sub-protocols: echo 61 | 62 | on-message: 63 | - client-request: 64 | message-type: text 65 | body: fragmentation-start 66 | server-response: 67 | policy: fragmentation 68 | message-type: binary 69 | file: ../json/response.1.external.file.json 70 | delay: 250 -------------------------------------------------------------------------------- /src/loadTest/java/io/github/azagniotov/stubby4j/PortTestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j; 18 | 19 | import static io.github.azagniotov.stubby4j.SpringSocketUtils.PORT_RANGE_MAX; 20 | import static io.github.azagniotov.stubby4j.SpringSocketUtils.PORT_RANGE_MIN; 21 | 22 | final class PortTestUtils { 23 | 24 | private PortTestUtils() {} 25 | 26 | static int findAvailableTcpPort() { 27 | return SpringSocketUtils.findAvailableTcpPort(PORT_RANGE_MIN, PORT_RANGE_MAX); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/annotations/GeneratedCodeClassCoverageExclusion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.annotations; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** 26 | * Marker annotation for exclusion of class from JaCoCo code coverage instrumentation. 27 | *

28 | * Required by JaCoCo starting from v0.83 29 | * https://github.com/jacoco/jacoco/pull/822 30 | * https://www.jacoco.org/jacoco/trunk/doc/changes.html (Release 0.8.3 (2019/01/23)) 31 | * https://github.com/jacoco/jacoco/blob/f72c2c865fa7c975debb1b3156120501843f5c74/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilter.java#L51 32 | * By default, annotation retention policy is CLASS, which does not make your annotation available to reflection 33 | */ 34 | @Documented 35 | @Retention(RetentionPolicy.CLASS) 36 | @Target({ElementType.TYPE}) 37 | public @interface GeneratedCodeClassCoverageExclusion {} 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/annotations/GeneratedCodeMethodCoverageExclusion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.annotations; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** 26 | * Marker annotation for exclusion of method from JaCoCo code coverage instrumentation. 27 | *

28 | * Required by JaCoCo starting from v0.83 29 | * https://github.com/jacoco/jacoco/pull/822 30 | * https://www.jacoco.org/jacoco/trunk/doc/changes.html (Release 0.8.3 (2019/01/23)) 31 | * https://github.com/jacoco/jacoco/blob/f72c2c865fa7c975debb1b3156120501843f5c74/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilter.java#L51 32 | * By default, annotation retention policy is RUNTIME, makes your annotation available to reflection 33 | */ 34 | @Documented 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) 37 | public @interface GeneratedCodeMethodCoverageExclusion {} 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/annotations/PotentiallyFlaky.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Retention(RetentionPolicy.RUNTIME) 25 | @Target({ElementType.METHOD, ElementType.TYPE}) 26 | public @interface PotentiallyFlaky { 27 | /** 28 | * The optional reason why the test is considered flaky. 29 | */ 30 | String value() default ""; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/annotations/VisibleForTesting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.annotations; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * An annotation that indicates that the visibility of a type or member has been relaxed to make the code testable. 26 | * Inspired by @VisibleForTesting from Google's Guava library 27 | */ 28 | @Retention(RetentionPolicy.CLASS) 29 | @Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD}) 30 | public @interface VisibleForTesting {} 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/caching/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.caching; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubHttpLifecycle; 20 | import java.util.Optional; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import java.util.regex.Pattern; 23 | import org.ehcache.UserManagedCache; 24 | 25 | public interface Cache { 26 | 27 | long CACHE_ENTRY_LIFETIME_SECONDS = 3600L; // 3600 secs => 60 minutes 28 | 29 | static Cache stubHttpLifecycleCache(final boolean buildNoOpCache) { 30 | if (buildNoOpCache) { 31 | return new NoOpStubHttpLifecycleCache(); 32 | } else { 33 | return new StubHttpLifecycleCache(CACHE_ENTRY_LIFETIME_SECONDS); 34 | } 35 | } 36 | 37 | static Cache regexPatternCache() { 38 | return new RegexPatternCache(CACHE_ENTRY_LIFETIME_SECONDS); 39 | } 40 | 41 | default Optional get(final K key) { 42 | return Optional.ofNullable(cache().get(key)); 43 | } 44 | 45 | default void putIfAbsent(final K key, final V value) { 46 | if (!cache().containsKey(key)) { 47 | cache().put(key, value); 48 | size().incrementAndGet(); 49 | } 50 | } 51 | 52 | default boolean clearByKey(final K key) { 53 | if (cache().containsKey(key)) { 54 | cache().remove(key); 55 | size().decrementAndGet(); 56 | 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | default void clear() { 64 | cache().clear(); 65 | size().set(0); 66 | } 67 | 68 | UserManagedCache cache(); 69 | 70 | AtomicInteger size(); 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/caching/NoOpStubHttpLifecycleCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.caching; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubHttpLifecycle; 20 | import java.util.Optional; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import org.ehcache.UserManagedCache; 23 | 24 | class NoOpStubHttpLifecycleCache implements Cache { 25 | 26 | private static final AtomicInteger ATOMIC_INTEGER_ZERO = new AtomicInteger(); 27 | 28 | NoOpStubHttpLifecycleCache() {} 29 | 30 | @Override 31 | public Optional get(final String key) { 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public void putIfAbsent(final String key, final StubHttpLifecycle value) { 37 | // NO-OP 38 | } 39 | 40 | @Override 41 | public boolean clearByKey(final String key) { 42 | return true; 43 | } 44 | 45 | @Override 46 | public void clear() { 47 | // NO-OP 48 | } 49 | 50 | @Override 51 | public UserManagedCache cache() { 52 | throw new UnsupportedOperationException(); 53 | } 54 | 55 | @Override 56 | public AtomicInteger size() { 57 | return ATOMIC_INTEGER_ZERO; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/caching/RegexPatternCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.caching; 18 | 19 | import java.time.Duration; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | import java.util.regex.Pattern; 22 | import org.ehcache.UserManagedCache; 23 | import org.ehcache.config.builders.ExpiryPolicyBuilder; 24 | import org.ehcache.config.builders.ResourcePoolsBuilder; 25 | import org.ehcache.config.builders.UserManagedCacheBuilder; 26 | 27 | class RegexPatternCache implements Cache { 28 | 29 | private final AtomicInteger cacheSize; 30 | private final UserManagedCache localCache; 31 | 32 | RegexPatternCache(final long cacheEntryLifetimeSeconds) { 33 | final Duration timeToLiveExpiration = Duration.ofSeconds(cacheEntryLifetimeSeconds); 34 | 35 | this.localCache = UserManagedCacheBuilder.newUserManagedCacheBuilder(Integer.class, Pattern.class) 36 | .withResourcePools(ResourcePoolsBuilder.heap(500L)) 37 | .identifier(this.getClass().getSimpleName()) 38 | .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(timeToLiveExpiration)) 39 | .build(true); 40 | 41 | this.cacheSize = new AtomicInteger(0); 42 | } 43 | 44 | @Override 45 | public UserManagedCache cache() { 46 | return localCache; 47 | } 48 | 49 | @Override 50 | public AtomicInteger size() { 51 | return cacheSize; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/caching/StubHttpLifecycleCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.caching; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubHttpLifecycle; 20 | import java.time.Duration; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import org.ehcache.UserManagedCache; 23 | import org.ehcache.config.builders.ExpiryPolicyBuilder; 24 | import org.ehcache.config.builders.ResourcePoolsBuilder; 25 | import org.ehcache.config.builders.UserManagedCacheBuilder; 26 | 27 | class StubHttpLifecycleCache implements Cache { 28 | 29 | private final AtomicInteger cacheSize; 30 | private final UserManagedCache localCache; 31 | 32 | StubHttpLifecycleCache(final long cacheEntryLifetimeSeconds) { 33 | final Duration timeToLiveExpiration = Duration.ofSeconds(cacheEntryLifetimeSeconds); 34 | 35 | this.localCache = UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, StubHttpLifecycle.class) 36 | .withResourcePools(ResourcePoolsBuilder.heap(500L)) 37 | .identifier(this.getClass().getSimpleName()) 38 | .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(timeToLiveExpiration)) 39 | .build(true); 40 | 41 | this.cacheSize = new AtomicInteger(0); 42 | } 43 | 44 | @Override 45 | public UserManagedCache cache() { 46 | return localCache; 47 | } 48 | 49 | @Override 50 | public AtomicInteger size() { 51 | return cacheSize; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/cli/EmptyLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.cli; 18 | 19 | import io.github.azagniotov.stubby4j.annotations.GeneratedCodeClassCoverageExclusion; 20 | import org.eclipse.jetty.util.log.Logger; 21 | 22 | /** 23 | * Class used to suppress default console output of Jetty 24 | * 25 | * @author Eric Mrak 26 | */ 27 | @GeneratedCodeClassCoverageExclusion 28 | public final class EmptyLogger implements Logger { 29 | @Override 30 | public String getName() { 31 | return null; 32 | } 33 | 34 | @Override 35 | public void warn(final String s, final Object... objects) {} 36 | 37 | @Override 38 | public void warn(final Throwable throwable) {} 39 | 40 | @Override 41 | public void warn(final String s, final Throwable throwable) {} 42 | 43 | @Override 44 | public void info(final String s, final Object... objects) {} 45 | 46 | @Override 47 | public void info(final Throwable throwable) {} 48 | 49 | @Override 50 | public void info(final String s, final Throwable throwable) {} 51 | 52 | @Override 53 | public boolean isDebugEnabled() { 54 | return false; 55 | } 56 | 57 | @Override 58 | public void setDebugEnabled(final boolean b) {} 59 | 60 | @Override 61 | public void debug(final String s, final Object... objects) {} 62 | 63 | @Override 64 | public void debug(final String msg, final long value) {} 65 | 66 | @Override 67 | public void debug(final Throwable throwable) {} 68 | 69 | @Override 70 | public void debug(final String s, final Throwable throwable) {} 71 | 72 | @Override 73 | public Logger getLogger(final String s) { 74 | return this; 75 | } 76 | 77 | @Override 78 | public void ignore(final Throwable throwable) {} 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/client/Authorization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.client; 18 | 19 | import io.github.azagniotov.stubby4j.annotations.GeneratedCodeMethodCoverageExclusion; 20 | 21 | public final class Authorization { 22 | 23 | private final AuthorizationType authorizationType; 24 | private final String value; 25 | 26 | public Authorization(final AuthorizationType authorizationType, final String value) { 27 | this.authorizationType = authorizationType; 28 | this.value = value; 29 | } 30 | 31 | public String asFullValue() { 32 | if (authorizationType == AuthorizationType.CUSTOM) { 33 | return value; 34 | } else { 35 | return String.format("%s %s", authorizationType.asString(), value); 36 | } 37 | } 38 | 39 | @Override 40 | @GeneratedCodeMethodCoverageExclusion 41 | public final String toString() { 42 | final StringBuilder sb = new StringBuilder(); 43 | sb.append("Authorization"); 44 | sb.append("{type=").append(authorizationType); 45 | sb.append(", value=").append(value); 46 | sb.append('}'); 47 | 48 | return sb.toString(); 49 | } 50 | 51 | enum AuthorizationType { 52 | BASIC("Basic"), 53 | BEARER("Bearer"), 54 | CUSTOM("Custom"); 55 | private final String type; 56 | 57 | AuthorizationType(final String type) { 58 | this.type = type; 59 | } 60 | 61 | public String asString() { 62 | return type; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/client/StubbyRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.client; 18 | 19 | import io.github.azagniotov.stubby4j.utils.StringUtils; 20 | import java.util.Locale; 21 | 22 | final class StubbyRequest { 23 | 24 | private static final String URL_TEMPLATE = "%s://%s:%s%s"; 25 | 26 | private final String scheme; 27 | private final String method; 28 | private final String uri; 29 | private final String host; 30 | private final String post; 31 | private final Authorization authorization; 32 | private final int clientPort; 33 | 34 | StubbyRequest( 35 | final String scheme, 36 | final String method, 37 | final String uri, 38 | final String host, 39 | final int port, 40 | final Authorization authorization) { 41 | this(scheme, method, uri, host, port, authorization, null); 42 | } 43 | 44 | StubbyRequest( 45 | final String scheme, 46 | final String method, 47 | final String uri, 48 | final String host, 49 | final int clientPort, 50 | final Authorization authorization, 51 | final String post) { 52 | this.scheme = scheme; 53 | this.method = method; 54 | this.uri = uri; 55 | this.host = host; 56 | this.clientPort = clientPort; 57 | this.post = post; 58 | this.authorization = authorization; 59 | } 60 | 61 | String getMethod() { 62 | return method; 63 | } 64 | 65 | String getPost() { 66 | return StringUtils.isSet(post) ? post : ""; 67 | } 68 | 69 | Authorization getAuthorization() { 70 | return authorization; 71 | } 72 | 73 | String constructFullUrl() { 74 | return String.format( 75 | URL_TEMPLATE, scheme.toLowerCase(Locale.US), host, clientPort, StringUtils.isSet(uri) ? uri : ""); 76 | } 77 | 78 | int calculatePostLength() { 79 | return StringUtils.calculateStringLength(post); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/client/StubbyResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.client; 18 | 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | public final class StubbyResponse { 24 | 25 | private final int statusCode; 26 | private final String body; 27 | private final Map> headerFields; 28 | 29 | public StubbyResponse(final int statusCode, final String body, final Map> headerFields) { 30 | this.statusCode = statusCode; 31 | this.body = body; 32 | this.headerFields = headerFields; 33 | } 34 | 35 | public int statusCode() { 36 | return statusCode; 37 | } 38 | 39 | public String body() { 40 | return body; 41 | } 42 | 43 | public Map> headers() { 44 | return new HashMap<>(headerFields); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/common/Common.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.common; 18 | 19 | import io.github.azagniotov.stubby4j.http.HttpMethodExtended; 20 | import java.util.Collections; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | import org.eclipse.jetty.http.HttpMethod; 24 | 25 | public final class Common { 26 | 27 | public static final Set POSTING_METHODS = Collections.unmodifiableSet(new HashSet() { 28 | { 29 | add(HttpMethod.PUT.asString()); 30 | add(HttpMethod.POST.asString()); 31 | // PATCH is not a part of org.eclipse.jetty.http.HttpMethod 32 | add(HttpMethodExtended.PATCH.asString()); 33 | } 34 | }); 35 | 36 | public static final String HEADER_APPLICATION_JSON = "application/json"; 37 | public static final String HEADER_APPLICATION_XML = "application/xml"; 38 | public static final String HEADER_X_STUBBY_RESOURCE_ID = "x-stubby-resource-id"; 39 | public static final String HEADER_X_STUBBY_PROXY_CONFIG = "x-stubby4j-proxy-config-uuid"; 40 | public static final String HEADER_X_STUBBY_PROXY_REQUEST = "x-stubby4j-proxy-request-uuid"; 41 | public static final String HEADER_X_STUBBY_PROXY_RESPONSE = "x-stubby4j-proxy-response-uuid"; 42 | public static final String HEADER_X_STUBBY_HTTP_ERROR_REAL_REASON = "x-stubby4j-http-error-real-reason"; 43 | 44 | private Common() {} 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/AbstractHandlerExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers; 18 | 19 | import io.github.azagniotov.stubby4j.utils.ConsoleUtils; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import org.eclipse.jetty.server.Request; 23 | 24 | public interface AbstractHandlerExtension { 25 | 26 | default boolean logAndCheckIsHandled( 27 | final String handlerName, 28 | final Request baseRequest, 29 | final HttpServletRequest request, 30 | final HttpServletResponse response) { 31 | ConsoleUtils.logIncomingRequest(request); 32 | if (baseRequest.isHandled() || response.isCommitted()) { 33 | ConsoleUtils.logIncomingRequestError( 34 | request, handlerName, "HTTP response was committed or base request was handled, aborting.."); 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/StubsPortalHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers; 18 | 19 | import static io.github.azagniotov.stubby4j.handlers.strategy.stubs.StubsResponseHandlingStrategyFactory.getStrategy; 20 | 21 | import io.github.azagniotov.stubby4j.handlers.strategy.stubs.StubResponseHandlingStrategy; 22 | import io.github.azagniotov.stubby4j.stubs.StubRepository; 23 | import io.github.azagniotov.stubby4j.stubs.StubSearchResult; 24 | import io.github.azagniotov.stubby4j.utils.ConsoleUtils; 25 | import io.github.azagniotov.stubby4j.utils.HandlerUtils; 26 | import java.io.IOException; 27 | import javax.servlet.ServletException; 28 | import javax.servlet.http.HttpServletRequest; 29 | import javax.servlet.http.HttpServletResponse; 30 | import org.eclipse.jetty.http.HttpStatus; 31 | import org.eclipse.jetty.server.Request; 32 | import org.eclipse.jetty.server.handler.AbstractHandler; 33 | 34 | public class StubsPortalHandler extends AbstractHandler implements AbstractHandlerExtension { 35 | 36 | private final StubRepository stubRepository; 37 | 38 | public StubsPortalHandler(final StubRepository stubRepository) { 39 | this.stubRepository = stubRepository; 40 | } 41 | 42 | @Override 43 | public void handle( 44 | final String target, 45 | final Request baseRequest, 46 | final HttpServletRequest request, 47 | final HttpServletResponse response) 48 | throws IOException, ServletException { 49 | if (logAndCheckIsHandled("stubs", baseRequest, request, response)) { 50 | return; 51 | } 52 | baseRequest.setHandled(true); 53 | 54 | try { 55 | final StubSearchResult stubSearchResult = stubRepository.search(request); 56 | final StubResponseHandlingStrategy strategyStubResponse = getStrategy(stubSearchResult.getMatch()); 57 | 58 | strategyStubResponse.handle(response, stubSearchResult.getInvariant()); 59 | ConsoleUtils.logOutgoingResponse(stubSearchResult.getInvariant().getUrl(), response); 60 | } catch (final Exception ex) { 61 | HandlerUtils.configureErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR_500, ex.toString()); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/admin/AdminResponseHandlingStrategyFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.admin; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | 21 | public final class AdminResponseHandlingStrategyFactory { 22 | 23 | private AdminResponseHandlingStrategyFactory() {} 24 | 25 | public static AdminResponseHandlingStrategy getStrategy(final HttpServletRequest request) { 26 | 27 | final String method = request.getMethod(); 28 | final HttpVerbsEnum verbEnum; 29 | 30 | try { 31 | verbEnum = HttpVerbsEnum.valueOf(method); 32 | } catch (final IllegalArgumentException ex) { 33 | return new NullHandlingStrategy(); 34 | } 35 | 36 | switch (verbEnum) { 37 | case POST: 38 | return new PostHandlingStrategy(); 39 | 40 | case PUT: 41 | return new PutHandlingStrategy(); 42 | 43 | case DELETE: 44 | return new DeleteHandlingStrategy(); 45 | 46 | default: 47 | return new GetHandlingStrategy(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/admin/HttpVerbsEnum.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.admin; 18 | 19 | public enum HttpVerbsEnum { 20 | GET, 21 | PUT, 22 | POST, 23 | DELETE; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/admin/NullHandlingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.admin; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubRepository; 20 | import java.io.IOException; 21 | import javax.servlet.http.HttpServletRequest; 22 | import javax.servlet.http.HttpServletResponse; 23 | import org.eclipse.jetty.http.HttpStatus; 24 | 25 | public class NullHandlingStrategy implements AdminResponseHandlingStrategy { 26 | 27 | @Override 28 | public void handle( 29 | final HttpServletRequest request, final HttpServletResponse response, final StubRepository stubRepository) 30 | throws IOException { 31 | response.setStatus(HttpStatus.NOT_IMPLEMENTED_501); 32 | response.getWriter() 33 | .println(String.format( 34 | "Method %s is not implemented on URI %s", request.getMethod(), request.getRequestURI())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/admin/PostHandlingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.admin; 18 | 19 | import io.github.azagniotov.stubby4j.handlers.AdminPortalHandler; 20 | import io.github.azagniotov.stubby4j.stubs.StubRepository; 21 | import io.github.azagniotov.stubby4j.utils.HandlerUtils; 22 | import io.github.azagniotov.stubby4j.yaml.YamlParser; 23 | import java.io.IOException; 24 | import java.util.Optional; 25 | import javax.servlet.http.HttpServletRequest; 26 | import javax.servlet.http.HttpServletResponse; 27 | import org.eclipse.jetty.http.HttpHeader; 28 | import org.eclipse.jetty.http.HttpStatus; 29 | 30 | public class PostHandlingStrategy implements AdminResponseHandlingStrategy { 31 | 32 | private static final int NUM_OF_STUBS_THRESHOLD = 1; 33 | 34 | @Override 35 | public void handle( 36 | final HttpServletRequest request, final HttpServletResponse response, final StubRepository stubRepository) 37 | throws Exception { 38 | 39 | if (!request.getRequestURI().equals(AdminPortalHandler.ADMIN_ROOT)) { 40 | response.setStatus(HttpStatus.METHOD_NOT_ALLOWED_405); 41 | return; 42 | } 43 | 44 | final Optional payloadOptional = extractRequestBodyWithOptionalError(request, response); 45 | if (payloadOptional.isPresent()) { 46 | try { 47 | stubRepository.refreshStubsByPost(new YamlParser(), payloadOptional.get()); 48 | if (stubRepository.getStubs().size() == NUM_OF_STUBS_THRESHOLD) { 49 | response.addHeader(HttpHeader.LOCATION.asString(), stubRepository.getOnlyStubRequestUrl()); 50 | } 51 | 52 | response.setStatus(HttpStatus.CREATED_201); 53 | response.getWriter().println("Configuration created successfully"); 54 | } catch (IOException a) { 55 | // Thrown by YamlParser if there are duplicate UUID keys or un-parseable YAML 56 | HandlerUtils.configureErrorResponse(response, HttpStatus.BAD_REQUEST_400, a.getMessage()); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/stubs/NotFoundResponseHandlingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.stubs; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubRequest; 20 | import io.github.azagniotov.stubby4j.utils.HandlerUtils; 21 | import javax.servlet.http.HttpServletResponse; 22 | import org.eclipse.jetty.http.HttpStatus; 23 | import org.json.JSONObject; 24 | 25 | public final class NotFoundResponseHandlingStrategy implements StubResponseHandlingStrategy { 26 | 27 | NotFoundResponseHandlingStrategy() {} 28 | 29 | @Override 30 | public void handle(final HttpServletResponse response, final StubRequest assertionStubRequest) throws Exception { 31 | 32 | HandlerUtils.setResponseMainHeaders(response); 33 | 34 | final String reason = String.format( 35 | "(404) Nothing found for %s request at URI %s", 36 | assertionStubRequest.getMethod().get(0), assertionStubRequest.getUrl()); 37 | 38 | final JSONObject json404Response = new JSONObject(); 39 | json404Response.put("reason", reason); 40 | json404Response.put("method", assertionStubRequest.getMethod().get(0)); 41 | json404Response.put("url", assertionStubRequest.getUrl()); 42 | 43 | if (assertionStubRequest.hasQuery()) { 44 | json404Response.put("query", new JSONObject(assertionStubRequest.getQuery())); 45 | } 46 | 47 | if (assertionStubRequest.hasHeaders()) { 48 | json404Response.put("headers", new JSONObject(assertionStubRequest.getHeaders())); 49 | } 50 | 51 | if (assertionStubRequest.hasPostBody()) { 52 | json404Response.put("post", assertionStubRequest.getPostBody()); 53 | } 54 | HandlerUtils.configureErrorResponse(response, HttpStatus.NOT_FOUND_404, json404Response.toString()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/stubs/RedirectResponseHandlingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.stubs; 18 | 19 | import static io.github.azagniotov.stubby4j.utils.StringUtils.isTokenized; 20 | import static io.github.azagniotov.stubby4j.utils.StringUtils.replaceTokensInString; 21 | 22 | import io.github.azagniotov.stubby4j.stubs.StubRequest; 23 | import io.github.azagniotov.stubby4j.stubs.StubResponse; 24 | import io.github.azagniotov.stubby4j.utils.HandlerUtils; 25 | import io.github.azagniotov.stubby4j.utils.StringUtils; 26 | import java.util.Map; 27 | import java.util.concurrent.TimeUnit; 28 | import javax.servlet.http.HttpServletResponse; 29 | import org.eclipse.jetty.http.HttpHeader; 30 | 31 | public class RedirectResponseHandlingStrategy implements StubResponseHandlingStrategy { 32 | 33 | private final StubResponse foundStubResponse; 34 | 35 | RedirectResponseHandlingStrategy(final StubResponse foundStubResponse) { 36 | this.foundStubResponse = foundStubResponse; 37 | } 38 | 39 | @Override 40 | public void handle(final HttpServletResponse response, final StubRequest assertionStubRequest) throws Exception { 41 | HandlerUtils.setResponseMainHeaders(response); 42 | final Map regexGroups = assertionStubRequest.getRegexGroups(); 43 | 44 | if (StringUtils.isSet(foundStubResponse.getLatency())) { 45 | final long latency = Long.parseLong(foundStubResponse.getLatency()); 46 | TimeUnit.MILLISECONDS.sleep(latency); 47 | } 48 | 49 | final String headerLocation = foundStubResponse.getHeaders().get("location"); 50 | if (isTokenized(headerLocation)) { 51 | response.setHeader(HttpHeader.LOCATION.asString(), replaceTokensInString(headerLocation, regexGroups)); 52 | } else { 53 | response.setHeader(HttpHeader.LOCATION.asString(), headerLocation); 54 | } 55 | 56 | response.setStatus(foundStubResponse.getHttpStatusCode().getCode()); 57 | response.setHeader(HttpHeader.CONNECTION.asString(), "close"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/stubs/StubResponseHandlingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.stubs; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | 22 | public interface StubResponseHandlingStrategy { 23 | void handle(final HttpServletResponse response, final StubRequest assertionStubRequest) throws Exception; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/stubs/StubsResponseHandlingStrategyFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.stubs; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubResponse; 20 | import org.eclipse.jetty.http.HttpStatus; 21 | 22 | public final class StubsResponseHandlingStrategyFactory { 23 | 24 | private StubsResponseHandlingStrategyFactory() {} 25 | 26 | public static StubResponseHandlingStrategy getStrategy(final StubResponse foundStubResponse) { 27 | 28 | final HttpStatus.Code httpStatusCode = foundStubResponse.getHttpStatusCode(); 29 | switch (httpStatusCode) { 30 | case NOT_FOUND: 31 | if (foundStubResponse.getResponseBodyAsBytes().length == 0) { 32 | return new NotFoundResponseHandlingStrategy(); 33 | } 34 | break; 35 | case UNAUTHORIZED: 36 | return new UnauthorizedResponseHandlingStrategy(); 37 | 38 | case MOVED_PERMANENTLY: 39 | /* 40 | This is an example of industry practice contradicting the standard. 41 | The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect 42 | (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with 43 | the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to 44 | distinguish between the two behaviours. However, some Web applications and frameworks use the 302 45 | status code as if it were the 303. 46 | */ 47 | case FOUND: 48 | case MOVED_TEMPORARILY: 49 | case SEE_OTHER: 50 | case TEMPORARY_REDIRECT: 51 | /* 52 | The request and all future requests should be repeated using another URI. 307 and 308 parallel the 53 | behaviors of 302 and 301, but do not allow the HTTP method to change. So, for example, submitting a 54 | form to a permanently redirected resource may continue smoothly. 55 | */ 56 | case PERMANENT_REDIRECT: 57 | return new RedirectResponseHandlingStrategy(foundStubResponse); 58 | } 59 | 60 | return new DefaultResponseHandlingStrategy(foundStubResponse); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/handlers/strategy/stubs/UnauthorizedResponseHandlingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy.stubs; 18 | 19 | import io.github.azagniotov.stubby4j.annotations.VisibleForTesting; 20 | import io.github.azagniotov.stubby4j.stubs.StubRequest; 21 | import io.github.azagniotov.stubby4j.utils.HandlerUtils; 22 | import io.github.azagniotov.stubby4j.utils.StringUtils; 23 | import javax.servlet.http.HttpServletResponse; 24 | import org.eclipse.jetty.http.HttpStatus; 25 | 26 | public final class UnauthorizedResponseHandlingStrategy implements StubResponseHandlingStrategy { 27 | 28 | @VisibleForTesting 29 | public static final String NO_AUTHORIZATION_HEADER = 30 | "You are not authorized to view this page without supplied 'Authorization' HTTP header"; 31 | 32 | @VisibleForTesting 33 | public static final String WRONG_AUTHORIZATION_HEADER_TEMPLATE = 34 | "Unauthorized with supplied 'authorized' header value: '%s'"; 35 | 36 | UnauthorizedResponseHandlingStrategy() {} 37 | 38 | @Override 39 | public void handle(final HttpServletResponse response, final StubRequest assertionStubRequest) throws Exception { 40 | HandlerUtils.setResponseMainHeaders(response); 41 | final String authorizationHeader = assertionStubRequest.getRawHeaderAuthorization(); 42 | if (!StringUtils.isSet(authorizationHeader)) { 43 | HandlerUtils.configureErrorResponse(response, HttpStatus.UNAUTHORIZED_401, NO_AUTHORIZATION_HEADER); 44 | return; 45 | } 46 | 47 | HandlerUtils.configureErrorResponse( 48 | response, 49 | HttpStatus.UNAUTHORIZED_401, 50 | String.format(WRONG_AUTHORIZATION_HEADER_TEMPLATE, authorizationHeader)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/http/HttpMethodExtended.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.http; 18 | 19 | public enum HttpMethodExtended { 20 | PATCH; 21 | 22 | HttpMethodExtended() {} 23 | 24 | public String asString() { 25 | return toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/server/JettyContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.server; 18 | 19 | import io.github.azagniotov.stubby4j.annotations.GeneratedCodeClassCoverageExclusion; 20 | 21 | @GeneratedCodeClassCoverageExclusion 22 | public final class JettyContext { 23 | 24 | private final String host; 25 | private final int stubsSslPort; 26 | private final int stubsPort; 27 | private final int adminPort; 28 | 29 | public JettyContext(final String host, final int stubsPort, final int stubsSslPort, final int adminPort) { 30 | this.host = host; 31 | this.stubsSslPort = stubsSslPort; 32 | this.stubsPort = stubsPort; 33 | this.adminPort = adminPort; 34 | } 35 | 36 | public int getStubsTlsPort() { 37 | return stubsSslPort; 38 | } 39 | 40 | public int getStubsPort() { 41 | return stubsPort; 42 | } 43 | 44 | public int getAdminPort() { 45 | return adminPort; 46 | } 47 | 48 | public String getHost() { 49 | return host; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/server/ssl/LanIPv4Validator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.server.ssl; 18 | 19 | import java.util.regex.Pattern; 20 | 21 | public final class LanIPv4Validator { 22 | 23 | /** 24 | * 127.0.0.0 – 127.255.255.255 127.0.0.0 /8 25 | * 10.0.0.0 – 10.255.255.255 10.0.0.0 /8 26 | * 172.16.0.0 – 172. 31.255.255 172.16.0.0 /12 27 | * 192.168.0.0 – 192.168.255.255 192.168.0.0 /16 28 | */ 29 | private static final String LAN_IPV4_PATTERN = 30 | "(^192\\.168\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])$)|(^172\\.([1][6-9]|[2][0-9]|[3][0-1])\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])$)|(^10\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])\\.([0-9]|[0-9][0-9]|[0-2][0-5][0-5])$)"; 31 | 32 | private static final Pattern PATTERN = Pattern.compile(LAN_IPV4_PATTERN); 33 | 34 | private LanIPv4Validator() {} 35 | 36 | static boolean isPrivateIp(final String privateIp) { 37 | return PATTERN.matcher(privateIp).matches(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/AbstractBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs; 18 | 19 | import static io.github.azagniotov.generics.TypeSafeConverter.as; 20 | 21 | import io.github.azagniotov.stubby4j.yaml.ConfigurableYAMLProperty; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | public abstract class AbstractBuilder { 27 | 28 | protected final Map fieldNameAndValues; 29 | 30 | public AbstractBuilder() { 31 | this.fieldNameAndValues = new HashMap<>(); 32 | } 33 | 34 | public E getStaged(final Class clazzor, final ConfigurableYAMLProperty property, E defaultValue) { 35 | return fieldNameAndValues.containsKey(property) ? as(clazzor, fieldNameAndValues.get(property)) : defaultValue; 36 | } 37 | 38 | public void stage(final ConfigurableYAMLProperty fieldName, final Optional fieldValueOptional) { 39 | fieldValueOptional.ifPresent(value -> fieldNameAndValues.put(fieldName, value)); 40 | } 41 | 42 | public abstract String yamlFamilyName(); 43 | 44 | public abstract T build(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/ReflectableStub.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs; 18 | 19 | /** 20 | * Marker interface 21 | */ 22 | public interface ReflectableStub {} 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/StubSearchResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs; 18 | 19 | public class StubSearchResult { 20 | 21 | private final StubRequest invariant; 22 | private final StubResponse match; 23 | 24 | StubSearchResult(final StubRequest invariant, final StubResponse match) { 25 | this.invariant = invariant; 26 | this.match = match; 27 | } 28 | 29 | public StubRequest getInvariant() { 30 | return invariant; 31 | } 32 | 33 | public StubResponse getMatch() { 34 | return match; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/StubTypes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs; 18 | 19 | public enum StubTypes { 20 | HTTPLIFECYCLE, 21 | REQUEST, 22 | RESPONSE 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/StubbableAuthorizationType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs; 18 | 19 | import io.github.azagniotov.stubby4j.utils.StringUtils; 20 | 21 | public enum StubbableAuthorizationType { 22 | BASIC("Basic"), 23 | BEARER("Bearer"), 24 | CUSTOM("Custom"); 25 | 26 | private final String name; 27 | private final String property; 28 | 29 | StubbableAuthorizationType(final String name) { 30 | this.name = name; 31 | this.property = String.format("authorization-%s", StringUtils.toLower(this.name)); 32 | } 33 | 34 | public String asYAMLProp() { 35 | return property; 36 | } 37 | 38 | public String asString() { 39 | return name; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/proxy/StubProxyStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs.proxy; 18 | 19 | import static io.github.azagniotov.stubby4j.utils.StringUtils.toLower; 20 | 21 | import java.util.EnumSet; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | public enum StubProxyStrategy { 27 | /** 28 | * No changes/modifications will be applied to the request before proxying it. 29 | *

30 | * See: https://github.com/azagniotov/stubby4j/blob/master/docs/REQUEST_PROXYING.md#supported-yaml-properties 31 | */ 32 | AS_IS("as-is"), 33 | 34 | /** 35 | * Additive changes will be applied to the request before proxying it. The additive changes 36 | * currently supported are setting of additional HTTP headers using the `headers` property on the 37 | * `proxy-config` object. 38 | *

39 | * See: https://github.com/azagniotov/stubby4j/blob/master/docs/REQUEST_PROXYING.md#supported-yaml-properties 40 | */ 41 | ADDITIVE("additive"); 42 | 43 | private static final Map PROPERTY_NAME_TO_ENUM_MEMBER; 44 | 45 | static { 46 | PROPERTY_NAME_TO_ENUM_MEMBER = new HashMap<>(); 47 | for (final StubProxyStrategy enumMember : EnumSet.allOf(StubProxyStrategy.class)) { 48 | PROPERTY_NAME_TO_ENUM_MEMBER.put(enumMember.toString(), enumMember); 49 | } 50 | } 51 | 52 | private final String value; 53 | 54 | StubProxyStrategy(final String value) { 55 | this.value = value; 56 | } 57 | 58 | public static boolean isUnknownProperty(final String stubbedProperty) { 59 | return !PROPERTY_NAME_TO_ENUM_MEMBER.containsKey(toLower(stubbedProperty)); 60 | } 61 | 62 | public static Optional ofNullableProperty(final String stubbedProperty) { 63 | return Optional.ofNullable(PROPERTY_NAME_TO_ENUM_MEMBER.get(toLower(stubbedProperty))); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return toLower(this.value); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/websocket/StubWebSocketMessageType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs.websocket; 18 | 19 | import static io.github.azagniotov.stubby4j.utils.StringUtils.toLower; 20 | 21 | import java.util.EnumSet; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | public enum StubWebSocketMessageType { 27 | TEXT("text"), 28 | BINARY("binary"); 29 | 30 | private static final Map PROPERTY_NAME_TO_ENUM_MEMBER; 31 | 32 | static { 33 | PROPERTY_NAME_TO_ENUM_MEMBER = new HashMap<>(); 34 | for (final StubWebSocketMessageType enumMember : EnumSet.allOf(StubWebSocketMessageType.class)) { 35 | PROPERTY_NAME_TO_ENUM_MEMBER.put(enumMember.toString(), enumMember); 36 | } 37 | } 38 | 39 | private final String value; 40 | 41 | StubWebSocketMessageType(final String value) { 42 | this.value = value; 43 | } 44 | 45 | public static boolean isUnknownProperty(final String stubbedProperty) { 46 | return !PROPERTY_NAME_TO_ENUM_MEMBER.containsKey(toLower(stubbedProperty)); 47 | } 48 | 49 | public static Optional ofNullableProperty(final String stubbedProperty) { 50 | return Optional.ofNullable(PROPERTY_NAME_TO_ENUM_MEMBER.get(toLower(stubbedProperty))); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return toLower(this.value); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/stubs/websocket/StubWebSocketServerResponsePolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs.websocket; 18 | 19 | import static io.github.azagniotov.stubby4j.utils.StringUtils.toLower; 20 | 21 | import java.util.EnumSet; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | public enum StubWebSocketServerResponsePolicy { 27 | ONCE("once"), 28 | PUSH("push"), 29 | FRAGMENTATION("fragmentation"), 30 | PING("ping"), 31 | DISCONNECT("disconnect"); 32 | 33 | private static final Map PROPERTY_NAME_TO_ENUM_MEMBER; 34 | 35 | static { 36 | PROPERTY_NAME_TO_ENUM_MEMBER = new HashMap<>(); 37 | for (final StubWebSocketServerResponsePolicy enumMember : 38 | EnumSet.allOf(StubWebSocketServerResponsePolicy.class)) { 39 | PROPERTY_NAME_TO_ENUM_MEMBER.put(enumMember.toString(), enumMember); 40 | } 41 | } 42 | 43 | private final String value; 44 | 45 | StubWebSocketServerResponsePolicy(final String value) { 46 | this.value = value; 47 | } 48 | 49 | public static Optional ofNullableProperty(final String stubbedProperty) { 50 | return Optional.ofNullable(PROPERTY_NAME_TO_ENUM_MEMBER.get(toLower(stubbedProperty))); 51 | } 52 | 53 | public static boolean isUnknownProperty(final String stubbedProperty) { 54 | return !PROPERTY_NAME_TO_ENUM_MEMBER.containsKey(toLower(stubbedProperty)); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return toLower(this.value); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/utils/DateTimeUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import java.time.Instant; 20 | import java.time.ZoneOffset; 21 | import java.time.format.DateTimeFormatter; 22 | 23 | public class DateTimeUtils { 24 | 25 | private static final DateTimeFormatter DATE_TIME_FORMATTER = 26 | DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssZ").withZone(ZoneOffset.systemDefault()); 27 | 28 | public static String systemDefault() { 29 | return DATE_TIME_FORMATTER.format(Instant.now()); 30 | } 31 | 32 | public static String systemDefault(final long epochMilli) { 33 | return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(epochMilli)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/utils/JarUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import io.github.azagniotov.stubby4j.annotations.GeneratedCodeClassCoverageExclusion; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.util.jar.Manifest; 23 | 24 | @GeneratedCodeClassCoverageExclusion 25 | public final class JarUtils { 26 | 27 | private JarUtils() {} 28 | 29 | public static String readManifestImplementationVersion() { 30 | try { 31 | final Manifest manifest = getManifest(); 32 | 33 | return manifest.getMainAttributes().getValue("Implementation-Version"); 34 | } catch (Exception e) { 35 | return "x.x.xx"; 36 | } 37 | } 38 | 39 | public static String readManifestBuiltDate() { 40 | try { 41 | final Manifest manifest = getManifest(); 42 | 43 | final String builtDate = manifest.getMainAttributes().getValue("Built-Date"); 44 | if (builtDate != null) { 45 | return builtDate; 46 | } 47 | } catch (Exception e) { 48 | return DateTimeUtils.systemDefault(); 49 | } 50 | 51 | return DateTimeUtils.systemDefault(); 52 | } 53 | 54 | private static Manifest getManifest() throws IOException { 55 | 56 | final InputStream inputStream = 57 | Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/MANIFEST.MF"); 58 | 59 | return new Manifest(inputStream); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/utils/NetworkPortUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import static io.github.azagniotov.stubby4j.utils.SpringSocketUtils.PORT_RANGE_MAX; 20 | import static io.github.azagniotov.stubby4j.utils.SpringSocketUtils.PORT_RANGE_MIN; 21 | 22 | import io.github.azagniotov.stubby4j.annotations.GeneratedCodeClassCoverageExclusion; 23 | 24 | @GeneratedCodeClassCoverageExclusion 25 | public final class NetworkPortUtils { 26 | 27 | private NetworkPortUtils() {} 28 | 29 | public static int findAvailableTcpPort() { 30 | return SpringSocketUtils.findAvailableTcpPort(PORT_RANGE_MIN, PORT_RANGE_MAX); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/utils/ObjectUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | public final class ObjectUtils { 20 | 21 | private ObjectUtils() {} 22 | 23 | public static boolean isNull(final Object instance) { 24 | return instance == null; 25 | } 26 | 27 | public static boolean isNotNull(final Object instance) { 28 | return !isNull(instance); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/yaml/SnakeYaml.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.yaml; 18 | 19 | import org.yaml.snakeyaml.DumperOptions; 20 | import org.yaml.snakeyaml.LoaderOptions; 21 | import org.yaml.snakeyaml.Yaml; 22 | import org.yaml.snakeyaml.constructor.Constructor; 23 | import org.yaml.snakeyaml.representer.Representer; 24 | import org.yaml.snakeyaml.resolver.Resolver; 25 | 26 | public enum SnakeYaml { 27 | INSTANCE; 28 | 29 | private final Yaml snakeYaml; 30 | 31 | SnakeYaml() { 32 | snakeYaml = new Yaml( 33 | new Constructor(new LoaderOptions()), 34 | new Representer(new DumperOptions()), 35 | new DumperOptions(), 36 | new YamlParserResolver()); 37 | } 38 | 39 | public Yaml getSnakeYaml() { 40 | return snakeYaml; 41 | } 42 | 43 | private static final class YamlParserResolver extends Resolver { 44 | YamlParserResolver() { 45 | super(); 46 | } 47 | 48 | @Override 49 | protected void addImplicitResolvers() { 50 | // no implicit resolvers - resolve everything to String 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/azagniotov/stubby4j/yaml/YamlParseResultSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.yaml; 18 | 19 | import io.github.azagniotov.stubby4j.stubs.StubHttpLifecycle; 20 | import io.github.azagniotov.stubby4j.stubs.proxy.StubProxyConfig; 21 | import io.github.azagniotov.stubby4j.stubs.websocket.StubWebSocketConfig; 22 | import java.util.HashMap; 23 | import java.util.LinkedHashMap; 24 | import java.util.LinkedList; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | public class YamlParseResultSet { 29 | 30 | private final List stubs; 31 | private final Map uuidToStubs; 32 | private final Map proxyConfigs; 33 | private final Map webSocketConfigs; 34 | 35 | public YamlParseResultSet(final List stubs, final Map uuidToStubs) { 36 | this.stubs = stubs; 37 | this.uuidToStubs = uuidToStubs; 38 | this.proxyConfigs = new HashMap<>(); 39 | this.webSocketConfigs = new LinkedHashMap<>(); 40 | } 41 | 42 | public YamlParseResultSet( 43 | final List stubs, 44 | final Map uuidToStubs, 45 | final Map proxyConfigs) { 46 | this.stubs = stubs; 47 | this.uuidToStubs = uuidToStubs; 48 | this.proxyConfigs = proxyConfigs; 49 | this.webSocketConfigs = new LinkedHashMap<>(); 50 | } 51 | 52 | public YamlParseResultSet( 53 | final List stubs, 54 | final Map uuidToStubs, 55 | final Map proxyConfigs, 56 | final Map webSocketConfigs) { 57 | this.stubs = stubs; 58 | this.uuidToStubs = uuidToStubs; 59 | this.proxyConfigs = proxyConfigs; 60 | this.webSocketConfigs = webSocketConfigs; 61 | } 62 | 63 | public List getStubs() { 64 | return new LinkedList<>(stubs); 65 | } 66 | 67 | public Map getUuidToStubs() { 68 | return new HashMap<>(uuidToStubs); 69 | } 70 | 71 | public Map getProxyConfigs() { 72 | return new HashMap<>(proxyConfigs); 73 | } 74 | 75 | public Map getWebSocketConfigs() { 76 | return new LinkedHashMap<>(webSocketConfigs); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder: -------------------------------------------------------------------------------- 1 | # https://www.eclipse.org/lists/jetty-users/msg09127.html 2 | org.eclipse.jetty.http.Http1FieldPreEncoder 3 | org.eclipse.jetty.http2.hpack.HpackFieldPreEncoder 4 | -------------------------------------------------------------------------------- /src/main/resources/jetty-logging.properties: -------------------------------------------------------------------------------- 1 | # Configure Jetty for StdErrLog Logging 2 | org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StrErrLog 3 | # Overall Logging Level is INFO 4 | org.eclipse.jetty.LEVEL=DEBUG 5 | # Detail Logging for WebSocket 6 | org.eclipse.jetty.websocket.LEVEL=DEBUG 7 | org.eclipse.jetty.http2.LEVEL=DEBUG 8 | -------------------------------------------------------------------------------- /src/main/resources/ssl/openssl.downloaded.stubby4j.self.signed.v3.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/src/main/resources/ssl/openssl.downloaded.stubby4j.self.signed.v3.jks -------------------------------------------------------------------------------- /src/main/resources/ssl/openssl.downloaded.stubby4j.self.signed.v3.pkcs12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/src/main/resources/ssl/openssl.downloaded.stubby4j.self.signed.v3.pkcs12 -------------------------------------------------------------------------------- /src/main/resources/ssl/stubby4j.self.signed.v3.commands.txt: -------------------------------------------------------------------------------- 1 | ######################################################### 2 | ### Server-side TLS configuration 3 | ######################################################### 4 | 5 | # 1. Generate a self-signed certificate 6 | openssl req -x509 -newkey rsa:4096 -keyout stubby4j.v3.key.pem -out stubby4j.v3.crt.pem -sha256 -days 10950 -config stubby4j.self.signed.v3.conf 7 | 8 | # 2. Convert self-signed certificate into .pkcs12 type key-store. This is the key-store to configure the TLS on the server 9 | openssl pkcs12 -inkey stubby4j.v3.key.pem -in stubby4j.v3.crt.pem -export -out stubby4j.self.signed.v3.pkcs12 10 | 11 | ######################################################### 12 | ### Client-side TLS configuration 13 | ######################################################### 14 | 15 | # 3. Download and save SSL certificate from the server 16 | echo quit | openssl s_client -showcerts -servername localhost -connect "localhost":7443 > openssl.downloaded.stubby4j.self.signed.v3.pem 17 | 18 | # 4. Optionally, you can display the contents of a PEM formatted certificate 19 | openssl x509 -in openssl.downloaded.stubby4j.self.signed.v3.pem -noout -text 20 | 21 | # 5. Optionally, you can perform verification using cURL. Note: the -k (or --insecure) option is NOT used 22 | curl -X GET --cacert openssl.downloaded.stubby4j.self.signed.v3.pem --tls-max 1.1 https://localhost:7443/hello -v 23 | curl -X GET --cacert openssl.downloaded.stubby4j.self.signed.v3.pem --tls-max 1.1 https://127.0.0.1:7443/hello -v 24 | curl -X GET --cacert openssl.downloaded.stubby4j.self.signed.v3.pem --tls-max 1.1 https://0.0.0.0:7443/hello -v 25 | 26 | # 6. Finally, load the saved self-signed certificate to a keystore. The Java web clients should use this file to add to their trust-store 27 | keytool -import -trustcacerts -alias stubby4j -file openssl.downloaded.stubby4j.self.signed.v3.pem -keystore openssl.downloaded.stubby4j.self.signed.v3.jks 28 | 29 | # 7. Optionally, you can display the contents of a JKS store 30 | keytool -list -v -keystore openssl.downloaded.stubby4j.self.signed.v3.jks -storetype JKS 31 | 32 | # 8. Optionally, you can convert JKS store to the PKCS12 type 33 | keytool -importkeystore -srckeystore openssl.downloaded.stubby4j.self.signed.v3.jks -destkeystore openssl.downloaded.stubby4j.self.signed.v3.pkcs12 -srcstoretype JKS -deststoretype PKCS12 -deststorepass stubby4j 34 | 35 | # 9. Optionally, you can display the contents of a PKCS12 store 36 | keytool -list -v -keystore openssl.downloaded.stubby4j.self.signed.v3.pkcs12 -storetype PKCS12 37 | -------------------------------------------------------------------------------- /src/main/resources/ssl/stubby4j.self.signed.v3.pkcs12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/src/main/resources/ssl/stubby4j.self.signed.v3.pkcs12 -------------------------------------------------------------------------------- /src/main/resources/ui/html/_popup_generic.html: -------------------------------------------------------------------------------- 1 |

2 | 22 | -------------------------------------------------------------------------------- /src/main/resources/ui/html/_popup_proxy_config.html: -------------------------------------------------------------------------------- 1 |
2 | 22 | -------------------------------------------------------------------------------- /src/main/resources/ui/html/_popup_stats.html: -------------------------------------------------------------------------------- 1 |
2 | 19 | -------------------------------------------------------------------------------- /src/main/resources/ui/html/_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %s 7 | 8 |
%s
-------------------------------------------------------------------------------- /src/main/resources/ui/html/default404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 404 No data found for request at URI / 5 | 6 | 7 |

HTTP ERROR: 404

8 |

Problem accessing / Reason: 9 |

    No data found for request at URI /
10 |

11 |
12 | Powered by Jetty:// 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/ui/html/status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Status 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | %s 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/ui/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/src/main/resources/ui/images/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/ui/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azagniotov/stubby4j/5a8620f3ab9d37369ca7b3451ea00b552984b821/src/main/resources/ui/images/loading.gif -------------------------------------------------------------------------------- /src/main/resources/ui/js/highlight/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Ivan Sagalaev 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of highlight.js nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /src/main/resources/ui/js/highlight/styles/solarized_light.min.css: -------------------------------------------------------------------------------- 1 | pre code{display:block;padding:.5em;background:#fdf6e3;color:#657b83}pre .comment,pre .template_comment,pre .diff .header,pre .doctype,pre .pi,pre .lisp .string,pre .javadoc{color:#93a1a1;font-style:italic}pre .keyword,pre .winutils,pre .method,pre .addition,pre .css .tag,pre .request,pre .status,pre .nginx .title{color:#859900}pre .number,pre .command,pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula,pre .regexp,pre .hexcolor{color:#2aa198}pre .title,pre .localvars,pre .chunk,pre .decorator,pre .built_in,pre .identifier,pre .vhdl .literal,pre .id{color:#268bd2}pre .attribute,pre .variable,pre .lisp .body,pre .smalltalk .number,pre .constant,pre .class .title,pre .parent,pre .haskell .type{color:#b58900}pre .preprocessor,pre .preprocessor .keyword,pre .shebang,pre .symbol,pre .symbol .string,pre .diff .change,pre .special,pre .attr_selector,pre .important,pre .subst,pre .cdata,pre .clojure .title{color:#cb4b16}pre .deletion{color:#dc322f}pre .tex .formula{background:#eee8d5} -------------------------------------------------------------------------------- /src/main/resources/yaml/empty-stub.yaml: -------------------------------------------------------------------------------- 1 | - request: 2 | method: 3 | - GET 4 | 5 | response: -------------------------------------------------------------------------------- /src/smoke-test/shell/make_request_for_sequenced_responses_using_websocat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script if you try to use an uninitialized variable. 4 | set -o nounset 5 | 6 | # Exit script if a statement returns a non-true return value. 7 | set -o errexit 8 | 9 | # Use the error status of the first failure, rather than that of the last item in a pipeline. 10 | set -o pipefail 11 | 12 | echo "" 13 | echo "#####################################################################" 14 | echo "## WebSocket request is being made to $1://$2:$3" 15 | echo "#####################################################################" 16 | 17 | # https://gist.github.com/htp/fbce19069187ec1cc486b594104f01d0#gistcomment-2638079 18 | smoke_test_connection_response=$(websocat \ 19 | -q -uU \ 20 | --protocol "zumba" \ 21 | $1://$2:$3/ws/hello-world/2 2> /dev/null; echo $?) 22 | 23 | if [ "$smoke_test_connection_response" != "0" ] 24 | then 25 | echo "WebSocket request to $2:$3 failed, exiting with status 1 ... " 26 | exit 1 27 | else 28 | echo "Got success status: $smoke_test_connection_response" 29 | fi 30 | 31 | 32 | for idx in $(seq 0 4); do echo "Hello, World!"; sleep 1; done | websocat --protocol "zumba" $1://$2:$3/ws/hello-world/2 > sequenced_responses.txt 33 | echo "" 34 | echo "#####################################################################" 35 | echo "## WebSocket sequenced responses received from $1://$2:$3/ws/hello-world/2" 36 | echo "#####################################################################" 37 | 38 | if grep -xq "world-0" "sequenced_responses.txt"; then 39 | echo "Received 'world-0' from the WebSocket server" 40 | else 41 | echo "WebSocket response from $2:$3 is incomplete, could not receive 'world-0' ... Exiting with status 1 ... " 42 | exit 1 43 | fi 44 | 45 | if grep -xq "world-1" "sequenced_responses.txt"; then 46 | echo "Received 'world-1' from the WebSocket server" 47 | else 48 | echo "WebSocket response from $2:$3 is incomplete, could not receive 'world-1' ... Exiting with status 1 ... " 49 | exit 1 50 | fi 51 | 52 | if grep -xq "world-2a,world-2b,world-2c,world-2d,world-2e" "sequenced_responses.txt"; then 53 | echo "Received 'world-2a,world-2b,world-2c,world-2d,world-2e' from the WebSocket server" 54 | else 55 | echo "WebSocket response from $2:$3 is incomplete, could not receive 'world-2a,world-2b,world-2c,world-2d,world-2e' ... Exiting with status 1 ... " 56 | exit 1 57 | fi 58 | 59 | if grep -xq "world-3" "sequenced_responses.txt"; then 60 | echo "Received 'world-3' from the WebSocket server" 61 | else 62 | echo "WebSocket response from $2:$3 is incomplete, could not receive 'world-3' ... Exiting with status 1 ... " 63 | exit 1 64 | fi 65 | 66 | -------------------------------------------------------------------------------- /src/smoke-test/shell/make_request_using_curl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script if you try to use an uninitialized variable. 4 | set -o nounset 5 | 6 | # Exit script if a statement returns a non-true return value. 7 | set -o errexit 8 | 9 | # Use the error status of the first failure, rather than that of the last item in a pipeline. 10 | set -o pipefail 11 | 12 | echo "" 13 | echo "#####################################################################" 14 | echo "## Request is being made to $2:$3 over TLS v$1" 15 | echo "#####################################################################" 16 | 17 | smoke_test_response=$(curl \ 18 | -X GET -H "Content-Type: application/json" \ 19 | --tls-max $1 \ 20 | --tlsv$1 \ 21 | --cacert src/main/resources/ssl/openssl.downloaded.stubby4j.self.signed.v3.pem \ 22 | --verbose \ 23 | https://$2:$3/tests/smoke-test/1) 24 | 25 | if [ "$smoke_test_response" != "OK" ] 26 | then 27 | echo "TLS $1 request to $2:$3 failed, exiting with status 1 ... " 28 | exit 1 29 | else 30 | echo "$smoke_test_response" 31 | exit 0 32 | fi 33 | -------------------------------------------------------------------------------- /src/smoke-test/shell/make_request_using_websocat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script if you try to use an uninitialized variable. 4 | set -o nounset 5 | 6 | # Exit script if a statement returns a non-true return value. 7 | set -o errexit 8 | 9 | # Use the error status of the first failure, rather than that of the last item in a pipeline. 10 | set -o pipefail 11 | 12 | echo "" 13 | echo "#####################################################################" 14 | echo "## WebSocket request is being made to $1://$2:$3" 15 | echo "#####################################################################" 16 | 17 | # https://gist.github.com/htp/fbce19069187ec1cc486b594104f01d0#gistcomment-2638079 18 | smoke_test_response=$(websocat \ 19 | -q -uU \ 20 | --protocol "zumba" \ 21 | $1://$2:$3/ws/hello-world/1 2> /dev/null; echo $?) 22 | 23 | if [ "$smoke_test_response" != "0" ] 24 | then 25 | echo "WebSocket request to $2:$3 failed, exiting with status 1 ... " 26 | exit 1 27 | else 28 | echo "$smoke_test_response" 29 | exit 0 30 | fi 31 | -------------------------------------------------------------------------------- /src/smoke-test/yaml/include-all-test-stubs.yaml: -------------------------------------------------------------------------------- 1 | - uuid: 9136d8b7-f7a7-478d-97a5-53292484aaf6 2 | description: stubby4j sanity smoke tests in CircleCI 3 | request: 4 | method: GET 5 | headers: 6 | content-type: application/json 7 | url: /tests/smoke-test/1 8 | 9 | response: 10 | headers: 11 | content-type: application/json 12 | status: 200 13 | body: > 14 | OK 15 | -------------------------------------------------------------------------------- /src/smoke-test/yaml/include-web-socket-config.yaml: -------------------------------------------------------------------------------- 1 | - web-socket: 2 | description: this is a web-socket config 3 | url: /hello-world/1 4 | sub-protocols: echo, mamba, zumba 5 | 6 | on-open: 7 | policy: once 8 | message-type: text 9 | body: Connected via WebSocket 10 | delay: 10 11 | 12 | - web-socket: 13 | description: this is a web-socket config 14 | url: /hello-world/2 15 | sub-protocols: echo, mamba, zumba 16 | 17 | on-open: 18 | policy: once 19 | message-type: text 20 | body: Connected via WebSocket 21 | delay: 10 22 | 23 | on-message: 24 | - client-request: 25 | message-type: text 26 | body: Hello, World! 27 | 28 | server-response: 29 | - policy: once 30 | message-type: text 31 | body: world-0 32 | delay: 20 33 | 34 | - policy: once 35 | message-type: text 36 | body: world-1 37 | delay: 30 38 | 39 | - policy: fragmentation 40 | message-type: binary 41 | body: world-2a,world-2b,world-2c,world-2d,world-2e 42 | delay: 40 43 | 44 | - policy: once 45 | message-type: text 46 | body: world-3 47 | delay: 50 48 | -------------------------------------------------------------------------------- /src/smoke-test/yaml/smoke-tests-stubs.yaml: -------------------------------------------------------------------------------- 1 | includes: 2 | - include-all-test-stubs.yaml 3 | - include-web-socket-config.yaml 4 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/caching/CacheTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.caching; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import io.github.azagniotov.stubby4j.stubs.StubHttpLifecycle; 22 | import java.util.Optional; 23 | import org.junit.Test; 24 | 25 | public class CacheTest { 26 | 27 | @Test 28 | public void shouldBuildNoOpCache() throws Exception { 29 | final Cache cache = Cache.stubHttpLifecycleCache(true); 30 | 31 | assertThat(cache).isInstanceOf(NoOpStubHttpLifecycleCache.class); 32 | } 33 | 34 | @Test 35 | public void shouldBuildDefaultCache() throws Exception { 36 | final Cache cache = Cache.stubHttpLifecycleCache(false); 37 | 38 | assertThat(cache).isInstanceOf(StubHttpLifecycleCache.class); 39 | } 40 | 41 | @Test 42 | public void shouldClearCacheByKey() throws Exception { 43 | 44 | final Cache cache = Cache.stubHttpLifecycleCache(false); 45 | 46 | final StubHttpLifecycle stubHttpLifeCycle = new StubHttpLifecycle.Builder().build(); 47 | final String targetKey = "/some/url"; 48 | 49 | cache.putIfAbsent(targetKey, stubHttpLifeCycle); 50 | assertThat(cache.size().get()).isEqualTo(1); 51 | 52 | assertThat(cache.get(targetKey)).isEqualTo(Optional.of(stubHttpLifeCycle)); 53 | 54 | assertThat(cache.clearByKey(targetKey)).isTrue(); 55 | assertThat(cache.size().get()).isEqualTo(0); 56 | assertThat(cache.get(targetKey)).isEqualTo(Optional.empty()); 57 | } 58 | 59 | @Test 60 | public void shouldNotClearCacheByKey() throws Exception { 61 | 62 | final Cache cache = Cache.stubHttpLifecycleCache(false); 63 | 64 | final StubHttpLifecycle stubHttpLifeCycle = new StubHttpLifecycle.Builder().build(); 65 | final String targetHashCodeKey = "-124354548"; 66 | 67 | cache.putIfAbsent(targetHashCodeKey, stubHttpLifeCycle); 68 | assertThat(cache.size().get()).isEqualTo(1); 69 | 70 | assertThat(cache.get(targetHashCodeKey)).isEqualTo(Optional.of(stubHttpLifeCycle)); 71 | 72 | assertThat(cache.clearByKey("99999")).isFalse(); 73 | assertThat(cache.size().get()).isEqualTo(1); 74 | assertThat(cache.get(targetHashCodeKey)).isEqualTo(Optional.of(stubHttpLifeCycle)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/caching/NoOpStubHttpLifecycleCacheTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.caching; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static org.junit.Assert.assertThrows; 21 | 22 | import io.github.azagniotov.stubby4j.stubs.StubHttpLifecycle; 23 | import java.util.Optional; 24 | import org.junit.Test; 25 | 26 | public class NoOpStubHttpLifecycleCacheTest { 27 | 28 | @Test 29 | public void putIfAbsentAndGet() { 30 | final Cache cache = Cache.stubHttpLifecycleCache(true); 31 | assertThat(cache.size().get()).isEqualTo(0); 32 | 33 | final StubHttpLifecycle stubHttpLifeCycle = new StubHttpLifecycle.Builder().build(); 34 | final String targetKey = "/some/url"; 35 | 36 | cache.putIfAbsent(targetKey, stubHttpLifeCycle); 37 | 38 | assertThat(cache.size().get()).isEqualTo(0); 39 | assertThat(cache.get(targetKey)).isEqualTo(Optional.empty()); 40 | } 41 | 42 | @Test 43 | public void clearByKey() { 44 | 45 | final Cache cache = Cache.stubHttpLifecycleCache(true); 46 | 47 | final StubHttpLifecycle stubHttpLifeCycle = new StubHttpLifecycle.Builder().build(); 48 | final String targetKey = "/some/url"; 49 | 50 | assertThat(cache.clearByKey(targetKey)).isTrue(); 51 | cache.putIfAbsent(targetKey, stubHttpLifeCycle); 52 | 53 | assertThat(cache.get(targetKey)).isEqualTo(Optional.empty()); 54 | assertThat(cache.clearByKey(targetKey)).isTrue(); 55 | } 56 | 57 | @Test 58 | public void cache() { 59 | final Cache cache = Cache.stubHttpLifecycleCache(true); 60 | assertThrows(UnsupportedOperationException.class, cache::cache); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/handlers/strategy/StubsResponseHandlingStrategyFactoryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.handlers.strategy; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | import static org.mockito.Mockito.when; 21 | 22 | import io.github.azagniotov.stubby4j.handlers.strategy.stubs.DefaultResponseHandlingStrategy; 23 | import io.github.azagniotov.stubby4j.handlers.strategy.stubs.NotFoundResponseHandlingStrategy; 24 | import io.github.azagniotov.stubby4j.handlers.strategy.stubs.StubResponseHandlingStrategy; 25 | import io.github.azagniotov.stubby4j.handlers.strategy.stubs.StubsResponseHandlingStrategyFactory; 26 | import io.github.azagniotov.stubby4j.stubs.StubResponse; 27 | import org.eclipse.jetty.http.HttpStatus; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.mockito.Mock; 31 | import org.mockito.junit.MockitoJUnitRunner; 32 | 33 | @RunWith(MockitoJUnitRunner.class) 34 | public class StubsResponseHandlingStrategyFactoryTest { 35 | 36 | private static final byte[] EMPTY_BYTES = {}; 37 | 38 | @Mock 39 | private StubResponse mockStubResponse; 40 | 41 | @Test 42 | public void shouldReturnNotFoundResponseHandlingStrategyWhen404ResponseHasNoBody() throws Exception { 43 | when(mockStubResponse.getHttpStatusCode()).thenReturn(HttpStatus.Code.NOT_FOUND); 44 | when(mockStubResponse.getResponseBodyAsBytes()).thenReturn(EMPTY_BYTES); 45 | 46 | StubResponseHandlingStrategy handlingStrategy = 47 | StubsResponseHandlingStrategyFactory.getStrategy(mockStubResponse); 48 | 49 | assertThat(handlingStrategy).isInstanceOf(NotFoundResponseHandlingStrategy.class); 50 | } 51 | 52 | @Test 53 | public void shouldReturnDefaultResponseHandlingStrategyWhen404ResponseHasNoBody() throws Exception { 54 | when(mockStubResponse.getHttpStatusCode()).thenReturn(HttpStatus.Code.NOT_FOUND); 55 | when(mockStubResponse.getResponseBodyAsBytes()).thenReturn("something".getBytes()); 56 | 57 | StubResponseHandlingStrategy handlingStrategy = 58 | StubsResponseHandlingStrategyFactory.getStrategy(mockStubResponse); 59 | 60 | assertThat(handlingStrategy).isInstanceOf(DefaultResponseHandlingStrategy.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/server/ssl/LanIPv4ValidatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.server.ssl; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | 23 | public class LanIPv4ValidatorTest { 24 | 25 | @Test 26 | public void isPrivateIp() throws Exception { 27 | assertThat(LanIPv4Validator.isPrivateIp("10.0.0.1")).isTrue(); 28 | assertThat(LanIPv4Validator.isPrivateIp("10.255.255.255")).isTrue(); 29 | assertThat(LanIPv4Validator.isPrivateIp("192.168.0.1")).isTrue(); 30 | assertThat(LanIPv4Validator.isPrivateIp("192.168.255.255")).isTrue(); 31 | assertThat(LanIPv4Validator.isPrivateIp("172.16.0.0")).isTrue(); 32 | assertThat(LanIPv4Validator.isPrivateIp("172.16.0.1")).isTrue(); 33 | assertThat(LanIPv4Validator.isPrivateIp("172.31.255.255")).isTrue(); 34 | } 35 | 36 | @Test 37 | public void isNotPrivateIp() throws Exception { 38 | assertThat(LanIPv4Validator.isPrivateIp("9.0.0.1")).isFalse(); 39 | assertThat(LanIPv4Validator.isPrivateIp("10.255.255.256")).isFalse(); 40 | assertThat(LanIPv4Validator.isPrivateIp("192.169.0.1")).isFalse(); 41 | assertThat(LanIPv4Validator.isPrivateIp("192.167.255.255")).isFalse(); 42 | assertThat(LanIPv4Validator.isPrivateIp("172.15.0.0")).isFalse(); 43 | assertThat(LanIPv4Validator.isPrivateIp("172.15.0.1")).isFalse(); 44 | assertThat(LanIPv4Validator.isPrivateIp("172.32.255.255")).isFalse(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/stubs/websocket/StubWebSocketMessageTypeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs.websocket; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | 23 | public class StubWebSocketMessageTypeTest { 24 | @Test 25 | public void returnsTrueOnKnownProperties() throws Exception { 26 | // Double negative logic 27 | assertThat(StubWebSocketMessageType.isUnknownProperty("text")).isFalse(); 28 | assertThat(StubWebSocketMessageType.isUnknownProperty("BiNary")).isFalse(); 29 | } 30 | 31 | @Test 32 | public void returnsFalseOnUnknownProperties() throws Exception { 33 | // Double negative logic 34 | assertThat(StubWebSocketMessageType.isUnknownProperty("apple")).isTrue(); 35 | assertThat(StubWebSocketMessageType.isUnknownProperty("")).isTrue(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/stubs/websocket/StubWebSocketServerResponsePolicyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.stubs.websocket; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import org.junit.Test; 22 | 23 | public class StubWebSocketServerResponsePolicyTest { 24 | 25 | @Test 26 | public void returnsTrueOnKnownProperties() throws Exception { 27 | // Double negative logic 28 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("once")).isFalse(); 29 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("push")).isFalse(); 30 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("fragmentation")) 31 | .isFalse(); 32 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("ping")).isFalse(); 33 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("disconnect")) 34 | .isFalse(); 35 | } 36 | 37 | @Test 38 | public void returnsFalseOnUnknownProperties() throws Exception { 39 | // Double negative logic 40 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("apple")).isTrue(); 41 | assertThat(StubWebSocketServerResponsePolicy.isUnknownProperty("")).isTrue(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/utils/DateTimeUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import java.time.Instant; 22 | import java.time.ZoneOffset; 23 | import java.time.format.DateTimeFormatter; 24 | import org.junit.Test; 25 | 26 | public class DateTimeUtilsTest { 27 | 28 | private static final DateTimeFormatter DATE_TIME_FORMATTER = 29 | DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssZ").withZone(ZoneOffset.systemDefault()); 30 | 31 | @Test 32 | public void systemDefault() throws Exception { 33 | final String localNow = DATE_TIME_FORMATTER.format(Instant.now()); 34 | final String systemDefault = DateTimeUtils.systemDefault(); 35 | 36 | assertThat(localNow).isEqualTo(systemDefault); 37 | } 38 | 39 | @Test 40 | public void systemDefaultFromMillis() throws Exception { 41 | final long currentTimeMillis = System.currentTimeMillis(); 42 | final String localNow = DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(currentTimeMillis)); 43 | 44 | final String systemDefault = DateTimeUtils.systemDefault(currentTimeMillis); 45 | 46 | assertThat(localNow).isEqualTo(systemDefault); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/utils/FileUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.utils; 18 | 19 | import java.io.IOException; 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import org.junit.rules.ExpectedException; 23 | 24 | public class FileUtilsTest { 25 | 26 | @Rule 27 | public ExpectedException expectedException = ExpectedException.none(); 28 | 29 | @Test 30 | public void shouldNotConvertFileToBytesWhenBadFilenameGiven() throws Exception { 31 | 32 | expectedException.expect(IOException.class); 33 | expectedException.expectMessage("Could not load file from path: bad/file/path"); 34 | 35 | FileUtils.binaryFileToBytes(".", "bad/file/path"); 36 | } 37 | 38 | @Test 39 | public void shouldNotLoadFileFromURWhenBadFilenameGiven() throws Exception { 40 | 41 | expectedException.expect(IOException.class); 42 | expectedException.expectMessage("Could not load file from path: bad/file/path"); 43 | 44 | FileUtils.uriToFile("bad/file/path"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/io/github/azagniotov/stubby4j/yaml/YamlParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2024 Alexander Zagniotov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.azagniotov.stubby4j.yaml; 18 | 19 | import static com.google.common.truth.Truth.assertThat; 20 | 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import org.junit.Test; 26 | 27 | public class YamlParserTest { 28 | 29 | @Test 30 | public void isMainYamlHasIncludesFoundAsTrue() { 31 | final YamlParser yamlParser = new YamlParser(); 32 | 33 | final Map> loadedYamlConfig = new HashMap<>(); 34 | loadedYamlConfig.put("includes", new ArrayList<>()); 35 | 36 | assertThat(yamlParser.isMainYamlHasIncludes(loadedYamlConfig)).isTrue(); 37 | } 38 | 39 | @Test 40 | public void isMainYamlHasIncludesFoundAsFalse() { 41 | final YamlParser yamlParser = new YamlParser(); 42 | 43 | final Map> loadedYamlConfig = new HashMap<>(); 44 | loadedYamlConfig.put("unexpectedKey", new ArrayList<>()); 45 | 46 | assertThat(yamlParser.isMainYamlHasIncludes(loadedYamlConfig)).isFalse(); 47 | } 48 | 49 | @Test 50 | public void isMainYamlHasIncludesFoundAsFalseWhenNotMap() { 51 | final YamlParser yamlParser = new YamlParser(); 52 | 53 | assertThat(yamlParser.isMainYamlHasIncludes(new ArrayList<>())).isFalse(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/resources/stubbed.request.response.yaml: -------------------------------------------------------------------------------- 1 | request: 2 | method: PUT 3 | url: /invoice/123 4 | headers: 5 | content-type: application/json 6 | post: > 7 | {"name": "milk", "description": "full", "department": "savoury"} 8 | 9 | response: 10 | headers: 11 | content-type: application/json 12 | pragma: no-cache 13 | status: 200 14 | body: > 15 | {"id": "123", "status": "updated"} --------------------------------------------------------------------------------