From a20869c8f15eaaf95780dd690ecb5406502ab7e7 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 13:50:17 +0200 Subject: [PATCH 1/9] initial merge from wiremock 3.0.4 upstream --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE.md | 20 - .github/release-drafter.yml | 2 + .github/workflows/build-and-test.yml | 2 +- .github/workflows/changelog-draft.yml | 20 + .run/Build WireMock.run.xml | 25 + .run/Publish to Maven local.run.xml | 24 + .run/Run Spotless.run.xml | 24 + .run/Run Tests.run.xml | 24 + .sdkmanrc | 2 +- CONTRIBUTING.md | 133 ++- NOTICE.txt | 2 + build.gradle | 179 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- .../tomakehurst/wiremock/WireMockServer.java | 43 +- .../wiremock/admin/AdminRoutes.java | 204 ++--- .../tomakehurst/wiremock/admin/AdminTask.java | 8 +- .../admin/FindStubMappingsByMetadataTask.java | 11 +- .../wiremock/admin/GetAllScenariosTask.java | 8 +- .../wiremock/admin/GetGlobalSettingsTask.java | 8 +- .../admin/GetRecordingStatusTask.java | 8 +- .../admin/ImportStubMappingsTask.java | 10 +- .../admin/LimitAndOffsetPaginator.java | 16 +- .../admin/LimitAndSinceDatePaginator.java | 26 +- .../admin/PatchExtendedSettingsTask.java | 10 +- .../wiremock/admin/RemoveServeEventTask.java | 12 +- ...RemoveServeEventsByRequestPatternTask.java | 11 +- .../RemoveServeEventsByStubMetadataTask.java | 10 +- .../RemoveStubMappingsByMetadataTask.java | 11 +- .../wiremock/admin/RequestSpec.java | 21 +- .../wiremock/admin/SetScenarioStateTask.java | 10 +- .../wiremock/admin/StartRecordingTask.java | 10 +- .../wiremock/admin/StopRecordingTask.java | 8 +- .../admin/model/HealthCheckResult.java | 49 ++ .../admin/model/SingleServedStubResult.java | 6 +- .../admin/model/SingleStubMappingResult.java | 4 +- .../admin/tasks/AbstractGetDocTask.java | 16 +- ...java => AbstractSingleServeEventTask.java} | 30 +- .../admin/tasks/AbstractSingleStubTask.java | 48 ++ .../admin/tasks/CreateStubMappingTask.java | 10 +- .../admin/tasks/DeleteStubFileTask.java | 32 +- .../admin/tasks/EditStubFileTask.java | 26 +- .../admin/tasks/EditStubMappingTask.java | 24 +- .../FindNearMissesForRequestPatternTask.java | 11 +- .../tasks/FindNearMissesForRequestTask.java | 11 +- .../tasks/FindNearMissesForUnmatchedTask.java | 8 +- .../admin/tasks/FindRequestsTask.java | 11 +- .../tasks/FindUnmatchedRequestsTask.java | 8 +- .../admin/tasks/GetAllRequestsTask.java | 14 +- .../admin/tasks/GetAllStubFilesTask.java | 31 +- .../admin/tasks/GetAllStubMappingsTask.java | 10 +- .../wiremock/admin/tasks/GetCaCertTask.java | 8 +- .../admin/tasks/GetRequestCountTask.java | 11 +- .../admin/tasks/GetServedStubTask.java | 15 +- .../admin/tasks/GetStubMappingTask.java | 22 +- .../admin/tasks/GlobalSettingsUpdateTask.java | 13 +- .../wiremock/admin/tasks/HealthCheckTask.java | 48 ++ .../admin/tasks/NotFoundAdminTask.java | 8 +- .../admin/tasks/OldCreateStubMappingTask.java | 33 - .../admin/tasks/OldEditStubMappingTask.java | 10 +- ...ava => RemoveMatchingStubMappingTask.java} | 12 +- ...sk.java => RemoveStubMappingByIdTask.java} | 16 +- .../admin/tasks/ResetRequestsTask.java | 8 +- .../admin/tasks/ResetScenariosTask.java | 8 +- .../admin/tasks/ResetStubMappingsTask.java | 8 +- .../wiremock/admin/tasks/ResetTask.java | 8 +- .../tasks/ResetToDefaultMappingsTask.java | 8 +- .../admin/tasks/RootRedirectTask.java | 8 +- .../admin/tasks/SaveMappingsTask.java | 8 +- .../admin/tasks/ShutdownServerTask.java | 8 +- .../wiremock/admin/tasks/SnapshotTask.java | 12 +- .../wiremock/client/BasicMappingBuilder.java | 89 +- .../wiremock/client/HttpAdminClient.java | 47 +- .../wiremock/client/MappingBuilder.java | 25 +- .../client/ResponseDefinitionBuilder.java | 40 +- .../client/ScenarioMappingBuilder.java | 18 +- .../client/VerificationException.java | 9 +- .../tomakehurst/wiremock/client/WireMock.java | 133 ++- .../wiremock/common/AbstractFileSource.java | 44 +- .../wiremock/common/BinaryFile.java | 7 +- .../wiremock/common/ClasspathFileSource.java | 82 +- .../wiremock/common/ContentTypes.java | 54 +- .../tomakehurst/wiremock/common/Encoding.java | 17 +- .../tomakehurst/wiremock/common/Gzip.java | 7 +- .../wiremock/common/HttpClientUtils.java | 36 +- ...se64Encoder.java => JdkBase64Encoder.java} | 15 +- .../wiremock/common/JettySettings.java | 70 +- .../tomakehurst/wiremock/common/Json.java | 21 +- .../wiremock/common/JsonException.java | 25 +- .../common/ListOrStringDeserialiser.java | 7 +- .../Message.java} | 21 +- .../tomakehurst/wiremock/common/Metadata.java | 22 +- .../wiremock/common/NetworkAddressRules.java | 9 +- .../wiremock/common/ParameterUtils.java | 87 ++ .../wiremock/common/ProxySettings.java | 7 +- .../wiremock/common/SafeNames.java | 76 -- .../common/ServletContextFileSource.java | 4 +- .../wiremock/common/StreamSources.java | 59 +- .../tomakehurst/wiremock/common/Strings.java | 27 +- .../tomakehurst/wiremock/common/TextFile.java | 4 +- .../tomakehurst/wiremock/common/Timing.java | 58 +- .../tomakehurst/wiremock/common/Urls.java | 55 +- .../common/filemaker/FilenameMaker.java | 79 ++ .../filemaker/FilenameTemplateModel.java | 79 ++ .../wiremock/common/ssl/KeyStoreSource.java | 4 +- .../common/ssl/KeyStoreSourceFactory.java | 4 +- .../model => common/url}/PathParams.java | 4 +- .../url/PathTemplate.java} | 57 +- .../model => common/url}/QueryParams.java | 4 +- .../wiremock/common/xml/XmlNode.java | 16 +- .../tomakehurst/wiremock/core/Admin.java | 2 +- .../tomakehurst/wiremock/core/Options.java | 33 +- .../tomakehurst/wiremock/core/StubServer.java | 5 +- .../wiremock/core/WireMockApp.java | 273 ++++--- .../wiremock/core/WireMockConfiguration.java | 191 ++++- .../wiremock/direct/DirectCallHttpServer.java | 5 +- .../extension/ExtensionDeclarations.java | 67 ++ .../wiremock/extension/ExtensionFactory.java | 22 + .../wiremock/extension/ExtensionLoader.java | 68 +- .../wiremock/extension/Extensions.java | 199 +++++ .../extension/GlobalSettingsListener.java | 7 +- .../wiremock/extension/Parameters.java | 5 +- .../wiremock/extension/PostServeAction.java | 4 +- .../ResponseDefinitionTransformer.java | 4 +- .../ResponseDefinitionTransformerV2.java | 28 + .../extension/ResponseTransformer.java | 4 +- .../extension/ResponseTransformerV2.java | 28 + .../extension/ServeEventListener.java | 57 ++ .../ServeEventListenerDefinition.java | 55 ++ .../extension/StubLifecycleListener.java | 18 +- .../TemplateHelperProviderExtension.java | 23 + .../TemplateModelDataProviderExtension.java | 23 + .../wiremock/extension/WireMockServices.java | 37 + .../requestfilter/AdminRequestFilterV2.java | 29 + .../requestfilter/FilterProcessor.java | 53 +- .../requestfilter/RequestFilter.java | 4 +- .../requestfilter/RequestFilterV2.java | 29 + .../requestfilter/RequestWrapper.java | 106 ++- .../requestfilter/StubRequestFilterV2.java | 29 + .../HandlebarsOptimizedTemplate.java | 18 +- .../responsetemplating/RequestLine.java | 44 +- .../RequestTemplateModel.java | 36 +- .../ResponseTemplateTransformer.java | 330 ++++---- .../SystemKeyAuthoriser.java | 13 +- .../responsetemplating/TemplateEngine.java | 19 +- .../responsetemplating/TemplatedUrlPath.java | 55 ++ .../extension/responsetemplating/UrlPath.java | 17 +- .../helpers/ArrayHelper.java | 13 +- .../helpers/FormDataHelper.java | 6 +- .../helpers/HandlebarsJsonPathHelper.java | 6 +- .../helpers/ParseJsonHelper.java | 14 +- .../helpers/PickRandomHelper.java | 18 +- .../wiremock/global/GlobalSettings.java | 21 +- .../wiremock/http/AbstractRequestHandler.java | 61 +- .../wiremock/http/AdminRequestHandler.java | 36 +- .../tomakehurst/wiremock/http/Body.java | 4 +- .../wiremock/http/CaseInsensitiveKey.java | 10 +- .../wiremock/http/ContentTypeHeader.java | 6 +- .../tomakehurst/wiremock/http/Cookie.java | 9 +- .../wiremock/http/FormParameter.java | 32 + .../tomakehurst/wiremock/http/HttpHeader.java | 10 +- .../wiremock/http/HttpHeaders.java | 41 +- .../http/HttpHeadersJsonDeserializer.java | 55 +- .../wiremock/http/HttpResponder.java | 6 +- .../wiremock/http/LoggedResponse.java | 6 +- .../tomakehurst/wiremock/http/MultiValue.java | 42 +- .../wiremock/http/ProxyResponseRenderer.java | 66 +- .../tomakehurst/wiremock/http/Request.java | 12 +- .../wiremock/http/RequestHandler.java | 6 +- .../tomakehurst/wiremock/http/Response.java | 23 +- .../wiremock/http/ResponseDefinition.java | 11 +- .../wiremock/http/StubRequestHandler.java | 129 ++- .../wiremock/http/StubResponseRenderer.java | 87 +- .../http/multipart/FileItemPartAdapter.java | 27 +- .../wiremock/http/multipart/FileUpload.java | 768 ++++++++++++++++++ .../wiremock/http/multipart/PartParser.java | 11 +- .../http/ssl/CertificateAuthority.java | 4 +- .../http/ssl/CompositeTrustManager.java | 44 +- .../CollectingNetworkTrafficListener.java | 6 +- ...tifyingWiremockNetworkTrafficListener.java | 34 +- ...tifyingWiremockNetworkTrafficListener.java | 64 ++ .../WiremockNetworkTrafficListeners.java | 40 + .../DefaultMultipartRequestConfigurer.java | 14 +- .../{jetty9 => jetty}/JettyFaultInjector.java | 17 +- .../JettyFaultInjectorFactory.java | 8 +- .../{jetty9 => jetty}/JettyHttpServer.java | 203 ++--- .../jetty/JettyHttpServerFactory.java | 33 + .../JettyHttpsFaultInjector.java | 11 +- .../{jetty9 => jetty}/JettyUtils.java | 51 +- .../{jetty9 => jetty}/NotFoundHandler.java | 12 +- .../QueuedThreadPoolFactory.java | 4 +- .../{jetty9 => jetty}/websockets/Message.java | 0 .../websockets/WebSocketEndpoint.java | 0 ...ertificateGeneratingSslContextFactory.java | 4 +- .../jetty11/HttpsProxyDetectingHandler.java | 45 + .../Jetty11HttpServer.java} | 55 +- .../wiremock/jetty11/Jetty11Utils.java | 68 ++ .../ManInTheMiddleSslConnectHandler.java | 12 +- .../{jetty94 => jetty11}/SslContexts.java | 4 +- ...WritableFileOrClasspathKeyStoreSource.java | 4 +- .../jetty9/JettyHttpServerFactory.java | 80 -- .../wiremock/junit5/WireMockExtension.java | 64 +- .../matching/AbstractLogicalMatcher.java | 56 ++ .../matching/BinaryEqualToPattern.java | 24 +- .../wiremock/matching/ContentPattern.java | 24 +- .../matching/ContentPatternDeserialiser.java | 20 +- .../wiremock/matching/EagerMatchResult.java | 10 +- .../wiremock/matching/EqualToJsonPattern.java | 10 +- .../wiremock/matching/EqualToPattern.java | 14 +- .../wiremock/matching/EqualToXmlPattern.java | 56 +- .../matching/ExactMatchMultiValuePattern.java | 58 ++ .../IncludesMatchMultiValuePattern.java | 47 ++ .../wiremock/matching/LogicalAnd.java | 26 +- .../wiremock/matching/LogicalOr.java | 26 +- .../wiremock/matching/MatchResult.java | 81 +- .../matching/MatchesJsonPathPattern.java | 48 +- .../matching/MatchesJsonSchemaPattern.java | 104 +++ .../matching/MatchesXPathPattern.java | 74 +- .../matching/MemoizingMatchResult.java | 2 +- .../wiremock/matching/MultiValuePattern.java | 77 +- .../MultiValuePatternDeserializer.java | 40 + .../matching/MultipartValuePattern.java | 51 +- .../MultipartValuePatternBuilder.java | 6 +- .../MultipleMatchMultiValuePattern.java | 67 ++ .../wiremock/matching/NotPattern.java | 55 ++ .../matching/PathTemplatePattern.java | 60 ++ .../wiremock/matching/RequestPattern.java | 239 +++--- .../matching/RequestPatternBuilder.java | 73 +- .../SingleMatchMultiValuePattern.java | 74 ++ .../wiremock/matching/StringValuePattern.java | 37 +- .../StringValuePatternJsonDeserializer.java | 183 +++-- .../matching/UrlPathTemplatePattern.java | 34 + .../wiremock/matching/UrlPattern.java | 21 +- .../matching/WeightedMatchResult.java | 6 +- .../LoggedResponseDefinitionTransformer.java | 23 +- .../recording/ProxiedServeEventFilters.java | 12 +- .../wiremock/recording/RecordSpecBuilder.java | 6 +- .../wiremock/recording/Recorder.java | 133 +-- .../wiremock/recording/RecorderState.java | 76 ++ .../recording/RequestPatternTransformer.java | 4 +- .../wiremock/recording/ScenarioProcessor.java | 46 +- .../recording/SnapshotRecordResult.java | 15 +- .../SnapshotStubMappingBodyExtractor.java | 15 +- .../SnapshotStubMappingGenerator.java | 9 +- .../SnapshotStubMappingPostProcessor.java | 59 +- .../SnapshotStubMappingTransformerRunner.java | 10 +- .../wiremock/security/BasicAuthenticator.java | 19 +- .../security/ClientBasicAuthenticator.java | 4 +- .../security/ClientTokenAuthenticator.java | 6 +- .../security/SingleHeaderAuthenticator.java | 4 +- .../wiremock/security/TokenAuthenticator.java | 6 +- .../servlet/ContentTypeSettingFilter.java | 8 +- .../servlet/FaultInjectorFactory.java | 6 +- .../servlet/MultipartRequestConfigurer.java | 4 +- .../wiremock/servlet/NoFaultInjector.java | 2 +- .../servlet/NoFaultInjectorFactory.java | 4 +- .../wiremock/servlet/NotMatchedServlet.java | 54 ++ .../wiremock/servlet/TrailingSlashFilter.java | 23 +- .../wiremock/servlet/WarConfiguration.java | 67 +- .../WireMockHandlerDispatchingServlet.java | 74 +- .../WireMockHttpServletMultipartAdapter.java | 32 +- .../WireMockHttpServletRequestAdapter.java | 134 +-- .../servlet/WireMockWebContextListener.java | 12 +- .../standalone/CommandLineOptions.java | 217 +++-- .../standalone/JsonFileMappingsSource.java | 37 +- .../standalone/RemoteMappingsLoader.java | 27 +- .../standalone/WireMockServerRunner.java | 63 +- .../tomakehurst/wiremock/store/BlobStore.java | 29 + .../wiremock/store/DefaultStores.java | 77 ++ .../store/InMemoryRecorderStateStore.java | 40 + .../store/InMemoryRequestJournalStore.java | 78 ++ .../store/InMemoryScenariosStore.java | 58 ++ .../wiremock/store/InMemorySettingsStore.java | 36 + .../store/InMemoryStubMappingStore.java | 59 ++ .../wiremock/store/RecorderStateStore.java | 26 + .../wiremock/store/RequestJournalStore.java | 31 + .../wiremock/store/ScenariosStore.java | 26 + .../wiremock/store/SettingsStore.java | 27 + .../tomakehurst/wiremock/store/Store.java | 34 + .../tomakehurst/wiremock/store/Stores.java | 45 + .../wiremock/store/StoresLifecycle.java | 26 + .../wiremock/store/StubMappingStore.java | 55 ++ .../store/files/BlobStoreBinaryFile.java | 55 ++ .../store/files/BlobStoreFileSource.java | 92 +++ .../store/files/BlobStoreTextFile.java | 67 ++ .../store/files/FileSourceBlobStore.java | 72 ++ .../wiremock/stubbing/AbstractScenarios.java | 149 ++++ .../stubbing/AbstractStubMappings.java | 249 ++++++ .../wiremock/stubbing/InMemoryScenarios.java | 30 + .../stubbing/InMemoryStubMappings.java | 237 +----- .../wiremock/stubbing/Scenario.java | 60 +- .../wiremock/stubbing/Scenarios.java | 122 +-- .../wiremock/stubbing/ServeEvent.java | 172 ++-- .../stubbing/SortedConcurrentMappingSet.java | 54 +- .../stubbing/StoreBackedStubMappings.java | 46 ++ .../wiremock/stubbing/StubMapping.java | 36 +- .../stubbing/StubMappingJsonRecorder.java | 38 +- .../wiremock/stubbing/StubMappings.java | 7 +- .../wiremock/stubbing/SubEvent.java | 85 ++ .../verification/AbstractRequestJournal.java | 144 ++++ .../verification/DisabledRequestJournal.java | 7 +- .../verification/InMemoryRequestJournal.java | 141 +--- .../wiremock/verification/LoggedRequest.java | 94 ++- .../verification/NearMissCalculator.java | 57 +- .../wiremock/verification/RequestJournal.java | 6 +- .../StoreBackedRequestJournal.java | 30 + .../wiremock/verification/diff/Diff.java | 230 ++++-- .../verification/diff/DiffEventData.java | 48 ++ .../diff/EmptyToStringRequestWrapper.java | 14 +- .../diff/JUnitStyleDiffRenderer.java | 39 +- .../diff/PlainTextDiffRenderer.java | 13 +- .../notmatched/NotMatchedRenderer.java | 12 +- .../PlainTextStubNotMatchedRenderer.java | 20 +- .../java/org/wiremock/annotations/Beta.java | 34 + .../org/wiremock/annotations/InternalAPI.java | 38 + src/main/java/wiremock/Run.java | 25 + .../swagger/schemas/request-pattern.yaml | 38 + .../swagger/schemas/stub-mapping.yaml | 18 + .../resources/swagger/wiremock-admin-api.json | 16 +- .../resources/swagger/wiremock-admin-api.yaml | 16 +- .../HelpFormatterMessages.properties | 16 + .../tomakehurst/wiremock/AdminApiTest.java | 79 +- .../wiremock/BrowserProxyAcceptanceTest.java | 59 +- .../wiremock/ConcurrentProxyingTest.java | 21 +- .../CookieMatchingAcceptanceTest.java | 4 +- .../EditStubMappingAcceptanceTest.java | 19 +- .../wiremock/ExtensionFactoryTest.java | 162 ++++ .../GlobalSettingsAcceptanceTest.java | 17 +- .../wiremock/GzipAcceptanceTest.java | 3 +- .../wiremock/Http2ClientFactory.java | 11 +- .../wiremock/HttpsAcceptanceTest.java | 26 +- .../HttpsBrowserProxyAcceptanceTest.java | 19 +- .../JsonSchemaMatchingAcceptanceTest.java | 74 ++ .../JvmProxyConfigAcceptanceTest.java | 15 +- .../MappingsLoaderAcceptanceTest.java | 9 +- .../MultipartBodyMatchingAcceptanceTest.java | 20 +- .../NotMatchedPageAcceptanceTest.java | 41 +- .../PostServeActionExtensionTest.java | 33 +- .../wiremock/ProxyAcceptanceTest.java | 94 ++- .../wiremock/RecordApiAcceptanceTest.java | 46 +- .../wiremock/RecordingDslAcceptanceTest.java | 68 +- .../RemoteMappingsLoaderAcceptanceTest.java | 21 +- .../RemoveStubMappingAcceptanceTest.java | 14 +- .../wiremock/RequestFilterAcceptanceTest.java | 31 +- .../RequestFilterV2AcceptanceTest.java | 333 ++++++++ ...DefinitionTransformerV2AcceptanceTest.java | 321 ++++++++ .../wiremock/ResponseDelayAcceptanceTest.java | 30 +- ...sponseDelayAsynchronousAcceptanceTest.java | 17 +- .../ResponseTemplatingAcceptanceTest.java | 158 +++- .../ResponseTransformerAcceptanceTest.java | 15 +- .../ResponseTransformerV2AcceptanceTest.java | 192 +++++ .../SavingMappingsAcceptanceTest.java | 22 +- .../wiremock/ScenarioAcceptanceTest.java | 10 +- .../ServeEventListenerExtensionTest.java | 494 +++++++++++ .../wiremock/ServeEventLogAcceptanceTest.java | 6 +- .../wiremock/SnapshotDslAcceptanceTest.java | 47 +- .../wiremock/StandaloneAcceptanceTest.java | 74 +- .../StubMappingPersistenceAcceptanceTest.java | 4 +- .../wiremock/StubbingAcceptanceTest.java | 207 ++++- .../SubServeEventsAcceptanceTest.java | 62 ++ .../wiremock/TemplateHelperExtensionTest.java | 75 ++ ...emplateModelDataProviderExtensionTest.java | 74 ++ .../wiremock/UrlPathTemplateMatchingTest.java | 100 +++ .../wiremock/VerificationAcceptanceTest.java | 105 ++- .../wiremock/WireMockJUnitRuleTest.java | 12 +- .../wiremock/WireMockServerTests.java | 10 +- .../wiremock/XmlHandlingAcceptanceTest.java | 4 +- .../admin/LimitAndOffsetPaginatorTest.java | 51 +- .../admin/OldEditStubMappingTaskTest.java | 63 -- .../wiremock/admin/SaveMappingsTaskTest.java | 12 +- .../wiremock/admin/model/QueryParamsTest.java | 3 +- .../admin/tasks/HealthCheckTaskTest.java | 50 ++ .../wiremock/archunit/UnusedCodeTest.java | 7 +- .../ClientAuthenticationAcceptanceTest.java | 71 +- .../client/ResponseDefinitionBuilderTest.java | 35 +- .../wiremock/common/Base64EncoderTest.java | 6 +- .../common/ClasspathFileSourceTest.java | 21 +- .../wiremock/common/FilenameMakerTest.java | 144 ++++ .../wiremock/common/JettySettingsTest.java | 4 +- .../common/NetworkAddressRangeTest.java | 2 +- .../common/NetworkAddressRulesTest.java | 2 +- .../wiremock/common/SafeNamesTest.java | 82 -- .../common/ServletContextFileSourceTest.java | 51 +- .../common/ssl/KeyStoreSettingsTest.java | 11 +- .../url/PathTemplateTest.java} | 85 +- .../core/WireMockConfigurationTest.java | 12 +- .../direct/DirectCallHttpServerTest.java | 23 +- .../wiremock/extension/ParametersTest.java | 22 +- .../requestfilter/RequestWrapperTest.java | 98 +-- .../ResponseTemplateTransformerTest.java | 161 ++-- .../SystemKeyAuthorisorTest.java | 10 +- .../HandlebarsCurrentDateHelperTest.java | 65 +- .../helpers/HandlebarsHelperTestBase.java | 17 +- .../helpers/HandlebarsJsonPathHelperTest.java | 97 ++- .../HandlebarsRandomValuesHelperTest.java | 43 +- .../helpers/HandlebarsSoapHelperTest.java | 23 +- .../helpers/HandlebarsXPathHelperTest.java | 57 +- .../helpers/HostnameHelperTest.java | 8 +- .../helpers/ParseDateHelperTest.java | 19 +- .../helpers/ParseJsonHelperTest.java | 114 ++- .../helpers/RegexExtractHelperTest.java | 20 +- .../helpers/SystemValueHelperTest.java | 54 +- .../tomakehurst/wiremock/http/BodyTest.java | 30 +- .../wiremock/http/ContentTypeHeaderTest.java | 4 +- .../http/ProxyResponseRendererTest.java | 85 +- .../wiremock/http/ResponseDefinitionTest.java | 7 +- .../http/StubResponseRendererTest.java | 55 +- ...ingWiremockNetworkTrafficListenerTest.java | 70 ++ ...ingWiremockNetworkTrafficListenerTest.java | 105 +++ .../JettyHttpServerTest.java | 18 +- .../{jetty9 => jetty11}/MultipartParser.java | 22 +- ...tensionDeclarativeProgrammaticMixTest.java | 127 +++ ...erExtensionNonStaticMultiInstanceTest.java | 5 +- ...piterExtensionStaticMultiInstanceTest.java | 10 +- .../wiremock/matching/AbsentPatternTest.java | 25 +- .../matching/AfterDateTimePatternTest.java | 21 +- .../matching/BeforeDateTimePatternTest.java | 21 +- .../BinaryEqualToPatternPatternTest.java | 38 +- .../matching/ContainsPatternTest.java | 21 +- .../matching/EqualToDateTimePatternTest.java | 21 +- .../wiremock/matching/EqualToJsonTest.java | 50 +- .../wiremock/matching/EqualToPatternTest.java | 20 +- .../matching/EqualToXmlPatternTest.java | 81 +- .../wiremock/matching/LogicalAndTest.java | 35 +- .../wiremock/matching/LogicalOrTest.java | 44 +- .../wiremock/matching/MatchResultTest.java | 2 +- .../matching/MatchesJsonPathPatternTest.java | 85 +- .../MatchesJsonSchemaPatternTest.java | 299 +++++++ .../matching/MatchesXPathPatternTest.java | 66 +- .../wiremock/matching/MockMultipart.java | 7 +- .../wiremock/matching/MockRequest.java | 85 +- .../matching/MultiValuePatternTest.java | 5 +- .../MultipartValuePatternBuilderTest.java | 10 +- .../matching/MultipartValuePatternTest.java | 50 +- .../matching/NegativeContainsPatternTest.java | 21 +- .../wiremock/matching/NotPatternTest.java | 53 ++ .../matching/PathTemplatePatternTest.java | 76 ++ .../matching/RegexValuePatternTest.java | 20 +- .../matching/RequestPatternBuilderTest.java | 27 +- .../wiremock/matching/RequestPatternTest.java | 195 ++++- .../matching/StringValuePatternTest.java | 103 +-- .../wiremock/matching/UrlPatternTest.java | 22 +- .../ProxiedServeEventFiltersTest.java | 36 +- .../RequestPatternTransformerTest.java | 11 +- ...DefinitionBodyMatcherDeserializerTest.java | 24 +- .../SnapshotStubMappingBodyExtractorTest.java | 13 +- .../SnapshotStubMappingGeneratorTest.java | 29 +- .../SnapshotStubMappingPostProcessorTest.java | 54 +- ...pshotStubMappingTransformerRunnerTest.java | 20 +- .../standalone/CommandLineOptionsTest.java | 160 ++-- .../JsonFileMappingsSourceTest.java | 47 +- .../store/files/BlobStoreFileSourceTest.java | 137 ++++ .../stubbing/AdminRequestHandlerTest.java | 19 +- .../stubbing/InMemoryMappingsTest.java | 58 +- .../stubbing/InMemoryStubMappingsTest.java | 2 +- .../stubbing/ResponseDefinitionTest.java | 6 +- .../wiremock/stubbing/ScenariosTest.java | 24 +- .../wiremock/stubbing/ServeEventFactory.java | 51 ++ .../stubbing/StubMappingJsonRecorderTest.java | 188 +++-- .../stubbing/StubRequestHandlerTest.java | 139 ---- .../testsupport/ExtensionFactoryUtils.java | 74 ++ .../testsupport/MockHttpResponder.java | 5 +- .../testsupport/MockHttpServletRequest.java | 30 +- .../testsupport/MockRequestBuilder.java | 31 +- .../testsupport/MockWireMockServices.java | 85 ++ .../testsupport/ServeEventChecks.java | 75 ++ .../wiremock/testsupport/TestFiles.java | 7 +- .../wiremock/testsupport/WireMatchers.java | 178 ++-- .../testsupport/WireMockResponse.java | 12 +- .../testsupport/WireMockTestClient.java | 114 ++- .../InMemoryRequestJournalTest.java | 23 +- .../verification/LoggedRequestTest.java | 7 +- .../verification/NearMissCalculatorTest.java | 38 +- .../wiremock/verification/diff/DiffTest.java | 57 +- .../diff/PlainTextDiffRendererTest.java | 211 ++++- src/test/java/ignored/Examples.java | 28 +- .../java/ignored/MassiveNearMissTest.java | 10 +- .../resources/filesource/subdir/deepfile.json | 2 +- .../frozen/do-not-throw-generic-exception | 12 +- src/test/resources/frozen/unused-methods | 6 + src/test/resources/logback.xml | 17 + .../not-found-diff-sample_ascii-narrow.txt | 3 +- .../resources/not-found-diff-sample_ascii.txt | 3 +- ..._exactmatch-for-multiple-values-header.txt | 15 + ...tmatch-for-multiple-values-query-param.txt | 15 + .../resources/not-found-diff-sample_form.txt | 19 + ...ncludematch-for-multiple-values-header.txt | 15 + ...ematch-for-multiple-values-query-param.txt | 16 + ...nd-diff-sample_json-path-body-not-json.txt | 14 + ...ot-found-diff-sample_json-path-no-body.txt | 14 + .../not-found-diff-sample_json-schema.txt | 28 + .../not-found-diff-sample_large_json.txt | 24 +- ...t-found-diff-sample_large_json_windows.txt | 9 +- .../not-found-diff-sample_large_xml_jre11.txt | 8 +- ...nd-diff-sample_large_xml_jre11_windows.txt | 34 +- .../not-found-diff-sample_multipart.txt | 11 +- ...-found-diff-sample_url-path-parameters.txt | 15 + .../not-found-diff-sample_url-template.txt | 13 + .../resources/remoteloader/__files/body.xml | 1 + .../with-several-mappings-in-one-file.json | 37 + .../schema-validation/draft-7.invalid.json | 9 + .../schema-validation/draft-7.schema.json | 11 + .../schema-validation/has-ref.schema.json | 15 + .../schema-validation/invalid.schema.json | 3 + .../schema-validation/new-pet.invalid.json | 3 + .../resources/schema-validation/new-pet.json | 4 + .../schema-validation/new-pet.schema.json | 14 + .../new-pet.unparseable.json | 3 + .../schema-validation/numeric.schema.json | 4 + .../schema-validation/recursive.schema.json | 16 + .../schema-validation/shop-order.schema.json | 18 + .../shop-order.slightly-wrong.json | 5 + .../schema-validation/stringy.schema.json | 4 + src/test/resources/test-truststore.jceks | Bin 0 -> 3152 bytes test-extension/build.gradle | 24 + .../wiremock/FactoryLoaderTestExtension.java | 50 ++ .../wiremock/InstanceLoaderTestExtension.java | 42 + .../wiremock/LoaderTestExtensionFactory.java | 29 + ...b.tomakehurst.wiremock.extension.Extension | 1 + ...ehurst.wiremock.extension.ExtensionFactory | 1 + test-extension/test-extension.jar | Bin 0 -> 3596 bytes ui/package-lock.json | 2 +- ui/package.json | 2 +- wiremock-webhooks-extension/build.gradle | 33 +- .../wiremock/webhooks/WebhookDefinition.java | 5 +- .../java/org/wiremock/webhooks/Webhooks.java | 10 +- .../java/testsupport/WireMockResponse.java | 9 +- .../java/testsupport/WireMockTestClient.java | 4 +- 528 files changed, 16682 insertions(+), 6308 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/changelog-draft.yml create mode 100644 .run/Build WireMock.run.xml create mode 100644 .run/Publish to Maven local.run.xml create mode 100644 .run/Run Spotless.run.xml create mode 100644 .run/Run Tests.run.xml create mode 100644 NOTICE.txt create mode 100644 src/main/java/com/github/tomakehurst/wiremock/admin/model/HealthCheckResult.java rename src/main/java/com/github/tomakehurst/wiremock/admin/tasks/{RemoveStubMappingTask.java => AbstractSingleServeEventTask.java} (50%) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleStubTask.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTask.java delete mode 100644 src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldCreateStubMappingTask.java rename src/main/java/com/github/tomakehurst/wiremock/admin/tasks/{OldRemoveStubMappingTask.java => RemoveMatchingStubMappingTask.java} (68%) rename src/main/java/com/github/tomakehurst/wiremock/admin/tasks/{OldResetRequestsTask.java => RemoveStubMappingByIdTask.java} (62%) rename src/main/java/com/github/tomakehurst/wiremock/common/{GuavaBase64Encoder.java => JdkBase64Encoder.java} (70%) rename src/main/java/com/github/tomakehurst/wiremock/{global/GlobalSettingsHolder.java => common/Message.java} (55%) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/common/ParameterUtils.java delete mode 100644 src/main/java/com/github/tomakehurst/wiremock/common/SafeNames.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameMaker.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameTemplateModel.java rename src/main/java/com/github/tomakehurst/wiremock/{admin/model => common/url}/PathParams.java (90%) rename src/main/java/com/github/tomakehurst/wiremock/{admin/AdminUriTemplate.java => common/url/PathTemplate.java} (77%) rename src/main/java/com/github/tomakehurst/wiremock/{admin/model => common/url}/QueryParams.java (94%) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionDeclarations.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionFactory.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/Extensions.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformerV2.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformerV2.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListener.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListenerDefinition.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/TemplateHelperProviderExtension.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/TemplateModelDataProviderExtension.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/WireMockServices.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/AdminRequestFilterV2.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilterV2.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/StubRequestFilterV2.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplatedUrlPath.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/http/FormParameter.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileUpload.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListener.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/WiremockNetworkTrafficListeners.java rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/DefaultMultipartRequestConfigurer.java (66%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/JettyFaultInjector.java (81%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/JettyFaultInjectorFactory.java (85%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/JettyHttpServer.java (70%) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/JettyHttpsFaultInjector.java (90%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/JettyUtils.java (61%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/NotFoundHandler.java (88%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/QueuedThreadPoolFactory.java (91%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/websockets/Message.java (100%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty9 => jetty}/websockets/WebSocketEndpoint.java (100%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty94 => jetty11}/CertificateGeneratingSslContextFactory.java (95%) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/jetty11/HttpsProxyDetectingHandler.java rename src/main/java/com/github/tomakehurst/wiremock/{jetty94/Jetty94HttpServer.java => jetty11/Jetty11HttpServer.java} (80%) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11Utils.java rename src/main/java/com/github/tomakehurst/wiremock/{jetty94 => jetty11}/ManInTheMiddleSslConnectHandler.java (84%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty94 => jetty11}/SslContexts.java (98%) rename src/main/java/com/github/tomakehurst/wiremock/{jetty94 => jetty11}/WritableFileOrClasspathKeyStoreSource.java (96%) delete mode 100644 src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerFactory.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/AbstractLogicalMatcher.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/ExactMatchMultiValuePattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/IncludesMatchMultiValuePattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternDeserializer.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/MultipleMatchMultiValuePattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/NotPattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/PathTemplatePattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/SingleMatchMultiValuePattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/matching/UrlPathTemplatePattern.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/servlet/NotMatchedServlet.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/BlobStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/DefaultStores.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRecorderStateStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRequestJournalStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/InMemoryScenariosStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/InMemorySettingsStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/InMemoryStubMappingStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/RecorderStateStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/RequestJournalStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/ScenariosStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/SettingsStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/Store.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/Stores.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/StoresLifecycle.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/StubMappingStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreBinaryFile.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSource.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreTextFile.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/store/files/FileSourceBlobStore.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractStubMappings.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/stubbing/StoreBackedStubMappings.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/stubbing/SubEvent.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/verification/AbstractRequestJournal.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/verification/StoreBackedRequestJournal.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/verification/diff/DiffEventData.java create mode 100644 src/main/java/org/wiremock/annotations/Beta.java create mode 100644 src/main/java/org/wiremock/annotations/InternalAPI.java create mode 100644 src/main/java/wiremock/Run.java create mode 100644 src/main/resources/wiremock/joptsimple/HelpFormatterMessages.properties create mode 100644 src/test/java/com/github/tomakehurst/wiremock/ExtensionFactoryTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/JsonSchemaMatchingAcceptanceTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/RequestFilterV2AcceptanceTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/ResponseDefinitionTransformerV2AcceptanceTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerV2AcceptanceTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/ServeEventListenerExtensionTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/SubServeEventsAcceptanceTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/TemplateHelperExtensionTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/TemplateModelDataProviderExtensionTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/UrlPathTemplateMatchingTest.java delete mode 100644 src/test/java/com/github/tomakehurst/wiremock/admin/OldEditStubMappingTaskTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTaskTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/common/FilenameMakerTest.java delete mode 100644 src/test/java/com/github/tomakehurst/wiremock/common/SafeNamesTest.java rename src/test/java/com/github/tomakehurst/wiremock/{admin/AdminUriTemplateTest.java => common/url/PathTemplateTest.java} (63%) create mode 100644 src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListenerTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListenerTest.java rename src/test/java/com/github/tomakehurst/wiremock/{jetty9 => jetty11}/JettyHttpServerTest.java (86%) rename src/test/java/com/github/tomakehurst/wiremock/{jetty9 => jetty11}/MultipartParser.java (67%) create mode 100644 src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionDeclarativeProgrammaticMixTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPatternTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/matching/NotPatternTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/matching/PathTemplatePatternTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSourceTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/stubbing/ServeEventFactory.java delete mode 100644 src/test/java/com/github/tomakehurst/wiremock/stubbing/StubRequestHandlerTest.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/testsupport/ExtensionFactoryUtils.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/testsupport/MockWireMockServices.java create mode 100644 src/test/java/com/github/tomakehurst/wiremock/testsupport/ServeEventChecks.java create mode 100644 src/test/resources/logback.xml create mode 100644 src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-header.txt create mode 100644 src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-query-param.txt create mode 100644 src/test/resources/not-found-diff-sample_form.txt create mode 100644 src/test/resources/not-found-diff-sample_includematch-for-multiple-values-header.txt create mode 100644 src/test/resources/not-found-diff-sample_includematch-for-multiple-values-query-param.txt create mode 100644 src/test/resources/not-found-diff-sample_json-path-body-not-json.txt create mode 100644 src/test/resources/not-found-diff-sample_json-path-no-body.txt create mode 100644 src/test/resources/not-found-diff-sample_json-schema.txt create mode 100644 src/test/resources/not-found-diff-sample_url-path-parameters.txt create mode 100644 src/test/resources/not-found-diff-sample_url-template.txt create mode 100644 src/test/resources/remoteloader/__files/body.xml create mode 100644 src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json create mode 100644 src/test/resources/schema-validation/draft-7.invalid.json create mode 100644 src/test/resources/schema-validation/draft-7.schema.json create mode 100644 src/test/resources/schema-validation/has-ref.schema.json create mode 100644 src/test/resources/schema-validation/invalid.schema.json create mode 100644 src/test/resources/schema-validation/new-pet.invalid.json create mode 100644 src/test/resources/schema-validation/new-pet.json create mode 100644 src/test/resources/schema-validation/new-pet.schema.json create mode 100644 src/test/resources/schema-validation/new-pet.unparseable.json create mode 100644 src/test/resources/schema-validation/numeric.schema.json create mode 100644 src/test/resources/schema-validation/recursive.schema.json create mode 100644 src/test/resources/schema-validation/shop-order.schema.json create mode 100644 src/test/resources/schema-validation/shop-order.slightly-wrong.json create mode 100644 src/test/resources/schema-validation/stringy.schema.json create mode 100644 src/test/resources/test-truststore.jceks create mode 100644 test-extension/build.gradle create mode 100644 test-extension/src/main/java/org/wiremock/FactoryLoaderTestExtension.java create mode 100644 test-extension/src/main/java/org/wiremock/InstanceLoaderTestExtension.java create mode 100644 test-extension/src/main/java/org/wiremock/LoaderTestExtensionFactory.java create mode 100644 test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.Extension create mode 100644 test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.ExtensionFactory create mode 100644 test-extension/test-extension.jar diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..bb4979ee16 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @wiremock/wiremock-java-co-maintainers diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index b99a395747..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,20 +0,0 @@ -# Issue guidelines - -If you're asking a question, rather than reporting a bug or requesting a feature, **please post your question on the [mailing list](https://groups.google.com/forum/#!forum/wiremock-user), and do not open an issue**. - -Please do not log bugs regarding classpath issues (typically manifesting as 'NoClassDefFoundException' or 'ClassNotFoundException'). -These are not WireMock bugs, and need to be diagnosed for your project using your build tool. Plenty has already been written in WireMock's issues and mailing list about how to resolve these issues -so please search these sources before asking for help. - -## Bug reports - -We're a lot more likely to look at and fix bugs that are clearly described and reproduceable. Please including the following details when reporting a bug: - -- [ ] Which version of WireMock you're using -- [ ] How you're starting and configuring WireMock, including configuration or CLI the command line -- [ ] A failing test case that demonstrates the problem -- [ ] Profiler data if the issue is performance related - -## Feature requests - -Please include details of the use case and motivation for a feature when suggesting it. \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000000..3ac09a2aca --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,2 @@ +# Use https://github.com/wiremock/.github/blob/main/.github/release_drafter.yml +_extends: .github diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7dce633065..9fe9eca878 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - jdk: [8, 11, 17] + jdk: [11, 17] runs-on: ${{ matrix.os }} env: JDK_VERSION: ${{ matrix.jdk }} diff --git a/.github/workflows/changelog-draft.yml b/.github/workflows/changelog-draft.yml new file mode 100644 index 0000000000..868bf47ea4 --- /dev/null +++ b/.github/workflows/changelog-draft.yml @@ -0,0 +1,20 @@ +name: Release Drafter + +on: + push: + branches: + - main + - master + workflow_dispatch: + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + with: + name: next + tag: next + version: next + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.run/Build WireMock.run.xml b/.run/Build WireMock.run.xml new file mode 100644 index 0000000000..f855fd701f --- /dev/null +++ b/.run/Build WireMock.run.xml @@ -0,0 +1,25 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/Publish to Maven local.run.xml b/.run/Publish to Maven local.run.xml new file mode 100644 index 0000000000..8ebb442bea --- /dev/null +++ b/.run/Publish to Maven local.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/Run Spotless.run.xml b/.run/Run Spotless.run.xml new file mode 100644 index 0000000000..7dcfa26a52 --- /dev/null +++ b/.run/Run Spotless.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/Run Tests.run.xml b/.run/Run Tests.run.xml new file mode 100644 index 0000000000..d3831c8d7f --- /dev/null +++ b/.run/Run Tests.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.sdkmanrc b/.sdkmanrc index 41ab80767f..711233886a 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1,3 +1,3 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=8.0.312-zulu +java=11.0.18-tem diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e0e1121d1..90a766b0c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,18 +1,69 @@ # Contributing to WireMock -WireMock exists and continues to thrive due to the efforts of over 150 contributors, and we continue to welcome contributions -to it evoltion. +[![Docs](https://img.shields.io/static/v1?label=Documentation&message=public&color=green)](https://wiremock.org/docs/) +[![a](https://img.shields.io/badge/slack-%23wiremock%5Fjava-brightgreen?style=flat&logo=slack)](https://slack.wiremock.org/) +[![a](https://img.shields.io/badge/Public-Roadmap-brightgreen?style=flat)](https://github.com/orgs/wiremock/projects/4) +[![Participate](https://img.shields.io/static/v1?label=Contributing&message=guide&color=blue)](https://github.com/wiremock/wiremock/blob/master/CONTRIBUTING.md) +WireMock exists and continues to thrive due to the efforts of over 150 contributors, +and we continue to welcome contributions to its evolution. +Regardless of your expertise and time you could dedicate, +there're opportunities to participate and help the project! ## Ways to contribute -You can help make WireMock a better tool in a number of ways: -* Write or improve [documentation](#writing-documentation). -* Join the [community Slack channel](https://up9.slack.com/archives/C02V3EGV3U3), help other WireMock users and share tips. -* Raise an issue if you discover a bug. -* Contribute bug fixes, new features or enhancements. +This guide is for contributing to WireMock, also known as _WireMock Java_ or _WireMock Core_. +There are many other repositories waiting for contributors, +check out the [Contributor Guide](https://github.com/wiremock/wiremock/blob/master/CONTRIBUTING.md) +for the references and details. + +## Getting started + +If you want to contribute to WireMock Java codebase, do the following: + +* Join the [community Slack channel](http://slack.wiremock.org/), + especially the `#help-contributing` and `#wiremock-java` channels. + The latter is used to coordinate development of this repository. +* Read the guidelines below +* Start contributing by creating issues, submitting patches via pull requests, and helping others! + +## Building WireMock locally + +To run all of WireMock's tests: + +```bash +./gradlew check +``` + +To build both JARs (thin and standalone), the JARs will be placed under ``build/libs``.: + +```bash +./gradlew jar shadowJar +``` + +To publish both JARs to your local Maven repository: + +```bash +./gradlew publishToMavenLocal +``` + +If you use IntelliJ, you can also use the corresponding run configurations, **Run Tests**, **Build WireMock** and **Publish to Maven local** respectively. + +## Contributing Code + +Please be mindful of the +following guidelines: + +* All changes should include suitable tests, whether to demonstrate the bug or exercise and document the new feature. +* Please make one change per pull request. +* If the new feature is significantly large/complex/breaks existing behaviour, please first post a summary of your idea +on the GitHub Issue to generate a discussion. This will avoid significant amounts of coding time spent on changes that ultimately get rejected. +* Try to avoid reformats of files that change the indentation, tabs to spaces etc., as this makes reviewing diffs much +more difficult. +* Abide by [the Architecture Rules](https://github.com/wiremock/wiremock/tree/master/src/test/java/com/github/tomakehurst/wiremock/archunit) enforced by ArchUnit. + +### Before opening a PR -## Before opening a PR When proposing new features or enhancements, we strongly recommend opening an issue first so that the problem being solved and the implementation design can be discussed. This helps to avoid time being invested in code that is never eventually merged, and also promotes better designs by involving the community more widely. @@ -20,8 +71,8 @@ merged, and also promotes better designs by involving the community more widely. For straightforward bug fixes where the issue is clear and can be illustrated via a failing unit or acceptance test, please just open a PR. +### Code style -## Code style WireMock uses the [Google Java style guide](https://google.github.io/styleguide/javaguide.html) and this is enforced in the build via the Gradle [Spotless plugin](https://github.com/diffplug/spotless). @@ -31,24 +82,72 @@ When running pre-commit checks, if there are any formatting failures the Spotles ./gradlew spotlessApply ``` -There's also an [IntelliJ plugin](https://plugins.jetbrains.com/plugin/8527-google-java-format) for the same purpose. - +If you use IntelliJ, you can also use the run configuration called **Run Spotless**, +or there's also an [IntelliJ plugin](https://plugins.jetbrains.com/plugin/8527-google-java-format) for the same purpose. ## Testing -WireMock has a fairly comprehensive test suite which ensures it remains robust and correct as the codebase envolves. + +WireMock has a fairly comprehensive test suite which ensures it remains robust and correct as the codebase evolves. In particular, there are acceptance tests for almost all features, where a full WireMock server is started up and tested via its public APIs. New features should by default come with acceptance tests covering the major positive and negative cases. Unit tests -should also be used judiciously where non-trivial logic would benefit from finer-grained checking. +should also be used judiciously where non-trivial logic would benefit from finer-grained checking. When making performance enhancements a representative benchmark test should be developed using an appropriate tool, and the results before and after applying the change attached to the associated PR. ## Writing documentation -WireMock's documentation is publised on wiremock.org, which is a static website built using Jekyll. -Documentation can be added or modified by cloning -[https://github.com/wiremock/wiremock.org-sources](https://github.com/wiremock/wiremock.org-sources), working with the -Markdown documents under the `_docs` directory, then raising a PR when ready to have you changes published. +It is expected that all new features and enhancements are documented properly, +in most cases before the patches are merged. + +Most of WireMock's documentation is published on `wiremock.org`, +which is a static website built using Jekyll. +The website sources are located here: [wiremock/wiremock.org](https://github.com/wiremock/wiremock.org). +All the documentation is located under the `_docs` directory as Markdown files, +and it can be edited with all modern text editors and IDEs. +See the repository's contributor guide for more information. + +## Merge process + +Merges to this repository can be performed by the WireMock maintainer ([Tom Akehurst](https://github.com/tomakehurst)) +and by _co-maintainers_ assigned by him. +This is a [community role](https://github.com/wiremock/community/blob/main/governance/README.md) +designed for WireMock itself and other key repositories, +specifically to facilitate review and changes while WireMock 3 is in beta +and receives a lot of incremental patches. + +- The maintainers are responsible to verify the pull request readiness + in accordance with contributing guidelines (e.g. code quality, test automation, documentation, etc.). + The pull request can only be approved if these requirements are met +- In the beginning, a review by one co-maintainer is required for the merge, + unless there are negative reviews and unaddressed comments by other contributors +- After the approval, it is generally recommended to give at least 24 hours for reviews before merging + +Beyond WireMock 3.x Beta releases, the scope of responsibilities for co-maintainers +is yet to be determined based on the experiences with this role for the Beta versions. + +### What can be merged by co-maintainers + +While WireMock 3.x is in Beta, co-maintainers can merge the following pull requests: + +- Minor features and improvements that do not impact the WireMock architecture +- Refactorings, including the major ones, e.g. Guava replacement +- Test Automation +- Non-production repository changes: documentation (including Javadoc), GitHub Actions, bots and automation +- Dependency updates for shaded dependencies, patch/minor versions for projects following the Semantic Versioning notation + +### What CANNOT be merged by co-maintainers without BDFL’s approval + +The following changes need a review by Tom Akehurst before being merged. + +- Any compatibility breaking changes, including binary API and REST API, + unless pre-approved by the BDFL in the associated GitHub issue +- New request matchers (patterns) +- Substantial changes to WireMock Architecture and API. + Examples: New REST API end-points, major features like GraphQL fetching +- Inclusion of new libraries, even if shaded +- Major version Dependency updates, e.g. Jetty 11 => 12 +- Changes in the deliverable artefacts, e.g. new modules \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000000..1e8e163d5c --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,2 @@ +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5b4a633b5c..22ed597698 100644 --- a/build.gradle +++ b/build.gradle @@ -15,23 +15,23 @@ plugins { id 'idea' id 'eclipse' id 'project-report' - id 'com.diffplug.spotless' version '6.11.0' - id 'com.github.johnrengelman.shadow' version '7.1.2' - id "org.sonarqube" version "3.4.0.2513" + id 'com.diffplug.spotless' version '6.21.0' + id 'com.github.johnrengelman.shadow' version '8.1.0' + id "org.sonarqube" version "4.3.1.3277" id 'jacoco' } -group = 'com.github.tomakehurst' +group = 'org.wiremock' project.ext { versions = [ handlebars : '4.3.1', - jetty : '9.4.49.v20220914', - guava : '31.1-jre', - jackson : '2.13.4.20221013', - xmlUnit : '2.9.0', - jsonUnit : '2.36.0', - junitJupiter : '5.9.1' + jetty : '11.0.16', + guava : '32.1.2-jre', + jackson : '2.15.2', + xmlUnit : '2.9.1', + jsonUnit : '2.38.0', + junitJupiter : '5.10.0' ] } @@ -50,9 +50,8 @@ dependencies { api "org.eclipse.jetty.http2:http2-server" api "org.eclipse.jetty:jetty-alpn-server" api "org.eclipse.jetty:jetty-alpn-java-server" - api "org.eclipse.jetty:jetty-alpn-openjdk8-server" api "org.eclipse.jetty:jetty-alpn-java-client" - api "org.eclipse.jetty:jetty-alpn-openjdk8-client" + api "org.eclipse.jetty:jetty-alpn-client" api "org.eclipse.jetty.websocket:javax-websocket-server-impl" api "io.jsonwebtoken:jjwt-api:0.10.6" @@ -60,22 +59,25 @@ dependencies { api "io.jsonwebtoken:jjwt-jackson:0.10.6" api "org.bouncycastle:bcprov-jdk15on:1.62" - api "com.google.guava:guava:$versions.guava" + api "com.google.guava:guava:$versions.guava", { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + } api platform("com.fasterxml.jackson:jackson-bom:$versions.jackson") api "com.fasterxml.jackson.core:jackson-core", "com.fasterxml.jackson.core:jackson-annotations", - "com.fasterxml.jackson.core:jackson-databind" - api "org.apache.httpcomponents.client5:httpclient5:5.1.3" + "com.fasterxml.jackson.core:jackson-databind", + "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" + api "org.apache.httpcomponents.client5:httpclient5:5.2.1" api "org.xmlunit:xmlunit-core:$versions.xmlUnit" api "org.xmlunit:xmlunit-legacy:$versions.xmlUnit", { exclude group: 'junit', module: 'junit' } api "org.xmlunit:xmlunit-placeholders:$versions.xmlUnit" api "net.javacrumbs.json-unit:json-unit-core:$versions.jsonUnit" - api "com.jayway.jsonpath:json-path:2.7.0", { + api "com.jayway.jsonpath:json-path:2.8.0", { exclude group: 'org.ow2.asm', module: 'asm' } - api "org.ow2.asm:asm:9.4" + api "org.ow2.asm:asm:9.5" implementation "org.slf4j:slf4j-api:1.7.36" standaloneOnly "org.slf4j:slf4j-nop:1.7.36" @@ -88,7 +90,7 @@ dependencies { compileOnly(platform("org.junit:junit-bom:$versions.junitJupiter")) compileOnly("org.junit.jupiter:junit-jupiter") - api 'org.apache.commons:commons-lang3:3.12.0' + api 'org.apache.commons:commons-lang3:3.13.0' api "com.github.jknack:handlebars:$versions.handlebars", { exclude group: 'org.mozilla', module: 'rhino' } @@ -96,36 +98,38 @@ dependencies { exclude group: 'org.mozilla', module: 'rhino' } - api 'commons-fileupload:commons-fileupload:1.4', { + api 'commons-fileupload:commons-fileupload:1.5', { exclude group: 'commons-io', module: 'commons-io' } - api "commons-io:commons-io:2.11.0" + api "commons-io:commons-io:2.13.0" + + api 'com.networknt:json-schema-validator:1.0.86' testImplementation "junit:junit:4.13" testImplementation("org.junit.jupiter:junit-jupiter:$versions.junitJupiter") testImplementation("org.junit.platform:junit-platform-testkit") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") testImplementation("org.junit.platform:junit-platform-launcher") - testImplementation 'org.mockito:mockito-junit-jupiter:4.8.1' + testImplementation("org.junit.jupiter:junit-jupiter-params") + testImplementation('org.junit-pioneer:junit-pioneer:2.0.1') testImplementation "org.hamcrest:hamcrest-core:2.2" testImplementation "org.hamcrest:hamcrest-library:2.2" - testImplementation 'org.mockito:mockito-core:4.8.1' - testImplementation 'org.mockito:mockito-junit-jupiter:4.8.1' + testImplementation 'org.mockito:mockito-core:5.5.0' + testImplementation 'org.mockito:mockito-junit-jupiter:5.5.0' testImplementation "net.javacrumbs.json-unit:json-unit:$versions.jsonUnit" testImplementation "org.skyscreamer:jsonassert:1.2.3" testImplementation 'com.toomuchcoding.jsonassert:jsonassert:0.6.2' testImplementation 'org.awaitility:awaitility:4.2.0' testImplementation "com.googlecode.jarjar:jarjar:1.3" - testImplementation "commons-io:commons-io:2.10.0" - testImplementation 'org.scala-lang:scala-library:2.13.10' + testImplementation "commons-io:commons-io:2.13.0" + testImplementation 'org.scala-lang:scala-library:2.13.12' testImplementation 'com.tngtech.archunit:archunit-junit5:0.23.1' testImplementation "org.eclipse.jetty:jetty-client" testImplementation "org.eclipse.jetty.http2:http2-http-client-transport" - - testImplementation "ch.qos.logback:logback-classic:1.3.0" - + testRuntimeOnly "org.slf4j:log4j-over-slf4j:2.0.9" + testRuntimeOnly "ch.qos.logback:logback-classic:1.4.0" testRuntimeOnly files('src/test/resources/classpath file source/classpathfiles.zip', 'src/test/resources/classpath-filesource.jar') testImplementation ('net.jockx:littleproxy:1.1.3') { @@ -134,10 +138,12 @@ dependencies { exclude group: 'org.slf4j', module: 'slf4j-api' exclude group: 'io.netty', module: 'netty-all' } - testImplementation "io.netty:netty-all:4.1.84.Final" + testImplementation "io.netty:netty-all:4.1.97.Final" + + testImplementation files('test-extension/test-extension.jar') constraints { - implementation "net.minidev:json-smart:2.4.8", { + implementation "net.minidev:json-smart:2.5.0", { because 'Pinning this above the transitive version from json-path to get CVE fix' } } @@ -148,7 +154,7 @@ allprojects { spotless { java { target 'src/**/*.java' - googleJavaFormat('1.7') + googleJavaFormat('1.17.0') licenseHeaderFile "${rootDir}/gradle/spotless.java.license.txt" ratchetFrom 'origin/master' trimTrailingWhitespace() @@ -164,7 +170,7 @@ allprojects { } json { target 'src/**/*.json' - targetExclude '**/tmp*.json', 'src/test/resources/sample.json', 'src/main/resources/swagger/*.json' + targetExclude '**/tmp*.json', 'src/test/resources/sample.json', 'src/main/resources/swagger/*.json', 'src/test/resources/filesource/subdir/deepfile.json', 'src/test/resources/schema-validation/*.json' simple().indentWithSpaces(2) } } @@ -208,21 +214,24 @@ allprojects { mavenCentral() } - version = '2.35.1.0' + version = '3.0.4.0' + - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 11 + targetCompatibility = 11 compileJava { options.encoding = 'UTF-8' // silences warnings about compiling against `sun` packages options.compilerArgs += '-XDenableSunApiLintControl' + options.compilerArgs += '--add-exports=java.base/sun.security.x509=ALL-UNNAMED' } compileTestJava { options.encoding = 'UTF-8' options.compilerArgs += '-XDenableSunApiLintControl' + options.compilerArgs += '--add-exports=java.base/sun.security.x509=ALL-UNNAMED' } test { @@ -242,7 +251,7 @@ allprojects { jacocoTestReport { reports { - xml.enabled true + xml.required = true } } test.finalizedBy jacocoTestReport @@ -254,6 +263,8 @@ allprojects { property "sonar.host.url", "https://sonarcloud.io" } } + + shadowJar.dependsOn jar } java { @@ -269,19 +280,20 @@ task testJar(type: Jar, dependsOn: testClasses) { final DOCS_DIR = project(':').rootDir.getAbsolutePath() + '/docs-v2' jar { - archiveBaseName.set('wiremock-jre8') + archiveBaseName.set('wiremock') manifest { - attributes("Main-Class": "com.github.tomakehurst.wiremock.standalone.WireMockServerRunner") + attributes("Main-Class": "wiremock.Run") attributes("Add-Exports": "java.base/sun.security.x509") } } shadowJar { + archiveBaseName.set('wiremock-standalone') + archiveClassifier.set('') configurations = [ project.configurations.runtimeClasspath, project.configurations.standaloneOnly ] - archiveBaseName.set('wiremock-jre8-standalone') relocate "org.mortbay", 'wiremock.org.mortbay' relocate "org.eclipse", 'wiremock.org.eclipse' @@ -302,10 +314,15 @@ shadowJar { relocate "net.sf", "wiremock.net.sf" relocate "com.github.jknack", "wiremock.com.github.jknack" relocate "org.antlr", "wiremock.org.antlr" - relocate "javax.servlet", "wiremock.javax.servlet" + relocate "jakarta.servlet", "wiremock.jakarta.servlet" relocate "org.checkerframework", "wiremock.org.checkerframework" relocate "org.hamcrest", "wiremock.org.hamcrest" relocate "org.slf4j", "wiremock.org.slf4j" + relocate "joptsimple", "wiremock.joptsimple" + exclude 'joptsimple/HelpFormatterMessages.properties' + relocate "org.yaml", "wiremock.org.yaml" + relocate "com.ethlo", "wiremock.com.ethlo" + relocate "com.networknt", "wiremock.com.networknt" dependencies { exclude(dependency('junit:junit')) @@ -350,6 +367,12 @@ publishing { } } + getComponents().withType(AdhocComponentWithVariants).each { c -> + c.withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { + skip() + } + } + publications { mavenJava(MavenPublication) { publication -> artifactId = "${jar.getArchiveBaseName().get()}" @@ -370,13 +393,6 @@ publishing { artifact javadocJar artifact testJar - publication.artifacts.each { - // for the shaded JAR - if ('all' == it.classifier) { - it.classifier = '' - } - } - pom.packaging 'jar' pom.withXml { asNode().appendNode('description', 'A web service test double for all occasions - standalone edition') @@ -384,7 +400,6 @@ publishing { } } } - } task checkReleasePreconditions { @@ -396,6 +411,10 @@ task checkReleasePreconditions { } } publish.dependsOn checkReleasePreconditions +project.tasks.publishMavenJavaPublicationToMavenLocal.dependsOn 'signStandaloneJarPublication' +project.tasks.publishStandaloneJarPublicationToMavenLocal.dependsOn 'signMavenJavaPublication' +project.tasks.publishMavenJavaPublicationToMavenRepository.dependsOn 'signStandaloneJarPublication' +project.tasks.publishStandaloneJarPublicationToMavenRepository.dependsOn 'signMavenJavaPublication' assemble.dependsOn jar, shadowJar @@ -444,15 +463,43 @@ task localRelease { dependsOn clean, assemble, publishToMavenLocal } +task 'bump-beta-version' { + doLast { + def filesWithVersion = [ + 'build.gradle' : { "version = '${it}" }, + 'ui/package.json' : { "\"version\": \"${it}\"" }, + 'src/main/resources/swagger/wiremock-admin-api.json': { "\"version\": \"${it}\"" }, + 'src/main/resources/swagger/wiremock-admin-api.yaml': { + "version: ${it}" + } + ] + + def currentVersion = project.version + def nextVersion = "3.0.0-beta-${betaVersion + 1}" + + filesWithVersion.each { fileName, lineWithVersionTemplates -> + def file = file(fileName) + def lineWithVersionTemplateList = [lineWithVersionTemplates].flatten() + + lineWithVersionTemplateList.each { lineWithVersionTemplate -> + def oldLine = lineWithVersionTemplate.call(currentVersion) + def newLine = lineWithVersionTemplate.call(nextVersion) + println "Replacing '${oldLine}' with '${newLine}' in ${fileName}" + file.text = file.text.replace(oldLine, newLine) + } + } + } +} + task 'bump-patch-version' { doLast { def filesWithVersion = [ 'build.gradle' : { "version = '${it}" }, - 'ui/package.json' : { "version: ${it}" }, - 'ui/package-lock.json' : { "version: ${it}" }, - 'src/main/resources/swagger/wiremock-admin-api.json': { "version: ${it}" }, + 'ui/package.json' : { "\"version\": \"${it}\"" }, + 'src/main/resources/swagger/wiremock-admin-api.json': { "\"version\": \"${it}\"" }, 'src/main/resources/swagger/wiremock-admin-api.yaml': { - "version: ${it}" } + "version: ${it}" + } ] def currentVersion = project.version @@ -476,11 +523,11 @@ task 'bump-minor-version' { doLast { def filesWithVersion = [ 'build.gradle' : { "version = '${it}" }, - 'ui/package.json' : { "version: ${it}" }, - 'ui/package-lock.json' : { "version: ${it}" }, - 'src/main/resources/swagger/wiremock-admin-api.json': { "version: ${it}" }, + 'ui/package.json' : { "\"version\": \"${it}\"" }, + 'src/main/resources/swagger/wiremock-admin-api.json': { "\"version\": \"${it}\"" }, 'src/main/resources/swagger/wiremock-admin-api.yaml': { - "version: ${it}" } + "version: ${it}" + } ] def currentVersion = project.version @@ -500,6 +547,22 @@ task 'bump-minor-version' { } } +tasks.withType(JavaCompile) { + options.compilerArgs.addAll([ + "--add-exports", + "java.base/sun.security.x509=ALL-UNNAMED" + ]) +} + +eclipse.classpath.file { + whenMerged { + entries.find{ it.path ==~ '.*JRE_CONTAINER.*' }.each { + it.entryAttributes['module'] = true + it.entryAttributes['add-exports'] = 'java.base/sun.security.x509=ALL-UNNAMED' + } + } +} + int getMajorVersion() { Integer.valueOf(project.version.substring(0, project.version.indexOf('.'))) } @@ -512,6 +575,10 @@ int getPatchVersion() { Integer.valueOf(project.version.substring(project.version.lastIndexOf('.') + 1)) } +int getBetaVersion() { + Integer.valueOf(project.version.substring(project.version.lastIndexOf('-') + 1)) +} + wrapper { gradleVersion = '7.2' distributionType = Wrapper.DistributionType.BIN diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b5..f72df95a7e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java b/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java index ba76787e88..cc4653db3d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package com.github.tomakehurst.wiremock; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkState; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static com.google.common.base.Preconditions.checkState; import com.github.tomakehurst.wiremock.admin.model.*; import com.github.tomakehurst.wiremock.client.CountMatchingStrategy; @@ -29,7 +29,6 @@ import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockApp; import com.github.tomakehurst.wiremock.global.GlobalSettings; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; import com.github.tomakehurst.wiremock.http.HttpServer; import com.github.tomakehurst.wiremock.http.HttpServerFactory; import com.github.tomakehurst.wiremock.http.RequestListener; @@ -43,6 +42,7 @@ import com.github.tomakehurst.wiremock.recording.RecordingStatusResult; import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult; import com.github.tomakehurst.wiremock.standalone.MappingsLoader; +import com.github.tomakehurst.wiremock.store.files.FileSourceBlobStore; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubImport; import com.github.tomakehurst.wiremock.stubbing.StubMapping; @@ -118,22 +118,23 @@ public WireMockServer() { this(wireMockConfig()); } - public void loadMappingsUsing(final MappingsLoader mappingsLoader) { + public WireMockServer(String filenameTemplate) { + this(wireMockConfig().filenameTemplate(filenameTemplate)); + }public void loadMappingsUsing(final MappingsLoader mappingsLoader) { this.wireMockApp.loadMappingsUsing(mappingsLoader); } - public GlobalSettingsHolder getGlobalSettingsHolder() { - return this.wireMockApp.getGlobalSettingsHolder(); + public void addMockServiceRequestListener(RequestListener listener) { + stubRequestHandler.addRequestListener(listener); } - public void addMockServiceRequestListener(final RequestListener listener) { - this.stubRequestHandler.addRequestListener(listener); - } - public void enableRecordMappings(final FileSource mappingsFileSource, final FileSource filesFileSource) { this.addMockServiceRequestListener( new StubMappingJsonRecorder( - mappingsFileSource, filesFileSource, this.wireMockApp, this.options.matchingHeaders())); + new FileSourceBlobStore(mappingsFileSource), + new FileSourceBlobStore(filesFileSource), + this.wireMockApp, + this.options.matchingHeaders())); this.notifier.info("Recording mappings to " + mappingsFileSource.getPath()); } @@ -158,20 +159,18 @@ public void start() { @Override public void shutdown() { final WireMockServer server = this; - final Thread shutdownThread = new Thread(new Runnable() { - @Override - public void run() { - try { - // We have to sleep briefly to finish serving the shutdown request before stopping - // the server, as - // there's no support in Jetty for shutting down after the current request. - // See http://stackoverflow.com/questions/4650713 - Thread.sleep(100); - } catch (final InterruptedException e) { + final Thread shutdownThread = new Thread(() -> { + try { + // We have to sleep briefly to finish serving the shutdown request before stopping + // the server, as + // there's no support in Jetty for shutting down after the current request. + // See http://stackoverflow.com/questions/4650713 + Thread.sleep(100); + } catch (final InterruptedException e) { throw new RuntimeException(e); } server.stop(); - } + }); shutdownThread.start(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java b/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java index 4d28c7fe59..aac61d34da 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,104 +18,102 @@ import static com.github.tomakehurst.wiremock.admin.RequestSpec.requestSpec; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static com.github.tomakehurst.wiremock.http.RequestMethod.*; -import static com.google.common.collect.Iterables.tryFind; import com.github.tomakehurst.wiremock.admin.tasks.*; import com.github.tomakehurst.wiremock.extension.AdminApiExtension; import com.github.tomakehurst.wiremock.http.RequestMethod; -import com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer; -import com.google.common.base.Function; -import com.google.common.base.Predicate; +import com.github.tomakehurst.wiremock.store.Stores; import com.google.common.collect.ImmutableBiMap; import java.util.Collections; -import java.util.Map; +import java.util.Map.Entry; public class AdminRoutes { private final ImmutableBiMap routes; private final Iterable apiExtensions; + private final Stores stores; - public static AdminRoutes defaults() { - return new AdminRoutes( - Collections.emptyList(), new PlainTextStubNotMatchedRenderer()); + public static AdminRoutes forClient() { + return new AdminRoutes(Collections.emptyList(), null); } - public static AdminRoutes defaultsPlus( - final Iterable apiExtensions, final AdminTask notMatchedTask) { - return new AdminRoutes(apiExtensions, notMatchedTask); - } + public static AdminRoutes forServer(Iterable apiExtensions, Stores stores) { + return new AdminRoutes(apiExtensions, stores); + } - protected AdminRoutes(final Iterable apiExtensions, final AdminTask notMatchedTask) { - this.apiExtensions = apiExtensions; - final RouteBuilder routeBuilder = new RouteBuilder(); - this.initDefaultRoutes(routeBuilder); - this.initAdditionalRoutes(routeBuilder); - routeBuilder.add(ANY, "/not-matched", notMatchedTask); - this.routes = routeBuilder.build(); + protected AdminRoutes(Iterable apiExtensions, Stores stores) { + this.apiExtensions = apiExtensions; + this.stores = stores; + RouteBuilder routeBuilder = new RouteBuilder(); + initDefaultRoutes(routeBuilder); + initAdditionalRoutes(routeBuilder); + routes = routeBuilder.build(); } private void initDefaultRoutes(final Router router) { - router.add(GET, "/", RootTask.class); - router.add(GET, "", RootRedirectTask.class); - router.add(POST, "/reset", ResetTask.class); - - router.add(GET, "/mappings", GetAllStubMappingsTask.class); - router.add(POST, "/mappings", CreateStubMappingTask.class); - router.add(DELETE, "/mappings", ResetStubMappingsTask.class); - - router.add(POST, "/mappings/new", OldCreateStubMappingTask.class); // Deprecated - router.add(POST, "/mappings/remove", OldRemoveStubMappingTask.class); // Deprecated - router.add(POST, "/mappings/edit", OldEditStubMappingTask.class); // Deprecated - router.add(POST, "/mappings/save", SaveMappingsTask.class); - router.add(POST, "/mappings/reset", ResetToDefaultMappingsTask.class); - router.add(GET, "/mappings/{id}", GetStubMappingTask.class); - router.add(PUT, "/mappings/{id}", EditStubMappingTask.class); - router.add(DELETE, "/mappings/{id}", RemoveStubMappingTask.class); - router.add(POST, "/mappings/find-by-metadata", FindStubMappingsByMetadataTask.class); - router.add(POST, "/mappings/remove-by-metadata", RemoveStubMappingsByMetadataTask.class); - router.add(POST, "/mappings/import", ImportStubMappingsTask.class); - - router.add(GET, "/files", GetAllStubFilesTask.class); + router.add(GET, "/", new RootTask()); + router.add(GET, "", new RootRedirectTask()); + router.add(POST, "/reset", new ResetTask()); + + router.add(GET, "/mappings", new GetAllStubMappingsTask()); + router.add(POST, "/mappings", new CreateStubMappingTask()); + router.add(DELETE, "/mappings", new ResetStubMappingsTask()); + + // Deprecated but kept so that 2.x client will still be compatible + router.add(POST, "/mappings/edit", new OldEditStubMappingTask()); + + router.add(POST, "/mappings/save", new SaveMappingsTask()); + router.add(POST, "/mappings/reset", new ResetToDefaultMappingsTask()); + router.add(GET, "/mappings/{id}", new GetStubMappingTask()); + router.add(PUT, "/mappings/{id}", new EditStubMappingTask()); + router.add(POST, "/mappings/remove", new RemoveMatchingStubMappingTask()); + router.add(DELETE, "/mappings/{id}", new RemoveStubMappingByIdTask()); + router.add(POST, "/mappings/find-by-metadata", new FindStubMappingsByMetadataTask()); + router.add(POST, "/mappings/remove-by-metadata", new RemoveStubMappingsByMetadataTask()); + router.add(POST, "/mappings/import", new ImportStubMappingsTask()); + + router.add(GET, "/files", new GetAllStubFilesTask(stores)); router.add(GET, "/files/**", GetStubFilesTask.class); - router.add(PUT, "/files/**", EditStubFileTask.class); - router.add(DELETE, "/files/**", DeleteStubFileTask.class); - - router.add(GET, "/scenarios", GetAllScenariosTask.class); - router.add(POST, "/scenarios/reset", ResetScenariosTask.class); - router.add(PUT, "/scenarios/{name}/state", SetScenarioStateTask.class); - - router.add(GET, "/requests", GetAllRequestsTask.class); - router.add(DELETE, "/requests", ResetRequestsTask.class); - router.add(POST, "/requests/reset", OldResetRequestsTask.class); // Deprecated - router.add(POST, "/requests/count", GetRequestCountTask.class); - router.add(POST, "/requests/find", FindRequestsTask.class); - router.add(GET, "/requests/unmatched", FindUnmatchedRequestsTask.class); - router.add(GET, "/requests/unmatched/near-misses", FindNearMissesForUnmatchedTask.class); - router.add(GET, "/requests/{id}", GetServedStubTask.class); - router.add(DELETE, "/requests/{id}", RemoveServeEventTask.class); - router.add(POST, "/requests/remove", RemoveServeEventsByRequestPatternTask.class); - router.add(POST, "/requests/remove-by-metadata", RemoveServeEventsByStubMetadataTask.class); - - router.add(POST, "/recordings/snapshot", SnapshotTask.class); - router.add(POST, "/recordings/start", StartRecordingTask.class); - router.add(POST, "/recordings/stop", StopRecordingTask.class); - router.add(GET, "/recordings/status", GetRecordingStatusTask.class); - router.add(GET, "/recorder", GetRecordingsIndexTask.class); - - router.add(POST, "/near-misses/request", FindNearMissesForRequestTask.class); - router.add(POST, "/near-misses/request-pattern", FindNearMissesForRequestPatternTask.class); - - router.add(GET, "/settings", GetGlobalSettingsTask.class); - router.add(PUT, "/settings", GlobalSettingsUpdateTask.class); - router.add(POST, "/settings", GlobalSettingsUpdateTask.class); - router.add(PATCH, "/settings/extended", PatchExtendedSettingsTask.class); - - router.add(POST, "/shutdown", ShutdownServerTask.class); - - router.add(GET, "/docs/swagger", GetSwaggerSpecTask.class); - router.add(GET, "/docs", GetDocIndexTask.class); - - router.add(GET, "/certs/wiremock-ca.crt", GetCaCertTask.class); + router.add(PUT, "/files/**", new EditStubFileTask(stores)); + router.add(DELETE, "/files/**", new DeleteStubFileTask(stores)); + + router.add(GET, "/scenarios", new GetAllScenariosTask()); + router.add(POST, "/scenarios/reset", new ResetScenariosTask()); + router.add(PUT, "/scenarios/{name}/state", new SetScenarioStateTask()); + + router.add(GET, "/requests", new GetAllRequestsTask()); + router.add(DELETE, "/requests", new ResetRequestsTask()); + router.add(POST, "/requests/count", new GetRequestCountTask()); + router.add(POST, "/requests/find", new FindRequestsTask()); + router.add(GET, "/requests/unmatched", new FindUnmatchedRequestsTask()); + router.add(GET, "/requests/unmatched/near-misses", new FindNearMissesForUnmatchedTask()); + router.add(GET, "/requests/{id}", new GetServedStubTask()); + router.add(DELETE, "/requests/{id}", new RemoveServeEventTask()); + router.add(POST, "/requests/remove", new RemoveServeEventsByRequestPatternTask()); + router.add(POST, "/requests/remove-by-metadata", new RemoveServeEventsByStubMetadataTask()); + + router.add(POST, "/recordings/snapshot", new SnapshotTask()); + router.add(POST, "/recordings/start", new StartRecordingTask()); + router.add(POST, "/recordings/stop", new StopRecordingTask()); + router.add(GET, "/recordings/status", new GetRecordingStatusTask()); + router.add(GET, "/recorder", new GetRecordingsIndexTask()); + + router.add(POST, "/near-misses/request", new FindNearMissesForRequestTask()); + router.add(POST, "/near-misses/request-pattern", new FindNearMissesForRequestPatternTask()); + + router.add(GET, "/settings", new GetGlobalSettingsTask()); + router.add(PUT, "/settings", new GlobalSettingsUpdateTask()); + router.add(POST, "/settings", new GlobalSettingsUpdateTask()); + router.add(PATCH, "/settings/extended", new PatchExtendedSettingsTask()); + + router.add(POST, "/shutdown", new ShutdownServerTask()); + + router.add(GET, "/docs/swagger", new GetSwaggerSpecTask()); + router.add(GET, "/docs", new GetDocIndexTask()); + + router.add(GET, "/certs/wiremock-ca.crt", new GetCaCertTask()); + + router.add(GET, "/health", new HealthCheckTask()); router.add(GET, "/proxy", GetProxyConfigTask.class); router.add(PUT, "/proxy/{id}", EnableProxyTask.class); @@ -129,43 +127,21 @@ protected void initAdditionalRoutes(final Router routeBuilder) { } public AdminTask taskFor(final RequestMethod method, final String path) { - return tryFind( - this.routes.entrySet(), - new Predicate>() { - @Override - public boolean apply(final Map.Entry entry) { - return entry.getKey().matches(method, path); - } - }) - .transform( - new Function, AdminTask>() { - @Override - public AdminTask apply(final Map.Entry input) { - return input.getValue(); - } - }) - .or(new NotFoundAdminTask()); + return routes.entrySet().stream() + .filter(entry -> entry.getKey().matches(method, path)) + .map(Entry::getValue) + .findFirst() + .orElse(new NotFoundAdminTask()); } public RequestSpec requestSpecForTask(final Class taskClass) { - final RequestSpec requestSpec = tryFind(this.routes.entrySet(), - new Predicate>() { - @Override - public boolean apply(final Map.Entry input) { - return input.getValue().getClass().equals(taskClass);} - }) - .transform(new Function, RequestSpec>() { - @Override - public RequestSpec apply(final Map.Entry input) { - return input.getKey();} - }) - .orNull(); - - if (requestSpec == null) { - throw new NotFoundException("No route could be found for " + taskClass.getSimpleName()); - } - - return requestSpec; + return routes.entrySet().stream() + .filter(input -> input.getValue().getClass().equals(taskClass)) + .map(Entry::getKey) + .findFirst() + .orElseThrow( + () -> + new NotFoundException("No route could be found for " + taskClass.getSimpleName())); } protected static class RouteBuilder implements Router { diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/AdminTask.java index 9cdcaf11a8..323ab2971c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/AdminTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public interface AdminTask { - ResponseDefinition execute(Admin admin, Request request, PathParams pathParams); + ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/FindStubMappingsByMetadataTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/FindStubMappingsByMetadataTask.java index f9c7066bfc..5c3d6554bc 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/FindStubMappingsByMetadataTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/FindStubMappingsByMetadataTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,19 @@ package com.github.tomakehurst.wiremock.admin; import com.github.tomakehurst.wiremock.admin.model.ListStubMappingsResult; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class FindStubMappingsByMetadataTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StringValuePattern pattern = Json.read(request.getBodyAsString(), StringValuePattern.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + StringValuePattern pattern = + Json.read(serveEvent.getRequest().getBodyAsString(), StringValuePattern.class); ListStubMappingsResult stubMappings = admin.findAllStubsByMetadata(pattern); return ResponseDefinition.okForJson(stubMappings); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/GetAllScenariosTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/GetAllScenariosTask.java index f0f87ab341..029a81cbb5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/GetAllScenariosTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/GetAllScenariosTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,15 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetAllScenariosTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { return ResponseDefinition.okForJson(admin.getAllScenarios()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/GetGlobalSettingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/GetGlobalSettingsTask.java index 69f41d3242..9910f25fa2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/GetGlobalSettingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/GetGlobalSettingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,15 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetGlobalSettingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { return ResponseDefinition.okForJson(admin.getGlobalSettings()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/GetRecordingStatusTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/GetRecordingStatusTask.java index ccf5e6650a..67b4216877 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/GetRecordingStatusTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/GetRecordingStatusTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,15 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetRecordingStatusTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { return ResponseDefinition.okForJson(admin.getRecordingStatus()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/ImportStubMappingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/ImportStubMappingsTask.java index d4644662b0..1dc45e47a6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/ImportStubMappingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/ImportStubMappingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,18 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubImport; public class ImportStubMappingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StubImport stubImport = Json.read(request.getBodyAsString(), StubImport.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + StubImport stubImport = Json.read(serveEvent.getRequest().getBodyAsString(), StubImport.class); admin.importStubs(stubImport); return ResponseDefinition.ok(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginator.java b/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginator.java index 50fdef54ab..431b4b1af5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package com.github.tomakehurst.wiremock.admin; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.base.Preconditions.checkArgument; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkParameter; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.tomakehurst.wiremock.http.Request; import java.util.List; @@ -29,14 +29,14 @@ public class LimitAndOffsetPaginator implements Paginator { public LimitAndOffsetPaginator(List source, Integer limit, Integer offset) { this.source = source; - checkArgument(limit == null || limit >= 0, "limit must be 0 or greater"); - checkArgument(offset == null || offset >= 0, "offset must be 0 or greater"); + checkParameter(limit == null || limit >= 0, "limit must be 0 or greater"); + checkParameter(offset == null || offset >= 0, "offset must be 0 or greater"); this.limit = limit; this.offset = offset; } public static LimitAndOffsetPaginator fromRequest(List source, Request request) { - return new LimitAndOffsetPaginator( + return new LimitAndOffsetPaginator<>( source, Conversions.toInt(request.queryParameter("limit")), Conversions.toInt(request.queryParameter("offset"))); @@ -44,8 +44,8 @@ public static LimitAndOffsetPaginator fromRequest(List source, Request @Override public List select() { - int start = firstNonNull(offset, 0); - int end = Math.min(source.size(), start + firstNonNull(limit, source.size())); + int start = getFirstNonNull(offset, 0); + int end = Math.min(source.size(), start + getFirstNonNull(limit, source.size())); return source.subList(start, end); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndSinceDatePaginator.java b/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndSinceDatePaginator.java index 5488ec9546..7e2a70f68b 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndSinceDatePaginator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/LimitAndSinceDatePaginator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,14 @@ import static com.github.tomakehurst.wiremock.admin.Conversions.toDate; import static com.github.tomakehurst.wiremock.admin.Conversions.toInt; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.base.Preconditions.checkArgument; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkParameter; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; public class LimitAndSinceDatePaginator implements Paginator { @@ -34,7 +33,7 @@ public class LimitAndSinceDatePaginator implements Paginator { private final Date since; public LimitAndSinceDatePaginator(List source, Integer limit, Date since) { - checkArgument(limit == null || limit >= 0, "limit must be 0 or greater"); + checkParameter(limit == null || limit >= 0, "limit must be 0 or greater"); this.source = source; this.limit = limit; this.since = since; @@ -47,17 +46,10 @@ public static LimitAndSinceDatePaginator fromRequest(List source, Re @Override public List select() { - FluentIterable chain = FluentIterable.from(source); - return chain - .filter( - new Predicate() { - @Override - public boolean apply(ServeEvent input) { - return since == null || input.getRequest().getLoggedDate().after(since); - } - }) - .limit(firstNonNull(limit, source.size())) - .toList(); + return source.stream() + .filter(input -> since == null || input.getRequest().getLoggedDate().after(since)) + .limit(getFirstNonNull(limit, source.size())) + .collect(Collectors.toList()); } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/PatchExtendedSettingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/PatchExtendedSettingsTask.java index e72ed107e9..59cd791bd2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/PatchExtendedSettingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/PatchExtendedSettingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,20 @@ package com.github.tomakehurst.wiremock.admin; import com.github.tomakehurst.wiremock.admin.model.ExtendedSettingsWrapper; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.global.GlobalSettings; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class PatchExtendedSettingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { ExtendedSettingsWrapper extendedSettingsWrapper = - Json.read(request.getBodyAsString(), ExtendedSettingsWrapper.class); + Json.read(serveEvent.getRequest().getBodyAsString(), ExtendedSettingsWrapper.class); Parameters newExtended = extendedSettingsWrapper.getExtended(); GlobalSettings existingSettings = admin.getGlobalSettings().getSettings(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventTask.java index c99290167e..ad26c25b13 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,16 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.admin.tasks.AbstractSingleServeEventTask; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.UUID; -public class RemoveServeEventTask implements AdminTask { +public class RemoveServeEventTask extends AbstractSingleServeEventTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - String idString = pathParams.get("id"); - UUID id = UUID.fromString(idString); + protected ResponseDefinition processServeEvent(Admin admin, ServeEvent adminServeEvent, UUID id) { admin.removeServeEvent(id); return ResponseDefinition.okEmptyJson(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByRequestPatternTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByRequestPatternTask.java index 2f5fd894a3..6c36b7876a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByRequestPatternTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByRequestPatternTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,20 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindServeEventsResult; public class RemoveServeEventsByRequestPatternTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - RequestPattern requestPattern = Json.read(request.getBodyAsString(), RequestPattern.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + RequestPattern requestPattern = + Json.read(serveEvent.getRequest().getBodyAsString(), RequestPattern.class); FindServeEventsResult findServeEventsResult = admin.removeServeEventsMatching(requestPattern); return ResponseDefinition.okForJson(findServeEventsResult); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByStubMetadataTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByStubMetadataTask.java index ad16c2d512..e51ad772b2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByStubMetadataTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveServeEventsByStubMetadataTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,20 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindServeEventsResult; public class RemoveServeEventsByStubMetadataTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { StringValuePattern metadataPattern = - Json.read(request.getBodyAsString(), StringValuePattern.class); + Json.read(serveEvent.getRequest().getBodyAsString(), StringValuePattern.class); FindServeEventsResult findServeEventsResult = admin.removeServeEventsForStubsMatchingMetadata(metadataPattern); return ResponseDefinition.okForJson(findServeEventsResult); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveStubMappingsByMetadataTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveStubMappingsByMetadataTask.java index 3e72cb5a8d..f606a1c5b0 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveStubMappingsByMetadataTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/RemoveStubMappingsByMetadataTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,19 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class RemoveStubMappingsByMetadataTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StringValuePattern pattern = Json.read(request.getBodyAsString(), StringValuePattern.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + StringValuePattern pattern = + Json.read(serveEvent.getRequest().getBodyAsString(), StringValuePattern.class); admin.removeStubsByMetadata(pattern); return ResponseDefinition.okEmptyJson(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/RequestSpec.java b/src/main/java/com/github/tomakehurst/wiremock/admin/RequestSpec.java index a2cdcc8d7d..ef1a40fdf2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/RequestSpec.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/RequestSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,22 @@ package com.github.tomakehurst.wiremock.admin; import static com.github.tomakehurst.wiremock.http.RequestMethod.ANY; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; import com.github.tomakehurst.wiremock.http.RequestMethod; public class RequestSpec { private final RequestMethod method; - private final AdminUriTemplate uriTemplate; + private final PathTemplate uriTemplate; public RequestSpec(RequestMethod method, String uriTemplate) { - checkNotNull(method); - checkNotNull(uriTemplate); + requireNonNull(method); + requireNonNull(uriTemplate); this.method = method; - this.uriTemplate = new AdminUriTemplate(uriTemplate); + this.uriTemplate = new PathTemplate(uriTemplate); } public static RequestSpec requestSpec(RequestMethod method, String path) { @@ -41,7 +42,7 @@ public RequestMethod method() { return method; } - public AdminUriTemplate getUriTemplate() { + public PathTemplate getUriTemplate() { return uriTemplate; } @@ -65,9 +66,7 @@ public boolean equals(Object o) { RequestSpec that = (RequestSpec) o; if (!method.equals(that.method)) return false; - if (!uriTemplate.equals(that.uriTemplate)) return false; - - return true; + return uriTemplate.equals(that.uriTemplate); } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/SetScenarioStateTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/SetScenarioStateTask.java index 93321f3958..33dc2d842e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/SetScenarioStateTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/SetScenarioStateTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,21 @@ */ package com.github.tomakehurst.wiremock.admin; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.admin.model.ScenarioState; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class SetScenarioStateTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { String name = pathParams.get("name"); - String body = request.getBodyAsString(); + String body = serveEvent.getRequest().getBodyAsString(); try { setOrResetScenarioState(admin, name, body); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/StartRecordingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/StartRecordingTask.java index 9a1d1b4afc..09a9ae7739 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/StartRecordingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/StartRecordingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,19 +17,19 @@ import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.jsonResponse; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.InvalidInputException; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.recording.RecordSpec; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class StartRecordingTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - RecordSpec recordSpec = Json.read(request.getBodyAsString(), RecordSpec.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + RecordSpec recordSpec = Json.read(serveEvent.getRequest().getBodyAsString(), RecordSpec.class); try { admin.startRecording(recordSpec); return ResponseDefinition.okEmptyJson(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/StopRecordingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/StopRecordingTask.java index f68a952d17..7032d4c052 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/StopRecordingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/StopRecordingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,18 +19,18 @@ import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_OK; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Errors; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.recording.NotRecordingException; import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class StopRecordingTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { try { SnapshotRecordResult result = admin.stopRecording(); return jsonResponse(result, HTTP_OK); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/model/HealthCheckResult.java b/src/main/java/com/github/tomakehurst/wiremock/admin/model/HealthCheckResult.java new file mode 100644 index 0000000000..445734f303 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/model/HealthCheckResult.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.admin.model; + +import java.lang.management.ManagementFactory; +import java.time.Instant; + +public class HealthCheckResult { + private final String status; + private final String message; + private final long uptimeInSeconds; + private final Instant timestamp; + + public HealthCheckResult(String status, String message) { + this.status = status; + this.message = message; + this.timestamp = Instant.now(); + this.uptimeInSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000; + } + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public Instant getTimestamp() { + return timestamp; + } + + public long getUptimeInSeconds() { + return uptimeInSeconds; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleServedStubResult.java b/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleServedStubResult.java index 9ea593973f..69ad43af97 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleServedStubResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleServedStubResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Optional; +import java.util.Optional; public class SingleServedStubResult extends SingleItemResult { @@ -27,6 +27,6 @@ public SingleServedStubResult(ServeEvent item) { } public static SingleServedStubResult fromOptional(Optional servedStub) { - return new SingleServedStubResult(servedStub.orNull()); + return new SingleServedStubResult(servedStub.orElse(null)); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java b/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java index d0f86e3b3e..3a2c499bca 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java @@ -17,7 +17,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Optional; +import java.util.Optional; public class SingleStubMappingResult extends SingleItemResult { @@ -27,6 +27,6 @@ public SingleStubMappingResult(StubMapping item) { } public static SingleStubMappingResult fromOptional(Optional optional) { - return new SingleStubMappingResult(optional.orNull()); + return new SingleStubMappingResult(optional.orElse(null)); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractGetDocTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractGetDocTask.java index 281e8fd555..caa437352d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractGetDocTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractGetDocTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,23 @@ package com.github.tomakehurst.wiremock.admin.tasks; import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.responseDefinition; -import static com.google.common.io.ByteStreams.toByteArray; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.google.common.io.Resources; import java.io.IOException; +import java.io.InputStream; public abstract class AbstractGetDocTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - try { - byte[] content = toByteArray(Resources.getResource(getFilePath()).openStream()); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + try (InputStream inputStream = Resources.getResource(getFilePath()).openStream()) { + byte[] content = inputStream.readAllBytes(); return responseDefinition() .withStatus(200) .withBody(content) diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleServeEventTask.java similarity index 50% rename from src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveStubMappingTask.java rename to src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleServeEventTask.java index c71cae76ff..826fec7eed 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveStubMappingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleServeEventTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,29 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; +import com.github.tomakehurst.wiremock.common.Errors; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.UUID; -public class RemoveStubMappingTask implements AdminTask { +public abstract class AbstractSingleServeEventTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - SingleStubMappingResult stubMappingResult = - admin.getStubMapping(UUID.fromString(pathParams.get("id"))); - - if (!stubMappingResult.isPresent()) { - return ResponseDefinition.notFound(); + public ResponseDefinition execute( + Admin admin, ServeEvent adminServeEvent, PathParams pathParams) { + String idString = pathParams.get("id"); + UUID id; + try { + id = UUID.fromString(idString); + } catch (IllegalArgumentException e) { + return ResponseDefinition.badRequest(Errors.single(10, idString + " is not a valid UUID")); } - admin.removeStubMapping(stubMappingResult.getItem()); - return ResponseDefinition.okEmptyJson(); + return processServeEvent(admin, adminServeEvent, id); } + + protected abstract ResponseDefinition processServeEvent( + Admin admin, ServeEvent adminServeEvent, UUID id); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleStubTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleStubTask.java new file mode 100644 index 0000000000..60d32aa23e --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/AbstractSingleStubTask.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.admin.tasks; + +import com.github.tomakehurst.wiremock.admin.AdminTask; +import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; +import com.github.tomakehurst.wiremock.common.Errors; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.util.UUID; + +public abstract class AbstractSingleStubTask implements AdminTask { + + @Override + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + String idString = pathParams.get("id"); + UUID id; + try { + id = UUID.fromString(idString); + } catch (IllegalArgumentException e) { + return ResponseDefinition.badRequest(Errors.single(10, idString + " is not a valid UUID")); + } + + final SingleStubMappingResult result = admin.getStubMapping(id); + return result.isPresent() + ? processStubMapping(admin, serveEvent, result.getItem()) + : ResponseDefinition.notFound(); + } + + protected abstract ResponseDefinition processStubMapping( + Admin admin, ServeEvent serveEvent, StubMapping stubMapping); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/CreateStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/CreateStubMappingTask.java index a54f142589..a23e1593ca 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/CreateStubMappingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/CreateStubMappingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,18 @@ import static java.net.HttpURLConnection.HTTP_CREATED; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; public class CreateStubMappingTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StubMapping newMapping = StubMapping.buildFrom(request.getBodyAsString()); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + StubMapping newMapping = StubMapping.buildFrom(serveEvent.getRequest().getBodyAsString()); admin.addStubMapping(newMapping); return ResponseDefinitionBuilder.jsonResponse(newMapping, HTTP_CREATED); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DeleteStubFileTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DeleteStubFileTask.java index dd0d2a92d0..5afe9b0a53 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DeleteStubFileTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DeleteStubFileTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,25 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; -import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; - import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import java.io.File; +import com.github.tomakehurst.wiremock.store.Stores; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class DeleteStubFileTask implements AdminTask { + + private final Stores stores; + + public DeleteStubFileTask(Stores stores) { + this.stores = stores; + } + @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - FileSource fileSource = admin.getOptions().filesRoot().child(FILES_ROOT); - File filename = new File(fileSource.getPath(), pathParams.get("0")); - boolean deleted = filename.delete(); - if (deleted) { - return ResponseDefinition.ok(); - } else { - return new ResponseDefinition(HTTP_INTERNAL_ERROR, "File not deleted"); - } + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + String filePath = pathParams.get("0"); + stores.getFilesBlobStore().remove(filePath); + return ResponseDefinition.ok(); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubFileTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubFileTask.java index c43670b7ff..39865b33da 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubFileTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubFileTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,28 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; - import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.store.Stores; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class EditStubFileTask implements AdminTask { + + private final Stores stores; + + public EditStubFileTask(Stores stores) { + this.stores = stores; + } + @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - byte[] fileContent = request.getBody(); - FileSource fileSource = admin.getOptions().filesRoot().child(FILES_ROOT); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { String filename = pathParams.get("0"); - fileSource.writeBinaryFile(filename, fileContent); + byte[] fileContent = serveEvent.getRequest().getBody(); + + stores.getFilesBlobStore().put(filename, fileContent); + return ResponseDefinition.okForJson(fileContent); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubMappingTask.java index 00b9026e96..63c6c7b919 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubMappingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EditStubMappingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,18 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import java.util.UUID; -public class EditStubMappingTask implements AdminTask { +public class EditStubMappingTask extends AbstractSingleStubTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StubMapping newStubMapping = StubMapping.buildFrom(request.getBodyAsString()); - UUID id = UUID.fromString(pathParams.get("id")); - SingleStubMappingResult stubMappingResult = admin.getStubMapping(id); - if (!stubMappingResult.isPresent()) { - return ResponseDefinition.notFound(); - } - - newStubMapping.setId(id); - + protected ResponseDefinition processStubMapping( + Admin admin, ServeEvent serveEvent, StubMapping stubMapping) { + StubMapping newStubMapping = StubMapping.buildFrom(serveEvent.getRequest().getBodyAsString()); + newStubMapping.setId(stubMapping.getId()); admin.editStubMapping(newStubMapping); return ResponseDefinition.okForJson(newStubMapping); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestPatternTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestPatternTask.java index 83bbd032d4..a152956d5a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestPatternTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestPatternTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,20 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindNearMissesResult; public class FindNearMissesForRequestPatternTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - RequestPattern requestPattern = Json.read(request.getBodyAsString(), RequestPattern.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + RequestPattern requestPattern = + Json.read(serveEvent.getRequest().getBodyAsString(), RequestPattern.class); FindNearMissesResult nearMissesResult = admin.findTopNearMissesFor(requestPattern); return ResponseDefinition.okForJson(nearMissesResult); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestTask.java index a9ec152717..b17fd6dbc3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForRequestTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,20 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindNearMissesResult; import com.github.tomakehurst.wiremock.verification.LoggedRequest; public class FindNearMissesForRequestTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - LoggedRequest loggedRequest = Json.read(request.getBodyAsString(), LoggedRequest.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + LoggedRequest loggedRequest = + Json.read(serveEvent.getRequest().getBodyAsString(), LoggedRequest.class); FindNearMissesResult nearMissesResult = admin.findTopNearMissesFor(loggedRequest); return ResponseDefinition.okForJson(nearMissesResult); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForUnmatchedTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForUnmatchedTask.java index 1385ec97a5..958a3f2f98 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForUnmatchedTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindNearMissesForUnmatchedTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,16 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindNearMissesResult; public class FindNearMissesForUnmatchedTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { FindNearMissesResult nearMissesResult = admin.findNearMissesForUnmatchedRequests(); return ResponseDefinition.okForJson(nearMissesResult); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindRequestsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindRequestsTask.java index 5b9c0e93e0..9865382ba2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindRequestsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindRequestsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,19 +19,20 @@ import static java.net.HttpURLConnection.HTTP_OK; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindRequestsResult; public class FindRequestsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - RequestPattern requestPattern = Json.read(request.getBodyAsString(), RequestPattern.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + RequestPattern requestPattern = + Json.read(serveEvent.getRequest().getBodyAsString(), RequestPattern.class); FindRequestsResult result = admin.findRequestsMatching(requestPattern); return responseDefinition() diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindUnmatchedRequestsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindUnmatchedRequestsTask.java index 7442577d45..e6cbad3c60 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindUnmatchedRequestsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/FindUnmatchedRequestsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,17 +19,17 @@ import static java.net.HttpURLConnection.HTTP_OK; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.FindRequestsResult; public class FindUnmatchedRequestsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { FindRequestsResult unmatchedRequests = admin.findUnmatchedRequests(); return responseDefinition() .withStatus(HTTP_OK) diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllRequestsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllRequestsTask.java index 451809129f..ff65d3a337 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllRequestsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllRequestsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,23 +23,25 @@ import com.github.tomakehurst.wiremock.admin.AdminTask; import com.github.tomakehurst.wiremock.admin.LimitAndSinceDatePaginator; import com.github.tomakehurst.wiremock.admin.model.GetServeEventsResult; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.admin.model.ServeEventQuery; import com.github.tomakehurst.wiremock.common.InvalidInputException; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetAllRequestsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - ServeEventQuery query = ServeEventQuery.fromRequest(request); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + ServeEventQuery query = ServeEventQuery.fromRequest(serveEvent.getRequest()); GetServeEventsResult serveEventsResult = admin.getServeEvents(query); LimitAndSinceDatePaginator paginator; try { - paginator = LimitAndSinceDatePaginator.fromRequest(serveEventsResult.getRequests(), request); + paginator = + LimitAndSinceDatePaginator.fromRequest( + serveEventsResult.getRequests(), serveEvent.getRequest()); } catch (InvalidInputException e) { return jsonResponse(e.getErrors(), HTTP_BAD_REQUEST); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubFilesTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubFilesTask.java index 8eacc46fba..887deedf25 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubFilesTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubFilesTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,29 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; +import static java.util.stream.Collectors.toList; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.common.FileSource; -import com.github.tomakehurst.wiremock.common.TextFile; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import java.util.ArrayList; -import java.util.Collections; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.Stores; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.List; public class GetAllStubFilesTask implements AdminTask { + + private final Stores stores; + + public GetAllStubFilesTask(Stores stores) { + this.stores = stores; + } + @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - FileSource fileSource = admin.getOptions().filesRoot().child(FILES_ROOT); - List filePaths = new ArrayList<>(); - for (TextFile textFile : fileSource.listFilesRecursively()) { - filePaths.add(textFile.getPath()); - } - Collections.sort(filePaths); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + BlobStore filesBlobStore = stores.getFilesBlobStore(); + List filePaths = filesBlobStore.getAllKeys().sorted().collect(toList()); return ResponseDefinition.okForJson(filePaths); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubMappingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubMappingsTask.java index cf865b4124..cfc4b00eee 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubMappingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetAllStubMappingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,20 +18,20 @@ import com.github.tomakehurst.wiremock.admin.AdminTask; import com.github.tomakehurst.wiremock.admin.LimitAndOffsetPaginator; import com.github.tomakehurst.wiremock.admin.model.ListStubMappingsResult; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetAllStubMappingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { ListStubMappingsResult result = new ListStubMappingsResult( LimitAndOffsetPaginator.fromRequest( - admin.listAllStubMappings().getMappings(), request)); + admin.listAllStubMappings().getMappings(), serveEvent.getRequest())); return ResponseDefinitionBuilder.jsonResponse(result); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetCaCertTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetCaCertTask.java index 1d7e67066c..51079d1823 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetCaCertTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetCaCertTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,14 @@ import static java.net.HttpURLConnection.HTTP_OK; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.BrowserProxySettings; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.http.ssl.X509KeyStore; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.security.cert.X509Certificate; import java.util.Base64; @@ -36,7 +36,7 @@ public class GetCaCertTask implements AdminTask { Base64.getMimeEncoder(64, new byte[] {'\r', '\n'}); @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { BrowserProxySettings browserProxySettings = admin.getOptions().browserProxySettings(); KeyStoreSettings caKeyStore = browserProxySettings.caKeyStore(); try { diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetRequestCountTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetRequestCountTask.java index 8ed3ea6bc0..c2a9a1dc98 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetRequestCountTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetRequestCountTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,19 +20,20 @@ import static java.net.HttpURLConnection.HTTP_OK; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.VerificationResult; public class GetRequestCountTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - RequestPattern requestPattern = Json.read(request.getBodyAsString(), RequestPattern.class); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + RequestPattern requestPattern = + Json.read(serveEvent.getRequest().getBodyAsString(), RequestPattern.class); VerificationResult result = admin.countRequestsMatching(requestPattern); return responseDefinition() diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetServedStubTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetServedStubTask.java index 7c57cd420a..a66cb816cf 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetServedStubTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetServedStubTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,17 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.admin.model.SingleServedStubResult; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.UUID; -public class GetServedStubTask implements AdminTask { +public class GetServedStubTask extends AbstractSingleServeEventTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - String idString = pathParams.get("id"); - UUID id = UUID.fromString(idString); - - SingleServedStubResult result = admin.getServedStub(id); + protected ResponseDefinition processServeEvent(Admin admin, ServeEvent adminServeEvent, UUID id) { + final SingleServedStubResult result = admin.getServedStub(id); return result.isPresent() ? ResponseDefinition.okForJson(result.getItem()) : ResponseDefinition.notFound(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubMappingTask.java index 5fc51dc454..8367b25f26 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubMappingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubMappingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,24 +15,16 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import java.util.UUID; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; -public class GetStubMappingTask implements AdminTask { +public class GetStubMappingTask extends AbstractSingleStubTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - String idString = pathParams.get("id"); - UUID id = UUID.fromString(idString); - - SingleStubMappingResult stubMappingResult = admin.getStubMapping(id); - return stubMappingResult.isPresent() - ? ResponseDefinition.okForJson(stubMappingResult.getItem()) - : ResponseDefinition.notFound(); + protected ResponseDefinition processStubMapping( + Admin admin, ServeEvent serveEvent, StubMapping stubMapping) { + return ResponseDefinition.okForJson(stubMapping); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GlobalSettingsUpdateTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GlobalSettingsUpdateTask.java index fa3fe6681c..76c8e20ac1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GlobalSettingsUpdateTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GlobalSettingsUpdateTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,24 @@ import com.github.tomakehurst.wiremock.admin.AdminTask; import com.github.tomakehurst.wiremock.admin.model.GetGlobalSettingsResult; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.global.GlobalSettings; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GlobalSettingsUpdateTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { GlobalSettings newSettings; try { - newSettings = Json.read(request.getBodyAsString(), GlobalSettings.class); + newSettings = Json.read(serveEvent.getRequest().getBodyAsString(), GlobalSettings.class); } catch (Exception e) { newSettings = - Json.read(request.getBodyAsString(), GetGlobalSettingsResult.class).getSettings(); + Json.read(serveEvent.getRequest().getBodyAsString(), GetGlobalSettingsResult.class) + .getSettings(); } admin.updateGlobalSettings(newSettings); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTask.java new file mode 100644 index 0000000000..647f7d11c8 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTask.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.admin.tasks; + +import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.responseDefinition; +import static java.net.HttpURLConnection.HTTP_OK; + +import com.github.tomakehurst.wiremock.admin.AdminTask; +import com.github.tomakehurst.wiremock.admin.model.HealthCheckResult; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public class HealthCheckTask implements AdminTask { + @Override + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + + return responseDefinition() + .withStatus(HTTP_OK) + .withStatusMessage("Wiremock is ok") + .withBody( + Json.write( + new HealthCheckResult( + HealthCheckStatus.HEALTHY.name().toLowerCase(), "Wiremock is ok"))) + .withHeader("Content-Type", "application/json") + .build(); + } + + protected enum HealthCheckStatus { + HEALTHY, + UNHEALTHY + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/NotFoundAdminTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/NotFoundAdminTask.java index 2c73b53759..37dd2facc6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/NotFoundAdminTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/NotFoundAdminTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class NotFoundAdminTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { return ResponseDefinition.notFound(); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldCreateStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldCreateStubMappingTask.java deleted file mode 100644 index 09df4af23f..0000000000 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldCreateStubMappingTask.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2013-2021 Thomas Akehurst - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.github.tomakehurst.wiremock.admin.tasks; - -import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; -import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; - -public class OldCreateStubMappingTask implements AdminTask { - - @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StubMapping newMapping = StubMapping.buildFrom(request.getBodyAsString()); - admin.addStubMapping(newMapping); - return ResponseDefinition.created(); - } -} diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldEditStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldEditStubMappingTask.java index aaacd1dd11..7421df26ba 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldEditStubMappingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldEditStubMappingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,17 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; public class OldEditStubMappingTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StubMapping stubMapping = StubMapping.buildFrom(request.getBodyAsString()); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + StubMapping stubMapping = StubMapping.buildFrom(serveEvent.getRequest().getBodyAsString()); admin.editStubMapping(stubMapping); return ResponseDefinition.noContent(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldRemoveStubMappingTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveMatchingStubMappingTask.java similarity index 68% rename from src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldRemoveStubMappingTask.java rename to src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveMatchingStubMappingTask.java index d119768791..82dcc67e13 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldRemoveStubMappingTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveMatchingStubMappingTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,17 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -public class OldRemoveStubMappingTask implements AdminTask { +public class RemoveMatchingStubMappingTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - StubMapping removeMapping = StubMapping.buildFrom(request.getBodyAsString()); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + StubMapping removeMapping = StubMapping.buildFrom(serveEvent.getRequest().getBodyAsString()); admin.removeStubMapping(removeMapping); return ResponseDefinition.ok(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldResetRequestsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveStubMappingByIdTask.java similarity index 62% rename from src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldResetRequestsTask.java rename to src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveStubMappingByIdTask.java index f2f924fdf2..5e9407a0b1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/OldResetRequestsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RemoveStubMappingByIdTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,17 @@ */ package com.github.tomakehurst.wiremock.admin.tasks; -import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; -public class OldResetRequestsTask implements AdminTask { +public class RemoveStubMappingByIdTask extends AbstractSingleStubTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - admin.resetRequests(); - return ResponseDefinition.ok(); + protected ResponseDefinition processStubMapping( + Admin admin, ServeEvent serveEvent, StubMapping stubMapping) { + admin.removeStubMapping(stubMapping); + return ResponseDefinition.okEmptyJson(); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetRequestsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetRequestsTask.java index 93bf9ad699..e98e52aaba 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetRequestsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetRequestsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class ResetRequestsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.resetRequests(); return ResponseDefinition.okEmptyJson(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetScenariosTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetScenariosTask.java index fcc13363a7..f9b52a891f 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetScenariosTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetScenariosTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class ResetScenariosTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.resetScenarios(); return ResponseDefinition.okEmptyJson(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetStubMappingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetStubMappingsTask.java index 0f73c405d9..54641d2915 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetStubMappingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetStubMappingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class ResetStubMappingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.resetMappings(); return ResponseDefinition.okEmptyJson(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetTask.java index 611e347bca..343c8a46dd 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class ResetTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.resetAll(); return ResponseDefinition.ok(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetToDefaultMappingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetToDefaultMappingsTask.java index 5fba07f76a..659a37ea33 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetToDefaultMappingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ResetToDefaultMappingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class ResetToDefaultMappingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.resetToDefaultMappings(); return ResponseDefinition.ok(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RootRedirectTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RootRedirectTask.java index f65143ef4e..df3e9110ae 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RootRedirectTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/RootRedirectTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,15 @@ import static com.github.tomakehurst.wiremock.core.WireMockApp.ADMIN_CONTEXT_ROOT; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class RootRedirectTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { return ResponseDefinition.redirectTo(ADMIN_CONTEXT_ROOT + "/"); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SaveMappingsTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SaveMappingsTask.java index 4e10f66656..29b501eaa7 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SaveMappingsTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SaveMappingsTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class SaveMappingsTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.saveMappings(); return ResponseDefinition.ok(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ShutdownServerTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ShutdownServerTask.java index ab61d1a793..498c65027e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ShutdownServerTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/ShutdownServerTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class ShutdownServerTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { admin.shutdownServer(); return ResponseDefinition.ok(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SnapshotTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SnapshotTask.java index 7dbc2cad82..1fd36c4563 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SnapshotTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/SnapshotTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,22 @@ import static java.net.HttpURLConnection.HTTP_OK; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.recording.RecordSpec; import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class SnapshotTask implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { RecordSpec recordSpec = - request.getBody().length == 0 + serveEvent.getRequest().getBody().length == 0 ? RecordSpec.DEFAULTS - : Json.read(request.getBodyAsString(), RecordSpec.class); + : Json.read(serveEvent.getRequest().getBodyAsString(), RecordSpec.class); SnapshotRecordResult result = admin.snapshotRecord(recordSpec); return jsonResponse(result, HTTP_OK); diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/BasicMappingBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/client/BasicMappingBuilder.java index ee07bb1099..2fdd9505f0 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/BasicMappingBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/BasicMappingBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,35 @@ package com.github.tomakehurst.wiremock.client; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Lists.newArrayList; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkParameter; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.tomakehurst.wiremock.common.Metadata; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition; +import com.github.tomakehurst.wiremock.extension.ServeEventListener; +import com.github.tomakehurst.wiremock.extension.ServeEventListenerDefinition; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.github.tomakehurst.wiremock.matching.*; +import com.github.tomakehurst.wiremock.matching.ContentPattern; +import com.github.tomakehurst.wiremock.matching.MultiValuePattern; +import com.github.tomakehurst.wiremock.matching.MultipartValuePatternBuilder; +import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.matching.UrlPattern; +import com.github.tomakehurst.wiremock.matching.ValueMatcher; import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; class BasicMappingBuilder implements ScenarioMappingBuilder { - private RequestPatternBuilder requestPatternBuilder; + private final RequestPatternBuilder requestPatternBuilder; private ResponseDefinitionBuilder responseDefBuilder; private Integer priority; private String scenarioName; @@ -43,7 +53,8 @@ class BasicMappingBuilder implements ScenarioMappingBuilder { private UUID id = UUID.randomUUID(); private String name; private Boolean isPersistent = null; - private List postServeActions = newArrayList(); + private List postServeActions = new ArrayList<>(); + private List serveEventListeners = new ArrayList<>(); private Metadata metadata; BasicMappingBuilder(RequestMethod method, UrlPattern urlPattern) { @@ -94,6 +105,12 @@ public BasicMappingBuilder withHeader(String key, StringValuePattern headerPatte return this; } + @Override + public BasicMappingBuilder withHeader(String key, MultiValuePattern headerPattern) { + requestPatternBuilder.withHeader(key, headerPattern); + return this; + } + @Override public BasicMappingBuilder withCookie(String name, StringValuePattern cookieValuePattern) { requestPatternBuilder.withCookie(name, cookieValuePattern); @@ -106,10 +123,35 @@ public BasicMappingBuilder withQueryParam(String key, StringValuePattern queryPa return this; } + @Override + public BasicMappingBuilder withQueryParam(String key, MultiValuePattern queryParamPattern) { + requestPatternBuilder.withQueryParam(key, queryParamPattern); + return this; + } + + @Override + public ScenarioMappingBuilder withFormParam(String key, StringValuePattern formParamPattern) { + requestPatternBuilder.withFormParam(key, formParamPattern); + return this; + } + + @Override + public ScenarioMappingBuilder withFormParam(String key, MultiValuePattern formParamPattern) { + requestPatternBuilder.withFormParam(key, formParamPattern); + return this; + } + + @Override + public BasicMappingBuilder withPathParam(String key, StringValuePattern pathParamPattern) { + requestPatternBuilder.withPathParam(key, pathParamPattern); + return this; + } + @Override public BasicMappingBuilder withQueryParams(Map queryParams) { - for (Map.Entry queryParam : queryParams.entrySet()) + for (Map.Entry queryParam : queryParams.entrySet()) { requestPatternBuilder.withQueryParam(queryParam.getKey(), queryParam.getValue()); + } return this; } @@ -128,7 +170,7 @@ public BasicMappingBuilder withMultipartRequestBody( @Override public BasicMappingBuilder inScenario(String scenarioName) { - checkArgument(scenarioName != null, "Scenario name must not be null"); + checkParameter(scenarioName != null, "Scenario name must not be null"); this.scenarioName = scenarioName; return this; @@ -178,12 +220,31 @@ public BasicMappingBuilder withBasicAuth(String username, String password) { @Override public

BasicMappingBuilder withPostServeAction(String extensionName, P parameters) { - Parameters params = - parameters instanceof Parameters ? (Parameters) parameters : Parameters.of(parameters); - postServeActions.add(new PostServeActionDefinition(extensionName, params)); + postServeActions.add( + new PostServeActionDefinition(extensionName, resolveParameters(parameters))); + return this; + } + + @Override + public

MappingBuilder withServeEventListener( + Set requestPhases, String extensionName, P parameters) { + serveEventListeners.add( + new ServeEventListenerDefinition( + extensionName, requestPhases, resolveParameters(parameters))); + return this; + } + + @Override + public

MappingBuilder withServeEventListener(String extensionName, P parameters) { + serveEventListeners.add( + new ServeEventListenerDefinition(extensionName, resolveParameters(parameters))); return this; } + private static

Parameters resolveParameters(P parameters) { + return parameters instanceof Parameters ? (Parameters) parameters : Parameters.of(parameters); + } + @Override public BasicMappingBuilder withMetadata(Map metadataMap) { this.metadata = new Metadata(metadataMap); @@ -227,7 +288,7 @@ public StubMapping build() { "Scenario name must be specified to require or set a new scenario state"); } RequestPattern requestPattern = requestPatternBuilder.build(); - ResponseDefinition response = firstNonNull(responseDefBuilder, aResponse()).build(); + ResponseDefinition response = getFirstNonNull(responseDefBuilder, aResponse()).build(); StubMapping mapping = new StubMapping(requestPattern, response); mapping.setPriority(priority); mapping.setScenarioName(scenarioName); @@ -238,6 +299,8 @@ public StubMapping build() { mapping.setPersistent(isPersistent); mapping.setPostServeActions(postServeActions.isEmpty() ? null : postServeActions); + mapping.setServeEventListenerDefinitions( + serveEventListeners.isEmpty() ? null : serveEventListeners); mapping.setMetadata(metadata); diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java b/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java index 300b2280fa..d910a89d36 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,15 @@ import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsStringAndCloseStream; import static com.github.tomakehurst.wiremock.security.NoClientAuthenticator.noClientAuthenticator; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static org.apache.hc.core5.http.HttpHeaders.HOST; import com.github.tomakehurst.wiremock.admin.*; import com.github.tomakehurst.wiremock.admin.model.*; import com.github.tomakehurst.wiremock.admin.tasks.*; import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.common.url.QueryParams; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; @@ -49,6 +51,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ClassicHttpRequest; @@ -114,7 +117,7 @@ public HttpAdminClient( this.hostHeader = hostHeader; this.authenticator = authenticator; - this.adminRoutes = AdminRoutes.defaults(); + this.adminRoutes = AdminRoutes.forClient(); this.httpClient = HttpClientFactory.createClient(this.createProxySettings(proxyHost, proxyPort)); } @@ -142,19 +145,22 @@ public void addStubMapping(final StubMapping stubMapping) { } @Override - public void editStubMapping(final StubMapping stubMapping) { - this.postJsonAssertOkAndReturnBody(this.urlFor(OldEditStubMappingTask.class), Json.write(stubMapping)); + public void editStubMapping(StubMapping stubMapping) { + putJsonAssertOkAndReturnBody( + urlFor(EditStubMappingTask.class, PathParams.single("id", stubMapping.getId().toString())), + Json.write(stubMapping)); } @Override - public void removeStubMapping(final StubMapping stubbMapping) { - this.postJsonAssertOkAndReturnBody(this.urlFor(OldRemoveStubMappingTask.class), Json.write(stubbMapping)); + public void removeStubMapping(StubMapping stubbMapping) { + this.postJsonAssertOkAndReturnBody( + this.urlFor(RemoveMatchingStubMappingTask.class), Json.write(stubbMapping)); } @Override public void removeStubMapping(UUID id) { executeRequest( - adminRoutes.requestSpecForTask(RemoveStubMappingTask.class), + adminRoutes.requestSpecForTask(RemoveStubMappingByIdTask.class), PathParams.single("id", id), Void.class); } @@ -166,8 +172,7 @@ public ListStubMappingsResult listAllStubMappings() { } @Override - @SuppressWarnings("unchecked") - public SingleStubMappingResult getStubMapping(final UUID id) { + public SingleStubMappingResult getStubMapping(UUID id) { return this.executeRequest( this.adminRoutes.requestSpecForTask(GetStubMappingTask.class), PathParams.single("id", id), @@ -456,6 +461,15 @@ private String postJsonAssertOkAndReturnBody(final String url, final String json post.setEntity(HttpAdminClient.jsonStringEntity(json)); } + return safelyExecuteRequest(url, post); + } + + private String putJsonAssertOkAndReturnBody(String url, String json) { + HttpPut post = new HttpPut(url); + if (json != null) { + post.setEntity(jsonStringEntity(json)); + } + return this.safelyExecuteRequest(url, post); } @@ -546,9 +560,14 @@ private String safelyExecuteRequest(String url, ClassicHttpRequest request) { } } - private String urlFor(final Class taskClass) { - final RequestSpec requestSpec = this.adminRoutes.requestSpecForTask(taskClass); - checkNotNull(requestSpec, "No admin task URL is registered for " + taskClass.getSimpleName()); - return String.format(HttpAdminClient.ADMIN_URL_PREFIX + requestSpec.path(), this.scheme, this.host, this.port, this.urlPathPrefix); + private String urlFor(Class taskClass) { + return urlFor(taskClass, PathParams.empty()); + } + + private String urlFor(Class taskClass, PathParams pathParams) { + RequestSpec requestSpec = adminRoutes.requestSpecForTask(taskClass); + requireNonNull(requestSpec, "No admin task URL is registered for " + taskClass.getSimpleName()); + return String.format( + ADMIN_URL_PREFIX + requestSpec.path(pathParams), scheme, host, port, urlPathPrefix); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/MappingBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/client/MappingBuilder.java index 4f3018e0ca..c05b41c749 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/MappingBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/MappingBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,16 @@ import com.github.tomakehurst.wiremock.common.Metadata; import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ServeEventListener; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.matching.ContentPattern; +import com.github.tomakehurst.wiremock.matching.MultiValuePattern; import com.github.tomakehurst.wiremock.matching.MultipartValuePatternBuilder; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.matching.ValueMatcher; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import java.util.Map; +import java.util.Set; import java.util.UUID; public interface MappingBuilder { @@ -38,8 +41,18 @@ public interface MappingBuilder { MappingBuilder withHeader(String key, StringValuePattern headerPattern); + MappingBuilder withHeader(String key, MultiValuePattern headerPattern); + + MappingBuilder withPathParam(String name, StringValuePattern pattern); + MappingBuilder withQueryParam(String key, StringValuePattern queryParamPattern); + MappingBuilder withQueryParam(String key, MultiValuePattern multiValueQueryParamPattern); + + MappingBuilder withFormParam(String key, StringValuePattern formParamPattern); + + MappingBuilder withFormParam(String key, MultiValuePattern multiValueFormParamPattern); + MappingBuilder withQueryParams(Map queryParams); MappingBuilder withRequestBody(ContentPattern bodyPattern); @@ -62,6 +75,16 @@ public interface MappingBuilder {

MappingBuilder withPostServeAction(String extensionName, P parameters); + default

MappingBuilder withServeEventListener( + ServeEventListener.RequestPhase requestPhase, String extensionName, P parameters) { + return withServeEventListener(Set.of(requestPhase), extensionName, parameters); + } + +

MappingBuilder withServeEventListener( + Set requestPhases, String extensionName, P parameters); + +

MappingBuilder withServeEventListener(String extensionName, P parameters); + MappingBuilder withMetadata(Map metadata); MappingBuilder withMetadata(Metadata metadata); diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilder.java index cff6fc435b..d1a608d544 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package com.github.tomakehurst.wiremock.client; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Maps.newHashMap; +import static com.github.tomakehurst.wiremock.common.ContentTypes.APPLICATION_JSON; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; import static java.net.HttpURLConnection.HTTP_OK; import static java.util.Arrays.asList; @@ -24,8 +24,8 @@ import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,7 +35,7 @@ public class ResponseDefinitionBuilder { protected String statusMessage; protected Body body = Body.none(); protected String bodyFileName; - protected List headers = newArrayList(); + protected List headers = new ArrayList<>(); protected Integer fixedDelayMilliseconds; protected DelayDistribution delayDistribution; protected ChunkedDribbleDelay chunkedDribbleDelay; @@ -43,7 +43,7 @@ public class ResponseDefinitionBuilder { protected String proxyUrlPrefixToRemove; protected Fault fault; protected List responseTransformerNames; - protected Map transformerParameters = newHashMap(); + protected Map transformerParameters = new HashMap<>(); protected Boolean wasConfigured = true; public static ResponseDefinitionBuilder like(ResponseDefinition responseDefinition) { @@ -52,8 +52,8 @@ public static ResponseDefinitionBuilder like(ResponseDefinition responseDefiniti builder.statusMessage = responseDefinition.getStatusMessage(); builder.headers = responseDefinition.getHeaders() != null - ? newArrayList(responseDefinition.getHeaders().all()) - : Lists.newArrayList(); + ? new ArrayList<>(responseDefinition.getHeaders().all()) + : new ArrayList<>(); builder.body = responseDefinition.getReponseBody(); builder.bodyFileName = responseDefinition.getBodyFileName(); builder.fixedDelayMilliseconds = responseDefinition.getFixedDelayMilliseconds(); @@ -70,12 +70,14 @@ public static ResponseDefinitionBuilder like(ResponseDefinition responseDefiniti builder.wasConfigured = responseDefinition.isFromConfiguredStub(); if (builder.proxyBaseUrl != null) { - ProxyResponseDefinitionBuilder proxyResponseDefinitionBuilder = new ProxyResponseDefinitionBuilder(builder); - proxyResponseDefinitionBuilder.proxyUrlPrefixToRemove = responseDefinition.getProxyUrlPrefixToRemove(); + ProxyResponseDefinitionBuilder proxyResponseDefinitionBuilder = + new ProxyResponseDefinitionBuilder(builder); + proxyResponseDefinitionBuilder.proxyUrlPrefixToRemove = + responseDefinition.getProxyUrlPrefixToRemove(); proxyResponseDefinitionBuilder.additionalRequestHeaders = - responseDefinition.getAdditionalProxyRequestHeaders() != null - ? (List) responseDefinition.getAdditionalProxyRequestHeaders().all() - : Lists.newArrayList(); + responseDefinition.getAdditionalProxyRequestHeaders() != null + ? (List) responseDefinition.getAdditionalProxyRequestHeaders().all() + : new ArrayList<>(); return proxyResponseDefinitionBuilder; } @@ -91,7 +93,7 @@ public static ResponseDefinition jsonResponse(Object body, int status) { return new ResponseDefinitionBuilder() .withBody(Json.write(body)) .withStatus(status) - .withHeader("Content-Type", "application/json") + .withHeader(CONTENT_TYPE, APPLICATION_JSON) .build(); } @@ -194,18 +196,18 @@ public static ResponseDefinitionBuilder okForJson(T body) { return responseDefinition() .withStatus(HTTP_OK) .withBody(Json.write(body)) - .withHeader("Content-Type", "application/json"); + .withHeader(CONTENT_TYPE, APPLICATION_JSON); } public static ResponseDefinitionBuilder okForEmptyJson() { return responseDefinition() .withStatus(HTTP_OK) .withBody("{}") - .withHeader("Content-Type", "application/json"); + .withHeader(CONTENT_TYPE, APPLICATION_JSON); } public ResponseDefinitionBuilder withHeaders(HttpHeaders headers) { - this.headers = ImmutableList.copyOf(headers.all()); + this.headers = new ArrayList<>(headers.all()); return this; } @@ -221,7 +223,7 @@ public ResponseDefinitionBuilder withStatusMessage(String message) { public static class ProxyResponseDefinitionBuilder extends ResponseDefinitionBuilder { - private List additionalRequestHeaders = newArrayList(); + private List additionalRequestHeaders = new ArrayList<>(); public ProxyResponseDefinitionBuilder(ResponseDefinitionBuilder from) { this.status = from.status; diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/ScenarioMappingBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/client/ScenarioMappingBuilder.java index 50d9985475..62478cccfa 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/ScenarioMappingBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/ScenarioMappingBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ package com.github.tomakehurst.wiremock.client; import com.github.tomakehurst.wiremock.common.Metadata; +import com.github.tomakehurst.wiremock.extension.ServeEventListener; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.matching.ContentPattern; +import com.github.tomakehurst.wiremock.matching.MultiValuePattern; import com.github.tomakehurst.wiremock.matching.MultipartValuePatternBuilder; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.matching.ValueMatcher; import java.util.Map; +import java.util.Set; import java.util.UUID; public interface ScenarioMappingBuilder extends MappingBuilder { @@ -34,8 +37,16 @@ public interface ScenarioMappingBuilder extends MappingBuilder { ScenarioMappingBuilder withHeader(String key, StringValuePattern headerPattern); + ScenarioMappingBuilder withHeader(String key, MultiValuePattern headerPattern); + ScenarioMappingBuilder withQueryParam(String key, StringValuePattern queryParamPattern); + ScenarioMappingBuilder withQueryParam(String key, MultiValuePattern queryParamPattern); + + ScenarioMappingBuilder withFormParam(String key, StringValuePattern formParamPattern); + + ScenarioMappingBuilder withFormParam(String key, MultiValuePattern formParamPattern); + ScenarioMappingBuilder withQueryParams(Map queryParams); ScenarioMappingBuilder withRequestBody(ContentPattern bodyPattern); @@ -57,6 +68,11 @@ ScenarioMappingBuilder withMultipartRequestBody(

ScenarioMappingBuilder withPostServeAction(String extensionName, P parameters); +

MappingBuilder withServeEventListener( + Set requestPhases, String extensionName, P parameters); + +

MappingBuilder withServeEventListener(String extensionName, P parameters); + ScenarioMappingBuilder withMetadata(Map metadata); ScenarioMappingBuilder withMetadata(Metadata metadata); diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/VerificationException.java b/src/main/java/com/github/tomakehurst/wiremock/client/VerificationException.java index 6075566ff4..b8a71f4ffa 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/VerificationException.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/VerificationException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,13 @@ */ package com.github.tomakehurst.wiremock.client; -import static com.google.common.base.Functions.toStringFunction; -import static com.google.common.collect.FluentIterable.from; - import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import com.github.tomakehurst.wiremock.verification.NearMiss; import com.github.tomakehurst.wiremock.verification.diff.Diff; -import com.google.common.base.Joiner; import java.util.List; +import java.util.stream.Collectors; public class VerificationException extends AssertionError { @@ -56,7 +53,7 @@ public static VerificationException forUnmatchedNearMisses(List nearMi } private static String renderList(List list) { - return Joiner.on("\n\n").join(from(list).transform(toStringFunction())); + return list.stream().map(Object::toString).collect(Collectors.joining("\n\n")); } private VerificationException(String messageStart, Diff diff) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/WireMock.java b/src/main/java/com/github/tomakehurst/wiremock/client/WireMock.java index 1f4857f1e7..4b14e82ea3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/WireMock.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/WireMock.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,32 +15,44 @@ */ package com.github.tomakehurst.wiremock.client; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; +import static com.github.tomakehurst.wiremock.common.ContentTypes.LOCATION; import static com.github.tomakehurst.wiremock.matching.RequestPattern.thatMatch; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.allRequests; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; -import static com.google.common.net.HttpHeaders.LOCATION; import com.github.tomakehurst.wiremock.admin.model.ListStubMappingsResult; import com.github.tomakehurst.wiremock.admin.model.ServeEventQuery; import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; -import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.SingleRootFileSource; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.global.GlobalSettings; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; import com.github.tomakehurst.wiremock.http.DelayDistribution; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.matching.*; +import com.github.tomakehurst.wiremock.recording.RecordSpec; import com.github.tomakehurst.wiremock.recording.RecordSpecBuilder; import com.github.tomakehurst.wiremock.recording.RecordingStatusResult; import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult; import com.github.tomakehurst.wiremock.security.ClientAuthenticator; import com.github.tomakehurst.wiremock.standalone.RemoteMappingsLoader; -import com.github.tomakehurst.wiremock.stubbing.*; -import com.github.tomakehurst.wiremock.verification.*; +import com.github.tomakehurst.wiremock.store.InMemorySettingsStore; +import com.github.tomakehurst.wiremock.store.SettingsStore; +import com.github.tomakehurst.wiremock.stubbing.Scenario; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.StubImport; +import com.github.tomakehurst.wiremock.stubbing.StubImportBuilder; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.verification.FindNearMissesResult; +import com.github.tomakehurst.wiremock.verification.FindRequestsResult; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import com.github.tomakehurst.wiremock.verification.NearMiss; +import com.github.tomakehurst.wiremock.verification.VerificationResult; import com.github.tomakehurst.wiremock.verification.diff.Diff; +import com.networknt.schema.SpecVersion; import java.io.File; import java.time.LocalDateTime; import java.time.ZonedDateTime; @@ -48,6 +60,8 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class WireMock { @@ -55,7 +69,8 @@ public class WireMock { private static final String DEFAULT_HOST = "localhost"; private final Admin admin; - private final GlobalSettingsHolder globalSettingsHolder = new GlobalSettingsHolder(); + + private final SettingsStore settingsStore = new InMemorySettingsStore(); private static InheritableThreadLocal defaultInstance = new InheritableThreadLocal() { @@ -221,6 +236,15 @@ public static StringValuePattern matchingJsonPath(String value, StringValuePatte return new MatchesJsonPathPattern(value, valuePattern); } + public static StringValuePattern matchingJsonSchema(String schema) { + return new MatchesJsonSchemaPattern(schema); + } + + public static StringValuePattern matchingJsonSchema( + String schema, JsonSchemaVersion jsonSchemaVersion) { + return new MatchesJsonSchemaPattern(schema, jsonSchemaVersion); + } + public static EqualToXmlPattern equalToXml(String value) { return new EqualToXmlPattern(value); } @@ -243,7 +267,7 @@ public static EqualToXmlPattern equalToXml( } public static MatchesXPathPattern matchingXPath(String value) { - return new MatchesXPathPattern(value, Collections.emptyMap()); + return new MatchesXPathPattern(value, Collections.emptyMap()); } public static StringValuePattern matchingXPath(String value, Map namespaces) { @@ -268,6 +292,10 @@ public static StringValuePattern notContaining(String value) { return new NegativeContainsPattern(value); } + public static StringValuePattern not(StringValuePattern unexpectedPattern) { + return new NotPattern(unexpectedPattern); + } + public static StringValuePattern matching(String regex) { return new RegexPattern(regex); } @@ -454,6 +482,37 @@ public static UrlPathPattern urlPathMatching(String urlRegex) { return new UrlPathPattern(matching(urlRegex), true); } + public static UrlPathPattern urlPathTemplate(String pathTemplate) { + return new UrlPathTemplatePattern(pathTemplate); + } + + public static MultiValuePattern havingExactly(final StringValuePattern... valuePatterns) { + if (valuePatterns.length == 0) { + return noValues(); + } + return new ExactMatchMultiValuePattern(Stream.of(valuePatterns).collect(Collectors.toList())); + } + + public static MultiValuePattern havingExactly(String... values) { + return havingExactly(Stream.of(values).map(EqualToPattern::new).toArray(EqualToPattern[]::new)); + } + + public static MultiValuePattern including(final StringValuePattern... valuePatterns) { + if (valuePatterns.length == 0) { + return noValues(); + } + return new IncludesMatchMultiValuePattern( + Stream.of(valuePatterns).collect(Collectors.toList())); + } + + public static MultiValuePattern including(String... values) { + return including(Stream.of(values).map(EqualToPattern::new).toArray(EqualToPattern[]::new)); + } + + public static MultiValuePattern noValues() { + return MultiValuePattern.of(absent()); + } + public static UrlPattern anyUrl() { return UrlPattern.ANY; } @@ -590,6 +649,10 @@ public static MappingBuilder delete(String url) { return delete(urlEqualTo(url)); } + public static MappingBuilder patch(String url) { + return patch(urlEqualTo(url)); + } + public static ResponseDefinitionBuilder created() { return aResponse().withStatus(201); } @@ -658,7 +721,7 @@ public void verifyThat( if (requestPattern.hasInlineCustomMatcher()) { List requests = admin.findRequestsMatching(RequestPattern.everything()).getRequests(); - actualCount = from(requests).filter(thatMatch(requestPattern)).size(); + actualCount = (int) requests.stream().filter(thatMatch(requestPattern)).count(); } else { VerificationResult result = admin.countRequestsMatching(requestPattern); result.assertRequestJournalEnabled(); @@ -675,7 +738,7 @@ public void verifyThat( private VerificationException verificationExceptionForNearMisses( RequestPatternBuilder requestPatternBuilder, RequestPattern requestPattern) { List nearMisses = findAllNearMissesFor(requestPatternBuilder); - if (nearMisses.size() > 0) { + if (!nearMisses.isEmpty()) { Diff diff = new Diff(requestPattern, nearMisses.get(0).getRequest()); return VerificationException.forUnmatchedRequestPattern(diff); } @@ -782,6 +845,10 @@ public static RequestPatternBuilder anyRequestedFor(UrlPattern urlPattern) { return new RequestPatternBuilder(RequestMethod.ANY, urlPattern); } + public static RequestPatternBuilder requestedFor(String method, UrlPattern urlPattern) { + return new RequestPatternBuilder(RequestMethod.fromString(method), urlPattern); + } + public static RequestPatternBuilder requestMadeFor( String customMatcherName, Parameters parameters) { return RequestPatternBuilder.forCustomMatcher(customMatcherName, parameters); @@ -796,7 +863,7 @@ public static void setGlobalFixedDelay(int milliseconds) { } public void setGlobalFixedDelayVariable(int milliseconds) { - GlobalSettings settings = globalSettingsHolder.get().copy().fixedDelay(milliseconds).build(); + GlobalSettings settings = settingsStore.get().copy().fixedDelay(milliseconds).build(); updateGlobalSettings(settings); } @@ -805,8 +872,7 @@ public static void setGlobalRandomDelay(DelayDistribution distribution) { } public void setGlobalRandomDelayVariable(DelayDistribution distribution) { - GlobalSettings settings = - globalSettingsHolder.get().copy().delayDistribution(distribution).build(); + GlobalSettings settings = settingsStore.get().copy().delayDistribution(distribution).build(); updateGlobalSettings(settings); } @@ -815,7 +881,7 @@ public static void updateSettings(GlobalSettings settings) { } public void updateGlobalSettings(GlobalSettings settings) { - globalSettingsHolder.replaceWith(settings); + settingsStore.set(settings); admin.updateGlobalSettings(settings); } @@ -901,6 +967,10 @@ public static void startRecording(String targetBaseUrl) { defaultInstance.get().startStubRecording(targetBaseUrl); } + public static void startRecording() { + defaultInstance.get().startStubRecording(); + } + public static void startRecording(RecordSpecBuilder spec) { defaultInstance.get().startStubRecording(spec); } @@ -909,6 +979,10 @@ public void startStubRecording(String targetBaseUrl) { admin.startRecording(targetBaseUrl); } + public void startStubRecording() { + admin.startRecording(RecordSpec.DEFAULTS); + } + public void startStubRecording(RecordSpecBuilder spec) { admin.startRecording(spec.build()); } @@ -972,4 +1046,31 @@ public GlobalSettings getGlobalSettings() { public static GlobalSettings getSettings() { return defaultInstance.get().getGlobalSettings(); } + + public enum JsonSchemaVersion { + V4, + V6, + V7, + V201909, + V202012; + + public static final JsonSchemaVersion DEFAULT = V202012; + + public SpecVersion.VersionFlag toVersionFlag() { + switch (this) { + case V4: + return SpecVersion.VersionFlag.V4; + case V6: + return SpecVersion.VersionFlag.V6; + case V7: + return SpecVersion.VersionFlag.V7; + case V201909: + return SpecVersion.VersionFlag.V201909; + case V202012: + return SpecVersion.VersionFlag.V202012; + default: + throw new IllegalArgumentException("Unknown schema version: " + this); + } + } + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/AbstractFileSource.java b/src/main/java/com/github/tomakehurst/wiremock/common/AbstractFileSource.java index 209f896bc0..66621bbee3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/AbstractFileSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/AbstractFileSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2022 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,26 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Lists.newArrayList; +import static java.nio.charset.StandardCharsets.UTF_8; import com.github.tomakehurst.wiremock.security.NotAuthorisedException; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; public abstract class AbstractFileSource implements FileSource { protected final File rootDirectory; - public AbstractFileSource(File rootDirectory) { + protected AbstractFileSource(File rootDirectory) { this.rootDirectory = rootDirectory; } @@ -75,13 +75,13 @@ public URI getUri() { @Override public List listFilesRecursively() { assertExistsAndIsDirectory(); - List fileList = newArrayList(); + List fileList = new ArrayList<>(); recursivelyAddFilesToList(rootDirectory, fileList); return toTextFileList(fileList); } private void recursivelyAddFilesToList(File root, List fileList) { - File[] files = root.listFiles(); + File[] files = Optional.ofNullable(root.listFiles()).orElse(new File[0]); for (File file : files) { if (file.isDirectory()) { recursivelyAddFilesToList(file, fileList); @@ -92,14 +92,7 @@ private void recursivelyAddFilesToList(File root, List fileList) { } private List toTextFileList(List fileList) { - return newArrayList( - transform( - fileList, - new Function() { - public TextFile apply(File input) { - return new TextFile(input.toURI()); - } - })); + return fileList.stream().map(input -> new TextFile(input.toURI())).collect(Collectors.toList()); } @Override @@ -161,7 +154,10 @@ private void assertFilePathIsUnderRoot(String path) { : new File(rootDirectory, path).getCanonicalPath(); if (!Paths.get(filePath).normalize().startsWith(rootPath)) { - throw new NotAuthorisedException("Access to file " + path + " is not permitted"); + throw new NotAuthorisedException( + "Access to file " + + path + + " is not permitted. An absolute path from the filesystem root might be specified"); } } catch (IOException ioe) { throw new NotAuthorisedException("File " + path + " cannot be accessed", ioe); @@ -179,7 +175,7 @@ private void ensureDirectoryExists(File toFile) throws IOException { private void writeTextFileAndTranslateExceptions(String contents, File toFile) { try { ensureDirectoryExists(toFile); - Files.asCharSink(toFile, UTF_8).write(contents); + Files.write(toFile.toPath(), contents.getBytes(UTF_8)); } catch (IOException ioe) { throw new RuntimeException(ioe); } @@ -188,17 +184,13 @@ private void writeTextFileAndTranslateExceptions(String contents, File toFile) { private void writeBinaryFileAndTranslateExceptions(byte[] contents, File toFile) { try { ensureDirectoryExists(toFile); - Files.write(contents, toFile); + Files.write(toFile.toPath(), contents); } catch (IOException ioe) { throw new RuntimeException(ioe); } } public static Predicate byFileExtension(final String extension) { - return new Predicate() { - public boolean apply(BinaryFile input) { - return input.name().endsWith("." + extension); - } - }; + return input -> input.name().endsWith("." + extension); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/BinaryFile.java b/src/main/java/com/github/tomakehurst/wiremock/common/BinaryFile.java index d0ec3cf9dc..5bf1fdefc3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/BinaryFile.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/BinaryFile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,13 @@ import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import com.google.common.io.ByteStreams; import java.io.IOException; import java.io.InputStream; import java.net.URI; public class BinaryFile implements InputStreamSource { - private URI uri; + private final URI uri; public BinaryFile(URI uri) { this.uri = uri; @@ -32,7 +31,7 @@ public BinaryFile(URI uri) { public byte[] readContents() { try (InputStream stream = getStream()) { - return ByteStreams.toByteArray(stream); + return stream.readAllBytes(); } catch (final IOException ioe) { return throwUnchecked(ioe, byte[].class); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ClasspathFileSource.java b/src/main/java/com/github/tomakehurst/wiremock/common/ClasspathFileSource.java index b7eff6d0fd..1e4fdac2ec 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ClasspathFileSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ClasspathFileSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,41 +16,45 @@ package com.github.tomakehurst.wiremock.common; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Lists.newArrayList; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static java.lang.Thread.currentThread; import static java.util.Arrays.asList; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Iterators; import com.google.common.io.Resources; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; import java.util.Enumeration; -import java.util.Iterator; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class ClasspathFileSource implements FileSource { private final String path; + private final ClassLoader classLoader; private URI pathUri; private ZipFile zipFile; private File rootDirectory; public ClasspathFileSource(String path) { + this((ClassLoader) null, path); + } + + public ClasspathFileSource(Class classpath, String path) { + this(classpath.getClassLoader(), path); + } + + public ClasspathFileSource(ClassLoader classLoader, String path) { this.path = path; + this.classLoader = classLoader; try { - URL resource = - firstNonNull(currentThread().getContextClassLoader(), Resources.class.getClassLoader()) - .getResource(path); + URL resource = getClassLoader().getResource(path); if (resource == null) { rootDirectory = new File(path); @@ -68,7 +72,7 @@ public ClasspathFileSource(String path) { } else if (pathUri.getScheme().equals("file")) { rootDirectory = new File(pathUri); } else { - throw new RuntimeException( + throw new IllegalArgumentException( "ClasspathFileSource can't handle paths of type " + pathUri.getScheme()); } @@ -77,6 +81,12 @@ public ClasspathFileSource(String path) { } } + private ClassLoader getClassLoader() { + if (classLoader != null) return classLoader; + return getFirstNonNull( + currentThread().getContextClassLoader(), Resources.class.getClassLoader()); + } + private boolean isFileSystem() { return rootDirectory != null; } @@ -108,10 +118,10 @@ private URI getZipEntryUri(final String name) { if (candidate.getName().equals(lookFor)) { return getUriFor(candidate); } - candidates.append(candidate.getName() + "\n"); + candidates.append(candidate.getName()).append("\n"); } throw new RuntimeException( - "Was unable to find entry: \"" + lookFor + "\", found:\n" + candidates.toString()); + "Was unable to find entry: \"" + lookFor + "\", found:\n" + candidates); } @Override @@ -119,7 +129,7 @@ public void createIfNecessary() {} @Override public FileSource child(String subDirectoryName) { - return new ClasspathFileSource(path + "/" + subDirectoryName); + return new ClasspathFileSource(classLoader, path + "/" + subDirectoryName); } @Override @@ -136,25 +146,15 @@ public URI getUri() { public List listFilesRecursively() { if (isFileSystem()) { assertExistsAndIsDirectory(); - List fileList = newArrayList(); + List fileList = new ArrayList<>(); recursivelyAddFilesToList(rootDirectory, fileList); return toTextFileList(fileList); } - return FluentIterable.from(toIterable(zipFile.entries())) - .filter( - new Predicate() { - public boolean apply(ZipEntry jarEntry) { - return !jarEntry.isDirectory() && jarEntry.getName().startsWith(path); - } - }) - .transform( - new Function() { - public TextFile apply(ZipEntry jarEntry) { - return new TextFile(getUriFor(jarEntry)); - } - }) - .toList(); + return zipFile.stream() + .filter(jarEntry -> !jarEntry.isDirectory() && jarEntry.getName().startsWith(path)) + .map(jarEntry -> new TextFile(getUriFor(jarEntry))) + .collect(Collectors.toList()); } private URI getUriFor(ZipEntry jarEntry) { @@ -166,7 +166,7 @@ private URI getUriFor(ZipEntry jarEntry) { } private void recursivelyAddFilesToList(File root, List fileList) { - File[] files = root.listFiles(); + File[] files = Optional.ofNullable(root.listFiles()).orElse(new File[0]); for (File file : files) { if (file.isDirectory()) { recursivelyAddFilesToList(file, fileList); @@ -177,14 +177,7 @@ private void recursivelyAddFilesToList(File root, List fileList) { } private List toTextFileList(List fileList) { - return newArrayList( - transform( - fileList, - new Function() { - public TextFile apply(File input) { - return new TextFile(input.toURI()); - } - })); + return fileList.stream().map(input -> new TextFile(input.toURI())).collect(Collectors.toList()); } @Override @@ -202,15 +195,6 @@ public boolean exists() { @Override public void deleteFile(String name) {} - private static Iterable toIterable(final Enumeration e) { - return new Iterable() { - @Override - public Iterator iterator() { - return Iterators.forEnumeration(e); - } - }; - } - private void assertExistsAndIsDirectory() { if (rootDirectory.exists() && !rootDirectory.isDirectory()) { throw new RuntimeException(rootDirectory + " is not a directory"); diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ContentTypes.java b/src/main/java/com/github/tomakehurst/wiremock/common/ContentTypes.java index e3027323c3..9411c7f997 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ContentTypes.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ContentTypes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,36 +17,44 @@ import static com.github.tomakehurst.wiremock.common.Strings.stringFromBytes; import static com.github.tomakehurst.wiremock.common.TextType.JSON; -import static com.google.common.collect.Iterables.any; import static java.util.Arrays.asList; import static org.apache.commons.lang3.StringUtils.substringAfterLast; import com.fasterxml.jackson.databind.JsonNode; import com.github.tomakehurst.wiremock.common.xml.Xml; import com.github.tomakehurst.wiremock.http.ContentTypeHeader; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; import java.net.URI; import java.util.List; import java.util.Map; public class ContentTypes { + private ContentTypes() {} + + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + public static final String OCTET_STREAM = "application/octet-stream"; + public static final String LOCATION = "Location"; + public static final String AUTHORIZATION = "Authorization"; + public static final String COOKIE = "Cookie"; + public static final String APPLICATION_JSON = "application/json"; + private static final Map COMMON_MIME_TYPES = - ImmutableMap.builder() - .put("image/jpeg", "jpeg") - .put("image/gif", "gif") - .put("image/tiff", "tiff") - .put("image/png", "png") - .put("image/x-icon", "ico") - .put("image/svg+xml", "svg") - .put("audio/x-aiff", "aiff") - .put("video/x-ms-asf", "asf") - .put("video/mpeg", "mp2") - .put("audio/mpeg", "mp3") - .put("video/quicktime", "mov") - .put("application/pdf", "pdf") - .build(); + Map.ofEntries( + Map.entry("image/jpeg", "jpeg"), + Map.entry("image/gif", "gif"), + Map.entry("image/tiff", "tiff"), + Map.entry("image/png", "png"), + Map.entry("image/x-icon", "ico"), + Map.entry("image/svg+xml", "svg"), + Map.entry("audio/x-aiff", "aiff"), + Map.entry("video/x-ms-asf", "asf"), + Map.entry("video/mpeg", "mp2"), + Map.entry("audio/mpeg", "mp3"), + Map.entry("video/quicktime", "mov"), + Map.entry("application/pdf", "pdf")); public static final List TEXT_FILE_EXTENSIONS = asList("txt", "json", "xml", "html", "htm", "yaml", "csv"); @@ -120,14 +128,8 @@ public static boolean determineIsTextFromExtension(String extension) { } public static boolean determineIsTextFromMimeType(final String mimeType) { - return any( - TEXT_MIME_TYPE_PATTERNS, - new Predicate() { - @Override - public boolean apply(String pattern) { - return mimeType != null && mimeType.matches(pattern); - } - }); + return TEXT_MIME_TYPE_PATTERNS.stream() + .anyMatch(pattern -> mimeType != null && mimeType.matches(pattern)); } public static boolean determineIsText(String extension, String mimeType) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Encoding.java b/src/main/java/com/github/tomakehurst/wiremock/common/Encoding.java index 7a98580534..9ad2740ec9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Encoding.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Encoding.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,21 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static java.nio.charset.StandardCharsets.UTF_8; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; public class Encoding { + private Encoding() {} + private static Base64Encoder encoder = null; private static Base64Encoder getInstance() { if (encoder == null) { synchronized (Encoding.class) { if (encoder == null) { - encoder = new GuavaBase64Encoder(); + encoder = new JdkBase64Encoder(); } } } @@ -48,11 +49,7 @@ public static String encodeBase64(byte[] content, boolean padding) { return content != null ? getInstance().encode(content, padding) : null; } - public static String urlEncode(String unencodedUrl) { - try { - return URLEncoder.encode(unencodedUrl, "utf-8"); - } catch (UnsupportedEncodingException e) { - return throwUnchecked(e, String.class); - } + public static String urlEncode(String encodedUrl) { + return URLEncoder.encode(encodedUrl, UTF_8); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Gzip.java b/src/main/java/com/github/tomakehurst/wiremock/common/Gzip.java index 78bc71c240..604fb85361 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Gzip.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Gzip.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import static com.github.tomakehurst.wiremock.common.Strings.DEFAULT_CHARSET; import static com.github.tomakehurst.wiremock.common.Strings.bytesFromString; -import com.google.common.io.ByteStreams; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -29,6 +28,8 @@ public class Gzip { + private Gzip() {} + public static byte[] unGzip(byte[] gzippedContent) { if (gzippedContent.length == 0) { return new byte[0]; @@ -37,7 +38,7 @@ public static byte[] unGzip(byte[] gzippedContent) { try { GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(gzippedContent)); - return ByteStreams.toByteArray(gzipInputStream); + return gzipInputStream.readAllBytes(); } catch (IOException e) { return throwUnchecked(e, byte[].class); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/HttpClientUtils.java b/src/main/java/com/github/tomakehurst/wiremock/common/HttpClientUtils.java index c5a4994c96..84a9bb7315 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/HttpClientUtils.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/HttpClientUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,8 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.base.Charsets.UTF_8; +import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; import org.apache.hc.core5.http.ClassicHttpResponse; @@ -25,11 +26,13 @@ public class HttpClientUtils { + private HttpClientUtils() {} + public static String getEntityAsStringAndCloseStream(ClassicHttpResponse httpResponse) { HttpEntity entity = httpResponse.getEntity(); if (entity != null) { try { - String content = EntityUtils.toString(entity, UTF_8.name()); + String content = EntityUtils.toString(entity, UTF_8); entity.getContent().close(); return content; } catch (IOException | ParseException ioe) { @@ -42,14 +45,27 @@ public static String getEntityAsStringAndCloseStream(ClassicHttpResponse httpRes public static byte[] getEntityAsByteArrayAndCloseStream(ClassicHttpResponse httpResponse) { HttpEntity entity = httpResponse.getEntity(); - if (entity != null) { - try { - byte[] content = EntityUtils.toByteArray(entity); - entity.getContent().close(); - return content; - } catch (IOException ioe) { - throw new RuntimeException(ioe); + try { + if (entity != null) { + return EntityUtils.toByteArray(entity); + } + } catch (IOException ioe) { + return throwUnchecked(ioe, byte[].class); + } finally { + Exceptions.uncheck(httpResponse::close); + } + + return null; + } + + public static byte[] getEntityAsByteArray(ClassicHttpResponse httpResponse) { + HttpEntity entity = httpResponse.getEntity(); + try { + if (entity != null) { + return EntityUtils.toByteArray(entity); } + } catch (IOException ioe) { + return throwUnchecked(ioe, byte[].class); } return null; diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/GuavaBase64Encoder.java b/src/main/java/com/github/tomakehurst/wiremock/common/JdkBase64Encoder.java similarity index 70% rename from src/main/java/com/github/tomakehurst/wiremock/common/GuavaBase64Encoder.java rename to src/main/java/com/github/tomakehurst/wiremock/common/JdkBase64Encoder.java index 16de3ca1a1..207f1d2889 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/GuavaBase64Encoder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/JdkBase64Encoder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ package com.github.tomakehurst.wiremock.common; -import com.google.common.io.BaseEncoding; +import java.util.Base64; -public class GuavaBase64Encoder implements Base64Encoder { +public class JdkBase64Encoder implements Base64Encoder { @Override public String encode(byte[] content) { @@ -26,15 +26,12 @@ public String encode(byte[] content) { @Override public String encode(byte[] content, boolean padding) { - BaseEncoding encoder = BaseEncoding.base64(); - if (!padding) { - encoder = encoder.omitPadding(); - } - return encoder.encode(content); + var encoder = padding ? Base64.getEncoder() : Base64.getEncoder().withoutPadding(); + return encoder.encodeToString(content); } @Override public byte[] decode(String base64) { - return BaseEncoding.base64().decode(base64); + return Base64.getDecoder().decode(base64); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/JettySettings.java b/src/main/java/com/github/tomakehurst/wiremock/common/JettySettings.java index 6c24f60715..d70106b98b 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/JettySettings.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/JettySettings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,57 +15,64 @@ */ package com.github.tomakehurst.wiremock.common; -import com.google.common.base.Optional; +import java.util.Optional; /** - * Exposed Jetty tuning options. See: - * http://download.eclipse.org/jetty/stable-7/apidocs/org/eclipse/jetty/server/AbstractConnector.html + * Exposed Jetty tuning options. See: AbstractConnector */ public class JettySettings { - private final Optional acceptors; - private final Optional acceptQueueSize; - private final Optional requestHeaderSize; - private final Optional responseHeaderSize; - private final Optional stopTimeout; - private final Optional idleTimeout; + private final Integer acceptors; + private final Integer acceptQueueSize; + private final Integer requestHeaderSize; + private final Integer responseHeaderSize; + private final Long stopTimeout; + private final Long idleTimeout; + private final Long shutdownIdleTimeout; private JettySettings( - Optional acceptors, - Optional acceptQueueSize, - Optional requestHeaderSize, - Optional responseHeaderSize, - Optional stopTimeout, - Optional idleTimeout) { + Integer acceptors, + Integer acceptQueueSize, + Integer requestHeaderSize, + Integer responseHeaderSize, + Long stopTimeout, + Long idleTimeout, + Long shutdownIdleTimeout) { this.acceptors = acceptors; this.acceptQueueSize = acceptQueueSize; this.requestHeaderSize = requestHeaderSize; this.responseHeaderSize = responseHeaderSize; this.stopTimeout = stopTimeout; this.idleTimeout = idleTimeout; + this.shutdownIdleTimeout = shutdownIdleTimeout; } public Optional getAcceptors() { - return acceptors; + return Optional.ofNullable(acceptors); } public Optional getAcceptQueueSize() { - return acceptQueueSize; + return Optional.ofNullable(acceptQueueSize); } public Optional getRequestHeaderSize() { - return requestHeaderSize; + return Optional.ofNullable(requestHeaderSize); } public Optional getResponseHeaderSize() { - return responseHeaderSize; + return Optional.ofNullable(responseHeaderSize); } public Optional getStopTimeout() { - return stopTimeout; + return Optional.ofNullable(stopTimeout); } public Optional getIdleTimeout() { - return idleTimeout; + return Optional.ofNullable(idleTimeout); + } + + public Optional getShutdownIdleTimeout() { + return Optional.ofNullable(shutdownIdleTimeout); } @Override @@ -89,6 +96,7 @@ public static class Builder { private Integer responseHeaderSize; private Long stopTimeout; private Long idleTimeout; + private Long shutdownIdleTimeout; private Builder() {} @@ -126,14 +134,20 @@ public Builder withIdleTimeout(Long idleTimeout) { return this; } + public Builder withShutdownIdleTimeout(Long shutdownIdleTimeout) { + this.shutdownIdleTimeout = shutdownIdleTimeout; + return this; + } + public JettySettings build() { return new JettySettings( - Optional.fromNullable(acceptors), - Optional.fromNullable(acceptQueueSize), - Optional.fromNullable(requestHeaderSize), - Optional.fromNullable(responseHeaderSize), - Optional.fromNullable(stopTimeout), - Optional.fromNullable(idleTimeout)); + acceptors, + acceptQueueSize, + requestHeaderSize, + responseHeaderSize, + stopTimeout, + idleTimeout, + shutdownIdleTimeout); } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Json.java b/src/main/java/com/github/tomakehurst/wiremock/common/Json.java index 0514d8532d..09d387b0d5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Json.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Json.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.io.IOException; import java.util.Map; @@ -46,6 +48,8 @@ protected ObjectMapper initialValue() { objectMapper.configure(JsonParser.Feature.IGNORE_UNDEFINED, true); objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true); objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return objectMapper; } }; @@ -150,4 +154,19 @@ public static Map objectToMap(T theObject) { ObjectMapper mapper = getObjectMapper(); return mapper.convertValue(theObject, new TypeReference>() {}); } + + public static int schemaPropertyCount(JsonNode schema) { + int count = 0; + final JsonNode propertiesNode = schema.get("properties"); + if (propertiesNode != null && !propertiesNode.isEmpty()) { + for (JsonNode property : propertiesNode) { + count++; + if (property.has("properties")) { + count += schemaPropertyCount(property); + } + } + } + + return count; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/JsonException.java b/src/main/java/com/github/tomakehurst/wiremock/common/JsonException.java index a40da7e61c..509a0923a7 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/JsonException.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/JsonException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,12 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.collect.Lists.transform; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; -import com.google.common.base.Function; -import com.google.common.base.Joiner; import java.util.List; +import java.util.function.Function; import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; public class JsonException extends InvalidInputException { @@ -46,8 +44,9 @@ public static JsonException fromJackson(JsonProcessingException processingExcept String pointer = null; if (processingException instanceof JsonMappingException) { List nodes = - transform(((JsonMappingException) processingException).getPath(), TO_NODE_NAMES); - pointer = '/' + Joiner.on('/').join(nodes); + ((JsonMappingException) processingException) + .getPath().stream().map(TO_NODE_NAMES).collect(Collectors.toList()); + pointer = "/" + String.join("/", nodes); } return new JsonException(Errors.single(10, pointer, "Error parsing JSON", message)); @@ -62,14 +61,6 @@ private static Throwable getRootCause(Throwable e) { } private static final Function TO_NODE_NAMES = - new Function() { - @Override - public String apply(JsonMappingException.Reference input) { - if (input.getFieldName() != null) { - return input.getFieldName(); - } - - return String.valueOf(input.getIndex()); - } - }; + input -> + input.getFieldName() != null ? input.getFieldName() : String.valueOf(input.getIndex()); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ListOrStringDeserialiser.java b/src/main/java/com/github/tomakehurst/wiremock/common/ListOrStringDeserialiser.java index 4a3e428d36..a713ba4e27 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ListOrStringDeserialiser.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ListOrStringDeserialiser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,6 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.collect.Lists.newArrayList; - import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; @@ -24,6 +22,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -36,7 +35,7 @@ public ListOrSingle deserialize(JsonParser parser, DeserializationContext ctx JsonNode rootNode = parser.readValueAsTree(); if (rootNode.isArray()) { ArrayNode arrayNode = (ArrayNode) rootNode; - List items = newArrayList(); + List items = new ArrayList<>(); for (Iterator i = arrayNode.elements(); i.hasNext(); ) { JsonNode node = i.next(); Object value = getValue(node); diff --git a/src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettingsHolder.java b/src/main/java/com/github/tomakehurst/wiremock/common/Message.java similarity index 55% rename from src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettingsHolder.java rename to src/main/java/com/github/tomakehurst/wiremock/common/Message.java index b75225f49f..8aaca5f31f 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettingsHolder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Message.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.global; +package com.github.tomakehurst.wiremock.common; -import java.util.concurrent.atomic.AtomicReference; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; -public class GlobalSettingsHolder { +public class Message { - private AtomicReference globalSettingsRef = - new AtomicReference<>(GlobalSettings.defaults()); + private final String message; - public void replaceWith(GlobalSettings globalSettings) { - globalSettingsRef.set(globalSettings); + @JsonCreator + public Message(@JsonProperty("message") String message) { + this.message = message; } - public GlobalSettings get() { - return globalSettingsRef.get(); + public String getMessage() { + return message; } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java b/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java index c2da518dbb..8e6ccc5374 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,8 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.base.Preconditions.checkArgument; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkParameter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -66,7 +64,7 @@ public Map getMap(String key) { @SuppressWarnings("unchecked") public Metadata getMetadata(String key) { checkKeyPresent(key); - checkArgument(Map.class.isAssignableFrom(get(key).getClass()), key + " is not a map"); + checkParameter(Map.class.isAssignableFrom(get(key).getClass()), key + " is not a map"); return new Metadata((Map) get(key)); } @@ -75,14 +73,14 @@ public Metadata getMetadata(String key, Metadata defaultValue) { return defaultValue; } - checkArgument(Map.class.isAssignableFrom(get(key).getClass()), key + " is not a map"); + checkParameter(Map.class.isAssignableFrom(get(key).getClass()), key + " is not a map"); return new Metadata((Map) get(key)); } @SuppressWarnings("unchecked") private T checkPresenceValidityAndCast(String key, Class type) { checkKeyPresent(key); - checkArgument( + checkParameter( type.isAssignableFrom(get(key).getClass()), key + " is not of type " + type.getSimpleName()); return (T) get(key); @@ -98,7 +96,7 @@ private T returnIfValidOrDefaultIfNot(String key, Class type, T defaultVa } private void checkKeyPresent(String key) { - checkArgument(containsKey(key), key + "' not present"); + checkParameter(containsKey(key), key + "' not present"); } public static Metadata from(T myData) { @@ -115,10 +113,10 @@ public T as(Class myDataClass) { public static class Builder { - private final ImmutableMap.Builder mapBuilder; + private final Map mapBuilder; public Builder() { - this.mapBuilder = ImmutableMap.builder(); + this.mapBuilder = new LinkedHashMap<>(); } public Builder attr(String key, Object value) { @@ -127,7 +125,7 @@ public Builder attr(String key, Object value) { } public Builder list(String key, Object... values) { - mapBuilder.put(key, ImmutableList.copyOf(values)); + mapBuilder.put(key, List.of(values)); return this; } @@ -137,7 +135,7 @@ public Builder attr(String key, Metadata.Builder metadataBuilder) { } public Metadata build() { - return new Metadata(mapBuilder.build()); + return new Metadata(mapBuilder); } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/NetworkAddressRules.java b/src/main/java/com/github/tomakehurst/wiremock/common/NetworkAddressRules.java index 9d3d0ee323..09ba48d4c8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/NetworkAddressRules.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/NetworkAddressRules.java @@ -34,8 +34,7 @@ public static Builder builder() { private final Set denied; private final Set deniedHostPatterns; - public static NetworkAddressRules ALLOW_ALL = - new NetworkAddressRules(ImmutableSet.of(ALL), emptySet()); + public static NetworkAddressRules ALLOW_ALL = new NetworkAddressRules(Set.of(ALL), emptySet()); public NetworkAddressRules(Set allowed, Set denied) { this.allowed = @@ -45,7 +44,7 @@ public NetworkAddressRules(Set allowed, Set !(networkAddressRange instanceof NetworkAddressRange.DomainNameWildcard)) .collect(toSet()), - ImmutableSet.of(ALL)); + Set.of(ALL)); this.allowedHostPatterns = defaultIfEmpty( allowed.stream() @@ -53,7 +52,7 @@ public NetworkAddressRules(Set allowed, Set (networkAddressRange instanceof NetworkAddressRange.DomainNameWildcard)) .collect(toSet()), - ImmutableSet.of(ALL)); + Set.of(ALL)); this.denied = denied.stream() .filter( @@ -105,7 +104,7 @@ public Builder deny(String expression) { public NetworkAddressRules build() { Set allowedRanges = allowed.build(); if (allowedRanges.isEmpty()) { - allowedRanges = ImmutableSet.of(ALL); + allowedRanges = Set.of(ALL); } return new NetworkAddressRules(allowedRanges, denied.build()); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ParameterUtils.java b/src/main/java/com/github/tomakehurst/wiremock/common/ParameterUtils.java new file mode 100644 index 0000000000..478d958303 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ParameterUtils.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.common; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.Predicate; + +public class ParameterUtils { + + private ParameterUtils() {} + + public static T getFirstNonNull(T first, T second) { + if (first != null) { + return first; + } + if (second != null) { + return second; + } + throw new NullPointerException("Both parameters are null"); + } + + public static void checkParameter(boolean condition, String errorMessage) { + if (!condition) { + throw new IllegalArgumentException(errorMessage); + } + } + + public static void checkState(boolean expression, String errorMessage) { + if (!expression) { + throw new IllegalStateException(errorMessage); + } + } + + public static T checkNotNull(T value, String errorMessage) { + if (value == null) { + throw new NullPointerException(errorMessage); + } + return value; + } + + public static int indexOf(Iterable iterable, Predicate predicate) { + checkNotNull(iterable, "iterable"); + checkNotNull(predicate, "predicate"); + Iterator iterator = iterable.iterator(); + for (int i = 0; iterator.hasNext(); i++) { + T current = iterator.next(); + if (predicate.test(current)) { + return i; + } + } + + return -1; + } + + public static T getFirst(Iterable iterable, T defaultValue) { + return iterable != null && iterable.iterator().hasNext() + ? iterable.iterator().next() + : defaultValue; + } + + public static T getLast(Iterable iterable) { + if (iterable == null || !iterable.iterator().hasNext()) { + throw new NoSuchElementException(); + } + Iterator iterator = iterable.iterator(); + while (true) { + T current = iterator.next(); + if (!iterator.hasNext()) { + return current; + } + } + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ProxySettings.java b/src/main/java/com/github/tomakehurst/wiremock/common/ProxySettings.java index cff742f33c..9850336d54 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ProxySettings.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ProxySettings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ package com.github.tomakehurst.wiremock.common; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkParameter; import static org.apache.commons.lang3.StringUtils.isEmpty; -import com.google.common.base.Preconditions; import java.net.MalformedURLException; import java.net.URL; @@ -50,8 +50,7 @@ public static ProxySettings fromString(String config) { throw new IllegalArgumentException( "Proxy via does not support any other protocol than http"); } - Preconditions.checkArgument( - !proxyUrl.getHost().isEmpty(), "Host part of proxy must be specified"); + checkParameter(!proxyUrl.getHost().isEmpty(), "Host part of proxy must be specified"); ProxySettings proxySettings = new ProxySettings( proxyUrl.getHost(), proxyUrl.getPort() == -1 ? DEFAULT_PORT : proxyUrl.getPort()); diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/SafeNames.java b/src/main/java/com/github/tomakehurst/wiremock/common/SafeNames.java deleted file mode 100644 index f4619bb81d..0000000000 --- a/src/main/java/com/github/tomakehurst/wiremock/common/SafeNames.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2017-2021 Thomas Akehurst - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.github.tomakehurst.wiremock.common; - -import static org.apache.commons.lang3.StringUtils.isNotEmpty; - -import com.github.tomakehurst.wiremock.matching.AnythingPattern; -import com.github.tomakehurst.wiremock.matching.UrlPattern; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import java.net.URI; -import java.text.Normalizer; -import java.util.Locale; -import java.util.regex.Pattern; -import org.apache.commons.lang3.StringUtils; - -public class SafeNames { - - private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^\\w-]"); - private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); - - public static String makeSafeFileName(StubMapping mapping) { - return makeSafeFileName(mapping, "json"); - } - - public static String makeSafeFileName(StubMapping mapping, String extension) { - String suffix = "-" + mapping.getId() + "." + extension; - if (isNotEmpty(mapping.getName())) { - return makeSafeName(mapping.getName()) + suffix; - } - - UrlPattern urlMatcher = mapping.getRequest().getUrlMatcher(); - - if (urlMatcher.getPattern() instanceof AnythingPattern) { - return suffix.substring(1); - } - - String expectedUrl = urlMatcher.getExpected(); - URI uri = URI.create(sanitise(expectedUrl)); - return makeSafeNameFromUrl(uri.getPath()) + suffix; - } - - public static String makeSafeNameFromUrl(String urlPath) { - String startingPath = urlPath.replace("/", "_"); - return makeSafeName(startingPath); - } - - public static String makeSafeName(String name) { - String nowhitespace = WHITESPACE.matcher(name).replaceAll("-"); - String normalized = Normalizer.normalize(nowhitespace, Normalizer.Form.NFD); - String slug = sanitise(normalized); - - slug = slug.replaceAll("^[_]*", ""); - slug = slug.replaceAll("[_]*$", ""); - - slug = StringUtils.truncate(slug, 200); - - return slug.toLowerCase(Locale.ENGLISH); - } - - private static String sanitise(String s) { - return NON_ALPHANUMERIC.matcher(s).replaceAll(""); - } -} diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java b/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java index 9c6303eac8..24215f5cc8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package com.github.tomakehurst.wiremock.common; +import jakarta.servlet.ServletContext; import java.io.File; -import javax.servlet.ServletContext; public class ServletContextFileSource extends AbstractFileSource { diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java b/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java index 9b7d764107..dcb56744a5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java @@ -15,44 +15,49 @@ */ package com.github.tomakehurst.wiremock.common; -import java.io.*; -import java.net.URI; +import com.github.tomakehurst.wiremock.admin.NotFoundException; +import com.github.tomakehurst.wiremock.store.BlobStore; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.nio.charset.Charset; public class StreamSources { private StreamSources() {} public static InputStreamSource forString(final String string, final Charset charset) { - return new InputStreamSource() { - @Override - public InputStream getStream() { - return string == null - ? null - : new ByteArrayInputStream(Strings.bytesFromString(string, charset)); - } - }; + return new StringInputStreamSource(string, charset); } public static InputStreamSource forBytes(final byte[] bytes) { - return new InputStreamSource() { - @Override - public InputStream getStream() { - return bytes == null ? null : new ByteArrayInputStream(bytes); - } - }; + return new ByteArrayInputStreamSource(bytes); } - public static InputStreamSource forURI(final URI uri) { - return new InputStreamSource() { - @Override - public InputStream getStream() { - try { - return uri == null ? null : new BufferedInputStream(uri.toURL().openStream()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }; + public static InputStreamSource forBlobStoreItem(BlobStore blobStore, String key) { + return () -> + blobStore + .getStream(key) + .orElseThrow(() -> new NotFoundException("Not found in blob store: " + key)); + } + + public static class StringInputStreamSource extends ByteArrayInputStreamSource { + + public StringInputStreamSource(String string, Charset charset) { + super(Strings.bytesFromString(string, charset)); + } + } + + public static class ByteArrayInputStreamSource implements InputStreamSource { + + private final byte[] bytes; + + public ByteArrayInputStreamSource(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public InputStream getStream() { + return bytes == null ? null : new ByteArrayInputStream(bytes); + } } public static InputStreamSource empty() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Strings.java b/src/main/java/com/github/tomakehurst/wiremock/common/Strings.java index 659ef04215..35b0ebe45d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Strings.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Strings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,17 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.base.Charsets.UTF_8; +import static java.lang.System.lineSeparator; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.lang3.StringUtils.getLevenshteinDistance; import java.nio.charset.Charset; import org.apache.commons.lang3.text.WordUtils; public class Strings { + + private Strings() {} + public static final Charset DEFAULT_CHARSET = UTF_8; public static String stringFromBytes(byte[] bytes) { @@ -69,4 +74,22 @@ private static int findLongestLineLength(String s) { return longestLength; } + + public static double normalisedLevenshteinDistance(String one, String two) { + if (one == null || two == null) { + return 1.0; + } + + double maxDistance = Math.max(one.length(), two.length()); + double actualDistance = getLevenshteinDistance(one, two); + return (actualDistance / maxDistance); + } + + public static String normaliseLineBreaks(String s) { + return s.replace("\r\n", "\n").replace("\n", lineSeparator()); + } + + public static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/TextFile.java b/src/main/java/com/github/tomakehurst/wiremock/common/TextFile.java index 8d3989c5eb..d30df58e5a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/TextFile.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/TextFile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.common; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.net.URI; diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Timing.java b/src/main/java/com/github/tomakehurst/wiremock/common/Timing.java index 8385d2deec..01a9a52a39 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Timing.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Timing.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,57 +15,75 @@ */ package com.github.tomakehurst.wiremock.common; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Stopwatch; public class Timing { - public static final Timing UNTIMED = new Timing(-1, -1); + public static final Timing UNTIMED = create(); - private final int addedDelay; - private final int processTime; - private final int responseSendTime; + private volatile Integer addedDelay; + private volatile Integer processTime; + private volatile Integer responseSendTime; - public Timing(int addedDelay, int processTime) { - this(addedDelay, processTime, -1, -1, -1); + public static Timing create() { + return new Timing(null, null, null, null, null); } private Timing( - @JsonProperty("addedDelay") int addedDelay, - @JsonProperty("processTime") int processTime, - @JsonProperty("responseSendTime") int responseSendTime, - @JsonProperty("serveTime") int ignored1, - @JsonProperty("totalTime") int ignored2) { + @JsonProperty("addedDelay") Integer addedDelay, + @JsonProperty("processTime") Integer processTime, + @JsonProperty("responseSendTime") Integer responseSendTime, + @JsonProperty("serveTime") Integer ignored1, + @JsonProperty("totalTime") Integer ignored2) { this.addedDelay = addedDelay; this.processTime = processTime; this.responseSendTime = responseSendTime; } /** The delay added to the response via the stub or global configuration */ - public int getAddedDelay() { + public Integer getAddedDelay() { return addedDelay; } /** The amount of time spent handling the stub request */ - public int getProcessTime() { + public Integer getProcessTime() { return processTime; } /** The amount of time taken to send the response to the client */ - public int getResponseSendTime() { + public Integer getResponseSendTime() { return responseSendTime; } /** The total request time from start to finish, minus added delay */ - public int getServeTime() { + public Integer getServeTime() { + if (processTime == null || responseSendTime == null) { + return null; + } return processTime + responseSendTime; } /** The total request time including added delay */ - public int getTotalTime() { - return getServeTime() + addedDelay; + public Integer getTotalTime() { + Integer serveTime = getServeTime(); + if (serveTime == null || addedDelay == null) { + return null; + } + return serveTime + addedDelay; + } + + public void setAddedTime(int addedDelayMillis) { + this.addedDelay = addedDelayMillis; + } + + public void logProcessTime(Stopwatch stopwatch) { + processTime = (int) stopwatch.elapsed(MILLISECONDS); } - public Timing withResponseSendTime(int responseSendTimeMillis) { - return new Timing(addedDelay, processTime, responseSendTimeMillis, -1, -1); + public void logResponseSendTime(Stopwatch stopwatch) { + responseSendTime = (int) stopwatch.elapsed(MILLISECONDS); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Urls.java b/src/main/java/com/github/tomakehurst/wiremock/common/Urls.java index b84df1b346..e5975d7529 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Urls.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Urls.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,23 @@ package com.github.tomakehurst.wiremock.common; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.google.common.collect.FluentIterable.from; +import static java.nio.charset.StandardCharsets.UTF_8; import com.github.tomakehurst.wiremock.http.QueryParameter; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.Iterables; +import com.google.common.collect.ImmutableListMultimap.Builder; import com.google.common.collect.Maps; -import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLDecoder; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; public class Urls { + private Urls() {} + public static Map splitQueryFromUrl(String url) { String queryPart = url.contains("?") && !url.endsWith("?") ? url.substring(url.indexOf('?') + 1) : null; @@ -56,8 +53,8 @@ public static Map splitQuery(String query) { return Collections.emptyMap(); } - Iterable pairs = Splitter.on('&').split(query); - ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + List pairs = Arrays.stream(query.split("&")).collect(Collectors.toList()); + Builder builder = ImmutableListMultimap.builder(); for (String queryElement : pairs) { int firstEqualsIndex = queryElement.indexOf('='); if (firstEqualsIndex == -1) { @@ -70,31 +67,29 @@ public static Map splitQuery(String query) { } return Maps.transformEntries( - builder.build().asMap(), - new Maps.EntryTransformer, QueryParameter>() { - public QueryParameter transformEntry(String key, Collection values) { - return new QueryParameter(key, ImmutableList.copyOf(values)); - } - }); + builder.build().asMap(), (key, values) -> new QueryParameter(key, new ArrayList<>(values))); } public static String getPath(String url) { return url.contains("?") ? url.substring(0, url.indexOf("?")) : url; } + public static List getPathSegments(String path) { + return List.of(path.split("/")); + } + public static String urlToPathParts(URI uri) { - Iterable uriPathNodes = Splitter.on("/").omitEmptyStrings().split(uri.getPath()); - int nodeCount = Iterables.size(uriPathNodes); + List uriPathNodes = + Arrays.stream(uri.getPath().split("/")) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toUnmodifiableList()); + int nodeCount = uriPathNodes.size(); - return nodeCount > 0 ? Joiner.on("-").join(from(uriPathNodes)) : ""; + return nodeCount > 0 ? String.join("-", uriPathNodes) : ""; } public static String decode(String encoded) { - try { - return URLDecoder.decode(encoded, "utf-8"); - } catch (UnsupportedEncodingException e) { - return throwUnchecked(e, String.class); - } + return URLDecoder.decode(encoded, UTF_8); } public static URL safelyCreateURL(String url) { @@ -109,4 +104,12 @@ public static URL safelyCreateURL(String url) { private static String clean(String url) { return url.matches(".*:[0-9]+null$") ? url.substring(0, url.length() - 4) : url; } + + public static int getPort(URL url) { + if (url.getPort() == -1) { + return url.getProtocol().equals("https") ? 443 : 80; + } + + return url.getPort(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameMaker.java b/src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameMaker.java new file mode 100644 index 0000000000..8fb7e0bd93 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameMaker.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.common.filemaker; + +import static com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine.defaultTemplateEngine; + +import com.github.tomakehurst.wiremock.extension.responsetemplating.HandlebarsOptimizedTemplate; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.text.Normalizer; +import java.util.Locale; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + +public class FilenameMaker { + public static final String DEFAULT_FILENAME_TEMPLATE = + "{{#if name}}{{{name}}}{{else}}{{{method}}}-{{{url}}}{{/if}}-{{{id}}}"; + private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^\\w-.]"); + private static final String DEFAULT_EXTENSION = ".json"; + private static final String POINT = "."; + private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); + + private final TemplateEngine templateEngine; + private final String filenameTemplate; + + public FilenameMaker() { + this.templateEngine = defaultTemplateEngine(); + this.filenameTemplate = DEFAULT_FILENAME_TEMPLATE + DEFAULT_EXTENSION; + } + + public FilenameMaker(String filenameTemplate) { + this.templateEngine = defaultTemplateEngine(); + this.filenameTemplate = filenameTemplate; + } + + public FilenameMaker(String filenameTemplate, String extension) { + this.templateEngine = defaultTemplateEngine(); + if (filenameTemplate.equals("default")) { + this.filenameTemplate = DEFAULT_FILENAME_TEMPLATE + POINT + extension; + } else { + this.filenameTemplate = filenameTemplate + POINT + extension; + } + } + + public String filenameFor(StubMapping stubMapping) { + HandlebarsOptimizedTemplate template = templateEngine.getUncachedTemplate(filenameTemplate); + + final FilenameTemplateModel templateModel = new FilenameTemplateModel(stubMapping); + String parsedFilename = template.apply(templateModel); + return sanitise(parsedFilename); + } + + public String sanitizeUrl(String url) { + String startingPath = url.replace("/", "_"); + String pathWithoutWhitespace = WHITESPACE.matcher(startingPath).replaceAll("-"); + String normalizedPath = Normalizer.normalize(pathWithoutWhitespace, Normalizer.Form.NFD); + String slug = sanitise(normalizedPath).replaceAll("^[_]*", "").replaceAll("[_]*$", ""); + slug = StringUtils.truncate(slug, 200); + return slug; + } + + private String sanitise(String s) { + String decoratedString = String.join("-", s.split(" ")); + return NON_ALPHANUMERIC.matcher(decoratedString).replaceAll("").toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameTemplateModel.java b/src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameTemplateModel.java new file mode 100644 index 0000000000..4bf0c74b84 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/common/filemaker/FilenameTemplateModel.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.common.filemaker; + +import com.github.tomakehurst.wiremock.common.Metadata; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.util.UUID; + +public class FilenameTemplateModel { + + private final StubMapping stubMapping; + + public FilenameTemplateModel(StubMapping stubMapping) { + this.stubMapping = stubMapping; + } + + public UUID getId() { + return stubMapping.getId(); + } + + public String getName() { + return stubMapping.getName(); + } + + public String getUrl() { + return stubMapping.getRequest().getUrlMatcher().getExpected(); + } + + public String getMethod() { + return stubMapping.getRequest().getMethod().getName(); + } + + public Integer getPriority() { + return stubMapping.getPriority(); + } + + public String getScenarioName() { + return stubMapping.getScenarioName(); + } + + public String getRequiredScenarioState() { + return stubMapping.getRequiredScenarioState(); + } + + public String getNewScenarioState() { + return stubMapping.getNewScenarioState(); + } + + public RequestPattern getRequest() { + return stubMapping.getRequest(); + } + + public ResponseDefinition getResponse() { + return stubMapping.getResponse(); + } + + public Metadata getMetadata() { + return stubMapping.getMetadata(); + } + + public long getInsertionIndex() { + return stubMapping.getInsertionIndex(); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSource.java b/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSource.java index 1a08ed9712..fee54bab2e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ protected KeyStoreSource(String keyStoreType, char[] keyStorePassword) { public KeyStore load() { InputStream instream = null; try { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + KeyStore trustStore = KeyStore.getInstance(keyStoreType); instream = createInputStream(); trustStore.load(instream, keyStorePassword); return trustStore; diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java b/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java index 5df44b6da4..2111987a2c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ public static KeyStoreSource getAppropriateForJreVersion( final Class theClass = (Class) Class.forName( - "com.github.tomakehurst.wiremock.jetty94.WritableFileOrClasspathKeyStoreSource"); + "com.github.tomakehurst.wiremock.jetty11.WritableFileOrClasspathKeyStoreSource"); return safelyGetConstructor(theClass, String.class, String.class, char[].class) .newInstance(path, keyStoreType, keyStorePassword); } catch (ClassNotFoundException diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/model/PathParams.java b/src/main/java/com/github/tomakehurst/wiremock/common/url/PathParams.java similarity index 90% rename from src/main/java/com/github/tomakehurst/wiremock/admin/model/PathParams.java rename to src/main/java/com/github/tomakehurst/wiremock/common/url/PathParams.java index 7b778d5cfa..8ebba59844 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/model/PathParams.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/url/PathParams.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.admin.model; +package com.github.tomakehurst.wiremock.common.url; import java.util.LinkedHashMap; diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminUriTemplate.java b/src/main/java/com/github/tomakehurst/wiremock/common/url/PathTemplate.java similarity index 77% rename from src/main/java/com/github/tomakehurst/wiremock/admin/AdminUriTemplate.java rename to src/main/java/com/github/tomakehurst/wiremock/common/url/PathTemplate.java index 48edea7e50..23c5cab517 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminUriTemplate.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/url/PathTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.admin; +package com.github.tomakehurst.wiremock.common.url; import static java.lang.String.format; -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.google.common.base.Function; -import com.google.common.base.Objects; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class AdminUriTemplate { +public class PathTemplate { static final Pattern SPECIAL_SYMBOL_REGEX = Pattern.compile("(?:\\{(?[^}]+)\\})|(?\\*\\*)"); @@ -34,7 +33,11 @@ public class AdminUriTemplate { private final Parser parser; private final Renderer renderer; - public AdminUriTemplate(String templateString) { + public static boolean couldBePathTemplate(String value) { + return SPECIAL_SYMBOL_REGEX.matcher(value).find(); + } + + public PathTemplate(String templateString) { this.templateString = templateString; Matcher matcher = SPECIAL_SYMBOL_REGEX.matcher(templateString); @@ -48,8 +51,9 @@ public AdminUriTemplate(String templateString) { String variable = matcher.group("variable"); if (variable != null) { - parserBuilder.addVariable(variable); - rendererBuilder.addVariable(variable); + String variableName = stripFormatCharacters(variable); + parserBuilder.addVariable(variableName); + rendererBuilder.addVariable(variableName); } String wildcard = matcher.group("wildcard"); @@ -80,27 +84,40 @@ public String render(PathParams pathParams) { return renderer.render(pathParams); } + public String withoutVariables() { + return templateString.replaceAll(SPECIAL_SYMBOL_REGEX.pattern(), ""); + } + + private static String stripFormatCharacters(String parameter) { + return parameter.replace(".", "").replace(";", "").replace("*", ""); + } + + @Override + public String toString() { + return templateString; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - AdminUriTemplate that = (AdminUriTemplate) o; - return Objects.equal(templateString, that.templateString); + PathTemplate that = (PathTemplate) o; + return Objects.equals(templateString, that.templateString); } @Override public int hashCode() { - return Objects.hashCode(templateString); + return Objects.hash(templateString); } } class Parser { private final Pattern templatePattern; - private final List templateVariables; + private final List templateParameters; - Parser(Pattern templatePattern, List templateVariables) { + Parser(Pattern templatePattern, List templateParameters) { this.templatePattern = templatePattern; - this.templateVariables = templateVariables; + this.templateParameters = templateParameters; } boolean matches(String url) { @@ -115,8 +132,8 @@ PathParams parse(String url) { } PathParams pathParams = new PathParams(); - for (int i = 0; i < templateVariables.size(); i++) { - pathParams.put(templateVariables.get(i), matcher.group(i + 1)); + for (int i = 0; i < templateParameters.size(); i++) { + pathParams.put(templateParameters.get(i), matcher.group(i + 1)); } return pathParams; @@ -139,7 +156,7 @@ void addVariable(String variable) { void addWildcard() { templatePattern.append("(.*?)"); - templateVariables.add("" + wildcardCount++); + templateVariables.add(String.valueOf(wildcardCount++)); } Parser build() { @@ -196,13 +213,13 @@ public String apply(PathParams input) { } void addWildcard() { - final String wildcardIndex = "" + wildcardCount++; + final String wildcardIndex = String.valueOf(wildcardCount++); class Wildcard implements Function { @Override public String apply(PathParams input) { String value = input.get(wildcardIndex); if (value == null) { - throw new IllegalArgumentException(format("Wildcard was not bound")); + throw new IllegalArgumentException("Wildcard was not bound"); } return value; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/model/QueryParams.java b/src/main/java/com/github/tomakehurst/wiremock/common/url/QueryParams.java similarity index 94% rename from src/main/java/com/github/tomakehurst/wiremock/admin/model/QueryParams.java rename to src/main/java/com/github/tomakehurst/wiremock/common/url/QueryParams.java index 2452e86905..0cdda87819 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/model/QueryParams.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/url/QueryParams.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.admin.model; +package com.github.tomakehurst.wiremock.common.url; import com.github.tomakehurst.wiremock.common.Pair; import java.util.Arrays; diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/xml/XmlNode.java b/src/main/java/com/github/tomakehurst/wiremock/common/xml/XmlNode.java index 0509219e92..1424c183fb 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/xml/XmlNode.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/xml/XmlNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,11 @@ import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION; import com.github.tomakehurst.wiremock.common.ListOrSingle; -import com.google.common.collect.ImmutableMap; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; @@ -40,7 +40,7 @@ public class XmlNode { protected static final InheritableThreadLocal XPATH_CACHE = - new InheritableThreadLocal() { + new InheritableThreadLocal<>() { @Override protected XPath initialValue() { final XPathFactory xPathfactory = XPathFactory.newInstance(); @@ -49,7 +49,7 @@ protected XPath initialValue() { }; protected static final InheritableThreadLocal TRANSFORMER_CACHE = - new InheritableThreadLocal() { + new InheritableThreadLocal<>() { @Override protected Transformer initialValue() { TransformerFactory transformerFactory; @@ -97,17 +97,17 @@ public XmlNode(Node domNode) { attributes = domNode.hasAttributes() ? convertAttributeMap(domNode.getAttributes()) - : Collections.emptyMap(); + : Collections.emptyMap(); } private static Map convertAttributeMap(NamedNodeMap namedNodeMap) { - ImmutableMap.Builder builder = ImmutableMap.builder(); + Map map = new HashMap<>(); for (int i = 0; i < namedNodeMap.getLength(); i++) { Node node = namedNodeMap.item(i); - builder.put(node.getNodeName(), node.getNodeValue()); + map.put(node.getNodeName(), node.getNodeValue()); } - return builder.build(); + return Collections.unmodifiableMap(map); } public Map getAttributes() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java b/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java index 4a5ffd4eab..64ed10df9f 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/Options.java b/src/main/java/com/github/tomakehurst/wiremock/core/Options.java index 50da10f79c..82f7c68488 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/Options.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/Options.java @@ -16,17 +16,22 @@ package com.github.tomakehurst.wiremock.core; import com.github.tomakehurst.wiremock.common.*; -import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; +import com.github.tomakehurst.wiremock.extension.ExtensionDeclarations; +import com.github.tomakehurst.wiremock.extension.Extensions; import com.github.tomakehurst.wiremock.http.CaseInsensitiveKey; import com.github.tomakehurst.wiremock.http.HttpServerFactory; import com.github.tomakehurst.wiremock.http.ThreadPoolFactory; import com.github.tomakehurst.wiremock.http.trafficlistener.WiremockNetworkTrafficListener; import com.github.tomakehurst.wiremock.security.Authenticator; import com.github.tomakehurst.wiremock.standalone.MappingsLoader; +import com.github.tomakehurst.wiremock.store.Stores; import com.github.tomakehurst.wiremock.verification.notmatched.NotMatchedRenderer; -import com.google.common.base.Optional; +import com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer; import java.util.List; -import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; public interface Options { @@ -60,6 +65,8 @@ enum ChunkedEncodingPolicy { ProxySettings proxyVia(); + Stores getStores(); + FileSource filesRoot(); MappingsLoader mappingsLoader(); @@ -74,6 +81,8 @@ enum ChunkedEncodingPolicy { String bindAddress(); + FilenameMaker getFilenameMaker(); + List matchingHeaders(); boolean shouldPreserveHostHeader(); @@ -84,7 +93,7 @@ enum ChunkedEncodingPolicy { ThreadPoolFactory threadPoolFactory(); - Map extensionsOfType(Class extensionType); + ExtensionDeclarations getDeclaredExtensions(); WiremockNetworkTrafficListener networkTrafficListener(); @@ -92,7 +101,9 @@ enum ChunkedEncodingPolicy { boolean getHttpsRequiredForAdminApi(); - NotMatchedRenderer getNotMatchedRenderer(); + default Function getNotMatchedRendererFactory() { + return PlainTextStubNotMatchedRenderer::new; + } AsynchronousResponseSettings getAsynchronousResponseSettings(); @@ -113,4 +124,16 @@ enum ChunkedEncodingPolicy { DataTruncationSettings getDataTruncationSettings(); NetworkAddressRules getProxyTargetRules(); + + int proxyTimeout(); + + boolean getResponseTemplatingEnabled(); + + boolean getResponseTemplatingGlobal(); + + Long getMaxTemplateCacheEntries(); + + Set getTemplatePermittedSystemKeys(); + + boolean getTemplateEscapingDisabled(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/StubServer.java b/src/main/java/com/github/tomakehurst/wiremock/core/StubServer.java index b60c1422a8..2f22e66a41 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/StubServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/StubServer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,9 @@ */ package com.github.tomakehurst.wiremock.core; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public interface StubServer { - ServeEvent serveStubFor(Request request); + ServeEvent serveStubFor(ServeEvent serveEvent); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java index 9a5f52a6e4..4db77c4c1d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java @@ -15,12 +15,7 @@ */ package com.github.tomakehurst.wiremock.core; -import static com.github.tomakehurst.wiremock.stubbing.ServeEvent.NOT_MATCHED; -import static com.github.tomakehurst.wiremock.stubbing.ServeEvent.TO_LOGGED_REQUEST; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.contains; -import static com.google.common.collect.Iterables.transform; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.tomakehurst.wiremock.admin.AdminRoutes; import com.github.tomakehurst.wiremock.admin.LimitAndOffsetPaginator; @@ -30,8 +25,8 @@ import com.github.tomakehurst.wiremock.common.xml.Xml; import com.github.tomakehurst.wiremock.extension.*; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; +import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; import com.github.tomakehurst.wiremock.global.GlobalSettings; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; import com.github.tomakehurst.wiremock.http.*; import com.github.tomakehurst.wiremock.jetty9.websockets.Message; import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; @@ -40,17 +35,13 @@ import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.recording.*; import com.github.tomakehurst.wiremock.standalone.MappingsLoader; +import com.github.tomakehurst.wiremock.store.DefaultStores; +import com.github.tomakehurst.wiremock.store.SettingsStore; +import com.github.tomakehurst.wiremock.store.Stores; import com.github.tomakehurst.wiremock.stubbing.*; import com.github.tomakehurst.wiremock.verification.*; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; import org.apache.commons.lang3.mutable.MutableBoolean; public class WireMockApp implements StubServer, Admin { @@ -60,10 +51,11 @@ public class WireMockApp implements StubServer, Admin { public static final String MAPPINGS_ROOT = "mappings"; private static final MutableBoolean FACTORIES_LOADING_OPTIMIZED = new MutableBoolean(false); + private final Stores stores; private final Scenarios scenarios; private final StubMappings stubMappings; private final RequestJournal requestJournal; - private final GlobalSettingsHolder globalSettingsHolder; + private final SettingsStore settingsStore; private final boolean browserProxyingEnabled; private final MappingsLoader defaultMappingsLoader; private final Container container; @@ -74,6 +66,8 @@ public class WireMockApp implements StubServer, Admin { private Options options; + private Extensions extensions; + private final ProxyHandler proxyHandler; public WireMockApp(Options options, Container container) { @@ -84,78 +78,100 @@ public WireMockApp(Options options, Container container) { } this.options = options; - - final FileSource fileSource = options.filesRoot(); + this.stores = options.getStores(); + this.stores.start(); this.browserProxyingEnabled = options.browserProxySettings().enabled(); this.defaultMappingsLoader = options.mappingsLoader(); this.mappingsSaver = options.mappingsSaver(); - globalSettingsHolder = new GlobalSettingsHolder(); + + this.settingsStore = stores.getSettingsStore(); + + extensions = + new Extensions( + options.getDeclaredExtensions(), + this, + options, + stores, + options.filesRoot().child(FILES_ROOT)); + extensions.load(); Map customMatchers = - options.extensionsOfType(RequestMatcherExtension.class); + extensions.ofType(RequestMatcherExtension.class); requestJournal = options.requestJournalDisabled() ? new DisabledRequestJournal() - : new InMemoryRequestJournal(options.maxRequestJournalEntries(), customMatchers); + : new StoreBackedRequestJournal( + options.maxRequestJournalEntries().orElse(null), + customMatchers, + stores.getRequestJournalStore()); - scenarios = new Scenarios(); + scenarios = new InMemoryScenarios(stores.getScenariosStore()); stubMappings = - new InMemoryStubMappings( + new StoreBackedStubMappings( + stores.getStubStore(), scenarios, customMatchers, - options.extensionsOfType(ResponseDefinitionTransformer.class), - fileSource, - ImmutableList.copyOf(options.extensionsOfType(StubLifecycleListener.class).values())); + extensions.ofType(ResponseDefinitionTransformer.class), + extensions.ofType(ResponseDefinitionTransformerV2.class), + stores.getFilesBlobStore(), + List.copyOf(extensions.ofType(StubLifecycleListener.class).values())); nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); - recorder = new Recorder(this); - globalSettingsListeners = - ImmutableList.copyOf(options.extensionsOfType(GlobalSettingsListener.class).values()); + recorder = + new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); + globalSettingsListeners = List.copyOf(extensions.ofType(GlobalSettingsListener.class).values()); this.container = container; this.loadDefaultMappings(); } - public WireMockApp( - final boolean browserProxyingEnabled, - final MappingsLoader defaultMappingsLoader, - final MappingsSaver mappingsSaver, - final boolean requestJournalDisabled, - final Optional maxRequestJournalEntries, - final Map transformers, - final Map requestMatchers, - final FileSource rootFileSource, - final Container container) { - this.proxyHandler = new ProxyHandler(this); + public WireMockApp( + boolean browserProxyingEnabled, + MappingsLoader defaultMappingsLoader, + MappingsSaver mappingsSaver, + boolean requestJournalDisabled, + Integer maxRequestJournalEntries, + Map transformers, + Map v2transformers, + Map requestMatchers, + FileSource rootFileSource, + Container container) { + + this.proxyHandler = new ProxyHandler(this); + + this.stores = new DefaultStores(rootFileSource); this.browserProxyingEnabled = browserProxyingEnabled; this.defaultMappingsLoader = defaultMappingsLoader; this.mappingsSaver = mappingsSaver; - globalSettingsHolder = new GlobalSettingsHolder(); + this.settingsStore = stores.getSettingsStore(); requestJournal = requestJournalDisabled ? new DisabledRequestJournal() - : new InMemoryRequestJournal(maxRequestJournalEntries, requestMatchers); - scenarios = new Scenarios(); + : new StoreBackedRequestJournal( + maxRequestJournalEntries, requestMatchers, stores.getRequestJournalStore()); + scenarios = new InMemoryScenarios(stores.getScenariosStore()); stubMappings = - new InMemoryStubMappings( + new StoreBackedStubMappings( + stores.getStubStore(), scenarios, requestMatchers, transformers, - rootFileSource, - Collections.emptyList()); + v2transformers, + stores.getFilesBlobStore(), + Collections.emptyList()); this.container = container; nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); - recorder = new Recorder(this); + recorder = + new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); globalSettingsListeners = Collections.emptyList(); loadDefaultMappings(); } public AdminRequestHandler buildAdminRequestHandler() { - final AdminRoutes adminRoutes = AdminRoutes.defaultsPlus( - this.options.extensionsOfType(AdminApiExtension.class).values(), - this.options.getNotMatchedRenderer()); + AdminRoutes adminRoutes = + AdminRoutes.forServer(extensions.ofType(AdminApiExtension.class).values(), stores); return new AdminRequestHandler( adminRoutes, this, @@ -163,62 +179,66 @@ public AdminRequestHandler buildAdminRequestHandler() { options.getAdminAuthenticator(), options.getHttpsRequiredForAdminApi(), getAdminRequestFilters(), + getV2AdminRequestFilters(), options.getDataTruncationSettings()); } public StubRequestHandler buildStubRequestHandler() { - final Map postServeActions = this.options.extensionsOfType(PostServeAction.class); + Map postServeActions = extensions.ofType(PostServeAction.class); + Map serveEventListeners = + extensions.ofType(ServeEventListener.class); BrowserProxySettings browserProxySettings = options.browserProxySettings(); return new StubRequestHandler( this, new StubResponseRenderer( - options.filesRoot().child(FILES_ROOT), - getGlobalSettingsHolder(), + options.getStores().getFilesBlobStore(), + settingsStore, new ProxyResponseRenderer( options.proxyVia(), options.httpsSettings().trustStore(), options.shouldPreserveHostHeader(), options.proxyHostHeader(), - globalSettingsHolder, + settingsStore, browserProxySettings.trustAllProxyTargets(), browserProxySettings.trustedProxyTargets(), options.getStubCorsEnabled(), - options.getProxyTargetRules()), - ImmutableList.copyOf(options.extensionsOfType(ResponseTransformer.class).values())), + options.getProxyTargetRules(), + options.proxyTimeout()), + List.copyOf(extensions.ofType(ResponseTransformer.class).values()), + List.copyOf(extensions.ofType(ResponseTransformerV2.class).values())), this, postServeActions, + serveEventListeners, requestJournal, getStubRequestFilters(), + getV2StubRequestFilters(), options.getStubRequestLoggingDisabled(), - options.getDataTruncationSettings()); + options.getDataTruncationSettings(), + options.getNotMatchedRendererFactory().apply(extensions)); } private List getAdminRequestFilters() { - return FluentIterable.from(options.extensionsOfType(RequestFilter.class).values()) - .filter( - new Predicate() { - @Override - public boolean apply(RequestFilter filter) { - return filter.applyToAdmin(); - } - }) - .toList(); + return extensions.ofType(RequestFilter.class).values().stream() + .filter(RequestFilter::applyToAdmin) + .collect(Collectors.toList()); + } + + private List getV2AdminRequestFilters() { + return extensions.ofType(RequestFilterV2.class).values().stream() + .filter(RequestFilterV2::applyToAdmin) + .collect(Collectors.toList()); } private List getStubRequestFilters() { - return FluentIterable.from(options.extensionsOfType(RequestFilter.class).values()) - .filter( - new Predicate() { - @Override - public boolean apply(RequestFilter filter) { - return filter.applyToStubs(); - } - }) - .toList(); + return extensions.ofType(RequestFilter.class).values().stream() + .filter(RequestFilter::applyToStubs) + .collect(Collectors.toList()); } - public GlobalSettingsHolder getGlobalSettingsHolder() { - return this.globalSettingsHolder; + private List getV2StubRequestFilters() { + return extensions.ofType(RequestFilterV2.class).values().stream() + .filter(RequestFilterV2::applyToStubs) + .collect(Collectors.toList()); } private void loadDefaultMappings() { @@ -230,14 +250,15 @@ public void loadMappingsUsing(final MappingsLoader mappingsLoader) { } @Override - public ServeEvent serveStubFor(final Request request) { - final ServeEvent serveEvent = this.stubMappings.serveFor(request); + public ServeEvent serveStubFor(ServeEvent initialServeEvent) { + ServeEvent serveEvent = stubMappings.serveFor(initialServeEvent); - if (serveEvent.isNoExactMatch()) { - final LoggedRequest loggedRequest = serveEvent.getRequest(); - if (request.isBrowserProxyRequest() && this.browserProxyingEnabled) { - return ServeEvent.of(loggedRequest, ResponseDefinition.browserProxy(request)); - } + if (serveEvent.isNoExactMatch() + && browserProxyingEnabled + && serveEvent.getRequest().isBrowserProxyRequest() + && getGlobalSettings().getSettings().getProxyPassThrough()) { + return ServeEvent.ofUnmatched( + serveEvent.getRequest(), ResponseDefinition.browserProxy(serveEvent.getRequest())); } return serveEvent; @@ -259,13 +280,14 @@ public void addStubMapping(StubMapping stubMapping) { @Override public void removeStubMapping(StubMapping stubMapping) { - final Optional maybeStub = stubMappings.get(stubMapping.getId()); - if (maybeStub.isPresent()) { - StubMapping stubToDelete = maybeStub.get(); - if (stubToDelete.shouldBePersisted()) { - mappingsSaver.remove(stubToDelete); - } - } + stubMappings + .get(stubMapping.getId()) + .ifPresent( + stubToDelete -> { + if (stubToDelete.shouldBePersisted()) { + mappingsSaver.remove(stubToDelete); + } + }); stubMappings.removeMapping(stubMapping); @@ -275,10 +297,7 @@ public void removeStubMapping(StubMapping stubMapping) { @Override public void removeStubMapping(UUID id) { - final Optional maybeStub = stubMappings.get(id); - if (maybeStub.isPresent()) { - removeStubMapping(maybeStub.get()); - } + stubMappings.get(id).ifPresent(this::removeStubMapping); } @Override @@ -306,6 +325,7 @@ public SingleStubMappingResult getStubMapping(final UUID id) { public void saveMappings() { for (StubMapping stubMapping : stubMappings.getAll()) { stubMapping.setPersistent(true); + stubMappings.editMapping(stubMapping); } mappingsSaver.save(stubMappings.getAll()); } @@ -392,16 +412,16 @@ public FindRequestsResult findRequestsMatching(final RequestPattern requestPatte @Override public FindRequestsResult findUnmatchedRequests() { try { - final List requests = - from(this.requestJournal.getAllServeEvents()) - .filter(NOT_MATCHED) - .transform(TO_LOGGED_REQUEST) - .toList(); - return FindRequestsResult.withRequests(requests); - } catch (final RequestJournalDisabledException e) { - return FindRequestsResult.withRequestJournalDisabled(); - } + List requests = + requestJournal.getAllServeEvents().stream() + .filter(ServeEvent::isNoExactMatch) + .map(ServeEvent::getRequest) + .collect(Collectors.toList()); + return FindRequestsResult.withRequests(requests); + } catch (RequestJournalDisabledException e) { + return FindRequestsResult.withRequestJournalDisabled(); } + } @Override public void removeServeEvent(UUID eventId) { @@ -422,21 +442,17 @@ public FindServeEventsResult removeServeEventsForStubsMatchingMetadata( @Override public FindNearMissesResult findNearMissesForUnmatchedRequests() { - final ImmutableList.Builder listBuilder = ImmutableList.builder(); - final Iterable unmatchedServeEvents = - from(this.requestJournal.getAllServeEvents()) - .filter(new Predicate() { - @Override - public boolean apply(final ServeEvent input) { - return input.isNoExactMatch(); - } - }); + List nearMisses = new ArrayList<>(); + List unmatchedServeEvents = + requestJournal.getAllServeEvents().stream() + .filter(ServeEvent::isNoExactMatch) + .collect(Collectors.toList()); for (final ServeEvent serveEvent : unmatchedServeEvents) { - listBuilder.addAll(this.nearMissCalculator.findNearestTo(serveEvent.getRequest())); + nearMisses.addAll(this.nearMissCalculator.findNearestTo(serveEvent.getRequest())); } - return new FindNearMissesResult(listBuilder.build()); + return new FindNearMissesResult(nearMisses); } @Override @@ -466,18 +482,18 @@ public FindNearMissesResult findTopNearMissesFor(final RequestPattern requestPat @Override public GetGlobalSettingsResult getGlobalSettings() { - return new GetGlobalSettingsResult(globalSettingsHolder.get()); + return new GetGlobalSettingsResult(settingsStore.get()); } @Override public void updateGlobalSettings(GlobalSettings newSettings) { - GlobalSettings oldSettings = globalSettingsHolder.get(); + GlobalSettings oldSettings = settingsStore.get(); for (GlobalSettingsListener listener : globalSettingsListeners) { listener.beforeGlobalSettingsUpdated(oldSettings, newSettings); } - globalSettingsHolder.replaceWith(newSettings); + settingsStore.set(newSettings); for (GlobalSettingsListener listener : globalSettingsListeners) { listener.afterGlobalSettingsUpdated(oldSettings, newSettings); @@ -495,8 +511,9 @@ public Options getOptions() { @Override public void shutdownServer() { - this.container.shutdown(); - } + stores.stop(); + container.shutdown(); + } @Override public ProxyConfig getProxyConfig() { @@ -583,7 +600,7 @@ public void removeStubsByMetadata(final StringValuePattern pattern) { public void importStubs(StubImport stubImport) { List mappings = stubImport.getMappings(); StubImport.Options importOptions = - firstNonNull(stubImport.getImportOptions(), StubImport.Options.DEFAULTS); + getFirstNonNull(stubImport.getImportOptions(), StubImport.Options.DEFAULTS); for (int i = mappings.size() - 1; i >= 0; i--) { StubMapping mapping = mappings.get(i); @@ -597,17 +614,9 @@ public void importStubs(StubImport stubImport) { } if (importOptions.getDeleteAllNotInImport()) { - Iterable ids = - transform( - mappings, - new Function() { - @Override - public UUID apply(StubMapping input) { - return input.getId(); - } - }); + List ids = mappings.stream().map(StubMapping::getId).collect(Collectors.toList()); for (StubMapping mapping : listAllStubMappings().getMappings()) { - if (!contains(ids, mapping.getId())) { + if (!ids.contains(mapping.getId())) { removeStubMapping(mapping); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java index 1a36e6223e..4e4126d8de 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java @@ -19,38 +19,55 @@ import static com.github.tomakehurst.wiremock.common.BrowserProxySettings.DEFAULT_CA_KEYSTORE_PATH; import static com.github.tomakehurst.wiremock.common.Limit.UNLIMITED; import static com.github.tomakehurst.wiremock.core.WireMockApp.MAPPINGS_ROOT; -import static com.github.tomakehurst.wiremock.extension.ExtensionLoader.valueAssignableFrom; -import static com.google.common.collect.Lists.transform; -import static com.google.common.collect.Maps.newLinkedHashMap; +import static com.github.tomakehurst.wiremock.http.CaseInsensitiveKey.TO_CASE_INSENSITIVE_KEYS; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.AsynchronousResponseSettings; +import com.github.tomakehurst.wiremock.common.BrowserProxySettings; +import com.github.tomakehurst.wiremock.common.ClasspathFileSource; +import com.github.tomakehurst.wiremock.common.DataTruncationSettings; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.HttpsSettings; +import com.github.tomakehurst.wiremock.common.JettySettings; +import com.github.tomakehurst.wiremock.common.Limit; +import com.github.tomakehurst.wiremock.common.NetworkAddressRules; +import com.github.tomakehurst.wiremock.common.Notifier; +import com.github.tomakehurst.wiremock.common.ProxySettings; +import com.github.tomakehurst.wiremock.common.SingleRootFileSource; +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSourceFactory; import com.github.tomakehurst.wiremock.extension.Extension; -import com.github.tomakehurst.wiremock.extension.ExtensionLoader; +import com.github.tomakehurst.wiremock.extension.ExtensionDeclarations; +import com.github.tomakehurst.wiremock.extension.ExtensionFactory; +import com.github.tomakehurst.wiremock.extension.Extensions; +import com.github.tomakehurst.wiremock.global.GlobalSettings; import com.github.tomakehurst.wiremock.http.CaseInsensitiveKey; import com.github.tomakehurst.wiremock.http.HttpServerFactory; import com.github.tomakehurst.wiremock.http.ThreadPoolFactory; import com.github.tomakehurst.wiremock.http.trafficlistener.DoNothingWiremockNetworkTrafficListener; import com.github.tomakehurst.wiremock.http.trafficlistener.WiremockNetworkTrafficListener; -import com.github.tomakehurst.wiremock.jetty9.JettyHttpServerFactory; -import com.github.tomakehurst.wiremock.jetty9.QueuedThreadPoolFactory; +import com.github.tomakehurst.wiremock.jetty.JettyHttpServerFactory; +import com.github.tomakehurst.wiremock.jetty.QueuedThreadPoolFactory; import com.github.tomakehurst.wiremock.security.Authenticator; import com.github.tomakehurst.wiremock.security.BasicAuthenticator; import com.github.tomakehurst.wiremock.security.NoAuthenticator; import com.github.tomakehurst.wiremock.standalone.JsonFileMappingsSource; import com.github.tomakehurst.wiremock.standalone.MappingsLoader; import com.github.tomakehurst.wiremock.standalone.MappingsSource; +import com.github.tomakehurst.wiremock.store.DefaultStores; +import com.github.tomakehurst.wiremock.store.Stores; import com.github.tomakehurst.wiremock.verification.notmatched.NotMatchedRenderer; import com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer; -import com.google.common.base.Optional; -import com.google.common.collect.Maps; import com.google.common.io.Resources; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; public class WireMockConfiguration implements Options { @@ -82,11 +99,13 @@ public class WireMockConfiguration implements Options { private ProxySettings proxySettings = ProxySettings.NO_PROXY; private FileSource filesRoot = new SingleRootFileSource("src/test/resources"); + private Stores stores; private MappingsSource mappingsSource; + private FilenameMaker filenameMaker; private Notifier notifier = new Slf4jNotifier(false); private boolean requestJournalDisabled = false; - private Optional maxRequestJournalEntries = Optional.absent(); + private Optional maxRequestJournalEntries = Optional.empty(); private List matchingHeaders = emptyList(); private boolean preserveHostHeader; @@ -101,31 +120,42 @@ public class WireMockConfiguration implements Options { private Long jettyStopTimeout; private Long jettyIdleTimeout; - private Map extensions = newLinkedHashMap(); + private ExtensionDeclarations extensions = new ExtensionDeclarations(); private WiremockNetworkTrafficListener networkTrafficListener = new DoNothingWiremockNetworkTrafficListener(); private Authenticator adminAuthenticator = new NoAuthenticator(); private boolean requireHttpsForAdminApi = false; - private NotMatchedRenderer notMatchedRenderer = new PlainTextStubNotMatchedRenderer(); + private Function notMatchedRendererFactory = + PlainTextStubNotMatchedRenderer::new; private boolean asynchronousResponseEnabled; private int asynchronousResponseThreads; private ChunkedEncodingPolicy chunkedEncodingPolicy; private boolean gzipDisabled = false; private boolean stubLoggingDisabled = false; - private String permittedSystemKeys = null; private boolean stubCorsEnabled = false; private boolean disableStrictHttpHeaders; + private boolean proxyPassThrough = true; + private Limit responseBodySizeLimit = UNLIMITED; private NetworkAddressRules proxyTargetRules = NetworkAddressRules.ALLOW_ALL; + private int proxyTimeout = DEFAULT_TIMEOUT; + + private boolean templatingEnabled = true; + private boolean globalTemplating = false; + private Set permittedSystemKeys = null; + private Long maxTemplateCacheEntries = null; + private boolean templateEscapingDisabled = true; + private MappingsSource getMappingsSource() { if (mappingsSource == null) { - mappingsSource = new JsonFileMappingsSource(filesRoot.child(MAPPINGS_ROOT)); + mappingsSource = + new JsonFileMappingsSource(filesRoot.child(MAPPINGS_ROOT), getFilenameMaker()); } return mappingsSource; @@ -139,6 +169,14 @@ public static WireMockConfiguration options() { return wireMockConfig(); } + public WireMockConfiguration proxyPassThrough(boolean proxyPassThrough) { + this.proxyPassThrough = proxyPassThrough; + GlobalSettings newSettings = + getStores().getSettingsStore().get().copy().proxyPassThrough(proxyPassThrough).build(); + getStores().getSettingsStore().set(newSettings); + return this; + } + public WireMockConfiguration timeout(int timeout) { this.asyncResponseTimeout = timeout; return this; @@ -149,6 +187,11 @@ public WireMockConfiguration port(int portNumber) { return this; } + public WireMockConfiguration filenameTemplate(String filenameTemplate) { + this.filenameMaker = new FilenameMaker(filenameTemplate); + return this; + } + public WireMockConfiguration dynamicPort() { this.portNumber = DYNAMIC_PORT; return this; @@ -285,6 +328,11 @@ public WireMockConfiguration proxyVia(ProxySettings proxySettings) { return this; } + public WireMockConfiguration withStores(Stores stores) { + this.stores = stores; + return this; + } + public WireMockConfiguration withRootDirectory(String path) { this.filesRoot = new SingleRootFileSource(path); return this; @@ -338,7 +386,8 @@ public WireMockConfiguration maxRequestJournalEntries(int maxRequestJournalEntri } public WireMockConfiguration recordRequestHeadersForMatching(List headers) { - this.matchingHeaders = transform(headers, CaseInsensitiveKey.TO_CASE_INSENSITIVE_KEYS); + this.matchingHeaders = + headers.stream().map(TO_CASE_INSENSITIVE_KEYS).collect(Collectors.toUnmodifiableList()); return this; } @@ -353,17 +402,22 @@ public WireMockConfiguration proxyHostHeader(String hostHeaderValue) { } public WireMockConfiguration extensions(String... classNames) { - extensions.putAll(ExtensionLoader.load(classNames)); + extensions.add(classNames); return this; } public WireMockConfiguration extensions(Extension... extensionInstances) { - extensions.putAll(ExtensionLoader.asMap(asList(extensionInstances))); + extensions.add(extensionInstances); + return this; + } + + public WireMockConfiguration extensions(ExtensionFactory... extensionFactories) { + extensions.add(extensionFactories); return this; } public WireMockConfiguration extensions(Class... classes) { - extensions.putAll(ExtensionLoader.load(classes)); + extensions.add(classes); return this; } @@ -397,8 +451,9 @@ public WireMockConfiguration requireHttpsForAdminApi() { return this; } - public WireMockConfiguration notMatchedRenderer(NotMatchedRenderer notMatchedRenderer) { - this.notMatchedRenderer = notMatchedRenderer; + public WireMockConfiguration notMatchedRendererFactory( + Function notMatchedRendererFactory) { + this.notMatchedRendererFactory = notMatchedRendererFactory; return this; } @@ -462,6 +517,47 @@ public WireMockConfiguration limitProxyTargets(NetworkAddressRules proxyTargetRu return this; } + public WireMockConfiguration disableOptimizeXmlFactoriesLoading( + boolean disableOptimizeXmlFactoriesLoading) { + this.disableOptimizeXmlFactoriesLoading = disableOptimizeXmlFactoriesLoading; + return this; + } + + public WireMockConfiguration maxLoggedResponseSize(int maxSize) { + this.responseBodySizeLimit = new Limit(maxSize); + return this; + } + + public WireMockConfiguration limitProxyTargets(NetworkAddressRules proxyTargetRules) { + this.proxyTargetRules = proxyTargetRules; + return this; + } + + public WireMockConfiguration proxyTimeout(int proxyTimeout) { + this.proxyTimeout = proxyTimeout; + return this; + } + + public WireMockConfiguration templatingEnabled(boolean templatingEnabled) { + this.templatingEnabled = templatingEnabled; + return this; + } + + public WireMockConfiguration globalTemplating(boolean globalTemplating) { + this.globalTemplating = globalTemplating; + return this; + } + + public WireMockConfiguration withPermittedSystemKeys(String... systemKeys) { + this.permittedSystemKeys = Set.of(systemKeys); + return this; + } + + public WireMockConfiguration withTemplateEscapingDisabled(boolean templateEscapingDisabled) { + this.templateEscapingDisabled = templateEscapingDisabled; + return this; + } + @Override public int portNumber() { return portNumber; @@ -515,6 +611,15 @@ public ProxySettings proxyVia() { return proxySettings; } + @Override + public Stores getStores() { + if (stores == null) { + stores = new DefaultStores(filesRoot); + } + + return stores; + } + @Override public FileSource filesRoot() { return filesRoot; @@ -550,6 +655,11 @@ public String bindAddress() { return bindAddress; } + @Override + public FilenameMaker getFilenameMaker() { + return filenameMaker; + } + @Override public List matchingHeaders() { return matchingHeaders; @@ -576,9 +686,8 @@ public String proxyHostHeader() { } @Override - @SuppressWarnings("unchecked") - public Map extensionsOfType(final Class extensionType) { - return (Map) Maps.filterEntries(extensions, valueAssignableFrom(extensionType)); + public ExtensionDeclarations getDeclaredExtensions() { + return extensions; } @Override @@ -597,8 +706,8 @@ public boolean getHttpsRequiredForAdminApi() { } @Override - public NotMatchedRenderer getNotMatchedRenderer() { - return notMatchedRenderer; + public Function getNotMatchedRendererFactory() { + return notMatchedRendererFactory; } @Override @@ -673,4 +782,34 @@ public BrowserProxySettings browserProxySettings() { public NetworkAddressRules getProxyTargetRules() { return proxyTargetRules; } + + @Override + public int proxyTimeout() { + return proxyTimeout; + } + + @Override + public boolean getResponseTemplatingEnabled() { + return templatingEnabled; + } + + @Override + public boolean getResponseTemplatingGlobal() { + return globalTemplating; + } + + @Override + public Long getMaxTemplateCacheEntries() { + return maxTemplateCacheEntries; + } + + @Override + public Set getTemplatePermittedSystemKeys() { + return permittedSystemKeys; + } + + @Override + public boolean getTemplateEscapingDisabled() { + return templateEscapingDisabled; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServer.java b/src/main/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServer.java index 580900d831..8cb6664fa6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,7 +111,8 @@ public int httpsPort() { private Response handleRequest(Request request, AbstractRequestHandler handler) { CompletableFuture responseFuture = new CompletableFuture<>(); - handler.handle(request, (ignored, response) -> responseFuture.complete(response)); + handler.handle( + request, (ignored, response, attributes) -> responseFuture.complete(response), null); try { Response response = responseFuture.get(timeout, TimeUnit.MILLISECONDS); diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionDeclarations.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionDeclarations.java new file mode 100644 index 0000000000..6a33069f91 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionDeclarations.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import static java.util.Arrays.asList; + +import java.util.*; + +public class ExtensionDeclarations { + + private final List classNames; + private final List> classes; + private final Map instances; + private final List factories; + + public ExtensionDeclarations() { + this.classNames = new ArrayList<>(); + this.classes = new ArrayList<>(); + this.instances = new LinkedHashMap<>(); + this.factories = new ArrayList<>(); + } + + public void add(String... classNames) { + this.classNames.addAll(asList(classNames)); + } + + public void add(Extension... extensionInstances) { + Arrays.stream(extensionInstances).forEach(e -> instances.put(e.getName(), e)); + } + + public void add(Class... classes) { + this.classes.addAll(asList(classes)); + } + + public void add(ExtensionFactory... factories) { + this.factories.addAll(asList(factories)); + } + + public List getClassNames() { + return classNames; + } + + public List> getClasses() { + return classes; + } + + public Map getInstances() { + return instances; + } + + public List getFactories() { + return factories; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionFactory.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionFactory.java new file mode 100644 index 0000000000..78d40a7f81 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionFactory.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import java.util.List; + +public interface ExtensionFactory { + List create(WireMockServices services); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionLoader.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionLoader.java index 65aadf736c..0671f51368 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionLoader.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ExtensionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,72 +16,66 @@ package com.github.tomakehurst.wiremock.extension; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.google.common.collect.FluentIterable.from; -import static java.util.Arrays.asList; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Maps; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; public class ExtensionLoader { + private ExtensionLoader() {} + @SuppressWarnings("unchecked") public static Map loadExtension(String... classNames) { return (Map) - asMap(from(asList(classNames)).transform(toClasses()).transform(toExtensions())); + asMap( + Arrays.stream(classNames) + .map(toClasses()) + .map(toExtensions()) + .collect(Collectors.toList())); } public static Map load(String... classNames) { return loadExtension(classNames); } - public static Map asMap(Iterable extensions) { - return Maps.uniqueIndex( - extensions, - new Function() { - public String apply(Extension extension) { - return extension.getName(); - } - }); + public static Map asMap(List extensions) { + return extensions.stream() + .map(extension -> Map.entry(extension.getName(), extension)) + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } + @SafeVarargs public static Map load(Class... classes) { - return asMap(from(asList(classes)).transform(toExtensions())); + return asMap(Arrays.stream(classes).map(toExtensions()).collect(Collectors.toList())); } private static Function, Extension> toExtensions() { - return new Function, Extension>() { - @SuppressWarnings("unchecked") - public Extension apply(Class extensionClass) { - try { - return extensionClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - return throwUnchecked(e, Extension.class); - } + return extensionClass -> { + try { + return extensionClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + return throwUnchecked(e, Extension.class); } }; } + @SuppressWarnings("unchecked") private static Function> toClasses() { - return new Function>() { - @SuppressWarnings("unchecked") - public Class apply(String className) { - try { - return (Class) Class.forName(className); - } catch (ClassNotFoundException e) { - return throwUnchecked(e, Class.class); - } + return className -> { + try { + return (Class) Class.forName(className); + } catch (ClassNotFoundException e) { + return throwUnchecked(e, Class.class); } }; } public static Predicate> valueAssignableFrom( final Class extensionType) { - return new Predicate>() { - public boolean apply(Map.Entry input) { - return extensionType.isAssignableFrom(input.getValue().getClass()); - } - }; + return input -> extensionType.isAssignableFrom(input.getValue().getClass()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/Extensions.java b/src/main/java/com/github/tomakehurst/wiremock/extension/Extensions.java new file mode 100644 index 0000000000..a1fba450ff --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/Extensions.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static com.github.tomakehurst.wiremock.extension.ExtensionLoader.valueAssignableFrom; +import static java.util.stream.Collectors.toMap; + +import com.github.jknack.handlebars.Helper; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; +import com.github.tomakehurst.wiremock.store.Stores; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Extensions implements WireMockServices { + + public static final Extensions NONE = + new Extensions(new ExtensionDeclarations(), null, null, null, null); + + private final ExtensionDeclarations extensionDeclarations; + private final Admin admin; + + private final Options options; + private final Stores stores; + private final FileSource files; + + private TemplateEngine templateEngine; + + private final Map loadedExtensions; + + public Extensions( + ExtensionDeclarations extensionDeclarations, + Admin admin, + Options options, + Stores stores, + FileSource files) { + this.extensionDeclarations = extensionDeclarations; + this.admin = admin; + this.options = options; + this.stores = stores; + this.files = files; + + loadedExtensions = new LinkedHashMap<>(); + } + + public void load() { + Stream.concat( + extensionDeclarations.getClassNames().stream().map(Extensions::loadClass), + extensionDeclarations.getClasses().stream()) + .map(Extensions::load) + .forEach( + extension -> { + if (loadedExtensions.containsKey(extension.getName())) { + throw new IllegalArgumentException( + "Duplicate extension name: " + extension.getName()); + } + loadedExtensions.put(extension.getName(), extension); + }); + + loadedExtensions.putAll(extensionDeclarations.getInstances()); + + loadedExtensions.putAll( + loadExtensionsAsServices().collect(toMap(Extension::getName, Function.identity()))); + + final Stream allFactories = + Stream.concat( + extensionDeclarations.getFactories().stream(), loadExtensionFactoriesAsServices()); + loadedExtensions.putAll( + allFactories + .map(factory -> factory.create(Extensions.this)) + .flatMap(List::stream) + .collect(toMap(Extension::getName, Function.identity()))); + + configureTemplating(); + } + + private Stream loadExtensionsAsServices() { + final ServiceLoader loader = ServiceLoader.load(Extension.class); + return loader.stream().map(ServiceLoader.Provider::get); + } + + private Stream loadExtensionFactoriesAsServices() { + final ServiceLoader loader = ServiceLoader.load(ExtensionFactory.class); + return loader.stream().map(ServiceLoader.Provider::get); + } + + private void configureTemplating() { + final Map> helpers = + ofType(TemplateHelperProviderExtension.class).values().stream() + .map(TemplateHelperProviderExtension::provideTemplateHelpers) + .map(Map::entrySet) + .flatMap(Set::stream) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + + templateEngine = + new TemplateEngine( + helpers, + options.getMaxTemplateCacheEntries(), + options.getTemplatePermittedSystemKeys(), + options.getTemplateEscapingDisabled()); + + final List templateModelProviders = + new ArrayList<>(ofType(TemplateModelDataProviderExtension.class).values()); + + if (options.getResponseTemplatingEnabled()) { + final ResponseTemplateTransformer responseTemplateTransformer = + new ResponseTemplateTransformer( + getTemplateEngine(), + options.getResponseTemplatingGlobal(), + getFiles(), + templateModelProviders); + loadedExtensions.put(responseTemplateTransformer.getName(), responseTemplateTransformer); + } + } + + @Override + public Admin getAdmin() { + return admin; + } + + @Override + public Stores getStores() { + return stores; + } + + @Override + public FileSource getFiles() { + return files; + } + + @Override + public Options getOptions() { + return options; + } + + @Override + public Extensions getExtensions() { + return this; + } + + @Override + public TemplateEngine getTemplateEngine() { + return templateEngine; + } + + public int getCount() { + return loadedExtensions.size(); + } + + @SuppressWarnings("unchecked") + private static Class loadClass(String className) { + try { + return (Class) Class.forName(className); + } catch (ClassNotFoundException e) { + return throwUnchecked(e, Class.class); + } + } + + public static Extension load(Class extensionClass) { + try { + return extensionClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + return throwUnchecked(e, Extension.class); + } + } + + @SuppressWarnings("unchecked") + public Map ofType(Class extensionType) { + return (Map) + Collections.unmodifiableMap( + loadedExtensions.entrySet().stream() + .filter(valueAssignableFrom(extensionType)) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (entry1, entry2) -> entry1, + LinkedHashMap::new))); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/GlobalSettingsListener.java b/src/main/java/com/github/tomakehurst/wiremock/extension/GlobalSettingsListener.java index 971a700d19..f8c69ecc72 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/GlobalSettingsListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/GlobalSettingsListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ public interface GlobalSettingsListener extends Extension { - void beforeGlobalSettingsUpdated(GlobalSettings oldSettings, GlobalSettings newSettings); + default void beforeGlobalSettingsUpdated( + GlobalSettings oldSettings, GlobalSettings newSettings) {} - void afterGlobalSettingsUpdated(GlobalSettings oldSettings, GlobalSettings newSettings); + default void afterGlobalSettingsUpdated(GlobalSettings oldSettings, GlobalSettings newSettings) {} } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/Parameters.java b/src/main/java/com/github/tomakehurst/wiremock/extension/Parameters.java index 4b62cb1e0f..dc97779367 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/Parameters.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/Parameters.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.common.Metadata; -import com.google.common.collect.ImmutableMap; import java.util.LinkedHashMap; import java.util.Map; @@ -34,7 +33,7 @@ public static Parameters from(Map parameterMap) { } public static Parameters one(String name, Object value) { - return from(ImmutableMap.of(name, value)); + return from(Map.of(name, value)); } public static Parameters of(T myData) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java b/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java index 761bc32788..4d2185b893 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +/** @deprecated Use {@link ServeEventListener} instead. */ +@Deprecated public abstract class PostServeAction implements Extension { /** diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java index 7ff87c008d..8d6fe4ed8c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +@Deprecated +/** @deprecated Use {@link ResponseDefinitionTransformerV2} instead */ public abstract class ResponseDefinitionTransformer extends AbstractTransformer { diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformerV2.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformerV2.java new file mode 100644 index 0000000000..8f427aad81 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformerV2.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public interface ResponseDefinitionTransformerV2 extends Extension { + + ResponseDefinition transform(ServeEvent serveEvent); + + default boolean applyGlobally() { + return true; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java index 39173ae26f..086622946f 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; +@Deprecated +/** @deprecated Use {@link ResponseTransformerV2} instead */ public abstract class ResponseTransformer extends AbstractTransformer { @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformerV2.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformerV2.java new file mode 100644 index 0000000000..74644fd631 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformerV2.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.github.tomakehurst.wiremock.http.Response; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public interface ResponseTransformerV2 extends Extension { + + Response transform(Response response, ServeEvent serveEvent); + + default boolean applyGlobally() { + return true; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListener.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListener.java new file mode 100644 index 0000000000..011e396db6 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListener.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public interface ServeEventListener extends Extension { + + enum RequestPhase { + BEFORE_MATCH, + AFTER_MATCH, + BEFORE_RESPONSE_SENT, + AFTER_COMPLETE + } + + default void onEvent(RequestPhase requestPhase, ServeEvent serveEvent, Parameters parameters) { + switch (requestPhase) { + case BEFORE_MATCH: + beforeMatch(serveEvent, parameters); + break; + case AFTER_MATCH: + afterMatch(serveEvent, parameters); + break; + case BEFORE_RESPONSE_SENT: + beforeResponseSent(serveEvent, parameters); + break; + case AFTER_COMPLETE: + afterComplete(serveEvent, parameters); + break; + } + } + + default void beforeMatch(ServeEvent serveEvent, Parameters parameters) {} + + default void afterMatch(ServeEvent serveEvent, Parameters parameters) {} + + default void beforeResponseSent(ServeEvent serveEvent, Parameters parameters) {} + + default void afterComplete(ServeEvent serveEvent, Parameters parameters) {} + + default boolean applyGlobally() { + return true; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListenerDefinition.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListenerDefinition.java new file mode 100644 index 0000000000..04b19260cf --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ServeEventListenerDefinition.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Set; + +public class ServeEventListenerDefinition { + + private final String name; + private final Set requestPhases; + private final Parameters parameters; + + public ServeEventListenerDefinition(String name, Parameters parameters) { + this(name, null, parameters); + } + + public ServeEventListenerDefinition( + @JsonProperty("name") String name, + @JsonProperty("requestPhases") Set requestPhases, + @JsonProperty("parameters") Parameters parameters) { + this.name = name; + this.requestPhases = requestPhases; + this.parameters = parameters; + } + + public String getName() { + return name; + } + + public Set getRequestPhases() { + return requestPhases; + } + + public Parameters getParameters() { + return parameters; + } + + public boolean shouldFireFor(ServeEventListener.RequestPhase requestPhase) { + return requestPhases == null || requestPhases.contains(requestPhase); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/StubLifecycleListener.java b/src/main/java/com/github/tomakehurst/wiremock/extension/StubLifecycleListener.java index 921cbef6f5..248f8875de 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/StubLifecycleListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/StubLifecycleListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,19 +19,19 @@ public interface StubLifecycleListener extends Extension { - void beforeStubCreated(StubMapping stub); + default void beforeStubCreated(StubMapping stub) {} - void afterStubCreated(StubMapping stub); + default void afterStubCreated(StubMapping stub) {} - void beforeStubEdited(StubMapping oldStub, StubMapping newStub); + default void beforeStubEdited(StubMapping oldStub, StubMapping newStub) {} - void afterStubEdited(StubMapping oldStub, StubMapping newStub); + default void afterStubEdited(StubMapping oldStub, StubMapping newStub) {} - void beforeStubRemoved(StubMapping stub); + default void beforeStubRemoved(StubMapping stub) {} - void afterStubRemoved(StubMapping stub); + default void afterStubRemoved(StubMapping stub) {} - void beforeStubsReset(); + default void beforeStubsReset() {} - void afterStubsReset(); + default void afterStubsReset() {} } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/TemplateHelperProviderExtension.java b/src/main/java/com/github/tomakehurst/wiremock/extension/TemplateHelperProviderExtension.java new file mode 100644 index 0000000000..0eb86ab40c --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/TemplateHelperProviderExtension.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.github.jknack.handlebars.Helper; +import java.util.Map; + +public interface TemplateHelperProviderExtension extends Extension { + Map> provideTemplateHelpers(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/TemplateModelDataProviderExtension.java b/src/main/java/com/github/tomakehurst/wiremock/extension/TemplateModelDataProviderExtension.java new file mode 100644 index 0000000000..8e1ea8d66a --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/TemplateModelDataProviderExtension.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import java.util.Map; + +public interface TemplateModelDataProviderExtension extends Extension { + Map provideTemplateModelData(ServeEvent serveEvent); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/WireMockServices.java b/src/main/java/com/github/tomakehurst/wiremock/extension/WireMockServices.java new file mode 100644 index 0000000000..beab8aa846 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/WireMockServices.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension; + +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; +import com.github.tomakehurst.wiremock.store.Stores; + +public interface WireMockServices { + + Admin getAdmin(); + + Stores getStores(); + + FileSource getFiles(); + + Options getOptions(); + + Extensions getExtensions(); + + TemplateEngine getTemplateEngine(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/AdminRequestFilterV2.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/AdminRequestFilterV2.java new file mode 100644 index 0000000000..3b62648030 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/AdminRequestFilterV2.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension.requestfilter; + +public interface AdminRequestFilterV2 extends RequestFilterV2 { + + @Override + default boolean applyToAdmin() { + return true; + } + + @Override + default boolean applyToStubs() { + return false; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/FilterProcessor.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/FilterProcessor.java index bb8b48b841..048de07abe 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/FilterProcessor.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/FilterProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,36 @@ package com.github.tomakehurst.wiremock.extension.requestfilter; import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.List; public class FilterProcessor { - public static RequestFilterAction processFilters( + private final List v1RequestFilters; + private final List v2RequestFilters; + + public FilterProcessor( + List v1RequestFilters, + List v2RequestFilters) { + this.v1RequestFilters = v1RequestFilters; + this.v2RequestFilters = v2RequestFilters; + } + + public RequestFilterAction processFilters(Request request, ServeEvent serveEvent) { + RequestFilterAction requestFilterAction = + processV1Filters(request, v1RequestFilters, RequestFilterAction.continueWith(request)); + if (requestFilterAction instanceof ContinueAction) { + return processV2Filters(request, serveEvent, v2RequestFilters, requestFilterAction); + } else { + return requestFilterAction; + } + } + + private RequestFilterAction processV1Filters( Request request, List requestFilters, RequestFilterAction lastAction) { + if (requestFilters.isEmpty()) { return lastAction; } @@ -32,9 +54,34 @@ public static RequestFilterAction processFilters( if (action instanceof ContinueAction) { Request newRequest = ((ContinueAction) action).getRequest(); - return processFilters(newRequest, requestFilters.subList(1, requestFilters.size()), action); + return processV1Filters(newRequest, requestFilters.subList(1, requestFilters.size()), action); } return action; } + + private RequestFilterAction processV2Filters( + Request request, + ServeEvent serveEvent, + List v2RequestFilters, + RequestFilterAction lastAction) { + + if (v2RequestFilters.isEmpty()) { + return lastAction; + } + + RequestFilterAction action = v2RequestFilters.get(0).filter(request, serveEvent); + + if (action instanceof ContinueAction) { + Request newRequest = ((ContinueAction) action).getRequest(); + return processV2Filters( + newRequest, serveEvent, v2RequestFilters.subList(1, v2RequestFilters.size()), action); + } + + return action; + } + + public boolean hasAnyFilters() { + return !v1RequestFilters.isEmpty() || !v2RequestFilters.isEmpty(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java index 70933f3509..42fe76a39c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import com.github.tomakehurst.wiremock.extension.Extension; import com.github.tomakehurst.wiremock.http.Request; +@Deprecated +/** @deprecated Use {@link RequestFilterV2} instead */ public interface RequestFilter extends Extension { RequestFilterAction filter(Request request); diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilterV2.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilterV2.java new file mode 100644 index 0000000000..9e407e29c1 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilterV2.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension.requestfilter; + +import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public interface RequestFilterV2 extends Extension { + + RequestFilterAction filter(Request request, ServeEvent serveEvent); + + boolean applyToAdmin(); + + boolean applyToStubs(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapper.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapper.java index f708cbdb92..06d2f26ed1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2022 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,13 @@ package com.github.tomakehurst.wiremock.extension.requestfilter; import static com.github.tomakehurst.wiremock.common.Encoding.encodeBase64; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Maps.newHashMap; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static org.apache.commons.lang3.StringUtils.countMatches; import static org.apache.commons.lang3.StringUtils.ordinalIndexOf; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; import java.util.*; +import java.util.stream.Collectors; public class RequestWrapper implements Request { @@ -50,12 +44,12 @@ public RequestWrapper(Request delegate) { delegate, null, null, - Collections.emptyList(), - Collections.emptyList(), - Collections.>>emptyMap(), - Collections.emptyMap(), - Collections.emptyList(), - Collections.>emptyMap(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyList(), + Collections.emptyMap(), null, null); } @@ -111,7 +105,7 @@ public String getAbsoluteUrl() { @Override public RequestMethod getMethod() { - return firstNonNull(method, delegate.getMethod()); + return getFirstNonNull(method, delegate.getMethod()); } @Override @@ -151,33 +145,25 @@ public ContentTypeHeader contentTypeHeader() { @Override public HttpHeaders getHeaders() { - Collection existingHeaders = delegate.getHeaders().all(); + List existingHeaders = new ArrayList<>(delegate.getHeaders().all()); + existingHeaders.addAll(addedHeaders); List combinedHeaders = - from(existingHeaders) - .append(addedHeaders) - .filter( - new Predicate() { - @Override - public boolean apply(HttpHeader httpHeader) { - return !removedHeaders.contains(httpHeader.key()); - } - }) - .transform( - new Function() { - @Override - public HttpHeader apply(HttpHeader httpHeader) { - if (headerTransformers.containsKey(httpHeader.caseInsensitiveKey())) { - FieldTransformer> transformer = - headerTransformers.get(httpHeader.caseInsensitiveKey()); - List newValues = transformer.transform(httpHeader.values()); - return new HttpHeader(httpHeader.key(), newValues); - } - - return httpHeader; + existingHeaders.stream() + .filter(httpHeader -> !removedHeaders.contains(httpHeader.key())) + .map( + httpHeader -> { + if (headerTransformers.containsKey(httpHeader.caseInsensitiveKey())) { + FieldTransformer> transformer = + headerTransformers.get(httpHeader.caseInsensitiveKey()); + List newValues = transformer.transform(httpHeader.values()); + return new HttpHeader(httpHeader.key(), newValues); } + + return httpHeader; }) - .toList(); + .collect(Collectors.toList()); + return new HttpHeaders(combinedHeaders); } @@ -193,7 +179,7 @@ public Set getAllHeaderKeys() { @Override public Map getCookies() { - ImmutableMap.Builder builder = ImmutableMap.builder(); + Map cookieMap = new HashMap<>(); for (Map.Entry entry : delegate.getCookies().entrySet()) { Cookie newCookie = cookieTransformers.containsKey(entry.getKey()) @@ -201,13 +187,13 @@ public Map getCookies() { : entry.getValue(); if (!cookiesToRemove.contains(entry.getKey())) { - builder.put(entry.getKey(), newCookie); + cookieMap.put(entry.getKey(), newCookie); } } - builder.putAll(additionalCookies); + cookieMap.putAll(additionalCookies); - return builder.build(); + return Collections.unmodifiableMap(cookieMap); } @Override @@ -215,6 +201,16 @@ public QueryParameter queryParameter(String key) { return delegate.queryParameter(key); } + @Override + public FormParameter formParameter(String key) { + return delegate.formParameter(key); + } + + @Override + public Map formParameters() { + return delegate.formParameters(); + } + @Override public byte[] getBody() { if (bodyTransformer != null) { @@ -249,15 +245,9 @@ public Collection getParts() { return delegate.getParts(); } - return from(delegate.getParts()) - .transform( - new Function() { - @Override - public Part apply(Part part) { - return multipartTransformer.transform(part); - } - }) - .toList(); + return delegate.getParts().stream() + .map(multipartTransformer::transform) + .collect(Collectors.toList()); } @Override @@ -289,14 +279,14 @@ public static class Builder { private RequestMethod requestMethod; private FieldTransformer absoluteUrlTransformer; - private final List additionalHeaders = newArrayList(); - private final List headersToRemove = newArrayList(); + private final List additionalHeaders = new ArrayList<>(); + private final List headersToRemove = new ArrayList<>(); private final Map>> headerTransformers = - newHashMap(); + new HashMap<>(); - private final Map additionalCookies = newHashMap(); - private final List cookiesToRemove = newArrayList(); - private final Map> cookieTransformers = newHashMap(); + private final Map additionalCookies = new HashMap<>(); + private final List cookiesToRemove = new ArrayList<>(); + private final Map> cookieTransformers = new HashMap<>(); private FieldTransformer bodyTransformer; private FieldTransformer mutlipartTransformer; diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/StubRequestFilterV2.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/StubRequestFilterV2.java new file mode 100644 index 0000000000..0e9490480f --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/StubRequestFilterV2.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension.requestfilter; + +public interface StubRequestFilterV2 extends RequestFilterV2 { + + @Override + default boolean applyToAdmin() { + return false; + } + + @Override + default boolean applyToStubs() { + return true; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/HandlebarsOptimizedTemplate.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/HandlebarsOptimizedTemplate.java index 9044567e5b..c48674636e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/HandlebarsOptimizedTemplate.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/HandlebarsOptimizedTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import com.github.jknack.handlebars.Template; import com.github.tomakehurst.wiremock.common.Exceptions; import java.io.IOException; +import java.io.Writer; +import org.apache.commons.io.output.StringBuilderWriter; public class HandlebarsOptimizedTemplate { @@ -63,8 +65,16 @@ public String apply(Object contextData) { final RenderCache renderCache = new RenderCache(); Context context = Context.newBuilder(contextData).combine("renderCache", renderCache).build(); - return startContent - + Exceptions.uncheck(() -> template.apply(context), String.class) - + endContent; + return startContent + applyTemplate(context) + endContent; + } + + private String applyTemplate(Context context) { + return Exceptions.uncheck( + () -> { + Writer stringWriter = new StringBuilderWriter(template.text().length() * 2); + template.apply(context, stringWriter); + return stringWriter.toString(); + }, + String.class); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java index b32ace5dc4..ebfea4195d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,14 @@ import com.github.tomakehurst.wiremock.common.ListOrSingle; import com.github.tomakehurst.wiremock.common.Urls; -import com.github.tomakehurst.wiremock.http.MultiValue; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; import com.github.tomakehurst.wiremock.http.QueryParameter; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; -import com.google.common.base.Function; -import com.google.common.collect.Maps; import java.net.URI; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; @Deprecated /** @deprecated Use the accessors on {@link RequestTemplateModel} */ @@ -35,6 +35,9 @@ public class RequestLine { private final int port; private final Map> query; private final String url; + private final String clientIp; + + private final PathTemplate pathTemplate; private RequestLine( RequestMethod method, @@ -42,35 +45,44 @@ private RequestLine( String host, int port, String url, - Map> query) { + String clientIp, + Map> query, + PathTemplate pathTemplate) { this.method = method; this.scheme = scheme; this.host = host; this.port = port; this.url = url; + this.clientIp = clientIp; this.query = query; + this.pathTemplate = pathTemplate; } - public static RequestLine fromRequest(final Request request) { + public static RequestLine fromRequest(final Request request, final PathTemplate pathTemplate) { URI url = URI.create(request.getUrl()); Map rawQuery = Urls.splitQuery(url); Map> adaptedQuery = - Maps.transformValues(rawQuery, TO_TEMPLATE_MODEL); + rawQuery.entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), ListOrSingle.of(entry.getValue().values()))) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + return new RequestLine( request.getMethod(), request.getScheme(), request.getHost(), request.getPort(), request.getUrl(), - adaptedQuery); + request.getClientIp(), + adaptedQuery, + pathTemplate); } public RequestMethod getMethod() { return method; } - public UrlPath getPathSegments() { - return new UrlPath(url); + public Object getPathSegments() { + return pathTemplate == null ? new UrlPath(url) : new TemplatedUrlPath(url, pathTemplate); } public String getPath() { @@ -103,15 +115,11 @@ public String getBaseUrl() { return scheme + "://" + host + portPart; } + public String getClientIp() { + return this.clientIp; + } + private boolean isStandardPort(String scheme, int port) { return (scheme.equals("http") && port == 80) || (scheme.equals("https") && port == 443); } - - private static final Function> TO_TEMPLATE_MODEL = - new Function>() { - @Override - public ListOrSingle apply(MultiValue input) { - return ListOrSingle.of(input.values()); - } - }; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestTemplateModel.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestTemplateModel.java index efafd95920..6b5a724b51 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestTemplateModel.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestTemplateModel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,9 @@ package com.github.tomakehurst.wiremock.extension.responsetemplating; import com.github.tomakehurst.wiremock.common.ListOrSingle; -import com.github.tomakehurst.wiremock.http.Cookie; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; -import com.google.common.base.Function; import com.google.common.collect.Maps; import java.util.Map; import java.util.TreeMap; @@ -43,26 +42,17 @@ protected RequestTemplateModel( } public static RequestTemplateModel from(final Request request) { - RequestLine requestLine = RequestLine.fromRequest(request); + return from(request, null); + } + + public static RequestTemplateModel from(final Request request, final PathTemplate pathTemplate) { + RequestLine requestLine = RequestLine.fromRequest(request, pathTemplate); Map> adaptedHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); adaptedHeaders.putAll( Maps.toMap( - request.getAllHeaderKeys(), - new Function>() { - @Override - public ListOrSingle apply(String input) { - return ListOrSingle.of(request.header(input).values()); - } - })); + request.getAllHeaderKeys(), input -> ListOrSingle.of(request.header(input).values()))); Map> adaptedCookies = - Maps.transformValues( - request.getCookies(), - new Function>() { - @Override - public ListOrSingle apply(Cookie cookie) { - return ListOrSingle.of(cookie.getValues()); - } - }); + Maps.transformValues(request.getCookies(), cookie -> ListOrSingle.of(cookie.getValues())); return new RequestTemplateModel( requestLine, adaptedHeaders, adaptedCookies, request.getBodyAsString()); @@ -76,11 +66,11 @@ public RequestMethod getMethod() { return requestLine.getMethod(); } - public UrlPath getPathSegments() { + public Object getPathSegments() { return requestLine.getPathSegments(); } - public UrlPath getPath() { + public Object getPath() { return requestLine.getPathSegments(); } @@ -119,4 +109,8 @@ public Map> getCookies() { public String getBody() { return body; } + + public String getClientIp() { + return requestLine.getClientIp(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformer.java index 1bc5b95278..2d24bd0267 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,61 +15,43 @@ */ package com.github.tomakehurst.wiremock.extension.responsetemplating; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.client.WireMock.serverError; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; -import com.github.jknack.handlebars.Handlebars; -import com.github.jknack.handlebars.Helper; +import com.github.jknack.handlebars.HandlebarsException; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.common.TextFile; -import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer; -import com.github.tomakehurst.wiremock.extension.StubLifecycleListener; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; +import com.github.tomakehurst.wiremock.extension.*; import com.github.tomakehurst.wiremock.http.*; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; -public class ResponseTemplateTransformer extends ResponseDefinitionTransformer - implements StubLifecycleListener { +public class ResponseTemplateTransformer + implements StubLifecycleListener, ResponseDefinitionTransformerV2 { public static final String NAME = "response-template"; private final boolean global; + private final FileSource files; private final TemplateEngine templateEngine; - public static Builder builder() { - return new Builder(); - } - - public ResponseTemplateTransformer(boolean global) { - this(global, Collections.emptyMap()); - } - - public ResponseTemplateTransformer(boolean global, String helperName, Helper helper) { - this(global, ImmutableMap.of(helperName, helper)); - } - - public ResponseTemplateTransformer(boolean global, Map> helpers) { - this(global, new Handlebars(), helpers, null, null); - } + private final List templateModelDataProviders; public ResponseTemplateTransformer( + TemplateEngine templateEngine, boolean global, - Handlebars handlebars, - Map> helpers, - Long maxCacheEntries, - Set permittedSystemKeys) { + FileSource files, + List templateModelDataProviders) { + this.templateEngine = templateEngine; this.global = global; - this.templateEngine = - new TemplateEngine(handlebars, helpers, maxCacheEntries, permittedSystemKeys); + this.files = files; + this.templateModelDataProviders = templateModelDataProviders; } @Override @@ -83,105 +65,131 @@ public String getName() { } @Override - public ResponseDefinition transform( - Request request, - final ResponseDefinition responseDefinition, - FileSource files, - Parameters parameters) { - ResponseDefinitionBuilder newResponseDefBuilder = - ResponseDefinitionBuilder.like(responseDefinition); - - final ImmutableMap model = - ImmutableMap.builder() - .put("parameters", firstNonNull(parameters, Collections.emptyMap())) - .put("request", RequestTemplateModel.from(request)) - .putAll(addExtraModelElements(request, responseDefinition, files, parameters)) - .build(); - - if (responseDefinition.specifiesTextBodyContent()) { - boolean isJsonBody = responseDefinition.getReponseBody().isJson(); - HandlebarsOptimizedTemplate bodyTemplate = - templateEngine.getTemplate( - HttpTemplateCacheKey.forInlineBody(responseDefinition), - responseDefinition.getTextBody()); - applyTemplatedResponseBody(newResponseDefBuilder, model, bodyTemplate, isJsonBody); - } else if (responseDefinition.specifiesBodyFile()) { - HandlebarsOptimizedTemplate filePathTemplate = - templateEngine.getUncachedTemplate(responseDefinition.getBodyFileName()); - String compiledFilePath = uncheckedApplyTemplate(filePathTemplate, model); - - boolean disableBodyFileTemplating = parameters.getBoolean("disableBodyFileTemplating", false); - if (disableBodyFileTemplating) { - newResponseDefBuilder.withBodyFile(compiledFilePath); - } else { - TextFile file = files.getTextFileNamed(compiledFilePath); + public ResponseDefinition transform(ServeEvent serveEvent) { + try { + final Request request = serveEvent.getRequest(); + final ResponseDefinition responseDefinition = serveEvent.getResponseDefinition(); + final Parameters parameters = + getFirstNonNull(responseDefinition.getTransformerParameters(), Parameters.empty()); + + ResponseDefinitionBuilder newResponseDefBuilder = + ResponseDefinitionBuilder.like(responseDefinition); + + final PathTemplate pathTemplate = + serveEvent.getStubMapping().getRequest().getUrlMatcher().getPathTemplate(); + + final Map additionalModelData = + templateModelDataProviders.stream() + .map(provider -> provider.provideTemplateModelData(serveEvent).entrySet()) + .flatMap(Set::stream) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + final Map model = new HashMap<>(); + model.put("parameters", parameters); + model.put("request", RequestTemplateModel.from(request, pathTemplate)); + model.putAll(addExtraModelElements(request, responseDefinition, files, parameters)); + model.putAll(additionalModelData); + + if (responseDefinition.specifiesTextBodyContent()) { + boolean isJsonBody = responseDefinition.getReponseBody().isJson(); HandlebarsOptimizedTemplate bodyTemplate = templateEngine.getTemplate( - HttpTemplateCacheKey.forFileBody(responseDefinition, compiledFilePath), - file.readContentsAsString()); - applyTemplatedResponseBody(newResponseDefBuilder, model, bodyTemplate, false); + HttpTemplateCacheKey.forInlineBody(responseDefinition), + responseDefinition.getTextBody()); + applyTemplatedResponseBody(newResponseDefBuilder, model, bodyTemplate, isJsonBody); + } else if (responseDefinition.specifiesBodyFile()) { + HandlebarsOptimizedTemplate filePathTemplate = + templateEngine.getUncachedTemplate(responseDefinition.getBodyFileName()); + String compiledFilePath = uncheckedApplyTemplate(filePathTemplate, model); + + boolean disableBodyFileTemplating = + parameters.getBoolean("disableBodyFileTemplating", false); + if (disableBodyFileTemplating) { + newResponseDefBuilder.withBodyFile(compiledFilePath); + } else { + TextFile file = files.getTextFileNamed(compiledFilePath); + HandlebarsOptimizedTemplate bodyTemplate = + templateEngine.getTemplate( + HttpTemplateCacheKey.forFileBody(responseDefinition, compiledFilePath), + file.readContentsAsString()); + applyTemplatedResponseBody(newResponseDefBuilder, model, bodyTemplate, false); + } } - } - - if (responseDefinition.getHeaders() != null) { - Iterable newResponseHeaders = - Iterables.transform( - responseDefinition.getHeaders().all(), - header -> { - ImmutableList.Builder valueListBuilder = ImmutableList.builder(); - int index = 0; - for (String headerValue : header.values()) { - HandlebarsOptimizedTemplate template = - templateEngine.getTemplate( - HttpTemplateCacheKey.forHeader(responseDefinition, header.key(), index++), - headerValue); - valueListBuilder.add(uncheckedApplyTemplate(template, model)); - } - - return new HttpHeader(header.key(), valueListBuilder.build()); - }); - newResponseDefBuilder.withHeaders(new HttpHeaders(newResponseHeaders)); - } - - if (responseDefinition.getProxyBaseUrl() != null) { - HandlebarsOptimizedTemplate proxyBaseUrlTemplate = - templateEngine.getTemplate( - HttpTemplateCacheKey.forProxyUrl(responseDefinition), - responseDefinition.getProxyBaseUrl()); - String newProxyBaseUrl = uncheckedApplyTemplate(proxyBaseUrlTemplate, model); - ResponseDefinitionBuilder.ProxyResponseDefinitionBuilder newProxyResponseDefBuilder = - newResponseDefBuilder.proxiedFrom(newProxyBaseUrl); + if (responseDefinition.getHeaders() != null) { + List newResponseHeaders = + responseDefinition.getHeaders().all().stream() + .map( + header -> { + ArrayList valueListBuilder = new ArrayList<>(); + int index = 0; + for (String headerValue : header.values()) { + HandlebarsOptimizedTemplate template = + templateEngine.getTemplate( + HttpTemplateCacheKey.forHeader( + responseDefinition, header.key(), index++), + headerValue); + valueListBuilder.add(uncheckedApplyTemplate(template, model)); + } + + return new HttpHeader(header.key(), valueListBuilder); + }) + .collect(Collectors.toList()); + newResponseDefBuilder.withHeaders(new HttpHeaders(newResponseHeaders)); + } - if (responseDefinition.getAdditionalProxyRequestHeaders() != null) { - Iterable newResponseHeaders = - Iterables.transform( - responseDefinition.getAdditionalProxyRequestHeaders().all(), - header -> { - ImmutableList.Builder valueListBuilder = ImmutableList.builder(); - int index = 0; - for (String headerValue : header.values()) { - HandlebarsOptimizedTemplate template = - templateEngine.getTemplate( - HttpTemplateCacheKey.forHeader( - responseDefinition, header.key(), index++), - headerValue); - valueListBuilder.add(uncheckedApplyTemplate(template, model)); - } - return new HttpHeader(header.key(), valueListBuilder.build()); - }); - HttpHeaders proxyHttpHeaders = new HttpHeaders(newResponseHeaders); - for (String key : proxyHttpHeaders.keys()) { - newProxyResponseDefBuilder.withAdditionalRequestHeader( - key, proxyHttpHeaders.getHeader(key).firstValue()); + if (responseDefinition.getProxyBaseUrl() != null) { + HandlebarsOptimizedTemplate proxyBaseUrlTemplate = + templateEngine.getTemplate( + HttpTemplateCacheKey.forProxyUrl(responseDefinition), + responseDefinition.getProxyBaseUrl()); + String newProxyBaseUrl = uncheckedApplyTemplate(proxyBaseUrlTemplate, model); + + ResponseDefinitionBuilder.ProxyResponseDefinitionBuilder newProxyResponseDefBuilder = + newResponseDefBuilder.proxiedFrom(newProxyBaseUrl); + + if (responseDefinition.getAdditionalProxyRequestHeaders() != null) { + List newResponseHeaders = + responseDefinition.getAdditionalProxyRequestHeaders().all().stream() + .map( + header -> { + ArrayList valueListBuilder = new ArrayList<>(); + int index = 0; + for (String headerValue : header.values()) { + HandlebarsOptimizedTemplate template = + templateEngine.getTemplate( + HttpTemplateCacheKey.forHeader( + responseDefinition, header.key(), index++), + headerValue); + valueListBuilder.add(uncheckedApplyTemplate(template, model)); + } + return new HttpHeader(header.key(), valueListBuilder); + }) + .collect(Collectors.toList()); + HttpHeaders proxyHttpHeaders = new HttpHeaders(newResponseHeaders); + for (String key : proxyHttpHeaders.keys()) { + newProxyResponseDefBuilder.withAdditionalRequestHeader( + key, proxyHttpHeaders.getHeader(key).firstValue()); + } } + return newProxyResponseDefBuilder.build(); + } else { + return newResponseDefBuilder.build(); } - return newProxyResponseDefBuilder.build(); - } else { - return newResponseDefBuilder.build(); + } catch (HandlebarsException he) { + final String message = cleanUpHandlebarsErrorMessage(he.getMessage()); + serveEvent.appendSubEvent(SubEvent.error(message)); + return serverError() + .withHeader(ContentTypeHeader.KEY, "text/plain") + .withBody(message) + .build(); } } + private static String cleanUpHandlebarsErrorMessage(String rawMessage) { + return rawMessage.replaceAll("inline@[a-z0-9]+:", "").replaceAll("\n.*", ""); + } + /** Override this to add extra elements to the template model */ protected Map addExtraModelElements( Request request, @@ -193,7 +201,7 @@ protected Map addExtraModelElements( private void applyTemplatedResponseBody( ResponseDefinitionBuilder newResponseDefBuilder, - ImmutableMap model, + Map model, HandlebarsOptimizedTemplate bodyTemplate, boolean isJsonBody) { String bodyString = uncheckedApplyTemplate(bodyTemplate, model); @@ -208,29 +216,11 @@ private String uncheckedApplyTemplate(HandlebarsOptimizedTemplate template, Obje return template.apply(context); } - @Override - public void beforeStubCreated(StubMapping stub) {} - - @Override - public void afterStubCreated(StubMapping stub) {} - - @Override - public void beforeStubEdited(StubMapping oldStub, StubMapping newStub) {} - - @Override - public void afterStubEdited(StubMapping oldStub, StubMapping newStub) {} - - @Override - public void beforeStubRemoved(StubMapping stub) {} - @Override public void afterStubRemoved(StubMapping stub) { templateEngine.invalidateCache(); } - @Override - public void beforeStubsReset() {} - @Override public void afterStubsReset() { templateEngine.invalidateCache(); @@ -243,52 +233,4 @@ public long getCacheSize() { public Long getMaxCacheEntries() { return templateEngine.getMaxCacheEntries(); } - - public static class Builder { - private boolean global = true; - private Handlebars handlebars = new Handlebars(); - private Map> helpers = new HashMap<>(); - private Long maxCacheEntries = null; - private Set permittedSystemKeys = null; - - public Builder global(boolean global) { - this.global = global; - return this; - } - - public Builder handlebars(Handlebars handlebars) { - this.handlebars = handlebars; - return this; - } - - public Builder helpers(Map> helpers) { - this.helpers = helpers; - return this; - } - - public Builder helper(String name, Helper helper) { - this.helpers.put(name, helper); - return this; - } - - public Builder maxCacheEntries(Long maxCacheEntries) { - this.maxCacheEntries = maxCacheEntries; - return this; - } - - public Builder permittedSystemKeys(Set keys) { - this.permittedSystemKeys = keys; - return this; - } - - public Builder permittedSystemKeys(String... keys) { - this.permittedSystemKeys = ImmutableSet.copyOf(keys); - return this; - } - - public ResponseTemplateTransformer build() { - return new ResponseTemplateTransformer( - global, handlebars, helpers, maxCacheEntries, permittedSystemKeys); - } - } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthoriser.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthoriser.java index 511e2d6a73..64c9c19487 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthoriser.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthoriser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,25 +17,22 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE; -import com.google.common.collect.ImmutableSet; +import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; public class SystemKeyAuthoriser { - private final ImmutableSet regexes; + private final Set regexes = new HashSet<>(); public SystemKeyAuthoriser(Set patterns) { if (patterns == null || patterns.isEmpty()) { - patterns = ImmutableSet.of("wiremock.*"); + patterns = Set.of("wiremock.*"); } - ImmutableSet.Builder builder = ImmutableSet.builder(); for (String pattern : patterns) { - builder.add(Pattern.compile(pattern, CASE_INSENSITIVE)); + regexes.add(Pattern.compile(pattern, CASE_INSENSITIVE)); } - - regexes = builder.build(); } public boolean isPermitted(String key) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplateEngine.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplateEngine.java index aa91e5a01e..914ff8a7de 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplateEngine.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplateEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,9 @@ */ package com.github.tomakehurst.wiremock.extension.responsetemplating; +import static java.util.Collections.emptyMap; + +import com.github.jknack.handlebars.EscapingStrategy; import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.helper.AssignHelper; @@ -36,17 +39,19 @@ public class TemplateEngine { private final Cache cache; private final Long maxCacheEntries; - public TemplateEngine( - Map> helpers, Long maxCacheEntries, Set permittedSystemKeys) { - this(new Handlebars(), helpers, maxCacheEntries, permittedSystemKeys); + public static TemplateEngine defaultTemplateEngine() { + return new TemplateEngine(emptyMap(), null, null, false); } public TemplateEngine( - Handlebars handlebars, Map> helpers, Long maxCacheEntries, - Set permittedSystemKeys) { - this.handlebars = handlebars; + Set permittedSystemKeys, + boolean escapingDisabled) { + + this.handlebars = + escapingDisabled ? new Handlebars().with(EscapingStrategy.NOOP) : new Handlebars(); + this.maxCacheEntries = maxCacheEntries; CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); if (maxCacheEntries != null) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplatedUrlPath.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplatedUrlPath.java new file mode 100644 index 0000000000..40aeaf05a9 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/TemplatedUrlPath.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.extension.responsetemplating; + +import com.github.tomakehurst.wiremock.common.Urls; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import org.apache.commons.lang3.StringUtils; + +public class TemplatedUrlPath extends LinkedHashMap implements Iterable { + + private final String originalPath; + + public TemplatedUrlPath(String url, PathTemplate pathTemplate) { + this.originalPath = Urls.getPath(url); + addAllPathSegments(); + putAll(pathTemplate.parse(originalPath)); + } + + private void addAllPathSegments() { + final List pathSegments = Urls.getPathSegments(originalPath); + int i = 0; + for (String pathNode : pathSegments) { + if (StringUtils.isNotEmpty(pathNode)) { + String key = String.valueOf(i++); + put(key, pathNode); + } + } + } + + @Override + public String toString() { + return originalPath; + } + + @Override + public Iterator iterator() { + return Urls.getPathSegments(originalPath).iterator(); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/UrlPath.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/UrlPath.java index efb744856b..32dab33835 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/UrlPath.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/UrlPath.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.extension.responsetemplating; -import com.google.common.base.Splitter; +import com.github.tomakehurst.wiremock.common.Urls; import java.net.URI; import java.util.ArrayList; import org.apache.commons.lang3.StringUtils; @@ -26,12 +26,13 @@ public class UrlPath extends ArrayList { public UrlPath(String url) { originalPath = URI.create(url).getPath(); - Iterable pathNodes = Splitter.on('/').split(originalPath); - for (String pathNode : pathNodes) { - if (StringUtils.isNotEmpty(pathNode)) { - add(pathNode); - } - } + Urls.getPathSegments(originalPath) + .forEach( + pathNode -> { + if (StringUtils.isNotEmpty(pathNode)) { + add(pathNode); + } + }); } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ArrayHelper.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ArrayHelper.java index 54ffbe8b2d..d1b5e0b057 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ArrayHelper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ArrayHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,22 @@ import static java.util.Arrays.asList; import com.github.jknack.handlebars.Options; -import com.google.common.collect.ImmutableList; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class ArrayHelper extends HandlebarsHelper { @Override public Object apply(Object context, Options options) throws IOException { if (context == null || context == options.context.model()) { - return ImmutableList.of(); + return List.of(); } - return ImmutableList.builder().add(context).addAll(asList(options.params)).build(); + List list = new ArrayList<>(); + list.add(context); + list.addAll(asList(options.params)); + + return list; } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/FormDataHelper.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/FormDataHelper.java index b6db7068f6..c34df43744 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/FormDataHelper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/FormDataHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.extension.responsetemplating.helpers; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.jknack.handlebars.Options; import com.github.tomakehurst.wiremock.common.ListOrSingle; @@ -29,7 +29,7 @@ public Object apply(Object context, Options options) { FormParser.parse( context.toString(), Boolean.TRUE.equals(options.hash.get("urlDecode")), - firstNonNull(options.hash.get("encoding"), "utf-8").toString()); + getFirstNonNull(options.hash.get("encoding"), "utf-8").toString()); if (options.params.length > 0) { String variableName = options.param(0); diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelper.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelper.java index 1725530c62..2f63c8584c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.extension.responsetemplating.helpers; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.jknack.handlebars.Options; import com.github.tomakehurst.wiremock.extension.responsetemplating.RenderCache; @@ -72,7 +72,7 @@ private Object getValue(JsonPath jsonPath, DocumentContext jsonDocument, Options } if (value == null) { - value = firstNonNull(defaultValue, ""); + value = getFirstNonNull(defaultValue, ""); } renderCache.put(cacheKey, value); diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelper.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelper.java index 6260fa064f..e7f42f6878 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,13 +57,13 @@ public Object apply(Object context, Options options) throws IOException { // Edge case if JSON object is empty {} String jsonAsStringWithoutSpace = jsonAsString.replaceAll("\\s", ""); if (jsonAsStringWithoutSpace.equals("{}") || jsonAsStringWithoutSpace.equals("")) { - return result; - } - - if (jsonAsString.startsWith("[") && jsonAsString.endsWith("]")) { - result = Json.read(jsonAsString, new TypeReference>() {}); + result = new HashMap(); } else { - result = Json.read(jsonAsString, new TypeReference>() {}); + if (jsonAsString.startsWith("[") && jsonAsString.endsWith("]")) { + result = Json.read(jsonAsString, new TypeReference>() {}); + } else { + result = Json.read(jsonAsString, new TypeReference>() {}); + } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/PickRandomHelper.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/PickRandomHelper.java index ceb7b15867..d5c7900b07 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/PickRandomHelper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/PickRandomHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,29 @@ package com.github.tomakehurst.wiremock.extension.responsetemplating.helpers; import com.github.jknack.handlebars.Options; -import com.google.common.collect.ImmutableList; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class PickRandomHelper extends HandlebarsHelper { - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public Object apply(Object context, Options options) throws IOException { if (context == null) { return this.handleError( "Must specify either a single list argument or a set of single value arguments."); } - List valueList = - (Iterable.class.isAssignableFrom(context.getClass())) - ? ImmutableList.copyOf((Iterable) context) - : ImmutableList.builder().add(context).add(options.params).build(); + List valueList = new ArrayList<>(); + if (Iterable.class.isAssignableFrom(context.getClass())) { + ((Iterable) context).forEach(valueList::add); + } else { + valueList.add(context); + valueList.addAll(Arrays.asList(options.params)); + } int index = ThreadLocalRandom.current().nextInt(valueList.size()); return valueList.get(index).toString(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettings.java b/src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettings.java index bde878bb91..296a7a7a37 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettings.java +++ b/src/main/java/com/github/tomakehurst/wiremock/global/GlobalSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ public class GlobalSettings { private final DelayDistribution delayDistribution; private final Parameters extended; + private final boolean proxyPassThrough; + public static GlobalSettings.Builder builder() { return new Builder(); } @@ -37,10 +39,12 @@ public static GlobalSettings defaults() { public GlobalSettings( @JsonProperty("fixedDelay") Integer fixedDelay, @JsonProperty("delayDistribution") DelayDistribution delayDistribution, - @JsonProperty("extended") Parameters extended) { + @JsonProperty("extended") Parameters extended, + @JsonProperty("proxyPassThrough") boolean proxyPassThrough) { this.fixedDelay = fixedDelay; this.delayDistribution = delayDistribution; this.extended = extended; + this.proxyPassThrough = proxyPassThrough; } public Integer getFixedDelay() { @@ -55,6 +59,10 @@ public Parameters getExtended() { return extended; } + public boolean getProxyPassThrough() { + return proxyPassThrough; + } + public GlobalSettings.Builder copy() { return new Builder() .fixedDelay(fixedDelay) @@ -82,6 +90,8 @@ public static class Builder { private DelayDistribution delayDistribution; private Parameters extended; + private boolean proxyPassThrough = true; + public Builder fixedDelay(Integer fixedDelay) { this.fixedDelay = fixedDelay; return this; @@ -97,8 +107,13 @@ public Builder extended(Parameters extended) { return this; } + public Builder proxyPassThrough(boolean proxyPassThrough) { + this.proxyPassThrough = proxyPassThrough; + return this; + } + public GlobalSettings build() { - return new GlobalSettings(fixedDelay, delayDistribution, extended); + return new GlobalSettings(fixedDelay, delayDistribution, extended, proxyPassThrough); } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/AbstractRequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/AbstractRequestHandler.java index 8dba3d1b23..9702f808d5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/AbstractRequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/AbstractRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,31 +16,30 @@ package com.github.tomakehurst.wiremock.http; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; -import static com.github.tomakehurst.wiremock.extension.requestfilter.FilterProcessor.processFilters; -import static com.google.common.collect.Lists.newArrayList; -import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static com.github.tomakehurst.wiremock.stubbing.ServeEvent.ORIGINAL_SERVE_EVENT_KEY; import com.github.tomakehurst.wiremock.common.DataTruncationSettings; import com.github.tomakehurst.wiremock.extension.requestfilter.*; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.google.common.base.Stopwatch; +import java.util.ArrayList; import java.util.List; +import java.util.Map; public abstract class AbstractRequestHandler implements RequestHandler, RequestEventSource { - protected List listeners = newArrayList(); + protected List listeners = new ArrayList<>(); protected final ResponseRenderer responseRenderer; - protected final List requestFilters; + protected final FilterProcessor filterProcessor; private final DataTruncationSettings dataTruncationSettings; public AbstractRequestHandler( ResponseRenderer responseRenderer, List requestFilters, + List v2RequestFilters, DataTruncationSettings dataTruncationSettings) { this.responseRenderer = responseRenderer; - this.requestFilters = requestFilters; + this.filterProcessor = new FilterProcessor(requestFilters, v2RequestFilters); this.dataTruncationSettings = dataTruncationSettings; } @@ -54,40 +53,36 @@ protected void beforeResponseSent(ServeEvent serveEvent, Response response) {} protected void afterResponseSent(ServeEvent serveEvent, Response response) {} @Override - public void handle(Request request, HttpResponder httpResponder) { - Stopwatch stopwatch = Stopwatch.createStarted(); - - ServeEvent serveEvent; + public void handle(Request request, HttpResponder httpResponder, ServeEvent originalServeEvent) { + ServeEvent serveEvent = ServeEvent.of(request); Request processedRequest = request; - if (!requestFilters.isEmpty()) { - RequestFilterAction requestFilterAction = - processFilters(request, requestFilters, RequestFilterAction.continueWith(request)); + + if (filterProcessor.hasAnyFilters()) { + RequestFilterAction requestFilterAction = filterProcessor.processFilters(request, serveEvent); + if (requestFilterAction instanceof ContinueAction) { processedRequest = ((ContinueAction) requestFilterAction).getRequest(); - serveEvent = handleRequest(processedRequest); + serveEvent = handleRequest(serveEvent.replaceRequest(processedRequest)); } else { serveEvent = - ServeEvent.of( - LoggedRequest.createFrom(request), + serveEvent.withResponseDefinition( ((StopAction) requestFilterAction).getResponseDefinition()); } } else { - serveEvent = handleRequest(request); + serveEvent = handleRequest(serveEvent); } ResponseDefinition responseDefinition = serveEvent.getResponseDefinition(); responseDefinition.setOriginalRequest(processedRequest); Response response = responseRenderer.render(serveEvent); response = Response.Builder.like(response).protocol(request.getProtocol()).build(); - ServeEvent completedServeEvent = - serveEvent.complete( - response, (int) stopwatch.elapsed(MILLISECONDS), dataTruncationSettings); + serveEvent = serveEvent.complete(response, dataTruncationSettings); if (logRequests()) { notifier() .info( "Request received:\n" - + formatRequest(processedRequest) + + formatRequest(request) + "\n\nMatched response definition:\n" + responseDefinition + "\n\nResponse:\n" @@ -95,18 +90,18 @@ public void handle(Request request, HttpResponder httpResponder) { } for (RequestListener listener : listeners) { - listener.requestReceived(processedRequest, response); + listener.requestReceived(request, response); } - beforeResponseSent(completedServeEvent, response); + beforeResponseSent(serveEvent, response); + + serveEvent.beforeSend(); - stopwatch.reset(); - stopwatch.start(); - httpResponder.respond(processedRequest, response); + Map attributes = Map.of(ORIGINAL_SERVE_EVENT_KEY, serveEvent); + httpResponder.respond(request, response, attributes); - completedServeEvent.afterSend((int) stopwatch.elapsed(MILLISECONDS)); - afterResponseSent(completedServeEvent, response); - stopwatch.stop(); + serveEvent.afterSend(); + afterResponseSent(serveEvent, response); } protected String formatRequest(Request request) { @@ -135,5 +130,5 @@ protected boolean logRequests() { return false; } - protected abstract ServeEvent handleRequest(Request request); + protected abstract ServeEvent handleRequest(ServeEvent request); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/AdminRequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/AdminRequestHandler.java index 196abb142f..ea1a4e4148 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/AdminRequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/AdminRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,15 +20,15 @@ import com.github.tomakehurst.wiremock.admin.AdminRoutes; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.AdminUriTemplate; import com.github.tomakehurst.wiremock.admin.NotFoundException; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; +import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; import com.github.tomakehurst.wiremock.security.Authenticator; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.github.tomakehurst.wiremock.verification.LoggedRequest; import java.net.URI; import java.util.List; @@ -46,8 +46,9 @@ public AdminRequestHandler( Authenticator authenticator, boolean requireHttps, List requestFilters, + List v2RequestFilters, DataTruncationSettings dataTruncationSettings) { - super(responseRenderer, requestFilters, dataTruncationSettings); + super(responseRenderer, requestFilters, v2RequestFilters, dataTruncationSettings); this.adminRoutes = adminRoutes; this.admin = admin; this.authenticator = authenticator; @@ -55,18 +56,17 @@ public AdminRequestHandler( } @Override - public ServeEvent handleRequest(Request request) { - final LoggedRequest loggedRequest = LoggedRequest.createFrom(request); + public ServeEvent handleRequest(ServeEvent initialServeEvent) { + final Request request = initialServeEvent.getRequest(); if (requireHttps && !URI.create(request.getAbsoluteUrl()).getScheme().equals("https")) { notifier().info("HTTPS is required for admin requests, sending upgrade redirect"); - return ServeEvent.of( - loggedRequest, + return initialServeEvent.withResponseDefinition( ResponseDefinition.notPermitted("HTTPS is required for accessing the admin API")); } if (!authenticator.authenticate(request)) { notifier().info("Authentication failed for " + request.getMethod() + " " + request.getUrl()); - return ServeEvent.of(loggedRequest, ResponseDefinition.notAuthorised()); + return initialServeEvent.withResponseDefinition(ResponseDefinition.notAuthorised()); } notifier().info("Admin request received:\n" + formatRequest(request)); @@ -75,19 +75,23 @@ public ServeEvent handleRequest(Request request) { try { AdminTask adminTask = adminRoutes.taskFor(request.getMethod(), path); - AdminUriTemplate uriTemplate = + PathTemplate uriTemplate = adminRoutes.requestSpecForTask(adminTask.getClass()).getUriTemplate(); PathParams pathParams = uriTemplate.parse(path); - return ServeEvent.of(loggedRequest, adminTask.execute(admin, request, pathParams)); + return initialServeEvent.withResponseDefinition( + adminTask.execute(admin, initialServeEvent, pathParams)); } catch (NotFoundException e) { - return ServeEvent.forUnmatchedRequest(loggedRequest); + return initialServeEvent.withResponseDefinition(ResponseDefinition.notConfigured()); } catch (InvalidParameterException ipe) { - return ServeEvent.forBadRequest(loggedRequest, ipe.getErrors()); + return initialServeEvent.withResponseDefinition( + ResponseDefinition.badRequest(ipe.getErrors())); } catch (InvalidInputException iie) { - return ServeEvent.forBadRequestEntity(loggedRequest, iie.getErrors()); + return initialServeEvent.withResponseDefinition( + ResponseDefinition.badRequestEntity(iie.getErrors())); } catch (NotPermittedException npe) { - return ServeEvent.forNotAllowedRequest(loggedRequest, npe.getErrors()); + return initialServeEvent.withResponseDefinition( + ResponseDefinition.notPermitted(npe.getErrors())); } catch (Throwable t) { notifier().error("Unrecoverable error handling admin request", t); throw t; diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/Body.java b/src/main/java/com/github/tomakehurst/wiremock/http/Body.java index d1386703b0..54675b0033 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/Body.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/Body.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,7 +135,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(content, binary); + return Objects.hash(Arrays.hashCode(content), binary); } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/CaseInsensitiveKey.java b/src/main/java/com/github/tomakehurst/wiremock/http/CaseInsensitiveKey.java index 5a400b6fd9..b975312bac 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/CaseInsensitiveKey.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/CaseInsensitiveKey.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.http; -import com.google.common.base.Function; +import java.util.function.Function; public class CaseInsensitiveKey { @@ -67,9 +67,5 @@ public String value() { } public static final Function TO_CASE_INSENSITIVE_KEYS = - new Function() { - public CaseInsensitiveKey apply(String input) { - return from(input); - } - }; + CaseInsensitiveKey::from; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/ContentTypeHeader.java b/src/main/java/com/github/tomakehurst/wiremock/http/ContentTypeHeader.java index 8ec225b346..9841dd48d5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/ContentTypeHeader.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/ContentTypeHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.github.tomakehurst.wiremock.http; import com.github.tomakehurst.wiremock.common.Strings; -import com.google.common.base.Optional; import java.nio.charset.Charset; +import java.util.Optional; public class ContentTypeHeader extends HttpHeader { @@ -53,7 +53,7 @@ public Optional encodingPart() { } } - return Optional.absent(); + return Optional.empty(); } public Charset charset() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/Cookie.java b/src/main/java/com/github/tomakehurst/wiremock/http/Cookie.java index 8b2f91cecd..2312d832dd 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/Cookie.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/Cookie.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonValue; import com.github.tomakehurst.wiremock.common.ListOrSingle; -import com.google.common.base.Joiner; import java.util.Collections; import java.util.List; @@ -38,7 +37,7 @@ public static Cookie cookie(String value) { } public static Cookie absent() { - return new Cookie(null, Collections.emptyList()); + return new Cookie(null, Collections.emptyList()); } public Cookie(String value) { @@ -64,7 +63,7 @@ public boolean isAbsent() { @JsonValue public ListOrSingle getValues() { - return new ListOrSingle<>(isPresent() ? values() : Collections.emptyList()); + return new ListOrSingle<>(isPresent() ? values() : Collections.emptyList()); } @JsonIgnore @@ -74,6 +73,6 @@ public String getValue() { @Override public String toString() { - return isAbsent() ? "(absent)" : Joiner.on("; ").join(getValues()); + return isAbsent() ? "(absent)" : String.join("; ", getValues()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/FormParameter.java b/src/main/java/com/github/tomakehurst/wiremock/http/FormParameter.java new file mode 100644 index 0000000000..5c90cc9a5f --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/http/FormParameter.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.http; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collections; +import java.util.List; + +public class FormParameter extends MultiValue { + + public FormParameter( + @JsonProperty("key") String key, @JsonProperty("values") List values) { + super(key, values); + } + + public static FormParameter absent(String key) { + return new FormParameter(key, Collections.emptyList()); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeader.java b/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeader.java index e95b6418af..0b3bc85c26 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeader.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ */ package com.github.tomakehurst.wiremock.http; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static java.util.Arrays.asList; -import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.Collections; +import java.util.List; public class HttpHeader extends MultiValue { @@ -29,11 +29,11 @@ public HttpHeader(String key, String... values) { } public HttpHeader(CaseInsensitiveKey key, Collection values) { - super(key.value(), ImmutableList.copyOf(values)); + super(key.value(), List.copyOf(values)); } public HttpHeader(String key, Collection values) { - super(key, ImmutableList.copyOf(firstNonNull(values, Collections.emptyList()))); + super(key, List.copyOf(getFirstNonNull(values, Collections.emptyList()))); } public static HttpHeader httpHeader(CaseInsensitiveKey key, String... values) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeaders.java b/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeaders.java index e1e2aa3c80..68acaf4dab 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeaders.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,15 @@ */ package com.github.tomakehurst.wiremock.http; -import static com.google.common.base.Functions.toStringFunction; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Sets.newHashSet; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static java.util.Arrays.asList; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; @JsonSerialize(using = HttpHeadersJsonSerializer.class) @JsonDeserialize(using = HttpHeadersJsonDeserializer.class) @@ -43,12 +36,12 @@ public HttpHeaders() { } public HttpHeaders(HttpHeader... headers) { - this(ImmutableList.copyOf(headers)); + this(Arrays.asList(headers)); } public HttpHeaders(Iterable headers) { ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - for (HttpHeader header : firstNonNull(headers, Collections.emptyList())) { + for (HttpHeader header : getFirstNonNull(headers, Collections.emptyList())) { builder.putAll(caseInsensitive(header.key()), header.values()); } @@ -82,7 +75,7 @@ public ContentTypeHeader getContentTypeHeader() { } public Collection all() { - List httpHeaderList = newArrayList(); + List httpHeaderList = new ArrayList<>(); for (CaseInsensitiveKey key : headers.keySet()) { httpHeaderList.add(new HttpHeader(key.value(), headers.get(key))); } @@ -91,7 +84,7 @@ public Collection all() { } public Set keys() { - return newHashSet(transform(headers.keySet(), toStringFunction())); + return headers.keySet().stream().map(CaseInsensitiveKey::toString).collect(Collectors.toSet()); } public static HttpHeaders copyOf(HttpHeaders source) { @@ -103,11 +96,9 @@ public int size() { } public HttpHeaders plus(HttpHeader... additionalHeaders) { - return new HttpHeaders( - ImmutableList.builder() - .addAll(all()) - .addAll(asList(additionalHeaders)) - .build()); + List httpHeaders = new ArrayList<>(all()); + httpHeaders.addAll(asList(additionalHeaders)); + return new HttpHeaders(httpHeaders); } @Override @@ -117,9 +108,7 @@ public boolean equals(Object o) { HttpHeaders that = (HttpHeaders) o; - if (headers != null ? !headers.equals(that.headers) : that.headers != null) return false; - - return true; + return headers != null ? headers.equals(that.headers) : that.headers == null; } @Override @@ -133,12 +122,12 @@ public String toString() { return "(no headers)\n"; } - String outString = ""; + StringBuilder outString = new StringBuilder(); for (CaseInsensitiveKey key : headers.keySet()) { - outString += key.toString() + ": " + headers.get(key).toString() + "\n"; + outString.append(key.toString()).append(": ").append(headers.get(key)).append("\n"); } - return outString; + return outString.toString(); } private CaseInsensitiveKey caseInsensitive(String key) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeadersJsonDeserializer.java b/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeadersJsonDeserializer.java index 4b61e5926b..c3256e4854 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeadersJsonDeserializer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/HttpHeadersJsonDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,16 @@ */ package com.github.tomakehurst.wiremock.http; -import static com.google.common.collect.Iterables.transform; - import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; public class HttpHeadersJsonDeserializer extends JsonDeserializer { @@ -33,38 +32,28 @@ public class HttpHeadersJsonDeserializer extends JsonDeserializer { public HttpHeaders deserialize(JsonParser parser, DeserializationContext context) throws IOException { JsonNode rootNode = parser.readValueAsTree(); - return new HttpHeaders(transform(all(rootNode.fields()), toHttpHeaders())); - } - - private static Function, HttpHeader> toHttpHeaders() { - return new Function, HttpHeader>() { - @Override - public HttpHeader apply(Map.Entry field) { - String key = field.getKey(); - if (field.getValue().isArray()) { - return new HttpHeader( - key, - ImmutableList.copyOf(transform(all(field.getValue().elements()), toStringValues()))); - } else { - return new HttpHeader(key, field.getValue().textValue()); - } - } - }; + Iterable> all = rootNode::fields; + List headers = + StreamSupport.stream(all.spliterator(), false) + .map(entry -> createHttpHeader(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + return new HttpHeaders(headers); } - private static Function toStringValues() { - return new Function() { - public String apply(JsonNode node) { - return node.textValue(); - } - }; + private static HttpHeader createHttpHeader(String key, JsonNode fieldValue) { + if (fieldValue.isArray()) { + Iterable all = fieldValue::elements; + List headerValues = + StreamSupport.stream(all.spliterator(), false) + .map(JsonNode::textValue) + .collect(Collectors.toList()); + return new HttpHeader(key, headerValues); + } else { + return new HttpHeader(key, fieldValue.textValue()); + } } public static Iterable all(final Iterator underlyingIterator) { - return new Iterable() { - public Iterator iterator() { - return underlyingIterator; - } - }; + return () -> underlyingIterator; } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/HttpResponder.java b/src/main/java/com/github/tomakehurst/wiremock/http/HttpResponder.java index 18b657e45a..449a9fe8bb 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/HttpResponder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/HttpResponder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package com.github.tomakehurst.wiremock.http; +import java.util.Map; + public interface HttpResponder { - void respond(Request request, Response response); + void respond(Request request, Response response, Map attributes); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/LoggedResponse.java b/src/main/java/com/github/tomakehurst/wiremock/http/LoggedResponse.java index ba7d1045da..ad05fd7227 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/LoggedResponse.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/LoggedResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.http; -import static com.google.common.net.MediaType.OCTET_STREAM; +import static com.github.tomakehurst.wiremock.common.ContentTypes.OCTET_STREAM; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -83,7 +83,7 @@ public String getBodyAsString() { @JsonIgnore public String getMimeType() { return headers == null || headers.getContentTypeHeader() == null - ? OCTET_STREAM.toString() + ? OCTET_STREAM : headers.getContentTypeHeader().mimeTypePart(); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/MultiValue.java b/src/main/java/com/github/tomakehurst/wiremock/http/MultiValue.java index 2db5d93325..8d97b83f06 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/MultiValue.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/MultiValue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,12 @@ */ package com.github.tomakehurst.wiremock.http; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.any; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkState; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.github.tomakehurst.wiremock.matching.StringValuePattern; -import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.base.Predicate; import java.util.List; +import java.util.stream.Collectors; public class MultiValue { @@ -35,8 +32,17 @@ public MultiValue(String key, List values) { this.values = values; } + public String getKey() { + return key; + } + + public List getValues() { + return values; + } + + @JsonIgnore public boolean isPresent() { - return values.size() > 0; + return !values.isEmpty(); } public String key() { @@ -57,6 +63,7 @@ private void checkPresent() { checkState(isPresent(), "No value for " + key); } + @JsonIgnore public boolean isSingleValued() { return values.size() == 1; } @@ -70,26 +77,11 @@ public boolean hasValueMatching(final StringValuePattern valuePattern) { } private boolean anyValueMatches(final StringValuePattern valuePattern) { - return any( - values, - new Predicate() { - public boolean apply(String headerValue) { - return valuePattern.match(headerValue).isExactMatch(); - } - }); + return values.stream().anyMatch(headerValue -> valuePattern.match(headerValue).isExactMatch()); } @Override public String toString() { - return Joiner.on("\n") - .join( - from(values) - .transform( - new Function() { - @Override - public String apply(String value) { - return key + ": " + value; - } - })); + return values.stream().map(value -> key + ": " + value).collect(Collectors.joining("\n")); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java b/src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java index 722223ec48..4d491febc2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.http; -import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArrayAndCloseStream; +import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArray; import static com.github.tomakehurst.wiremock.http.Response.response; import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; @@ -23,9 +23,9 @@ import com.github.tomakehurst.wiremock.common.ProhibitedNetworkAddressException; import com.github.tomakehurst.wiremock.common.ProxySettings; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; +import com.github.tomakehurst.wiremock.global.GlobalSettings; +import com.github.tomakehurst.wiremock.store.SettingsStore; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.collect.ImmutableList; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; @@ -37,30 +37,26 @@ import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.entity.GzipCompressingEntity; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.*; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.InputStreamEntity; public class ProxyResponseRenderer implements ResponseRenderer { - private static final int MINUTES = 1000 * 60; - private static final int DEFAULT_SO_TIMEOUT = 5 * MINUTES; - private static final String TRANSFER_ENCODING = "transfer-encoding"; private static final String CONTENT_ENCODING = "content-encoding"; private static final String CONTENT_LENGTH = "content-length"; private static final String HOST_HEADER = "host"; - public static final ImmutableList FORBIDDEN_RESPONSE_HEADERS = - ImmutableList.of(TRANSFER_ENCODING, "connection"); - public static final ImmutableList FORBIDDEN_REQUEST_HEADERS = - ImmutableList.of(CONTENT_LENGTH, TRANSFER_ENCODING, "connection"); + public static final List FORBIDDEN_RESPONSE_HEADERS = + List.of(TRANSFER_ENCODING, "connection"); + public static final List FORBIDDEN_REQUEST_HEADERS = + List.of(CONTENT_LENGTH, TRANSFER_ENCODING, "connection"); private final CloseableHttpClient reverseProxyClient; private final CloseableHttpClient forwardProxyClient; private final boolean preserveHostHeader; private final String hostHeaderValue; - private final GlobalSettingsHolder globalSettingsHolder; + private final SettingsStore settingsStore; private final boolean stubCorsEnabled; private final NetworkAddressRules targetAddressRules; @@ -70,16 +66,17 @@ public ProxyResponseRenderer( KeyStoreSettings trustStoreSettings, boolean preserveHostHeader, String hostHeaderValue, - GlobalSettingsHolder globalSettingsHolder, + SettingsStore settingsStore, boolean trustAllProxyTargets, List trustedProxyTargets, boolean stubCorsEnabled, - NetworkAddressRules targetAddressRules) { - this.globalSettingsHolder = globalSettingsHolder; + NetworkAddressRules targetAddressRules, + int proxyTimeout) { + this.settingsStore = settingsStore; reverseProxyClient = HttpClientFactory.createClient( 1000, - DEFAULT_SO_TIMEOUT, + proxyTimeout, proxySettings, trustStoreSettings, true, @@ -89,7 +86,7 @@ public ProxyResponseRenderer( forwardProxyClient = HttpClientFactory.createClient( 1000, - DEFAULT_SO_TIMEOUT, + proxyTimeout, proxySettings, trustStoreSettings, trustAllProxyTargets, @@ -117,25 +114,32 @@ public Response render(ServeEvent serveEvent) { HttpUriRequest httpRequest = getHttpRequestFor(responseDefinition); addRequestHeaders(httpRequest, responseDefinition); + GlobalSettings settings = settingsStore.get(); + Request originalRequest = responseDefinition.getOriginalRequest(); if ((originalRequest.getBody() != null && originalRequest.getBody().length > 0) || originalRequest.containsHeader(CONTENT_LENGTH)) { httpRequest.setEntity(buildEntityFrom(originalRequest)); } + CloseableHttpClient client = chooseClient(serveEvent.getRequest().isBrowserProxyRequest()); - try (CloseableHttpResponse httpResponse = client.execute(httpRequest)) { - return response() - .status(httpResponse.getCode()) - .headers(headersFrom(httpResponse, responseDefinition)) - .body(getEntityAsByteArrayAndCloseStream(httpResponse)) - .fromProxy(true) - .configureDelay( - globalSettingsHolder.get().getFixedDelay(), - globalSettingsHolder.get().getDelayDistribution(), - responseDefinition.getFixedDelayMilliseconds(), - responseDefinition.getDelayDistribution()) - .chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay()) - .build(); + + try { + return client.execute( + httpRequest, + httpResponse -> + response() + .status(httpResponse.getCode()) + .headers(headersFrom(httpResponse, responseDefinition)) + .body(getEntityAsByteArray(httpResponse)) + .fromProxy(true) + .configureDelay( + settings.getFixedDelay(), + settings.getDelayDistribution(), + responseDefinition.getFixedDelayMilliseconds(), + responseDefinition.getDelayDistribution()) + .chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay()) + .build()); } catch (ProhibitedNetworkAddressException e) { return response() .status(HTTP_INTERNAL_ERROR) @@ -240,7 +244,7 @@ private static HttpEntity buildEntityFrom(Request originalRequest) { ContentTypeHeader contentTypeHeader = originalRequest.contentTypeHeader().or("text/plain"); ContentType contentType = ContentType.create( - contentTypeHeader.mimeTypePart(), contentTypeHeader.encodingPart().or("utf-8")); + contentTypeHeader.mimeTypePart(), contentTypeHeader.encodingPart().orElse("utf-8")); if (originalRequest.containsHeader(TRANSFER_ENCODING) && originalRequest.header(TRANSFER_ENCODING).firstValue().equals("chunked")) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/Request.java b/src/main/java/com/github/tomakehurst/wiremock/http/Request.java index 20c5c4761d..1b9a39fd0a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/Request.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/Request.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ package com.github.tomakehurst.wiremock.http; -import com.google.common.base.Optional; import java.util.Collection; import java.util.Map; +import java.util.Optional; import java.util.Set; public interface Request { @@ -58,10 +58,14 @@ interface Part { Set getAllHeaderKeys(); - Map getCookies(); - QueryParameter queryParameter(String key); + FormParameter formParameter(String key); + + Map formParameters(); + + Map getCookies(); + byte[] getBody(); String getBodyAsString(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/RequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/RequestHandler.java index e27895a385..4a3b2ad6c2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/RequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/RequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,11 @@ */ package com.github.tomakehurst.wiremock.http; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + public interface RequestHandler { String HANDLER_CLASS_KEY = "RequestHandlerClass"; - void handle(Request request, HttpResponder httpResponder); + void handle(Request request, HttpResponder httpResponder, ServeEvent originalServeEvent); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/Response.java b/src/main/java/com/github/tomakehurst/wiremock/http/Response.java index 9c5d8d4e21..80a71f37e2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/Response.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/Response.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,9 @@ import static java.net.HttpURLConnection.HTTP_OK; import com.github.tomakehurst.wiremock.common.*; -import com.google.common.base.Optional; -import com.google.common.io.ByteStreams; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; public class Response { @@ -103,12 +102,9 @@ private static byte[] getBytesFromStream(InputStreamSource streamSource, Limit l return null; } - InputStream trimmedStream = - limit != null && !limit.isUnlimited() - ? ByteStreams.limit(stream, limit.getValue()) - : stream; - - return ByteStreams.toByteArray(trimmedStream); + return limit != null && !limit.isUnlimited() + ? stream.readNBytes(limit.getValue()) + : stream.readAllBytes(); } } @@ -121,7 +117,8 @@ public InputStream getBodyStream() { } public boolean hasInlineBody() { - return !BinaryFile.class.isAssignableFrom(bodyStreamSource.getClass()); + return StreamSources.ByteArrayInputStreamSource.class.isAssignableFrom( + bodyStreamSource.getClass()); } public HttpHeaders getHeaders() { @@ -248,16 +245,14 @@ public Builder configureDelay( private void addDelayIfSpecifiedGloballyOrIn(Integer fixedDelay, Integer globalFixedDelay) { Optional optionalDelay = getDelayFromResponseOrGlobalSetting(fixedDelay, globalFixedDelay); - if (optionalDelay.isPresent()) { - incrementInitialDelay(optionalDelay.get()); - } + optionalDelay.ifPresent(this::incrementInitialDelay); } private Optional getDelayFromResponseOrGlobalSetting( Integer fixedDelay, Integer globalFixedDelay) { Integer delay = fixedDelay != null ? fixedDelay : globalFixedDelay; - return Optional.fromNullable(delay); + return Optional.ofNullable(delay); } private void addRandomDelayIfSpecifiedGloballyOrIn( diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/ResponseDefinition.java b/src/main/java/com/github/tomakehurst/wiremock/http/ResponseDefinition.java index 4ac1e8a926..b542828af5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/ResponseDefinition.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/ResponseDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,8 @@ package com.github.tomakehurst.wiremock.http; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; +import static com.github.tomakehurst.wiremock.common.ContentTypes.LOCATION; import static java.net.HttpURLConnection.*; import com.fasterxml.jackson.annotation.JsonCreator; @@ -27,7 +28,7 @@ import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.Json; -import com.github.tomakehurst.wiremock.extension.AbstractTransformer; +import com.github.tomakehurst.wiremock.extension.Extension; import com.github.tomakehurst.wiremock.extension.Parameters; import java.util.Collections; import java.util.List; @@ -262,7 +263,7 @@ public static ResponseDefinition badRequestEntity(Errors errors) { public static ResponseDefinition redirectTo(String path) { return new ResponseDefinitionBuilder() - .withHeader("Location", path) + .withHeader(LOCATION, path) .withStatus(HTTP_MOVED_TEMP) .build(); } @@ -454,7 +455,7 @@ public Parameters getTransformerParameters() { return transformerParameters; } - public boolean hasTransformer(AbstractTransformer transformer) { + public boolean hasTransformer(Extension transformer) { return transformers != null && transformers.contains(transformer.getName()); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java index a04d6584d6..5cb2a8c694 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,22 @@ package com.github.tomakehurst.wiremock.http; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; +import static com.github.tomakehurst.wiremock.extension.ServeEventListener.RequestPhase.*; import com.github.tomakehurst.wiremock.common.DataTruncationSettings; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.core.StubServer; -import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.PostServeAction; -import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition; +import com.github.tomakehurst.wiremock.extension.*; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; +import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; import com.github.tomakehurst.wiremock.jetty9.websockets.Message; import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; import com.github.tomakehurst.wiremock.verification.RequestJournal; +import com.github.tomakehurst.wiremock.verification.diff.DiffEventData; +import com.github.tomakehurst.wiremock.verification.notmatched.NotMatchedRenderer; import java.util.List; import java.util.Map; @@ -36,30 +40,41 @@ public class StubRequestHandler extends AbstractRequestHandler { private final StubServer stubServer; private final Admin admin; private final Map postServeActions; - private final RequestJournal requestJournal; + private final Map serveEventListeners; + private final RequestJournal requestJournal; private final boolean loggingDisabled; + private final NotMatchedRenderer notMatchedRenderer; + public StubRequestHandler( - final StubServer stubServer, - final ResponseRenderer responseRenderer, - final Admin admin, - final Map postServeActions, - final RequestJournal requestJournal, - final List requestFilters, - boolean loggingDisabled, - DataTruncationSettings dataTruncationSettings) { - super(responseRenderer, requestFilters, dataTruncationSettings); - this.stubServer = stubServer; - this.admin = admin; - this.postServeActions = postServeActions; - this.requestJournal = requestJournal; - this.loggingDisabled = loggingDisabled; - } + StubServer stubServer, + ResponseRenderer responseRenderer, + Admin admin, + Map postServeActions, + Map serveEventListeners, + RequestJournal requestJournal, + List requestFilters, + List v2RequestFilters, + boolean loggingDisabled, + DataTruncationSettings dataTruncationSettings, + NotMatchedRenderer notMatchedRenderer) { + super(responseRenderer, requestFilters, v2RequestFilters, dataTruncationSettings); + this.stubServer = stubServer; + this.admin = admin; + this.postServeActions = postServeActions; + this.serveEventListeners = serveEventListeners; + this.requestJournal = requestJournal; + this.loggingDisabled = loggingDisabled; + this.notMatchedRenderer = notMatchedRenderer; + } - @Override - public ServeEvent handleRequest(final Request request) { - return this.stubServer.serveStubFor(request); - } + @Override + public ServeEvent handleRequest(ServeEvent initialServeEvent) { + triggerListeners(BEFORE_MATCH, initialServeEvent); + final ServeEvent serveEvent = stubServer.serveStubFor(initialServeEvent); + triggerListeners(AFTER_MATCH, serveEvent); + return serveEvent; + } @Override protected boolean logRequests() { @@ -67,18 +82,47 @@ protected boolean logRequests() { } @Override - protected void beforeResponseSent(final ServeEvent serveEvent, final Response response) { - this.requestJournal.requestReceived(serveEvent); - - if (serveEvent.getWasMatched()) { - WebSocketEndpoint.broadcast(Message.MATCHED); - } else { - WebSocketEndpoint.broadcast(Message.UNMATCHED); - } + protected void beforeResponseSent(ServeEvent serveEvent, Response response) { + if (!response.wasConfigured()) { + appendNonMatchSubEvent(serveEvent); } + requestJournal.requestReceived(serveEvent); + + if (serveEvent.getWasMatched()) { + WebSocketEndpoint.broadcast(Message.MATCHED); + } else { + WebSocketEndpoint.broadcast(Message.UNMATCHED); + } + + triggerListeners(BEFORE_RESPONSE_SENT, serveEvent); + } + + private void appendNonMatchSubEvent(ServeEvent serveEvent) { + final ResponseDefinition responseDefinition = + notMatchedRenderer.execute(admin, serveEvent, PathParams.empty()); + final HttpHeaders headers = responseDefinition.getHeaders(); + final String contentTypeHeader = + headers != null && headers.getHeader(ContentTypeHeader.KEY).isPresent() + ? headers.getContentTypeHeader().firstValue() + : null; + + serveEvent.appendSubEvent( + SubEvent.NON_MATCH_TYPE, + new DiffEventData( + responseDefinition.getStatus(), contentTypeHeader, responseDefinition.getBody())); + } + @Override protected void afterResponseSent(final ServeEvent serveEvent, final Response response) { + requestJournal.serveCompleted(serveEvent); + + triggerPostServeActions(serveEvent); + + triggerListeners(AFTER_COMPLETE, serveEvent); + } + + private void triggerPostServeActions(ServeEvent serveEvent) { for (final PostServeAction postServeAction : this.postServeActions.values()) { postServeAction.doGlobalAction(serveEvent, this.admin); } @@ -94,4 +138,25 @@ protected void afterResponseSent(final ServeEvent serveEvent, final Response res } } } + + private void triggerListeners( + ServeEventListener.RequestPhase requestPhase, ServeEvent serveEvent) { + serveEventListeners.values().stream() + .filter(ServeEventListener::applyGlobally) + .forEach(listener -> listener.onEvent(requestPhase, serveEvent, Parameters.empty())); + + List serveEventListenerDefinitions = + serveEvent.getServeEventListeners(); + for (ServeEventListenerDefinition listenerDef : serveEventListenerDefinitions) { + ServeEventListener listener = serveEventListeners.get(listenerDef.getName()); + if (listener != null + && !listener.applyGlobally() + && listenerDef.shouldFireFor(requestPhase)) { + Parameters parameters = listenerDef.getParameters(); + listener.onEvent(requestPhase, serveEvent, parameters); + } else { + notifier().error("No per-stub listener was found named \"" + listenerDef.getName() + "\""); + } + } + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/StubResponseRenderer.java b/src/main/java/com/github/tomakehurst/wiremock/http/StubResponseRenderer.java index 176b843f7b..7477c9794d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/StubResponseRenderer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/StubResponseRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,33 +15,43 @@ */ package com.github.tomakehurst.wiremock.http; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.http.Response.response; -import static com.google.common.base.MoreObjects.firstNonNull; -import com.github.tomakehurst.wiremock.common.BinaryFile; import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.InputStreamSource; import com.github.tomakehurst.wiremock.extension.ResponseTransformer; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; +import com.github.tomakehurst.wiremock.global.GlobalSettings; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.SettingsStore; +import com.github.tomakehurst.wiremock.store.files.BlobStoreFileSource; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import java.util.List; public class StubResponseRenderer implements ResponseRenderer { - private final FileSource fileSource; - private final GlobalSettingsHolder globalSettingsHolder; + private final BlobStore filesBlobStore; + private final FileSource filesFileSource; + private final SettingsStore settingsStore; private final ProxyResponseRenderer proxyResponseRenderer; private final List responseTransformers; + private final List v2ResponseTransformers; public StubResponseRenderer( - FileSource fileSource, - GlobalSettingsHolder globalSettingsHolder, + BlobStore filesBlobStore, + SettingsStore settingsStore, ProxyResponseRenderer proxyResponseRenderer, - List responseTransformers) { - this.fileSource = fileSource; - this.globalSettingsHolder = globalSettingsHolder; + List responseTransformers, + List v2ResponseTransformers) { + this.filesBlobStore = filesBlobStore; + this.settingsStore = settingsStore; this.proxyResponseRenderer = proxyResponseRenderer; this.responseTransformers = responseTransformers; + this.v2ResponseTransformers = v2ResponseTransformers; + + filesFileSource = new BlobStoreFileSource(filesBlobStore); } @Override @@ -52,11 +62,17 @@ public Response render(ServeEvent serveEvent) { } Response response = buildResponse(serveEvent); - return applyTransformations( - responseDefinition.getOriginalRequest(), - responseDefinition, - response, - responseTransformers); + + response = + applyTransformations( + responseDefinition.getOriginalRequest(), + responseDefinition, + response, + responseTransformers); + + response = applyV2Transformations(response, serveEvent, v2ResponseTransformers); + + return response; } private Response buildResponse(ServeEvent serveEvent) { @@ -81,13 +97,32 @@ private Response applyTransformations( Response newResponse = transformer.applyGlobally() || responseDefinition.hasTransformer(transformer) ? transformer.transform( - request, response, fileSource, responseDefinition.getTransformerParameters()) + request, response, filesFileSource, responseDefinition.getTransformerParameters()) : response; return applyTransformations( request, responseDefinition, newResponse, transformers.subList(1, transformers.size())); } + private Response applyV2Transformations( + Response response, ServeEvent serveEvent, List transformers) { + + if (transformers.isEmpty()) { + return response; + } + + final ResponseTransformerV2 transformer = transformers.get(0); + final ResponseDefinition responseDefinition = serveEvent.getResponseDefinition(); + + Response newResponse = + transformer.applyGlobally() || responseDefinition.hasTransformer(transformer) + ? transformer.transform(response, serveEvent) + : response; + + return applyV2Transformations( + newResponse, serveEvent, transformers.subList(1, transformers.size())); + } + private Response.Builder renderDirectly(ServeEvent serveEvent) { ResponseDefinition responseDefinition = serveEvent.getResponseDefinition(); @@ -95,7 +130,7 @@ private Response.Builder renderDirectly(ServeEvent serveEvent) { StubMapping stubMapping = serveEvent.getStubMapping(); if (serveEvent.getWasMatched() && stubMapping != null) { headers = - firstNonNull(headers, new HttpHeaders()) + getFirstNonNull(headers, new HttpHeaders()) .plus(new HttpHeader("Matched-Stub-Id", stubMapping.getId().toString())); if (stubMapping.getName() != null) { @@ -103,6 +138,7 @@ private Response.Builder renderDirectly(ServeEvent serveEvent) { } } + GlobalSettings settings = settingsStore.get(); Response.Builder responseBuilder = response() .status(responseDefinition.getStatus()) @@ -110,21 +146,18 @@ private Response.Builder renderDirectly(ServeEvent serveEvent) { .headers(headers) .fault(responseDefinition.getFault()) .configureDelay( - globalSettingsHolder.get().getFixedDelay(), - globalSettingsHolder.get().getDelayDistribution(), + settings.getFixedDelay(), + settings.getDelayDistribution(), responseDefinition.getFixedDelayMilliseconds(), responseDefinition.getDelayDistribution()) .chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay()); if (responseDefinition.specifiesBodyFile()) { - BinaryFile bodyFile = fileSource.getBinaryFileNamed(responseDefinition.getBodyFileName()); - responseBuilder.body(bodyFile); + final InputStreamSource bodyStreamSource = + filesBlobStore.getStreamSource(responseDefinition.getBodyFileName()); + responseBuilder.body(bodyStreamSource); } else if (responseDefinition.specifiesBodyContent()) { - if (responseDefinition.specifiesBinaryBodyContent()) { - responseBuilder.body(responseDefinition.getByteBody()); - } else { - responseBuilder.body(responseDefinition.getByteBody()); - } + responseBuilder.body(responseDefinition.getByteBody()); } return responseBuilder; diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileItemPartAdapter.java b/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileItemPartAdapter.java index 09b9f7bfb4..3b9df65e2d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileItemPartAdapter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileItemPartAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,11 @@ import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.Request; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterators; +import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; +import java.util.List; +import java.util.function.Function; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemHeaders; @@ -42,20 +43,22 @@ public String getName() { @Override public HttpHeader getHeader(String name) { Iterator headerValues = fileItem.getHeaders().getHeaders(name); - return new HttpHeader(name, Iterators.toArray(headerValues, String.class)); + List values = new ArrayList<>(); + headerValues.forEachRemaining(values::add); + return new HttpHeader(name, values); } @Override public HttpHeaders getHeaders() { FileItemHeaders headers = fileItem.getHeaders(); Iterator i = headers.getHeaderNames(); - ImmutableList.Builder builder = ImmutableList.builder(); + List headersList = new ArrayList<>(); while (i.hasNext()) { String name = i.next(); - builder.add(getHeader(name)); + headersList.add(getHeader(name)); } - return new HttpHeaders(builder.build()); + return new HttpHeaders(Collections.unmodifiableList(headersList)); } @Override @@ -63,11 +66,5 @@ public Body getBody() { return new Body(fileItem.get()); } - public static final Function TO_PARTS = - new Function() { - @Override - public Request.Part apply(FileItem fileItem) { - return new FileItemPartAdapter(fileItem); - } - }; + public static final Function TO_PARTS = FileItemPartAdapter::new; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileUpload.java b/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileUpload.java new file mode 100644 index 0000000000..2fda022a00 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/http/multipart/FileUpload.java @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.http.multipart; + +import static java.lang.String.format; + +import com.github.tomakehurst.wiremock.common.Exceptions; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileItemHeaders; +import org.apache.commons.fileupload.FileItemIterator; +import org.apache.commons.fileupload.FileItemStream; +import org.apache.commons.fileupload.FileUploadBase; +import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException; +import org.apache.commons.fileupload.FileUploadBase.FileUploadIOException; +import org.apache.commons.fileupload.FileUploadBase.IOFileUploadException; +import org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException; +import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.MultipartStream; +import org.apache.commons.fileupload.ParameterParser; +import org.apache.commons.fileupload.RequestContext; +import org.apache.commons.fileupload.UploadContext; +import org.apache.commons.fileupload.util.Closeable; +import org.apache.commons.fileupload.util.FileItemHeadersImpl; +import org.apache.commons.fileupload.util.LimitedInputStream; +import org.apache.commons.fileupload.util.Streams; +import org.apache.commons.io.IOUtils; + +/** + * The implementation is largely ported from {@link org.apache.commons.fileupload.FileUpload} and + * {@link org.apache.commons.fileupload.FileUploadBase} to support 'jakarta.servlet' instead of + * 'javax.servlet'. The standard support of multipart content type by Jetty in limited to + * 'multipart/form-data', so 'multipart/mixed' and 'multipart/related' are not recognized and parsed + * properly. To preserve backward compatibility and support wider range of multipart content, + * re-implementing this part of the upload. + */ +class FileUpload { + private final FileItemFactory fileItemFactory; + + FileUpload(FileItemFactory fileItemFactory) { + this.fileItemFactory = fileItemFactory; + } + + /** + * The maximum size permitted for the complete request, as opposed to {@link #fileSizeMax}. A + * value of -1 indicates no maximum. + */ + private long sizeMax = -1; + + /** + * The maximum size permitted for a single uploaded file, as opposed to {@link #sizeMax}. A value + * of -1 indicates no maximum. + */ + private long fileSizeMax = -1; + + /** The content encoding to use when reading part headers. */ + private String headerEncoding; + + protected FileItemIterator getItemIterator(RequestContext ctx) + throws FileUploadException, IOException { + try { + return new FileItemIteratorImpl(ctx); + } catch (FileUploadIOException e) { + // unwrap encapsulated SizeException + throw (FileUploadException) e.getCause(); + } + } + + protected FileItemFactory getFileItemFactory() { + return fileItemFactory; + } + + /** + * Processes an RFC 1867 compliant + * multipart/form-data stream. + * + * @param ctx The context for the request to be parsed. + * @return A list of FileItem instances parsed from the request, in the order that + * they were transmitted. + * @throws FileUploadException if there are problems reading/parsing the request or storing files. + */ + public List parseRequest(RequestContext ctx) throws FileUploadException { + List items = new ArrayList(); + boolean successful = false; + try { + FileItemIterator iter = getItemIterator(ctx); + FileItemFactory fac = getFileItemFactory(); + if (fac == null) { + throw new NullPointerException("No FileItemFactory has been set."); + } + while (iter.hasNext()) { + final FileItemStream item = iter.next(); + // Don't use getName() here to prevent an InvalidFileNameException. + final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name; + FileItem fileItem = + fac.createItem( + item.getFieldName(), item.getContentType(), item.isFormField(), fileName); + items.add(fileItem); + try { + Streams.copy(item.openStream(), fileItem.getOutputStream(), true); + } catch (FileUploadIOException e) { + throw (FileUploadException) e.getCause(); + } catch (IOException e) { + throw new IOFileUploadException( + format( + "Processing of %s request failed. %s", + FileUploadBase.MULTIPART_FORM_DATA, e.getMessage()), + e); + } + final FileItemHeaders fih = item.getHeaders(); + fileItem.setHeaders(fih); + } + successful = true; + return items; + } catch (FileUploadIOException e) { + throw (FileUploadException) e.getCause(); + } catch (IOException e) { + throw new FileUploadException(e.getMessage(), e); + } finally { + if (!successful) { + for (FileItem fileItem : items) { + try { + fileItem.delete(); + } catch (Exception ignored) { + // ignored TODO perhaps add to tracker delete failure list somehow? + } + } + } + } + } + + /** + * Retrieves the boundary from the Content-type header. + * + * @param contentType The value of the content type header from which to extract the boundary + * value. + * @return The boundary, as a byte array. + */ + protected byte[] getBoundary(String contentType) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(contentType, new char[] {';', ','}); + String boundaryStr = params.get("boundary"); + + if (boundaryStr == null) { + return null; + } + byte[] boundary; + try { + boundary = boundaryStr.getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + boundary = boundaryStr.getBytes(); // Intentionally falls back to default charset + } + return boundary; + } + + /** + * Retrieves the file name from the Content-disposition header. + * + * @param headers The HTTP headers object. + * @return The file name for the current encapsulation. + */ + protected String getFileName(FileItemHeaders headers) { + return getFileName(headers.getHeader(FileUploadBase.CONTENT_DISPOSITION)); + } + + /** + * Returns the given content-disposition headers file name. + * + * @param pContentDisposition The content-disposition headers value. + * @return The file name + */ + private String getFileName(String pContentDisposition) { + String fileName = null; + if (pContentDisposition != null) { + String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH); + if (cdl.startsWith(FileUploadBase.FORM_DATA) || cdl.startsWith(FileUploadBase.ATTACHMENT)) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(pContentDisposition, ';'); + if (params.containsKey("filename")) { + fileName = params.get("filename"); + if (fileName != null) { + fileName = fileName.trim(); + } else { + // Even if there is no value, the parameter is present, + // so we return an empty file name rather than no file + // name. + fileName = ""; + } + } + } + } + return fileName; + } + + /** + * Retrieves the field name from the Content-disposition header. + * + * @param headers A Map containing the HTTP request headers. + * @return The field name for the current encapsulation. + */ + protected String getFieldName(FileItemHeaders headers) { + return getFieldName(headers.getHeader(FileUploadBase.CONTENT_DISPOSITION)); + } + + /** + * Returns the field name, which is given by the content-disposition header. + * + * @param pContentDisposition The content-dispositions header value. + * @return The field jake + */ + private String getFieldName(String pContentDisposition) { + String fieldName = null; + if (pContentDisposition != null + && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FileUploadBase.FORM_DATA)) { + ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + Map params = parser.parse(pContentDisposition, ';'); + fieldName = params.get("name"); + if (fieldName != null) { + fieldName = fieldName.trim(); + } + } + return fieldName; + } + + /** + * Parses the header-part and returns as key/value pairs. + * + *

If there are multiple headers of the same names, the name will map to a comma-separated list + * containing the values. + * + * @param headerPart The header-part of the current encapsulation. + * @return A Map containing the parsed HTTP request headers. + */ + protected FileItemHeaders getParsedHeaders(String headerPart) { + final int len = headerPart.length(); + FileItemHeadersImpl headers = newFileItemHeaders(); + int start = 0; + for (; ; ) { + int end = parseEndOfLine(headerPart, start); + if (start == end) { + break; + } + StringBuilder header = new StringBuilder(headerPart.substring(start, end)); + start = end + 2; + while (start < len) { + int nonWs = start; + while (nonWs < len) { + char c = headerPart.charAt(nonWs); + if (c != ' ' && c != '\t') { + break; + } + ++nonWs; + } + if (nonWs == start) { + break; + } + // Continuation line found + end = parseEndOfLine(headerPart, nonWs); + header.append(" ").append(headerPart.substring(nonWs, end)); + start = end + 2; + } + parseHeaderLine(headers, header.toString()); + } + return headers; + } + + /** + * Creates a new instance of {@link FileItemHeaders}. + * + * @return The new instance. + */ + protected FileItemHeadersImpl newFileItemHeaders() { + return new FileItemHeadersImpl(); + } + + /** + * Skips bytes until the end of the current line. + * + * @param headerPart The headers, which are being parsed. + * @param end Index of the last byte, which has yet been processed. + * @return Index of the \r\n sequence, which indicates end of line. + */ + private int parseEndOfLine(String headerPart, int end) { + int index = end; + for (; ; ) { + int offset = headerPart.indexOf('\r', index); + if (offset == -1 || offset + 1 >= headerPart.length()) { + throw new IllegalStateException("Expected headers to be terminated by an empty line."); + } + if (headerPart.charAt(offset + 1) == '\n') { + return offset; + } + index = offset + 1; + } + } + + /** + * Reads the next header line. + * + * @param headers String with all headers. + * @param header Map where to store the current header. + */ + private void parseHeaderLine(FileItemHeadersImpl headers, String header) { + final int colonOffset = header.indexOf(':'); + if (colonOffset == -1) { + // This header line is malformed, skip it. + return; + } + String headerName = header.substring(0, colonOffset).trim(); + String headerValue = header.substring(header.indexOf(':') + 1).trim(); + headers.addHeader(headerName, headerValue); + } + + /** The iterator, which is returned by {@link FileUpload#getItemIterator(RequestContext)}. */ + private class FileItemIteratorImpl implements FileItemIterator { + + /** Default implementation of {@link FileItemStream}. */ + class FileItemStreamImpl implements FileItemStream { + + /** The file items content type. */ + private final String contentType; + + /** The file items field name. */ + private final String fieldName; + + /** The file items file name. */ + private final String name; + + /** Whether the file item is a form field. */ + private final boolean formField; + + /** The file items input stream. */ + private final InputStream stream; + + /** Whether the file item was already opened. */ + private boolean opened; + + /** The headers, if any. */ + private FileItemHeaders headers; + + /** + * Creates a new instance. + * + * @param pName The items file name, or null. + * @param pFieldName The items field name. + * @param pContentType The items content type, or null. + * @param pFormField Whether the item is a form field. + * @param pContentLength The items content length, if known, or -1 + * @throws IOException Creating the file item failed. + */ + FileItemStreamImpl( + String pName, + String pFieldName, + String pContentType, + boolean pFormField, + long pContentLength) + throws IOException { + name = pName; + fieldName = pFieldName; + contentType = pContentType; + formField = pFormField; + if (fileSizeMax != -1) { // Check if limit is already exceeded + if (pContentLength != -1 && pContentLength > fileSizeMax) { + FileSizeLimitExceededException e = + new FileSizeLimitExceededException( + format( + "The field %s exceeds its maximum permitted size of %s bytes.", + fieldName, Long.valueOf(fileSizeMax)), + pContentLength, + fileSizeMax); + e.setFileName(pName); + e.setFieldName(pFieldName); + throw new FileUploadIOException(e); + } + } + // OK to construct stream now + final MultipartStream.ItemInputStream itemStream = newInputStream(multi); + InputStream istream = itemStream; + if (fileSizeMax != -1) { + istream = + new LimitedInputStream(istream, fileSizeMax) { + @Override + protected void raiseError(long pSizeMax, long pCount) throws IOException { + itemStream.close(); + FileSizeLimitExceededException e = + new FileSizeLimitExceededException( + format( + "The field %s exceeds its maximum permitted size of %s bytes.", + fieldName, Long.valueOf(pSizeMax)), + pCount, + pSizeMax); + e.setFieldName(fieldName); + e.setFileName(name); + throw new FileUploadIOException(e); + } + }; + } + stream = istream; + } + + private MultipartStream.ItemInputStream newInputStream(MultipartStream multipartStream) { + return Exceptions.uncheck( + () -> { + final Method newInputStreamMethod = + multipartStream.getClass().getDeclaredMethod("newInputStream"); + newInputStreamMethod.setAccessible(true); + return (MultipartStream.ItemInputStream) newInputStreamMethod.invoke(multipartStream); + }, + MultipartStream.ItemInputStream.class); + } + + /** + * Returns the items content type, or null. + * + * @return Content type, if known, or null. + */ + @Override + public String getContentType() { + return contentType; + } + + /** + * Returns the items field name. + * + * @return Field name. + */ + @Override + public String getFieldName() { + return fieldName; + } + + /** + * Returns the items file name. + * + * @return File name, if known, or null. + * @throws InvalidFileNameException The file name contains a NUL character, which might be an + * indicator of a security attack. If you intend to use the file name anyways, catch the + * exception and use InvalidFileNameException#getName(). + */ + @Override + public String getName() { + return Streams.checkFileName(name); + } + + /** + * Returns, whether this is a form field. + * + * @return True, if the item is a form field, otherwise false. + */ + @Override + public boolean isFormField() { + return formField; + } + + /** + * Returns an input stream, which may be used to read the items contents. + * + * @return Opened input stream. + * @throws IOException An I/O error occurred. + */ + @Override + public InputStream openStream() throws IOException { + if (opened) { + throw new IllegalStateException("The stream was already opened."); + } + if (stream instanceof Closeable && ((Closeable) stream).isClosed()) { + throw new FileItemStream.ItemSkippedException(); + } + return stream; + } + + /** + * Closes the file item. + * + * @throws IOException An I/O error occurred. + */ + void close() throws IOException { + stream.close(); + } + + /** + * Returns the file item headers. + * + * @return The items header object + */ + @Override + public FileItemHeaders getHeaders() { + return headers; + } + + /** + * Sets the file item headers. + * + * @param pHeaders The items header object + */ + @Override + public void setHeaders(FileItemHeaders pHeaders) { + headers = pHeaders; + } + } + + /** The multi part stream to process. */ + private final MultipartStream multi; + + /** The boundary, which separates the various parts. */ + private final byte[] boundary; + + /** The item, which we currently process. */ + private FileItemStreamImpl currentItem; + + /** The current items field name. */ + private String currentFieldName; + + /** Whether we are currently skipping the preamble. */ + private boolean skipPreamble; + + /** Whether the current item may still be read. */ + private boolean itemValid; + + /** Whether we have seen the end of the file. */ + private boolean eof; + + /** + * Creates a new instance. + * + * @param ctx The request context. + * @throws FileUploadException An error occurred while parsing the request. + * @throws IOException An I/O error occurred. + */ + FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException { + if (ctx == null) { + throw new NullPointerException("ctx parameter"); + } + + String contentType = ctx.getContentType(); + if ((null == contentType) + || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(FileUploadBase.MULTIPART))) { + throw new InvalidContentTypeException( + format( + "the request doesn't contain a %s or %s stream, content type header is %s", + FileUploadBase.MULTIPART_FORM_DATA, FileUploadBase.MULTIPART_MIXED, contentType)); + } + + @SuppressWarnings("deprecation") // still has to be backward compatible + final int contentLengthInt = ctx.getContentLength(); + + final long requestSize = + UploadContext.class.isAssignableFrom(ctx.getClass()) + // Inline conditional is OK here CHECKSTYLE:OFF + ? ((UploadContext) ctx).contentLength() + : contentLengthInt; + // CHECKSTYLE:ON + + InputStream input; // N.B. this is eventually closed in MultipartStream processing + if (sizeMax >= 0) { + if (requestSize != -1 && requestSize > sizeMax) { + throw new SizeLimitExceededException( + format( + "the request was rejected because its size (%s) exceeds the configured maximum (%s)", + Long.valueOf(requestSize), Long.valueOf(sizeMax)), + requestSize, + sizeMax); + } + // N.B. this is eventually closed in MultipartStream processing + input = + new LimitedInputStream(ctx.getInputStream(), sizeMax) { + @Override + protected void raiseError(long pSizeMax, long pCount) throws IOException { + FileUploadException ex = + new SizeLimitExceededException( + format( + "the request was rejected because its size (%s) exceeds the configured maximum (%s)", + Long.valueOf(pCount), Long.valueOf(pSizeMax)), + pCount, + pSizeMax); + throw new FileUploadIOException(ex); + } + }; + } else { + input = ctx.getInputStream(); + } + + String charEncoding = headerEncoding; + if (charEncoding == null) { + charEncoding = ctx.getCharacterEncoding(); + } + + boundary = getBoundary(contentType); + if (boundary == null) { + IOUtils.closeQuietly(input); // avoid possible resource leak + throw new FileUploadException( + "the request was rejected because no multipart boundary was found"); + } + + try { + multi = new MultipartStream(input, boundary, 4096, null); + } catch (IllegalArgumentException iae) { + IOUtils.closeQuietly(input); // avoid possible resource leak + throw new InvalidContentTypeException( + format( + "The boundary specified in the %s header is too long", FileUploadBase.CONTENT_TYPE), + iae); + } + multi.setHeaderEncoding(charEncoding); + + skipPreamble = true; + findNextItem(); + } + + /** + * Called for finding the next item, if any. + * + * @return True, if an next item was found, otherwise false. + * @throws IOException An I/O error occurred. + */ + private boolean findNextItem() throws IOException { + if (eof) { + return false; + } + if (currentItem != null) { + currentItem.close(); + currentItem = null; + } + for (; ; ) { + boolean nextPart; + if (skipPreamble) { + nextPart = multi.skipPreamble(); + } else { + nextPart = multi.readBoundary(); + } + if (!nextPart) { + if (currentFieldName == null) { + // Outer multipart terminated -> No more data + eof = true; + return false; + } + // Inner multipart terminated -> Return to parsing the outer + multi.setBoundary(boundary); + currentFieldName = null; + continue; + } + FileItemHeaders headers = getParsedHeaders(multi.readHeaders()); + if (currentFieldName == null) { + // We're parsing the outer multipart + String fieldName = getFieldName(headers); + if (fieldName != null) { + String subContentType = headers.getHeader(FileUploadBase.CONTENT_TYPE); + if (subContentType != null + && subContentType + .toLowerCase(Locale.ENGLISH) + .startsWith(FileUploadBase.MULTIPART_MIXED)) { + currentFieldName = fieldName; + // Multiple files associated with this field name + byte[] subBoundary = getBoundary(subContentType); + multi.setBoundary(subBoundary); + skipPreamble = true; + continue; + } + String fileName = getFileName(headers); + currentItem = + new FileItemStreamImpl( + fileName, + fieldName, + headers.getHeader(FileUploadBase.CONTENT_TYPE), + fileName == null, + getContentLength(headers)); + currentItem.setHeaders(headers); + itemValid = true; + return true; + } + } else { + String fileName = getFileName(headers); + if (fileName != null) { + currentItem = + new FileItemStreamImpl( + fileName, + currentFieldName, + headers.getHeader(FileUploadBase.CONTENT_TYPE), + false, + getContentLength(headers)); + currentItem.setHeaders(headers); + itemValid = true; + return true; + } + } + multi.discardBodyData(); + } + } + + private long getContentLength(FileItemHeaders pHeaders) { + try { + return Long.parseLong(pHeaders.getHeader(FileUploadBase.CONTENT_LENGTH)); + } catch (Exception e) { + return -1; + } + } + + /** + * Returns, whether another instance of {@link FileItemStream} is available. + * + * @throws FileUploadException Parsing or processing the file item failed. + * @throws IOException Reading the file item failed. + * @return True, if one or more additional file items are available, otherwise false. + */ + @Override + public boolean hasNext() throws FileUploadException, IOException { + if (eof) { + return false; + } + if (itemValid) { + return true; + } + try { + return findNextItem(); + } catch (FileUploadIOException e) { + // unwrap encapsulated SizeException + throw (FileUploadException) e.getCause(); + } + } + + /** + * Returns the next available {@link FileItemStream}. + * + * @throws java.util.NoSuchElementException No more items are available. Use {@link #hasNext()} + * to prevent this exception. + * @throws FileUploadException Parsing or processing the file item failed. + * @throws IOException Reading the file item failed. + * @return FileItemStream instance, which provides access to the next file item. + */ + @Override + public FileItemStream next() throws FileUploadException, IOException { + if (eof || (!itemValid && !hasNext())) { + throw new NoSuchElementException(); + } + itemValid = false; + return currentItem; + } + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/multipart/PartParser.java b/src/main/java/com/github/tomakehurst/wiremock/http/multipart/PartParser.java index 8f7a0dfaaf..ede8c20f14 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/multipart/PartParser.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/multipart/PartParser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,17 @@ import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.Request; -import com.google.common.collect.Lists; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.List; -import org.apache.commons.fileupload.*; +import java.util.stream.Collectors; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.UploadContext; import org.apache.commons.fileupload.disk.DiskFileItemFactory; public class PartParser { @@ -49,7 +52,7 @@ public static Collection parseFrom(Request request) { try { List items = upload.parseRequest(uploadContext); - return Lists.transform(items, TO_PARTS); + return items.stream().map(TO_PARTS).collect(Collectors.toList()); } catch (FileUploadException e) { return throwUnchecked(e, Collection.class); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java b/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java index 4b4bfa720b..d12065f6a4 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java @@ -52,6 +52,7 @@ public static CertificateAuthority generateCertificateAuthority() makeX509CertInfo( sigAlg, "WireMock Local Self Signed Root Certificate", + ZonedDateTime.now().minus(Period.ofDays(1)), Period.ofYears(10), pair.getPublic(), certificateAuthorityExtensions(pair.getPublic())); @@ -125,6 +126,7 @@ CertChainAndKey generateCertificate(String keyType, SNIHostName hostName) makeX509CertInfo( sigAlg, hostName.getAsciiName(), + ZonedDateTime.now().minus(Period.ofDays(1)), Period.ofYears(1), pair.getPublic(), subjectAlternativeName(hostName)); @@ -168,11 +170,11 @@ private static KeyPair generateKeyPair(String keyType) throws NoSuchAlgorithmExc private static X509CertInfo makeX509CertInfo( String sigAlg, String subjectName, + ZonedDateTime start, Period validity, PublicKey publicKey, CertificateExtensions certificateExtensions) throws IOException, CertificateException, NoSuchAlgorithmException { - ZonedDateTime start = ZonedDateTime.now(); ZonedDateTime end = start.plus(validity); X500Name myname = new X500Name("CN=" + subjectName); diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CompositeTrustManager.java b/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CompositeTrustManager.java index de0e7a4b29..f710503d4a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CompositeTrustManager.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CompositeTrustManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,11 +69,8 @@ private X509Certificate[] loadAcceptedIssuers(List tru public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { checkAllTrustManagers( - new CertificateChecker() { - @Override - public void check(X509ExtendedTrustManager tm) throws CertificateException { - tm.checkClientTrusted(chain, authType); - } + tm -> { + tm.checkClientTrusted(chain, authType); }); } @@ -81,11 +78,8 @@ public void check(X509ExtendedTrustManager tm) throws CertificateException { public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { checkAllTrustManagers( - new CertificateChecker() { - @Override - public void check(X509ExtendedTrustManager tm) throws CertificateException { - tm.checkServerTrusted(chain, authType); - } + tm -> { + tm.checkServerTrusted(chain, authType); }); } @@ -94,11 +88,8 @@ public void checkClientTrusted( final X509Certificate[] chain, final String authType, final Socket socket) throws CertificateException { checkAllTrustManagers( - new CertificateChecker() { - @Override - public void check(X509ExtendedTrustManager tm) throws CertificateException { - tm.checkClientTrusted(chain, authType, socket); - } + tm -> { + tm.checkClientTrusted(chain, authType, socket); }); } @@ -107,11 +98,8 @@ public void checkServerTrusted( final X509Certificate[] chain, final String authType, final Socket socket) throws CertificateException { checkAllTrustManagers( - new CertificateChecker() { - @Override - public void check(X509ExtendedTrustManager tm) throws CertificateException { - tm.checkServerTrusted(chain, authType, socket); - } + tm -> { + tm.checkServerTrusted(chain, authType, socket); }); } @@ -120,11 +108,8 @@ public void checkClientTrusted( final X509Certificate[] chain, final String authType, final SSLEngine engine) throws CertificateException { checkAllTrustManagers( - new CertificateChecker() { - @Override - public void check(X509ExtendedTrustManager tm) throws CertificateException { - tm.checkClientTrusted(chain, authType, engine); - } + tm -> { + tm.checkClientTrusted(chain, authType, engine); }); } @@ -133,11 +118,8 @@ public void checkServerTrusted( final X509Certificate[] chain, final String authType, final SSLEngine engine) throws CertificateException { checkAllTrustManagers( - new CertificateChecker() { - @Override - public void check(X509ExtendedTrustManager tm) throws CertificateException { - tm.checkServerTrusted(chain, authType, engine); - } + tm -> { + tm.checkServerTrusted(chain, authType, engine); }); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/CollectingNetworkTrafficListener.java b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/CollectingNetworkTrafficListener.java index 31cfbbb043..bd7bc8c9ec 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/CollectingNetworkTrafficListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/CollectingNetworkTrafficListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.github.tomakehurst.wiremock.http.trafficlistener; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; +import static java.nio.charset.StandardCharsets.UTF_8; import java.net.Socket; import java.nio.ByteBuffer; @@ -24,10 +25,11 @@ import java.nio.charset.CharsetDecoder; public class CollectingNetworkTrafficListener implements WiremockNetworkTrafficListener { + private final StringBuilder requestBuilder = new StringBuilder(); private final StringBuilder responseBuilder = new StringBuilder(); - private final Charset charset = Charset.forName("UTF-8"); + private final Charset charset = UTF_8; private final CharsetDecoder decoder = charset.newDecoder(); @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListener.java b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListener.java index 1578992bff..2067dadc9e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,45 +15,41 @@ */ package com.github.tomakehurst.wiremock.http.trafficlistener; -import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import java.net.Socket; import java.nio.ByteBuffer; -import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; public class ConsoleNotifyingWiremockNetworkTrafficListener implements WiremockNetworkTrafficListener { - private static final ConsoleNotifier CONSOLE_NOTIFIER = new ConsoleNotifier(true); - private final Charset charset = Charset.forName("UTF-8"); - private final CharsetDecoder decoder = charset.newDecoder(); + private final WiremockNetworkTrafficListener wiremockNetworkTrafficListener; + + public ConsoleNotifyingWiremockNetworkTrafficListener(Charset charset) { + this.wiremockNetworkTrafficListener = + WiremockNetworkTrafficListeners.createConsoleNotifying(charset); + } + + public ConsoleNotifyingWiremockNetworkTrafficListener() { + this.wiremockNetworkTrafficListener = WiremockNetworkTrafficListeners.createConsoleNotifying(); + } @Override public void opened(Socket socket) { - CONSOLE_NOTIFIER.info("Opened " + socket); + wiremockNetworkTrafficListener.opened(socket); } @Override public void incoming(Socket socket, ByteBuffer bytes) { - try { - CONSOLE_NOTIFIER.info("Incoming bytes: " + decoder.decode(bytes)); - } catch (CharacterCodingException e) { - CONSOLE_NOTIFIER.error("Problem decoding network traffic", e); - } + wiremockNetworkTrafficListener.incoming(socket, bytes); } @Override public void outgoing(Socket socket, ByteBuffer bytes) { - try { - CONSOLE_NOTIFIER.info("Outgoing bytes: " + decoder.decode(bytes)); - } catch (CharacterCodingException e) { - CONSOLE_NOTIFIER.error("Problem decoding network traffic", e); - } + wiremockNetworkTrafficListener.outgoing(socket, bytes); } @Override public void closed(Socket socket) { - CONSOLE_NOTIFIER.info("Closed " + socket); + wiremockNetworkTrafficListener.closed(socket); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListener.java b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListener.java new file mode 100644 index 0000000000..aa5a703f6d --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListener.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.http.trafficlistener; + +import com.github.tomakehurst.wiremock.common.Notifier; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +final class NotifyingWiremockNetworkTrafficListener implements WiremockNetworkTrafficListener { + + private final Notifier notifier; + private final Charset charset; + private final CharsetDecoder charsetDecoder; + + NotifyingWiremockNetworkTrafficListener(Notifier notifier, Charset charset) { + this.notifier = notifier; + this.charset = charset; + this.charsetDecoder = charset.newDecoder(); + } + + @Override + public void opened(Socket socket) { + notifier.info("Opened " + socket); + } + + @Override + public void incoming(Socket socket, ByteBuffer bytes) { + try { + notifier.info("Incoming bytes: " + charsetDecoder.decode(bytes)); + } catch (CharacterCodingException e) { + notifier.error("Incoming bytes omitted. Could not decode with charset: " + charset); + } + } + + @Override + public void outgoing(Socket socket, ByteBuffer bytes) { + try { + notifier.info("Outgoing bytes: " + charsetDecoder.decode(bytes)); + } catch (CharacterCodingException e) { + notifier.error("Outgoing bytes omitted. Could not decode with charset: " + charset); + } + } + + @Override + public void closed(Socket socket) { + notifier.info("Closed " + socket); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/WiremockNetworkTrafficListeners.java b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/WiremockNetworkTrafficListeners.java new file mode 100644 index 0000000000..c63e623202 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/http/trafficlistener/WiremockNetworkTrafficListeners.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.http.trafficlistener; + +import com.github.tomakehurst.wiremock.common.ConsoleNotifier; +import com.github.tomakehurst.wiremock.common.Notifier; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public final class WiremockNetworkTrafficListeners { + private static final ConsoleNotifier CONSOLE_NOTIFIER = new ConsoleNotifier(true); + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private WiremockNetworkTrafficListeners() {} + + public static WiremockNetworkTrafficListener createNotifying(Notifier notifier, Charset charset) { + return new NotifyingWiremockNetworkTrafficListener(notifier, charset); + } + + public static WiremockNetworkTrafficListener createConsoleNotifying() { + return new NotifyingWiremockNetworkTrafficListener(CONSOLE_NOTIFIER, DEFAULT_CHARSET); + } + + public static WiremockNetworkTrafficListener createConsoleNotifying(Charset charset) { + return new NotifyingWiremockNetworkTrafficListener(CONSOLE_NOTIFIER, charset); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/DefaultMultipartRequestConfigurer.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java similarity index 66% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/DefaultMultipartRequestConfigurer.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java index 1edfa74426..d6ac513291 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/DefaultMultipartRequestConfigurer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,17 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import com.github.tomakehurst.wiremock.servlet.MultipartRequestConfigurer; -import javax.servlet.MultipartConfigElement; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.MultipartConfigElement; +import jakarta.servlet.http.HttpServletRequest; public class DefaultMultipartRequestConfigurer implements MultipartRequestConfigurer { @Override public void configure(HttpServletRequest request) { - MultipartConfigElement multipartConfigElement = new MultipartConfigElement((String) null); - request.setAttribute("org.eclipse.jetty.multipartConfig", multipartConfigElement); + request.setAttribute( + org.eclipse.jetty.server.Request.__MULTIPART_CONFIG_ELEMENT, + new MultipartConfigElement( + System.getProperty("java.io.tmpdir"), Integer.MAX_VALUE, -1L, 0)); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyFaultInjector.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjector.java similarity index 81% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyFaultInjector.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjector.java index 32f67d9c80..133a53eb63 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyFaultInjector.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjector.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,26 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.github.tomakehurst.wiremock.jetty9.JettyUtils.unwrapResponse; +import static com.github.tomakehurst.wiremock.jetty.JettyUtils.unwrapResponse; +import static java.nio.charset.StandardCharsets.UTF_8; import com.github.tomakehurst.wiremock.core.FaultInjector; -import com.google.common.base.Charsets; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.Socket; import java.nio.channels.SocketChannel; -import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.io.ChannelEndPoint; +import org.eclipse.jetty.io.SelectableChannelEndPoint; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; public class JettyFaultInjector implements FaultInjector { - private static final byte[] GARBAGE = - "lskdu018973t09sylgasjkfg1][]'./.sdlv".getBytes(Charsets.UTF_8); + private static final byte[] GARBAGE = "lskdu018973t09sylgasjkfg1][]'./.sdlv".getBytes(UTF_8); private final Response response; private final Socket socket; @@ -85,7 +84,7 @@ public void randomDataAndCloseConnection() { private Socket socket() { HttpChannel httpChannel = response.getHttpOutput().getHttpChannel(); - ChannelEndPoint ep = (ChannelEndPoint) httpChannel.getEndPoint(); + SelectableChannelEndPoint ep = (SelectableChannelEndPoint) httpChannel.getEndPoint(); return ((SocketChannel) ep.getChannel()).socket(); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyFaultInjectorFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java similarity index 85% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyFaultInjectorFactory.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java index b5297c28e0..0599b216df 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyFaultInjectorFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import com.github.tomakehurst.wiremock.core.FaultInjector; import com.github.tomakehurst.wiremock.servlet.FaultInjectorFactory; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public class JettyFaultInjectorFactory implements FaultInjectorFactory { diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServer.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java similarity index 70% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServer.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java index 2fd9a93fab..0f89e13af4 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static com.github.tomakehurst.wiremock.core.WireMockApp.ADMIN_CONTEXT_ROOT; @@ -29,48 +29,49 @@ import com.github.tomakehurst.wiremock.http.trafficlistener.WiremockNetworkTrafficListener; import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.servlet.*; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; -import java.lang.reflect.Method; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.net.Socket; import java.nio.ByteBuffer; import java.util.EnumSet; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ScheduledExecutorService; -import javax.servlet.DispatcherType; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import java.util.concurrent.TimeoutException; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.mutable.MutableBoolean; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.NetworkTrafficListener; import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.rewrite.handler.RewriteRegexRule; -import org.eclipse.jetty.server.*; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; -import org.eclipse.jetty.util.ssl.SslContextFactory; -public class JettyHttpServer implements HttpServer { +public abstract class JettyHttpServer implements HttpServer { private static final String FILES_URL_MATCH = String.format("/%s/*", WireMockApp.FILES_ROOT); private static final String[] GZIPPABLE_METHODS = new String[]{"POST", "PUT", "PATCH", "DELETE"}; - private static final int DEFAULT_ACCEPTORS = 3; - private static final int DEFAULT_HEADER_SIZE = 8192; private static final MutableBoolean STRICT_HTTP_HEADERS_APPLIED = new MutableBoolean(false); - private final Server jettyServer; - private final ServerConnector httpConnector; - private final ServerConnector httpsConnector; + protected final Server jettyServer; + protected final ServerConnector httpConnector; + protected final ServerConnector httpsConnector; - private ScheduledExecutorService scheduledExecutorService; + protected ScheduledExecutorService scheduledExecutorService; public JettyHttpServer( final Options options, @@ -101,7 +102,6 @@ public JettyHttpServer( if (options.httpsSettings().enabled()) { this.httpsConnector = this.createHttpsConnector( - jettyServer, options.bindAddress(), options.httpsSettings(), options.jettySettings(), @@ -163,39 +163,20 @@ public void handle( private void addGZipHandler( final ServletContextHandler mockServiceContext, final HandlerCollection handlers) { - Class gzipHandlerClass = null; try { - gzipHandlerClass = Class.forName("org.eclipse.jetty.servlets.gzip.GzipHandler"); - } catch (final ClassNotFoundException e) { - try { - gzipHandlerClass = Class.forName("org.eclipse.jetty.server.handler.gzip.GzipHandler"); - } catch (final ClassNotFoundException e1) { - throwUnchecked(e1); - } - } - - try { - final HandlerWrapper gzipWrapper = (HandlerWrapper) gzipHandlerClass.getDeclaredConstructor().newInstance(); - setGZippableMethods(gzipWrapper, gzipHandlerClass); - gzipWrapper.setHandler(mockServiceContext); - handlers.addHandler(gzipWrapper); - } catch (final Exception e) { - throwUnchecked(e); - } - } - - private static void setGZippableMethods(HandlerWrapper gzipHandler, Class gzipHandlerClass) { - try { - Method addIncludedMethods = gzipHandlerClass.getDeclaredMethod("addIncludedMethods", String[].class); - addIncludedMethods.invoke(gzipHandler, new Object[]{GZIPPABLE_METHODS}); - } catch (Exception ignored) { - + GzipHandler gzipHandler = new GzipHandler(); + gzipHandler.addIncludedMethods(GZIPPABLE_METHODS); + gzipHandler.setHandler(mockServiceContext); + gzipHandler.setVary(null); + handlers.addHandler(gzipHandler); + } catch (Exception e) { + throwUnchecked(e); } } - protected void finalizeSetup(final Options options) { - if (!options.jettySettings().getStopTimeout().isPresent()) { + protected void finalizeSetup(final Options options) { + if (options.jettySettings().getStopTimeout().isEmpty()) { this.jettyServer.setStopTimeout(1000); } } @@ -204,9 +185,8 @@ protected Server createServer(final Options options) { final Server server = new Server(options.threadPoolFactory().buildThreadPool(options)); final JettySettings jettySettings = options.jettySettings(); final Optional stopTimeout = jettySettings.getStopTimeout(); - if (stopTimeout.isPresent()) { - server.setStopTimeout(stopTimeout.get()); - } + stopTimeout.ifPresent(server::setStopTimeout); + return server; } @@ -242,8 +222,17 @@ public void stop() { scheduledExecutorService.shutdown(); } + if (httpConnector != null) { + httpConnector.getConnectedEndPoints().forEach(EndPoint::close); + } + + if (httpsConnector != null) { + httpsConnector.getConnectedEndPoints().forEach(EndPoint::close); + } + jettyServer.stop(); jettyServer.join(); + } catch (TimeoutException ignored) { } catch (Exception e) { throwUnchecked(e); } @@ -268,105 +257,15 @@ public long stopTimeout() { return this.jettyServer.getStopTimeout(); } - protected ServerConnector createHttpConnector( - final String bindAddress, - final int port, - final JettySettings jettySettings, - final NetworkTrafficListener listener) { - - final HttpConfiguration httpConfig = this.createHttpConfig(jettySettings); - - final ServerConnector connector = this.createServerConnector( - bindAddress, jettySettings, port, listener, new HttpConnectionFactory(httpConfig)); - - return connector; - } + protected abstract ServerConnector createHttpConnector( + String bindAddress, int port, JettySettings jettySettings, NetworkTrafficListener listener); - protected ServerConnector createHttpsConnector( - Server server, + protected abstract ServerConnector createHttpsConnector( String bindAddress, HttpsSettings httpsSettings, JettySettings jettySettings, - NetworkTrafficListener listener) { - - // Added to support Android https communication. - SslContextFactory sslContextFactory = buildSslContextFactory(); - - sslContextFactory.setKeyStorePath(httpsSettings.keyStorePath()); - sslContextFactory.setKeyStorePassword(httpsSettings.keyStorePassword()); - sslContextFactory.setKeyManagerPassword(httpsSettings.keyManagerPassword()); - sslContextFactory.setKeyStoreType(httpsSettings.keyStoreType()); - if (httpsSettings.hasTrustStore()) { - sslContextFactory.setTrustStorePath(httpsSettings.trustStorePath()); - sslContextFactory.setTrustStorePassword(httpsSettings.trustStorePassword()); - } - sslContextFactory.setNeedClientAuth(httpsSettings.needClientAuth()); - - final HttpConfiguration httpConfig = this.createHttpConfig(jettySettings); - httpConfig.addCustomizer(new SecureRequestCustomizer()); - - final int port = httpsSettings.port(); - - HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig); - SslConnectionFactory sslConnectionFactory = - new SslConnectionFactory(sslContextFactory, "http/1.1"); - ConnectionFactory[] connectionFactories = - ArrayUtils.addAll( - new ConnectionFactory[]{sslConnectionFactory, httpConnectionFactory}, - buildAdditionalConnectionFactories( - httpsSettings, httpConnectionFactory, sslConnectionFactory)); - - return this.createServerConnector(bindAddress, jettySettings, port, listener, connectionFactories); - } - - protected ConnectionFactory[] buildAdditionalConnectionFactories( - HttpsSettings httpsSettings, - HttpConnectionFactory httpConnectionFactory, - SslConnectionFactory sslConnectionFactory) { - return new ConnectionFactory[]{}; - } - - // Override this for platform-specific impls - protected SslContextFactory buildSslContextFactory() { - return new SslContextFactory(); - } - - protected HttpConfiguration createHttpConfig(final JettySettings jettySettings) { - final HttpConfiguration httpConfig = new HttpConfiguration(); - httpConfig.setRequestHeaderSize( - jettySettings.getRequestHeaderSize().or(DEFAULT_HEADER_SIZE)); - httpConfig.setResponseHeaderSize(jettySettings.getResponseHeaderSize().or(DEFAULT_HEADER_SIZE)); - httpConfig.setSendDateHeader(false); - return httpConfig; - } + NetworkTrafficListener listener); - protected ServerConnector createServerConnector( - final String bindAddress, - final JettySettings jettySettings, - final int port, final NetworkTrafficListener listener, - final ConnectionFactory... connectionFactories) { - final int acceptors = jettySettings.getAcceptors().or(DEFAULT_ACCEPTORS); - final NetworkTrafficServerConnector connector = new NetworkTrafficServerConnector( - this.jettyServer, null, null, null, acceptors, 2, connectionFactories); - - connector.setPort(port); - connector.addNetworkTrafficListener(listener); - this.setJettySettings(jettySettings, connector); - connector.setHost(bindAddress); - return connector; - } - - private void setJettySettings(final JettySettings jettySettings, final ServerConnector connector) { - if (jettySettings.getAcceptQueueSize().isPresent()) { - connector.setAcceptQueueSize(jettySettings.getAcceptQueueSize().get()); - } - - if (jettySettings.getIdleTimeout().isPresent()) { - connector.setIdleTimeout(jettySettings.getIdleTimeout().get()); - } - } - - @SuppressWarnings({"rawtypes", "unchecked"}) private ServletContextHandler addMockServiceContext( StubRequestHandler stubRequestHandler, FileSource fileSource, @@ -487,6 +386,8 @@ private ServletContextHandler addAdminContext( adminContext.setAttribute(MultipartRequestConfigurer.KEY, buildMultipartRequestConfigurer()); + adminContext.addServlet(NotMatchedServlet.class, "/not-matched"); + addCorsFilter(adminContext); return adminContext; @@ -499,11 +400,15 @@ private void addCorsFilter(ServletContextHandler context) { private FilterHolder buildCorsFilter() { FilterHolder filterHolder = new FilterHolder(CrossOriginFilter.class); filterHolder.setInitParameters( - ImmutableMap.of( - "chainPreflight", "false", - "allowedOrigins", "*", - "allowedHeaders", "*", - "allowedMethods", "OPTIONS,GET,POST,PUT,PATCH,DELETE")); + Map.of( + "chainPreflight", + "false", + "allowedOrigins", + "*", + "allowedHeaders", + "*", + "allowedMethods", + "OPTIONS,GET,POST,PUT,PATCH,DELETE")); return filterHolder; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java new file mode 100644 index 0000000000..0099cd95e0 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014-2022 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.jetty; + +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.http.AdminRequestHandler; +import com.github.tomakehurst.wiremock.http.HttpServer; +import com.github.tomakehurst.wiremock.http.HttpServerFactory; +import com.github.tomakehurst.wiremock.http.StubRequestHandler; +import com.github.tomakehurst.wiremock.jetty11.Jetty11HttpServer; + +public class JettyHttpServerFactory implements HttpServerFactory { + @Override + public HttpServer buildHttpServer( + Options options, + AdminRequestHandler adminRequestHandler, + StubRequestHandler stubRequestHandler) { + return new Jetty11HttpServer(options, adminRequestHandler, stubRequestHandler); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpsFaultInjector.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpsFaultInjector.java similarity index 90% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpsFaultInjector.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpsFaultInjector.java index 97bc3060cb..eaf9a1ed84 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpsFaultInjector.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpsFaultInjector.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,24 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; +import static java.nio.charset.StandardCharsets.UTF_8; import com.github.tomakehurst.wiremock.core.FaultInjector; -import com.google.common.base.Charsets; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.Socket; -import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; public class JettyHttpsFaultInjector implements FaultInjector { - private static final byte[] GARBAGE = - "lskdu018973t09sylgasjkfg1][]'./.sdlv".getBytes(Charsets.UTF_8); + private static final byte[] GARBAGE = "lskdu018973t09sylgasjkfg1][]'./.sdlv".getBytes(UTF_8); private final Response response; private final Socket socket; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyUtils.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyUtils.java similarity index 61% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyUtils.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/JettyUtils.java index 0ab75facee..a28b7c8b3c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyUtils.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static com.github.tomakehurst.wiremock.jetty11.HttpsProxyDetectingHandler.IS_HTTPS_PROXY_REQUEST_ATTRIBUTE; -import java.lang.reflect.Method; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; import java.net.Socket; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import org.eclipse.jetty.http.HttpURI; +import java.nio.channels.SocketChannel; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.Request; @@ -32,7 +31,6 @@ public class JettyUtils { - private static final Map, Method> URI_METHOD_BY_CLASS_CACHE = new HashMap<>(); private static final boolean IS_JETTY; static { @@ -74,36 +72,21 @@ public static Socket getTlsSocket(Response response) { (SslConnection.DecryptedEndPoint) httpChannel.getEndPoint(); Object endpoint = sslEndpoint.getSslConnection().getEndPoint(); try { - return (Socket) endpoint.getClass().getMethod("getSocket").invoke(endpoint); + final SocketChannel channel = + (SocketChannel) endpoint.getClass().getMethod("getChannel").invoke(endpoint); + return channel.socket(); } catch (Exception e) { return throwUnchecked(e, Socket.class); } } - public static boolean uriIsAbsolute(Request request) { - HttpURI uri = getHttpUri(request); - return uri.getScheme() != null; - } - - private static HttpURI getHttpUri(Request request) { - try { - return (HttpURI) getURIMethodFromClass(request.getClass()).invoke(request); - } catch (Exception e) { - throw new IllegalArgumentException(request + " does not have a getUri or getHttpURI method"); + public static boolean isBrowserProxyRequest(HttpServletRequest request) { + if (request instanceof Request) { + Request jettyRequest = (Request) request; + return Boolean.TRUE.equals(request.getAttribute(IS_HTTPS_PROXY_REQUEST_ATTRIBUTE)) + || "http".equals(jettyRequest.getMetaData().getURI().getScheme()); } - } - private static Method getURIMethodFromClass(Class requestClass) throws NoSuchMethodException { - if (URI_METHOD_BY_CLASS_CACHE.containsKey(requestClass)) { - return URI_METHOD_BY_CLASS_CACHE.get(requestClass); - } - Method method; - try { - method = requestClass.getDeclaredMethod("getUri"); - } catch (NoSuchMethodException ignored) { - method = requestClass.getDeclaredMethod("getHttpURI"); - } - URI_METHOD_BY_CLASS_CACHE.put(requestClass, method); - return method; + return false; } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/NotFoundHandler.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java similarity index 88% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/NotFoundHandler.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java index 1e35409552..dc339ec4d1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/NotFoundHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/QueuedThreadPoolFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java similarity index 91% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/QueuedThreadPoolFactory.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java index 3ed53af686..763e7cae6e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/QueuedThreadPoolFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.http.ThreadPoolFactory; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/websockets/Message.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java similarity index 100% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/websockets/Message.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/websockets/WebSocketEndpoint.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java similarity index 100% rename from src/main/java/com/github/tomakehurst/wiremock/jetty9/websockets/WebSocketEndpoint.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty94/CertificateGeneratingSslContextFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java similarity index 95% rename from src/main/java/com/github/tomakehurst/wiremock/jetty94/CertificateGeneratingSslContextFactory.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java index 4b32b52759..cf33996794 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty94/CertificateGeneratingSslContextFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty94; +package com.github.tomakehurst.wiremock.jetty11; import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty11/HttpsProxyDetectingHandler.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/HttpsProxyDetectingHandler.java new file mode 100644 index 0000000000..d30d6becba --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/HttpsProxyDetectingHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.jetty11; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; + +public class HttpsProxyDetectingHandler extends AbstractHandler { + + public static final String IS_HTTPS_PROXY_REQUEST_ATTRIBUTE = "wiremock.isHttpsProxyRequest"; + + private final ServerConnector mitmProxyConnector; + + public HttpsProxyDetectingHandler(ServerConnector mitmProxyConnector) { + this.mitmProxyConnector = mitmProxyConnector; + } + + @Override + public void handle( + String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException { + final int httpsProxyPort = mitmProxyConnector.getLocalPort(); + if (request.getLocalPort() == httpsProxyPort) { + request.setAttribute(IS_HTTPS_PROXY_REQUEST_ATTRIBUTE, true); + } + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty94/Jetty94HttpServer.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11HttpServer.java similarity index 80% rename from src/main/java/com/github/tomakehurst/wiremock/jetty94/Jetty94HttpServer.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11HttpServer.java index 4143043510..681c734ef6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty94/Jetty94HttpServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11HttpServer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty94; +package com.github.tomakehurst.wiremock.jetty11; -import static com.github.tomakehurst.wiremock.jetty94.SslContexts.buildManInTheMiddleSslContextFactory; +import static com.github.tomakehurst.wiremock.jetty11.Jetty11Utils.createHttpConfig; +import static com.github.tomakehurst.wiremock.jetty11.SslContexts.buildManInTheMiddleSslContextFactory; import com.github.tomakehurst.wiremock.common.HttpsSettings; import com.github.tomakehurst.wiremock.common.JettySettings; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.http.AdminRequestHandler; import com.github.tomakehurst.wiremock.http.StubRequestHandler; -import com.github.tomakehurst.wiremock.jetty9.DefaultMultipartRequestConfigurer; -import com.github.tomakehurst.wiremock.jetty9.JettyHttpServer; -import com.github.tomakehurst.wiremock.servlet.MultipartRequestConfigurer; +import com.github.tomakehurst.wiremock.jetty.JettyHttpServer; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; @@ -34,31 +33,17 @@ import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.util.ssl.SslContextFactory; -public class Jetty94HttpServer extends JettyHttpServer { +public class Jetty11HttpServer extends JettyHttpServer { private ServerConnector mitmProxyConnector; - public Jetty94HttpServer( + public Jetty11HttpServer( Options options, AdminRequestHandler adminRequestHandler, StubRequestHandler stubRequestHandler) { super(options, adminRequestHandler, stubRequestHandler); } - @Override - protected MultipartRequestConfigurer buildMultipartRequestConfigurer() { - return new DefaultMultipartRequestConfigurer(); - } - - @Override - protected HttpConfiguration createHttpConfig(JettySettings jettySettings) { - HttpConfiguration httpConfig = super.createHttpConfig(jettySettings); - httpConfig.setSendXPoweredBy(false); - httpConfig.setSendServerVersion(false); - httpConfig.addCustomizer(new SecureRequestCustomizer()); - return httpConfig; - } - @Override protected ServerConnector createHttpConnector( String bindAddress, int port, JettySettings jettySettings, NetworkTrafficListener listener) { @@ -67,13 +52,18 @@ protected ServerConnector createHttpConnector( HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig); - return createServerConnector( - bindAddress, jettySettings, port, listener, new HttpConnectionFactory(httpConfig), h2c); + return Jetty11Utils.createServerConnector( + jettyServer, + bindAddress, + jettySettings, + port, + listener, + new HttpConnectionFactory(httpConfig), + h2c); } @Override protected ServerConnector createHttpsConnector( - Server server, String bindAddress, HttpsSettings httpsSettings, JettySettings jettySettings, @@ -101,8 +91,13 @@ protected ServerConnector createHttpsConnector( connectionFactories = new ConnectionFactory[] {ssl, http}; } - return createServerConnector( - bindAddress, jettySettings, httpsSettings.port(), listener, connectionFactories); + return Jetty11Utils.createServerConnector( + jettyServer, + bindAddress, + jettySettings, + httpsSettings.port(), + listener, + connectionFactories); } @Override @@ -114,6 +109,7 @@ protected HandlerCollection createHandler( super.createHandler(options, adminRequestHandler, stubRequestHandler); if (options.browserProxySettings().enabled()) { + handler.prependHandler(new HttpsProxyDetectingHandler(mitmProxyConnector)); handler.prependHandler(new ManInTheMiddleSslConnectHandler(mitmProxyConnector)); } @@ -150,12 +146,15 @@ actual request (this is how curl 7.64.1 behaves!). Neither */ HttpVersion.HTTP_1_1.asString()); - HttpConfiguration httpConfig = createHttpConfig(options.jettySettings()); + JettySettings jettySettings = options.jettySettings(); + HttpConfiguration httpConfig = createHttpConfig(jettySettings); HttpConnectionFactory http = new HttpConnectionFactory(httpConfig); mitmProxyConnector = new NetworkTrafficServerConnector(jettyServer, null, null, null, 2, 2, ssl, http); mitmProxyConnector.setPort(0); + mitmProxyConnector.setShutdownIdleTimeout( + jettySettings.getShutdownIdleTimeout().orElse(100L)); jettyServer.addConnector(mitmProxyConnector); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11Utils.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11Utils.java new file mode 100644 index 0000000000..e18ac5fef1 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/Jetty11Utils.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.jetty11; + +import com.github.tomakehurst.wiremock.common.JettySettings; +import org.eclipse.jetty.io.NetworkTrafficListener; +import org.eclipse.jetty.server.*; + +public class Jetty11Utils { + + private Jetty11Utils() {} + + private static final int DEFAULT_ACCEPTORS = 3; + private static final int DEFAULT_HEADER_SIZE = 32768; + + public static ServerConnector createServerConnector( + Server jettyServer, + String bindAddress, + JettySettings jettySettings, + int port, + NetworkTrafficListener listener, + ConnectionFactory... connectionFactories) { + + int acceptors = jettySettings.getAcceptors().orElse(DEFAULT_ACCEPTORS); + + NetworkTrafficServerConnector connector = + new NetworkTrafficServerConnector( + jettyServer, null, null, null, acceptors, 2, connectionFactories); + + connector.setPort(port); + connector.setNetworkTrafficListener(listener); + setJettySettings(jettySettings, connector); + connector.setHost(bindAddress); + return connector; + } + + public static void setJettySettings(JettySettings jettySettings, ServerConnector connector) { + jettySettings.getAcceptQueueSize().ifPresent(connector::setAcceptQueueSize); + jettySettings.getIdleTimeout().ifPresent(connector::setIdleTimeout); + connector.setShutdownIdleTimeout(jettySettings.getShutdownIdleTimeout().orElse(200L)); + } + + public static HttpConfiguration createHttpConfig(JettySettings jettySettings) { + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.setRequestHeaderSize( + jettySettings.getRequestHeaderSize().orElse(DEFAULT_HEADER_SIZE)); + httpConfig.setResponseHeaderSize( + jettySettings.getResponseHeaderSize().orElse(DEFAULT_HEADER_SIZE)); + httpConfig.setSendDateHeader(false); + httpConfig.setSendXPoweredBy(false); + httpConfig.setSendServerVersion(false); + httpConfig.addCustomizer(new SecureRequestCustomizer(false)); + return httpConfig; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty94/ManInTheMiddleSslConnectHandler.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/ManInTheMiddleSslConnectHandler.java similarity index 84% rename from src/main/java/com/github/tomakehurst/wiremock/jetty94/ManInTheMiddleSslConnectHandler.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty11/ManInTheMiddleSslConnectHandler.java index ea0e3372bc..e9f6470516 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty94/ManInTheMiddleSslConnectHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/ManInTheMiddleSslConnectHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty94; +package com.github.tomakehurst.wiremock.jetty11; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; +import jakarta.servlet.http.HttpServletRequest; import java.io.Closeable; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; -import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.proxy.ConnectHandler; import org.eclipse.jetty.server.*; import org.eclipse.jetty.util.Promise; @@ -45,7 +45,7 @@ protected void connectToServer( channel.socket().setTcpNoDelay(true); channel.configureBlocking(false); - String host = firstNonNull(mitmProxyConnector.getHost(), "localhost"); + String host = getFirstNonNull(mitmProxyConnector.getHost(), "localhost"); int port = mitmProxyConnector.getLocalPort(); InetSocketAddress address = newConnectAddress(host, port); @@ -61,7 +61,7 @@ private void close(Closeable closeable) { try { if (closeable != null) closeable.close(); } catch (Throwable x) { - LOG.ignore(x); + /* Ignore */ } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty94/SslContexts.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java similarity index 98% rename from src/main/java/com/github/tomakehurst/wiremock/jetty94/SslContexts.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java index 7aecb556fb..ef2bedd791 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty94/SslContexts.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty94; +package com.github.tomakehurst.wiremock.jetty11; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty94/WritableFileOrClasspathKeyStoreSource.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java similarity index 96% rename from src/main/java/com/github/tomakehurst/wiremock/jetty94/WritableFileOrClasspathKeyStoreSource.java rename to src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java index 0ee00d85df..b596da2fe8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty94/WritableFileOrClasspathKeyStoreSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty94; +package com.github.tomakehurst.wiremock.jetty11; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static java.nio.file.attribute.PosixFilePermission.*; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerFactory.java deleted file mode 100644 index 7b44ea4841..0000000000 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerFactory.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2014-2021 Thomas Akehurst - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.github.tomakehurst.wiremock.jetty9; - -import com.github.tomakehurst.wiremock.common.Exceptions; -import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.http.AdminRequestHandler; -import com.github.tomakehurst.wiremock.http.HttpServer; -import com.github.tomakehurst.wiremock.http.HttpServerFactory; -import com.github.tomakehurst.wiremock.http.StubRequestHandler; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -public class JettyHttpServerFactory implements HttpServerFactory { - - private static final Constructor SERVER_CONSTRUCTOR = - getServerConstructor(); - - @SuppressWarnings("unchecked") - private static Constructor getServerConstructor() { - try { - Class serverClass = - (Class) - Class.forName("com.github.tomakehurst.wiremock.jetty94.Jetty94HttpServer"); - return safelyGetConstructor( - serverClass, Options.class, AdminRequestHandler.class, StubRequestHandler.class); - } catch (ClassNotFoundException e) { - try { - Class serverClass = - (Class) - Class.forName("com.github.tomakehurst.wiremock.jetty92.Jetty92HttpServer"); - return safelyGetConstructor( - serverClass, Options.class, AdminRequestHandler.class, StubRequestHandler.class); - } catch (ClassNotFoundException cnfe) { - return safelyGetConstructor( - JettyHttpServer.class, - Options.class, - AdminRequestHandler.class, - StubRequestHandler.class); - } - } - } - - @SuppressWarnings("unchecked") - private static Constructor safelyGetConstructor( - Class clazz, Class... parameterTypes) { - try { - return clazz.getConstructor(parameterTypes); - } catch (NoSuchMethodException e) { - return Exceptions.throwUnchecked(e, Constructor.class); - } - } - - @Override - public HttpServer buildHttpServer( - Options options, - AdminRequestHandler adminRequestHandler, - StubRequestHandler stubRequestHandler) { - try { - return SERVER_CONSTRUCTOR.newInstance(options, adminRequestHandler, stubRequestHandler); - } catch (InstantiationException | IllegalAccessException e) { - return Exceptions.throwUnchecked(e, HttpServer.class); - } catch (InvocationTargetException e) { - return Exceptions.throwUnchecked(e.getCause(), null); - } - } -} diff --git a/src/main/java/com/github/tomakehurst/wiremock/junit5/WireMockExtension.java b/src/main/java/com/github/tomakehurst/wiremock/junit5/WireMockExtension.java index 4c681c7247..a50c06a341 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/junit5/WireMockExtension.java +++ b/src/main/java/com/github/tomakehurst/wiremock/junit5/WireMockExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ /** * JUnit Jupiter extension that manages a WireMock server instance's lifecycle and configuration. * - * See http://wiremock.org/docs/junit-jupiter/ for full documentation. + *

See http://wiremock.org/docs/junit-jupiter/ for full documentation. */ public class WireMockExtension extends DslWrapper implements ParameterResolver, @@ -37,36 +37,38 @@ public class WireMockExtension extends DslWrapper AfterEachCallback, AfterAllCallback { - private static final Options DEFAULT_OPTIONS = WireMockConfiguration.options().dynamicPort(); - private final boolean configureStaticDsl; private final boolean failOnUnmatchedRequests; + private final boolean isDeclarative; + private Options options; private WireMockServer wireMockServer; private WireMockRuntimeInfo runtimeInfo; private boolean isNonStatic = false; - private Boolean proxyMode; - public WireMockExtension() { + WireMockExtension() { configureStaticDsl = true; failOnUnmatchedRequests = false; + isDeclarative = true; } /** * Constructor intended for subclasses. * - * The parameter is a builder so that we can avoid a constructor explosion or + *

The parameter is a builder so that we can avoid a constructor explosion or * backwards-incompatible changes when new options are added. * - * @param builder a {@link com.github.tomakehurst.wiremock.junit5.WireMockExtension.Builder} instance holding the initialisation parameters for the extension. + * @param builder a {@link com.github.tomakehurst.wiremock.junit5.WireMockExtension.Builder} + * instance holding the initialisation parameters for the extension. */ protected WireMockExtension(Builder builder) { this.options = builder.options; this.configureStaticDsl = builder.configureStaticDsl; this.failOnUnmatchedRequests = builder.failOnUnmatchedRequests; this.proxyMode = builder.proxyMode; + this.isDeclarative = false; } private WireMockExtension( @@ -78,11 +80,15 @@ private WireMockExtension( this.configureStaticDsl = configureStaticDsl; this.failOnUnmatchedRequests = failOnUnmatchedRequests; this.proxyMode = proxyMode; + this.isDeclarative = false; } /** - * Alias for {@link #newInstance()} for use with custom subclasses, with a more relevant name for that use. - * @return a new {@link com.github.tomakehurst.wiremock.junit5.WireMockExtension.Builder} instance. + * Alias for {@link #newInstance()} for use with custom subclasses, with a more relevant name for + * that use. + * + * @return a new {@link com.github.tomakehurst.wiremock.junit5.WireMockExtension.Builder} + * instance. */ public static Builder extensionOptions() { return newInstance(); @@ -90,7 +96,9 @@ public static Builder extensionOptions() { /** * Create a new builder for the extension. - * @return a new {@link com.github.tomakehurst.wiremock.junit5.WireMockExtension.Builder} instance. + * + * @return a new {@link com.github.tomakehurst.wiremock.junit5.WireMockExtension.Builder} + * instance. */ public static Builder newInstance() { return new Builder(); @@ -98,25 +106,35 @@ public static Builder newInstance() { /** * To be overridden in subclasses in order to run code immediately after per-class WireMock setup. - * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock instance/ + * + * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock + * instance/ */ protected void onBeforeAll(WireMockRuntimeInfo wireMockRuntimeInfo) {} /** * To be overridden in subclasses in order to run code immediately after per-test WireMock setup. - * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock instance/ + * + * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock + * instance/ */ protected void onBeforeEach(WireMockRuntimeInfo wireMockRuntimeInfo) {} /** - * To be overridden in subclasses in order to run code immediately after per-test cleanup of WireMock and its associated resources. - * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock instance/ + * To be overridden in subclasses in order to run code immediately after per-test cleanup of + * WireMock and its associated resources. + * + * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock + * instance/ */ protected void onAfterEach(WireMockRuntimeInfo wireMockRuntimeInfo) {} /** - * To be overridden in subclasses in order to run code immediately after per-class cleanup of WireMock. - * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock instance/ + * To be overridden in subclasses in order to run code immediately after per-class cleanup of + * WireMock. + * + * @param wireMockRuntimeInfo port numbers, base URLs and HTTPS info for the running WireMock + * instance/ */ protected void onAfterAll(WireMockRuntimeInfo wireMockRuntimeInfo) {} @@ -169,13 +187,16 @@ private void setAdditionalOptions(ExtensionContext extensionContext) { } private Options resolveOptions(ExtensionContext extensionContext) { + final Options defaultOptions = WireMockConfiguration.options().dynamicPort(); return extensionContext .getElement() .flatMap( annotatedElement -> - AnnotationSupport.findAnnotation(annotatedElement, WireMockTest.class)) - .map(this::buildOptionsFromWireMockTestAnnotation) - .orElse(Optional.ofNullable(this.options).orElse(DEFAULT_OPTIONS)); + this.isDeclarative + ? AnnotationSupport.findAnnotation(annotatedElement, WireMockTest.class) + : Optional.empty()) + .map(this::buildOptionsFromWireMockTestAnnotation) + .orElse(Optional.ofNullable(this.options).orElse(defaultOptions)); } private Options buildOptionsFromWireMockTestAnnotation(WireMockTest annotation) { @@ -198,7 +219,8 @@ private void stopServerIfRunning() { } private boolean parameterIsWireMockRuntimeInfo(ParameterContext parameterContext) { - return parameterContext.getParameter().getType().equals(WireMockRuntimeInfo.class); + return parameterContext.getParameter().getType().equals(WireMockRuntimeInfo.class) + && this.isDeclarative; } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractLogicalMatcher.java b/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractLogicalMatcher.java new file mode 100644 index 0000000000..4e12db99fe --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractLogicalMatcher.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import static java.util.Arrays.asList; + +import java.util.List; +import java.util.stream.Collectors; + +public abstract class AbstractLogicalMatcher extends StringValuePattern { + + protected final List operands; + + public AbstractLogicalMatcher(StringValuePattern... operands) { + this(asList(operands)); + } + + public AbstractLogicalMatcher(List operands) { + super(checkAtLeast2OperandsAndReturnFirstExpected(operands)); + this.operands = operands; + } + + private static String checkAtLeast2OperandsAndReturnFirstExpected( + List operands) { + if (operands.size() < 2) { + throw new IllegalArgumentException("Must be constructed with at least two matchers"); + } + + return operands.stream() + .findFirst() + .map(ContentPattern::getExpected) + .orElseThrow(() -> new IllegalArgumentException("Matchers must have expected values")); + } + + @Override + public String getExpected() { + return operands.stream() + .map(contentPattern -> contentPattern.getName() + " " + contentPattern.getExpected()) + .collect(Collectors.joining(" " + getOperationName() + " ")); + } + + protected abstract String getOperationName(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPattern.java index b52e3d181f..c42d338a6e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.io.BaseEncoding; import java.util.Arrays; +import java.util.Base64; +import java.util.Objects; public class BinaryEqualToPattern extends ContentPattern { @@ -29,7 +30,7 @@ public BinaryEqualToPattern(byte[] expected) { @JsonCreator public BinaryEqualToPattern(@JsonProperty("binaryEqualTo") String expected) { - this(BaseEncoding.base64().decode(expected)); + this(Base64.getDecoder().decode(expected)); } @Override @@ -46,7 +47,7 @@ public String getName() { @Override @JsonIgnore public String getExpected() { - return BaseEncoding.base64().encode(expectedValue); + return Base64.getEncoder().encodeToString(expectedValue); } public String getBinaryEqualTo() { @@ -57,4 +58,19 @@ public String getBinaryEqualTo() { public String toString() { return getName() + " " + getExpected(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BinaryEqualToPattern that = (BinaryEqualToPattern) o; + + return Objects.equals(getExpected(), that.getExpected()); + } + + @Override + public int hashCode() { + return getExpected() != null ? getExpected().hashCode() : 0; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPattern.java index dadd7fbbcf..4656d2a7e6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,11 @@ */ package com.github.tomakehurst.wiremock.matching; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.checkNotNull; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.google.common.base.Preconditions; +import java.util.Objects; @JsonDeserialize(using = ContentPatternDeserialiser.class) public abstract class ContentPattern implements NamedValueMatcher { @@ -26,8 +28,7 @@ public abstract class ContentPattern implements NamedValueMatcher { public ContentPattern(T expectedValue) { if (!isNullValuePermitted()) { - Preconditions.checkNotNull( - expectedValue, "'" + getName() + "' expected value cannot be null"); + checkNotNull(expectedValue, "'" + getName() + "' expected value cannot be null"); } this.expectedValue = expectedValue; } @@ -40,4 +41,19 @@ public T getValue() { protected boolean isNullValuePermitted() { return false; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ContentPattern that = (ContentPattern) o; + + return Objects.equals(expectedValue, that.expectedValue); + } + + @Override + public int hashCode() { + return expectedValue != null ? expectedValue.hashCode() : 0; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPatternDeserialiser.java b/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPatternDeserialiser.java index ab66d305f0..e94adb2228 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPatternDeserialiser.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/ContentPatternDeserialiser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableList; import java.io.IOException; -import java.util.Map; +import java.util.*; +import java.util.stream.StreamSupport; public class ContentPatternDeserialiser extends JsonDeserializer> { @@ -43,20 +42,15 @@ public ContentPattern deserialize(JsonParser parser, DeserializationContext c return new StringValuePatternJsonDeserializer().buildStringValuePattern(rootNode); } - private BinaryEqualToPattern deserializeBinaryEqualTo(JsonNode rootNode) - throws JsonMappingException { + private BinaryEqualToPattern deserializeBinaryEqualTo(JsonNode rootNode) { String operand = rootNode.findValue("binaryEqualTo").textValue(); return new BinaryEqualToPattern(operand); } private static boolean isAbsent(JsonNode rootNode) { - for (Map.Entry node : ImmutableList.copyOf(rootNode.fields())) { - if (node.getKey().equals("absent")) { - return true; - } - } - - return false; + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(rootNode.fields(), Spliterator.ORDERED), false) + .anyMatch(node -> node.getKey().equals("absent")); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/EagerMatchResult.java b/src/main/java/com/github/tomakehurst/wiremock/matching/EagerMatchResult.java index 47827f25cc..f2818f4b95 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/EagerMatchResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/EagerMatchResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,19 @@ */ package com.github.tomakehurst.wiremock.matching; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; +import java.util.List; + public class EagerMatchResult extends MatchResult { private final double distance; EagerMatchResult(double distance) { + this(distance, List.of()); + } + + EagerMatchResult(double distance, List subEvents) { + super(subEvents); this.distance = distance; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToJsonPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToJsonPattern.java index 36d0dafb66..60f86aa468 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToJsonPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToJsonPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,13 @@ */ package com.github.tomakehurst.wiremock.matching; +import static com.github.tomakehurst.wiremock.stubbing.SubEvent.JSON_ERROR; + import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.JsonException; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; import net.javacrumbs.jsonunit.core.Configuration; import net.javacrumbs.jsonunit.core.Option; import net.javacrumbs.jsonunit.core.internal.Diff; @@ -77,8 +81,10 @@ public MatchResult match(String value) { "", "", diffConfig); + } catch (JsonException je) { + return MatchResult.noMatch(new SubEvent(JSON_ERROR, je.getErrors())); } catch (Exception e) { - return MatchResult.noMatch(); + return MatchResult.noMatch(SubEvent.warning(e.getMessage())); } return new MatchResult() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToPattern.java index df2213f829..11fab76f69 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.matching; -import static org.apache.commons.lang3.StringUtils.getLevenshteinDistance; +import static com.github.tomakehurst.wiremock.common.Strings.normalisedLevenshteinDistance; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; @@ -63,14 +63,4 @@ public double getDistance() { private boolean shouldMatchCaseInsensitive() { return caseInsensitive != null && caseInsensitive; } - - private double normalisedLevenshteinDistance(String one, String two) { - if (one == null || two == null) { - return 1.0; - } - - double maxDistance = Math.max(one.length(), two.length()); - double actualDistance = getLevenshteinDistance(one, two); - return (actualDistance / maxDistance); - } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPattern.java index 944a136d53..e55630393c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,15 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; -import static com.google.common.base.Strings.isNullOrEmpty; +import static com.github.tomakehurst.wiremock.common.Strings.isNullOrEmpty; import static org.xmlunit.diff.ComparisonType.*; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tomakehurst.wiremock.common.xml.Xml; -import com.google.common.base.Joiner; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.*; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xmlunit.XMLUnitException; @@ -37,8 +35,8 @@ public class EqualToXmlPattern extends StringValuePattern { - private static Set COUNTED_COMPARISONS = - ImmutableSet.of( + private static final Set COUNTED_COMPARISONS = + Set.of( ELEMENT_TAG_NAME, SCHEMA_LOCATION, NO_NAMESPACE_SCHEMA_LOCATION, @@ -138,6 +136,8 @@ public boolean isExactMatch() { return !diff.hasDifferences(); } catch (XMLUnitException e) { + appendSubEvent(SubEvent.warning(e.getMessage())); + notifier() .info( "Failed to process XML. " @@ -168,16 +168,12 @@ public double getDistance() { .ignoreComments() .withDifferenceEvaluator(diffEvaluator) .withComparisonListeners( - new ComparisonListener() { - @Override - public void comparisonPerformed( - Comparison comparison, ComparisonResult outcome) { - if (COUNTED_COMPARISONS.contains(comparison.getType()) - && comparison.getControlDetails().getValue() != null) { - totalComparisons.incrementAndGet(); - if (outcome == ComparisonResult.DIFFERENT) { - differences.incrementAndGet(); - } + (comparison, outcome) -> { + if (COUNTED_COMPARISONS.contains(comparison.getType()) + && comparison.getControlDetails().getValue() != null) { + totalComparisons.incrementAndGet(); + if (outcome == ComparisonResult.DIFFERENT) { + differences.incrementAndGet(); } } }) @@ -195,7 +191,11 @@ public void comparisonPerformed( return 1.0; } - notifier().info(Joiner.on("\n").join(diff.getDifferences())); + notifier() + .info( + StreamSupport.stream(diff.getDifferences().spliterator(), false) + .map(Object::toString) + .collect(Collectors.joining("\n"))); return differences.doubleValue() / totalComparisons.doubleValue(); } @@ -209,7 +209,9 @@ private static class IgnoreUncountedDifferenceEvaluator implements DifferenceEva public IgnoreUncountedDifferenceEvaluator(Set exemptedComparisons) { finalCountedComparisons = exemptedComparisons != null - ? Sets.difference(COUNTED_COMPARISONS, exemptedComparisons) + ? COUNTED_COMPARISONS.stream() + .filter(e -> !exemptedComparisons.contains(e)) + .collect(Collectors.toSet()) : COUNTED_COMPARISONS; } @@ -230,7 +232,7 @@ public EqualToXmlPattern exemptingComparisons(ComparisonType... comparisons) { enablePlaceholders, placeholderOpeningDelimiterRegex, placeholderClosingDelimiterRegex, - ImmutableSet.copyOf(comparisons)); + new HashSet<>(Arrays.asList(comparisons))); } private static final class OrderInvariantNodeMatcher extends DefaultNodeMatcher { @@ -242,15 +244,11 @@ public Iterable> match( } private static Iterable sort(Iterable nodes) { - return FluentIterable.from(nodes).toSortedList(COMPARATOR); + return StreamSupport.stream(nodes.spliterator(), false) + .sorted(COMPARATOR) + .collect(Collectors.toList()); } - private static final Comparator COMPARATOR = - new Comparator() { - @Override - public int compare(Node node1, Node node2) { - return node1.getLocalName().compareTo(node2.getLocalName()); - } - }; + private static final Comparator COMPARATOR = Comparator.comparing(Node::getLocalName); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/ExactMatchMultiValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/ExactMatchMultiValuePattern.java new file mode 100644 index 0000000000..9b89537bc0 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/ExactMatchMultiValuePattern.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.github.tomakehurst.wiremock.http.MultiValue; +import java.util.List; + +@JsonDeserialize(as = ExactMatchMultiValuePattern.class) +public class ExactMatchMultiValuePattern extends MultipleMatchMultiValuePattern { + + public static final String JSON_KEY = "hasExactly"; + public static final String HAS_EXACTLY_OPERATOR = " exactly "; + + @JsonProperty(JSON_KEY) + private List stringValuePatterns; + + @JsonCreator + public ExactMatchMultiValuePattern( + @JsonProperty(JSON_KEY) final List valuePatterns) { + this.stringValuePatterns = valuePatterns; + } + + @Override + public MatchResult match(MultiValue value) { + + if (!value.isPresent()) { + return MatchResult.of(false); + } + return MatchResult.aggregate( + MatchResult.of(stringValuePatterns.size() == value.values().size()), super.match(value)); + } + + @Override + public List getValues() { + return stringValuePatterns; + } + + @Override + public String getOperator() { + return HAS_EXACTLY_OPERATOR; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/IncludesMatchMultiValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/IncludesMatchMultiValuePattern.java new file mode 100644 index 0000000000..a41c60cf17 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/IncludesMatchMultiValuePattern.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; + +@JsonDeserialize(as = IncludesMatchMultiValuePattern.class) +public class IncludesMatchMultiValuePattern extends MultipleMatchMultiValuePattern { + + public static final String JSON_KEY = "includes"; + public static final String INCLUDING_OPERATOR = " including "; + + @JsonProperty(JSON_KEY) + private final List stringValuePatterns; + + @JsonCreator + public IncludesMatchMultiValuePattern( + @JsonProperty(JSON_KEY) final List stringValuePatterns) { + this.stringValuePatterns = stringValuePatterns; + } + + @Override + public List getValues() { + return stringValuePatterns; + } + + @Override + public String getOperator() { + return INCLUDING_OPERATOR; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalAnd.java b/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalAnd.java index 5fd7da055b..518d091739 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalAnd.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalAnd.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,37 +15,23 @@ */ package com.github.tomakehurst.wiremock.matching; -import static java.util.Arrays.asList; - import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.stream.Collectors; -public class LogicalAnd extends StringValuePattern { - - private final List operands; +public class LogicalAnd extends AbstractLogicalMatcher { public LogicalAnd(StringValuePattern... operands) { - this(asList(operands)); + super(operands); } public LogicalAnd(@JsonProperty("and") List operands) { - super( - operands.stream() - .findFirst() - .map(ContentPattern::getValue) - .orElseThrow( - () -> - new IllegalArgumentException( - "Logical AND must be constructed with at least two matchers"))); - this.operands = operands; + super(operands); } @Override - public String getExpected() { - return operands.stream() - .map(contentPattern -> contentPattern.getName() + " " + contentPattern.getExpected()) - .collect(Collectors.joining(" AND ")); + protected String getOperationName() { + return "AND"; } public List getAnd() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalOr.java b/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalOr.java index 41dac1e534..5b2896259a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalOr.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/LogicalOr.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,37 +15,23 @@ */ package com.github.tomakehurst.wiremock.matching; -import static java.util.Arrays.asList; - import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.stream.Collectors; -public class LogicalOr extends StringValuePattern { - - private final List operands; +public class LogicalOr extends AbstractLogicalMatcher { public LogicalOr(StringValuePattern... operands) { - this(asList(operands)); + super(operands); } public LogicalOr(@JsonProperty("or") List operands) { - super( - operands.stream() - .findFirst() - .map(ContentPattern::getValue) - .orElseThrow( - () -> - new IllegalArgumentException( - "Logical OR must be constructed with at least two matchers"))); - this.operands = operands; + super(operands); } @Override - public String getExpected() { - return operands.stream() - .map(contentPattern -> contentPattern.getName() + " " + contentPattern.getExpected()) - .collect(Collectors.joining(" OR ")); + protected String getOperationName() { + return "OR"; } public List getOr() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchResult.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchResult.java index 6fed6bdc35..5223930273 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,34 +15,65 @@ */ package com.github.tomakehurst.wiremock.matching; -import static com.google.common.collect.Iterables.all; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toUnmodifiableList; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Lists; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.stream.Collectors; public abstract class MatchResult implements Comparable { + private final Queue subEvents; + + public MatchResult() { + this.subEvents = new LinkedBlockingQueue<>(); + } + + public MatchResult(List subEvents) { + this.subEvents = new LinkedBlockingQueue<>(subEvents); + } + + protected void appendSubEvent(SubEvent subEvent) { + subEvents.add(subEvent); + } + + public List getSubEvents() { + return subEvents.stream().collect(toUnmodifiableList()); + } + @JsonCreator public static MatchResult partialMatch(@JsonProperty("distance") double distance) { return new EagerMatchResult(distance); } - public static MatchResult exactMatch() { - return new EagerMatchResult(0); + public static MatchResult exactMatch(SubEvent... subEvents) { + return exactMatch(List.of(subEvents)); } - public static MatchResult noMatch() { - return new EagerMatchResult(1); + public static MatchResult exactMatch(List subEvents) { + return new EagerMatchResult(0, subEvents); } - public static MatchResult of(boolean isMatch) { - return isMatch ? exactMatch() : noMatch(); + public static MatchResult noMatch(SubEvent... subEvents) { + return noMatch(List.of(subEvents)); + } + + public static MatchResult noMatch(List subEvents) { + return new EagerMatchResult(1, subEvents); + } + + public static MatchResult of(boolean isMatch, SubEvent... subEvents) { + return of(isMatch, List.of(subEvents)); + } + + public static MatchResult of(boolean isMatch, List subEvents) { + return isMatch ? exactMatch(subEvents) : noMatch(subEvents); } public static MatchResult aggregate(MatchResult... matches) { @@ -51,14 +82,7 @@ public static MatchResult aggregate(MatchResult... matches) { public static MatchResult aggregate(final List matchResults) { return aggregateWeighted( - Lists.transform( - matchResults, - new Function() { - @Override - public WeightedMatchResult apply(MatchResult matchResult) { - return new WeightedMatchResult(matchResult); - } - })); + matchResults.stream().map(WeightedMatchResult::new).collect(Collectors.toList())); } public static MatchResult aggregateWeighted(WeightedMatchResult... matchResults) { @@ -66,10 +90,16 @@ public static MatchResult aggregateWeighted(WeightedMatchResult... matchResults) } public static MatchResult aggregateWeighted(final List matchResults) { - return new MatchResult() { + + final List allSubEvents = + matchResults.stream() + .flatMap(weightedResult -> weightedResult.getMatchResult().getSubEvents().stream()) + .collect(Collectors.toList()); + + return new MatchResult(allSubEvents) { @Override public boolean isExactMatch() { - return all(matchResults, ARE_EXACT_MATCH); + return matchResults.stream().allMatch(ARE_EXACT_MATCH); } @Override @@ -96,11 +126,6 @@ public int compareTo(MatchResult other) { return Double.compare(other.getDistance(), getDistance()); } - public static final Predicate ARE_EXACT_MATCH = - new Predicate() { - @Override - public boolean apply(WeightedMatchResult matchResult) { - return matchResult.isExactMatch(); - } - }; + public static final java.util.function.Predicate ARE_EXACT_MATCH = + WeightedMatchResult::isExactMatch; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPattern.java index 08e742fa1a..b6856e26e9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.common.ListOrSingle; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.PathNotFoundException; import java.util.*; @@ -45,21 +46,21 @@ public String getMatchesJsonPath() { protected MatchResult isSimpleMatch(String value) { // For performance reason, don't try to parse XML value if (value != null && value.trim().startsWith("<")) { - notifier() - .info( - String.format( - "Warning: JSON path expression '%s' failed to match document '%s' because it's not JSON document", - expectedValue, value)); - return MatchResult.noMatch(); + final String message = + String.format( + "Warning: JSON path expression '%s' failed to match document '%s' because it's not JSON but probably XML", + expectedValue, value); + notifier().info(message); + return MatchResult.noMatch(SubEvent.warning(message)); } try { Object obj = JsonPath.read(value, expectedValue); boolean result; if (obj instanceof Collection) { - result = !((Collection) obj).isEmpty(); + result = !((Collection) obj).isEmpty(); } else if (obj instanceof Map) { - result = !((Map) obj).isEmpty(); + result = !((Map) obj).isEmpty(); } else { result = obj != null; } @@ -79,9 +80,8 @@ protected MatchResult isSimpleMatch(String value) { String.format( "Warning: JSON path expression '%s' failed to match document '%s' because %s", expectedValue, value, error); - notifier().info(message); - return MatchResult.noMatch(); + return MatchResult.noMatch(SubEvent.warning(message)); } } @@ -95,13 +95,19 @@ protected MatchResult isAdvancedMatch(String value) { expressionResult = ListOrSingle.of((String) null); } - return expressionResult.stream() - .map(valuePattern::match) + final List matchResults = + expressionResult.stream().map(valuePattern::match).collect(toList()); + final List subEvents = + matchResults.stream() + .map(MatchResult::getSubEvents) + .flatMap(Collection::stream) + .collect(toList()); + + return matchResults.stream() .min(Comparator.comparingDouble(MatchResult::getDistance)) - .orElse(MatchResult.noMatch()); + .orElse(MatchResult.noMatch(subEvents)); } catch (SubExpressionException e) { - notifier().info(e.getMessage()); - return MatchResult.noMatch(); + return MatchResult.noMatch(SubEvent.warning(e.getMessage())); } } @@ -111,16 +117,16 @@ public ListOrSingle getExpressionResult(final String value) { if (value != null && value.trim().startsWith("<")) { final String message = String.format( - "Warning: JSON path expression '%s' failed to match document '%s' because it's not JSON document", + "Warning: JSON path expression '%s' failed to match document '%s' because it's not JSON but probably XML", expectedValue, value); - notifier().info(message); + throw new SubExpressionException(message); } Object obj = null; try { obj = JsonPath.read(value, expectedValue); - } catch (PathNotFoundException pnfe) { + } catch (PathNotFoundException ignored) { } catch (Exception e) { String error; if (e.getMessage().equalsIgnoreCase("invalid container object")) { @@ -138,7 +144,9 @@ public ListOrSingle getExpressionResult(final String value) { } ListOrSingle expressionResult; - if (obj instanceof Map || EqualToJsonPattern.class.isAssignableFrom(valuePattern.getClass())) { + if (obj instanceof Map + || (obj instanceof List + && EqualToJsonPattern.class.isAssignableFrom(valuePattern.getClass()))) { expressionResult = ListOrSingle.of(Json.write(obj)); } else if (obj instanceof List) { final List stringValues = diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPattern.java new file mode 100644 index 0000000000..0ebe0cf79a --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPattern.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.JsonException; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.ValidationMessage; +import java.util.Set; + +public class MatchesJsonSchemaPattern extends StringValuePattern { + + private final JsonSchema schema; + private final WireMock.JsonSchemaVersion schemaVersion; + private final int schemaPropertyCount; + + public MatchesJsonSchemaPattern(String schemaJson) { + this(schemaJson, WireMock.JsonSchemaVersion.V202012); + } + + public MatchesJsonSchemaPattern( + @JsonProperty("matchesJsonSchema") String schemaJson, + @JsonProperty("schemaVersion") WireMock.JsonSchemaVersion schemaVersion) { + super(schemaJson); + + SchemaValidatorsConfig config = new SchemaValidatorsConfig(); + config.setTypeLoose(false); + config.setHandleNullableField(true); + + final JsonSchemaFactory schemaFactory = + JsonSchemaFactory.getInstance(schemaVersion.toVersionFlag()); + schema = schemaFactory.getSchema(schemaJson, config); + this.schemaVersion = schemaVersion; + + schemaPropertyCount = Json.schemaPropertyCount(Json.read(schemaJson, JsonNode.class)); + } + + public String getMatchesJsonSchema() { + return expectedValue; + } + + public WireMock.JsonSchemaVersion getSchemaVersion() { + return schemaVersion; + } + + @Override + public String getExpected() { + return Json.prettyPrint(getValue()); + } + + @Override + public MatchResult match(String json) { + if (json == null) { + return MatchResult.noMatch(); + } + + JsonNode jsonNode; + try { + jsonNode = Json.read(json, JsonNode.class); + } catch (JsonException je) { + jsonNode = new TextNode(json); + } + + final Set validationMessages = schema.validate(jsonNode); + if (validationMessages.isEmpty()) { + return MatchResult.exactMatch(); + } + + return new MatchResult() { + @Override + public boolean isExactMatch() { + return false; + } + + @Override + public double getDistance() { + if (schemaPropertyCount == 0) { + return 1; + } + + return (double) validationMessages.size() / schemaPropertyCount; + } + }; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPattern.java index 30a79aebff..1220a404aa 100755 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,15 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.Sets.newTreeSet; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.github.tomakehurst.wiremock.common.ListOrSingle; import com.github.tomakehurst.wiremock.common.xml.*; -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.Map; -import java.util.SortedSet; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; +import java.util.*; import java.util.stream.Collectors; @JsonSerialize(using = XPathPatternJsonSerializer.class) @@ -57,11 +54,9 @@ public MatchesXPathPattern( public MatchesXPathPattern withXPathNamespace(String name, String namespaceUri) { Map namespaceMap = - ImmutableMap.builder() - .putAll(firstNonNull(xpathNamespaces, Collections.emptyMap())) - .put(name, namespaceUri) - .build(); - return new MatchesXPathPattern(expectedValue, namespaceMap); + new HashMap<>(getFirstNonNull(xpathNamespaces, new HashMap<>())); + namespaceMap.put(name, namespaceUri); + return new MatchesXPathPattern(expectedValue, Collections.unmodifiableMap(namespaceMap)); } public String getMatchesXPath() { @@ -75,18 +70,20 @@ public Map getXPathNamespaces() { @Override protected MatchResult isSimpleMatch(String value) { - ListOrSingle nodeList = findXmlNodes(value); - return MatchResult.of(nodeList != null && nodeList.size() > 0); + final XmlNodeFindResult xmlNodeFindResult = findXmlNodes(value); + ListOrSingle nodeList = xmlNodeFindResult.nodes; + return MatchResult.of(nodeList != null && !nodeList.isEmpty(), xmlNodeFindResult.subEvents); } @Override protected MatchResult isAdvancedMatch(String value) { - ListOrSingle nodeList = findXmlNodes(value); - if (nodeList == null || nodeList.size() == 0) { - return MatchResult.noMatch(); + final XmlNodeFindResult xmlNodeFindResult = findXmlNodes(value); + ListOrSingle nodeList = xmlNodeFindResult.nodes; + if (nodeList == null || nodeList.isEmpty()) { + return MatchResult.noMatch(xmlNodeFindResult.subEvents); } - SortedSet results = newTreeSet(); + SortedSet results = new TreeSet<>(); for (XmlNode node : nodeList) { results.add(valuePattern.match(node.toString())); } @@ -96,34 +93,47 @@ protected MatchResult isAdvancedMatch(String value) { @Override public ListOrSingle getExpressionResult(String value) { - ListOrSingle nodeList = findXmlNodes(value); - if (nodeList == null || nodeList.size() == 0) { + ListOrSingle nodeList = findXmlNodes(value).nodes; + if (nodeList == null || nodeList.isEmpty()) { return ListOrSingle.of(); } return ListOrSingle.of(nodeList.stream().map(XmlNode::toString).collect(Collectors.toList())); } - private ListOrSingle findXmlNodes(String value) { + private XmlNodeFindResult findXmlNodes(String value) { // For performance reason, don't try to parse non XML value if (value == null || !value.trim().startsWith("<")) { - notifier().info(String.format("Warning: failed to parse the XML document\nXML: %s", value)); - return null; + final String message = + String.format("Warning: failed to parse the XML document\nXML: %s", value); + notifier().info(message); + return new XmlNodeFindResult(null, SubEvent.warning(message)); } try { XmlDocument xmlDocument = Xml.parse(value); - return xmlDocument.findNodes(expectedValue, xpathNamespaces); + return new XmlNodeFindResult(xmlDocument.findNodes(expectedValue, xpathNamespaces)); } catch (XmlException e) { - notifier() - .info( - String.format( - "Warning: failed to parse the XML document. Reason: %s\nXML: %s", - e.getMessage(), value)); - return null; + final String message = + String.format( + "Warning: failed to parse the XML document. Reason: %s\nXML: %s", + e.getMessage(), value); + notifier().info(message); + return new XmlNodeFindResult(null, SubEvent.warning(message)); } catch (XPathException e) { - notifier().info("Warning: failed to evaluate the XPath expression " + expectedValue); - return null; + final String message = "Warning: failed to evaluate the XPath expression " + expectedValue; + notifier().info(message); + return new XmlNodeFindResult(null, SubEvent.warning(message)); + } + } + + private static class XmlNodeFindResult { + final ListOrSingle nodes; + final List subEvents; + + public XmlNodeFindResult(ListOrSingle nodes, SubEvent... subEvents) { + this.nodes = nodes; + this.subEvents = List.of(subEvents); } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MemoizingMatchResult.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MemoizingMatchResult.java index baeabd9ac4..f53817b85e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MemoizingMatchResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MemoizingMatchResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePattern.java index 189b02c725..4535f063b7 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,86 +16,29 @@ package com.github.tomakehurst.wiremock.matching; import static java.util.Collections.min; -import static java.util.Collections.singletonList; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.http.MultiValue; -import com.google.common.base.Function; -import com.google.common.base.Objects; -import com.google.common.collect.Lists; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; -public class MultiValuePattern implements NamedValueMatcher { +@JsonDeserialize(using = MultiValuePatternDeserializer.class) +public abstract class MultiValuePattern implements NamedValueMatcher { - private final StringValuePattern valuePattern; - - public MultiValuePattern(StringValuePattern valuePattern) { - this.valuePattern = valuePattern; - } - - @JsonCreator public static MultiValuePattern of(StringValuePattern valuePattern) { - return new MultiValuePattern(valuePattern); + return new SingleMatchMultiValuePattern(valuePattern); } public static MultiValuePattern absent() { - return new MultiValuePattern(WireMock.absent()); - } - - @Override - public MatchResult match(MultiValue multiValue) { - List values = multiValue.isPresent() ? multiValue.values() : singletonList(null); - return getBestMatch(valuePattern, values); - } - - @JsonValue - public StringValuePattern getValuePattern() { - return valuePattern; - } - - @Override - public String getName() { - return valuePattern.getName(); - } - - @Override - public String getExpected() { - return valuePattern.getExpected(); + return new SingleMatchMultiValuePattern(WireMock.absent()); } - private static MatchResult getBestMatch( + protected static MatchResult getBestMatch( final StringValuePattern valuePattern, List values) { List allResults = - Lists.transform( - values, - new Function() { - public MatchResult apply(String input) { - return valuePattern.match(input); - } - }); - - return min( - allResults, - new Comparator() { - public int compare(MatchResult o1, MatchResult o2) { - return Double.compare(o1.getDistance(), o2.getDistance()); - } - }); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MultiValuePattern that = (MultiValuePattern) o; - return Objects.equal(valuePattern, that.valuePattern); - } - - @Override - public int hashCode() { - return Objects.hashCode(valuePattern); + values.stream().map(valuePattern::match).collect(Collectors.toList()); + return min(allResults, Comparator.comparingDouble(MatchResult::getDistance)); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternDeserializer.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternDeserializer.java new file mode 100644 index 0000000000..ac85c62dc8 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternDeserializer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; + +public class MultiValuePatternDeserializer extends JsonDeserializer { + + @Override + public MultiValuePattern deserialize(JsonParser parser, DeserializationContext ctxt) + throws IOException { + JsonNode rootNode = parser.readValueAsTree(); + final ObjectMapper mapper = (ObjectMapper) parser.getCodec(); + if (rootNode.has(ExactMatchMultiValuePattern.JSON_KEY)) { + return mapper.treeToValue(rootNode, ExactMatchMultiValuePattern.class); + } else if (rootNode.has(IncludesMatchMultiValuePattern.JSON_KEY)) { + return mapper.treeToValue(rootNode, IncludesMatchMultiValuePattern.class); + } else { + return mapper.treeToValue(rootNode, SingleMatchMultiValuePattern.class); + } + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePattern.java index a0aff06234..2f57957f5a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,16 @@ */ package com.github.tomakehurst.wiremock.matching; -import static com.google.common.collect.FluentIterable.from; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tomakehurst.wiremock.http.Body; import com.github.tomakehurst.wiremock.http.Request; -import com.google.common.base.Function; -import com.google.common.base.Predicate; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; public class MultipartValuePattern implements ValueMatcher { @@ -79,14 +76,8 @@ public MatchResult match(final Request request) { } private MatchResult matchAllMultiparts(final Request request) { - return from(request.getParts()) - .allMatch( - new Predicate() { - @Override - public boolean apply(Request.Part input) { - return MultipartValuePattern.this.match(input).isExactMatch(); - } - }) + return request.getParts().stream() + .allMatch(input -> MultipartValuePattern.this.match(input).isExactMatch()) ? MatchResult.exactMatch() : MatchResult.noMatch(); } @@ -97,14 +88,7 @@ private MatchResult matchAnyMultipart(final Request request) { return MatchResult.noMatch(); } - return from(parts) - .anyMatch( - new Predicate() { - @Override - public boolean apply(Request.Part input) { - return MultipartValuePattern.this.match(input).isExactMatch(); - } - }) + return parts.stream().anyMatch(input -> MultipartValuePattern.this.match(input).isExactMatch()) ? MatchResult.exactMatch() : MatchResult.noMatch(); } @@ -128,14 +112,11 @@ public List> getBodyPatterns() { private MatchResult matchHeaderPatterns(final Request.Part part) { if (headers != null && !headers.isEmpty()) { return MatchResult.aggregate( - from(headers.entrySet()) - .transform( - new Function, MatchResult>() { - public MatchResult apply(Map.Entry headerPattern) { - return headerPattern.getValue().match(part.getHeader(headerPattern.getKey())); - } - }) - .toList()); + headers.entrySet().stream() + .map( + headerPattern -> + headerPattern.getValue().match(part.getHeader(headerPattern.getKey()))) + .collect(Collectors.toList())); } return MatchResult.exactMatch(); @@ -143,15 +124,9 @@ public MatchResult apply(Map.Entry headerPattern) { private MatchResult matchBodyPatterns(final Request.Part value) { return MatchResult.aggregate( - from(bodyPatterns) - .transform( - new Function() { - @Override - public MatchResult apply(ContentPattern bodyPattern) { - return matchBody(value, bodyPattern); - } - }) - .toList()); + bodyPatterns.stream() + .map(bodyPattern -> matchBody(value, bodyPattern)) + .collect(Collectors.toList())); } private static MatchResult matchBody(Request.Part part, ContentPattern bodyPattern) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilder.java index 835489f809..0cbd13d331 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.containing; -import static com.google.common.collect.Maps.newLinkedHashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -25,7 +25,7 @@ public class MultipartValuePatternBuilder { private String name = null; - private Map headerPatterns = newLinkedHashMap(); + private Map headerPatterns = new LinkedHashMap<>(); private List> bodyPatterns = new LinkedList<>(); private MultipartValuePattern.MatchingType matchingType = MultipartValuePattern.MatchingType.ANY; diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/MultipleMatchMultiValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/MultipleMatchMultiValuePattern.java new file mode 100644 index 0000000000..72d527293d --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/MultipleMatchMultiValuePattern.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.github.tomakehurst.wiremock.http.MultiValue; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +public abstract class MultipleMatchMultiValuePattern extends MultiValuePattern { + + private static final String AND = " AND "; + + @Override + public String getName() { + return getValues().stream() + .map( + stringValuePattern -> + stringValuePattern.getName() + " " + stringValuePattern.getExpected()) + .collect(Collectors.joining(AND)); + } + + /** + * since this method will only be used by diff in case of multiple match values, so returning + * empty. For other patterns, it should not return empty value + * + * @return expected value + */ + @Override + public String getExpected() { + return StringUtils.EMPTY; + } + + @Override + public MatchResult match(MultiValue value) { + if (!value.isPresent()) { + return MatchResult.of(false); + } + List matchResults = + getValues().stream() + .map(stringValuePattern -> getBestMatch(stringValuePattern, value.values())) + .collect(Collectors.toList()); + return MatchResult.aggregate(matchResults); + } + + @JsonIgnore + public abstract List getValues(); + + @JsonIgnore + public String getOperator() { + return StringUtils.EMPTY; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/NotPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/NotPattern.java new file mode 100644 index 0000000000..563c5e369f --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/NotPattern.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class NotPattern extends StringValuePattern { + + private StringValuePattern unexpectedPattern; + + public NotPattern(@JsonProperty("not") StringValuePattern unexpectedPattern) { + super(unexpectedPattern.getExpected()); + this.unexpectedPattern = unexpectedPattern; + } + + public StringValuePattern getNot() { + return unexpectedPattern; + } + + @Override + public MatchResult match(String value) { + return invert(unexpectedPattern.match(value)); + } + + private MatchResult invert(final MatchResult matchResult) { + return new MatchResult() { + @Override + public boolean isExactMatch() { + return !matchResult.isExactMatch(); + } + + @Override + public double getDistance() { + if (isExactMatch()) { + return 0; + } + + return 1.0; + } + }; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/PathTemplatePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/PathTemplatePattern.java new file mode 100644 index 0000000000..adae93b297 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/PathTemplatePattern.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.common.Strings; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; + +public class PathTemplatePattern extends StringValuePattern { + + private final PathTemplate pathTemplate; + + public PathTemplatePattern(@JsonProperty("matchesPathTemplate") String expectedValue) { + super(expectedValue); + this.pathTemplate = new PathTemplate(expectedValue); + } + + public String getMatchesPathTemplate() { + return expectedValue; + } + + @JsonIgnore + public PathTemplate getPathTemplate() { + return pathTemplate; + } + + @Override + public MatchResult match(String path) { + return new MatchResult() { + @Override + public boolean isExactMatch() { + return pathTemplate.matches(path); + } + + @Override + public double getDistance() { + if (isExactMatch()) { + return 0; + } + + String expected = pathTemplate.withoutVariables(); + return Strings.normalisedLevenshteinDistance(expected, path); + } + }; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPattern.java index 7fad2b88b9..26eccc22e9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,28 +16,29 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.matching.RequestMatcherExtension.NEVER; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; import static com.github.tomakehurst.wiremock.matching.WeightedMatchResult.weight; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.net.HttpHeaders.AUTHORIZATION; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tomakehurst.wiremock.client.BasicCredentials; -import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.Urls; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; import com.github.tomakehurst.wiremock.http.Cookie; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableMap; import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; public class RequestPattern implements NamedValueMatcher { @@ -48,7 +49,10 @@ public class RequestPattern implements NamedValueMatcher { private final UrlPattern url; private final RequestMethod method; private final Map headers; + + private final Map pathParams; private final Map queryParams; + private final Map formParams; private final Map cookies; private final BasicCredentials basicAuthCredentials; private final List> bodyPatterns; @@ -65,7 +69,9 @@ public RequestPattern( final UrlPattern url, final RequestMethod method, final Map headers, + final Map pathParams, final Map queryParams, + final Map formParams, final Map cookies, final BasicCredentials basicAuthCredentials, final List> bodyPatterns, @@ -75,9 +81,11 @@ public RequestPattern( this.scheme = scheme; this.host = host; this.port = port; - this.url = firstNonNull(url, UrlPattern.ANY); - this.method = firstNonNull(method, RequestMethod.ANY); + this.url = getFirstNonNull(url, UrlPattern.ANY); + this.method = getFirstNonNull(method, RequestMethod.ANY); this.headers = headers; + this.pathParams = pathParams; + this.formParams = formParams; this.queryParams = queryParams; this.cookies = cookies; this.basicAuthCredentials = basicAuthCredentials; @@ -98,8 +106,10 @@ public MatchResult match(Request request) { weight(portMatches(request), 10.0), weight(RequestPattern.this.url.match(request.getUrl()), 10.0), weight(RequestPattern.this.method.match(request.getMethod()), 3.0), + weight(allPathParamsMatch(request)), weight(allHeadersMatchResult(request)), weight(allQueryParamsMatch(request)), + weight(allFormParamsMatch(request)), weight(allCookiesMatch(request)), weight(allBodyPatternsMatch(request)), weight(allMultipartPatternsMatch(request)))); @@ -127,9 +137,12 @@ public RequestPattern( @JsonProperty("urlPattern") String urlPattern, @JsonProperty("urlPath") String urlPath, @JsonProperty("urlPathPattern") String urlPathPattern, + @JsonProperty("urlPathTemplate") String urlPathTemplate, @JsonProperty("method") RequestMethod method, @JsonProperty("headers") Map headers, + @JsonProperty("pathParameters") Map pathParams, @JsonProperty("queryParameters") Map queryParams, + @JsonProperty("formParameters") Map formParams, @JsonProperty("cookies") Map cookies, @JsonProperty("basicAuth") BasicCredentials basicAuthCredentials, @JsonProperty("bodyPatterns") List> bodyPatterns, @@ -140,10 +153,12 @@ public RequestPattern( scheme, host, port, - UrlPattern.fromOneOf(url, urlPattern, urlPath, urlPathPattern), + UrlPattern.fromOneOf(url, urlPattern, urlPath, urlPathPattern, urlPathTemplate), method, headers, + pathParams, queryParams, + formParams, cookies, basicAuthCredentials, bodyPatterns, @@ -152,12 +167,12 @@ public RequestPattern( multiPattern); } - public static RequestPattern ANYTHING = + public static final RequestPattern ANYTHING = new RequestPattern( null, null, null, - WireMock.anyUrl(), + anyUrl(), RequestMethod.ANY, null, null, @@ -166,6 +181,8 @@ public RequestPattern( null, null, null, + null, + null, null); public RequestPattern(ValueMatcher customMatcher) { @@ -181,6 +198,8 @@ public RequestPattern(ValueMatcher customMatcher) { null, null, null, + null, + null, customMatcher, null); } @@ -197,6 +216,8 @@ public RequestPattern(CustomMatcherDefinition customMatcherDefinition) { null, null, null, + null, + null, customMatcherDefinition, null, null); @@ -204,7 +225,7 @@ public RequestPattern(CustomMatcherDefinition customMatcherDefinition) { @Override public MatchResult match(Request request) { - return match(request, Collections.emptyMap()); + return match(request, Collections.emptyMap()); } public static RequestPattern everything() { @@ -214,7 +235,7 @@ public static RequestPattern everything() { public MatchResult match(Request request, Map customMatchers) { if (customMatcherDefinition != null) { RequestMatcherExtension requestMatcher = - firstNonNull(customMatchers.get(customMatcherDefinition.getName()), NEVER); + getFirstNonNull(customMatchers.get(customMatcherDefinition.getName()), NEVER); MatchResult standardMatchResult = matcher.match(request); MatchResult customMatchResult = @@ -229,37 +250,23 @@ public MatchResult match(Request request, Map c private MatchResult allCookiesMatch(final Request request) { if (cookies != null && !cookies.isEmpty()) { return MatchResult.aggregate( - from(cookies.entrySet()) - .transform( - new Function, MatchResult>() { - public MatchResult apply( - final Map.Entry cookiePattern) { - Cookie cookie = request.getCookies().get(cookiePattern.getKey()); - if (cookie == null) { - return cookiePattern.getValue().nullSafeIsAbsent() - ? MatchResult.exactMatch() - : MatchResult.noMatch(); - } - - return from(cookie.getValues()) - .transform( - new Function() { - @Override - public MatchResult apply(String cookieValue) { - return cookiePattern.getValue().match(cookieValue); - } - }) - .toSortedList( - new Comparator() { - @Override - public int compare(MatchResult o1, MatchResult o2) { - return o2.compareTo(o1); - } - }) - .get(0); + cookies.entrySet().stream() + .map( + entry -> { + final StringValuePattern cookiePattern = entry.getValue(); + Cookie cookie = request.getCookies().get(entry.getKey()); + if (cookie == null) { + return cookiePattern.nullSafeIsAbsent() + ? MatchResult.exactMatch() + : MatchResult.noMatch(); } + + return cookie.getValues().stream() + .map(cookiePattern::match) + .max(Comparator.naturalOrder()) + .orElse(MatchResult.noMatch()); }) - .toList()); + .collect(toList())); } return MatchResult.exactMatch(); @@ -284,14 +291,11 @@ private MatchResult allHeadersMatchResult(final Request request) { if (combinedHeaders != null && !combinedHeaders.isEmpty()) { return MatchResult.aggregate( - from(combinedHeaders.entrySet()) - .transform( - new Function, MatchResult>() { - public MatchResult apply(Map.Entry headerPattern) { - return headerPattern.getValue().match(request.header(headerPattern.getKey())); - } - }) - .toList()); + combinedHeaders.entrySet().stream() + .map( + headerPattern -> + headerPattern.getValue().match(request.header(headerPattern.getKey()))) + .collect(toList())); } return MatchResult.exactMatch(); @@ -303,75 +307,94 @@ public Map combineBasicAuthAndOtherHeaders() { } Map combinedHeaders = headers; - ImmutableMap.Builder allHeadersBuilder = - ImmutableMap.builder() - .putAll( - firstNonNull(combinedHeaders, Collections.emptyMap())); + Map allHeadersBuilder = + new HashMap<>(getFirstNonNull(combinedHeaders, Collections.emptyMap())); allHeadersBuilder.put(AUTHORIZATION, basicAuthCredentials.asAuthorizationMultiValuePattern()); - combinedHeaders = allHeadersBuilder.build(); + combinedHeaders = allHeadersBuilder; return combinedHeaders; } private MatchResult allQueryParamsMatch(final Request request) { if (queryParams != null && !queryParams.isEmpty()) { return MatchResult.aggregate( - from(queryParams.entrySet()) - .transform( - new Function, MatchResult>() { - public MatchResult apply( - Map.Entry queryParamPattern) { - return queryParamPattern + queryParams.entrySet().stream() + .map( + queryParamPattern -> + queryParamPattern .getValue() - .match(request.queryParameter(queryParamPattern.getKey())); - } - }) - .toList()); + .match(request.queryParameter(queryParamPattern.getKey()))) + .collect(toList())); + } + + return MatchResult.exactMatch(); + } + + private MatchResult allFormParamsMatch(final Request request) { + if (formParams != null && !formParams.isEmpty()) { + return MatchResult.aggregate( + formParams.entrySet().stream() + .map( + formParamPattern -> + formParamPattern + .getValue() + .match(request.formParameter(formParamPattern.getKey()))) + .collect(toList())); + } + + return MatchResult.exactMatch(); + } + + private MatchResult allPathParamsMatch(final Request request) { + if (url.getClass().equals(UrlPathTemplatePattern.class) + && pathParams != null + && !pathParams.isEmpty()) { + final UrlPathTemplatePattern urlPathTemplatePattern = (UrlPathTemplatePattern) url; + final PathTemplate pathTemplate = urlPathTemplatePattern.getPathTemplate(); + if (!pathTemplate.matches(request.getUrl())) { + return MatchResult.noMatch(); + } + + final PathParams requestPathParams = pathTemplate.parse(Urls.getPath(request.getUrl())); + return MatchResult.aggregate( + pathParams.entrySet().stream() + .map(entry -> entry.getValue().match(requestPathParams.get(entry.getKey()))) + .collect(toList())); } return MatchResult.exactMatch(); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) private MatchResult allBodyPatternsMatch(final Request request) { if (bodyPatterns != null && !bodyPatterns.isEmpty() && request.getBody() != null) { return MatchResult.aggregate( - from(bodyPatterns) - .transform( - new Function() { - @Override - public MatchResult apply(ContentPattern pattern) { - if (StringValuePattern.class.isAssignableFrom(pattern.getClass())) { - String body = - StringUtils.isEmpty(request.getBodyAsString()) - ? null - : request.getBodyAsString(); - return pattern.match(body); - } - - return pattern.match(request.getBody()); - } - }) - .toList()); + bodyPatterns.stream() + .map( + (Function) + pattern -> { + if (StringValuePattern.class.isAssignableFrom(pattern.getClass())) { + String body = + StringUtils.isEmpty(request.getBodyAsString()) + ? null + : request.getBodyAsString(); + return pattern.match(body); + } + + return pattern.match(request.getBody()); + }) + .collect(toList())); } return MatchResult.exactMatch(); } - @SuppressWarnings("unchecked") private MatchResult allMultipartPatternsMatch(final Request request) { if (multipartPatterns != null && !multipartPatterns.isEmpty()) { if (!request.isMultipart()) { return MatchResult.noMatch(); } return MatchResult.aggregate( - from(multipartPatterns) - .transform( - new Function() { - public MatchResult apply(MultipartValuePattern pattern) { - return pattern.match(request); - } - }) - .toList()); + multipartPatterns.stream().map(pattern -> pattern.match(request)).collect(toList())); } return MatchResult.exactMatch(); @@ -409,6 +432,10 @@ public String getUrlPathPattern() { return urlPatternOrNull(UrlPathPattern.class, true); } + public String getUrlPathTemplate() { + return urlPatternOrNull(UrlPathTemplatePattern.class, false); + } + @JsonIgnore public UrlPattern getUrlMatcher() { return url; @@ -435,10 +462,18 @@ public BasicCredentials getBasicAuthCredentials() { return basicAuthCredentials; } + public Map getPathParameters() { + return pathParams; + } + public Map getQueryParameters() { return queryParams; } + public Map getFormParameters() { + return formParams; + } + public Map getCookies() { return cookies; } @@ -533,20 +568,10 @@ public static Predicate thatMatch(final RequestPattern pattern) { public static Predicate thatMatch( final RequestPattern pattern, final Map customMatchers) { - return new Predicate() { - @Override - public boolean apply(Request request) { - return pattern.match(request, customMatchers).isExactMatch(); - } - }; + return request -> pattern.match(request, customMatchers).isExactMatch(); } - public static Predicate withRequstMatching(final RequestPattern pattern) { - return new Predicate() { - @Override - public boolean apply(ServeEvent serveEvent) { - return pattern.match(serveEvent.getRequest()).isExactMatch(); - } - }; + public static Predicate withRequestMatching(final RequestPattern pattern) { + return serveEvent -> pattern.match(serveEvent.getRequest()).isExactMatch(); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilder.java index 96678f979a..86da113afc 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,16 @@ */ package com.github.tomakehurst.wiremock.matching; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Lists.newLinkedList; -import static com.google.common.collect.Maps.newLinkedHashMap; - import com.github.tomakehurst.wiremock.client.BasicCredentials; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Errors; +import com.github.tomakehurst.wiremock.common.InvalidInputException; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -34,12 +35,15 @@ public class RequestPatternBuilder { private Integer port; private UrlPattern url = UrlPattern.ANY; private RequestMethod method = RequestMethod.ANY; - private Map headers = newLinkedHashMap(); - private Map queryParams = newLinkedHashMap(); - private List> bodyPatterns = newArrayList(); - private Map cookies = newLinkedHashMap(); + private Map headers = new LinkedHashMap<>(); + private Map queryParams = new LinkedHashMap<>(); + + private Map formParams = new LinkedHashMap<>(); + private Map pathParams = new LinkedHashMap<>(); + private List> bodyPatterns = new ArrayList<>(); + private Map cookies = new LinkedHashMap<>(); private BasicCredentials basicCredentials; - private List multiparts = newLinkedList(); + private List multiparts = new LinkedList<>(); private ValueMatcher customMatcher; @@ -98,9 +102,15 @@ public static RequestPatternBuilder like(RequestPattern requestPattern) { if (requestPattern.getHeaders() != null) { builder.headers = requestPattern.getHeaders(); } + if (requestPattern.getPathParameters() != null) { + builder.pathParams = requestPattern.getPathParameters(); + } if (requestPattern.getQueryParameters() != null) { builder.queryParams = requestPattern.getQueryParameters(); } + if (requestPattern.getFormParameters() != null) { + builder.formParams = requestPattern.getFormParameters(); + } if (requestPattern.getCookies() != null) { builder.cookies = requestPattern.getCookies(); } @@ -147,16 +157,51 @@ public RequestPatternBuilder withHeader(String key, StringValuePattern valuePatt return this; } + public RequestPatternBuilder withHeader(String key, MultiValuePattern multiValuePattern) { + headers.put(key, multiValuePattern); + return this; + } + public RequestPatternBuilder withoutHeader(String key) { headers.put(key, MultiValuePattern.absent()); return this; } + public RequestPatternBuilder withPathParam(String key, StringValuePattern valuePattern) { + pathParams.put(key, valuePattern); + return this; + } + public RequestPatternBuilder withQueryParam(String key, StringValuePattern valuePattern) { queryParams.put(key, MultiValuePattern.of(valuePattern)); return this; } + public RequestPatternBuilder withQueryParam(String key, MultiValuePattern multiValuePattern) { + queryParams.put(key, multiValuePattern); + return this; + } + + public RequestPatternBuilder withFormParam(String key, StringValuePattern valuePattern) { + formParams.put(key, MultiValuePattern.of(valuePattern)); + return this; + } + + public RequestPatternBuilder withFormParam(String key, MultiValuePattern multiValuePattern) { + formParams.put(key, multiValuePattern); + return this; + } + + public RequestPatternBuilder withoutFormParam(String key) { + formParams.put(key, MultiValuePattern.absent()); + return this; + } + + public RequestPatternBuilder withoutQueryParam(String key) { + queryParams.put(key, MultiValuePattern.absent()); + return this; + } + public RequestPatternBuilder withCookie(String key, StringValuePattern valuePattern) { cookies.put(key, valuePattern); return this; @@ -207,6 +252,12 @@ public RequestPatternBuilder andMatching(String customRequestMatcherName, Parame } public RequestPattern build() { + if (!(url instanceof UrlPathTemplatePattern) && !pathParams.isEmpty()) { + throw new InvalidInputException( + Errors.single( + 19, "URL path parameters specified without a path template as the URL matcher")); + } + return new RequestPattern( scheme, hostPattern, @@ -214,7 +265,9 @@ public RequestPattern build() { url, method, headers.isEmpty() ? null : headers, + pathParams.isEmpty() ? null : pathParams, queryParams.isEmpty() ? null : queryParams, + formParams.isEmpty() ? null : formParams, cookies.isEmpty() ? null : cookies, basicCredentials, bodyPatterns.isEmpty() ? null : bodyPatterns, diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/SingleMatchMultiValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/SingleMatchMultiValuePattern.java new file mode 100644 index 0000000000..306468cbb1 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/SingleMatchMultiValuePattern.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import static java.util.Collections.singletonList; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.github.tomakehurst.wiremock.http.MultiValue; +import java.util.List; +import java.util.Objects; + +@JsonDeserialize(as = SingleMatchMultiValuePattern.class) +public class SingleMatchMultiValuePattern extends MultiValuePattern { + + private final StringValuePattern valuePattern; + + @JsonCreator + public SingleMatchMultiValuePattern(StringValuePattern valuePattern) { + this.valuePattern = valuePattern; + } + + @Override + public MatchResult match(MultiValue multiValue) { + List values = multiValue.isPresent() ? multiValue.values() : singletonList(null); + return getBestMatch(valuePattern, values); + } + + @JsonValue + public StringValuePattern getValuePattern() { + return valuePattern; + } + + @Override + public String getName() { + return valuePattern.getName(); + } + + @Override + public String getExpected() { + return valuePattern.getExpected(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SingleMatchMultiValuePattern that = (SingleMatchMultiValuePattern) o; + return Objects.equals(valuePattern, that.valuePattern); + } + + @Override + public int hashCode() { + return Objects.hash(valuePattern); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePattern.java index f6d9fbc31c..e5ebd0d398 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.google.common.base.Objects; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Objects; @JsonDeserialize(using = StringValuePatternJsonDeserializer.class) public abstract class StringValuePattern extends ContentPattern { - public StringValuePattern(String expectedValue) { + protected StringValuePattern(String expectedValue) { super(expectedValue); } @@ -51,22 +50,18 @@ public String toString() { public final String getName() { Constructor constructor = - FluentIterable.from(this.getClass().getDeclaredConstructors()) - .firstMatch( - new Predicate>() { - @Override - public boolean apply(Constructor input) { - return (input.getParameterAnnotations().length > 0 + Arrays.stream(this.getClass().getDeclaredConstructors()) + .filter( + input -> + input.getParameterAnnotations().length > 0 && input.getParameterAnnotations()[0].length > 0 - && input.getParameterAnnotations()[0][0] instanceof JsonProperty); - } - }) - .orNull(); + && input.getParameterAnnotations()[0][0] instanceof JsonProperty) + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "Constructor must have a first parameter annotated with JsonProperty(\"\")")); - if (constructor == null) { - throw new IllegalStateException( - "Constructor must have a first parameter annotated with JsonProperty(\"\")"); - } JsonProperty jsonPropertyAnnotation = (JsonProperty) constructor.getParameterAnnotations()[0][0]; return jsonPropertyAnnotation.value(); @@ -90,11 +85,11 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StringValuePattern that = (StringValuePattern) o; - return Objects.equal(expectedValue, that.expectedValue); + return Objects.equals(expectedValue, that.expectedValue); } @Override public int hashCode() { - return Objects.hashCode(expectedValue); + return Objects.hash(expectedValue); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePatternJsonDeserializer.java b/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePatternJsonDeserializer.java index d6716a4369..998ee76517 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePatternJsonDeserializer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/StringValuePatternJsonDeserializer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,24 +16,16 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.google.common.collect.Iterables.tryFind; -import static com.google.common.collect.Iterators.find; -import static java.util.Arrays.asList; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; +import com.github.tomakehurst.wiremock.client.WireMock.JsonSchemaVersion; import com.github.tomakehurst.wiremock.common.DateTimeUnit; import com.github.tomakehurst.wiremock.common.Json; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.*; @@ -42,28 +34,30 @@ public class StringValuePatternJsonDeserializer extends JsonDeserializer { private static final Map> PATTERNS = - new ImmutableMap.Builder>() - .put("equalTo", EqualToPattern.class) - .put("equalToJson", EqualToJsonPattern.class) - .put("matchesJsonPath", MatchesJsonPathPattern.class) - .put("equalToXml", EqualToXmlPattern.class) - .put("matchesXPath", MatchesXPathPattern.class) - .put("contains", ContainsPattern.class) - .put("doesNotContain", NegativeContainsPattern.class) - .put("matches", RegexPattern.class) - .put("doesNotMatch", NegativeRegexPattern.class) - .put("before", BeforeDateTimePattern.class) - .put("after", AfterDateTimePattern.class) - .put("equalToDateTime", EqualToDateTimePattern.class) - .put("anything", AnythingPattern.class) - .put("absent", AbsentPattern.class) - .put("and", LogicalAnd.class) - .put("or", LogicalOr.class) - .build(); + Map.ofEntries( + Map.entry("equalTo", EqualToPattern.class), + Map.entry("equalToJson", EqualToJsonPattern.class), + Map.entry("matchesJsonPath", MatchesJsonPathPattern.class), + Map.entry("matchesJsonSchema", MatchesJsonSchemaPattern.class), + Map.entry("equalToXml", EqualToXmlPattern.class), + Map.entry("matchesXPath", MatchesXPathPattern.class), + Map.entry("contains", ContainsPattern.class), + Map.entry("not", NotPattern.class), + Map.entry("doesNotContain", NegativeContainsPattern.class), + Map.entry("matches", RegexPattern.class), + Map.entry("doesNotMatch", NegativeRegexPattern.class), + Map.entry("before", BeforeDateTimePattern.class), + Map.entry("after", AfterDateTimePattern.class), + Map.entry("equalToDateTime", EqualToDateTimePattern.class), + Map.entry("anything", AnythingPattern.class), + Map.entry("absent", AbsentPattern.class), + Map.entry("and", LogicalAnd.class), + Map.entry("or", LogicalOr.class), + Map.entry("matchesPathTemplate", PathTemplatePattern.class)); @Override public StringValuePattern deserialize(JsonParser parser, DeserializationContext context) - throws IOException, JsonProcessingException { + throws IOException { JsonNode rootNode = parser.readValueAsTree(); return buildStringValuePattern(rootNode); } @@ -76,6 +70,8 @@ public StringValuePattern buildStringValuePattern(JsonNode rootNode) throws Json Class patternClass = findPatternClass(rootNode); if (patternClass.equals(EqualToJsonPattern.class)) { return deserializeEqualToJson(rootNode); + } else if (patternClass.equals(MatchesJsonSchemaPattern.class)) { + return deserializeMatchesJsonSchema(rootNode); } else if (patternClass.equals(EqualToXmlPattern.class)) { return deserializeEqualToXml(rootNode); } else if (patternClass.equals(MatchesJsonPathPattern.class)) { @@ -92,6 +88,8 @@ public StringValuePattern buildStringValuePattern(JsonNode rootNode) throws Json return deserializeAnd(rootNode); } else if (patternClass.equals(LogicalOr.class)) { return deserializeOr(rootNode); + } else if (patternClass.equals(NotPattern.class)) { + return deserializeNot(rootNode); } final Map.Entry mainFieldEntry = findMainFieldEntry(rootNode); @@ -109,19 +107,16 @@ public StringValuePattern buildStringValuePattern(JsonNode rootNode) throws Json } private static Map.Entry findMainFieldEntry(JsonNode rootNode) { - return find( - rootNode.fields(), - new Predicate>() { - @Override - public boolean apply(Map.Entry input) { - return PATTERNS.keySet().contains(input.getKey()); - } - }); + List> list = getListFromNode(rootNode); + return list.stream() + .filter(input -> PATTERNS.containsKey(input.getKey())) + .findFirst() + .orElseThrow(NoSuchElementException::new); } private EqualToPattern deserializeEqualTo(JsonNode rootNode) throws JsonMappingException { if (!rootNode.has("equalTo")) { - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); + throw new JsonMappingException(rootNode + " is not a valid match operation"); } JsonNode equalToNode = rootNode.findValue("equalTo"); @@ -137,7 +132,7 @@ private EqualToPattern deserializeEqualTo(JsonNode rootNode) throws JsonMappingE private EqualToJsonPattern deserializeEqualToJson(JsonNode rootNode) throws JsonMappingException { if (!rootNode.has("equalToJson")) { - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); + throw new JsonMappingException(rootNode + " is not a valid match operation"); } JsonNode operand = rootNode.findValue("equalToJson"); @@ -153,9 +148,32 @@ private EqualToJsonPattern deserializeEqualToJson(JsonNode rootNode) throws Json } } + private MatchesJsonSchemaPattern deserializeMatchesJsonSchema(JsonNode rootNode) + throws JsonMappingException { + if (!rootNode.has("matchesJsonSchema")) { + throw new JsonMappingException(rootNode + " is not a valid match operation"); + } + + JsonNode operand = rootNode.findValue("matchesJsonSchema"); + + JsonSchemaVersion schemaVersion; + try { + String schemaVersionString = fromNullableTextNode(rootNode.findValue("schemaVersion")); + schemaVersion = + schemaVersionString != null + ? JsonSchemaVersion.valueOf(schemaVersionString) + : JsonSchemaVersion.DEFAULT; + } catch (Exception e) { + throw new JsonMappingException( + "schemaVersion must be one of " + Json.write(JsonSchemaVersion.values())); + } + + return new MatchesJsonSchemaPattern(operand.textValue(), schemaVersion); + } + private EqualToXmlPattern deserializeEqualToXml(JsonNode rootNode) throws JsonMappingException { if (!rootNode.has("equalToXml")) { - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); + throw new JsonMappingException(rootNode + " is not a valid match operation"); } JsonNode operand = rootNode.findValue("equalToXml"); @@ -179,7 +197,7 @@ private EqualToXmlPattern deserializeEqualToXml(JsonNode rootNode) throws JsonMa private MatchesJsonPathPattern deserialiseMatchesJsonPathPattern(JsonNode rootNode) throws JsonMappingException { if (!rootNode.has("matchesJsonPath")) { - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); + throw new JsonMappingException(rootNode + " is not a valid match operation"); } JsonNode outerPatternNode = rootNode.findValue("matchesJsonPath"); @@ -200,15 +218,13 @@ private MatchesJsonPathPattern deserialiseMatchesJsonPathPattern(JsonNode rootNo private MatchesXPathPattern deserialiseMatchesXPathPattern(JsonNode rootNode) throws JsonMappingException { if (!rootNode.has("matchesXPath")) { - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); + throw new JsonMappingException(rootNode + " is not a valid match operation"); } JsonNode namespacesNode = rootNode.findValue("xPathNamespaces"); Map namespaces = - namespacesNode != null - ? toNamespaceMap(namespacesNode) - : Collections.emptyMap(); + namespacesNode != null ? toNamespaceMap(namespacesNode) : Collections.emptyMap(); JsonNode outerPatternNode = rootNode.findValue("matchesXPath"); if (outerPatternNode.isTextual()) { @@ -265,9 +281,9 @@ private StringValuePattern deserialiseDateTimePattern(JsonNode rootNode, String expectedOffsetUnitNode != null ? DateTimeUnit.valueOf(expectedOffsetUnitNode.textValue().toUpperCase()) : null); + default: + throw new JsonMappingException(rootNode + " is not a valid match operation"); } - - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); } private LogicalAnd deserializeAnd(JsonNode node) throws JsonMappingException { @@ -276,9 +292,7 @@ private LogicalAnd deserializeAnd(JsonNode node) throws JsonMappingException { throw new JsonMappingException("and field must be an array of matchers"); } - JsonParser parser = Json.getObjectMapper().treeAsTokens(node.get("and")); - - try { + try (JsonParser parser = Json.getObjectMapper().treeAsTokens(node.get("and"))) { List operands = parser.readValueAs(new TypeReference>() {}); return new LogicalAnd(operands); @@ -293,9 +307,7 @@ private LogicalOr deserializeOr(JsonNode node) throws JsonMappingException { throw new JsonMappingException("and field must be an array of matchers"); } - JsonParser parser = Json.getObjectMapper().treeAsTokens(node.get("or")); - - try { + try (JsonParser parser = Json.getObjectMapper().treeAsTokens(node.get("or"))) { List operands = parser.readValueAs(new TypeReference>() {}); return new LogicalOr(operands); @@ -304,15 +316,31 @@ private LogicalOr deserializeOr(JsonNode node) throws JsonMappingException { } } + private StringValuePattern deserializeNot(JsonNode rootNode) throws JsonMappingException { + if (!rootNode.has("not")) { + throw new JsonMappingException(rootNode + " is not a valid not operation"); + } + + JsonNode notNode = rootNode.findValue("not"); + + try (JsonParser parser = Json.getObjectMapper().treeAsTokens(notNode)) { + StringValuePattern unexpectedPattern = + parser.readValueAs(new TypeReference() {}); + return new NotPattern(unexpectedPattern); + } catch (IOException e) { + return throwUnchecked(e, NotPattern.class); + } + } + private static Map toNamespaceMap(JsonNode namespacesNode) { - ImmutableMap.Builder builder = ImmutableMap.builder(); + Map map = new LinkedHashMap<>(); for (Iterator> fields = namespacesNode.fields(); fields.hasNext(); ) { Map.Entry field = fields.next(); - builder.put(field.getKey(), field.getValue().textValue()); + map.put(field.getKey(), field.getValue().textValue()); } - return builder.build(); + return map; } private static Boolean fromNullable(JsonNode node) { @@ -328,29 +356,26 @@ private static Set comparisonTypeSetFromArray(JsonNode node) { return null; } - ImmutableSet.Builder builder = ImmutableSet.builder(); + Set comparisonTypes = new LinkedHashSet<>(); for (JsonNode itemNode : node) { - builder.add(ComparisonType.valueOf(itemNode.textValue())); + comparisonTypes.add(ComparisonType.valueOf(itemNode.textValue())); } - return builder.build(); + return comparisonTypes; } @SuppressWarnings("unchecked") private static Constructor findConstructor( Class clazz) { Optional> optionalConstructor = - tryFind( - asList(clazz.getDeclaredConstructors()), - new Predicate>() { - @Override - public boolean apply(Constructor input) { - return input.getParameterTypes().length == 1 - && input.getGenericParameterTypes()[0].equals(String.class); - } - }); - - if (!optionalConstructor.isPresent()) { + Arrays.stream(clazz.getDeclaredConstructors()) + .filter( + input -> + input.getParameterTypes().length == 1 + && input.getGenericParameterTypes()[0].equals(String.class)) + .findFirst(); + + if (optionalConstructor.isEmpty()) { throw new IllegalStateException( "Constructor for " + clazz.getSimpleName() @@ -361,24 +386,24 @@ public boolean apply(Constructor input) { } private static boolean isAbsent(JsonNode rootNode) { - for (Map.Entry node : ImmutableList.copyOf(rootNode.fields())) { - if (node.getKey().equals("absent")) { - return true; - } - } - - return false; + return getListFromNode(rootNode).stream().anyMatch(node -> node.getKey().equals("absent")); } private static Class findPatternClass(JsonNode rootNode) throws JsonMappingException { - for (Map.Entry node : ImmutableList.copyOf(rootNode.fields())) { + for (Map.Entry node : getListFromNode(rootNode)) { Class patternClass = PATTERNS.get(node.getKey()); if (patternClass != null) { return patternClass; } } - throw new JsonMappingException(rootNode.toString() + " is not a valid match operation"); + throw new JsonMappingException(rootNode + " is not a valid match operation"); + } + + private static List> getListFromNode(JsonNode rootNode) { + List> list = new LinkedList<>(); + rootNode.fields().forEachRemaining(list::add); + return list; } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPathTemplatePattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPathTemplatePattern.java new file mode 100644 index 0000000000..4454a5e317 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPathTemplatePattern.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import com.github.tomakehurst.wiremock.common.url.PathTemplate; + +public class UrlPathTemplatePattern extends UrlPathPattern { + + public UrlPathTemplatePattern(String expectedValue) { + super(new PathTemplatePattern(expectedValue), false); + } + + @Override + public PathTemplatePattern getPattern() { + return (PathTemplatePattern) super.getPattern(); + } + + public PathTemplate getPathTemplate() { + return getPattern().getPathTemplate(); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPattern.java b/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPattern.java index 0aca669820..8ee6bf7337 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPattern.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/UrlPattern.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,8 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.github.tomakehurst.wiremock.client.WireMock; -import com.google.common.base.Objects; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; +import java.util.Objects; public class UrlPattern implements NamedValueMatcher { @@ -32,7 +33,11 @@ public UrlPattern(StringValuePattern pattern, boolean regex) { } public static UrlPattern fromOneOf( - String url, String urlPattern, String urlPath, String urlPathPattern) { + String url, + String urlPattern, + String urlPath, + String urlPathPattern, + String urlPathTemplate) { if (url != null) { return WireMock.urlEqualTo(url); } else if (urlPattern != null) { @@ -41,6 +46,8 @@ public static UrlPattern fromOneOf( return WireMock.urlPathEqualTo(urlPath); } else if (urlPathPattern != null) { return WireMock.urlPathMatching(urlPathPattern); + } else if (urlPathTemplate != null) { + return WireMock.urlPathTemplate(urlPathTemplate); } else { return WireMock.anyUrl(); } @@ -65,6 +72,10 @@ public StringValuePattern getPattern() { return pattern; } + public PathTemplate getPathTemplate() { + return null; + } + @Override public String getExpected() { return pattern.expectedValue; @@ -80,12 +91,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UrlPattern that = (UrlPattern) o; - return regex == that.regex && Objects.equal(pattern, that.pattern); + return regex == that.regex && Objects.equals(pattern, that.pattern); } @Override public int hashCode() { - return Objects.hashCode(pattern, regex); + return Objects.hash(pattern, regex); } public boolean isSpecified() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/WeightedMatchResult.java b/src/main/java/com/github/tomakehurst/wiremock/matching/WeightedMatchResult.java index a6830d5234..ab0a030e8c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/WeightedMatchResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/WeightedMatchResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,4 +48,8 @@ public double getDistance() { public double getWeighting() { return weighting; } + + public MatchResult getMatchResult() { + return matchResult; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/LoggedResponseDefinitionTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/recording/LoggedResponseDefinitionTransformer.java index 6cdd5773ee..934a2319d8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/LoggedResponseDefinitionTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/LoggedResponseDefinitionTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,16 @@ */ package com.github.tomakehurst.wiremock.recording; -import static com.github.tomakehurst.wiremock.common.ContentTypes.determineIsTextFromMimeType; -import static com.google.common.collect.Iterables.filter; -import static com.google.common.net.HttpHeaders.*; +import static com.github.tomakehurst.wiremock.common.ContentTypes.*; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.Gzip; import com.github.tomakehurst.wiremock.common.Strings; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; import java.nio.charset.Charset; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Transforms a LoggedResponse into a ResponseDefinition, which will be used to construct a @@ -37,7 +34,7 @@ public class LoggedResponseDefinitionTransformer implements Function { private static final List EXCLUDED_HEADERS = - ImmutableList.of( + List.of( CaseInsensitiveKey.from(CONTENT_ENCODING), CaseInsensitiveKey.from(CONTENT_LENGTH), CaseInsensitiveKey.from(TRANSFER_ENCODING)); @@ -76,12 +73,8 @@ private byte[] bodyDecompressedIfRequired(LoggedResponse response) { private HttpHeaders withoutContentEncodingAndContentLength(LoggedResponse response) { return new HttpHeaders( - filter( - response.getHeaders().all(), - new Predicate() { - public boolean apply(HttpHeader header) { - return !EXCLUDED_HEADERS.contains(header.caseInsensitiveKey()); - } - })); + response.getHeaders().all().stream() + .filter(header -> !EXCLUDED_HEADERS.contains(header.caseInsensitiveKey())) + .collect(Collectors.toList())); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFilters.java b/src/main/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFilters.java index c2aa93d1e8..10596180a3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFilters.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFilters.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Predicate; import java.util.List; import java.util.UUID; +import java.util.function.Predicate; /** A predicate to filter proxied ServeEvents against RequestPattern filters and IDs */ public class ProxiedServeEventFilters implements Predicate { @@ -69,7 +69,7 @@ public void setAllowNonProxied(boolean allowNonProxied) { } @Override - public boolean apply(ServeEvent serveEvent) { + public boolean test(ServeEvent serveEvent) { if (!serveEvent.getResponseDefinition().isProxyResponse() && !allowNonProxied) { return false; } @@ -78,10 +78,6 @@ public boolean apply(ServeEvent serveEvent) { return false; } - if (ids != null && !ids.contains(serveEvent.getId())) { - return false; - } - - return true; + return ids == null || ids.contains(serveEvent.getId()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/RecordSpecBuilder.java b/src/main/java/com/github/tomakehurst/wiremock/recording/RecordSpecBuilder.java index d41c34f82a..ea87330062 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/RecordSpecBuilder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/RecordSpecBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ */ package com.github.tomakehurst.wiremock.recording; -import static com.google.common.collect.Maps.newLinkedHashMap; import static java.util.Arrays.asList; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -30,7 +30,7 @@ public class RecordSpecBuilder { private String targetBaseUrl; private RequestPatternBuilder filterRequestPatternBuilder; private List filterIds; - private Map headers = newLinkedHashMap(); + private Map headers = new LinkedHashMap<>(); private RequestBodyPatternFactory requestBodyPatternFactory; private long maxTextBodySize = ResponseDefinitionBodyMatcher.DEFAULT_MAX_TEXT_SIZE; private long maxBinaryBodySize = ResponseDefinitionBodyMatcher.DEFAULT_MAX_BINARY_SIZE; diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/Recorder.java b/src/main/java/com/github/tomakehurst/wiremock/recording/Recorder.java index a1d00a4b77..e485becf51 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/Recorder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/Recorder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,55 +17,59 @@ import static com.github.tomakehurst.wiremock.client.WireMock.proxyAllTo; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; -import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.indexOf; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.indexOf; -import com.github.tomakehurst.wiremock.common.Errors; -import com.github.tomakehurst.wiremock.common.FileSource; -import com.github.tomakehurst.wiremock.common.InvalidInputException; import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.extension.Extensions; import com.github.tomakehurst.wiremock.extension.StubMappingTransformer; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.RecorderStateStore; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Predicate; -import com.google.common.collect.Lists; import java.util.List; import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; public class Recorder { private final Admin admin; - private State state; + private final Extensions extensions; + private final BlobStore filesBlobStore; - public Recorder(Admin admin) { + private final RecorderStateStore stateStore; + + public Recorder( + Admin admin, Extensions extensions, BlobStore filesBlobStore, RecorderStateStore stateStore) { this.admin = admin; - state = State.initial(); + this.extensions = extensions; + this.filesBlobStore = filesBlobStore; + this.stateStore = stateStore; } public synchronized void startRecording(RecordSpec spec) { + RecorderState state = stateStore.get(); if (state.getStatus() == RecordingStatus.Recording) { return; } - if (spec.getTargetBaseUrl() == null || spec.getTargetBaseUrl().isEmpty()) { - throw new InvalidInputException( - Errors.validation("/targetBaseUrl", "targetBaseUrl is required")); + StubMapping proxyMapping = null; + if (spec.getTargetBaseUrl() != null && !spec.getTargetBaseUrl().isEmpty()) { + proxyMapping = proxyAllTo(spec.getTargetBaseUrl()).build(); + admin.addStubMapping(proxyMapping); } - StubMapping proxyMapping = proxyAllTo(spec.getTargetBaseUrl()).build(); - admin.addStubMapping(proxyMapping); - List serveEvents = admin.getServeEvents().getServeEvents(); UUID initialId = serveEvents.isEmpty() ? null : serveEvents.get(0).getId(); state = state.start(initialId, proxyMapping, spec); + stateStore.set(state); notifier().info("Started recording with record spec:\n" + Json.write(spec)); } public synchronized SnapshotRecordResult stopRecording() { + RecorderState state = stateStore.get(); if (state.getStatus() != RecordingStatus.Recording) { throw new NotRecordingException(); } @@ -74,7 +78,11 @@ public synchronized SnapshotRecordResult stopRecording() { UUID lastId = serveEvents.isEmpty() ? null : serveEvents.get(0).getId(); state = state.stop(lastId); - admin.removeStubMapping(state.getProxyMapping()); + stateStore.set(state); + + if (state.getProxyMapping() != null) { + admin.removeStubMapping(state.getProxyMapping()); + } if (serveEvents.isEmpty()) { return SnapshotRecordResult.empty(); @@ -94,22 +102,17 @@ public synchronized SnapshotRecordResult stopRecording() { } private static Predicate withId(final UUID id) { - return new Predicate() { - @Override - public boolean apply(ServeEvent input) { - return input.getId().equals(id); - } - }; + return input -> input.getId().equals(id); } public SnapshotRecordResult takeSnapshot(List serveEvents, RecordSpec recordSpec) { final List stubMappings = serveEventsToStubMappings( - Lists.reverse(serveEvents), + serveEvents, recordSpec.getFilters(), new SnapshotStubMappingGenerator( recordSpec.getCaptureHeaders(), recordSpec.getRequestBodyPatternFactory()), - getStubMappingPostProcessor(admin.getOptions(), recordSpec)); + getStubMappingPostProcessor(recordSpec)); for (StubMapping stubMapping : stubMappings) { if (recordSpec.shouldPersist()) { @@ -126,85 +129,31 @@ public List serveEventsToStubMappings( ProxiedServeEventFilters serveEventFilters, SnapshotStubMappingGenerator stubMappingGenerator, SnapshotStubMappingPostProcessor stubMappingPostProcessor) { - final Iterable stubMappings = - from(serveEventsResult).filter(serveEventFilters).transform(stubMappingGenerator); + final List stubMappings = + serveEventsResult.stream() + .filter(serveEventFilters) + .map(stubMappingGenerator) + .collect(Collectors.toList()); return stubMappingPostProcessor.process(stubMappings); } - public SnapshotStubMappingPostProcessor getStubMappingPostProcessor( - Options options, RecordSpec recordSpec) { - FileSource filesRoot = options.filesRoot().child(FILES_ROOT); + public SnapshotStubMappingPostProcessor getStubMappingPostProcessor(RecordSpec recordSpec) { final SnapshotStubMappingTransformerRunner transformerRunner = new SnapshotStubMappingTransformerRunner( - options.extensionsOfType(StubMappingTransformer.class).values(), + extensions.ofType(StubMappingTransformer.class).values(), recordSpec.getTransformers(), recordSpec.getTransformerParameters(), - filesRoot); + filesBlobStore); return new SnapshotStubMappingPostProcessor( recordSpec.shouldRecordRepeatsAsScenarios(), transformerRunner, recordSpec.getExtractBodyCriteria(), - new SnapshotStubMappingBodyExtractor(filesRoot)); + new SnapshotStubMappingBodyExtractor(filesBlobStore)); } public RecordingStatus getStatus() { - return state.getStatus(); - } - - private static class State { - - private final RecordingStatus status; - private final StubMapping proxyMapping; - private final RecordSpec spec; - private final UUID startingServeEventId; - private final UUID finishingServeEventId; - - public State( - RecordingStatus status, - StubMapping proxyMapping, - RecordSpec spec, - UUID startingServeEventId, - UUID finishingServeEventId) { - this.status = status; - this.proxyMapping = proxyMapping; - this.spec = spec; - this.startingServeEventId = startingServeEventId; - this.finishingServeEventId = finishingServeEventId; - } - - public static State initial() { - return new State(RecordingStatus.NeverStarted, null, null, null, null); - } - - public State start(UUID startingServeEventId, StubMapping proxyMapping, RecordSpec spec) { - return new State(RecordingStatus.Recording, proxyMapping, spec, startingServeEventId, null); - } - - public State stop(UUID finishingServeEventId) { - return new State( - RecordingStatus.Stopped, proxyMapping, spec, startingServeEventId, finishingServeEventId); - } - - public RecordingStatus getStatus() { - return status; - } - - public StubMapping getProxyMapping() { - return proxyMapping; - } - - public RecordSpec getSpec() { - return spec; - } - - public UUID getStartingServeEventId() { - return startingServeEventId; - } - - public UUID getFinishingServeEventId() { - return finishingServeEventId; - } + return stateStore.get().getStatus(); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java b/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java new file mode 100644 index 0000000000..b869732c51 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.recording; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.util.UUID; + +public class RecorderState { + + private final RecordingStatus status; + private final StubMapping proxyMapping; + private final RecordSpec spec; + private final UUID startingServeEventId; + private final UUID finishingServeEventId; + + public RecorderState( + @JsonProperty("status") RecordingStatus status, + @JsonProperty("proxyMapping") StubMapping proxyMapping, + @JsonProperty("spec") RecordSpec spec, + @JsonProperty("startingServeEventId") UUID startingServeEventId, + @JsonProperty("finishingServeEventId") UUID finishingServeEventId) { + this.status = status; + this.proxyMapping = proxyMapping; + this.spec = spec; + this.startingServeEventId = startingServeEventId; + this.finishingServeEventId = finishingServeEventId; + } + + public static RecorderState initial() { + return new RecorderState(RecordingStatus.NeverStarted, null, null, null, null); + } + + public RecorderState start(UUID startingServeEventId, StubMapping proxyMapping, RecordSpec spec) { + return new RecorderState( + RecordingStatus.Recording, proxyMapping, spec, startingServeEventId, null); + } + + public RecorderState stop(UUID finishingServeEventId) { + return new RecorderState( + RecordingStatus.Stopped, proxyMapping, spec, startingServeEventId, finishingServeEventId); + } + + public RecordingStatus getStatus() { + return status; + } + + public StubMapping getProxyMapping() { + return proxyMapping; + } + + public RecordSpec getSpec() { + return spec; + } + + public UUID getStartingServeEventId() { + return startingServeEventId; + } + + public UUID getFinishingServeEventId() { + return finishingServeEventId; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformer.java index 1427110db8..5aff7b0a18 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.matching.*; -import com.google.common.base.Function; import java.util.Map; +import java.util.function.Function; /** * Creates a RequestPatternBuilder from a Request's URL, method, body (if present), and optionally diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/ScenarioProcessor.java b/src/main/java/com/github/tomakehurst/wiremock/recording/ScenarioProcessor.java index cb35afc071..a5316b14c3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/ScenarioProcessor.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/ScenarioProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,42 +19,36 @@ import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.stubbing.Scenario; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.*; import java.net.URI; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; public class ScenarioProcessor { public void putRepeatedRequestsInScenarios(List stubMappings) { - ImmutableListMultimap stubsGroupedByRequest = - Multimaps.index( - stubMappings, - new Function() { - @Override - public RequestPattern apply(StubMapping mapping) { - return mapping.getRequest(); - } - }); + Map> stubsGroupedByRequest = + stubMappings.stream() + .collect( + Collectors.groupingBy( + StubMapping::getRequest, + LinkedHashMap::new, + Collectors.toCollection(LinkedList::new))); Map> groupsWithMoreThanOneStub = - Maps.filterEntries( - stubsGroupedByRequest.asMap(), - new Predicate>>() { - @Override - public boolean apply(Map.Entry> input) { - return input.getValue().size() > 1; - } - }); + stubsGroupedByRequest.entrySet().stream() + .filter(entry -> entry.getValue().size() > 1) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (entry1, entry2) -> entry1, + LinkedHashMap::new)); int scenarioIndex = 0; for (Map.Entry> entry : groupsWithMoreThanOneStub.entrySet()) { scenarioIndex++; - putStubsInScenario(scenarioIndex, ImmutableList.copyOf(entry.getValue())); + putStubsInScenario(scenarioIndex, List.copyOf(entry.getValue())); } } @@ -62,7 +56,7 @@ private void putStubsInScenario(int scenarioIndex, List stubMapping StubMapping firstScenario = stubMappings.get(0); String scenarioName = "scenario-" - + Integer.toString(scenarioIndex) + + scenarioIndex + "-" + Urls.urlToPathParts(URI.create(firstScenario.getRequest().getUrl())); diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotRecordResult.java b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotRecordResult.java index 8cf6f336de..ecbfff0819 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotRecordResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotRecordResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Function; -import com.google.common.collect.Lists; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; @JsonDeserialize(using = SnapshotRecordResultDeserialiser.class) public class SnapshotRecordResult { @@ -43,15 +42,7 @@ public static SnapshotRecordResult full(List stubMappings) { } public static SnapshotRecordResult idsFromMappings(List stubMappings) { - return new Ids( - Lists.transform( - stubMappings, - new Function() { - @Override - public UUID apply(StubMapping input) { - return input.getId(); - } - })); + return new Ids(stubMappings.stream().map(StubMapping::getId).collect(Collectors.toList())); } public static SnapshotRecordResult ids(List ids) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractor.java b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractor.java index d396e424d0..e4f21981b5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractor.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,17 @@ import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.http.ContentTypeHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; +import com.github.tomakehurst.wiremock.store.BlobStore; import com.github.tomakehurst.wiremock.stubbing.StubMapping; public class SnapshotStubMappingBodyExtractor { - private final FileSource fileSource; + private final BlobStore filesBlobStore; - public SnapshotStubMappingBodyExtractor(FileSource fileSource) { - this.fileSource = fileSource; + public SnapshotStubMappingBodyExtractor(BlobStore filesBlobStore) { + this.filesBlobStore = filesBlobStore; } /** @@ -45,7 +47,8 @@ public void extractInPlace(StubMapping stubMapping) { : ContentTypeHeader.absent(), body); - String bodyFileName = SafeNames.makeSafeFileName(stubMapping, extension); + FilenameMaker filenameMaker = new FilenameMaker("default", extension); + String bodyFileName = filenameMaker.filenameFor(stubMapping); // used to prevent ambiguous method call error for withBody() String noStringBody = null; @@ -59,6 +62,6 @@ public void extractInPlace(StubMapping stubMapping) { .withBase64Body(null) .build()); - fileSource.writeBinaryFile(bodyFileName, body); + filesBlobStore.put(bodyFileName, body); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGenerator.java b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGenerator.java index 98d4b4f70c..7434d7d0f3 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGenerator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,14 @@ */ package com.github.tomakehurst.wiremock.recording; -import com.github.tomakehurst.wiremock.common.SafeNames; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Function; import java.net.URI; import java.util.Map; +import java.util.function.Function; /** * Transforms ServeEvents to StubMappings using RequestPatternTransformer and @@ -54,7 +54,8 @@ public StubMapping apply(ServeEvent event) { StubMapping stubMapping = new StubMapping(requestPattern, responseDefinition); URI uri = URI.create(event.getRequest().getUrl()); - stubMapping.setName(SafeNames.makeSafeNameFromUrl(uri.getPath())); + FilenameMaker filenameMaker = new FilenameMaker(); + stubMapping.setName(filenameMaker.sanitizeUrl(uri.getPath())); return stubMapping; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessor.java b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessor.java index e6d0a0a135..935190c6e9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessor.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,24 @@ */ package com.github.tomakehurst.wiremock.recording; +import static java.util.stream.Collectors.toList; + import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.google.common.collect.HashMultiset; -import com.google.common.collect.Lists; import com.google.common.collect.Multiset; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** - * Performs stateful post-processing tasks on stub mappings generated from ServeEvents: 1. Detect - * duplicate requests and either discard them or turn them into scenarios 2. Extract response bodies - * to a separate file, if applicable 3. Run any applicable StubMappingTransformers against the stub - * mappings + * Performs stateful post-processing tasks on stub mappings generated from ServeEvents: + * + *

    + *
  1. Run any applicable StubMappingTransformers against the stub mappings. + *
  2. Detect duplicate requests and either discard them or turn them into scenarios. + *
  3. Extract response bodies to a separate file, if applicable. + *
*/ public class SnapshotStubMappingPostProcessor { private final boolean shouldRecordRepeatsAsScenarios; @@ -46,31 +51,45 @@ public SnapshotStubMappingPostProcessor( this.bodyExtractor = bodyExtractor; } - public List process(Iterable stubMappings) { - final Multiset requestCounts = HashMultiset.create(); - final List processedStubMappings = new ArrayList<>(); + public List process(Collection stubMappings) { + // 1. Run any applicable StubMappingTransformers against the stub mappings. + List transformedStubMappings = + stubMappings.stream().map(transformerRunner).collect(toList()); - for (StubMapping stubMapping : stubMappings) { - requestCounts.add(stubMapping.getRequest()); + // 2. Detect duplicate requests and either discard them or turn them into scenarios. + Multiset requestCounts = HashMultiset.create(); + List processedStubMappings = new ArrayList<>(); + for (StubMapping transformedStubMapping : transformedStubMappings) { + requestCounts.add(transformedStubMapping.getRequest()); // Skip duplicate requests if shouldRecordRepeatsAsScenarios is not enabled - if (requestCounts.count(stubMapping.getRequest()) > 1 && !shouldRecordRepeatsAsScenarios) { + if (requestCounts.count(transformedStubMapping.getRequest()) > 1 + && !shouldRecordRepeatsAsScenarios) { continue; } - if (bodyExtractMatcher != null - && bodyExtractMatcher.match(stubMapping.getResponse()).isExactMatch()) { - bodyExtractor.extractInPlace(stubMapping); - } - - processedStubMappings.add(stubMapping); + processedStubMappings.add(transformedStubMapping); } if (shouldRecordRepeatsAsScenarios) { new ScenarioProcessor().putRepeatedRequestsInScenarios(processedStubMappings); } - // Run any stub mapping transformer extensions - return Lists.transform(processedStubMappings, transformerRunner); + // 3. Extract response bodies to a separate file, if applicable. + extractStubMappingBodies(processedStubMappings); + + return processedStubMappings; + } + + private void extractStubMappingBodies(List stubMappings) { + if (bodyExtractMatcher == null) { + return; + } + + for (StubMapping stubMapping : stubMappings) { + if (bodyExtractMatcher.match(stubMapping.getResponse()).isExactMatch()) { + bodyExtractor.extractInPlace(stubMapping); + } + } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunner.java b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunner.java index 5a74d0fc0b..3bcf26fa26 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunner.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,11 @@ import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.StubMappingTransformer; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.files.BlobStoreFileSource; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Function; import java.util.List; +import java.util.function.Function; /** * Applies all registered StubMappingTransformer extensions against a stub mapping when applicable, @@ -41,11 +43,11 @@ public SnapshotStubMappingTransformerRunner( Iterable registeredTransformers, List requestedTransformers, Parameters parameters, - FileSource filesRoot) { + BlobStore filesBlobStore) { this.requestedTransformers = requestedTransformers; this.registeredTransformers = registeredTransformers; this.parameters = parameters; - this.filesRoot = filesRoot; + this.filesRoot = new BlobStoreFileSource(filesBlobStore); } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/security/BasicAuthenticator.java b/src/main/java/com/github/tomakehurst/wiremock/security/BasicAuthenticator.java index c9f5557389..64ba26417a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/security/BasicAuthenticator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/security/BasicAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,13 @@ */ package com.github.tomakehurst.wiremock.security; -import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; import static java.util.Arrays.asList; import com.github.tomakehurst.wiremock.client.BasicCredentials; import com.github.tomakehurst.wiremock.http.Request; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; import java.util.List; +import java.util.stream.Collectors; public class BasicAuthenticator implements Authenticator { @@ -43,15 +42,9 @@ public BasicAuthenticator(String username, String password) { @Override public boolean authenticate(Request request) { List headerValues = - FluentIterable.from(credentials) - .transform( - new Function() { - @Override - public String apply(BasicCredentials input) { - return input.asAuthorizationHeaderValue(); - } - }) - .toList(); + credentials.stream() + .map(BasicCredentials::asAuthorizationHeaderValue) + .collect(Collectors.toList()); return request.containsHeader(AUTHORIZATION) && headerValues.contains(request.header(AUTHORIZATION).firstValue()); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/security/ClientBasicAuthenticator.java b/src/main/java/com/github/tomakehurst/wiremock/security/ClientBasicAuthenticator.java index 0d6b013b6e..e8238f9759 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/security/ClientBasicAuthenticator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/security/ClientBasicAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package com.github.tomakehurst.wiremock.security; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; import static com.github.tomakehurst.wiremock.http.HttpHeader.httpHeader; -import static com.google.common.net.HttpHeaders.AUTHORIZATION; import static java.util.Collections.singletonList; import com.github.tomakehurst.wiremock.client.BasicCredentials; diff --git a/src/main/java/com/github/tomakehurst/wiremock/security/ClientTokenAuthenticator.java b/src/main/java/com/github/tomakehurst/wiremock/security/ClientTokenAuthenticator.java index 3efe628443..0ddc6cb721 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/security/ClientTokenAuthenticator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/security/ClientTokenAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ */ package com.github.tomakehurst.wiremock.security; -import com.google.common.net.HttpHeaders; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; public class ClientTokenAuthenticator extends SingleHeaderClientAuthenticator { public ClientTokenAuthenticator(String token) { - super(HttpHeaders.AUTHORIZATION, "Token " + token); + super(AUTHORIZATION, "Token " + token); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/security/SingleHeaderAuthenticator.java b/src/main/java/com/github/tomakehurst/wiremock/security/SingleHeaderAuthenticator.java index ff60484bec..a6b738029d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/security/SingleHeaderAuthenticator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/security/SingleHeaderAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.security; -import static com.google.common.net.HttpHeaders.AUTHORIZATION; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.Request; diff --git a/src/main/java/com/github/tomakehurst/wiremock/security/TokenAuthenticator.java b/src/main/java/com/github/tomakehurst/wiremock/security/TokenAuthenticator.java index 9ec82f0667..361bfeee1d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/security/TokenAuthenticator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/security/TokenAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,11 @@ */ package com.github.tomakehurst.wiremock.security; -import com.google.common.net.HttpHeaders; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; public class TokenAuthenticator extends SingleHeaderAuthenticator { public TokenAuthenticator(String token) { - super(HttpHeaders.AUTHORIZATION, "Token " + token); + super(AUTHORIZATION, "Token " + token); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java index 2863d60f57..2b325ca537 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,10 @@ */ package com.github.tomakehurst.wiremock.servlet; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class ContentTypeSettingFilter implements Filter { diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java index 0b4c29f240..85161a8ae1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.github.tomakehurst.wiremock.servlet; import com.github.tomakehurst.wiremock.core.FaultInjector; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public interface FaultInjectorFactory { diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java index 669325324d..c167cad5fe 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package com.github.tomakehurst.wiremock.servlet; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; public interface MultipartRequestConfigurer { diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java index f5a741738c..cc5d1cccf4 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java @@ -18,8 +18,8 @@ import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import com.github.tomakehurst.wiremock.core.FaultInjector; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; -import javax.servlet.http.HttpServletResponse; public class NoFaultInjector implements FaultInjector { diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java index cbd6e35170..6663f4546f 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java @@ -16,8 +16,8 @@ package com.github.tomakehurst.wiremock.servlet; import com.github.tomakehurst.wiremock.core.FaultInjector; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; public class NoFaultInjectorFactory implements FaultInjectorFactory { diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/NotMatchedServlet.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/NotMatchedServlet.java new file mode 100644 index 0000000000..a9488cf884 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/NotMatchedServlet.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.servlet; + +import com.github.tomakehurst.wiremock.common.Exceptions; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.verification.diff.DiffEventData; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +public class NotMatchedServlet extends HttpServlet { + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + Optional.ofNullable(req.getAttribute(ServeEvent.ORIGINAL_SERVE_EVENT_KEY)) + .map(ServeEvent.class::cast) + .flatMap(ServeEvent::getDiffSubEvent) + .ifPresentOrElse( + diffSubEvent -> { + final DiffEventData diffData = diffSubEvent.getDataAs(DiffEventData.class); + resp.setStatus(diffData.getStatus()); + resp.setContentType(diffData.getContentType()); + resp.setCharacterEncoding(StandardCharsets.UTF_8.name()); + + try (final PrintWriter writer = resp.getWriter()) { + writer.write(diffData.getReport()); + writer.flush(); + } catch (IOException e) { + Exceptions.throwUnchecked(e); + } + }, + () -> Exceptions.uncheck(() -> resp.sendError(404))); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/TrailingSlashFilter.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/TrailingSlashFilter.java index 61841af6aa..ec90be3a31 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/TrailingSlashFilter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/TrailingSlashFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,14 @@ */ package com.github.tomakehurst.wiremock.servlet; +import static java.nio.charset.StandardCharsets.UTF_8; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.*; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; public class TrailingSlashFilter implements Filter { @@ -44,8 +45,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha private static class StatusAndRedirectExposingHttpServletResponse extends HttpServletResponseWrapper { - private String path; - private HttpServletRequest request; + private final String path; + private final HttpServletRequest request; public StatusAndRedirectExposingHttpServletResponse( HttpServletResponse response, String path, HttpServletRequest request) { @@ -84,13 +85,11 @@ private static boolean isRelativePath(String location) { private String getRequestPathFrom(HttpServletRequest httpServletRequest) throws ServletException { try { String fullPath = - new URI(URLEncoder.encode(httpServletRequest.getRequestURI(), "utf-8")).getPath(); + new URI(URLEncoder.encode(httpServletRequest.getRequestURI(), UTF_8)).getPath(); String pathWithoutContext = fullPath.substring(httpServletRequest.getContextPath().length()); - return URLDecoder.decode(pathWithoutContext, "utf-8"); + return URLDecoder.decode(pathWithoutContext, UTF_8); } catch (URISyntaxException e) { throw new ServletException(e); - } catch (UnsupportedEncodingException e) { - throw new ServletException(e); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/WarConfiguration.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/WarConfiguration.java index 310b2c9852..9839d3e36c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/WarConfiguration.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/WarConfiguration.java @@ -19,9 +19,10 @@ import com.github.tomakehurst.wiremock.common.*; import com.github.tomakehurst.wiremock.common.BrowserProxySettings; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.core.MappingsSaver; import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.extension.ExtensionDeclarations; import com.github.tomakehurst.wiremock.http.CaseInsensitiveKey; import com.github.tomakehurst.wiremock.http.HttpServerFactory; import com.github.tomakehurst.wiremock.http.ThreadPoolFactory; @@ -31,13 +32,12 @@ import com.github.tomakehurst.wiremock.security.NoAuthenticator; import com.github.tomakehurst.wiremock.standalone.JsonFileMappingsSource; import com.github.tomakehurst.wiremock.standalone.MappingsLoader; -import com.github.tomakehurst.wiremock.verification.notmatched.NotMatchedRenderer; -import com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer; -import com.google.common.base.Optional; -import java.util.Collections; +import com.github.tomakehurst.wiremock.store.DefaultStores; +import com.github.tomakehurst.wiremock.store.Stores; +import jakarta.servlet.ServletContext; import java.util.List; -import java.util.Map; -import javax.servlet.ServletContext; +import java.util.Optional; +import java.util.Set; public class WarConfiguration implements Options { @@ -84,6 +84,11 @@ public ProxySettings proxyVia() { return ProxySettings.NO_PROXY; } + @Override + public Stores getStores() { + return new DefaultStores(filesRoot()); + } + @Override public FileSource filesRoot() { String fileSourceRoot = servletContext.getInitParameter(FILE_SOURCE_ROOT_KEY); @@ -92,7 +97,7 @@ public FileSource filesRoot() { @Override public MappingsLoader mappingsLoader() { - return new JsonFileMappingsSource(filesRoot().child("mappings")); + return new JsonFileMappingsSource(filesRoot().child("mappings"), new FilenameMaker()); } @Override @@ -114,7 +119,7 @@ public boolean requestJournalDisabled() { public Optional maxRequestJournalEntries() { String str = servletContext.getInitParameter("maxRequestJournalEntries"); if (str == null) { - return Optional.absent(); + return Optional.empty(); } return Optional.of(Integer.parseInt(str)); } @@ -124,6 +129,11 @@ public String bindAddress() { return null; } + @Override + public FilenameMaker getFilenameMaker() { + return null; + } + @Override public List matchingHeaders() { return emptyList(); @@ -150,8 +160,8 @@ public ThreadPoolFactory threadPoolFactory() { } @Override - public Map extensionsOfType(Class extensionType) { - return Collections.emptyMap(); + public ExtensionDeclarations getDeclaredExtensions() { + return new ExtensionDeclarations(); } @Override @@ -169,11 +179,6 @@ public boolean getHttpsRequiredForAdminApi() { return false; } - @Override - public NotMatchedRenderer getNotMatchedRenderer() { - return new PlainTextStubNotMatchedRenderer(); - } - @Override public AsynchronousResponseSettings getAsynchronousResponseSettings() { return new AsynchronousResponseSettings(false, 0); @@ -228,4 +233,34 @@ public NetworkAddressRules getProxyTargetRules() { public BrowserProxySettings browserProxySettings() { return BrowserProxySettings.DISABLED; } + + @Override + public int proxyTimeout() { + return DEFAULT_TIMEOUT; + } + + @Override + public boolean getResponseTemplatingEnabled() { + return true; + } + + @Override + public boolean getResponseTemplatingGlobal() { + return false; + } + + @Override + public Long getMaxTemplateCacheEntries() { + return null; + } + + @Override + public Set getTemplatePermittedSystemKeys() { + return null; + } + + @Override + public boolean getTemplateEscapingDisabled() { + return false; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHandlerDispatchingServlet.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHandlerDispatchingServlet.java index 1ebd6cae4b..f10d026c73 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHandlerDispatchingServlet.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHandlerDispatchingServlet.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,17 @@ */ package com.github.tomakehurst.wiremock.servlet; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_LENGTH; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.core.Options.ChunkedEncodingPolicy.BODY_FILE; import static com.github.tomakehurst.wiremock.core.Options.ChunkedEncodingPolicy.NEVER; import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; import static com.github.tomakehurst.wiremock.servlet.WireMockHttpServletRequestAdapter.ORIGINAL_REQUEST_KEY; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.github.tomakehurst.wiremock.stubbing.ServeEvent.ORIGINAL_SERVE_EVENT_KEY; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.URLDecoder.decode; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MILLISECONDS; import com.github.tomakehurst.wiremock.common.LocalNotifier; @@ -33,15 +34,17 @@ import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockApp; import com.github.tomakehurst.wiremock.http.*; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.google.common.io.ByteStreams; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ScheduledExecutorService; -import javax.servlet.*; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class WireMockHandlerDispatchingServlet extends HttpServlet { @@ -108,7 +111,7 @@ public void init(ServletConfig config) { browserProxyingEnabled = Boolean.parseBoolean( - firstNonNull(context.getAttribute("browserProxyingEnabled"), "false").toString()); + getFirstNonNull(context.getAttribute("browserProxyingEnabled"), "false").toString()); } private String getNormalizedMappedUnder(ServletConfig config) { @@ -124,7 +127,7 @@ private String getNormalizedMappedUnder(ServletConfig config) { private boolean getFileContextForwardingFlagFrom(ServletConfig config) { String flagValue = config.getInitParameter(SHOULD_FORWARD_TO_FILES_CONTEXT); - return Boolean.valueOf(flagValue); + return Boolean.parseBoolean(flagValue); } @Override @@ -133,13 +136,25 @@ protected void service( throws ServletException, IOException { LocalNotifier.set(notifier); + // TODO: The HTTP/1.x CONNECT is also forwarded to the servlet now. To keep backward + // compatible behavior (with proxy involved), skipping the CONNECT handling altogether. + if (Objects.equals(httpServletRequest.getMethod(), "CONNECT")) { + return; + } + Request request = new WireMockHttpServletRequestAdapter( httpServletRequest, multipartRequestConfigurer, mappedUnder, browserProxyingEnabled); ServletHttpResponder responder = new ServletHttpResponder(httpServletRequest, httpServletResponse); - requestHandler.handle(request, responder); + + final ServeEvent originalServeEvent = + httpServletRequest.getAttribute(ORIGINAL_SERVE_EVENT_KEY) != null + ? (ServeEvent) httpServletRequest.getAttribute(ORIGINAL_SERVE_EVENT_KEY) + : null; + + requestHandler.handle(request, responder, originalServeEvent); } private class ServletHttpResponder implements HttpResponder { @@ -154,12 +169,14 @@ public ServletHttpResponder( } @Override - public void respond(final Request request, final Response response) { + public void respond( + final Request request, final Response response, Map attributes) { if (Thread.currentThread().isInterrupted()) { return; } httpServletRequest.setAttribute(ORIGINAL_REQUEST_KEY, LoggedRequest.createFrom(request)); + attributes.forEach(httpServletRequest::setAttribute); if (isAsyncSupported(response, httpServletRequest)) { respondAsync(request, response); @@ -190,14 +207,11 @@ private boolean isAsyncSupported(Response response, HttpServletRequest httpServl private void respondAsync(final Request request, final Response response) { final AsyncContext asyncContext = httpServletRequest.startAsync(); scheduledExecutorService.schedule( - new Runnable() { - @Override - public void run() { - try { - respondTo(request, response); - } finally { - asyncContext.complete(); - } + () -> { + try { + respondTo(request, response); + } finally { + asyncContext.complete(); } }, response.getInitialDelay(), @@ -234,7 +248,17 @@ public void applyResponse( if (response.getStatusMessage() == null) { httpServletResponse.setStatus(response.getStatus()); } else { - httpServletResponse.setStatus(response.getStatus(), response.getStatusMessage()); + // The Jetty 11 does not implement HttpServletResponse::setStatus and always sets the + // reason as `null`, the workaround using + // org.eclipse.jetty.server.Response::setStatusWithReason + // still works. + if (httpServletResponse instanceof org.eclipse.jetty.server.Response) { + final org.eclipse.jetty.server.Response jettyResponse = + (org.eclipse.jetty.server.Response) httpServletResponse; + jettyResponse.setStatusWithReason(response.getStatus(), response.getStatusMessage()); + } else { + httpServletResponse.setStatus(response.getStatus(), response.getStatusMessage()); + } } for (HttpHeader header : response.getHeaders().all()) { @@ -265,7 +289,7 @@ private FaultInjector buildFaultInjector( private static void writeAndTranslateExceptions( HttpServletResponse httpServletResponse, InputStream content) { try (ServletOutputStream out = httpServletResponse.getOutputStream()) { - ByteStreams.copy(content, out); + content.transferTo(out); out.flush(); } catch (IOException e) { throwUnchecked(e); @@ -283,7 +307,7 @@ private void writeAndTranslateExceptionsWithChunkedDribbleDelay( InputStream bodyStream, ChunkedDribbleDelay chunkedDribbleDelay) { try (ServletOutputStream out = httpServletResponse.getOutputStream()) { - byte[] body = ByteStreams.toByteArray(bodyStream); + byte[] body = bodyStream.readAllBytes(); if (body.length < 1) { notifier.error("Cannot chunk dribble delay when no body set"); @@ -316,7 +340,7 @@ private void forwardToFilesContext( throws ServletException, IOException { String forwardUrl = wiremockFileSourceRoot + WireMockApp.FILES_ROOT + request.getUrl(); RequestDispatcher dispatcher = - httpServletRequest.getRequestDispatcher(decode(forwardUrl, UTF_8.name())); + httpServletRequest.getRequestDispatcher(decode(forwardUrl, UTF_8)); dispatcher.forward(httpServletRequest, httpServletResponse); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletMultipartAdapter.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletMultipartAdapter.java index 1fec55f004..ea89a82dd6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletMultipartAdapter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletMultipartAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,11 @@ import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; -import com.google.common.io.ByteStreams; +import jakarta.servlet.http.Part; import java.io.IOException; import java.util.Collection; -import javax.servlet.http.Part; +import java.util.List; +import java.util.stream.Collectors; public class WireMockHttpServletMultipartAdapter implements Request.Part { @@ -32,18 +31,15 @@ public class WireMockHttpServletMultipartAdapter implements Request.Part { public WireMockHttpServletMultipartAdapter(final Part servletPart) { mPart = servletPart; - Iterable httpHeaders = - FluentIterable.from(mPart.getHeaderNames()) - .transform( - new Function() { - @Override - public HttpHeader apply(String name) { - Collection headerValues = servletPart.getHeaders(name); - return HttpHeader.httpHeader( - name, headerValues.toArray(new String[headerValues.size()])); - } - }); - + List httpHeaders = + mPart.getHeaderNames().stream() + .map( + name -> { + Collection headerValues = servletPart.getHeaders(name); + return HttpHeader.httpHeader( + name, headerValues.toArray(new String[headerValues.size()])); + }) + .collect(Collectors.toList()); headers = new HttpHeaders(httpHeaders); } @@ -69,7 +65,7 @@ public HttpHeaders getHeaders() { @Override public Body getBody() { try { - byte[] bytes = ByteStreams.toByteArray(mPart.getInputStream()); + byte[] bytes = mPart.getInputStream().readAllBytes(); HttpHeader header = getHeader(ContentTypeHeader.KEY); ContentTypeHeader contentTypeHeader = header.isPresent() diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletRequestAdapter.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletRequestAdapter.java index 1fabd637c2..5dd044bad8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletRequestAdapter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletRequestAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,43 +16,38 @@ package com.github.tomakehurst.wiremock.servlet; import static com.github.tomakehurst.wiremock.common.Encoding.encodeBase64; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; +import static com.github.tomakehurst.wiremock.common.Strings.isNullOrEmpty; import static com.github.tomakehurst.wiremock.common.Strings.stringFromBytes; import static com.github.tomakehurst.wiremock.common.Urls.splitQuery; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.io.ByteStreams.toByteArray; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.list; import com.github.tomakehurst.wiremock.common.Gzip; -import com.github.tomakehurst.wiremock.http.ContentTypeHeader; -import com.github.tomakehurst.wiremock.http.Cookie; -import com.github.tomakehurst.wiremock.http.HttpHeader; -import com.github.tomakehurst.wiremock.http.HttpHeaders; -import com.github.tomakehurst.wiremock.http.QueryParameter; -import com.github.tomakehurst.wiremock.http.Request; -import com.github.tomakehurst.wiremock.http.RequestMethod; +import com.github.tomakehurst.wiremock.http.*; import com.github.tomakehurst.wiremock.http.multipart.PartParser; -import com.github.tomakehurst.wiremock.jetty9.JettyUtils; -import com.google.common.base.*; -import com.google.common.base.Optional; -import com.google.common.collect.*; +import com.github.tomakehurst.wiremock.jetty.JettyUtils; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Maps; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import java.nio.charset.Charset; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; public class WireMockHttpServletRequestAdapter implements Request { public static final String ORIGINAL_REQUEST_KEY = "wiremock.ORIGINAL_REQUEST"; private final HttpServletRequest request; - private final MultipartRequestConfigurer multipartRequestConfigurer; private byte[] cachedBody; private final Supplier> cachedQueryParams; + + private final Map cachedFormParameters; private final boolean browserProxyingEnabled; private final String urlPrefixToRemove; private Collection cachedMultiparts; @@ -63,18 +58,16 @@ public WireMockHttpServletRequestAdapter( String urlPrefixToRemove, boolean browserProxyingEnabled) { this.request = request; - this.multipartRequestConfigurer = multipartRequestConfigurer; this.urlPrefixToRemove = urlPrefixToRemove; this.browserProxyingEnabled = browserProxyingEnabled; - cachedQueryParams = - Suppliers.memoize( - new Supplier>() { - @Override - public Map get() { - return splitQuery(request.getQueryString()); - } - }); + cachedQueryParams = Suppliers.memoize(() -> splitQuery(request.getQueryString())); + + this.cachedFormParameters = getFormParameters(request); + + if (multipartRequestConfigurer != null) { + multipartRequestConfigurer.configure(request); + } } @Override @@ -125,7 +118,7 @@ public int getPort() { public String getClientIp() { String forwardedForHeader = this.getHeader("X-Forwarded-For"); - if (forwardedForHeader != null && forwardedForHeader.length() > 0) { + if (forwardedForHeader != null && !forwardedForHeader.isEmpty()) { return forwardedForHeader; } @@ -136,7 +129,7 @@ public String getClientIp() { public byte[] getBody() { if (cachedBody == null) { try { - byte[] body = toByteArray(request.getInputStream()); + byte[] body = request.getInputStream().readAllBytes(); boolean isGzipped = hasGzipEncoding() || Gzip.isGzipped(body); cachedBody = isGzipped ? Gzip.unGzip(body) : body; } catch (IOException ioe) { @@ -170,14 +163,12 @@ public String getBodyAsBase64() { return encodeBase64(getBody()); } - @SuppressWarnings("unchecked") @Override public String getHeader(String key) { return request.getHeader(key); // case-insensitive per javadoc } @Override - @SuppressWarnings("unchecked") public HttpHeader header(String key) { if (request.getHeader(key) == null) { return HttpHeader.absent(key); @@ -212,16 +203,15 @@ public HttpHeaders getHeaders() { } private static HttpHeaders getHeadersLinear(org.eclipse.jetty.server.Request request) { - org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request) request; List headers = - jettyRequest.getHttpFields().stream() + request.getHttpFields().stream() .map(field -> HttpHeader.httpHeader(field.getName(), field.getValue())) .collect(Collectors.toList()); return new HttpHeaders(headers); } private HttpHeaders getHeadersQuadratic() { - List headerList = newArrayList(); + List headerList = new ArrayList<>(); for (String key : getAllHeaderKeys()) { headerList.add(header(key)); } @@ -229,7 +219,6 @@ private HttpHeaders getHeadersQuadratic() { return new HttpHeaders(headerList); } - @SuppressWarnings("unchecked") @Override public Set getAllHeaderKeys() { LinkedHashSet headerKeys = new LinkedHashSet<>(); @@ -245,20 +234,30 @@ public Set getAllHeaderKeys() { public Map getCookies() { ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - javax.servlet.http.Cookie[] cookies = - firstNonNull(request.getCookies(), new javax.servlet.http.Cookie[0]); - for (javax.servlet.http.Cookie cookie : cookies) { + jakarta.servlet.http.Cookie[] cookies = + getFirstNonNull(request.getCookies(), new jakarta.servlet.http.Cookie[0]); + for (jakarta.servlet.http.Cookie cookie : cookies) { builder.put(cookie.getName(), cookie.getValue()); } return Maps.transformValues( - builder.build().asMap(), input -> new Cookie(null, ImmutableList.copyOf(input))); + builder.build().asMap(), input -> new Cookie(null, List.copyOf(input))); } @Override public QueryParameter queryParameter(String key) { Map queryParams = cachedQueryParams.get(); - return firstNonNull(queryParams.get(key), QueryParameter.absent(key)); + return getFirstNonNull(queryParams.get(key), QueryParameter.absent(key)); + } + + @Override + public FormParameter formParameter(String key) { + return getFirstNonNull(cachedFormParameters.get(key), FormParameter.absent(key)); + } + + @Override + public Map formParameters() { + return cachedFormParameters; } @Override @@ -268,12 +267,7 @@ public boolean isBrowserProxyRequest() { return false; } - if (request instanceof org.eclipse.jetty.server.Request) { - org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request) request; - return JettyUtils.uriIsAbsolute(jettyRequest); - } - - return false; + return JettyUtils.isBrowserProxyRequest(request); } @Override @@ -286,7 +280,7 @@ public Collection getParts() { cachedMultiparts = PartParser.parseFrom(this); } - return (cachedMultiparts.size() > 0) ? cachedMultiparts : null; + return (cachedMultiparts.isEmpty()) ? null : cachedMultiparts; } @Override @@ -297,29 +291,20 @@ public boolean isMultipart() { @Override public Part getPart(final String name) { - if (name == null || name.length() == 0) { + if (isNullOrEmpty(name) || (cachedMultiparts == null && getParts() == null)) { return null; } - if (cachedMultiparts == null) { - if (getParts() == null) { - return null; - } - } - return from(cachedMultiparts) - .firstMatch( - new Predicate() { - @Override - public boolean apply(Part input) { - return name.equals(input.getName()); - } - }) - .get(); + + return cachedMultiparts.stream() + .filter(part -> name.equals(part.getName())) + .findFirst() + .orElse(null); } @Override public Optional getOriginalRequest() { Request originalRequest = (Request) request.getAttribute(ORIGINAL_REQUEST_KEY); - return Optional.fromNullable(originalRequest); + return Optional.ofNullable(originalRequest); } @Override @@ -331,4 +316,23 @@ public String toString() { public String getProtocol() { return request.getProtocol(); } + + private Map getFormParameters(HttpServletRequest request) { + + final String contentType = request.getContentType(); + if (contentType == null || !contentType.contains("application/x-www-form-urlencoded")) { + return Collections.emptyMap(); + } + + final MultiMap formParameterMultimap = new MultiMap<>(); + final String characterEncoding = request.getCharacterEncoding(); + final Charset charset = + characterEncoding != null ? Charset.forName(characterEncoding) : Charset.defaultCharset(); + UrlEncoded.decodeTo(getBodyAsString(), formParameterMultimap, charset); + + return formParameterMultimap.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, entry -> new FormParameter(entry.getKey(), entry.getValue()))); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockWebContextListener.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockWebContextListener.java index 7c9f85cd49..de321f7d7e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockWebContextListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockWebContextListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,16 @@ */ package com.github.tomakehurst.wiremock.servlet; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.github.tomakehurst.wiremock.common.Notifier; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.github.tomakehurst.wiremock.core.WireMockApp; import com.github.tomakehurst.wiremock.http.AdminRequestHandler; import com.github.tomakehurst.wiremock.http.StubRequestHandler; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; public class WireMockWebContextListener implements ServletContextListener { @@ -36,7 +36,7 @@ public void contextInitialized(ServletContextEvent sce) { boolean verboseLoggingEnabled = Boolean.parseBoolean( - firstNonNull(context.getInitParameter("verboseLoggingEnabled"), "true")); + getFirstNonNull(context.getInitParameter("verboseLoggingEnabled"), "true")); WireMockApp wireMockApp = new WireMockApp(new WarConfiguration(context), new NotImplementedContainer()); diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptions.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptions.java index cd631f7cf5..5a9570a2e8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptions.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptions.java @@ -19,41 +19,39 @@ import static com.github.tomakehurst.wiremock.common.BrowserProxySettings.DEFAULT_CA_KEYSTORE_PATH; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static com.github.tomakehurst.wiremock.common.ProxySettings.NO_PROXY; +import static com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker.DEFAULT_FILENAME_TEMPLATE; import static com.github.tomakehurst.wiremock.core.WireMockApp.MAPPINGS_ROOT; -import static com.github.tomakehurst.wiremock.extension.ExtensionLoader.valueAssignableFrom; import static com.github.tomakehurst.wiremock.http.CaseInsensitiveKey.TO_CASE_INSENSITIVE_KEYS; import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSourceFactory; import com.github.tomakehurst.wiremock.core.MappingsSaver; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockApp; -import com.github.tomakehurst.wiremock.extension.Extension; -import com.github.tomakehurst.wiremock.extension.ExtensionLoader; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.ExtensionDeclarations; +import com.github.tomakehurst.wiremock.global.GlobalSettings; import com.github.tomakehurst.wiremock.http.CaseInsensitiveKey; import com.github.tomakehurst.wiremock.http.HttpServerFactory; import com.github.tomakehurst.wiremock.http.ThreadPoolFactory; import com.github.tomakehurst.wiremock.http.trafficlistener.ConsoleNotifyingWiremockNetworkTrafficListener; import com.github.tomakehurst.wiremock.http.trafficlistener.DoNothingWiremockNetworkTrafficListener; import com.github.tomakehurst.wiremock.http.trafficlistener.WiremockNetworkTrafficListener; -import com.github.tomakehurst.wiremock.jetty9.QueuedThreadPoolFactory; +import com.github.tomakehurst.wiremock.jetty.QueuedThreadPoolFactory; import com.github.tomakehurst.wiremock.security.Authenticator; import com.github.tomakehurst.wiremock.security.BasicAuthenticator; import com.github.tomakehurst.wiremock.security.NoAuthenticator; -import com.github.tomakehurst.wiremock.verification.notmatched.NotMatchedRenderer; -import com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.base.Optional; -import com.google.common.base.Strings; -import com.google.common.collect.*; +import com.github.tomakehurst.wiremock.store.DefaultStores; +import com.github.tomakehurst.wiremock.store.Stores; import com.google.common.io.Resources; import java.io.IOException; import java.io.StringWriter; import java.net.URI; import java.util.*; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import joptsimple.OptionParser; import joptsimple.OptionSet; @@ -95,6 +93,7 @@ public class CommandLineOptions implements Options { private static final String ROOT_DIR = "root-dir"; private static final String CONTAINER_THREADS = "container-threads"; private static final String GLOBAL_RESPONSE_TEMPLATING = "global-response-templating"; + public static final String FILENAME_TEMPLATE = "filename-template"; private static final String LOCAL_RESPONSE_TEMPLATING = "local-response-templating"; private static final String ADMIN_API_BASIC_AUTH = "admin-api-basic-auth"; private static final String ADMIN_API_REQUIRE_HTTPS = "admin-api-require-https"; @@ -118,11 +117,18 @@ public class CommandLineOptions implements Options { private static final String LOGGED_RESPONSE_BODY_SIZE_LIMIT = "logged-response-body-size-limit"; private static final String ALLOW_PROXY_TARGETS = "allow-proxy-targets"; private static final String DENY_PROXY_TARGETS = "deny-proxy-targets"; + private static final String PROXY_TIMEOUT = "proxy-timeout"; + + private static final String PROXY_PASS_THROUGH = "proxy-pass-through"; private final OptionSet optionSet; + + private final Stores stores; private final FileSource fileSource; + private final MappingsSource mappingsSource; - private final Map extensions; + private final ExtensionDeclarations extensions; + private final FilenameMaker filenameMaker; private String helpText; private Integer actualHttpPort; @@ -257,6 +263,7 @@ public CommandLineOptions(String... args) { "Print all raw incoming and outgoing network traffic to console"); optionParser.accepts( GLOBAL_RESPONSE_TEMPLATING, "Preprocess all responses with Handlebars templates"); + optionParser.accepts(FILENAME_TEMPLATE, "Add filename template").withRequiredArg(); optionParser.accepts( LOCAL_RESPONSE_TEMPLATING, "Preprocess selected responses with Handlebars templates"); optionParser @@ -351,6 +358,12 @@ public CommandLineOptions(String... args) { DENY_PROXY_TARGETS, "Comma separated list of IP addresses, IP ranges (hyphenated) and domain name wildcards that cannot be proxied to/recorded from. Is evaluated after the list of allowed addresses.") .withRequiredArg(); + optionParser + .accepts(PROXY_TIMEOUT, "Timeout in milliseconds for requests to proxy") + .withRequiredArg(); + optionParser + .accepts(PROXY_PASS_THROUGH, "Flag to control browser proxy pass through") + .withRequiredArg(); optionParser.accepts(HELP, "Print this message").forHelp(); @@ -358,6 +371,8 @@ public CommandLineOptions(String... args) { validate(); captureHelpTextIfRequested(optionParser); + extensions = new ExtensionDeclarations(); + if (optionSet.has(LOAD_RESOURCES_FROM_CLASSPATH)) { fileSource = new ClasspathFileSource((String) optionSet.valueOf(LOAD_RESOURCES_FROM_CLASSPATH)); @@ -365,37 +380,53 @@ public CommandLineOptions(String... args) { fileSource = new SingleRootFileSource((String) optionSet.valueOf(ROOT_DIR)); } - mappingsSource = new JsonFileMappingsSource(fileSource.child(MAPPINGS_ROOT)); - extensions = buildExtensions(); + stores = new DefaultStores(fileSource); + + if (optionSet.has(PROXY_PASS_THROUGH)) { + GlobalSettings newSettings = + stores + .getSettingsStore() + .get() + .copy() + .proxyPassThrough( + Boolean.parseBoolean((String) optionSet.valueOf(PROXY_PASS_THROUGH))) + .build(); + stores.getSettingsStore().set(newSettings); + } + + filenameMaker = new FilenameMaker(getFilenameTemplateOption()); + mappingsSource = new JsonFileMappingsSource(fileSource.child(MAPPINGS_ROOT), filenameMaker); + buildExtensions(); actualHttpPort = null; } - private Map buildExtensions() { - ImmutableMap.Builder builder = ImmutableMap.builder(); + private void buildExtensions() { if (optionSet.has(EXTENSIONS)) { - String classNames = (String) optionSet.valueOf(EXTENSIONS); - builder.putAll(ExtensionLoader.load(classNames.split(","))); + String classNamesParamValue = (String) optionSet.valueOf(EXTENSIONS); + final String[] classNames = classNamesParamValue.split(","); + extensions.add(classNames); } + } - if (optionSet.has(GLOBAL_RESPONSE_TEMPLATING)) { - contributeResponseTemplateTransformer(builder, true); - } else if (optionSet.has(LOCAL_RESPONSE_TEMPLATING)) { - contributeResponseTemplateTransformer(builder, false); + private String getFilenameTemplateOption() { + if (optionSet.has(FILENAME_TEMPLATE)) { + String filenameTemplate = (String) optionSet.valueOf(FILENAME_TEMPLATE); + validateFilenameTemplate(filenameTemplate); + return filenameTemplate; } - - return builder.build(); + return DEFAULT_FILENAME_TEMPLATE; } - private void contributeResponseTemplateTransformer( - ImmutableMap.Builder builder, boolean global) { - ResponseTemplateTransformer transformer = - ResponseTemplateTransformer.builder() - .global(global) - .maxCacheEntries(getMaxTemplateCacheEntries()) - .permittedSystemKeys(getPermittedSystemKeys()) - .build(); - builder.put(transformer.getName(), transformer); + private void validateFilenameTemplate(String filenameTemplate) { + String[] templateParts = filenameTemplate.split("-"); + boolean handlebarIdentifierMissed = + Arrays.stream(templateParts) + .anyMatch(part -> !part.contains("{{{") || !part.contains("}}}")); + if (handlebarIdentifierMissed) { + throw new IllegalArgumentException( + "Format for filename template should be contain handlebar value. Please check format one more time"); + } } private void validate() { @@ -437,8 +468,10 @@ public boolean recordMappingsEnabled() { public List matchingHeaders() { if (optionSet.hasArgument(MATCH_HEADERS)) { String headerSpec = (String) optionSet.valueOf(MATCH_HEADERS); - UnmodifiableIterator headerKeys = Iterators.forArray(headerSpec.split(",")); - return ImmutableList.copyOf(Iterators.transform(headerKeys, TO_CASE_INSENSITIVE_KEYS)); + + return Arrays.stream(headerSpec.split(",")) + .map(TO_CASE_INSENSITIVE_KEYS) + .collect(Collectors.toUnmodifiableList()); } return Collections.emptyList(); @@ -449,7 +482,7 @@ public HttpServerFactory httpServerFactory() { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class cls = - loader.loadClass("com.github.tomakehurst.wiremock.jetty9.JettyHttpServerFactory"); + loader.loadClass("com.github.tomakehurst.wiremock.jetty.JettyHttpServerFactory"); return (HttpServerFactory) cls.getDeclaredConstructor().newInstance(); } catch (Exception e) { return throwUnchecked(e, null); @@ -496,6 +529,11 @@ public String bindAddress() { return DEFAULT_BIND_ADDRESS; } + @Override + public FilenameMaker getFilenameMaker() { + return filenameMaker; + } + @Override public HttpsSettings httpsSettings() { return new HttpsSettings.Builder() @@ -595,9 +633,8 @@ public String proxyHostHeader() { } @Override - @SuppressWarnings("unchecked") - public Map extensionsOfType(final Class extensionType) { - return (Map) Maps.filterEntries(extensions, valueAssignableFrom(extensionType)); + public ExtensionDeclarations getDeclaredExtensions() { + return extensions; } @Override @@ -629,12 +666,9 @@ public boolean getHttpsRequiredForAdminApi() { return optionSet.has(ADMIN_API_REQUIRE_HTTPS); } - @Override - public NotMatchedRenderer getNotMatchedRenderer() { - return new PlainTextStubNotMatchedRenderer(); - } - - /** @deprecated use {@link BrowserProxySettings#enabled()} */ + /** + * @deprecated use {@link BrowserProxySettings#enabled()} + */ @Deprecated @Override public boolean browserProxyingEnabled() { @@ -650,6 +684,11 @@ public ProxySettings proxyVia() { return NO_PROXY; } + @Override + public Stores getStores() { + return stores; + } + @Override public FileSource filesRoot() { return fileSource; @@ -688,7 +727,7 @@ public Optional maxRequestJournalEntries() { if (specifiesMaxRequestJournalEntries()) { return Optional.of(Integer.parseInt((String) optionSet.valueOf(MAX_ENTRIES_REQUEST_JOURNAL))); } - return Optional.absent(); + return Optional.empty(); } @Override @@ -702,79 +741,78 @@ public int containerThreads() { @Override public String toString() { - ImmutableMap.Builder builder = ImmutableMap.builder(); + Map map = new LinkedHashMap<>(); if (actualHttpPort != null) { - builder.put(PORT, actualHttpPort); + map.put(PORT, actualHttpPort); } if (actualHttpsPort != null) { - builder.put(HTTPS_PORT, actualHttpsPort); + map.put(HTTPS_PORT, actualHttpsPort); } if (httpsSettings().enabled()) { - builder.put(HTTPS_KEYSTORE, nullToString(httpsSettings().keyStorePath())); + map.put(HTTPS_KEYSTORE, nullToString(httpsSettings().keyStorePath())); } - if (!(proxyVia() == NO_PROXY)) { - builder.put(PROXY_VIA, proxyVia()); + if (proxyVia() != NO_PROXY) { + map.put(PROXY_VIA, proxyVia()); } if (proxyUrl() != null) { - builder - .put(PROXY_ALL, nullToString(proxyUrl())) - .put(PRESERVE_HOST_HEADER, shouldPreserveHostHeader()); + map.put(PROXY_ALL, nullToString(proxyUrl())); + map.put(PRESERVE_HOST_HEADER, shouldPreserveHostHeader()); } BrowserProxySettings browserProxySettings = browserProxySettings(); - builder.put(ENABLE_BROWSER_PROXYING, browserProxySettings.enabled()); + map.put(ENABLE_BROWSER_PROXYING, browserProxySettings.enabled()); if (browserProxySettings.enabled()) { KeyStoreSettings keyStoreSettings = browserProxySettings.caKeyStore(); - builder.put(TRUST_ALL_PROXY_TARGETS, browserProxySettings.trustAllProxyTargets()); + map.put(TRUST_ALL_PROXY_TARGETS, browserProxySettings.trustAllProxyTargets()); List trustedProxyTargets = browserProxySettings.trustedProxyTargets(); if (!trustedProxyTargets.isEmpty()) { - builder.put(TRUST_PROXY_TARGET, Joiner.on(", ").join(trustedProxyTargets)); + map.put(TRUST_PROXY_TARGET, String.join(", ", trustedProxyTargets)); } - builder.put(HTTPS_CA_KEYSTORE, keyStoreSettings.path()); - builder.put(HTTPS_CA_KEYSTORE_TYPE, keyStoreSettings.type()); + map.put(HTTPS_CA_KEYSTORE, keyStoreSettings.path()); + map.put(HTTPS_CA_KEYSTORE_TYPE, keyStoreSettings.type()); } - builder.put(DISABLE_BANNER, bannerDisabled()); + map.put(DISABLE_BANNER, bannerDisabled()); if (recordMappingsEnabled()) { - builder.put(RECORD_MAPPINGS, recordMappingsEnabled()).put(MATCH_HEADERS, matchingHeaders()); + map.put(RECORD_MAPPINGS, recordMappingsEnabled()); + map.put(MATCH_HEADERS, matchingHeaders()); } - builder - .put(DISABLE_REQUEST_JOURNAL, requestJournalDisabled()) - .put(VERBOSE, verboseLoggingEnabled()); + map.put(DISABLE_REQUEST_JOURNAL, requestJournalDisabled()); + map.put(VERBOSE, verboseLoggingEnabled()); if (jettySettings().getAcceptQueueSize().isPresent()) { - builder.put(JETTY_ACCEPT_QUEUE_SIZE, jettySettings().getAcceptQueueSize().get()); + map.put(JETTY_ACCEPT_QUEUE_SIZE, jettySettings().getAcceptQueueSize().get()); } if (jettySettings().getAcceptors().isPresent()) { - builder.put(JETTY_ACCEPTOR_THREAD_COUNT, jettySettings().getAcceptors().get()); + map.put(JETTY_ACCEPTOR_THREAD_COUNT, jettySettings().getAcceptors().get()); } if (jettySettings().getRequestHeaderSize().isPresent()) { - builder.put(JETTY_HEADER_BUFFER_SIZE, jettySettings().getRequestHeaderSize().get()); + map.put(JETTY_HEADER_BUFFER_SIZE, jettySettings().getRequestHeaderSize().get()); } if (!(getAdminAuthenticator() instanceof NoAuthenticator)) { - builder.put(ADMIN_API_BASIC_AUTH, "enabled"); + map.put(ADMIN_API_BASIC_AUTH, "enabled"); } if (getHttpsRequiredForAdminApi()) { - builder.put(ADMIN_API_REQUIRE_HTTPS, "true"); + map.put(ADMIN_API_REQUIRE_HTTPS, "true"); } StringBuilder sb = new StringBuilder(); - for (Map.Entry param : builder.build().entrySet()) { + for (Map.Entry param : map.entrySet()) { int paddingLength = 29 - param.getKey().length(); sb.append(param.getKey()) .append(":") - .append(Strings.repeat(" ", paddingLength)) + .append(" ".repeat(Math.max(0, paddingLength))) .append(nullToString(param.getValue())) .append("\n"); } @@ -879,18 +917,41 @@ public BrowserProxySettings browserProxySettings() { .build(); } - private Long getMaxTemplateCacheEntries() { + @Override + public int proxyTimeout() { + return optionSet.has(PROXY_TIMEOUT) + ? Integer.valueOf((String) optionSet.valueOf(PROXY_TIMEOUT)) + : DEFAULT_TIMEOUT; + } + + @Override + public boolean getResponseTemplatingEnabled() { + return optionSet.has(GLOBAL_RESPONSE_TEMPLATING) || optionSet.has(LOCAL_RESPONSE_TEMPLATING); + } + + @Override + public boolean getResponseTemplatingGlobal() { + return optionSet.has(GLOBAL_RESPONSE_TEMPLATING); + } + + @Override + public Long getMaxTemplateCacheEntries() { return optionSet.has(MAX_TEMPLATE_CACHE_ENTRIES) ? Long.valueOf(optionSet.valueOf(MAX_TEMPLATE_CACHE_ENTRIES).toString()) : null; } @SuppressWarnings("unchecked") - @VisibleForTesting - public Set getPermittedSystemKeys() { + @Override + public Set getTemplatePermittedSystemKeys() { return optionSet.has(PERMITTED_SYSTEM_KEYS) - ? ImmutableSet.copyOf((List) optionSet.valuesOf(PERMITTED_SYSTEM_KEYS)) - : Collections.emptySet(); + ? Set.copyOf((List) optionSet.valuesOf(PERMITTED_SYSTEM_KEYS)) + : Collections.emptySet(); + } + + @Override + public boolean getTemplateEscapingDisabled() { + return true; } private boolean isAsynchronousResponseEnabled() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java index 0e4ac8e251..ff9d076ce7 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,18 @@ import static com.github.tomakehurst.wiremock.common.AbstractFileSource.byFileExtension; import static com.github.tomakehurst.wiremock.common.Json.writePrivate; -import static com.google.common.collect.Iterables.any; -import static com.google.common.collect.Iterables.filter; import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.stubbing.StubMappingCollection; import com.github.tomakehurst.wiremock.stubbing.StubMappings; -import com.google.common.base.Predicate; - -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; public class JsonFileMappingsSource implements MappingsSource { @@ -35,9 +37,11 @@ public class JsonFileMappingsSource implements MappingsSource { private final FileSource mappingsFileSource; private final Map fileNameMap; + private final FilenameMaker filenameMaker; - public JsonFileMappingsSource(FileSource mappingsFileSource) { + public JsonFileMappingsSource(FileSource mappingsFileSource, FilenameMaker filenameMaker) { this.mappingsFileSource = mappingsFileSource; + this.filenameMaker = Objects.requireNonNullElseGet(filenameMaker, FilenameMaker::new); fileNameMap = new HashMap<>(); } @@ -61,7 +65,7 @@ public void save(StubMapping stubMapping) { if (folderDefinition != null) { fileMetadata = new StubMappingFileMetadata(folderDefinition.replaceFirst("/", "") + "/" + SafeNames.makeSafeFileName(stubMapping), false); } else { - fileMetadata = new StubMappingFileMetadata(SafeNames.makeSafeFileName(stubMapping), false); + fileMetadata = new StubMappingFileMetadata(filenameMaker.filenameFor(stubMapping), false); } } @@ -103,15 +107,10 @@ public void removeAll() { } private boolean anyFilesAreMultiMapping() { - return any( - fileNameMap.values(), - new Predicate() { - @Override - public boolean apply(StubMappingFileMetadata input) { - return input.multi; + return + fileNameMap.values().stream().anyMatch(input -> input.multi); } - }); - } + @Override public void loadMappingsInto(StubMappings stubMappings) { @@ -119,8 +118,10 @@ public void loadMappingsInto(StubMappings stubMappings) { return; } - Iterable mappingFiles = - filter(mappingsFileSource.listFilesRecursively(), byFileExtension("json")); + List mappingFiles = + mappingsFileSource.listFilesRecursively().stream() + .filter(byFileExtension("json")) + .collect(Collectors.toList()); for (TextFile mappingFile : mappingFiles) { try { StubMappingCollection stubCollection = diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java index ba55b76ea5..8bdf835181 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import static com.github.tomakehurst.wiremock.common.AbstractFileSource.byFileExtension; import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; import static com.github.tomakehurst.wiremock.core.WireMockApp.MAPPINGS_ROOT; -import static com.google.common.collect.Iterables.filter; import static org.apache.commons.lang3.StringUtils.substringAfterLast; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; @@ -26,10 +25,15 @@ import com.github.tomakehurst.wiremock.common.BinaryFile; import com.github.tomakehurst.wiremock.common.ContentTypes; import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.JsonException; import com.github.tomakehurst.wiremock.common.TextFile; import com.github.tomakehurst.wiremock.http.ContentTypeHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.stubbing.StubMappingCollection; +import java.util.List; +import java.util.stream.Collectors; public class RemoteMappingsLoader { @@ -44,12 +48,21 @@ public RemoteMappingsLoader(FileSource fileSource, WireMock wireMock) { } public void load() { - Iterable mappingFiles = - filter(mappingsFileSource.listFilesRecursively(), byFileExtension("json")); + List mappingFiles = + mappingsFileSource.listFilesRecursively().stream() + .filter(byFileExtension("json")) + .collect(Collectors.toList()); for (TextFile mappingFile : mappingFiles) { - StubMapping mapping = StubMapping.buildFrom(mappingFile.readContentsAsString()); - convertBodyFromFileIfNecessary(mapping); - wireMock.register(mapping); + try { + StubMappingCollection stubCollection = + Json.read(mappingFile.readContentsAsString(), StubMappingCollection.class); + for (StubMapping mapping : stubCollection.getMappingOrMappings()) { + convertBodyFromFileIfNecessary(mapping); + wireMock.register(mapping); + } + } catch (JsonException e) { + throw new MappingFileException(mappingFile.getPath(), e.getErrors().first().getDetail()); + } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java index 6f453ab5de..0c66390a29 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockApp.MAPPINGS_ROOT; import static com.github.tomakehurst.wiremock.http.RequestMethod.ANY; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; +import static java.lang.System.err; import static java.lang.System.out; import com.github.tomakehurst.wiremock.WireMockServer; @@ -29,23 +30,29 @@ import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.github.tomakehurst.wiremock.stubbing.StubMappings; +import java.io.PrintStream; public class WireMockServerRunner { private static final String BANNER = - " /$$ /$$ /$$ /$$ /$$ /$$ \n" - + "| $$ /$ | $$|__/ | $$$ /$$$ | $$ \n" - + "| $$ /$$$| $$ /$$ /$$$$$$ /$$$$$$ | $$$$ /$$$$ /$$$$$$ /$$$$$$$| $$ /$$\n" - + "| $$/$$ $$ $$| $$ /$$__ $$ /$$__ $$| $$ $$/$$ $$ /$$__ $$ /$$_____/| $$ /$$/\n" - + "| $$$$_ $$$$| $$| $$ \\__/| $$$$$$$$| $$ $$$| $$| $$ \\ $$| $$ | $$$$$$/ \n" - + "| $$$/ \\ $$$| $$| $$ | $$_____/| $$\\ $ | $$| $$ | $$| $$ | $$_ $$ \n" - + "| $$/ \\ $$| $$| $$ | $$$$$$$| $$ \\/ | $$| $$$$$$/| $$$$$$$| $$ \\ $$\n" - + "|__/ \\__/|__/|__/ \\_______/|__/ |__/ \\______/ \\_______/|__/ \\__/"; + "\n" + + "\u001B[34m██ ██ ██ ██████ ███████ \u001B[33m███ ███ ██████ ██████ ██ ██ \n" + + "\u001B[34m██ ██ ██ ██ ██ ██ \u001B[33m████ ████ ██ ██ ██ ██ ██ \n" + + "\u001B[34m██ █ ██ ██ ██████ █████ \u001B[33m██ ████ ██ ██ ██ ██ █████ \n" + + "\u001B[34m██ ███ ██ ██ ██ ██ ██ \u001B[33m██ ██ ██ ██ ██ ██ ██ ██ \n" + + "\u001B[34m ███ ███ ██ ██ ██ ███████ \u001B[33m██ ██ ██████ ██████ ██ ██ \n" + + "\n\u001B[0m" + + "----------------------------------------------------------------\n" + + "| Cloud: https://wiremock.io/cloud |\n" + + "| |\n" + + "| Slack: https://slack.wiremock.org |\n" + + "----------------------------------------------------------------"; private WireMockServer wireMockServer; public void run(String... args) { + suppressSlf4jWarnings(); + CommandLineOptions options = new CommandLineOptions(args); if (options.help()) { out.println(options.helpText()); @@ -95,11 +102,35 @@ public void run(String... args) { } } + private static void suppressSlf4jWarnings() { + System.setErr( + new PrintStream(err) { + @Override + public void println(String s) { + if (!s.startsWith("SLF4J")) { + super.println(s); + } + } + + @Override + public void println(char[] chars) { + if (!new String(chars).startsWith("SLF4J")) { + super.println(chars); + } + } + + @Override + public void println(Object o) { + if (!o.toString().startsWith("SLF4J")) { + super.println(o); + } + } + }); + } + private void addProxyMapping(final String baseUrl) { wireMockServer.loadMappingsUsing( - new MappingsLoader() { - @Override - public void loadMappingsInto(StubMappings stubMappings) { + stubMappings -> { RequestPattern requestPattern = newRequestPattern(ANY, anyUrl()).build(); ResponseDefinition responseDef = responseDefinition().proxiedFrom(baseUrl).build(); @@ -108,7 +139,7 @@ public void loadMappingsInto(StubMappings stubMappings) { 10); // Make it low priority so that existing stubs will take precedence stubMappings.addMapping(proxyBasedMapping); } - }); + ); } public void stop() { @@ -128,8 +159,4 @@ public boolean isRunning() { public int port() { return wireMockServer.port(); } - - public static void main(String... args) { - new WireMockServerRunner().run(args); - } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/BlobStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/BlobStore.java new file mode 100644 index 0000000000..0568f772e8 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/BlobStore.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.common.InputStreamSource; +import java.io.InputStream; +import java.util.Optional; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface BlobStore extends Store { + + Optional getStream(String key); + + InputStreamSource getStreamSource(String key); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/DefaultStores.java b/src/main/java/com/github/tomakehurst/wiremock/store/DefaultStores.java new file mode 100644 index 0000000000..381e2b4d93 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/DefaultStores.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.store.files.FileSourceBlobStore; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class DefaultStores implements Stores { + + private final FileSource fileRoot; + + private final StubMappingStore stubMappingStore; + private final RequestJournalStore requestJournalStore; + private final SettingsStore settingsStore; + + private final ScenariosStore scenariosStore; + + public DefaultStores(FileSource fileRoot) { + this.fileRoot = fileRoot; + + this.stubMappingStore = new InMemoryStubMappingStore(); + this.requestJournalStore = new InMemoryRequestJournalStore(); + this.settingsStore = new InMemorySettingsStore(); + this.scenariosStore = new InMemoryScenariosStore(); + } + + @Override + public StubMappingStore getStubStore() { + return stubMappingStore; + } + + @Override + public RequestJournalStore getRequestJournalStore() { + return requestJournalStore; + } + + @Override + public SettingsStore getSettingsStore() { + return settingsStore; + } + + @Override + public ScenariosStore getScenariosStore() { + return scenariosStore; + } + + @Override + public RecorderStateStore getRecorderStateStore() { + return new InMemoryRecorderStateStore(); + } + + @Override + public BlobStore getBlobStore(String name) { + return new FileSourceBlobStore(fileRoot.child(name)); + } + + @Override + public void start() {} + + @Override + public void stop() {} +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRecorderStateStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRecorderStateStore.java new file mode 100644 index 0000000000..c2d63cf2ef --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRecorderStateStore.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.recording.RecorderState; +import java.util.concurrent.atomic.AtomicReference; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class InMemoryRecorderStateStore implements RecorderStateStore { + + private final AtomicReference store; + + public InMemoryRecorderStateStore() { + this.store = new AtomicReference<>(RecorderState.initial()); + } + + @Override + public RecorderState get() { + return store.get(); + } + + @Override + public void set(RecorderState state) { + store.set(state); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRequestJournalStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRequestJournalStore.java new file mode 100644 index 0000000000..7bfd1d8b35 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryRequestJournalStore.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class InMemoryRequestJournalStore implements RequestJournalStore { + + private final Deque deque = new ConcurrentLinkedDeque<>(); + private final Map serveEvents = new ConcurrentHashMap<>(); + + @Override + public void add(ServeEvent event) { + serveEvents.put(event.getId(), event); + deque.addFirst(event.getId()); + } + + @Override + public Stream getAll() { + return deque.stream().map(serveEvents::get); + } + + @Override + public void removeLast() { + final UUID id = deque.pollLast(); + if (id != null) { + serveEvents.remove(id); + } + } + + @Override + public Stream getAllKeys() { + return getAll().map(ServeEvent::getId); + } + + @Override + public Optional get(UUID id) { + return Optional.ofNullable(serveEvents.get(id)); + } + + @Override + public void put(UUID id, ServeEvent event) { + if (deque.contains(id)) { + serveEvents.put(id, event); + } + } + + @Override + public void remove(UUID id) { + deque.stream().filter(eventId -> eventId.equals(id)).forEach(deque::remove); + serveEvents.remove(id); + } + + @Override + public void clear() { + deque.clear(); + serveEvents.clear(); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryScenariosStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryScenariosStore.java new file mode 100644 index 0000000000..70c282a6a9 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryScenariosStore.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.stubbing.Scenario; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class InMemoryScenariosStore implements ScenariosStore { + + private final ConcurrentHashMap scenarioMap = new ConcurrentHashMap<>(); + + @Override + public Stream getAllKeys() { + return scenarioMap.keySet().stream(); + } + + @Override + public Stream getAll() { + return scenarioMap.values().stream(); + } + + @Override + public Optional get(String key) { + return Optional.ofNullable(scenarioMap.get(key)); + } + + @Override + public void put(String key, Scenario content) { + scenarioMap.put(key, content); + } + + @Override + public void remove(String key) { + scenarioMap.remove(key); + } + + @Override + public void clear() { + scenarioMap.clear(); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/InMemorySettingsStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/InMemorySettingsStore.java new file mode 100644 index 0000000000..d8dbb6745f --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/InMemorySettingsStore.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.global.GlobalSettings; +import java.util.concurrent.atomic.AtomicReference; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class InMemorySettingsStore implements SettingsStore { + + private AtomicReference holder = new AtomicReference<>(GlobalSettings.defaults()); + + @Override + public GlobalSettings get() { + return holder.get(); + } + + @Override + public void set(GlobalSettings newSettings) { + holder.set(newSettings); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryStubMappingStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryStubMappingStore.java new file mode 100644 index 0000000000..bfbc0f5ab7 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/InMemoryStubMappingStore.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.stubbing.SortedConcurrentMappingSet; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class InMemoryStubMappingStore implements StubMappingStore { + + private final SortedConcurrentMappingSet mappings = new SortedConcurrentMappingSet(); + + @Override + public Optional get(UUID id) { + return mappings.stream().filter(stubMapping -> stubMapping.getId().equals(id)).findFirst(); + } + + @Override + public void remove(StubMapping stubMapping) { + mappings.remove(stubMapping); + } + + @Override + public void clear() { + mappings.clear(); + } + + @Override + public Stream getAll() { + return mappings.stream(); + } + + @Override + public void add(StubMapping stubMapping) { + mappings.add(stubMapping); + } + + @Override + public void replace(StubMapping existing, StubMapping updated) { + mappings.replace(existing, updated); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/RecorderStateStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/RecorderStateStore.java new file mode 100644 index 0000000000..ae81e43109 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/RecorderStateStore.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.recording.RecorderState; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface RecorderStateStore { + RecorderState get(); + + void set(RecorderState state); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/RequestJournalStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/RequestJournalStore.java new file mode 100644 index 0000000000..6672e2f997 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/RequestJournalStore.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import java.util.UUID; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface RequestJournalStore extends Store { + + Stream getAll(); + + void add(ServeEvent event); + + void removeLast(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/ScenariosStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/ScenariosStore.java new file mode 100644 index 0000000000..5ad6aa2c72 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/ScenariosStore.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.stubbing.Scenario; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface ScenariosStore extends Store { + + Stream getAll(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/SettingsStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/SettingsStore.java new file mode 100644 index 0000000000..a138c59aa8 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/SettingsStore.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.global.GlobalSettings; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface SettingsStore { + + GlobalSettings get(); + + void set(GlobalSettings newSettings); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/Store.java b/src/main/java/com/github/tomakehurst/wiremock/store/Store.java new file mode 100644 index 0000000000..67d431dc18 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/Store.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import java.util.Optional; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface Store { + + Stream getAllKeys(); + + Optional get(K key); + + void put(K key, V content); + + void remove(K key); + + void clear(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/Stores.java b/src/main/java/com/github/tomakehurst/wiremock/store/Stores.java new file mode 100644 index 0000000000..32cedab26a --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/Stores.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; +import static com.github.tomakehurst.wiremock.core.WireMockApp.MAPPINGS_ROOT; + +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface Stores extends StoresLifecycle { + + StubMappingStore getStubStore(); + + RequestJournalStore getRequestJournalStore(); + + SettingsStore getSettingsStore(); + + ScenariosStore getScenariosStore(); + + RecorderStateStore getRecorderStateStore(); + + default BlobStore getMappingsBlobStore() { + return getBlobStore(MAPPINGS_ROOT); + } + + default BlobStore getFilesBlobStore() { + return getBlobStore(FILES_ROOT); + } + + BlobStore getBlobStore(String name); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/StoresLifecycle.java b/src/main/java/com/github/tomakehurst/wiremock/store/StoresLifecycle.java new file mode 100644 index 0000000000..9ecb3e6e9f --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/StoresLifecycle.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface StoresLifecycle { + + void start(); + + void stop(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/StubMappingStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/StubMappingStore.java new file mode 100644 index 0000000000..4131c1cd3d --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/StubMappingStore.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store; + +import com.github.tomakehurst.wiremock.common.Pair; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public interface StubMappingStore { + + Stream getAll(); + + Optional get(UUID id); + + default Stream findAllMatchingRequest( + Request request, + Map customMatchers, + Consumer subEventConsumer) { + return getAll() + .map( + stubMapping -> + Pair.pair(stubMapping, stubMapping.getRequest().match(request, customMatchers))) + .peek(stubAndMatchResult -> stubAndMatchResult.b.getSubEvents().forEach(subEventConsumer)) + .filter(stubAndMatchResult -> stubAndMatchResult.b.isExactMatch()) + .map(stubAndMatchResult -> stubAndMatchResult.a); + } + + void add(StubMapping stub); + + void replace(StubMapping existing, StubMapping updated); + + void remove(StubMapping stubMapping); + + void clear(); +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreBinaryFile.java b/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreBinaryFile.java new file mode 100644 index 0000000000..057070bbc1 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreBinaryFile.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store.files; + +import com.github.tomakehurst.wiremock.admin.NotFoundException; +import com.github.tomakehurst.wiremock.common.BinaryFile; +import com.github.tomakehurst.wiremock.store.BlobStore; +import java.io.InputStream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class BlobStoreBinaryFile extends BinaryFile { + + private final BlobStore blobStore; + private final String path; + + public BlobStoreBinaryFile(BlobStore blobStore, String path) { + super(null); + this.blobStore = blobStore; + this.path = path; + } + + @Override + public byte[] readContents() { + return blobStore.get(path).orElseThrow(() -> new NotFoundException(path + " not found")); + } + + @Override + public String name() { + return path; + } + + @Override + public String toString() { + return name(); + } + + @Override + public InputStream getStream() { + return blobStore.getStream(path).orElseThrow(() -> new NotFoundException(path + " not found")); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSource.java b/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSource.java new file mode 100644 index 0000000000..a202ff0dde --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSource.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store.files; + +import com.github.tomakehurst.wiremock.common.BinaryFile; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.Strings; +import com.github.tomakehurst.wiremock.common.TextFile; +import com.github.tomakehurst.wiremock.store.BlobStore; +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class BlobStoreFileSource implements FileSource { + + private final BlobStore blobStore; + + public BlobStoreFileSource(BlobStore blobStore) { + this.blobStore = blobStore; + } + + @Override + public BinaryFile getBinaryFileNamed(String name) { + return new BlobStoreBinaryFile(blobStore, name); + } + + @Override + public TextFile getTextFileNamed(String name) { + return new BlobStoreTextFile(blobStore, name); + } + + @Override + public void createIfNecessary() {} + + @Override + public FileSource child(String subDirectoryName) { + return this; + } + + @Override + public String getPath() { + return ""; + } + + @Override + public URI getUri() { + return null; + } + + @Override + public List listFilesRecursively() { + return blobStore + .getAllKeys() + .map(path -> new BlobStoreTextFile(blobStore, path)) + .collect(Collectors.toList()); + } + + @Override + public void writeTextFile(String name, String contents) { + blobStore.put(name, Strings.bytesFromString(contents)); + } + + @Override + public void writeBinaryFile(String name, byte[] contents) { + blobStore.put(name, contents); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public void deleteFile(String name) { + blobStore.remove(name); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreTextFile.java b/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreTextFile.java new file mode 100644 index 0000000000..0910f97dc8 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/files/BlobStoreTextFile.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store.files; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.github.tomakehurst.wiremock.admin.NotFoundException; +import com.github.tomakehurst.wiremock.common.TextFile; +import com.github.tomakehurst.wiremock.store.BlobStore; +import java.io.InputStream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class BlobStoreTextFile extends TextFile { + + private final BlobStore blobStore; + private final String path; + + public BlobStoreTextFile(BlobStore blobStore, String path) { + super(null); + this.blobStore = blobStore; + this.path = path; + } + + @Override + public byte[] readContents() { + return blobStore.get(path).orElseThrow(() -> new NotFoundException(path + " not found")); + } + + @Override + public String name() { + return path; + } + + @Override + public String toString() { + return name(); + } + + @Override + public InputStream getStream() { + return blobStore.getStream(path).orElseThrow(() -> new NotFoundException(path + " not found")); + } + + @Override + public String readContentsAsString() { + return new String(readContents(), UTF_8); + } + + @Override + public String getPath() { + return path; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/store/files/FileSourceBlobStore.java b/src/main/java/com/github/tomakehurst/wiremock/store/files/FileSourceBlobStore.java new file mode 100644 index 0000000000..5e68093915 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/store/files/FileSourceBlobStore.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store.files; + +import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.store.BlobStore; +import java.io.InputStream; +import java.util.Optional; +import java.util.stream.Stream; +import org.wiremock.annotations.Beta; + +@Beta(justification = "Externalized State API: https://github.com/wiremock/wiremock/issues/2144") +public class FileSourceBlobStore implements BlobStore { + + private final FileSource fileSource; + + FileSourceBlobStore(String root) { + this.fileSource = new SingleRootFileSource(root); + } + + public FileSourceBlobStore(FileSource fileSource) { + this.fileSource = fileSource; + } + + @Override + public Optional getStream(String key) { + return Optional.of(fileSource.getBinaryFileNamed(key).getStream()); + } + + @Override + public InputStreamSource getStreamSource(String key) { + return StreamSources.forBlobStoreItem(this, key); + } + + @Override + public Stream getAllKeys() { + return fileSource.listFilesRecursively().stream().map(TextFile::getPath); + } + + @Override + public Optional get(String key) { + return Optional.of(fileSource.getBinaryFileNamed(key).readContents()); + } + + @Override + public void put(String key, byte[] content) { + fileSource.writeBinaryFile(key, content); + } + + @Override + public void remove(String key) { + fileSource.deleteFile(key); + } + + @Override + public void clear() { + fileSource.listFilesRecursively().forEach(file -> fileSource.deleteFile(file.getPath())); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java new file mode 100644 index 0000000000..666e894adb --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2017-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.stubbing; + +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; +import static java.util.stream.Collectors.toList; + +import com.github.tomakehurst.wiremock.admin.NotFoundException; +import com.github.tomakehurst.wiremock.store.ScenariosStore; +import java.util.List; + +public abstract class AbstractScenarios implements Scenarios { + + private final ScenariosStore store; + + public AbstractScenarios(ScenariosStore store) { + this.store = store; + } + + @Override + public Scenario getByName(String name) { + return store.get(name).orElse(null); + } + + @Override + public List getAll() { + return store.getAll().collect(toList()); + } + + @Override + public void onStubMappingAdded(StubMapping mapping) { + if (mapping.isInScenario()) { + String scenarioName = mapping.getScenarioName(); + Scenario scenario = + getFirstNonNull( + store.get(scenarioName).orElse(null), Scenario.inStartedState(scenarioName)) + .withStubMapping(mapping); + store.put(scenarioName, scenario); + } + } + + @Override + public void onStubMappingUpdated(StubMapping oldMapping, StubMapping newMapping) { + if (oldMapping.isInScenario() + && !oldMapping.getScenarioName().equals(newMapping.getScenarioName())) { + Scenario scenarioForOldMapping = + store + .get(oldMapping.getScenarioName()) + .map(scenario -> scenario.withoutStubMapping(oldMapping)) + .orElseThrow(IllegalStateException::new); + + if (scenarioForOldMapping.getMappings().isEmpty()) { + store.remove(scenarioForOldMapping.getId()); + } else { + store.put(oldMapping.getScenarioName(), scenarioForOldMapping); + } + } + + if (newMapping.isInScenario()) { + String scenarioName = newMapping.getScenarioName(); + Scenario scenario = + getFirstNonNull( + store.get(scenarioName).orElse(null), Scenario.inStartedState(scenarioName)) + .withStubMapping(newMapping); + store.put(scenarioName, scenario); + } + } + + @Override + public void onStubMappingRemoved(StubMapping mapping) { + if (mapping.isInScenario()) { + final String scenarioName = mapping.getScenarioName(); + Scenario scenario = + store + .get(scenarioName) + .orElseThrow(IllegalStateException::new) + .withoutStubMapping(mapping); + + if (scenario.getMappings().isEmpty()) { + store.remove(scenarioName); + } else { + store.put(scenarioName, scenario); + } + } + } + + @Override + public void onStubServed(StubMapping mapping) { + if (mapping.isInScenario()) { + final String scenarioName = mapping.getScenarioName(); + Scenario scenario = store.get(scenarioName).orElseThrow(IllegalStateException::new); + if (mapping.modifiesScenarioState() + && (mapping.getRequiredScenarioState() == null + || scenario.getState().equals(mapping.getRequiredScenarioState()))) { + Scenario newScenario = scenario.setState(mapping.getNewScenarioState()); + store.put(scenarioName, newScenario); + } + } + } + + @Override + public void reset() { + store.getAll().map(Scenario::reset).forEach(scenario -> store.put(scenario.getId(), scenario)); + } + + @Override + public void resetSingle(String name) { + setSingleScenarioState(name, Scenario::reset); + } + + @Override + public void setSingle(String name, String state) { + setSingleScenarioState(name, scenario -> scenario.setState(state)); + } + + private void setSingleScenarioState( + String name, java.util.function.Function fn) { + Scenario scenario = + store + .get(name) + .orElseThrow(() -> new NotFoundException("Scenario " + name + " does not exist")); + + store.put(name, fn.apply(scenario)); + } + + @Override + public void clear() { + store.clear(); + } + + @Override + public boolean mappingMatchesScenarioState(StubMapping mapping) { + String currentScenarioState = getByName(mapping.getScenarioName()).getState(); + return mapping.getRequiredScenarioState().equals(currentScenarioState); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractStubMappings.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractStubMappings.java new file mode 100644 index 0000000000..afb3a5bb71 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractStubMappings.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.stubbing; + +import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; +import static com.github.tomakehurst.wiremock.common.Pair.pair; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; +import static com.github.tomakehurst.wiremock.http.ResponseDefinition.copyOf; +import static java.util.stream.Collectors.toList; + +import com.github.tomakehurst.wiremock.admin.NotFoundException; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.Pair; +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; +import com.github.tomakehurst.wiremock.extension.StubLifecycleListener; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.StubMappingStore; +import com.github.tomakehurst.wiremock.store.files.BlobStoreFileSource; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import java.util.*; + +public abstract class AbstractStubMappings implements StubMappings { + + protected final Scenarios scenarios; + protected final Map customMatchers; + protected final Map transformers; + protected final Map v2transformers; + protected final FileSource filesFileSource; + protected final List stubLifecycleListeners; + protected final StubMappingStore store; + + public AbstractStubMappings( + StubMappingStore store, + Scenarios scenarios, + Map customMatchers, + Map transformers, + Map v2transformers, + BlobStore filesBlobStore, + List stubLifecycleListeners) { + + this.store = store; + this.scenarios = scenarios; + this.customMatchers = customMatchers; + this.transformers = transformers; + this.v2transformers = v2transformers; + this.filesFileSource = new BlobStoreFileSource(filesBlobStore); + this.stubLifecycleListeners = stubLifecycleListeners; + } + + @Override + public ServeEvent serveFor(ServeEvent initialServeEvent) { + final LoggedRequest request = initialServeEvent.getRequest(); + + final List subEvents = new LinkedList<>(); + + StubMapping matchingMapping = + store + .findAllMatchingRequest(request, customMatchers, subEvents::add) + .filter( + stubMapping -> + stubMapping.isIndependentOfScenarioState() + || scenarios.mappingMatchesScenarioState(stubMapping)) + .findFirst() + .orElse(StubMapping.NOT_CONFIGURED); + + subEvents.forEach(initialServeEvent::appendSubEvent); + + scenarios.onStubServed(matchingMapping); + + ResponseDefinition responseDefinition = + applyV1Transformations( + request, matchingMapping.getResponse(), List.copyOf(transformers.values())); + + ServeEvent serveEvent = + initialServeEvent + .withStubMapping(matchingMapping) + .withResponseDefinition(responseDefinition); + + final Pair transformed = + applyV2Transformations(serveEvent, List.copyOf(v2transformers.values())); + serveEvent = transformed.a; + responseDefinition = transformed.b; + + return serveEvent.withResponseDefinition(copyOf(responseDefinition)); + } + + private ResponseDefinition applyV1Transformations( + Request request, + ResponseDefinition responseDefinition, + List transformers) { + + if (transformers.isEmpty()) { + return responseDefinition; + } + + ResponseDefinitionTransformer transformer = transformers.get(0); + ResponseDefinition newResponseDef = + transformer.applyGlobally() || responseDefinition.hasTransformer(transformer) + ? transformer.transform( + request, + responseDefinition, + filesFileSource, + getFirstNonNull(responseDefinition.getTransformerParameters(), Parameters.empty())) + : responseDefinition; + + return applyV1Transformations( + request, newResponseDef, transformers.subList(1, transformers.size())); + } + + private Pair applyV2Transformations( + ServeEvent serveEvent, List transformers) { + + final ResponseDefinition responseDefinition = serveEvent.getResponseDefinition(); + + if (transformers.isEmpty()) { + return pair(serveEvent, responseDefinition); + } + + ResponseDefinitionTransformerV2 transformer = transformers.get(0); + ResponseDefinition newResponseDef = + transformer.applyGlobally() || responseDefinition.hasTransformer(transformer) + ? transformer.transform(serveEvent) + : responseDefinition; + + return applyV2Transformations( + serveEvent.withResponseDefinition(newResponseDef), + transformers.subList(1, transformers.size())); + } + + @Override + public void addMapping(StubMapping mapping) { + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.beforeStubCreated(mapping); + } + + store.add(mapping); + scenarios.onStubMappingAdded(mapping); + + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.afterStubCreated(mapping); + } + } + + @Override + public void removeMapping(StubMapping mapping) { + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.beforeStubRemoved(mapping); + } + + store.remove(mapping); + scenarios.onStubMappingRemoved(mapping); + + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.afterStubRemoved(mapping); + } + } + + @Override + public void editMapping(StubMapping stubMapping) { + final Optional optionalExistingMapping = store.get(stubMapping.getId()); + + if (optionalExistingMapping.isEmpty()) { + String msg = "StubMapping with UUID: " + stubMapping.getUuid() + " not found"; + notifier().error(msg); + throw new NotFoundException(msg); + } + + final StubMapping existingMapping = optionalExistingMapping.get(); + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.beforeStubEdited(existingMapping, stubMapping); + } + + stubMapping.setInsertionIndex(existingMapping.getInsertionIndex()); + stubMapping.setDirty(true); + + store.replace(existingMapping, stubMapping); + scenarios.onStubMappingUpdated(existingMapping, stubMapping); + + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.afterStubEdited(existingMapping, stubMapping); + } + } + + @Override + public void reset() { + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.beforeStubsReset(); + } + + store.clear(); + scenarios.clear(); + + for (StubLifecycleListener listener : stubLifecycleListeners) { + listener.afterStubsReset(); + } + } + + @Override + public void resetScenarios() { + scenarios.reset(); + } + + @Override + public List getAll() { + return store.getAll().collect(toList()); + } + + @Override + public Optional get(final UUID id) { + return store.get(id); + } + + @Override + public List getAllScenarios() { + return scenarios.getAll(); + } + + @Override + public List findByMetadata(final StringValuePattern pattern) { + return store + .getAll() + .filter( + stubMapping -> { + String metadataJson = Json.write(stubMapping.getMetadata()); + return pattern.match(metadataJson).isExactMatch(); + }) + .collect(toList()); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java new file mode 100644 index 0000000000..a205109696 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.stubbing; + +import com.github.tomakehurst.wiremock.store.InMemoryScenariosStore; +import com.github.tomakehurst.wiremock.store.ScenariosStore; + +public class InMemoryScenarios extends AbstractScenarios { + + public InMemoryScenarios(ScenariosStore store) { + super(store); + } + + public InMemoryScenarios() { + this(new InMemoryScenariosStore()); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappings.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappings.java index 8dba2dfa0e..a0b123b260 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappings.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,232 +15,21 @@ */ package com.github.tomakehurst.wiremock.stubbing; -import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; -import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; -import static com.github.tomakehurst.wiremock.http.ResponseDefinition.copyOf; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.find; -import static com.google.common.collect.Iterables.tryFind; - -import com.github.tomakehurst.wiremock.common.FileSource; -import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.common.SingleRootFileSource; -import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer; -import com.github.tomakehurst.wiremock.extension.StubLifecycleListener; -import com.github.tomakehurst.wiremock.http.Request; -import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; -import com.github.tomakehurst.wiremock.matching.StringValuePattern; -import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; -import java.util.*; - -public class InMemoryStubMappings implements StubMappings { - - private final SortedConcurrentMappingSet mappings = new SortedConcurrentMappingSet(); - private final Scenarios scenarios; - private final Map customMatchers; - private final Map transformers; - private final FileSource rootFileSource; - private final List stubLifecycleListeners; +import com.github.tomakehurst.wiremock.store.InMemoryStubMappingStore; +import com.github.tomakehurst.wiremock.store.files.FileSourceBlobStore; +import java.util.Collections; - public InMemoryStubMappings( - Scenarios scenarios, - Map customMatchers, - Map transformers, - FileSource rootFileSource, - List stubLifecycleListeners) { - this.scenarios = scenarios; - this.customMatchers = customMatchers; - this.transformers = transformers; - this.rootFileSource = rootFileSource; - this.stubLifecycleListeners = stubLifecycleListeners; - } +public class InMemoryStubMappings extends StoreBackedStubMappings { public InMemoryStubMappings() { - this( - new Scenarios(), - Collections.emptyMap(), - Collections.emptyMap(), - new SingleRootFileSource("."), - Collections.emptyList()); - } - - @Override - public ServeEvent serveFor(Request request) { - StubMapping matchingMapping = - find( - mappings, - mappingMatchingAndInCorrectScenarioState(request), - StubMapping.NOT_CONFIGURED); - - scenarios.onStubServed(matchingMapping); - - ResponseDefinition responseDefinition = - applyTransformations( - request, matchingMapping.getResponse(), ImmutableList.copyOf(transformers.values())); - - return ServeEvent.of( - LoggedRequest.createFrom(request), copyOf(responseDefinition), matchingMapping); - } - - private ResponseDefinition applyTransformations( - Request request, - ResponseDefinition responseDefinition, - List transformers) { - if (transformers.isEmpty()) { - return responseDefinition; - } - - ResponseDefinitionTransformer transformer = transformers.get(0); - ResponseDefinition newResponseDef = - transformer.applyGlobally() || responseDefinition.hasTransformer(transformer) - ? transformer.transform( - request, - responseDefinition, - rootFileSource.child(FILES_ROOT), - firstNonNull(responseDefinition.getTransformerParameters(), Parameters.empty())) - : responseDefinition; - - return applyTransformations( - request, newResponseDef, transformers.subList(1, transformers.size())); - } - - @Override - public void addMapping(StubMapping mapping) { - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.beforeStubCreated(mapping); - } - - mappings.add(mapping); - scenarios.onStubMappingAdded(mapping); - - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.afterStubCreated(mapping); - } - } - - @Override - public void removeMapping(StubMapping mapping) { - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.beforeStubRemoved(mapping); - } - - mappings.remove(mapping); - scenarios.onStubMappingRemoved(mapping); - - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.afterStubRemoved(mapping); - } - } - - @Override - public void editMapping(StubMapping stubMapping) { - final Optional optionalExistingMapping = - tryFind(mappings, mappingMatchingUuid(stubMapping.getUuid())); - - if (!optionalExistingMapping.isPresent()) { - String msg = "StubMapping with UUID: " + stubMapping.getUuid() + " not found"; - notifier().error(msg); - throw new RuntimeException(msg); - } - - final StubMapping existingMapping = optionalExistingMapping.get(); - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.beforeStubEdited(existingMapping, stubMapping); - } - - stubMapping.setInsertionIndex(existingMapping.getInsertionIndex()); - stubMapping.setDirty(true); - - mappings.replace(existingMapping, stubMapping); - scenarios.onStubMappingUpdated(existingMapping, stubMapping); - - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.afterStubEdited(existingMapping, stubMapping); - } - } - - @Override - public void reset() { - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.beforeStubsReset(); - } - - mappings.clear(); - scenarios.clear(); - - for (StubLifecycleListener listener : stubLifecycleListeners) { - listener.afterStubsReset(); - } - } - - @Override - public void resetScenarios() { - scenarios.reset(); - } - - @Override - public List getAll() { - return ImmutableList.copyOf(mappings); - } - - @Override - public Optional get(final UUID id) { - return tryFind( - mappings, - new Predicate() { - @Override - public boolean apply(StubMapping input) { - return input.getUuid().equals(id); - } - }); - } - - @Override - public List getAllScenarios() { - return scenarios.getAll(); - } - - @Override - public List findByMetadata(final StringValuePattern pattern) { - return from(mappings) - .filter( - new Predicate() { - @Override - public boolean apply(StubMapping stub) { - String metadataJson = Json.write(stub.getMetadata()); - return pattern.match(metadataJson).isExactMatch(); - } - }) - .toList(); - } - - private Predicate mappingMatchingAndInCorrectScenarioState(final Request request) { - return mappingMatchingAndInCorrectScenarioStateNew(request); - } - - private Predicate mappingMatchingAndInCorrectScenarioStateNew( - final Request request) { - return new Predicate() { - public boolean apply(StubMapping mapping) { - return mapping.getRequest().match(request, customMatchers).isExactMatch() - && (mapping.isIndependentOfScenarioState() - || scenarios.mappingMatchesScenarioState(mapping)); - } - }; - } - - private Predicate mappingMatchingUuid(final UUID uuid) { - return new Predicate() { - @Override - public boolean apply(StubMapping input) { - return input.getUuid().equals(uuid); - } - }; + super( + new InMemoryStubMappingStore(), + new InMemoryScenarios(), + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap(), + new FileSourceBlobStore(new SingleRootFileSource(".")), + Collections.emptyList()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenario.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenario.java index 9b9f8d1cb1..ecb4b005d8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenario.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenario.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2022 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,16 @@ */ package com.github.tomakehurst.wiremock.stubbing; -import static com.google.common.collect.FluentIterable.from; +import static java.util.stream.Collectors.toSet; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.InvalidInputException; import com.github.tomakehurst.wiremock.common.Json; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; public class Scenario { @@ -72,27 +68,15 @@ public String getState() { } public Set getPossibleStates() { - FluentIterable requiredStates = - from(stubMappings) - .transform( - new Function() { - @Override - public String apply(StubMapping mapping) { - return mapping.getRequiredScenarioState(); - } - }); - - return from(stubMappings) - .transform( - new Function() { - @Override - public String apply(StubMapping mapping) { - return mapping.getNewScenarioState(); - } - }) - .append(requiredStates) - .filter(Predicates.notNull()) - .toSet(); + List requiredStates = + stubMappings.stream() + .map(StubMapping::getRequiredScenarioState) + .collect(Collectors.toList()); + + requiredStates.addAll( + stubMappings.stream().map(StubMapping::getNewScenarioState).collect(Collectors.toList())); + + return requiredStates.stream().filter(Objects::nonNull).collect(Collectors.toSet()); } public Set getMappings() { @@ -113,14 +97,17 @@ Scenario reset() { } Scenario withStubMapping(StubMapping stubMapping) { - Set newMappings = - ImmutableSet.builder().addAll(stubMappings).add(stubMapping).build(); + Set newMappings = new LinkedHashSet<>(stubMappings); + newMappings.add(stubMapping); return new Scenario(id, state, newMappings); } Scenario withoutStubMapping(StubMapping stubMapping) { - Set newMappings = Sets.difference(stubMappings, ImmutableSet.of(stubMapping)); + Set newMappings = + stubMappings.stream() + .filter(stub -> !stub.getId().equals(stubMapping.getId())) + .collect(toSet()); return new Scenario(id, state, newMappings); } @@ -144,12 +131,7 @@ public int hashCode() { return Objects.hash(getId(), getState(), getMappings()); } - public static final Predicate withName(final String name) { - return new Predicate() { - @Override - public boolean apply(Scenario input) { - return input.getId().equals(name); - } - }; + public static Predicate withName(final String name) { + return input -> input.getId().equals(name); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java index ea1cf83e1e..54bc9e2baa 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,124 +15,28 @@ */ package com.github.tomakehurst.wiremock.stubbing; -import static com.google.common.base.MoreObjects.firstNonNull; - -import com.github.tomakehurst.wiremock.admin.NotFoundException; -import com.github.tomakehurst.wiremock.jetty9.websockets.Message; -import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - -public class Scenarios { - - private final ConcurrentHashMap scenarioMap = new ConcurrentHashMap<>(); - - public Scenario getByName(String name) { - return scenarioMap.get(name); - } - - public List getAll() { - return ImmutableList.copyOf(scenarioMap.values()); - } - - public void onStubMappingAdded(StubMapping mapping) { - if (mapping.isInScenario()) { - String scenarioName = mapping.getScenarioName(); - Scenario scenario = - firstNonNull(scenarioMap.get(scenarioName), Scenario.inStartedState(scenarioName)) - .withStubMapping(mapping); - scenarioMap.put(scenarioName, scenario); - } - } - - public void onStubMappingUpdated(StubMapping oldMapping, StubMapping newMapping) { - if (oldMapping.isInScenario() - && !newMapping.getScenarioName().equals(oldMapping.getScenarioName())) { - Scenario scenarioForOldMapping = - scenarioMap.get(oldMapping.getScenarioName()).withoutStubMapping(oldMapping); - - if (scenarioForOldMapping.getMappings().isEmpty()) { - scenarioMap.remove(scenarioForOldMapping.getId()); - } else { - scenarioMap.put(oldMapping.getScenarioName(), scenarioForOldMapping); - } - } - if (newMapping.isInScenario()) { - String scenarioName = newMapping.getScenarioName(); - Scenario scenario = - firstNonNull(scenarioMap.get(scenarioName), Scenario.inStartedState(scenarioName)) - .withStubMapping(newMapping); - scenarioMap.put(scenarioName, scenario); - } - } +public interface Scenarios { + Scenario getByName(String name); - public void onStubMappingRemoved(StubMapping mapping) { - if (mapping.isInScenario()) { - final String scenarioName = mapping.getScenarioName(); - Scenario scenario = scenarioMap.get(scenarioName).withoutStubMapping(mapping); + List getAll(); - if (scenario.getMappings().isEmpty()) { - scenarioMap.remove(scenarioName); - } else { - scenarioMap.put(scenarioName, scenario); - } - } - } + void onStubMappingAdded(StubMapping mapping); - public void onStubServed(StubMapping mapping) { - if (mapping.isInScenario()) { - final String scenarioName = mapping.getScenarioName(); - Scenario scenario = scenarioMap.get(scenarioName); - if (mapping.modifiesScenarioState() - && (mapping.getRequiredScenarioState() == null - || scenario.getState().equals(mapping.getRequiredScenarioState()))) { - Scenario newScenario = scenario.setState(mapping.getNewScenarioState()); - WebSocketEndpoint.broadcast(Message.SCENARIO); - scenarioMap.put(scenarioName, newScenario); - } - } - } + void onStubMappingUpdated(StubMapping oldMapping, StubMapping newMapping); - public void reset() { - scenarioMap.putAll( - Maps.transformValues( - scenarioMap, - new Function() { - @Override - public Scenario apply(Scenario input) { - return input.reset(); - } - })); - } + void onStubMappingRemoved(StubMapping mapping); - public void resetSingle(String name) { - setSingleScenarioState(name, Scenario::reset); - } + void onStubServed(StubMapping mapping); - public void setSingle(String name, String state) { - setSingleScenarioState(name, scenario -> scenario.setState(state)); - } + void reset(); - private void setSingleScenarioState( - String name, java.util.function.Function fn) { - Scenario scenario = scenarioMap.get(name); - if (scenario == null) { - throw new NotFoundException("Scenario " + name + " does not exist"); - } + void resetSingle(String name); - scenarioMap.replace(name, fn.apply(scenario)); - } + void setSingle(String name, String state); - public void clear() { - scenarioMap.clear(); - } + void clear(); - public boolean mappingMatchesScenarioState(StubMapping mapping) { - String currentScenarioState = getByName(mapping.getScenarioName()).getState(); - return mapping.getRequiredScenarioState().equals(currentScenarioState); - } + boolean mappingMatchesScenarioState(StubMapping mapping); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/ServeEvent.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/ServeEvent.java index 3abfcc593b..34e3b63210 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/ServeEvent.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/ServeEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,32 +15,59 @@ */ package com.github.tomakehurst.wiremock.stubbing; +import static com.github.tomakehurst.wiremock.stubbing.SubEvent.NON_MATCH_TYPE; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.tomakehurst.wiremock.common.DataTruncationSettings; -import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.Timing; +import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition; +import com.github.tomakehurst.wiremock.extension.ServeEventListenerDefinition; import com.github.tomakehurst.wiremock.http.LoggedResponse; +import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; +import com.google.common.base.Stopwatch; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; public class ServeEvent { + public static final String ORIGINAL_SERVE_EVENT_KEY = "wiremock.ORIGINAL_SERVE_EVENT"; + private final UUID id; private final LoggedRequest request; private final StubMapping stubMapping; private final ResponseDefinition responseDefinition; private final LoggedResponse response; - private final AtomicReference timing; + private final Timing timing; + + private final ConcurrentLinkedQueue subEvents; + + private final Stopwatch stopwatch; + + protected ServeEvent( + UUID id, + LoggedRequest request, + StubMapping stubMapping, + ResponseDefinition responseDefinition, + LoggedResponse response, + Timing timing, + ConcurrentLinkedQueue subEvents, + Stopwatch stopwatch) { + this.id = id; + this.request = request; + this.stubMapping = stubMapping; + this.responseDefinition = responseDefinition; + this.response = response; + this.timing = timing; + this.subEvents = subEvents; + this.stopwatch = stopwatch; + } @JsonCreator public ServeEvent( @@ -50,47 +77,59 @@ public ServeEvent( @JsonProperty("responseDefinition") ResponseDefinition responseDefinition, @JsonProperty("response") LoggedResponse response, @JsonProperty("wasMatched") boolean ignoredReadOnly, - @JsonProperty("timing") Timing timing) { - this.id = id; - this.request = request; - this.responseDefinition = responseDefinition; - this.stubMapping = stubMapping; - this.response = response; - this.timing = new AtomicReference<>(timing); + @JsonProperty("timing") Timing timing, + @JsonProperty("subEvents") Queue subEvents) { + this( + id, + request, + stubMapping, + responseDefinition, + response, + timing != null ? timing : Timing.create(), + subEvents != null ? new ConcurrentLinkedQueue<>(subEvents) : new ConcurrentLinkedQueue<>(), + Stopwatch.createStarted()); } - public ServeEvent( + protected ServeEvent( LoggedRequest request, StubMapping stubMapping, ResponseDefinition responseDefinition) { - this(UUID.randomUUID(), request, stubMapping, responseDefinition, null, false, null); + this(UUID.randomUUID(), request, stubMapping, responseDefinition, null, false, null, null); } - public static ServeEvent forUnmatchedRequest(LoggedRequest request) { - return new ServeEvent(request, null, ResponseDefinition.notConfigured()); + public static ServeEvent of(Request request) { + return new ServeEvent(LoggedRequest.createFrom(request), null, null); } - public static ServeEvent forBadRequest(LoggedRequest request, Errors errors) { - return new ServeEvent(request, null, ResponseDefinition.badRequest(errors)); + public static ServeEvent ofUnmatched( + LoggedRequest request, ResponseDefinition responseDefinition) { + return new ServeEvent(request, null, responseDefinition); } - public static ServeEvent forBadRequestEntity(LoggedRequest request, Errors errors) { - return new ServeEvent(request, null, ResponseDefinition.badRequestEntity(errors)); + public ServeEvent replaceRequest(Request request) { + return new ServeEvent( + id, + LoggedRequest.createFrom(request), + stubMapping, + responseDefinition, + response, + timing, + subEvents, + stopwatch); } - public static ServeEvent forNotAllowedRequest(LoggedRequest request, Errors errors) { - return new ServeEvent(request, null, ResponseDefinition.notPermitted(errors)); + public ServeEvent withStubMapping(StubMapping stubMapping) { + return new ServeEvent( + id, request, stubMapping, responseDefinition, response, false, timing, subEvents); } - public static ServeEvent of(LoggedRequest request, ResponseDefinition responseDefinition) { - return new ServeEvent(request, null, responseDefinition); + public ServeEvent withResponseDefinition(ResponseDefinition responseDefinition) { + return new ServeEvent( + id, request, stubMapping, responseDefinition, response, false, timing, subEvents); } - public static ServeEvent of( - LoggedRequest request, ResponseDefinition responseDefinition, StubMapping stubMapping) { - return new ServeEvent(request, stubMapping, responseDefinition); - } + public ServeEvent complete(Response response, DataTruncationSettings dataTruncationSettings) { + timing.logProcessTime(stopwatch); + timing.setAddedTime((int) response.getInitialDelay()); - public ServeEvent complete( - Response response, int processTimeMillis, DataTruncationSettings dataTruncationSettings) { return new ServeEvent( id, request, @@ -98,16 +137,21 @@ public ServeEvent complete( responseDefinition, LoggedResponse.from(response, dataTruncationSettings.getMaxResponseBodySize()), false, - new Timing((int) response.getInitialDelay(), processTimeMillis)); + timing, + subEvents); } - public void afterSend(int responseSendTimeMillis) { - timing.set(timing.get().withResponseSendTime(responseSendTimeMillis)); + public void beforeSend() { + stopwatch.reset(); + } + + public void afterSend() { + timing.logResponseSendTime(stopwatch); } @JsonIgnore public boolean isNoExactMatch() { - return !responseDefinition.wasConfigured(); + return responseDefinition == null || !responseDefinition.wasConfigured(); } public UUID getId() { @@ -135,7 +179,27 @@ public LoggedResponse getResponse() { } public Timing getTiming() { - return timing.get(); + return timing; + } + + public Queue getSubEvents() { + return subEvents; + } + + public void appendSubEvent(String type, Object data) { + final long elapsedNanos = stopwatch.elapsed(NANOSECONDS); + appendSubEvent(new SubEvent(type, elapsedNanos, data)); + } + + public void appendSubEvent(SubEvent subEvent) { + subEvents.add(subEvent); + } + + @JsonIgnore + public Optional getDiffSubEvent() { + return subEvents.stream() + .filter(subEvent -> subEvent.getType().equals(NON_MATCH_TYPE)) + .findFirst(); } @JsonIgnore @@ -145,19 +209,19 @@ public List getPostServeActions() { : Collections.emptyList(); } - public static final Function TO_LOGGED_REQUEST = - new Function() { - @Override - public LoggedRequest apply(ServeEvent serveEvent) { - return serveEvent.getRequest(); - } - }; - - public static final Predicate NOT_MATCHED = - new Predicate() { - @Override - public boolean apply(ServeEvent serveEvent) { - return serveEvent.isNoExactMatch(); - } - }; + @JsonIgnore + public List getServeEventListeners() { + return stubMapping != null && stubMapping.getServeEventListeners() != null + ? getStubMapping().getServeEventListeners() + : Collections.emptyList(); + } + + @JsonIgnore + public Parameters getTransformerParameters() { + return stubMapping != null + && stubMapping.getResponse() != null + && stubMapping.getResponse().getTransformerParameters() != null + ? stubMapping.getResponse().getTransformerParameters() + : Parameters.empty(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/SortedConcurrentMappingSet.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/SortedConcurrentMappingSet.java index 40fc5399d4..3d83fc83d4 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/SortedConcurrentMappingSet.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/SortedConcurrentMappingSet.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,35 +15,30 @@ */ package com.github.tomakehurst.wiremock.stubbing; -import static com.google.common.collect.Iterables.removeIf; - -import com.google.common.base.Predicate; import java.util.Comparator; import java.util.Iterator; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; public class SortedConcurrentMappingSet implements Iterable { - private AtomicLong insertionCount; - private ConcurrentSkipListSet mappingSet; + private final AtomicLong insertionCount; + private final ConcurrentSkipListSet mappingSet; public SortedConcurrentMappingSet() { insertionCount = new AtomicLong(); - mappingSet = - new ConcurrentSkipListSet(sortedByPriorityThenReverseInsertionOrder()); + mappingSet = new ConcurrentSkipListSet<>(sortedByPriorityThenReverseInsertionOrder()); } private Comparator sortedByPriorityThenReverseInsertionOrder() { - return new Comparator() { - public int compare(StubMapping one, StubMapping two) { - int priorityComparison = one.comparePriorityWith(two); - if (priorityComparison != 0) { - return priorityComparison; - } - - return Long.compare(two.getInsertionIndex(), one.getInsertionIndex()); + return (one, two) -> { + int priorityComparison = one.comparePriorityWith(two); + if (priorityComparison != 0) { + return priorityComparison; } + + return Long.compare(two.getInsertionIndex(), one.getInsertionIndex()); }; } @@ -52,6 +47,10 @@ public Iterator iterator() { return mappingSet.iterator(); } + public Stream stream() { + return mappingSet.stream(); + } + public void add(StubMapping mapping) { mapping.setInsertionIndex(insertionCount.getAndIncrement()); mappingSet.add(mapping); @@ -59,27 +58,16 @@ public void add(StubMapping mapping) { public boolean remove(final StubMapping mappingToRemove) { boolean removedByUuid = - removeIf( - mappingSet, - new Predicate() { - @Override - public boolean apply(StubMapping mapping) { - return mappingToRemove.getUuid() != null + mappingSet.removeIf( + mapping -> + mappingToRemove.getUuid() != null && mapping.getUuid() != null - && mappingToRemove.getUuid().equals(mapping.getUuid()); - } - }); + && mappingToRemove.getUuid().equals(mapping.getUuid())); boolean removedByRequestPattern = !removedByUuid - && removeIf( - mappingSet, - new Predicate() { - @Override - public boolean apply(StubMapping mapping) { - return mappingToRemove.getRequest().equals(mapping.getRequest()); - } - }); + && mappingSet.removeIf( + mapping -> mappingToRemove.getRequest().equals(mapping.getRequest())); return removedByUuid || removedByRequestPattern; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StoreBackedStubMappings.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StoreBackedStubMappings.java new file mode 100644 index 0000000000..7fc2a5976c --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StoreBackedStubMappings.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.stubbing; + +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; +import com.github.tomakehurst.wiremock.extension.StubLifecycleListener; +import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.StubMappingStore; +import java.util.List; +import java.util.Map; + +public class StoreBackedStubMappings extends AbstractStubMappings { + + public StoreBackedStubMappings( + StubMappingStore store, + Scenarios scenarios, + Map customMatchers, + Map transformers, + Map v2transformers, + BlobStore filesBlobStore, + List stubLifecycleListeners) { + super( + store, + scenarios, + customMatchers, + transformers, + v2transformers, + filesBlobStore, + stubLifecycleListeners); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMapping.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMapping.java index 18ffdea8ce..6d5dfdf98b 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMapping.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,14 @@ */ package com.github.tomakehurst.wiremock.stubbing; -import static com.google.common.base.MoreObjects.firstNonNull; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import com.fasterxml.jackson.annotation.*; import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.common.Metadata; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.PostServeActionDefinition; +import com.github.tomakehurst.wiremock.extension.ServeEventListenerDefinition; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestPattern; import java.util.List; @@ -50,6 +51,8 @@ public class StubMapping { private List postServeActions; + private List serveEventListeners; + private Metadata metadata; private long insertionIndex; @@ -112,11 +115,11 @@ public void setPersistent(Boolean persistent) { } public RequestPattern getRequest() { - return firstNonNull(request, RequestPattern.ANYTHING); + return getFirstNonNull(request, RequestPattern.ANYTHING); } public ResponseDefinition getResponse() { - return firstNonNull(response, ResponseDefinition.ok()); + return getFirstNonNull(response, ResponseDefinition.ok()); } public void setRequest(RequestPattern request) { @@ -208,6 +211,10 @@ public List getPostServeActions() { return postServeActions; } + public List getServeEventListeners() { + return serveEventListeners; + } + public void setPostServeActions(List postServeActions) { this.postServeActions = postServeActions; } @@ -238,6 +245,27 @@ public void setPostServeActions(Object postServeActions) { } } + public void setServeEventListenerDefinitions( + List serveEventListeners) { + this.serveEventListeners = serveEventListeners; + } + + @SuppressWarnings("unchecked") + @JsonProperty("serveEventListeners") + public void setServeEventListeners(Object serveEventListeners) { + if (serveEventListeners == null) { + return; + } + + if (List.class.isAssignableFrom(serveEventListeners.getClass())) { + this.serveEventListeners = + ((List>) serveEventListeners) + .stream() + .map(item -> Json.mapToObject(item, ServeEventListenerDefinition.class)) + .collect(Collectors.toList()); + } + } + public Metadata getMetadata() { return metadata; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java index b4afd990fd..2f31956515 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,34 +20,36 @@ import static com.github.tomakehurst.wiremock.common.Json.write; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; -import static com.google.common.collect.Iterables.filter; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.common.*; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.http.*; import com.github.tomakehurst.wiremock.matching.*; +import com.github.tomakehurst.wiremock.store.BlobStore; import com.github.tomakehurst.wiremock.verification.VerificationResult; -import com.google.common.base.Predicate; import java.util.Collection; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +/** @deprecated this is the legacy recorder and will be removed before 3.x is out of beta */ +@Deprecated public class StubMappingJsonRecorder implements RequestListener { - private final FileSource mappingsFileSource; - private final FileSource filesFileSource; + private final BlobStore mappingsBlobStore; + private final BlobStore filesBlobStore; private final Admin admin; private final List headersToMatch; private IdGenerator idGenerator; public StubMappingJsonRecorder( - FileSource mappingsFileSource, - FileSource filesFileSource, + BlobStore mappingsBlobStore, + BlobStore filesBlobStore, Admin admin, List headersToMatch) { - this.mappingsFileSource = mappingsFileSource; - this.filesFileSource = filesFileSource; + this.mappingsBlobStore = mappingsBlobStore; + this.filesBlobStore = filesBlobStore; this.admin = admin; this.headersToMatch = headersToMatch; idGenerator = new VeryShortIdGenerator(); @@ -81,7 +83,7 @@ private RequestPattern buildRequestPatternFrom(Request request) { } } - if (request.isMultipart()) { + if (request.isMultipart() && request.getParts() != null) { for (Request.Part part : request.getParts()) { builder.withRequestBodyPart(valuePatternForPart(part)); } @@ -165,19 +167,17 @@ private void writeToMappingAndBodyFile( StubMapping mapping = new StubMapping(requestPattern, responseToWrite); mapping.setUuid(UUID.nameUUIDFromBytes(fileId.getBytes())); - filesFileSource.writeBinaryFile(bodyFileName, body); - mappingsFileSource.writeTextFile(mappingFileName, write(mapping)); + filesBlobStore.put(bodyFileName, body); + mappingsBlobStore.put(mappingFileName, Strings.bytesFromString(write(mapping))); } private HttpHeaders withoutContentEncodingAndContentLength(HttpHeaders httpHeaders) { return new HttpHeaders( - filter( - httpHeaders.all(), - new Predicate() { - public boolean apply(HttpHeader header) { - return !header.keyEquals("Content-Encoding") && !header.keyEquals("Content-Length"); - } - })); + httpHeaders.all().stream() + .filter( + header -> + !header.keyEquals("Content-Encoding") && !header.keyEquals("Content-Length")) + .collect(Collectors.toList())); } private byte[] bodyDecompressedIfRequired(Response response) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappings.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappings.java index 48d537f9f7..31f92bf53c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappings.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappings.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,14 @@ */ package com.github.tomakehurst.wiremock.stubbing; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.matching.StringValuePattern; -import com.google.common.base.Optional; import java.util.List; +import java.util.Optional; import java.util.UUID; public interface StubMappings { - ServeEvent serveFor(Request request); + ServeEvent serveFor(ServeEvent request); void addMapping(StubMapping mapping); diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/SubEvent.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/SubEvent.java new file mode 100644 index 0000000000..6c3f9e0304 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/SubEvent.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.stubbing; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.Message; +import java.util.Map; + +public class SubEvent { + + public static final String NON_MATCH_TYPE = "REQUEST_NOT_MATCHED"; + public static final String JSON_ERROR = "JSON_ERROR"; + public static final String XML_ERROR = "XML"; + public static final String INFO = "INFO"; + public static final String WARNING = "WARNING"; + public static final String ERROR = "ERROR"; + private final String type; + + private final Long timeOffsetNanos; + + private final Map data; + + public static SubEvent info(String message) { + return message(INFO, message); + } + + public static SubEvent warning(String message) { + return message(WARNING, message); + } + + public static SubEvent error(String message) { + return message(ERROR, message); + } + + public static SubEvent message(String type, String message) { + return new SubEvent(type, null, new Message(message)); + } + + public SubEvent(String type, Object data) { + this(type, null, data); + } + + public SubEvent(String type, Long timeOffsetMillis, Object data) { + this(type, timeOffsetMillis, Json.objectToMap(data)); + } + + public SubEvent( + @JsonProperty("type") String type, + @JsonProperty("timeOffsetNanos") Long timeOffsetNanos, + @JsonProperty("data") Map data) { + this.type = type; + this.timeOffsetNanos = timeOffsetNanos; + this.data = data; + } + + public String getType() { + return type; + } + + public Long getTimeOffsetNanos() { + return timeOffsetNanos; + } + + public Map getData() { + return data; + } + + public T getDataAs(Class dataType) { + return Json.mapToObject(data, dataType); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/AbstractRequestJournal.java b/src/main/java/com/github/tomakehurst/wiremock/verification/AbstractRequestJournal.java new file mode 100644 index 0000000000..e38227b23b --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/AbstractRequestJournal.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.verification; + +import static com.github.tomakehurst.wiremock.matching.RequestPattern.thatMatch; +import static com.github.tomakehurst.wiremock.matching.RequestPattern.withRequestMatching; +import static java.util.stream.Collectors.toList; + +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.store.RequestJournalStore; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public abstract class AbstractRequestJournal implements RequestJournal { + + protected final RequestJournalStore store; + + private final Integer maxEntries; + private final Map customMatchers; + + public AbstractRequestJournal( + Integer maxEntries, + Map customMatchers, + RequestJournalStore store) { + + if (maxEntries != null && maxEntries < 0) { + throw new IllegalArgumentException( + "Maximum number of entries of journal must be greater than zero"); + } + this.maxEntries = maxEntries; + this.customMatchers = customMatchers; + this.store = store; + } + + @Override + public int countRequestsMatching(RequestPattern requestPattern) { + return (int) getRequests().filter(thatMatch(requestPattern, customMatchers)).count(); + } + + @Override + public List getRequestsMatching(RequestPattern requestPattern) { + List loggedRequests = + getRequests().filter(thatMatch(requestPattern, customMatchers)).collect(toList()); + Collections.reverse(loggedRequests); + return loggedRequests; + } + + @Override + public void requestReceived(ServeEvent serveEvent) { + store.add(serveEvent); + removeOldEntries(); + } + + @Override + public void serveCompleted(ServeEvent serveEvent) { + store.put(serveEvent.getId(), serveEvent); + } + + @Override + public void removeEvent(final UUID eventId) { + store.remove(eventId); + } + + @Override + public List removeEventsMatching(RequestPattern requestPattern) { + return removeServeEvents(withRequestMatching(requestPattern)); + } + + @Override + public List removeServeEventsForStubsMatchingMetadata( + StringValuePattern metadataPattern) { + return removeServeEvents(withStubMetadataMatching(metadataPattern)); + } + + private List removeServeEvents(Predicate predicate) { + List toDelete = store.getAll().filter(predicate).collect(toList()); + + for (ServeEvent event : toDelete) { + store.remove(event.getId()); + } + + return toDelete; + } + + @Override + public List getAllServeEvents() { + return store.getAll().collect(toList()); + } + + @Override + public Optional getServeEvent(final UUID id) { + return store.get(id); + } + + @Override + public void reset() { + store.clear(); + } + + private Stream getRequests() { + return store.getAll().map(ServeEvent::getRequest); + } + + private void removeOldEntries() { + if (maxEntries != null) { + while (store.getAllKeys().count() > maxEntries) { + store.removeLast(); + } + } + } + + private static Predicate withStubMetadataMatching( + final StringValuePattern metadataPattern) { + + return (ServeEvent serveEvent) -> { + StubMapping stub = serveEvent.getStubMapping(); + if (stub != null) { + String metadataJson = Json.write(stub.getMetadata()); + return metadataPattern.match(metadataJson).isExactMatch(); + } + + return false; + }; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/DisabledRequestJournal.java b/src/main/java/com/github/tomakehurst/wiremock/verification/DisabledRequestJournal.java index 40479e6bee..d87b3bd80c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/DisabledRequestJournal.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/DisabledRequestJournal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Optional; import java.util.List; +import java.util.Optional; import java.util.UUID; public class DisabledRequestJournal implements RequestJournal { @@ -50,6 +50,9 @@ public void reset() {} @Override public void requestReceived(ServeEvent serveEvent) {} + @Override + public void serveCompleted(ServeEvent serveEvent) {} + @Override public void removeEvent(UUID eventId) {} diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournal.java b/src/main/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournal.java index ca93dfe5c2..1e201ba3c1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournal.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,145 +15,14 @@ */ package com.github.tomakehurst.wiremock.verification; -import static com.github.tomakehurst.wiremock.matching.RequestPattern.thatMatch; -import static com.github.tomakehurst.wiremock.matching.RequestPattern.withRequstMatching; -import static com.google.common.collect.Iterables.*; - -import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; -import com.github.tomakehurst.wiremock.matching.RequestPattern; -import com.github.tomakehurst.wiremock.matching.StringValuePattern; -import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableList; -import java.util.List; +import com.github.tomakehurst.wiremock.store.InMemoryRequestJournalStore; import java.util.Map; -import java.util.Queue; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class InMemoryRequestJournal implements RequestJournal { - private final Queue serveEvents = new ConcurrentLinkedQueue(); - - private final Optional maxEntries; - private final Map customMatchers; +public class InMemoryRequestJournal extends StoreBackedRequestJournal { public InMemoryRequestJournal( - Optional maxEntries, Map customMatchers) { - if (maxEntries.isPresent() && maxEntries.get() < 0) { - throw new IllegalArgumentException( - "Maximum number of entries of journal must be greater than zero"); - } - this.maxEntries = maxEntries; - this.customMatchers = customMatchers; - } - - @Override - public int countRequestsMatching(RequestPattern requestPattern) { - return size(filter(getRequests(), thatMatch(requestPattern, customMatchers))); - } - - @Override - public List getRequestsMatching(RequestPattern requestPattern) { - return ImmutableList.copyOf(filter(getRequests(), thatMatch(requestPattern, customMatchers))); - } - - @Override - public void requestReceived(ServeEvent serveEvent) { - serveEvents.add(serveEvent); - removeOldEntries(); - } - - @Override - public void removeEvent(final UUID eventId) { - removeServeEvents( - new Predicate() { - @Override - public boolean apply(ServeEvent input) { - return input.getId().equals(eventId); - } - }); - } - - @Override - public List removeEventsMatching(RequestPattern requestPattern) { - return removeServeEvents(withRequstMatching(requestPattern)); - } - - @Override - public List removeServeEventsForStubsMatchingMetadata( - StringValuePattern metadataPattern) { - return removeServeEvents(withStubMetadataMatching(metadataPattern)); - } - - private List removeServeEvents(Predicate predicate) { - List toDelete = FluentIterable.from(serveEvents).filter(predicate).toList(); - - for (ServeEvent event : toDelete) { - serveEvents.remove(event); - } - - return toDelete; - } - - @Override - public List getAllServeEvents() { - return ImmutableList.copyOf(serveEvents).reverse(); - } - - @Override - public Optional getServeEvent(final UUID id) { - return tryFind( - serveEvents, - new Predicate() { - @Override - public boolean apply(ServeEvent input) { - return input.getId().equals(id); - } - }); - } - - @Override - public void reset() { - serveEvents.clear(); - } - - private Iterable getRequests() { - return transform( - serveEvents, - new Function() { - public LoggedRequest apply(ServeEvent input) { - return input.getRequest(); - } - }); - } - - private void removeOldEntries() { - if (maxEntries.isPresent()) { - while (serveEvents.size() > maxEntries.get()) { - serveEvents.poll(); - } - } - } - - private static Predicate withStubMetadataMatching( - final StringValuePattern metadataPattern) { - return new Predicate() { - @Override - public boolean apply(ServeEvent serveEvent) { - StubMapping stub = serveEvent.getStubMapping(); - if (stub != null) { - String metadataJson = Json.write(stub.getMetadata()); - return metadataPattern.match(metadataJson).isExactMatch(); - } - - return false; - } - }; + Integer maxEntries, Map customMatchers) { + super(maxEntries, customMatchers, new InMemoryRequestJournalStore()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/LoggedRequest.java b/src/main/java/com/github/tomakehurst/wiremock/verification/LoggedRequest.java index a6f00c6f71..9b952cb56e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/LoggedRequest.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/LoggedRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,27 +17,20 @@ import static com.github.tomakehurst.wiremock.common.Encoding.decodeBase64; import static com.github.tomakehurst.wiremock.common.Encoding.encodeBase64; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.common.Strings.stringFromBytes; -import static com.github.tomakehurst.wiremock.common.Urls.*; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.FluentIterable.from; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import static com.github.tomakehurst.wiremock.common.Urls.safelyCreateURL; +import static com.github.tomakehurst.wiremock.common.Urls.splitQueryFromUrl; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.fasterxml.jackson.annotation.*; import com.github.tomakehurst.wiremock.common.Dates; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.common.Urls; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; import java.net.URL; import java.nio.charset.Charset; -import java.util.Collection; -import java.util.Date; -import java.util.Map; -import java.util.Set; +import java.util.*; @JsonIgnoreProperties(ignoreUnknown = true) public class LoggedRequest implements Request { @@ -52,6 +45,7 @@ public class LoggedRequest implements Request { private final HttpHeaders headers; private final Map cookies; private final Map queryParams; + private final Map formParameters; private final byte[] body; private final boolean isBrowserProxyRequest; private final Date loggedDate; @@ -60,6 +54,9 @@ public class LoggedRequest implements Request { public static LoggedRequest createFrom(Request request) { return new LoggedRequest( + request.getScheme(), + request.getHost(), + request.getPort(), request.getUrl(), request.getAbsoluteUrl(), request.getMethod(), @@ -70,11 +67,12 @@ public static LoggedRequest createFrom(Request request) { new Date(), request.getBody(), request.getParts(), - request.getProtocol()); + request.getProtocol(), + request.formParameters()); } @JsonCreator - public LoggedRequest( + LoggedRequest( @JsonProperty("url") String url, @JsonProperty("absoluteUrl") String absoluteUrl, @JsonProperty("method") RequestMethod method, @@ -88,6 +86,9 @@ public LoggedRequest( @JsonProperty("multiparts") Collection multiparts, @JsonProperty("protocol") String protocol) { this( + null, + null, + null, url, absoluteUrl, method, @@ -98,10 +99,14 @@ public LoggedRequest( loggedDate, decodeBase64(bodyAsBase64), multiparts, - protocol); + protocol, + new HashMap<>()); } - public LoggedRequest( + private LoggedRequest( + String scheme, + String host, + Integer port, String url, String absoluteUrl, RequestMethod method, @@ -112,19 +117,20 @@ public LoggedRequest( Date loggedDate, byte[] body, Collection multiparts, - String protocol) { + String protocol, + Map formParameters) { this.url = url; this.absoluteUrl = absoluteUrl; if (absoluteUrl == null) { - this.scheme = null; - this.host = null; - this.port = -1; + this.scheme = scheme; + this.host = host; + this.port = port != null ? port : -1; } else { URL fullUrl = safelyCreateURL(absoluteUrl); this.scheme = fullUrl.getProtocol(); this.host = fullUrl.getHost(); - this.port = fullUrl.getPort(); + this.port = Urls.getPort(fullUrl); } this.clientIp = clientIp; @@ -132,7 +138,8 @@ public LoggedRequest( this.body = body; this.headers = headers; this.cookies = cookies; - this.queryParams = splitQueryFromUrl(url); + this.queryParams = url != null ? splitQueryFromUrl(url) : Collections.emptyMap(); + this.formParameters = formParameters; this.isBrowserProxyRequest = isBrowserProxyRequest; this.loggedDate = loggedDate; this.multiparts = multiparts; @@ -241,7 +248,22 @@ public Set getAllHeaderKeys() { @Override public QueryParameter queryParameter(String key) { - return firstNonNull(queryParams.get(key), QueryParameter.absent(key)); + return getFirstNonNull(queryParams.get(key), QueryParameter.absent(key)); + } + + @Override + public FormParameter formParameter(String key) { + return getFirstNonNull(formParameters.get(key), FormParameter.absent(key)); + } + + @Override + public Map formParameters() { + return formParameters; + } + + @JsonProperty("formParams") + public Map getFormParameters() { + return formParameters; } @JsonProperty("queryParams") @@ -261,7 +283,7 @@ public boolean isBrowserProxyRequest() { @JsonIgnore @Override public Optional getOriginalRequest() { - return Optional.absent(); + return Optional.empty(); } @Override @@ -269,6 +291,7 @@ public String getProtocol() { return protocol; } + @JsonFormat(shape = JsonFormat.Shape.NUMBER) public Date getLoggedDate() { return loggedDate; } @@ -285,7 +308,7 @@ public String toString() { @JsonIgnore @Override public boolean isMultipart() { - return (multiparts != null && multiparts.size() > 0); + return (multiparts != null && !multiparts.isEmpty()); } @JsonIgnore @@ -298,15 +321,10 @@ public Collection getParts() { @Override public Part getPart(final String name) { return (multiparts != null && name != null) - ? from(multiparts) - .firstMatch( - new Predicate() { - @Override - public boolean apply(Part input) { - return (name.equals(input.getName())); - } - }) - .get() + ? multiparts.stream() + .filter(input -> (name.equals(input.getName()))) + .findFirst() + .orElse(null) : null; } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/NearMissCalculator.java b/src/main/java/com/github/tomakehurst/wiremock/verification/NearMissCalculator.java index 16991b4b46..d7f11a1cc5 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/NearMissCalculator.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/NearMissCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,21 @@ */ package com.github.tomakehurst.wiremock.verification; -import static com.google.common.collect.FluentIterable.from; import static java.lang.Math.min; import com.github.tomakehurst.wiremock.matching.MatchResult; import com.github.tomakehurst.wiremock.matching.MemoizingMatchResult; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.stubbing.*; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; public class NearMissCalculator { public static final int NEAR_MISS_COUNT = 3; - public static final Comparator NEAR_MISS_ASCENDING_COMPARATOR = - new Comparator() { - public int compare(NearMiss o1, NearMiss o2) { - return o1.compareTo(o2); - } - }; + public static final Comparator NEAR_MISS_ASCENDING_COMPARATOR = Comparable::compareTo; private final StubMappings stubMappings; private final RequestJournal requestJournal; @@ -53,16 +46,15 @@ public List findNearestTo(final LoggedRequest request) { List allMappings = stubMappings.getAll(); return sortAndTruncate( - from(allMappings) - .transform( - new Function() { - public NearMiss apply(StubMapping stubMapping) { - MatchResult matchResult = - new MemoizingMatchResult(stubMapping.getRequest().match(request)); - String actualScenarioState = getScenarioStateOrNull(stubMapping); - return new NearMiss(request, stubMapping, matchResult, actualScenarioState); - } - }), + allMappings.stream() + .map( + stubMapping -> { + MatchResult matchResult = + new MemoizingMatchResult(stubMapping.getRequest().match(request)); + String actualScenarioState = getScenarioStateOrNull(stubMapping); + return new NearMiss(request, stubMapping, matchResult, actualScenarioState); + }) + .collect(Collectors.toList()), allMappings.size()); } @@ -78,22 +70,19 @@ private String getScenarioStateOrNull(StubMapping stubMapping) { public List findNearestTo(final RequestPattern requestPattern) { List serveEvents = requestJournal.getAllServeEvents(); return sortAndTruncate( - from(serveEvents) - .transform( - new Function() { - public NearMiss apply(ServeEvent serveEvent) { - MatchResult matchResult = - new MemoizingMatchResult(requestPattern.match(serveEvent.getRequest())); - return new NearMiss(serveEvent.getRequest(), requestPattern, matchResult); - } - }), + serveEvents.stream() + .map( + serveEvent -> { + MatchResult matchResult = + new MemoizingMatchResult(requestPattern.match(serveEvent.getRequest())); + return new NearMiss(serveEvent.getRequest(), requestPattern, matchResult); + }) + .collect(Collectors.toList()), serveEvents.size()); } - private static List sortAndTruncate( - FluentIterable nearMisses, int originalSize) { - return nearMisses - .toSortedList(NEAR_MISS_ASCENDING_COMPARATOR) - .subList(0, min(NEAR_MISS_COUNT, originalSize)); + private static List sortAndTruncate(List nearMisses, int originalSize) { + nearMisses.sort(NEAR_MISS_ASCENDING_COMPARATOR); + return nearMisses.subList(0, min(NEAR_MISS_COUNT, originalSize)); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/RequestJournal.java b/src/main/java/com/github/tomakehurst/wiremock/verification/RequestJournal.java index 22903cfcbf..f51674a961 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/RequestJournal.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/RequestJournal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Optional; import java.util.List; +import java.util.Optional; import java.util.UUID; public interface RequestJournal { @@ -36,6 +36,8 @@ public interface RequestJournal { void requestReceived(ServeEvent serveEvent); + void serveCompleted(ServeEvent serveEvent); + void removeEvent(UUID eventId); List removeEventsMatching(RequestPattern requestPattern); diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/StoreBackedRequestJournal.java b/src/main/java/com/github/tomakehurst/wiremock/verification/StoreBackedRequestJournal.java new file mode 100644 index 0000000000..58a8036995 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/StoreBackedRequestJournal.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.verification; + +import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.store.RequestJournalStore; +import java.util.Map; + +public class StoreBackedRequestJournal extends AbstractRequestJournal { + + public StoreBackedRequestJournal( + Integer maxEntries, + Map customMatchers, + RequestJournalStore store) { + super(maxEntries, customMatchers, store); + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/Diff.java b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/Diff.java index 1967b0f1c8..359e9f9b7c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/Diff.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/Diff.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,21 +17,47 @@ import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.verification.diff.SpacerLine.SPACER; -import static com.google.common.base.MoreObjects.firstNonNull; import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.common.ListOrSingle; import com.github.tomakehurst.wiremock.common.Urls; +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathTemplate; import com.github.tomakehurst.wiremock.common.xml.Xml; -import com.github.tomakehurst.wiremock.http.*; -import com.github.tomakehurst.wiremock.matching.*; +import com.github.tomakehurst.wiremock.http.Body; +import com.github.tomakehurst.wiremock.http.Cookie; +import com.github.tomakehurst.wiremock.http.FormParameter; +import com.github.tomakehurst.wiremock.http.HttpHeader; +import com.github.tomakehurst.wiremock.http.HttpHeaders; +import com.github.tomakehurst.wiremock.http.MultiValue; +import com.github.tomakehurst.wiremock.http.QueryParameter; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.RequestMethod; +import com.github.tomakehurst.wiremock.matching.BinaryEqualToPattern; +import com.github.tomakehurst.wiremock.matching.ContentPattern; +import com.github.tomakehurst.wiremock.matching.EqualToJsonPattern; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; +import com.github.tomakehurst.wiremock.matching.EqualToXmlPattern; +import com.github.tomakehurst.wiremock.matching.MultiValuePattern; +import com.github.tomakehurst.wiremock.matching.MultipartValuePattern; +import com.github.tomakehurst.wiremock.matching.MultipleMatchMultiValuePattern; +import com.github.tomakehurst.wiremock.matching.PathPattern; +import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.matching.SingleMatchMultiValuePattern; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.matching.UrlPathPattern; +import com.github.tomakehurst.wiremock.matching.UrlPathTemplatePattern; +import com.github.tomakehurst.wiremock.matching.UrlPattern; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.collect.ImmutableList; import java.net.URI; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; public class Diff { @@ -70,11 +96,11 @@ public String toString() { } public List> getLines() { - return getLines(Collections.emptyMap()); + return getLines(Collections.emptyMap()); } public List> getLines(Map customMatcherExtensions) { - ImmutableList.Builder> builder = ImmutableList.builder(); + List> diffLineList = new LinkedList<>(); if (requestPattern.getHost() != null) { String hostOperator = generateOperatorString(requestPattern.getHost(), ""); @@ -82,7 +108,7 @@ public List> getLines(Map customMat DiffLine hostSection = new DiffLine<>( "Host", requestPattern.getHost(), request.getHost(), printedHostPatternValue.trim()); - builder.add(hostSection); + diffLineList.add(hostSection); } if (requestPattern.getPort() != null) { @@ -90,14 +116,14 @@ public List> getLines(Map customMat String actualPort = String.valueOf(request.getPort()); DiffLine portSection = new DiffLine<>("Port", expectedPort, actualPort, expectedPort.getExpected()); - builder.add(portSection); + diffLineList.add(portSection); } if (requestPattern.getScheme() != null) { StringValuePattern expectedScheme = equalTo(String.valueOf(requestPattern.getScheme())); DiffLine schemeSection = new DiffLine<>("Scheme", expectedScheme, request.getScheme(), requestPattern.getScheme()); - builder.add(schemeSection); + diffLineList.add(schemeSection); } DiffLine methodSection = @@ -106,18 +132,46 @@ public List> getLines(Map customMat requestPattern.getMethod(), request.getMethod(), requestPattern.getMethod().getName()); - builder.add(methodSection); + diffLineList.add(methodSection); - UrlPattern urlPattern = firstNonNull(requestPattern.getUrlMatcher(), anyUrl()); + UrlPattern urlPattern = getFirstNonNull(requestPattern.getUrlMatcher(), anyUrl()); String printedUrlPattern = generatePrintedUrlPattern(urlPattern); DiffLine urlSection = new DiffLine<>("URL", urlPattern, request.getUrl(), printedUrlPattern); - builder.add(urlSection); + diffLineList.add(urlSection); - builder.add(SPACER); + diffLineList.add(SPACER); addHeaderSection( - requestPattern.combineBasicAuthAndOtherHeaders(), request.getHeaders(), builder); + requestPattern.combineBasicAuthAndOtherHeaders(), request.getHeaders(), diffLineList); + + final Map pathParameters = requestPattern.getPathParameters(); + if (urlPattern instanceof UrlPathTemplatePattern + && pathParameters != null + && !pathParameters.isEmpty() + && !urlSection.isForNonMatch()) { + final UrlPathTemplatePattern urlPathTemplatePattern = + (UrlPathTemplatePattern) requestPattern.getUrlMatcher(); + final PathTemplate pathTemplate = urlPathTemplatePattern.getPathTemplate(); + final PathParams requestPathParameterValues = + pathTemplate.parse(Urls.getPath(request.getUrl())); + + for (Map.Entry entry : requestPathParameterValues.entrySet()) { + String parameterName = entry.getKey(); + final String parameterValue = parameterName + ": " + entry.getValue(); + final StringValuePattern pattern = pathParameters.get(parameterName); + String operator = generateOperatorString(pattern, " = "); + DiffLine section = + new DiffLine<>( + "Path parameter", + pattern, + parameterValue, + "Path parameter: " + parameterName + operator + pattern.getValue()); + diffLineList.add(section); + } + + diffLineList.add(SPACER); + } boolean anyQueryParams = false; if (requestPattern.getQueryParameters() != null) { @@ -129,32 +183,58 @@ public List> getLines(Map customMat String key = entry.getKey(); MultiValuePattern pattern = entry.getValue(); QueryParameter queryParameter = - firstNonNull(requestQueryParams.get(key), QueryParameter.absent(key)); + getFirstNonNull(requestQueryParams.get(key), QueryParameter.absent(key)); - String operator = generateOperatorString(pattern.getValuePattern(), " = "); + String operator = generateOperatorStringForMultiValuePattern(pattern, " = "); DiffLine section = new DiffLine<>( "Query", pattern, queryParameter, - "Query: " + key + operator + pattern.getValuePattern().getValue()); - builder.add(section); + "Query: " + key + operator + pattern.getExpected()); + diffLineList.add(section); anyQueryParams = true; } } if (anyQueryParams) { - builder.add(SPACER); + diffLineList.add(SPACER); + } + + boolean anyFormParams = false; + if (requestPattern.getFormParameters() != null) { + Map requestFormParameters = request.formParameters(); + + for (Map.Entry entry : + requestPattern.getFormParameters().entrySet()) { + String key = entry.getKey(); + MultiValuePattern pattern = entry.getValue(); + FormParameter formParameter = + getFirstNonNull(requestFormParameters.get(key), FormParameter.absent(key)); + + String operator = generateOperatorStringForMultiValuePattern(pattern, " = "); + DiffLine section = + new DiffLine<>( + "Form data", + pattern, + formParameter, + "Form: " + key + operator + pattern.getExpected()); + diffLineList.add(section); + anyFormParams = true; + } + } + + if (anyFormParams) { + diffLineList.add(SPACER); } boolean anyCookieSections = false; if (requestPattern.getCookies() != null) { - Map cookies = - firstNonNull(request.getCookies(), Collections.emptyMap()); + Map cookies = getFirstNonNull(request.getCookies(), Collections.emptyMap()); for (Map.Entry entry : requestPattern.getCookies().entrySet()) { String key = entry.getKey(); StringValuePattern pattern = entry.getValue(); - Cookie cookie = firstNonNull(cookies.get(key), Cookie.absent()); + Cookie cookie = getFirstNonNull(cookies.get(key), Cookie.absent()); String operator = generateOperatorString(pattern, "="); DiffLine section = @@ -163,41 +243,41 @@ public List> getLines(Map customMat pattern, cookie.isPresent() ? cookie.getValue() : "", "Cookie: " + key + operator + pattern.getValue()); - builder.add(section); + diffLineList.add(section); anyCookieSections = true; } } if (anyCookieSections) { - builder.add(SPACER); + diffLineList.add(SPACER); } List> bodyPatterns = requestPattern.getBodyPatterns(); - addBodySection(bodyPatterns, new Body(request.getBody()), builder); + addBodySection(bodyPatterns, new Body(request.getBody()), diffLineList); List multipartPatterns = requestPattern.getMultipartPatterns(); if (multipartPatterns != null && !multipartPatterns.isEmpty()) { for (MultipartValuePattern pattern : multipartPatterns) { if (!request.isMultipart()) { - builder.add(new SectionDelimiter("[Multipart request body]", "")); + diffLineList.add(new SectionDelimiter("[Multipart request body]", "")); } else if (!pattern.match(request).isExactMatch()) { for (Request.Part part : request.getParts()) { - builder.add(SPACER); + diffLineList.add(SPACER); String patternPartName = pattern.getName() == null ? "" : ": " + pattern.getName(); String partName = part.getName() == null ? "" : part.getName(); - builder.add( + diffLineList.add( new SectionDelimiter("[Multipart" + patternPartName + "]", "[" + partName + "]")); - builder.add(SPACER); + diffLineList.add(SPACER); if (!pattern.match(part).isExactMatch()) { - addHeaderSection(pattern.getHeaders(), part.getHeaders(), builder); - addBodySection(pattern.getBodyPatterns(), part.getBody(), builder); - builder.add(SPACER); + addHeaderSection(pattern.getHeaders(), part.getHeaders(), diffLineList); + addBodySection(pattern.getBodyPatterns(), part.getBody(), diffLineList); + diffLineList.add(SPACER); } - builder.add(new SectionDelimiter("[/Multipart]", "[/" + partName + "]")); - builder.add(SPACER); + diffLineList.add(new SectionDelimiter("[/Multipart]", "[/" + partName + "]")); + diffLineList.add(SPACER); } } } @@ -206,7 +286,7 @@ public List> getLines(Map customMat if (requestPattern.hasInlineCustomMatcher()) { InlineCustomMatcherLine customMatcherLine = new InlineCustomMatcherLine(requestPattern.getMatcher(), request); - builder.add(customMatcherLine); + diffLineList.add(customMatcherLine); } if (requestPattern.hasNamedCustomMatcher()) { @@ -216,16 +296,16 @@ public List> getLines(Map customMat NamedCustomMatcherLine namedCustomMatcherLine = new NamedCustomMatcherLine( customMatcher, requestPattern.getCustomMatcher().getParameters(), request); - builder.add(namedCustomMatcherLine); + diffLineList.add(namedCustomMatcherLine); } else { - builder.add( + diffLineList.add( new SectionDelimiter( "[custom matcher: " + requestPattern.getCustomMatcher().getName() + "]")); } } - if (scenarioName != null) { - builder.add( + if (scenarioName != null && expectedScenarioState != null) { + diffLineList.add( new DiffLine<>( "Scenario", new EqualToPattern(expectedScenarioState), @@ -233,7 +313,7 @@ public List> getLines(Map customMat buildScenarioLine(scenarioName, expectedScenarioState))); } - return builder.build(); + return diffLineList; } private static String buildScenarioLine(String scenarioName, String scenarioState) { @@ -243,7 +323,7 @@ private static String buildScenarioLine(String scenarioName, String scenarioStat private void addHeaderSection( Map headerPatterns, HttpHeaders headers, - ImmutableList.Builder> builder) { + List> builder) { boolean anyHeaderSections = false; if (headerPatterns != null && !headerPatterns.isEmpty()) { anyHeaderSections = true; @@ -251,8 +331,12 @@ private void addHeaderSection( HttpHeader header = headers.getHeader(key); MultiValuePattern headerPattern = headerPatterns.get(header.key()); - String operator = generateOperatorString(headerPattern.getValuePattern(), ""); - String printedPatternValue = header.key() + operator + ": " + headerPattern.getExpected(); + String operator = generateOperatorStringForMultiValuePattern(headerPattern, ""); + String expected = + StringUtils.isEmpty(headerPattern.getExpected()) + ? "" + : ": " + headerPattern.getExpected(); + String printedPatternValue = header.key() + operator + expected; DiffLine section = new DiffLine<>("Header", headerPattern, header, printedPatternValue); @@ -266,19 +350,14 @@ private void addHeaderSection( } private void addBodySection( - List> bodyPatterns, Body body, ImmutableList.Builder> builder) { + List> bodyPatterns, Body body, List> builder) { if (bodyPatterns != null && !bodyPatterns.isEmpty()) { for (ContentPattern pattern : bodyPatterns) { String formattedBody = formatIfJsonOrXml(pattern, body); if (PathPattern.class.isAssignableFrom(pattern.getClass())) { PathPattern pathPattern = (PathPattern) pattern; if (!pathPattern.isSimple()) { - ListOrSingle expressionResult = - pathPattern.getExpressionResult(body.asString()); - String expressionResultString = - expressionResult != null && !expressionResult.isEmpty() - ? expressionResult.toString() - : null; + String expressionResultString = getExpressionResultString(body, pathPattern); String printedExpectedValue = pathPattern.getExpected() + " [" @@ -300,8 +379,10 @@ private void addBodySection( } } else if (StringValuePattern.class.isAssignableFrom(pattern.getClass())) { StringValuePattern stringValuePattern = (StringValuePattern) pattern; + String printedPatternValue = "[" + pattern.getName() + "]\n" + pattern.getExpected(); builder.add( - new DiffLine<>("Body", stringValuePattern, formattedBody, pattern.getExpected())); + new DiffLine<>( + "Body", stringValuePattern, "\n" + formattedBody, printedPatternValue)); } else { BinaryEqualToPattern nonStringPattern = (BinaryEqualToPattern) pattern; builder.add( @@ -312,10 +393,31 @@ private void addBodySection( } } + private static String getExpressionResultString(Body body, PathPattern pathPattern) { + String bodyStr = body.asString(); + if (StringUtils.isEmpty(bodyStr)) { + return null; + } else { + try { + ListOrSingle expressionResult = pathPattern.getExpressionResult(bodyStr); + return expressionResult != null && !expressionResult.isEmpty() + ? expressionResult.toString() + : null; + } catch (Exception e) { + return null; + } + } + } + private String generatePrintedUrlPattern(UrlPattern urlPattern) { - String matchPart = - (urlPattern instanceof UrlPathPattern ? "path" : "") - + (urlPattern.isRegex() ? " regex" : ""); + String matchPart; + if (urlPattern instanceof UrlPathTemplatePattern) { + matchPart = "path template"; + } else { + matchPart = + (urlPattern instanceof UrlPathPattern ? "path" : "") + + (urlPattern.isRegex() ? " regex" : ""); + } matchPart = matchPart.trim(); @@ -328,6 +430,20 @@ private String generateOperatorString(ContentPattern pattern, String defaultV return isAnEqualToPattern(pattern) ? defaultValue : " [" + pattern.getName() + "] "; } + private String generateOperatorStringForMultiValuePattern( + final MultiValuePattern valuePattern, final String defaultValue) { + if (valuePattern instanceof MultipleMatchMultiValuePattern) { + return ((MultipleMatchMultiValuePattern) valuePattern).getOperator() + + "[" + + valuePattern.getName() + + "]"; + } else { + return isAnEqualToPattern(((SingleMatchMultiValuePattern) valuePattern).getValuePattern()) + ? defaultValue + : " [" + valuePattern.getName() + "] "; + } + } + public String getStubMappingName() { return stubMappingName; } @@ -356,8 +472,4 @@ private static boolean isAnEqualToPattern(ContentPattern pattern) { || pattern instanceof EqualToXmlPattern || pattern instanceof BinaryEqualToPattern; } - - public boolean hasCustomMatcher() { - return requestPattern.hasInlineCustomMatcher() || requestPattern.hasNamedCustomMatcher(); - } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/DiffEventData.java b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/DiffEventData.java new file mode 100644 index 0000000000..16494b94f1 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/DiffEventData.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.verification.diff; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DiffEventData { + + public static final String KEY = "DIFF_REPORT"; + + private final int status; + private final String contentType; + private final String report; + + public DiffEventData( + @JsonProperty("status") int status, + @JsonProperty("contentType") String contentType, + @JsonProperty("report") String report) { + this.status = status; + this.contentType = contentType; + this.report = report; + } + + public int getStatus() { + return status; + } + + public String getContentType() { + return contentType; + } + + public String getReport() { + return report; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/EmptyToStringRequestWrapper.java b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/EmptyToStringRequestWrapper.java index 08f8033f38..de186e7923 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/EmptyToStringRequestWrapper.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/EmptyToStringRequestWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2022 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package com.github.tomakehurst.wiremock.verification.diff; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.base.Optional; import java.util.Collection; import java.util.Map; +import java.util.Optional; import java.util.Set; public class EmptyToStringRequestWrapper implements Request { @@ -104,6 +104,16 @@ public QueryParameter queryParameter(String key) { return target.queryParameter(key); } + @Override + public FormParameter formParameter(String key) { + return target.formParameter(key); + } + + @Override + public Map formParameters() { + return target.formParameters(); + } + @Override public byte[] getBody() { return target.getBody(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/JUnitStyleDiffRenderer.java b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/JUnitStyleDiffRenderer.java index c983c7697d..7d2beb6916 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/JUnitStyleDiffRenderer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/JUnitStyleDiffRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,40 +15,33 @@ */ package com.github.tomakehurst.wiremock.verification.diff; -import static com.google.common.collect.FluentIterable.from; - -import com.google.common.base.Function; -import com.google.common.base.Joiner; +import com.github.tomakehurst.wiremock.common.Strings; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; public class JUnitStyleDiffRenderer { public String render(Diff diff) { List> lines = diff.getLines(); - String expected = Joiner.on("\n").join(from(lines).transform(EXPECTED)); - String actual = Joiner.on("\n").join(from(lines).transform(ACTUAL)); + String expected = + lines.stream().map(EXPECTED).map(Object::toString).collect(Collectors.joining("\n")); + String actual = + lines.stream().map(ACTUAL).map(Object::toString).collect(Collectors.joining("\n")); return lines.isEmpty() ? "" : junitStyleDiffMessage(expected, actual); } public static String junitStyleDiffMessage(Object expected, Object actual) { - return String.format(" expected:<\n%s> but was:<\n%s>", expected, actual); + return String.format( + " expected:<\n%s> but was:<\n%s>", + Strings.normaliseLineBreaks(expected.toString()), + Strings.normaliseLineBreaks(actual.toString())); } - private static Function, Object> EXPECTED = - new Function, Object>() { - @Override - public Object apply(DiffLine line) { - return line.isForNonMatch() ? line.getPrintedPatternValue() : line.getActual(); - } - }; - - private static Function, Object> ACTUAL = - new Function, Object>() { - @Override - public Object apply(DiffLine input) { - return input.getActual(); - } - }; + private static final Function, Object> EXPECTED = + line -> line.isForNonMatch() ? line.getPrintedPatternValue() : line.getActual(); + + private static final Function, Object> ACTUAL = DiffLine::getActual; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRenderer.java b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRenderer.java index 6421af7f90..6b3cd79a7e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRenderer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.github.tomakehurst.wiremock.verification.diff; +import static com.github.tomakehurst.wiremock.common.Strings.normaliseLineBreaks; import static java.lang.System.lineSeparator; import static org.apache.commons.lang3.StringUtils.isNotEmpty; import static org.apache.commons.lang3.StringUtils.repeat; @@ -95,12 +96,12 @@ private void footer(StringBuilder sb) { } private void writeLine(StringBuilder sb, String left, String right, String message) { - String[] leftLines = wrap(left).split(SEPARATOR); - String[] rightLines = wrap(right).split(SEPARATOR); + String[] leftLines = wrap(normaliseLineBreaks(left)).split(SEPARATOR); + String[] rightLines = wrap(normaliseLineBreaks(right)).split(SEPARATOR); int maxLines = Math.max(leftLines.length, rightLines.length); - writeSingleLine(sb, leftLines[0], rightLines[0], message); + writeSingleLine(sb, firstOrEmpty(leftLines), firstOrEmpty(rightLines), message); if (maxLines > 1) { for (int i = 1; i < maxLines; i++) { @@ -111,6 +112,10 @@ private void writeLine(StringBuilder sb, String left, String right, String messa } } + private static String firstOrEmpty(String[] lines) { + return lines.length > 0 ? lines[0] : ""; + } + private void writeBlankLine(StringBuilder sb) { writeSingleLine(sb, "", null, null); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/NotMatchedRenderer.java b/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/NotMatchedRenderer.java index 4871e38d07..4d89bb34cc 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/NotMatchedRenderer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/NotMatchedRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,17 @@ package com.github.tomakehurst.wiremock.verification.notmatched; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public abstract class NotMatchedRenderer implements AdminTask { @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - return render(admin, request); + public ResponseDefinition execute(Admin admin, ServeEvent serveEvent, PathParams pathParams) { + return render(admin, serveEvent); } - protected abstract ResponseDefinition render(Admin admin, Request request); + protected abstract ResponseDefinition render(Admin admin, ServeEvent serveEvent); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/PlainTextStubNotMatchedRenderer.java b/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/PlainTextStubNotMatchedRenderer.java index ca50ea0f48..4a7a83f132 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/PlainTextStubNotMatchedRenderer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/verification/notmatched/PlainTextStubNotMatchedRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,15 @@ */ package com.github.tomakehurst.wiremock.verification.notmatched; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.extension.Extensions; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import com.github.tomakehurst.wiremock.verification.NearMiss; import com.github.tomakehurst.wiremock.verification.diff.Diff; @@ -34,14 +35,19 @@ public class PlainTextStubNotMatchedRenderer extends NotMatchedRenderer { public static final String CONSOLE_WIDTH_HEADER_KEY = "X-WireMock-Console-Width"; + private final Extensions extensions; + + public PlainTextStubNotMatchedRenderer(Extensions extensions) { + this.extensions = extensions; + } + @Override - public ResponseDefinition render(Admin admin, Request request) { - LoggedRequest loggedRequest = - LoggedRequest.createFrom(request.getOriginalRequest().or(request)); + public ResponseDefinition render(Admin admin, ServeEvent serveEvent) { + LoggedRequest loggedRequest = serveEvent.getRequest(); List nearMisses = admin.findTopNearMissesFor(loggedRequest).getNearMisses(); Map customMatcherExtensions = - admin.getOptions().extensionsOfType(RequestMatcherExtension.class); + extensions.ofType(RequestMatcherExtension.class); PlainTextDiffRenderer diffRenderer = loggedRequest.containsHeader(CONSOLE_WIDTH_HEADER_KEY) diff --git a/src/main/java/org/wiremock/annotations/Beta.java b/src/main/java/org/wiremock/annotations/Beta.java new file mode 100644 index 0000000000..186673661b --- /dev/null +++ b/src/main/java/org/wiremock/annotations/Beta.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wiremock.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the class or a method represent preview Beta API that might change in the future + * as a part of the minor release. + * + * @since 3.0.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Beta { + + public String justification() default ""; +} diff --git a/src/main/java/org/wiremock/annotations/InternalAPI.java b/src/main/java/org/wiremock/annotations/InternalAPI.java new file mode 100644 index 0000000000..f085189968 --- /dev/null +++ b/src/main/java/org/wiremock/annotations/InternalAPI.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wiremock.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This item, while being declared as public or protected, is shared only for internal needs and not + * supposed to be used outside the project. The API may change in minor and patch releases without + * advance notice. + * + *

The annotation will be replaced by sealed classes after updating to Java 17. It is not + * expected to be used outside the project on its own. + * + * @since 3.0.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +public @interface InternalAPI { + + public String justification() default ""; +} diff --git a/src/main/java/wiremock/Run.java b/src/main/java/wiremock/Run.java new file mode 100644 index 0000000000..34fa9e90a2 --- /dev/null +++ b/src/main/java/wiremock/Run.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package wiremock; + +import com.github.tomakehurst.wiremock.standalone.WireMockServerRunner; + +public class Run extends WireMockServerRunner { + + public static void main(String... args) { + new Run().run(args); + } +} diff --git a/src/main/resources/swagger/schemas/request-pattern.yaml b/src/main/resources/swagger/schemas/request-pattern.yaml index 628878f0ce..4035ff8e67 100644 --- a/src/main/resources/swagger/schemas/request-pattern.yaml +++ b/src/main/resources/swagger/schemas/request-pattern.yaml @@ -8,6 +8,15 @@ example: method: POST url: /some/thing properties: + scheme: + type: string + description: The URI scheme + host: + type: object + description: 'URI host pattern to match against in the "": "" form' + port: + type: integer + description: The HTTP port number method: type: string description: The HTTP request method e.g. GET @@ -48,3 +57,32 @@ properties: description: 'Request body patterns to match against in the : { "": "" } form' items: type: object + customMatcher: + type: object + description: Custom request matcher to match against + properties: + name: + type: string + description: The matcher's name specified in the implementation of the matcher. + parameters: + type: object + multipartPatterns: + type: array + description: Multipart patterns to match against headers and body + items: + type: object + properties: + name: + type: string + matchingType: + type: string + default: ANY + enum: + - ALL + - ANY + headers: + type: object + bodyPatterns: + type: array + items: + type: object diff --git a/src/main/resources/swagger/schemas/stub-mapping.yaml b/src/main/resources/swagger/schemas/stub-mapping.yaml index 16ed220ff7..357e32042f 100644 --- a/src/main/resources/swagger/schemas/stub-mapping.yaml +++ b/src/main/resources/swagger/schemas/stub-mapping.yaml @@ -32,6 +32,24 @@ properties: postServeActions: type: object description: A map of the names of post serve action extensions to trigger and their parameters. + serveEventListeners: + type: array + description: The list of serve event listeners + items: + type: object + properties: + name: + type: string + requestPhases: + type: array + items: + type: string + enum: + - BEFORE_MATCH + - AFTER_MATCH + - AFTER_COMPLETE + parameters: + type: object metadata: type: object description: Arbitrary metadata to be used for e.g. tagging, documentation. Can also be used to find and remove stubs. diff --git a/src/main/resources/swagger/wiremock-admin-api.json b/src/main/resources/swagger/wiremock-admin-api.json index 414a24f3d5..d15a3feafe 100644 --- a/src/main/resources/swagger/wiremock-admin-api.json +++ b/src/main/resources/swagger/wiremock-admin-api.json @@ -2,7 +2,7 @@ "openapi": "3.0.0", "info": { "title": "WireMock", - "version": "2.35.1" + "version": "3.0.4" }, "externalDocs": { "description": "WireMock user documentation", @@ -971,6 +971,20 @@ } } }, + "/__admin/mappings/import": { + "post": { + "summary": "Import stub mappings", + "description": "Import given stub mappings to the backing store", + "tags": [ + "Stub Mappings" + ], + "responses": { + "200": { + "description": "Successfully imported" + } + } + } + }, "/__admin/mappings/{stubMappingId}": { "parameters": [ { diff --git a/src/main/resources/swagger/wiremock-admin-api.yaml b/src/main/resources/swagger/wiremock-admin-api.yaml index ee6e5b066d..164a428a5e 100644 --- a/src/main/resources/swagger/wiremock-admin-api.yaml +++ b/src/main/resources/swagger/wiremock-admin-api.yaml @@ -2,7 +2,7 @@ openapi: 3.0.0 info: title: WireMock - version: 2.35.1 + version: 3.0.4 externalDocs: description: WireMock user documentation @@ -107,6 +107,16 @@ paths: '200': description: Successfully saved + /__admin/mappings/import: + post: + summary: Import stub mappings + description: Import given stub mappings to the backing store + tags: + - Stub Mappings + responses: + '200': + description: Successfully imported + /__admin/mappings/{stubMappingId}: parameters: - description: The UUID of stub mapping @@ -301,7 +311,7 @@ paths: application/json: example: $ref: "examples/requests.yaml" - + /__admin/requests/remove-by-metadata: post: summary: Delete requests mappings matching metadata @@ -608,4 +618,4 @@ components: items: $ref: "schemas/logged-request.yaml" example: - $ref: 'examples/near-misses.yaml' \ No newline at end of file + $ref: 'examples/near-misses.yaml' diff --git a/src/main/resources/wiremock/joptsimple/HelpFormatterMessages.properties b/src/main/resources/wiremock/joptsimple/HelpFormatterMessages.properties new file mode 100644 index 0000000000..7bc4887bfd --- /dev/null +++ b/src/main/resources/wiremock/joptsimple/HelpFormatterMessages.properties @@ -0,0 +1,16 @@ +# +# This is copied from joptsimple. +# Properties prefixed with "wiremock" to match after relocation into standalone jar +# Without this, the "--help" command will throw MissingResourceException +# +wiremock.joptsimple.BuiltinHelpFormatter.no.options.specified = No options specified +wiremock.joptsimple.BuiltinHelpFormatter.non.option.arguments.header = Non-option arguments: +wiremock.joptsimple.BuiltinHelpFormatter.option.header.with.required.indicator = Option (* = required) +wiremock.joptsimple.BuiltinHelpFormatter.option.divider.with.required.indicator = --------------------- +wiremock.joptsimple.BuiltinHelpFormatter.option.header = Option +wiremock.joptsimple.BuiltinHelpFormatter.option.divider = ------ +wiremock.joptsimple.BuiltinHelpFormatter.description.header = Description +wiremock.joptsimple.BuiltinHelpFormatter.description.divider = ----------- +wiremock.joptsimple.BuiltinHelpFormatter.default.value.header = default: +wiremock.joptsimple.AlternativeLongOptionSpec.description = Alternative form of long options +wiremock.joptsimple.AlternativeLongOptionSpec.arg.description = opt=value diff --git a/src/test/java/com/github/tomakehurst/wiremock/AdminApiTest.java b/src/test/java/com/github/tomakehurst/wiremock/AdminApiTest.java index 93204f6e38..77d8709b55 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/AdminApiTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/AdminApiTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,6 @@ import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; -import com.google.common.collect.ImmutableMap; import com.toomuchcoding.jsonassert.JsonAssertion; import com.toomuchcoding.jsonassert.JsonVerifiable; import java.io.File; @@ -313,7 +312,7 @@ public void getLoggedRequestsWithLimitLargerThanResults() throws Exception { } @Test - public void getLoggedRequestById() throws Exception { + public void getLoggedRequestById() { for (int i = 1; i <= 3; i++) { testClient.get("/received-request/" + i); } @@ -487,7 +486,7 @@ void getScenarios() { assertThat(body, jsonPartEquals("scenarios[0].id", "my-scenario")); assertThat(body, jsonPartEquals("scenarios[0].name", "my-scenario")); assertThat(body, jsonPartEquals("scenarios[0].state", "\"2\"")); - assertThat(body, jsonPartEquals("scenarios[0].possibleStates", asList("2", "3", "Started"))); + assertThat(body, jsonPartEquals("scenarios[0].possibleStates", asList("Started", "2", "3"))); assertThat(body, jsonPartEquals("scenarios[0].mappings[0].request.url", "/one")); } @@ -934,15 +933,7 @@ public void fetchStubWithMetadata() { get("/with-metadata") .withId(id) .withMetadata( - ImmutableMap.of( - "one", - 1, - "two", - "2", - "three", - true, - "four", - ImmutableMap.of("five", "55555")))); + Map.of("one", 1, "two", "2", "three", true, "four", Map.of("five", "55555")))); WireMockResponse response = testClient.get("/__admin/mappings/" + id); @@ -1280,6 +1271,68 @@ public void returnsSensibleErrorIfStubIdIsNull() { "errors[0].title", "Query parameter matchingStub value '' is not a valid UUID")); } + @Test + void returnsDefaultStubMappingInServeEventWhenRequestNotMatched() { + testClient.get("/wrong-request/1"); + + WireMockResponse serveEventsResponse = testClient.get("/__admin/requests"); + + String data = serveEventsResponse.content(); + assertThat(data, jsonPartEquals("requests[0].stubMapping.id", "\"${json-unit.any-string}\"")); + assertThat(data, jsonPartEquals("requests[0].stubMapping.response.status", 404)); + } + + @Test + void returnsBadRequestWhenAttemptingToGetByNonUuid() { + WireMockResponse response = testClient.get("/__admin/mappings/not-a-uuid"); + assertThat(response.statusCode(), is(400)); + assertThat( + response.content(), jsonPartEquals("errors[0].title", "not-a-uuid is not a valid UUID")); + } + + @Test + void returnsNotFoundWhenAttemptingToGetNonExistentStub() { + assertThat(testClient.get("/__admin/mappings/" + UUID.randomUUID()).statusCode(), is(404)); + } + + @Test + void returnsBadRequestWhenAttemptingToEditByNonUuid() { + assertThat(testClient.putJson("/__admin/mappings/not-a-uuid", "{}").statusCode(), is(400)); + } + + @Test + void returnsNotFoundWhenAttemptingToEditNonExistentStub() { + assertThat(testClient.put("/__admin/mappings/" + UUID.randomUUID()).statusCode(), is(404)); + } + + @Test + void returnsBadRequestWhenAttemptingToRemoveByNonUuid() { + assertThat(testClient.delete("/__admin/mappings/not-a-uuid").statusCode(), is(400)); + } + + @Test + void returnsNotFoundWhenAttemptingToRemoveNonExistentStub() { + assertThat(testClient.put("/__admin/mappings/" + UUID.randomUUID()).statusCode(), is(404)); + } + + @Test + void returnsBadRequestWhenAttemptingToGetServeEventByNonUuid() { + WireMockResponse response = testClient.get("/__admin/requests/not-a-uuid"); + assertThat(response.statusCode(), is(400)); + assertThat( + response.content(), jsonPartEquals("errors[0].title", "not-a-uuid is not a valid UUID")); + } + + @Test + void returnsNotFoundWhenAttemptingToGetServeEventByNonExistentId() { + assertThat(testClient.get("/__admin/requests/" + UUID.randomUUID()).statusCode(), is(404)); + } + + @Test + void returnsBadRequestWhenAttemptingToRemoveServeEventByNonUuid() { + assertThat(testClient.delete("/__admin/requests/not-a-uuid").statusCode(), is(400)); + } + public static class TestExtendedSettingsData { public String name; } diff --git a/src/test/java/com/github/tomakehurst/wiremock/BrowserProxyAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/BrowserProxyAcceptanceTest.java index 2ff2bdb51a..933f9ebf33 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/BrowserProxyAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/BrowserProxyAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import com.github.tomakehurst.wiremock.global.GlobalSettings; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import org.junit.jupiter.api.AfterEach; @@ -41,7 +43,7 @@ class BrowserProxyAcceptanceTest { public void init() { testClient = new WireMockTestClient(target.getPort()); - proxy = new WireMockServer(wireMockConfig().dynamicPort().enableBrowserProxying(true)); + proxy = new WireMockServer(wireMockConfig().port(8111).enableBrowserProxying(true)); proxy.start(); } @@ -54,7 +56,7 @@ public void stopServer() { @Test public void canProxyHttp() { - target.stubFor(get(urlEqualTo("/whatever")).willReturn(aResponse().withBody("Got it"))); + target.stubFor(get("/whatever").willReturn(aResponse().withBody("Got it"))); assertThat( testClient.getViaProxy(target.url("/whatever"), proxy.port()).content(), is("Got it")); @@ -70,6 +72,57 @@ public void passesQueryParameters() { is(200)); } + @Test + public void returnNotConfiguredResponseOnPassThroughDisabled() { + target.stubFor(get("/whatever").willReturn(ok("Got it"))); + + GlobalSettings newSettings = + target.getGlobalSettings().getSettings().copy().proxyPassThrough(false).build(); + target.updateGlobalSettings(newSettings); + + assertThat( + testClient.getViaProxy(target.url("/something"), proxy.port()).statusCode(), is(404)); + } + + @Test + public void returnStubbedResponseOnPassThroughDisabled() { + proxy.updateGlobalSettings( + proxy.getGlobalSettings().getSettings().copy().proxyPassThrough(false).build()); + + proxy.stubFor(get("/whatever").willReturn(ok("Default response"))); + + WireMockResponse wireMockResponse = + testClient.getViaProxy(target.url("/whatever"), proxy.port()); + + assertThat(wireMockResponse.statusCode(), is(200)); + assertThat(wireMockResponse.content(), is("Default response")); + } + + @Test + public void returnStubbedResponseOnPassThroughEnabled() { + // by default, passProxyThrough is true/enabled + target.stubFor(get("/whatever").willReturn(ok("Got it"))); + + WireMockResponse wireMockResponse = + testClient.getViaProxy(target.url("/whatever"), proxy.port()); + assertThat(wireMockResponse.statusCode(), is(200)); + assertThat(wireMockResponse.content(), is("Got it")); + } + + @Test + void disablingPassThroughDoesNotAffectReverseProxying() { + proxy.updateGlobalSettings( + proxy.getGlobalSettings().getSettings().copy().proxyPassThrough(false).build()); + + proxy.stubFor(proxyAllTo(target.baseUrl())); + + target.stubFor(get("/whatever").willReturn(ok("Got it"))); + + WireMockTestClient testClient = new WireMockTestClient(proxy.port()); + + assertThat(testClient.get("/whatever").content(), is("Got it")); + } + @Nested class Disabled { diff --git a/src/test/java/com/github/tomakehurst/wiremock/ConcurrentProxyingTest.java b/src/test/java/com/github/tomakehurst/wiremock/ConcurrentProxyingTest.java index 7f91aa7367..c59da604c3 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ConcurrentProxyingTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ConcurrentProxyingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,12 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.testsupport.TestFiles.defaultTestFilesRoot; -import static com.google.common.collect.Lists.newArrayList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -57,19 +57,16 @@ public void concurrent() throws Exception { ExecutorService executor = Executors.newFixedThreadPool(20); - List> results = newArrayList(); + List> results = new ArrayList<>(); for (int i = 0; i < 100; i++) { results.add( executor.submit( - new Runnable() { - @Override - public void run() { - assertThat(client.get("/plain-example1.txt").content(), is("Example 1")); - assertThat(client.get("/plain-example2.txt").content(), is("Example 2")); - assertThat(client.get("/plain-example3.txt").content(), is("Example 3")); - assertThat(client.get("/plain-example4.txt").content(), is("Example 4")); - assertThat(client.get("/plain-example5.txt").content(), is("Example 5")); - } + () -> { + assertThat(client.get("/plain-example1.txt").content(), is("Example 1")); + assertThat(client.get("/plain-example2.txt").content(), is("Example 2")); + assertThat(client.get("/plain-example3.txt").content(), is("Example 3")); + assertThat(client.get("/plain-example4.txt").content(), is("Example 4")); + assertThat(client.get("/plain-example5.txt").content(), is("Example 5")); })); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/CookieMatchingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/CookieMatchingAcceptanceTest.java index 703324a878..80a4deb9e0 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/CookieMatchingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/CookieMatchingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.common.ContentTypes.COOKIE; import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; -import static com.google.common.net.HttpHeaders.COOKIE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; diff --git a/src/test/java/com/github/tomakehurst/wiremock/EditStubMappingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/EditStubMappingAcceptanceTest.java index c531ed6bef..e3200d95ea 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/EditStubMappingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/EditStubMappingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,12 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.google.common.collect.FluentIterable.from; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Predicate; import java.util.UUID; +import java.util.function.Predicate; import org.junit.jupiter.api.Test; public class EditStubMappingAcceptanceTest extends AcceptanceTestBase { @@ -42,18 +41,16 @@ public void canEditAnExistingStubMapping() { assertThat(testClient.get("/edit-this").content(), is("Modified")); int editThisStubCount = - from(wireMockServer.listAllStubMappings().getMappings()) - .filter(withUrl("/edit-this")) - .size(); + (int) + wireMockServer.listAllStubMappings().getMappings().stream() + .filter(withUrl("/edit-this")) + .count(); assertThat(editThisStubCount, is(1)); } private Predicate withUrl(final String url) { - return new Predicate() { - public boolean apply(StubMapping mapping) { - return (mapping.getRequest().getUrl() != null && mapping.getRequest().getUrl().equals(url)); - } - }; + return mapping -> + (mapping.getRequest().getUrl() != null && mapping.getRequest().getUrl().equals(url)); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/ExtensionFactoryTest.java b/src/test/java/com/github/tomakehurst/wiremock/ExtensionFactoryTest.java new file mode 100644 index 0000000000..3f53e66e56 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/ExtensionFactoryTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.noContent; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; +import static com.github.tomakehurst.wiremock.testsupport.TestFiles.defaultTestFilesRoot; +import static net.javacrumbs.jsonunit.JsonMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.admin.Router; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.Strings; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.extension.AdminApiExtension; +import com.github.tomakehurst.wiremock.extension.Extensions; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.store.Stores; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.io.File; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class ExtensionFactoryTest { + + WireMockServer wm; + WireMockTestClient client; + + @AfterEach + void stopServer() { + if (wm != null) { + wm.stop(); + } + } + + @Test + void injectsCoreServicesOnConstructionByFactory() { + initialiseWireMockServer( + options() + .dynamicPort() + .withRootDirectory(defaultTestFilesRoot()) + .stubCorsEnabled(true) + .templatingEnabled(false) + .extensions( + services -> + List.of( + new MiscInfoApi( + services.getAdmin(), + services.getOptions(), + services.getStores(), + services.getFiles(), + services.getExtensions())))); + + client.get("/something"); + client.get("/something"); + + String content = client.get("/__admin/misc-info").content(); + + assertThat(content, jsonPartEquals("example1", "Example 1")); + assertThat( + content, + jsonPartMatches("fileSourcePath", endsWith("test-file-root" + File.separator + "__files"))); + assertThat(content, jsonPartEquals("requestCount", 2)); + assertThat(content, jsonPartEquals("stubCorsEnabled", true)); + assertThat( + content, jsonPartEquals("extensionCount", 3)); // Includes the two service loaded extensions + } + + @Test + void usesExtensionFactoryLoadedViaServiceLoader() { + initialiseWireMockServer( + options().dynamicPort().withRootDirectory(defaultTestFilesRoot()).templatingEnabled(false)); + + wm.stubFor(get("/transform-this").willReturn(noContent().withTransformers("loader-test"))); + + client.get("/just-count-this"); + + assertThat(client.get("/transform-this").content(), is("Request count 1")); + } + + @Test + void usesExtensionInstanceLoadedViaServiceLoader() { + initialiseWireMockServer( + options().dynamicPort().withRootDirectory(defaultTestFilesRoot()).templatingEnabled(false)); + + wm.stubFor( + get("/transform-this").willReturn(noContent().withTransformers("instance-loader-test"))); + + assertThat(client.get("/transform-this").content(), is("Expected stuff")); + } + + private void initialiseWireMockServer(WireMockConfiguration options) { + wm = new WireMockServer(options); + wm.start(); + client = new WireMockTestClient(wm.port()); + } + + public static class MiscInfoApi implements AdminApiExtension { + + private final Admin admin; + private final Options options; + private final Stores stores; + private final FileSource fileSource; + + private final Extensions extensions; + + public MiscInfoApi( + Admin admin, Options options, Stores stores, FileSource fileSource, Extensions extensions) { + this.admin = admin; + this.options = options; + this.stores = stores; + this.fileSource = fileSource; + this.extensions = extensions; + } + + @Override + public String getName() { + return "request-counter"; + } + + @Override + public void contributeAdminApiRoutes(Router router) { + router.add( + GET, + "/misc-info", + (ignored, serveEvent, pathParams) -> { + String example1 = + Strings.stringFromBytes(stores.getFilesBlobStore().get("plain-example1.txt").get()); + String fileSourcePath = fileSource.getPath(); + int requestCount = admin.getServeEvents().getRequests().size(); + return ResponseDefinition.okForJson( + Map.of( + "example1", example1, + "fileSourcePath", fileSourcePath, + "requestCount", requestCount, + "stubCorsEnabled", options.getStubCorsEnabled(), + "extensionCount", extensions.getCount())); + }); + } + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/GlobalSettingsAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/GlobalSettingsAcceptanceTest.java index b22191d430..f9605eae46 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/GlobalSettingsAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/GlobalSettingsAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,14 @@ */ package com.github.tomakehurst.wiremock; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.givenThat; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertFalse; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.extension.Parameters; @@ -88,4 +92,13 @@ public void setAndRetrieveExtendedSettings() { assertThat(fetchedSettings.getExtended().getString("mySetting"), is("setting-value")); } + + @Test + public void setAndRetrieveProxyPassThroughSettings() { + WireMock.updateSettings(GlobalSettings.builder().proxyPassThrough(false).build()); + + GlobalSettings fetchedSettings = WireMock.getSettings(); + + assertFalse(fetchedSettings.getProxyPassThrough()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java index 7e9ede2843..b8dbb1eb68 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java @@ -54,6 +54,7 @@ public void servesGzippedResponseForGet() { assertThat(response.firstHeader("Content-Encoding"), is("gzip")); assertThat(response.firstHeader("Transfer-Encoding"), is("chunked")); assertThat(response.headers().containsKey("Content-Length"), is(false)); + assertThat(response.headers().containsKey("Vary"), is(false)); byte[] gzippedContent = response.binaryContent(); @@ -62,7 +63,7 @@ public void servesGzippedResponseForGet() { } @Test - public void servesGzippedResponseForPost() throws Exception { + public void servesGzippedResponseForPost() { wireMockServer.stubFor(post("/gzip-response").willReturn(ok("body text"))); WireMockResponse response = diff --git a/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java b/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java index 7c686175be..baf270bf4e 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java +++ b/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,14 +20,17 @@ import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.http2.client.HTTP2Client; import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; public class Http2ClientFactory { public static HttpClient create() { - SslContextFactory sslContextFactory = new SslContextFactory.Client(true); - HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client()); - HttpClient httpClient = new HttpClient(transport, sslContextFactory); + final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true); + final ClientConnector connector = new ClientConnector(); + connector.setSslContextFactory(sslContextFactory); + HttpClientTransport transport = new HttpClientTransportOverHTTP2(new HTTP2Client(connector)); + HttpClient httpClient = new HttpClient(transport); httpClient.setFollowRedirects(false); try { diff --git a/src/test/java/com/github/tomakehurst/wiremock/HttpsAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/HttpsAcceptanceTest.java index a1963bea7e..069ca01299 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/HttpsAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/HttpsAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.http.Fault; import com.github.tomakehurst.wiremock.http.HttpClientFactory; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.google.common.io.Resources; import java.io.FileInputStream; import java.io.IOException; @@ -299,6 +300,29 @@ public void proxyingFailsWhenTargetServiceRequiresClientCertificatesAndProxyDoes assertThat(response.getCode(), is(500)); } + @Test + void doesNotTreatPlainHttpsRequestAsBrowserProxyRequest() throws Exception { + proxy = + new WireMockServer( + wireMockConfig().dynamicPort().dynamicHttpsPort().enableBrowserProxying(true)); + proxy.start(); + proxy.stubFor(get("/no-proxying-thanks").willReturn(ok("proxyless"))); + + httpClient = HttpClientFactory.createClient(); + + String url = "https://localhost:" + proxy.httpsPort() + "/no-proxying-thanks"; + HttpGet get = new HttpGet(url); + int status = httpClient.execute(get, HttpResponse::getCode); + assertThat(status, is(200)); + + ServeEvent serveEvent = + proxy.getAllServeEvents().stream() + .filter(event -> event.getRequest().getUrl().equals("/no-proxying-thanks")) + .findFirst() + .get(); + assertThat(serveEvent.getRequest().isBrowserProxyRequest(), is(false)); + } + private String url(String path) { return String.format("https://localhost:%d%s", wireMockServer.httpsPort(), path); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/HttpsBrowserProxyAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/HttpsBrowserProxyAcceptanceTest.java index 0fd2a28189..e7347b1b73 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/HttpsBrowserProxyAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/HttpsBrowserProxyAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,7 +140,7 @@ public void canProxyHttpsUsingHttp2InBrowserHttpsProxyMode() throws Exception { ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration(); HttpProxy httpProxy = new HttpProxy(new Origin.Address("localhost", proxy.getHttpsPort()), true); - proxyConfig.getProxies().add(httpProxy); + proxyConfig.addProxy(httpProxy); target.stubFor(get(urlEqualTo("/whatever")).willReturn(aResponse().withBody("Got it"))); @@ -346,10 +346,9 @@ public void failsIfCaKeystorePathIsNotAKeystore() throws IOException { IOException.class, () -> { new WireMockServer( - options() - .enableBrowserProxying(true) - .caKeystorePath(Files.createTempFile("notakeystore", "jks").toString())) - .start(); + options() + .enableBrowserProxying(true) + .caKeystorePath(Files.createTempFile("notakeystore", "jks").toString())); }); } @@ -357,11 +356,9 @@ public void failsIfCaKeystorePathIsNotAKeystore() throws IOException { public void failsIfCaKeystoreDoesNotContainACaCertificate() throws Exception { assertThrows( FatalStartupException.class, - () -> { - new WireMockServer( - options().enableBrowserProxying(true).caKeystorePath(emptyKeyStore().toString())) - .start(); - }); + new WireMockServer( + options().enableBrowserProxying(true).caKeystorePath(emptyKeyStore().toString())) + ::start); } @Test diff --git a/src/test/java/com/github/tomakehurst/wiremock/JsonSchemaMatchingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/JsonSchemaMatchingAcceptanceTest.java new file mode 100644 index 0000000000..af2419507f --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/JsonSchemaMatchingAcceptanceTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.testsupport.TestFiles.file; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import org.junit.jupiter.api.Test; + +public class JsonSchemaMatchingAcceptanceTest extends AcceptanceTestBase { + + @Test + void matchesStubWhenRequestBodyJsonValidatesAgainstSchema() { + String schema = file("schema-validation/new-pet.schema.json"); + String json = file("schema-validation/new-pet.json"); + + stubFor( + post(urlPathEqualTo("/schema-match")) + .withRequestBody(matchingJsonSchema(schema)) + .willReturn(ok())); + + WireMockResponse response = testClient.postJson("/schema-match", json); + + assertThat(response.statusCode(), is(200)); + } + + @Test + void doesNotMatchStubWhenRequestBodyJsonDoesNotValidateAgainstSchema() { + String schema = file("schema-validation/new-pet.schema.json"); + String json = file("schema-validation/new-pet.invalid.json"); + + stubFor( + post(urlPathEqualTo("/schema-match")) + .withRequestBody(matchingJsonSchema(schema)) + .willReturn(ok())); + + WireMockResponse response = testClient.postJson("/schema-match", json); + + assertThat(response.statusCode(), is(404)); + } + + @Test + void doesNotMatchStubWhenRequestBodyIsNotValidJson() { + String schema = file("schema-validation/new-pet.schema.json"); + String json = file("schema-validation/new-pet.unparseable.json"); + + stubFor( + post(urlPathEqualTo("/schema-match")) + .withRequestBody(matchingJsonSchema(schema)) + .willReturn(ok())); + + WireMockResponse response = testClient.postJson("/schema-match", json); + + assertThat(response.statusCode(), is(404)); + } + + // TODO: Diffs +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/JvmProxyConfigAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/JvmProxyConfigAcceptanceTest.java index 2d45a14814..42d1a8f675 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/JvmProxyConfigAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/JvmProxyConfigAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,13 @@ */ package com.github.tomakehurst.wiremock; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.tomakehurst.wiremock.http.HttpClientFactory; import com.github.tomakehurst.wiremock.http.JvmProxyConfigurer; -import com.google.common.io.ByteStreams; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; @@ -34,7 +31,13 @@ import org.apache.hc.core5.http.io.entity.EntityUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ClearSystemProperty; +@ClearSystemProperty(key = "http.proxyHost") +@ClearSystemProperty(key = "http.proxyPort") +@ClearSystemProperty(key = "https.proxyHost") +@ClearSystemProperty(key = "https.proxyPort") +@ClearSystemProperty(key = "http.nonProxyHosts") public class JvmProxyConfigAcceptanceTest { WireMockServer wireMockServer; @@ -114,7 +117,7 @@ public void restoresPreviousSettings() { private String getContentUsingDefaultJvmHttpClient(String url) throws Exception { final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); try (InputStream in = urlConnection.getInputStream()) { - return new String(ByteStreams.toByteArray(in)); + return new String(in.readAllBytes()); } } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/MappingsLoaderAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/MappingsLoaderAcceptanceTest.java index 5430aced6a..94243130b6 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/MappingsLoaderAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/MappingsLoaderAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.is; import com.github.tomakehurst.wiremock.common.SingleRootFileSource; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.standalone.JsonFileMappingsSource; @@ -60,7 +61,8 @@ private void buildWireMock(Options options) { public void mappingsLoadedFromJsonFiles() { buildWireMock(configuration); wireMockServer.loadMappingsUsing( - new JsonFileMappingsSource(new SingleRootFileSource(filePath("test-requests")))); + new JsonFileMappingsSource( + new SingleRootFileSource(filePath("test-requests")), new FilenameMaker())); WireMockResponse response = testClient.get("/canned/resource/1"); assertThat(response.statusCode(), is(200)); @@ -80,7 +82,8 @@ public void loadsStubMappingsFromAMixtureOfSingleAndMultiStubFiles() { buildWireMock(configuration); wireMockServer.resetMappings(); wireMockServer.loadMappingsUsing( - new JsonFileMappingsSource(new SingleRootFileSource(filePath("multi-stub")))); + new JsonFileMappingsSource( + new SingleRootFileSource(filePath("multi-stub")), new FilenameMaker())); List stubs = wireMockServer.listAllStubMappings().getMappings(); diff --git a/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java index a58f03ec61..9f18ea2b75 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.testsupport.MultipartBody.part; import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; import static org.apache.hc.core5.http.ContentType.MULTIPART_FORM_DATA; import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN; import static org.hamcrest.MatcherAssert.assertThat; @@ -34,6 +35,7 @@ import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; public class MultipartBodyMatchingAcceptanceTest extends AcceptanceTestBase { @@ -149,4 +151,20 @@ public void multipartBodiesCanBeMatchedWhenStubsWithOtherBodyMatchTypesArePresen assertThat(response.statusCode(), is(200)); } + + @Test + @Timeout(2) + void handlesLargeMultipartBody() { + stubFor( + post("/multipart") + .withMultipartRequestBody( + aMultipart().withHeader("Content-Disposition", containing("vlarge"))) + .willReturn(ok())); + + WireMockResponse response = + testClient.postWithMultiparts( + "/multipart", singletonList(part("vlarge", randomAlphanumeric(300000), TEXT_PLAIN))); + + assertThat(response.statusCode(), is(200)); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/NotMatchedPageAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/NotMatchedPageAcceptanceTest.java index 7129cd334b..61021f80bc 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/NotMatchedPageAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/NotMatchedPageAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,32 +16,30 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static com.github.tomakehurst.wiremock.testsupport.TestFiles.file; import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalsMultiLine; import static com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer.CONSOLE_WIDTH_HEADER_KEY; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Gzip; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.extension.requestfilter.FieldTransformer; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterAction; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestWrapper; import com.github.tomakehurst.wiremock.extension.requestfilter.StubRequestFilter; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; import com.github.tomakehurst.wiremock.verification.notmatched.NotMatchedRenderer; -import java.util.List; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.junit.jupiter.api.AfterEach; @@ -143,7 +141,7 @@ public void showsADefaultMessageWhenNoStubsWerePresent() { WireMockResponse response = testClient.get("/no-stubs-to-match"); assertThat(response.statusCode(), is(404)); - assertThat(response.firstHeader(CONTENT_TYPE), is("text/plain")); + assertThat(response.firstHeader(CONTENT_TYPE), startsWith("text/plain")); assertThat( response.content(), is("No response could be served as there are no stub mappings in this WireMock instance.")); @@ -153,16 +151,17 @@ public void showsADefaultMessageWhenNoStubsWerePresent() { public void supportsCustomNoMatchRenderer() { configure( wireMockConfig() - .notMatchedRenderer( - new NotMatchedRenderer() { - @Override - protected ResponseDefinition render(Admin admin, Request request) { - return ResponseDefinitionBuilder.responseDefinition() - .withStatus(403) - .withBody("No you don't!") - .build(); - } - })); + .notMatchedRendererFactory( + extensions -> + new NotMatchedRenderer() { + @Override + protected ResponseDefinition render(Admin admin, ServeEvent serveEvent) { + return ResponseDefinitionBuilder.responseDefinition() + .withStatus(403) + .withBody("No you don't!") + .build(); + } + })); WireMockResponse response = testClient.get("/should-not-match"); @@ -224,13 +223,7 @@ public RequestFilterAction filter(Request request) { Request wrappedRequest = RequestWrapper.create() .transformHeader( - "X-My-Header", - new FieldTransformer>() { - @Override - public List transform(List source) { - return singletonList("modified value"); - } - }) + "X-My-Header", source -> singletonList("modified value")) .wrap(request); return RequestFilterAction.continueWith(wrappedRequest); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/PostServeActionExtensionTest.java b/src/test/java/com/github/tomakehurst/wiremock/PostServeActionExtensionTest.java index be53abbcdf..c39f4bf18c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/PostServeActionExtensionTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/PostServeActionExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ import static com.github.tomakehurst.wiremock.PostServeActionExtensionTest.CounterNameParameter.counterNameParameter; import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.responseDefinition; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; -import static com.google.common.base.MoreObjects.firstNonNull; import static java.util.concurrent.TimeUnit.SECONDS; import static net.javacrumbs.jsonunit.JsonMatchers.jsonPartEquals; import static org.awaitility.Awaitility.await; @@ -28,17 +28,13 @@ import static org.hamcrest.Matchers.is; import com.fasterxml.jackson.annotation.JsonProperty; -import com.github.tomakehurst.wiremock.admin.AdminTask; import com.github.tomakehurst.wiremock.admin.Router; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.extension.AdminApiExtension; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.PostServeAction; -import com.github.tomakehurst.wiremock.http.Request; -import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; @@ -243,21 +239,11 @@ public void multipleActionsOfTheSameNameCanBeSpecifiedAsAJsonArray() { } private Callable getValue(final AtomicInteger value) { - return new Callable() { - @Override - public Integer call() throws Exception { - return value.get(); - } - }; + return value::get; } private Callable getContent(final String url) { - return new Callable() { - @Override - public String call() throws Exception { - return client.get(url).content(); - } - }; + return () -> client.get(url).content(); } public static class NamedCounterAction extends PostServeAction implements AdminApiExtension { @@ -274,13 +260,10 @@ public void contributeAdminApiRoutes(Router router) { router.add( GET, "/named-counter/{name}", - new AdminTask() { - @Override - public ResponseDefinition execute(Admin admin, Request request, PathParams pathParams) { - String name = pathParams.get("name"); - Integer count = firstNonNull(counters.get(name), 0); - return responseDefinition().withStatus(200).withBody(String.valueOf(count)).build(); - } + (admin, serveEvent, pathParams) -> { + String name = pathParams.get("name"); + Integer count = getFirstNonNull(counters.get(name), 0); + return responseDefinition().withStatus(200).withBody(String.valueOf(count)).build(); }); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/ProxyAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ProxyAcceptanceTest.java index 8c7effb988..a69a73e361 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ProxyAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ProxyAcceptanceTest.java @@ -16,12 +16,10 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_ENCODING; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getLast; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; -import static com.google.common.collect.Iterables.getLast; -import static com.google.common.net.HttpHeaders.CONTENT_ENCODING; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN; import static org.hamcrest.MatcherAssert.assertThat; @@ -33,14 +31,11 @@ import com.github.tomakehurst.wiremock.common.ProxySettings; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.HttpClientFactory; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import com.google.common.base.Stopwatch; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.io.InputStream; @@ -224,24 +219,21 @@ public void successfullyGetsResponseBinaryResponses() throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); server.createContext( "/binary", - new HttpHandler() { - @Override - public void handle(HttpExchange exchange) throws IOException { - InputStream request = exchange.getRequestBody(); - - byte[] buffy = new byte[10]; - request.read(buffy); - - if (Arrays.equals(buffy, bytes)) { - exchange.sendResponseHeaders(200, bytes.length); - - OutputStream out = exchange.getResponseBody(); - out.write(bytes); - out.close(); - } else { - exchange.sendResponseHeaders(500, 0); - exchange.close(); - } + exchange -> { + InputStream request = exchange.getRequestBody(); + + byte[] buffy = new byte[10]; + request.read(buffy); + + if (Arrays.equals(buffy, bytes)) { + exchange.sendResponseHeaders(200, bytes.length); + + OutputStream out = exchange.getResponseBody(); + out.write(bytes); + out.close(); + } else { + exchange.sendResponseHeaders(500, 0); + exchange.close(); } }); server.start(); @@ -604,7 +596,7 @@ public void removesPrefixFromProxyRequestWhenMatching() { @Test public void removesPrefixFromProxyRequestWhenResponseTransformersAreUsed() { - init(wireMockConfig().extensions(new ResponseTemplateTransformer(true))); + init(wireMockConfig().templatingEnabled(true).globalTemplating(true)); proxy.register( get("/other/service/doc/123") @@ -693,6 +685,56 @@ void preventsProxyingToIpResolvedFromHostname() { is("The target proxy address is denied in WireMock's configuration.")); } + @Test + void proxyRequestWillNotTimeoutIfProxyResponseIsFastEnough() { + init(wireMockConfig().proxyTimeout(1000)); + + target.register( + get(urlEqualTo("/proxied/resource?param=value")) + .willReturn( + aResponse() + .withFixedDelay(500) + .withStatus(200) + .withHeader("Content-Type", "text/plain") + .withBody("Proxied content"))); + + proxy.register( + any(urlEqualTo("/proxied/resource?param=value")) + .atPriority(10) + .willReturn(aResponse().proxiedFrom(targetServiceBaseUrl))); + + WireMockResponse response = testClient.get("/proxied/resource?param=value"); + + assertThat(response.content(), is("Proxied content")); + assertThat(response.firstHeader("Content-Type"), is("text/plain")); + } + + @Test + void proxyRequestWillTimeoutIfProxyResponseIsTooSlow() { + init(wireMockConfig().proxyTimeout(1000)); + + target.register( + get(urlEqualTo("/proxied/resource?param=value")) + .willReturn( + aResponse() + .withFixedDelay(1500) + .withStatus(200) + .withHeader("Content-Type", "text/plain") + .withBody("Proxied content"))); + + proxy.register( + any(urlEqualTo("/proxied/resource?param=value")) + .atPriority(10) + .willReturn(aResponse().proxiedFrom(targetServiceBaseUrl))); + + WireMockResponse response = testClient.get("/proxied/resource?param=value"); + + assertThat( + response.content(), + startsWith("Network failure trying to make a proxied request from WireMock")); + assertThat(response.statusCode(), is(500)); + } + private void register200StubOnProxyAndTarget(String url) { target.register(get(urlEqualTo(url)).willReturn(aResponse().withStatus(200))); proxy.register(get(urlEqualTo(url)).willReturn(aResponse().proxiedFrom(targetServiceBaseUrl))); diff --git a/src/test/java/com/github/tomakehurst/wiremock/RecordApiAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RecordApiAcceptanceTest.java index b33be21caa..872cdcb034 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/RecordApiAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/RecordApiAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,7 @@ import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalToJson; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.findMappingWithUrl; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Iterables.find; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,7 +33,6 @@ import com.github.tomakehurst.wiremock.testsupport.NonGlobalStubMappingTransformer; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; -import com.google.common.base.Predicate; import java.util.UUID; import org.apache.hc.core5.http.io.entity.StringEntity; import org.junit.jupiter.api.AfterEach; @@ -80,7 +78,7 @@ public void proxyServerShutdown() { + " \"mappings\": [ \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/foo/bar\", \n" + + " \"url\" : \"/foo/bar/baz\", \n" + " \"method\" : \"GET\" \n" + " }, \n" + " \"response\" : { \n" @@ -89,13 +87,13 @@ public void proxyServerShutdown() { + " }, \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/foo/bar/baz\", \n" + + " \"url\" : \"/foo/bar\", \n" + " \"method\" : \"GET\" \n" + " }, \n" + " \"response\" : { \n" + " \"status\" : 200 \n" + " } \n" - + " }, \n" + + " } \n" + " ] \n" + "} "; @@ -130,22 +128,22 @@ public void returnsRequestsWithDefaultOptions() throws Exception { + " \"mappings\": [ \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/foo/bar\", \n" + + " \"url\" : \"/foo/bar/baz\", \n" + " \"method\" : \"GET\" \n" + " }, \n" + " \"response\" : { \n" + " \"status\" : 200 \n" + " } \n" - + " }, \n" + + " }, \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/foo/bar/baz\", \n" + + " \"url\" : \"/foo/bar\", \n" + " \"method\" : \"GET\" \n" + " }, \n" + " \"response\" : { \n" + " \"status\" : 200 \n" + " } \n" - + " } \n" + + " } \n" + " ] \n" + "} "; @@ -253,14 +251,10 @@ public void returnsStubsFromNonProxiedRequestsWhenRequested() { } private ServeEvent findServeEventWithRequestUrl(final String url) { - return find( - proxyingService.getAllServeEvents(), - new Predicate() { - @Override - public boolean apply(ServeEvent input) { - return url.equals(input.getRequest().getUrl()); - } - }); + return proxyingService.getAllServeEvents().stream() + .filter(input -> url.equals(input.getRequest().getUrl())) + .findFirst() + .orElse(null); } private static final String CAPTURE_HEADERS_SNAPSHOT_REQUEST = @@ -374,7 +368,7 @@ public void returnsStubMappingsWithScenariosForRepeatedRequests() { + " \"mappings\": [ \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/?transformed=global\", \n" + + " \"url\" : \"/foo?transformed=global\", \n" + " \"method\" : \"GET\" \n" + " }, \n" + " \"response\" : { \n" @@ -383,13 +377,13 @@ public void returnsStubMappingsWithScenariosForRepeatedRequests() { + " }, \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/foo?transformed=global\", \n" + + " \"url\" : \"/?transformed=global\", \n" + " \"method\" : \"GET\" \n" + " }, \n" + " \"response\" : { \n" + " \"status\" : 200 \n" + " } \n" - + " }, \n" + + " } \n" + " ] \n" + "} "; @@ -425,7 +419,7 @@ public void returnsTransformedStubMappingWithGlobalTransformer() { + " \"mappings\": [ \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/?transformed=nonglobal\", \n" + + " \"url\" : \"/foo?transformed=nonglobal\", \n" + " \"method\" : \"GET\", \n" + " \"headers\": { \n" + " \"Accept\": { \n" @@ -436,10 +430,10 @@ public void returnsTransformedStubMappingWithGlobalTransformer() { + " \"response\" : { \n" + " \"status\" : 200 \n" + " } \n" - + " }, \n" + + " }, \n" + " { \n" + " \"request\" : { \n" - + " \"url\" : \"/foo?transformed=nonglobal\", \n" + + " \"url\" : \"/?transformed=nonglobal\", \n" + " \"method\" : \"GET\", \n" + " \"headers\": { \n" + " \"Accept\": { \n" @@ -450,7 +444,7 @@ public void returnsTransformedStubMappingWithGlobalTransformer() { + " \"response\" : { \n" + " \"status\" : 200 \n" + " } \n" - + " } \n" + + " } \n" + " ] \n" + "} "; diff --git a/src/test/java/com/github/tomakehurst/wiremock/RecordingDslAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RecordingDslAcceptanceTest.java index baf47a745b..e26d80868e 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/RecordingDslAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/RecordingDslAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.common.ContentTypes.CONTENT_TYPE; import static com.github.tomakehurst.wiremock.common.Gzip.gzip; import static com.github.tomakehurst.wiremock.common.Strings.DEFAULT_CHARSET; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.findMappingWithUrl; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; import static org.apache.hc.core5.http.ContentType.APPLICATION_OCTET_STREAM; import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN; import static org.hamcrest.MatcherAssert.assertThat; @@ -30,7 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.common.InvalidInputException; import com.github.tomakehurst.wiremock.matching.EqualToJsonPattern; import com.github.tomakehurst.wiremock.recording.NotRecordingException; import com.github.tomakehurst.wiremock.recording.RecordingStatus; @@ -62,7 +61,11 @@ public void init() { fileRoot = setupTempFileRoot(); proxyingService = new WireMockServer( - wireMockConfig().dynamicPort().withRootDirectory(fileRoot.getAbsolutePath())); + wireMockConfig() + .dynamicPort() + .withRootDirectory(fileRoot.getAbsolutePath()) + .enableBrowserProxying(true) + .trustAllProxyTargets(true)); proxyingService.start(); targetService = wireMockServer; @@ -148,7 +151,7 @@ public void recordsNothingWhenNoServeEventsAreRecievedDuringRecording() { client.get("/do-not-record-this/3"); assertThat(returnedMappings.size(), is(0)); - assertThat(proxyingService.getStubMappings(), Matchers.empty()); + assertThat(proxyingService.getStubMappings(), Matchers.empty()); } @Test @@ -157,7 +160,7 @@ public void recordsNothingWhenNoServeEventsAreRecievedAtAll() { List returnedMappings = stopRecording().getStubMappings(); assertThat(returnedMappings.size(), is(0)); - assertThat(proxyingService.getStubMappings(), Matchers.empty()); + assertThat(proxyingService.getStubMappings(), Matchers.empty()); } @Test @@ -322,7 +325,7 @@ public void defaultsToWritingBinaryResponseFilesOfAnySize() { StubMapping mapping = mappings.get(0); String bodyFileName = mapping.getResponse().getBodyFileName(); - assertThat(bodyFileName, is("myimagepng-" + mapping.getId() + ".png")); + assertThat(bodyFileName, is("myimage.png-" + mapping.getId() + ".png")); File bodyFile = new File(fileRoot, "__files/" + bodyFileName); assertThat(bodyFile.exists(), is(true)); } @@ -344,7 +347,7 @@ public void defaultsToWritingTextResponseFilesOver1Kb() { StubMapping mapping = mappings.get(0); String bodyFileName = mapping.getResponse().getBodyFileName(); - assertThat(bodyFileName, is("largetxt-" + mapping.getId() + ".txt")); + assertThat(bodyFileName, is("large.txt-" + mapping.getId() + ".txt")); File bodyFile = new File(fileRoot, "__files/" + bodyFileName); assertThat(bodyFile.exists(), is(true)); } @@ -369,47 +372,32 @@ public void doesNotWriteTextResponseFilesUnder1KbByDefault() { } @Test - public void throwsAnErrorIfAttemptingToStopViaStaticRemoteDslWhenNotRecording() { - assertThrows( - NotRecordingException.class, - () -> { - stopRecording(); - }); - } + void recordsViaBrowserProxyingWhenNoTargetUrlSpecified() { + targetService.stubFor(get(urlPathMatching("/record-this/.*")).willReturn(ok("Via proxy"))); - @Test - public void throwsAnErrorIfAttemptingToStopViaInstanceRemoteDslWhenNotRecording() { - assertThrows( - NotRecordingException.class, - () -> { - adminClient.stopStubRecording(); - }); + startRecording(); + + String url = targetService.baseUrl() + "/record-this/123"; + client.getViaProxy(url, proxyingService.port()); + + List mappings = stopRecording().getStubMappings(); + + StubMapping mapping = mappings.get(0); + assertThat(mapping.getRequest().getUrl(), is("/record-this/123")); } @Test - public void throwsAnErrorIfAttemptingToStopViaDirectDslWhenNotRecording() { - assertThrows( - NotRecordingException.class, - () -> { - proxyingService.stopRecording(); - }); + public void throwsAnErrorIfAttemptingToStopViaStaticRemoteDslWhenNotRecording() { + assertThrows(NotRecordingException.class, WireMock::stopRecording); } @Test - public void throwsValidationErrorWhenAttemptingToStartRecordingViaStaticDslWithNoTargetUrl() { - assertThrows( - InvalidInputException.class, - () -> { - startRecording(recordSpec()); - }); + public void throwsAnErrorIfAttemptingToStopViaInstanceRemoteDslWhenNotRecording() { + assertThrows(NotRecordingException.class, adminClient::stopStubRecording); } @Test - public void throwsValidationErrorWhenAttemptingToStartRecordingViaDirectDslWithNoTargetUrl() { - assertThrows( - InvalidInputException.class, - () -> { - proxyingService.startRecording(recordSpec()); - }); + public void throwsAnErrorIfAttemptingToStopViaDirectDslWhenNotRecording() { + assertThrows(NotRecordingException.class, proxyingService::stopRecording); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java index eeef44aec7..9c26a72573 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,4 +87,23 @@ public void convertsBodyFileToStringBodyWhenAKnownImageTypeFromContentTypeHeader assertThat(stubMapping.getItem().getResponse().specifiesBinaryBodyContent(), is(true)); } + + @Test + public void loadMultipleMappingsFromOneFile() { + wmClient.loadMappingsFrom(rootDir); + + assertThat(testClient.get("/todo/items").content(), is("Buy milk")); + assertThat( + testClient + .postWithBody( + "/todo/items", + "{\"subscription\": \"Cancel newspaper subscription\"}", + "application/json", + "UTF-8") + .statusCode(), + is(201)); + assertThat( + testClient.get("/todo/items").content(), + is("Buy milkCancel newspaper subscription")); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/RemoveStubMappingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RemoveStubMappingAcceptanceTest.java index d2c3f7bafd..6da969cdcf 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/RemoveStubMappingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/RemoveStubMappingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,13 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.google.common.collect.FluentIterable.from; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Predicate; import java.util.UUID; +import java.util.function.Predicate; import org.junit.jupiter.api.Test; public class RemoveStubMappingAcceptanceTest extends AcceptanceTestBase { @@ -137,15 +136,12 @@ public void removeStubThatDoesNotExists() { } private Predicate withAnyOf(final String... urls) { - return new Predicate() { - public boolean apply(StubMapping mapping) { - return mapping.getRequest().getUrl() != null + return mapping -> + mapping.getRequest().getUrl() != null && asList(urls).contains(mapping.getRequest().getUrl()); - } - }; } private synchronized int getMatchingStubCount(String url1, String url2) { - return from(listAllStubMappings().getMappings()).filter(withAnyOf(url1, url2)).size(); + return (int) listAllStubMappings().getMappings().stream().filter(withAnyOf(url1, url2)).count(); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/RequestFilterAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RequestFilterAcceptanceTest.java index bc63d26579..951f796683 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/RequestFilterAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/RequestFilterAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; import java.util.Collections; -import java.util.List; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -164,16 +163,9 @@ public static class RequestHeaderModifyingFilter extends StubRequestFilter { @Override public RequestFilterAction filter(Request request) { Request newRequest = - new RequestWrapper(request) { - @Override - public HttpHeader header(String key) { - if (key.equals("X-Modify-Me")) { - return new HttpHeader("X-Modify-Me", "modified"); - } - - return super.header(key); - } - }; + RequestWrapper.create() + .transformHeader("X-Modify-Me", values -> Collections.singletonList("modified")) + .wrap(request); return RequestFilterAction.continueWith(newRequest); } @@ -216,12 +208,7 @@ public RequestFilterAction filter(Request request) { RequestWrapper.create() .transformHeader( "X-Modify-Me", - new FieldTransformer>() { - @Override - public List transform(List existingValue) { - return Collections.singletonList(existingValue.get(0) + value); - } - }) + existingValue -> Collections.singletonList(existingValue.get(0) + value)) .wrap(request); return RequestFilterAction.continueWith(newRequest); @@ -285,13 +272,7 @@ public static class PathModifyingStubFilter extends StubRequestFilter { public RequestFilterAction filter(Request request) { Request wrappedRequest = RequestWrapper.create() - .transformAbsoluteUrl( - new FieldTransformer() { - @Override - public String transform(String url) { - return url.replace("/subpath", "/prefix/subpath"); - } - }) + .transformAbsoluteUrl(url -> url.replace("/subpath", "/prefix/subpath")) .wrap(request); return RequestFilterAction.continueWith(wrappedRequest); diff --git a/src/test/java/com/github/tomakehurst/wiremock/RequestFilterV2AcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RequestFilterV2AcceptanceTest.java new file mode 100644 index 0000000000..17b61afc4c --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/RequestFilterV2AcceptanceTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2019-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.extension.requestfilter.*; +import com.github.tomakehurst.wiremock.http.HttpHeader; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.net.URI; +import java.util.Collections; +import java.util.Map; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class RequestFilterV2AcceptanceTest { + + private WireMockServer wm; + private WireMockTestClient client; + private String url; + + @Test + public void filterCanContinueWithModifiedRequest() { + initialise(new RequestHeaderModifyingFilter()); + + wm.stubFor(get(url).withHeader("X-Modify-Me", equalTo("modified")).willReturn(ok())); + + WireMockResponse response = client.get(url, withHeader("X-Modify-Me", "original")); + assertThat(response.statusCode(), is(200)); + } + + @Test + public void filterCanStopWithResponse() { + initialise(new StubAuthenticatingFilter()); + + wm.stubFor(get(url).willReturn(ok())); + + WireMockResponse good = client.get(url, withHeader("Authorization", "Token 123")); + assertThat(good.statusCode(), is(200)); + + WireMockResponse bad = client.get(url); + assertThat(bad.statusCode(), is(401)); + } + + @Test + public void filtersAreChained() { + initialise( + new RequestHeaderAppendingFilter("A"), + new RequestHeaderAppendingFilter("B"), + new RequestHeaderAppendingFilter("C")); + + wm.stubFor(get(url).withHeader("X-Modify-Me", matching("_[ABC]{3}")).willReturn(ok())); + + WireMockResponse response = client.get(url, withHeader("X-Modify-Me", "_")); + assertThat(response.statusCode(), is(200)); + } + + @Test + public void v1FilterCanStillStopExecution() { + initialise( + new RequestFilterAcceptanceTest.StubAuthenticatingFilter(), + new RequestHeaderAppendingFilter("A")); + + wm.stubFor(get(url).withHeader("X-Modify-Me", equalTo("_A")).willReturn(ok())); + + WireMockResponse good = + client.get(url, withHeader("Authorization", "Token 123"), withHeader("X-Modify-Me", "_")); + assertThat(good.statusCode(), is(200)); + + WireMockResponse bad = client.get(url, withHeader("X-Modify-Me", "_")); + assertThat(bad.statusCode(), is(401)); + } + + @Test + public void filterCanBeAppliedToAdmin() { + initialise(new AdminAuthenticatingFilter()); + + wm.stubFor(get(url).willReturn(ok())); + + String adminUrl = "/__admin/mappings"; + WireMockResponse good = client.get(adminUrl, withHeader("Authorization", "Token 123")); + assertThat(good.statusCode(), is(200)); + + WireMockResponse bad = client.get(adminUrl); + assertThat(bad.statusCode(), is(401)); + + // Stubs are unaffected + WireMockResponse stub = client.get(url); + assertThat(stub.statusCode(), is(200)); + } + + @Test + public void filterCanBeAppliedToStubs() { + initialise(new StubAuthenticatingFilter()); + + wm.stubFor(get(url).willReturn(ok())); + + String adminUrl = "/__admin/mappings"; + WireMockResponse good = client.get(url, withHeader("Authorization", "Token 123")); + assertThat(good.statusCode(), is(200)); + + WireMockResponse bad = client.get(url); + assertThat(bad.statusCode(), is(401)); + + // Admin routes are unaffected + WireMockResponse stub = client.get(adminUrl); + assertThat(stub.statusCode(), is(200)); + } + + @Test + public void filterCanBeAppliedToStubsAndAdmin() { + initialise(new BothAuthenticatingFilter()); + + wm.stubFor(get(url).willReturn(ok())); + + String adminUrl = "/__admin/mappings"; + + WireMockResponse stub = client.get(url); + assertThat(stub.statusCode(), is(401)); + + WireMockResponse admin = client.get(adminUrl); + assertThat(admin.statusCode(), is(401)); + } + + @Test + public void wrappedRequestsAreUsedWhenProxying() { + WireMockServer proxyTarget = new WireMockServer(wireMockConfig().dynamicPort()); + proxyTarget.start(); + initialise(new PathModifyingStubFilter()); + + wm.stubFor( + get(anyUrl()) + .willReturn(aResponse().proxiedFrom("http://localhost:" + proxyTarget.port()))); + proxyTarget.stubFor(get("/prefix/subpath/item").willReturn(ok("From the proxy"))); + + assertThat(client.get("/subpath/item").content(), is("From the proxy")); + + proxyTarget.stop(); + } + + @Test + void stubRequestFilterCanAddSubEvents() { + initialise( + new StubRequestFilterV2() { + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + String path = URI.create(request.getUrl()).getPath(); + serveEvent.appendSubEvent("REQ_PATH", Map.of("path", path)); + return RequestFilterAction.continueWith(request); + } + + @Override + public String getName() { + return "sub-event-adding-filter"; + } + }); + + wm.stubFor(any(anyUrl()).willReturn(ok())); + + client.get("/find-this-path"); + + SubEvent subEvent = wm.getAllServeEvents().get(0).getSubEvents().stream().findFirst().get(); + assertThat(subEvent.getType(), is("REQ_PATH")); + assertThat(subEvent.getDataAs(Map.class).get("path"), is("/find-this-path")); + } + + @BeforeEach + public void init() { + url = "/" + RandomStringUtils.randomAlphabetic(5); + } + + @AfterEach + public void stopServer() { + wm.stop(); + } + + private void initialise(Extension... filters) { + wm = new WireMockServer(wireMockConfig().dynamicPort().extensions(filters)); + wm.start(); + client = new WireMockTestClient(wm.port()); + } + + public static class RequestHeaderModifyingFilter implements StubRequestFilterV2 { + + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + Request newRequest = + RequestWrapper.create() + .transformHeader("X-Modify-Me", values -> Collections.singletonList("modified")) + .wrap(request); + + return RequestFilterAction.continueWith(newRequest); + } + + @Override + public String getName() { + return "request-header-modifier"; + } + } + + public static class StubAuthenticatingFilter implements StubRequestFilterV2 { + + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + HttpHeader authHeader = request.header("Authorization"); + if (!authHeader.isPresent() || !authHeader.firstValue().equals("Token 123")) { + return RequestFilterAction.stopWith(ResponseDefinition.notAuthorised()); + } + + return RequestFilterAction.continueWith(request); + } + + @Override + public String getName() { + return "stub-authenticator"; + } + } + + public static class RequestHeaderAppendingFilter implements StubRequestFilterV2 { + + private final String value; + + public RequestHeaderAppendingFilter(String value) { + this.value = value; + } + + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + Request newRequest = + RequestWrapper.create() + .transformHeader( + "X-Modify-Me", + existingValue -> Collections.singletonList(existingValue.get(0) + value)) + .wrap(request); + + return RequestFilterAction.continueWith(newRequest); + } + + @Override + public String getName() { + return "request-header-appender-" + value; + } + } + + public static class AdminAuthenticatingFilter implements AdminRequestFilterV2 { + + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + HttpHeader authHeader = request.header("Authorization"); + if (!authHeader.isPresent() || !authHeader.firstValue().equals("Token 123")) { + return RequestFilterAction.stopWith(ResponseDefinition.notAuthorised()); + } + + return RequestFilterAction.continueWith(request); + } + + @Override + public String getName() { + return "admin-authenticator"; + } + } + + public static class BothAuthenticatingFilter implements RequestFilterV2 { + + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + HttpHeader authHeader = request.header("Authorization"); + if (!authHeader.isPresent() || !authHeader.firstValue().equals("Token 123")) { + return RequestFilterAction.stopWith(ResponseDefinition.notAuthorised()); + } + + return RequestFilterAction.continueWith(request); + } + + @Override + public boolean applyToAdmin() { + return true; + } + + @Override + public boolean applyToStubs() { + return true; + } + + @Override + public String getName() { + return "both-authenticator"; + } + } + + public static class PathModifyingStubFilter implements StubRequestFilterV2 { + + @Override + public RequestFilterAction filter(Request request, ServeEvent serveEvent) { + Request wrappedRequest = + RequestWrapper.create() + .transformAbsoluteUrl(url -> url.replace("/subpath", "/prefix/subpath")) + .wrap(request); + + return RequestFilterAction.continueWith(wrappedRequest); + } + + @Override + public String getName() { + return "path-mod-filter"; + } + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseDefinitionTransformerV2AcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseDefinitionTransformerV2AcceptanceTest.java new file mode 100644 index 0000000000..df996693ab --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseDefinitionTransformerV2AcceptanceTest.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2014-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.github.tomakehurst.wiremock.testsupport.TestFiles.defaultTestFilesRoot; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class ResponseDefinitionTransformerV2AcceptanceTest { + + WireMockServer wm; + WireMockTestClient client; + + @Test + public void transformerSpecifiedByClassTransformsHeadersStatusAndBody() { + startWithExtensions( + "com.github.tomakehurst.wiremock.ResponseDefinitionTransformerAcceptanceTest$ExampleTransformer"); + createStub("/to-transform"); + + WireMockResponse response = client.get("/to-transform"); + assertThat(response.statusCode(), is(200)); + assertThat(response.firstHeader("MyHeader"), is("Transformed")); + assertThat(response.content(), is("Transformed body")); + } + + @Test + public void supportsMultipleTransformers() { + startWithExtensions( + "com.github.tomakehurst.wiremock.ResponseDefinitionTransformerAcceptanceTest$MultiTransformer1", + "com.github.tomakehurst.wiremock.ResponseDefinitionTransformerAcceptanceTest$MultiTransformer2"); + createStub("/to-multi-transform"); + + WireMockResponse response = client.get("/to-multi-transform"); + assertThat(response.statusCode(), is(201)); + assertThat(response.content(), is("Expect this")); + } + + @Test + public void supportsSpecifiyingExtensionsByClass() { + wm = + new WireMockServer( + wireMockConfig() + .dynamicPort() + .extensions(ExampleTransformer.class, MultiTransformer1.class)); + wm.start(); + client = new WireMockTestClient(wm.port()); + createStub("/to-class-transform"); + + WireMockResponse response = client.get("/to-class-transform"); + assertThat(response.statusCode(), is(201)); + assertThat(response.content(), is("Transformed body")); + } + + @Test + public void supportsSpecifiyingExtensionsByInstance() { + wm = + new WireMockServer( + wireMockConfig() + .dynamicPort() + .extensions(new ExampleTransformer(), new MultiTransformer2())); + wm.start(); + client = new WireMockTestClient(wm.port()); + createStub("/to-instance-transform"); + + WireMockResponse response = client.get("/to-instance-transform"); + assertThat(response.statusCode(), is(200)); + assertThat(response.content(), is("Expect this")); + } + + @Test + public void doesNotApplyNonGlobalExtensionsWhenNotExplicitlySpecfiedByStub() { + wm = + new WireMockServer( + wireMockConfig() + .dynamicPort() + .extensions(new ExampleTransformer(), new NonGlobalTransformer())); + wm.start(); + client = new WireMockTestClient(wm.port()); + createStub("/non-global-transform"); + + WireMockResponse response = client.get("/non-global-transform"); + assertThat(response.content(), is("Transformed body")); + } + + @Test + public void appliesNonGlobalExtensionsWhenSpecifiedByStub() { + wm = new WireMockServer(wireMockConfig().dynamicPort().extensions(new NonGlobalTransformer())); + wm.start(); + client = new WireMockTestClient(wm.port()); + + wm.stubFor( + get(urlEqualTo("/local-transform")) + .willReturn( + aResponse() + .withStatus(200) + .withBody("Should not see this") + .withTransformers("local"))); + + WireMockResponse response = client.get("/local-transform"); + assertThat(response.content(), is("Non-global transformed body")); + } + + @Test + @SuppressWarnings("unchecked") + public void preventsMoreThanOneExtensionWithTheSameNameFromBeingAdded() { + assertThrows( + IllegalArgumentException.class, + () -> { + new WireMockServer( + wireMockConfig() + .dynamicPort() + .extensions(ExampleTransformer.class) + .extensions( + "com.github.tomakehurst.wiremock.ResponseDefinitionTransformerV2AcceptanceTest$AnotherExampleTransformer")) + .start(); + }); + } + + @Test + public void supportsAccessingTheFilesFileSource() { + startWithExtensions( + "com.github.tomakehurst.wiremock.ResponseDefinitionTransformerAcceptanceTest$FileAccessTransformer"); + createStub("/files-access-transform"); + + WireMockResponse response = client.get("/files-access-transform"); + assertThat(response.content(), is("Some example test from a file")); + } + + @Test + public void supportsParameters() { + startWithExtensions( + "com.github.tomakehurst.wiremock.ResponseDefinitionTransformerAcceptanceTest$ParameterisedTransformer"); + + wm.stubFor( + get(urlEqualTo("/transform-with-params")) + .willReturn( + aResponse().withStatus(200).withTransformerParameter("newBody", "Use this body"))); + + assertThat(client.get("/transform-with-params").content(), is("Use this body")); + } + + private void startWithExtensions(String... extensions) { + wm = + new WireMockServer( + wireMockConfig() + .dynamicPort() + .withRootDirectory(defaultTestFilesRoot()) + .extensions(extensions)); + wm.start(); + client = new WireMockTestClient(wm.port()); + } + + @AfterEach + public void cleanup() { + if (wm != null) { + wm.stop(); + } + } + + private void createStub(String url) { + wm.stubFor( + get(urlEqualTo(url)) + .willReturn( + aResponse() + .withHeader("MyHeader", "Initial") + .withStatus(300) + .withBody("Should not see this"))); + } + + public static class ExampleTransformer implements ResponseDefinitionTransformerV2 { + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return new ResponseDefinitionBuilder() + .withHeader("MyHeader", "Transformed") + .withStatus(200) + .withBody("Transformed body") + .build(); + } + + @Override + public String getName() { + return "example"; + } + } + + public static class MultiTransformer1 implements ResponseDefinitionTransformerV2 { + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return ResponseDefinitionBuilder.like(serveEvent.getResponseDefinition()) + .but() + .withStatus(201) + .build(); + } + + @Override + public String getName() { + return "multi1"; + } + } + + public static class MultiTransformer2 implements ResponseDefinitionTransformerV2 { + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return ResponseDefinitionBuilder.like(serveEvent.getResponseDefinition()) + .but() + .withBody("Expect this") + .build(); + } + + @Override + public String getName() { + return "multi2"; + } + } + + public static class NonGlobalTransformer implements ResponseDefinitionTransformerV2 { + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return ResponseDefinitionBuilder.like(serveEvent.getResponseDefinition()) + .but() + .withBody("Non-global transformed body") + .build(); + } + + @Override + public boolean applyGlobally() { + return false; + } + + @Override + public String getName() { + return "local"; + } + } + + public static class AnotherExampleTransformer implements ResponseDefinitionTransformerV2 { + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return serveEvent.getResponseDefinition(); + } + + @Override + public String getName() { + return "example"; + } + } + + public static class FileAccessTransformer implements ResponseDefinitionTransformerV2 { + + private final FileSource files; + + public FileAccessTransformer(FileSource files) { + this.files = files; + } + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return ResponseDefinitionBuilder.like(serveEvent.getResponseDefinition()) + .but() + .withBody(files.getBinaryFileNamed("plain-example.txt").readContents()) + .build(); + } + + @Override + public String getName() { + return "filesource"; + } + } + + public static class ParameterisedTransformer implements ResponseDefinitionTransformerV2 { + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return ResponseDefinitionBuilder.like(serveEvent.getResponseDefinition()) + .but() + .withBody( + serveEvent + .getStubMapping() + .getResponse() + .getTransformerParameters() + .getString("newBody")) + .build(); + } + + @Override + public String getName() { + return "params"; + } + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAcceptanceTest.java index 60fa226e12..2884178c8b 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.github.tomakehurst.wiremock.common.Exceptions; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.http.HttpClientFactory; @@ -30,6 +31,7 @@ import com.github.tomakehurst.wiremock.matching.UrlPattern; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.io.IOException; import java.net.SocketTimeoutException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -37,6 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.HttpResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -185,9 +188,7 @@ public void requestIsSuccessfulWhenDelayIsShorterThanSocketTimeout() throws Exce @Test public void requestIsRecordedInJournalBeforePerformingDelay() throws Exception { - stubFor( - get(urlEqualTo("/delayed")) - .willReturn(aResponse().withStatus(200).withFixedDelay(SHORTER_THAN_SOCKET_TIMEOUT))); + stubFor(get("/delayed").willReturn(ok().withFixedDelay(SHORTER_THAN_SOCKET_TIMEOUT))); ExecutorService executorService = Executors.newSingleThreadExecutor(); final AtomicBoolean callSucceeded = callDelayedEndpointAsynchronously(executorService); @@ -203,8 +204,7 @@ public void requestIsRecordedInJournalBeforePerformingDelay() throws Exception { @Test public void inFlightDelayedRequestsAreNotRecordedInJournalAfterReset() throws Exception { stubFor( - get(urlEqualTo("/delayed")) - .willReturn(aResponse().withStatus(200).withFixedDelay(SHORTER_THAN_SOCKET_TIMEOUT))); + get(urlEqualTo("/delayed")).willReturn(ok().withFixedDelay(SHORTER_THAN_SOCKET_TIMEOUT))); ExecutorService executorService = Executors.newSingleThreadExecutor(); final AtomicBoolean callSucceeded = callDelayedEndpointAsynchronously(executorService); @@ -221,18 +221,14 @@ public void inFlightDelayedRequestsAreNotRecordedInJournalAfterReset() throws Ex private AtomicBoolean callDelayedEndpointAsynchronously(ExecutorService executorService) { final AtomicBoolean success = new AtomicBoolean(false); + HttpGet request = new HttpGet(wireMockRule.url("/delayed")); executorService.submit( - new Runnable() { - @Override - public void run() { - try { - HttpGet request = new HttpGet(wireMockRule.url("/delayed")); - final HttpResponse execute = httpClient.execute(request); - assertThat(execute.getCode(), is(200)); - success.set(true); - } catch (Throwable e) { - e.printStackTrace(); - } + () -> { + try (final CloseableHttpResponse response = httpClient.execute(request)) { + assertThat(response.getCode(), is(200)); + success.set(true); + } catch (IOException e) { + Exceptions.throwUnchecked(e, AtomicBoolean.class); } }); return success; diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAsynchronousAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAsynchronousAcceptanceTest.java index d95679d6f4..e55f7305a7 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAsynchronousAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseDelayAsynchronousAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,15 +92,12 @@ private List> getHttpRequestCallables(int requestCou for (int i = 0; i < requestCount; i++) { final Stopwatch stopwatch = Stopwatch.createStarted(); requests.add( - new Callable() { - @Override - public TimedHttpResponse call() throws Exception { - CloseableHttpResponse response = - HttpClientFactory.createClient(SOCKET_TIMEOUT_MILLISECONDS) - .execute(new HttpGet(wireMockRule.url("/delayed"))); - - return new TimedHttpResponse(response, stopwatch.stop().elapsed(MILLISECONDS)); - } + () -> { + CloseableHttpResponse response = + HttpClientFactory.createClient(SOCKET_TIMEOUT_MILLISECONDS) + .execute(new HttpGet(wireMockRule.url("/delayed"))); + + return new TimedHttpResponse(response, stopwatch.stop().elapsed(MILLISECONDS)); }); } return requests; diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseTemplatingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseTemplatingAcceptanceTest.java index 7a577bfad7..3c41212db1 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ResponseTemplatingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseTemplatingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static com.github.tomakehurst.wiremock.testsupport.ServeEventChecks.assertMessageSubEventPresent; import static com.github.tomakehurst.wiremock.testsupport.TestFiles.defaultTestFilesRoot; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.testsupport.WireMatchers; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.ClearSystemProperty; public class ResponseTemplatingAcceptanceTest { @@ -44,7 +45,7 @@ class Local { @RegisterExtension public WireMockExtension wm = WireMockExtension.newInstance() - .options(options().dynamicPort().extensions(new ResponseTemplateTransformer(false))) + .options(options().dynamicPort().templatingEnabled(true).globalTemplating(false)) .build(); @BeforeEach @@ -64,6 +65,20 @@ public void appliesResponseTemplateWhenAddedToStubMapping() { assertThat(client.get("/templated").content(), is("templated")); } + @Test + public void doesNotIncludeQueryParametersInPathVariableValue() { + wm.stubFor( + get(urlPathTemplate("/{template_param}")) + .willReturn( + aResponse() + .withBody("{ \"key\": \"{{{ request.path.template_param }}}\" }") + .withTransformers("response-template"))); + + String content = client.get("/foo?bar=1").content(); + + assertThat(content, is("{ \"key\": \"foo\" }")); + } + @Test public void doesNotApplyResponseTemplateWhenNotAddedToStubMapping() { wm.stubFor( @@ -86,7 +101,8 @@ class Global { options() .dynamicPort() .withRootDirectory(defaultTestFilesRoot()) - .extensions(new ResponseTemplateTransformer(true))) + .templatingEnabled(true) + .globalTemplating(true)) .build(); @BeforeEach @@ -258,6 +274,64 @@ public void canLookupSquareBracketedQueryParameters() { assertThat(client.get("/squares?filter[id]=321").content(), is("ID: 321")); assertThat(client.get("/squares?filter%5Bid%5D=321").content(), is("ID: 321")); } + + @Test + void canReadPathParametersFromModelWhenStubUsesPathTemplate() { + wm.stubFor( + get(urlPathTemplate("/v1/contacts/{contactId}/addresses/{addressId}")) + .willReturn( + ok( + "contactId: {{request.path.contactId}}, addressId: {{request.path.addressId}}"))); + + String content = client.get("/v1/contacts/12345/addresses/67890").content(); + + assertThat(content, is("contactId: 12345, addressId: 67890")); + } + + @Test + void canReadPathSegmentsByIndexWhenStubUsesPathTemplate() { + wm.stubFor( + get(urlPathTemplate("/v1/contacts/{contactId}/addresses/{addressId}")) + .willReturn(ok("1: {{request.path.1}}, 2: {{request.path.2}}"))); + + String content = client.get("/v1/contacts/12345/addresses/67890").content(); + + assertThat(content, is("1: contacts, 2: 12345")); + } + + @Test + void canReadNumericPathVariableValuesWhenUsingPathTemnplate() { + wm.stubFor( + get(urlPathTemplate("/v1/first/{0}/second/{1}")) + .willReturn(ok("1: {{request.path.0}}, 2: {{request.path.1}}"))); + + String content = client.get("/v1/first/first1/second/second2").content(); + + assertThat(content, is("1: first1, 2: second2")); + } + + @Test + void canLoopOverPathSegmentsWhenUsingPathTemplate() { + wm.stubFor( + get(urlPathTemplate("/v1/first/{0}/second/{1}")) + .willReturn(ok("{{#each request.path as |segment|}}{{segment}} {{/each}}"))); + + String content = client.get("/v1/first/first1/second/second2").content(); + + assertThat(content, is(" v1 first first1 second second2 ")); + } + + @Test + void exceptionThrownWhileRenderingIsReportedViaSubEvent() { + wm.stubFor(get("/bad").willReturn(ok("{{math '1' '/' 0}}"))); + + WireMockResponse response = client.get("/bad"); + + assertThat(response.statusCode(), is(500)); + assertThat(response.content(), is("1:2: java.lang.ArithmeticException: / by zero")); + + assertMessageSubEventPresent(wm, "ERROR", "1:2: java.lang.ArithmeticException: / by zero"); + } } @Nested @@ -272,11 +346,8 @@ class RestrictedSystemPropertiesAndEnvVars { options() .dynamicPort() .withRootDirectory(defaultTestFilesRoot()) - .extensions( - new ResponseTemplateTransformer.Builder() - .global(true) - .permittedSystemKeys("allowed.*") - .build())) + .withPermittedSystemKeys("allowed.*") + .globalTemplating(true)) .build(); @BeforeEach @@ -295,6 +366,7 @@ public void appliesResponseTemplateWithHostname() throws Exception { } @Test + @ClearSystemProperty(key = "allowed.thing") public void rendersPermittedSystemProperty() { System.setProperty("allowed.thing", "123"); @@ -307,6 +379,7 @@ public void rendersPermittedSystemProperty() { } @Test + @ClearSystemProperty(key = "forbidden.thing") public void refusesToRenderForbiddenSystemProperty() { System.setProperty("forbidden.thing", "456"); @@ -328,4 +401,71 @@ public void appliesResponseTemplateShouldNotEmptyWithExistingSystemValue() { assertThat(client.get("/templated").content(), notNullValue()); } } + + @Nested + class NoEscaping { + + WireMockTestClient client; + + @RegisterExtension + public WireMockExtension wm = + WireMockExtension.newInstance() + .options( + options() + .dynamicPort() + .withRootDirectory(defaultTestFilesRoot()) + .templatingEnabled(true) + .globalTemplating(true)) + .build(); + + @BeforeEach + public void init() { + client = new WireMockTestClient(wm.getPort()); + } + + @Test + void escapingIsDisabledByDefault() { + wm.stubFor( + post("/noescape").willReturn(ok("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}"))); + + WireMockResponse response = + client.postJson("/noescape", "{\"a\": {\"test\": \"look at my 'single quotes'\"}}"); + + assertThat(response.content(), is("{\"test\": \"look at my 'single quotes'\"}")); + } + } + + @Nested + class Escaping { + + WireMockTestClient client; + + @RegisterExtension + public WireMockExtension wm = + WireMockExtension.newInstance() + .options( + options() + .dynamicPort() + .withRootDirectory(defaultTestFilesRoot()) + .templatingEnabled(true) + .globalTemplating(true) + .withTemplateEscapingDisabled(false)) + .build(); + + @BeforeEach + public void init() { + client = new WireMockTestClient(wm.getPort()); + } + + @Test + void escapingIsEnabled() { + wm.stubFor( + post("/noescape").willReturn(ok("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}"))); + + WireMockResponse response = + client.postJson("/noescape", "{\"a\": {\"test\": \"look at my 'single quotes'\"}}"); + + assertThat(response.content(), is("{\"test\": \"look at my 'single quotes'\"}")); + } + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java index 431afc20d6..1a6f5ac890 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; -import java.io.File; import org.junit.jupiter.api.Test; public class ResponseTransformerAcceptanceTest { @@ -79,17 +78,7 @@ public void filesRootIsCorrectlyPassedToTransformer() { wm.stubFor(get(urlEqualTo("/response-transform-with-files")).willReturn(ok())); assertThat( - client.get("/response-transform-with-files").content(), - endsWith( - "src" - + File.separator - + "test" - + File.separator - + "resources" - + File.separator - + "__files" - + File.separator - + "plain-example.txt")); + client.get("/response-transform-with-files").content(), endsWith("plain-example.txt")); } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerV2AcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerV2AcceptanceTest.java new file mode 100644 index 0000000000..77dbf3c43c --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerV2AcceptanceTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2014-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.github.tomakehurst.wiremock.http.HttpHeader.httpHeader; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.extension.ExtensionFactory; +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; +import com.github.tomakehurst.wiremock.http.Response; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ResponseTransformerV2AcceptanceTest { + + WireMockServer wm; + WireMockTestClient client; + + @Test + public void transformsStubResponse() { + startWithExtensions(StubResponseTransformer.class); + + wm.stubFor( + get(urlEqualTo("/response-transform")).willReturn(aResponse().withBody("Original body"))); + + assertThat(client.get("/response-transform").content(), is("Modified body")); + } + + @Test + public void acceptsTransformerParameters() { + startWithExtensions(StubResponseTransformerWithParams.class); + + wm.stubFor( + get(urlEqualTo("/response-transform-with-params")) + .willReturn( + aResponse() + .withTransformerParameter("name", "John") + .withTransformerParameter("number", 66) + .withTransformerParameter("flag", true) + .withBody("Original body"))); + + assertThat(client.get("/response-transform-with-params").content(), is("John, 66, true")); + } + + @Test + public void globalTransformAppliedWithLocalParameters() { + startWithExtensions(GlobalResponseTransformer.class); + + wm.stubFor(get(urlEqualTo("/global-response-transform")).willReturn(aResponse())); + + assertThat(client.get("/global-response-transform").firstHeader("X-Extra"), is("extra val")); + } + + @Test + public void filesRootIsCorrectlyPassedToTransformer() { + startWithExtensions( + services -> List.of(new FilesUsingResponseTransformer(services.getFiles()))); + + wm.stubFor(get(urlEqualTo("/response-transform-with-files")).willReturn(ok())); + + assertThat( + client.get("/response-transform-with-files").content(), endsWith("plain-example.txt")); + } + + @SuppressWarnings("unchecked") + private void startWithExtensions(Class extensionClasses) { + wm = new WireMockServer(wireMockConfig().dynamicPort().extensions(extensionClasses)); + wm.start(); + client = new WireMockTestClient(wm.port()); + } + + private void startWithExtensions(ExtensionFactory... extensionFactories) { + wm = new WireMockServer(wireMockConfig().dynamicPort().extensions(extensionFactories)); + wm.start(); + client = new WireMockTestClient(wm.port()); + } + + public static class StubResponseTransformer implements ResponseTransformerV2 { + + @Override + public Response transform(Response response, ServeEvent serveEvent) { + return Response.Builder.like(response).but().body("Modified body").build(); + } + + @Override + public boolean applyGlobally() { + return true; + } + + @Override + public String getName() { + return "stub-transformer"; + } + } + + public static class StubResponseTransformerWithParams implements ResponseTransformerV2 { + + @Override + public Response transform(Response response, ServeEvent serveEvent) { + Parameters parameters = serveEvent.getTransformerParameters(); + return Response.Builder.like(response) + .but() + .body( + parameters.getString("name") + + ", " + + parameters.getInt("number") + + ", " + + parameters.getBoolean("flag")) + .build(); + } + + @Override + public boolean applyGlobally() { + return true; + } + + @Override + public String getName() { + return "stub-transformer-with-params"; + } + } + + public static class GlobalResponseTransformer implements ResponseTransformerV2 { + + @Override + public Response transform(Response response, ServeEvent serveEvent) { + return Response.Builder.like(response) + .but() + .headers(response.getHeaders().plus(httpHeader("X-Extra", "extra val"))) + .build(); + } + + @Override + public String getName() { + return "global-response-transformer"; + } + + @Override + public boolean applyGlobally() { + return true; + } + } + + public static class FilesUsingResponseTransformer implements ResponseTransformerV2 { + + private final FileSource fileSource; + + public FilesUsingResponseTransformer(FileSource fileSource) { + this.fileSource = fileSource; + } + + @Override + public Response transform(Response response, ServeEvent serveEvent) { + return Response.Builder.like(response) + .but() + .body(fileSource.getTextFileNamed("plain-example.txt").getPath()) + .build(); + } + + @Override + public String getName() { + return "files-using-response-transformer"; + } + + @Override + public boolean applyGlobally() { + return true; + } + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/SavingMappingsAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/SavingMappingsAcceptanceTest.java index 12b284c46a..1c0c4c0be2 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/SavingMappingsAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/SavingMappingsAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,9 @@ import com.github.tomakehurst.wiremock.common.SingleRootFileSource; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; import java.io.File; +import java.util.Arrays; +import java.util.Objects; import org.apache.commons.io.FileUtils; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -102,20 +102,12 @@ public void savedMappingIsDeletedFromTheDiskOnRemove() { } private static Matcher containsFileWithNameContaining(final String namePart) { - return new TypeSafeDiagnosingMatcher() { + return new TypeSafeDiagnosingMatcher<>() { @Override protected boolean matchesSafely(File directory, Description mismatchDescription) { boolean found = - FluentIterable.from(directory.list()) - .filter( - new Predicate() { - @Override - public boolean apply(String filename) { - return filename.contains(namePart); - } - }) - .first() - .isPresent(); + Arrays.stream(Objects.requireNonNull(directory.list())) + .anyMatch(filename -> filename.contains(namePart)); if (!found) { mismatchDescription.appendText("file with name containing " + namePart + " not found"); @@ -167,7 +159,7 @@ public void doesNotDuplicateMappingsAlreadyPersistedAfterReset() { } static final TypeSafeDiagnosingMatcher IS_PERSISTENT = - new TypeSafeDiagnosingMatcher() { + new TypeSafeDiagnosingMatcher<>() { @Override public void describeTo(Description description) { description.appendText("a stub mapping marked as persistent"); diff --git a/src/test/java/com/github/tomakehurst/wiremock/ScenarioAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ScenarioAcceptanceTest.java index c0cb1d02d3..6f48b505f3 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ScenarioAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ScenarioAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2022 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,8 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.github.tomakehurst.wiremock.client.WireMock.resetScenario; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; import static com.github.tomakehurst.wiremock.stubbing.Scenario.withName; -import static com.google.common.collect.Iterables.find; import static java.net.HttpURLConnection.HTTP_OK; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -134,11 +132,13 @@ public void canGetAllScenarios() { List scenarios = getAllScenarios(); - Scenario scenario1 = find(scenarios, withName("scenario_one")); + Scenario scenario1 = + scenarios.stream().filter(withName("scenario_one")).findAny().orElseThrow(); assertThat(scenario1.getPossibleStates(), hasItems(STARTED, "state_2")); assertThat(scenario1.getState(), is("state_2")); - Scenario scenario2 = find(scenarios, withName("scenario_two")); + Scenario scenario2 = + scenarios.stream().filter(withName("scenario_two")).findAny().orElseThrow(); assertThat(scenario2.getState(), is("Started")); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/ServeEventListenerExtensionTest.java b/src/test/java/com/github/tomakehurst/wiremock/ServeEventListenerExtensionTest.java new file mode 100644 index 0000000000..623a2641c4 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/ServeEventListenerExtensionTest.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2016-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.PostServeActionExtensionTest.CounterNameParameter.counterNameParameter; +import static com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.responseDefinition; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static com.github.tomakehurst.wiremock.extension.ServeEventListener.RequestPhase.*; +import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; +import static java.util.concurrent.TimeUnit.SECONDS; +import static net.javacrumbs.jsonunit.JsonMatchers.jsonPartEquals; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.tomakehurst.wiremock.admin.Router; +import com.github.tomakehurst.wiremock.common.ConsoleNotifier; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.extension.AdminApiExtension; +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ServeEventListener; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +public class ServeEventListenerExtensionTest { + + WireMockServer wm; + WireMockTestClient client; + + void initWithOptions(Options options) { + wm = new WireMockServer(options); + wm.start(); + client = new WireMockTestClient(wm.port()); + } + + @AfterEach + public void cleanup() { + if (wm != null) { + wm.stop(); + } + } + + @Test + void eventSynchronouslyTriggeredBeforeMatching() { + AtomicBoolean completed = new AtomicBoolean(false); + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + + @Override + public void beforeMatch(ServeEvent serveEvent, Parameters parameters) { + assertThat(serveEvent.getRequest().getUrl(), is("/get-this")); + assertThat(serveEvent.getResponseDefinition(), nullValue()); + assertThat(serveEvent.getStubMapping(), nullValue()); + assertThat(serveEvent.getResponse(), nullValue()); + + completed.set(true); + } + + @Override + public String getName() { + return "before-match"; + } + })); + + wm.stubFor(any(anyUrl()).willReturn(ok())); + + client.get("/get-this"); + + assertTrue(completed.get()); + } + + @Test + void eventSynchronouslyTriggeredAfterMatching() { + AtomicBoolean completed = new AtomicBoolean(false); + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + + @Override + public void afterMatch(ServeEvent serveEvent, Parameters parameters) { + assertThat(serveEvent.getRequest().getUrl(), is("/get-this")); + assertThat(serveEvent.getResponseDefinition(), notNullValue()); + assertThat(serveEvent.getStubMapping(), notNullValue()); + assertThat(serveEvent.getResponse(), nullValue()); + + completed.set(true); + } + + @Override + public String getName() { + return "after-match"; + } + })); + + wm.stubFor(any(anyUrl()).willReturn(ok())); + + client.get("/get-this"); + + assertTrue(completed.get()); + } + + @Test + void eventSynchronouslyTriggeredBeforeResponseSent() { + AtomicBoolean completed = new AtomicBoolean(false); + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + + @Override + public void beforeResponseSent(ServeEvent serveEvent, Parameters parameters) { + assertThat(serveEvent.getRequest().getUrl(), is("/get-this")); + assertThat(serveEvent.getResponseDefinition(), notNullValue()); + assertThat(serveEvent.getStubMapping(), notNullValue()); + assertThat(serveEvent.getResponse().getStatus(), is(200)); + + completed.set(true); + } + + @Override + public String getName() { + return "before-resposnse-sent"; + } + })); + + wm.stubFor(any(anyUrl()).willReturn(ok())); + + client.get("/get-this"); + + assertTrue(completed.get()); + } + + @Test + void eventAsynchronouslyTriggeredAfterCompletion() throws Exception { + final CompletableFuture completed = new CompletableFuture<>(); + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + + @Override + public void afterComplete(ServeEvent serveEvent, Parameters parameters) { + assertThat(serveEvent.getRequest().getUrl(), is("/get-this")); + assertThat(serveEvent.getResponseDefinition(), notNullValue()); + assertThat(serveEvent.getStubMapping(), notNullValue()); + assertThat(serveEvent.getResponse().getStatus(), is(200)); + + completed.complete(null); + } + + @Override + public String getName() { + return "after-complete"; + } + })); + + wm.stubFor(any(anyUrl()).willReturn(ok())); + + client.get("/get-this"); + + completed.get(2, SECONDS); + } + + @Test + void eventTriggeredWhenAppliedToAStubMapping() { + initWithOptions(options().dynamicPort().extensions(new NamedCounterAction())); + + StubMapping stubMapping = + wm.stubFor( + get(urlPathEqualTo("/count-me")) + .withServeEventListener("count-request", counterNameParameter().withName("things")) + .willReturn(aResponse())); + + client.get("/count-me"); + client.get("/count-me"); + client.get("/count-me"); + client.get("/count-me"); + + await().atMost(5, SECONDS).until(getContent("/__admin/named-counter/things"), is("4")); + + // We should serialise out in array form + assertThat( + client.get("/__admin/mappings/" + stubMapping.getId()).content(), + jsonPartEquals( + "serveEventListeners", + "[\n" + + " {\n" + + " \"name\": \"count-request\",\n" + + " \"parameters\": {\n" + + " \"counterName\": \"things\"\n" + + " }\n" + + " }\n" + + " ]")); + } + + @Test + void eventSelectedPerStubWithVaryingParameters() { + final List messages = new ArrayList<>(); + + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + + @Override + public void onEvent( + RequestPhase requestPhase, ServeEvent serveEvent, Parameters parameters) { + messages.add(requestPhase.name() + ": " + parameters.getString("phase")); + } + + @Override + public boolean applyGlobally() { + return false; + } + + @Override + public String getName() { + return "request-phase-reporter"; + } + })); + + wm.stubFor( + get(urlPathEqualTo("/report")) + .withServeEventListener( + AFTER_MATCH, "request-phase-reporter", Parameters.one("phase", "after-match")) + .withServeEventListener( + AFTER_COMPLETE, "request-phase-reporter", Parameters.one("phase", "after-complete")) + .willReturn(aResponse())); + + client.get("/report"); + + await() + .atMost(2, SECONDS) + .until( + () -> messages, hasItems("AFTER_MATCH: after-match", "AFTER_COMPLETE: after-complete")); + } + + @Test + void globalOnEventListenerIsTriggeredInAllRequestPhases() { + final List messages = new ArrayList<>(); + + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + + @Override + public void onEvent( + RequestPhase requestPhase, ServeEvent serveEvent, Parameters parameters) { + messages.add(requestPhase.name()); + } + + @Override + public boolean applyGlobally() { + return true; + } + + @Override + public String getName() { + return "request-phase-reporter"; + } + })); + + wm.stubFor(get(urlPathEqualTo("/report")).willReturn(aResponse())); + + client.get("/report"); + + await() + .atMost(2, SECONDS) + .until(() -> messages, hasItems("BEFORE_MATCH", "AFTER_MATCH", "AFTER_COMPLETE")); + } + + @Test + void continuesWithNoEffectIfANonExistentActionIsReferenced() { + initWithOptions(options().dynamicPort()); + + wm.stubFor( + get(urlPathEqualTo("/as-normal")) + .withServeEventListener("does-not-exist", counterNameParameter().withName("things")) + .willReturn(aResponse().withStatus(200))); + + assertThat(client.get("/as-normal").statusCode(), is(200)); + } + + @Test + void providesServeEventWithResponseFieldPopulated() { + final AtomicInteger finalStatus = new AtomicInteger(); + initWithOptions( + options() + .dynamicPort() + .extensions( + new ServeEventListener() { + @Override + public String getName() { + return "response-field-test"; + } + + @Override + public boolean applyGlobally() { + return true; + } + + @Override + public void afterComplete(ServeEvent serveEvent, Parameters parameters) { + if (serveEvent.getResponse() != null) { + finalStatus.set(serveEvent.getResponse().getStatus()); + } + } + })); + + wm.stubFor(get(urlPathEqualTo("/response-status")).willReturn(aResponse().withStatus(418))); + + client.get("/response-status"); + + await().atMost(5, SECONDS).until(getValue(finalStatus), is(418)); + } + + @Test + public void multipleActionsOfTheSameNameCanBeSpecifiedAsAJsonArray() { + initWithOptions( + options() + .dynamicPort() + .notifier(new ConsoleNotifier(true)) + .extensions(new NamedCounterAction())); + + WireMockResponse response = + client.postJson( + "/__admin/mappings", + "{\n" + + " \"request\": {\n" + + " \"urlPath\": \"/count-me\",\n" + + " \"method\": \"GET\"\n" + + " },\n" + + " \"response\": {\n" + + " \"status\": 200\n" + + " },\n" + + " \"serveEventListeners\": [\n" + + " {\n" + + " \"name\": \"count-request\",\n" + + " \"parameters\": {\n" + + " \"counterName\": \"one\" \n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"count-request\",\n" + + " \"parameters\": {\n" + + " \"counterName\": \"two\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"); + + assertThat(response.content(), response.statusCode(), is(201)); + + client.get("/count-me"); + client.get("/count-me"); + client.get("/count-me"); + + await().atMost(5, SECONDS).until(getContent("/__admin/named-counter/one"), is("3")); + + await().atMost(5, SECONDS).until(getContent("/__admin/named-counter/two"), is("3")); + } + + @Test + void multipleActionsOfTheSameNameCanBeSpecifiedViaTheDSL() { + initWithOptions( + options() + .dynamicPort() + .notifier(new ConsoleNotifier(true)) + .extensions(new NamedCounterAction())); + + wm.stubFor( + get(urlPathEqualTo("/count-me")) + .willReturn(ok()) + .withServeEventListener("count-request", counterNameParameter().withName("one")) + .withServeEventListener("count-request", counterNameParameter().withName("two"))); + + client.get("/count-me"); + client.get("/count-me"); + client.get("/count-me"); + + await().atMost(5, SECONDS).until(getContent("/__admin/named-counter/one"), is("3")); + + await().atMost(5, SECONDS).until(getContent("/__admin/named-counter/two"), is("3")); + } + + private Callable getValue(final AtomicInteger value) { + return value::get; + } + + private Callable getContent(final String url) { + return () -> client.get(url).content(); + } + + public static class NamedCounterAction implements ServeEventListener, AdminApiExtension { + + private final ConcurrentHashMap counters = new ConcurrentHashMap<>(); + + @Override + public String getName() { + return "count-request"; + } + + @Override + public boolean applyGlobally() { + return false; + } + + @Override + public void contributeAdminApiRoutes(Router router) { + router.add( + GET, + "/named-counter/{name}", + (admin, serveEvent, pathParams) -> { + String name = pathParams.get("name"); + Integer count = getFirstNonNull(counters.get(name), 0); + return responseDefinition().withStatus(200).withBody(String.valueOf(count)).build(); + }); + } + + @Override + public void afterComplete(ServeEvent serveEvent, Parameters parameters) { + CounterNameParameter counterNameParam = parameters.as(CounterNameParameter.class); + + String counterName = counterNameParam.counterName; + + counters.putIfAbsent(counterName, 0); + Integer oldValue; + Integer newValue; + + do { + oldValue = counters.get(counterName); + newValue = oldValue + 1; + } while (!counters.replace(counterName, oldValue, newValue)); + } + } + + public static class CounterNameParameter { + + public String counterName; + + public CounterNameParameter(@JsonProperty("counterName") String counterName) { + this.counterName = counterName; + } + + public CounterNameParameter() {} + + public static CounterNameParameter counterNameParameter() { + return new CounterNameParameter(); + } + + public CounterNameParameter withName(String name) { + this.counterName = name; + return this; + } + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/ServeEventLogAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ServeEventLogAcceptanceTest.java index 3abf6d7d4e..4c7f69f835 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ServeEventLogAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ServeEventLogAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.hasExactly; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.isToday; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; @@ -208,7 +208,7 @@ public void getsAllServeEventsThatMatchedStubId() { } private Matcher withUrl(final String url) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public boolean matchesSafely(LoggedRequest loggedRequest) { return loggedRequest.getUrl().equals(url); diff --git a/src/test/java/com/github/tomakehurst/wiremock/SnapshotDslAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/SnapshotDslAcceptanceTest.java index 4d2a0c367c..a3776ff02e 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/SnapshotDslAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/SnapshotDslAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,8 +37,8 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.testsupport.WireMatchers; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; -import com.google.common.collect.ImmutableMap; import java.util.List; +import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -97,19 +97,19 @@ public void snapshotRecordsAllLoggedRequestsWhenNoParametersPassed() throws Exce "All of the returned mappings should be present in the server"); assertThat(returnedMappings.size(), is(3)); - assertThat(returnedMappings.get(0).getRequest().getUrl(), is("/one")); - assertThat(returnedMappings.get(0).getRequest().getHeaders(), nullValue()); - assertThat(returnedMappings.get(0).getRequest().getMethod(), is(RequestMethod.GET)); + assertThat(returnedMappings.get(2).getRequest().getUrl(), is("/one")); + assertThat(returnedMappings.get(2).getRequest().getHeaders(), nullValue()); + assertThat(returnedMappings.get(2).getRequest().getMethod(), is(RequestMethod.GET)); assertThat( - returnedMappings.get(0).getResponse().getHeaders().getHeader("Content-Type").firstValue(), + returnedMappings.get(2).getResponse().getHeaders().getHeader("Content-Type").firstValue(), is("text/plain")); - assertThat(returnedMappings.get(0).getResponse().getBody(), is("Number one")); + assertThat(returnedMappings.get(2).getResponse().getBody(), is("Number one")); assertThat(returnedMappings.get(1).getRequest().getUrl(), is("/two")); - assertThat(returnedMappings.get(2).getRequest().getUrl(), is("/three")); + assertThat(returnedMappings.get(0).getRequest().getUrl(), is("/three")); - ContentPattern bodyPattern = returnedMappings.get(2).getRequest().getBodyPatterns().get(0); + ContentPattern bodyPattern = returnedMappings.get(0).getRequest().getBodyPatterns().get(0); assertThat(bodyPattern, instanceOf(EqualToJsonPattern.class)); JSONAssert.assertEquals("{ \"counter\": 55 }", bodyPattern.getExpected(), true); @@ -179,19 +179,21 @@ public void supportsRequestHeaderCriteria() { snapshotRecord(recordSpec().captureHeader("Yes").captureHeader("Also-Yes", true)); StringValuePattern yesValuePattern = - mappings.get(0).getRequest().getHeaders().get("Yes").getValuePattern(); + ((SingleMatchMultiValuePattern) mappings.get(1).getRequest().getHeaders().get("Yes")) + .getValuePattern(); assertThat(yesValuePattern, instanceOf(EqualToPattern.class)); assertThat(((EqualToPattern) yesValuePattern).getCaseInsensitive(), nullValue()); assertFalse(mappings.get(0).getRequest().getHeaders().containsKey("No")); StringValuePattern alsoYesValuePattern = - mappings.get(1).getRequest().getHeaders().get("Also-Yes").getValuePattern(); + ((SingleMatchMultiValuePattern) mappings.get(0).getRequest().getHeaders().get("Also-Yes")) + .getValuePattern(); assertThat(alsoYesValuePattern, instanceOf(EqualToPattern.class)); assertThat(((EqualToPattern) alsoYesValuePattern).getCaseInsensitive(), is(true)); } @Test - public void supportsBodyExtractCriteria() throws Exception { + public void supportsBodyExtractCriteria() { targetService.stubFor( get("/small/text") .willReturn(aResponse().withHeader("Content-Type", "text/plain").withBody("123"))); @@ -228,13 +230,13 @@ public void supportsBodyExtractCriteria() throws Exception { nullValue()); assertThat( WireMatchers.findMappingWithUrl(mappings, "/large/text").getResponse().getBodyFileName(), - containsString("large_text")); + startsWith("large_text")); assertThat( WireMatchers.findMappingWithUrl(mappings, "/small/binary").getResponse().getBodyFileName(), nullValue()); assertThat( WireMatchers.findMappingWithUrl(mappings, "/large/binary").getResponse().getBodyFileName(), - containsString("large_binary")); + startsWith("large_binary")); } @Test @@ -260,14 +262,14 @@ public void buildsAScenarioForRepeatedIdenticalRequests() { // Scenario creation is the default List mappings = snapshotRecord(); - assertThat(client.get("/stateful").content(), is("One")); - assertThat(client.get("/stateful").content(), is("Two")); assertThat(client.get("/stateful").content(), is("Three")); + assertThat(client.get("/stateful").content(), is("Two")); + assertThat(client.get("/stateful").content(), is("One")); assertThat(mappings, everyItem(WireMatchers.isInAScenario())); - assertThat(mappings.get(0).getRequiredScenarioState(), is(Scenario.STARTED)); - assertThat(mappings.get(1).getRequiredScenarioState(), is("scenario-1-stateful-2")); assertThat(mappings.get(2).getRequiredScenarioState(), is("scenario-1-stateful-3")); + assertThat(mappings.get(1).getRequiredScenarioState(), is("scenario-1-stateful-2")); + assertThat(mappings.get(0).getRequiredScenarioState(), is(Scenario.STARTED)); } @Test @@ -279,10 +281,7 @@ public void appliesTransformerWithParameters() { recordSpec() .transformers("test-transformer") .transformerParameters( - Parameters.from( - ImmutableMap.of( - "headerKey", "X-Key", - "headerValue", "My value")))); + Parameters.from(Map.of("headerKey", "X-Key", "headerValue", "My value")))); assertThat( mappings.get(0).getResponse().getHeaders().getHeader("X-Key").firstValue(), is("My value")); @@ -298,7 +297,7 @@ public void supportsConfigurationOfAutoRequestBodyPatternFactory() { snapshotRecord(recordSpec().chooseBodyMatchTypeAutomatically(false, false, true)); EqualToJsonPattern jsonBodyPattern = - (EqualToJsonPattern) mappings.get(0).getRequest().getBodyPatterns().get(0); + (EqualToJsonPattern) mappings.get(2).getRequest().getBodyPatterns().get(0); assertThat(jsonBodyPattern.getEqualToJson(), is("{}")); assertThat(jsonBodyPattern.isIgnoreArrayOrder(), is(false)); assertThat(jsonBodyPattern.isIgnoreExtraElements(), is(false)); @@ -308,7 +307,7 @@ public void supportsConfigurationOfAutoRequestBodyPatternFactory() { assertThat(xmlBodyPattern.getEqualToXml(), is("")); EqualToPattern textBodyPattern = - (EqualToPattern) mappings.get(2).getRequest().getBodyPatterns().get(0); + (EqualToPattern) mappings.get(0).getRequest().getBodyPatterns().get(0); assertThat(textBodyPattern.getEqualTo(), is("foo")); assertThat(textBodyPattern.getCaseInsensitive(), is(true)); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/StandaloneAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/StandaloneAcceptanceTest.java index 2393c0c4d4..b5bb2c63e2 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/StandaloneAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/StandaloneAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,11 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.testsupport.Network.findFreePort; import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Iterables.any; -import static com.google.common.collect.Iterables.filter; -import static com.google.common.collect.Iterables.size; -import static com.google.common.io.Files.asCharSink; -import static com.google.common.io.Files.createParentDirs; -import static com.google.common.io.Files.write; import static java.io.File.separator; import static java.net.HttpURLConnection.HTTP_OK; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.Files.createDirectories; +import static java.nio.file.Files.write; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; @@ -43,12 +39,15 @@ import com.github.tomakehurst.wiremock.testsupport.MappingJsonSamples; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; -import com.google.common.base.Predicate; import java.io.*; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; import java.util.zip.GZIPInputStream; import org.apache.commons.io.FileUtils; import org.hamcrest.Description; @@ -197,7 +196,7 @@ public void servesXmlIndexFileWhenTrailingSlashPresent() { WireMockResponse response = testClient.get("/json/34567/"); assertThat(response.statusCode(), is(200)); assertThat(response.content(), is("BLAB")); - assertThat(response.firstHeader("Content-Type"), is("application/xml")); + assertThat(response.firstHeader("Content-Type"), is("application/xml;charset=utf-8")); } @Test @@ -459,7 +458,7 @@ public void isRunningReturnsFalseBeforeRunMethodIsExecuted() { public void failsWithUsefulErrorMessageWhenMappingFileIsInvalid() { writeMappingFile("bad-mapping.json", BAD_MAPPING); - MappingFileException exception = assertThrows(MappingFileException.class, () -> startRunner()); + MappingFileException exception = assertThrows(MappingFileException.class, this::startRunner); assertThat( exception.getMessage(), allOf( @@ -470,6 +469,19 @@ public void failsWithUsefulErrorMessageWhenMappingFileIsInvalid() { containsString("not marked as ignorable"))); } + @Test + void savesMappingFileOnCreationOfPersistentStub() { + startRunner(); + + stubFor( + get(urlPathEqualTo("/one/two/three")) + .withName("Named stuff here __$$ things!") + .persistent() + .willReturn(ok())); + + assertThat(mappingsDirectory, containsExactlyOneFileWithNameContaining("named-stuff-here")); + } + private String contentsOfFirstFileNamedLike(String namePart) throws IOException { return FileUtils.readFileToString(firstFileWithNameLike(mappingsDirectory, namePart), UTF_8); } @@ -486,12 +498,7 @@ private File firstFileWithNameLike(File directory, String namePart) { } private FilenameFilter namedLike(final String namePart) { - return new FilenameFilter() { - @Override - public boolean accept(File file, String name) { - return name.contains(namePart); - } - }; + return (file, name) -> name.contains(namePart); } private WireMock startOtherServerAndClient() { @@ -507,9 +514,9 @@ private void writeFileToFilesDir(String name, String contents) { private void writeFileToFilesDir(String name, byte[] contents) { try { String filePath = underFileSourceRoot(underFiles(name)); - File file = new File(filePath); - createParentDirs(file); - write(contents, file); + Path file = Paths.get(filePath); + createDirectories(file.getParent()); + write(file, contents); } catch (IOException e) { throw new RuntimeException(e); } @@ -521,9 +528,9 @@ private void writeMappingFile(String name, String contents) { private void writeFile(String absolutePath, String contents) { try { - File file = new File(absolutePath); - createParentDirs(file); - asCharSink(file, StandardCharsets.UTF_8).write(contents); + Path file = Paths.get(absolutePath); + createDirectories(file.getParent()); + write(file, contents.getBytes(UTF_8)); } catch (IOException e) { throw new RuntimeException(e); } @@ -594,8 +601,7 @@ public void describeTo(Description desc) { public boolean matchesSafely(File dir) { for (File file : dir.listFiles()) { try { - if (FileUtils.readFileToString(file, StandardCharsets.UTF_8) - .contains(expectedContents)) { + if (FileUtils.readFileToString(file, UTF_8).contains(expectedContents)) { return true; } } catch (IOException e) { @@ -618,13 +624,13 @@ public void describeTo(Description desc) { @Override public boolean matchesSafely(File dir) { - return !any(asList(dir.list()), contains(namePart)); + return Arrays.stream(Objects.requireNonNull(dir.list())).noneMatch(contains(namePart)); } }; } private Matcher containsExactlyOneFileWithNameContaining(final String namePart) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description desc) { @@ -633,19 +639,15 @@ public void describeTo(Description desc) { @Override public boolean matchesSafely(File dir) { - Iterable fileNames = filter(asList(dir.list()), contains(namePart)); - return size(fileNames) == 1; + return (int) + Arrays.stream(Objects.requireNonNull(dir.list())).filter(contains(namePart)).count() + == 1; } }; } private static Predicate contains(final String part) { - return new Predicate() { - @Override - public boolean apply(String s) { - return s.contains(part); - } - }; + return s -> s.contains(part); } /** diff --git a/src/test/java/com/github/tomakehurst/wiremock/StubMappingPersistenceAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/StubMappingPersistenceAcceptanceTest.java index 1a84e0530b..e759f0cda7 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/StubMappingPersistenceAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/StubMappingPersistenceAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import static com.github.tomakehurst.wiremock.core.WireMockApp.MAPPINGS_ROOT; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.hasFileContaining; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; diff --git a/src/test/java/com/github/tomakehurst/wiremock/StubbingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/StubbingAcceptanceTest.java index e1196208cb..12444a1918 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/StubbingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/StubbingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package com.github.tomakehurst.wiremock; import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.common.DateTimeTruncation.FIRST_MINUTE_OF_HOUR; import static com.github.tomakehurst.wiremock.common.DateTimeUnit.HOURS; import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; @@ -27,9 +25,16 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_OK; import static java.util.Collections.singletonList; -import static org.apache.hc.core5.http.ContentType.*; +import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON; +import static org.apache.hc.core5.http.ContentType.APPLICATION_OCTET_STREAM; +import static org.apache.hc.core5.http.ContentType.APPLICATION_XML; +import static org.apache.hc.core5.http.ContentType.TEXT_PLAIN; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -37,6 +42,7 @@ import com.github.tomakehurst.wiremock.http.Fault; import com.github.tomakehurst.wiremock.matching.StringValuePattern; import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.testsupport.TestHttpHeader; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import java.io.IOException; import java.net.HttpURLConnection; @@ -46,6 +52,7 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.stream.Stream; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.MalformedChunkCodingException; import org.apache.hc.core5.http.NoHttpResponseException; @@ -56,6 +63,9 @@ import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class StubbingAcceptanceTest extends AcceptanceTestBase { @@ -600,6 +610,38 @@ public void matchesQueryParamsUnencoded() { assertThat(response.statusCode(), is(200)); } + @Test + public void matchesFormParamUnencoded() { + stubFor( + put(urlPathEqualTo("/form")) + .withFormParam("key-one", equalTo("one two three ?")) + .willReturn(aResponse().withStatus(200))); + + WireMockResponse response = + testClient.putWithBody( + "/form", + "key-one=one%20two%20three%20%3F", + "application/x-www-form-urlencoded", + TestHttpHeader.withHeader("Content-Type", "application/x-www-form-urlencoded")); + assertThat(response.statusCode(), is(200)); + } + + @Test + public void matchesFormParamWithKeyInArrayStyle() { + stubFor( + put(urlPathEqualTo("/form")) + .withFormParam("key[one]", equalTo("firstValue")) + .willReturn(aResponse().withStatus(200))); + + WireMockResponse response = + testClient.putWithBody( + "/form", + "key[one]=firstValue", + "application/x-www-form-urlencoded", + TestHttpHeader.withHeader("Content-Type", "application/x-www-form-urlencoded")); + assertThat(response.statusCode(), is(200)); + } + @Test public void copesWithEmptyRequestHeaderValueWhenMatchingOnEqualTo() { stubFor( @@ -964,6 +1006,152 @@ public void matchesQueryParametersWithLogicalOr() { assertThat(testClient.get("/or?q=wrong").statusCode(), is(404)); } + @ParameterizedTest + @MethodSource("provideInputsForMultiValueQueryParamsForExactMatch") + public void matchesMultipleQueryParametersUsingExactMatch( + final String queryParams, final int statusCode) { + stubFor( + get(urlPathEqualTo("/match")) + .withQueryParam("q", havingExactly("1", "2", "3")) + .willReturn(ok())); + + assertThat(testClient.get("/match" + queryParams).statusCode(), is(statusCode)); + } + + @Test + public void matchesMultipleValuesForHeaderUsingExactMatch() { + stubFor( + get(urlPathEqualTo("/match")) + .withHeader("q", havingExactly("1", "2", "3")) + .willReturn(ok())); + + assertThat( + testClient + .get("/match", withHeader("q", "1"), withHeader("q", "2"), withHeader("q", "3")) + .statusCode(), + is(200)); + } + + @Test + public void matchesMultipleValuesForHeaderUsingIncludesMatch() { + stubFor( + get(urlPathEqualTo("/match")).withHeader("q", including("1", "2", "3")).willReturn(ok())); + + assertThat( + testClient + .get( + "/match", + withHeader("q", "1"), + withHeader("q", "2"), + withHeader("q", "3"), + withHeader("q", "4"), + withHeader("q", "5")) + .statusCode(), + is(200)); + } + + @Test + public void matchesMultipleValuesForHeaderUsingIncludesMatchReturnsNotFound() { + stubFor( + get(urlPathEqualTo("/match")).withHeader("q", including("1", "8", "3")).willReturn(ok())); + + assertThat( + testClient + .get( + "/match", + withHeader("q", "1"), + withHeader("q", "2"), + withHeader("q", "3"), + withHeader("q", "4"), + withHeader("q", "5")) + .statusCode(), + is(404)); + } + + @Test + public void matchesMultipleValuesForHeaderUsingExactMatchReturnsNotFound() { + stubFor( + get(urlPathEqualTo("/match")) + .withHeader("q", havingExactly("1", "2", "3")) + .willReturn(ok())); + + assertThat( + testClient + .get( + "/match", + withHeader("q", "1"), + withHeader("q", "4"), + withHeader("q", "5"), + withHeader("q", "6"), + withHeader("q", "5")) + .statusCode(), + is(404)); + } + + @Test + public void matchesNoValuesForHeaders() { + stubFor(get(urlPathEqualTo("/match")).withHeader("q", noValues()).willReturn(ok())); + assertThat(testClient.get("/match").statusCode(), is(200)); + } + + @ParameterizedTest + @MethodSource("provideInputsForMultiValueQueryParamsForExactMatch") + public void matchesMultipleQueryParametersUsingExactMatchWithMultipleValuePatterns( + final String queryParams, final int statusCode) { + stubFor( + get(urlPathEqualTo("/match")) + .withQueryParam("q", havingExactly(equalTo("1"), notContaining("7"), equalTo("3"))) + .willReturn(ok())); + + assertThat(testClient.get("/match" + queryParams).statusCode(), is(statusCode)); + } + + @ParameterizedTest + @MethodSource("provideInputsForMultiValueQueryParamsForIncludeMatch") + public void matchesMultipleQueryParametersUsingIncludeMatch( + final String queryParams, final int statusCode) { + stubFor( + get(urlPathEqualTo("/match")) + .withQueryParam("q", including("1", "2", "3")) + .willReturn(ok())); + + assertThat(testClient.get("/match" + queryParams).statusCode(), is(statusCode)); + } + + public static Stream provideInputsForMultiValueQueryParamsForExactMatch() { + + return Stream.of( + Arguments.of("?q=1&q=2&q=3", 200), + Arguments.of("?q=1&q=3&q=2", 200), + Arguments.of("?q=2&q=3&q=1", 200), + Arguments.of("?q=2&q=1&q=3", 200), + Arguments.of("?q=3&q=1&q=2", 200), + Arguments.of("?q=3&q=2&q=1", 200), + Arguments.of("?q=3&q=1&q=2", 200), + Arguments.of("", 404), + Arguments.of("?q=wrong", 404), + Arguments.of("?q=1&q=2&q=3&q=4", 404), + Arguments.of("?q=1&q=4&q=5&q=6", 404), + Arguments.of("?q=1&q=1&q=1&q=1", 404)); + } + + public static Stream provideInputsForMultiValueQueryParamsForIncludeMatch() { + + return Stream.of( + Arguments.of("?q=1&q=2&q=3", 200), + Arguments.of("?q=1&q=3&q=2", 200), + Arguments.of("?q=2&q=3&q=1", 200), + Arguments.of("?q=2&q=1&q=3", 200), + Arguments.of("?q=3&q=1&q=2", 200), + Arguments.of("?q=3&q=2&q=1", 200), + Arguments.of("?q=3&q=1&q=2", 200), + Arguments.of("?q=1&q=2&q=3&q=4", 200), + Arguments.of("", 404), + Arguments.of("?q=wrong", 404), + Arguments.of("?q=1&q=4&q=5&q=6", 404), + Arguments.of("?q=1&q=1&q=1&q=1", 404)); + } + @Test public void matchesHeadersWithLogicalOr() { stubFor( @@ -1027,6 +1215,14 @@ public void removesASingleStubMappingById() { assertThat(testClient.get("/stub-to-remove-by-id").statusCode(), is(404)); } + @Test + void queryParamCanBeMatchedAsNotAbsent() { + stubFor(get(urlPathEqualTo("/search")).withQueryParam("q", not(absent())).willReturn(ok())); + + assertThat(testClient.get("/search?q=something").statusCode(), is(200)); + assertThat(testClient.get("/search").statusCode(), is(404)); + } + private int getStatusCodeUsingJavaUrlConnection(String url) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("GET"); @@ -1064,6 +1260,7 @@ private void getAndAssertUnderlyingExceptionInstanceClass(String url, Class e } public static class MockResponse { + private final String message; public MockResponse(String message) { diff --git a/src/test/java/com/github/tomakehurst/wiremock/SubServeEventsAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/SubServeEventsAcceptanceTest.java new file mode 100644 index 0000000000..9daa0192c9 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/SubServeEventsAcceptanceTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import com.github.tomakehurst.wiremock.common.Errors; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.SubEvent; +import com.github.tomakehurst.wiremock.verification.diff.DiffEventData; +import org.junit.jupiter.api.Test; + +public class SubServeEventsAcceptanceTest extends AcceptanceTestBase { + + // Diffs are saved as sub events + @Test + void nonMatchDiffsAreSavedAsSubEvents() { + wm.stubFor(get("/right").willReturn(ok())); + + testClient.get("/wrong"); + + ServeEvent serveEvent = wm.getAllServeEvents().get(0); + SubEvent subEvent = serveEvent.getSubEvents().stream().findFirst().get(); + assertThat(subEvent.getType(), is("REQUEST_NOT_MATCHED")); + assertThat(subEvent.getTimeOffsetNanos(), greaterThan(0L)); + assertThat(subEvent.getDataAs(DiffEventData.class).getReport(), containsString("/wrong")); + } + + @Test + void errorsDuringMatchingAreCapturedInSubEvents() { + wm.stubFor( + post("/json").withRequestBody(equalToJson("{ \"thing\": \"value\" }")).willReturn(ok())); + + testClient.postJson("/json", "{ \"thing\": "); + + ServeEvent serveEvent = wm.getAllServeEvents().get(0); + SubEvent failedJsonParseWarning = + serveEvent.getSubEvents().stream() + .filter(sub -> sub.getType().equals(SubEvent.JSON_ERROR)) + .findFirst() + .get(); + Errors.Error error = + failedJsonParseWarning.getDataAs(Errors.class).getErrors().stream().findFirst().get(); + assertThat( + error.getDetail(), containsString("Unexpected end-of-input within/between Object entries")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/TemplateHelperExtensionTest.java b/src/test/java/com/github/tomakehurst/wiremock/TemplateHelperExtensionTest.java new file mode 100644 index 0000000000..da5ef42c6d --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/TemplateHelperExtensionTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import com.github.jknack.handlebars.Helper; +import com.github.tomakehurst.wiremock.extension.TemplateHelperProviderExtension; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.util.Map; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class TemplateHelperExtensionTest { + + @RegisterExtension + public WireMockExtension wm = + WireMockExtension.newInstance() + .options( + wireMockConfig() + .dynamicPort() + .templatingEnabled(true) + .globalTemplating(true) + .extensions( + new TemplateHelperProviderExtension() { + @Override + public String getName() { + return "custom-helpers"; + } + + @Override + public Map> provideTemplateHelpers() { + Helper helper = (context, options) -> context.length(); + return Map.of("string-length", helper); + } + })) + .build(); + + WireMockTestClient client; + + @BeforeEach + void init() { + client = new WireMockTestClient(wm.getPort()); + } + + @Test + void appliesHelpersFromProvider() { + wm.stubFor(post("/things").willReturn(ok("{{{ string-length request.body }}}"))); + + WireMockResponse response = client.post("/things", new StringEntity("fiver")); + + assertThat(response.content(), is("5")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/TemplateModelDataProviderExtensionTest.java b/src/test/java/com/github/tomakehurst/wiremock/TemplateModelDataProviderExtensionTest.java new file mode 100644 index 0000000000..99c551cbc9 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/TemplateModelDataProviderExtensionTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.extension.TemplateModelDataProviderExtension; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class TemplateModelDataProviderExtensionTest { + + @RegisterExtension + public WireMockExtension wm = + WireMockExtension.newInstance() + .options( + wireMockConfig() + .dynamicPort() + .templatingEnabled(true) + .globalTemplating(true) + .extensions( + new TemplateModelDataProviderExtension() { + @Override + public Map provideTemplateModelData(ServeEvent serveEvent) { + return Map.of( + "customData", Map.of("path", serveEvent.getRequest().getUrl())); + } + + @Override + public String getName() { + return "custom-model-data"; + } + })) + .build(); + + WireMockTestClient client; + + @BeforeEach + void init() { + client = new WireMockTestClient(wm.getPort()); + } + + @Test + void appliesHelpersFromProvider() { + wm.stubFor(get("/things").willReturn(ok("{{{ customData.path }}}"))); + + WireMockResponse response = client.get("/things"); + + assertThat(response.content(), is("/things")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/UrlPathTemplateMatchingTest.java b/src/test/java/com/github/tomakehurst/wiremock/UrlPathTemplateMatchingTest.java new file mode 100644 index 0000000000..6184103045 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/UrlPathTemplateMatchingTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.github.tomakehurst.wiremock.common.InvalidInputException; +import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; +import org.junit.jupiter.api.Test; + +public class UrlPathTemplateMatchingTest extends AcceptanceTestBase { + + @Test + void matches_path_template_without_bound_variable() { + stubFor(get(urlPathTemplate("/v1/contacts/{contactId}")).willReturn(ok())); + + assertThat(testClient.get("/v1/contacts/12345").statusCode(), is(200)); + assertThat(testClient.get("/v1/contacts/23456").statusCode(), is(200)); + + assertThat(testClient.get("/v2/contacts/23456").statusCode(), is(404)); + } + + @Test + void matches_path_template_with_single_bound_variable() { + stubFor( + get(urlPathTemplate("/v1/contacts/{contactId}/addresses/{addressId}")) + .withPathParam("contactId", equalTo("12345")) + .withPathParam("addressId", equalTo("99876")) + .willReturn(ok())); + + assertThat(testClient.get("/v1/contacts/12345/addresses/99876").statusCode(), is(200)); + + assertThat(testClient.get("/v1/contacts/12345/addresses/55555").statusCode(), is(404)); + assertThat(testClient.get("/v1/contacts/23456/addresses/99876").statusCode(), is(404)); + assertThat(testClient.get("/v1/contacts/23456/addresses/55555").statusCode(), is(404)); + } + + @Test + void returns_non_match_without_error_when_request_url_path_does_not_match_template() { + stubFor( + get(urlPathTemplate("/contacts/{contactId}/addresses/{addressId}")) + .withPathParam("contactId", equalTo("123")) + .willReturn(ok())); + + WireMockResponse response = testClient.get("/contacts/123/addresssssses/1"); + assertThat(response.content(), containsString("Request was not matched")); + assertThat(response.statusCode(), is(404)); + } + + @Test + void correctly_matches_when_query_parameters_present_in_request() { + stubFor( + get(urlPathTemplate("/contacts/{contactId}")) + .withPathParam("contactId", equalTo("123")) + .willReturn(ok())); + + WireMockResponse response = testClient.get("/contacts/123?detail=summary"); + assertThat(response.statusCode(), is(200)); + } + + @Test + void static_dsl_throws_error_when_attempting_to_use_path_param_matchers_without_path_template() { + assertThrows( + InvalidInputException.class, + () -> + stubFor( + get(urlPathEqualTo("/stuff")) + .withPathParam("wrong", containing("things")) + .willReturn(ok()))); + } + + @Test + void + instance_dsl_throws_error_when_attempting_to_use_path_param_matchers_without_path_template() { + assertThrows( + InvalidInputException.class, + () -> + wm.stubFor( + get(urlPathEqualTo("/stuff")) + .withPathParam("wrong", containing("things")) + .willReturn(ok()))); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/VerificationAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/VerificationAcceptanceTest.java index 33015d1c9b..fd3f1e5ffc 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/VerificationAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/VerificationAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,17 +42,21 @@ import com.github.tomakehurst.wiremock.matching.MatchResult; import com.github.tomakehurst.wiremock.matching.RequestMatcher; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; -import com.github.tomakehurst.wiremock.matching.ValueMatcher; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import com.github.tomakehurst.wiremock.verification.RequestJournalDisabledException; import java.util.List; import java.util.UUID; +import org.apache.hc.client5.http.entity.EntityBuilder; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicNameValuePair; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class VerificationAcceptanceTest { @@ -310,6 +314,31 @@ public void verifiesWithBodyContainingString() { .withRequestBody(containing("Important value"))); } + @Test + public void throwsVerificitationExceptionWhenBodyMatches() { + testClient.postWithBody("/body/json", SAMPLE_JSON, "application/json", "utf-8"); + assertThrows( + VerificationException.class, + () -> + verify( + postRequestedFor(urlEqualTo("/body/json")) + .withRequestBody(not(containing("Important value"))))); + } + + @Test + public void verifiesWithBodyDoesNotContainValue() { + testClient.postWithBody("/body/json", SAMPLE_JSON, "application/json", "utf-8"); + verify(postRequestedFor(urlEqualTo("/body/json")).withRequestBody(not(containing("stuff")))); + } + + @Test + public void verifiesWithHeaderDoesNotContainValue() { + testClient.get("/header/not", withHeader("X-Thing", "One")); + verify( + getRequestedFor(urlEqualTo("/header/not")) + .withHeader("X-Thing", not(containing("Four")))); + } + @Test public void verifiesWithQueryParam() { testClient.get("/query?param=my-value"); @@ -349,6 +378,27 @@ public void verifyIsFalseWhenExpectedQueryParamMissing() { }); } + @Test + public void verifiesQueryParamAbsent() { + testClient.get("/without/queryParam?test-param=test-value"); + verify( + getRequestedFor(urlPathEqualTo("/without/queryParam")) + .withQueryParam("test-param", equalTo("test-value")) + .withoutQueryParam("absent-param")); + } + + @Test + public void failsVerificationWhenAbsentQueryParamPresent() { + assertThrows( + VerificationException.class, + () -> { + testClient.get("/without/queryParam?test-param=test-value"); + verify( + getRequestedFor(urlPathEqualTo("/without/queryParam")) + .withoutQueryParam("test-param")); + }); + } + @Test public void resetErasesCounters() { assertThrows( @@ -511,6 +561,16 @@ public void verifiesHeaderAbsent() { .withoutHeader("Accept")); } + @ParameterizedTest + @ValueSource( + strings = { + "GET", "POST", "PUT", "HEAD", "TRACE", "PATCH", "OPTIONS", "DELETE", "ANY", "RANDOM" + }) + public void verifyRequestedForSameMethodAsRequest(String method) { + testClient.request(method, "/methods"); + verify(requestedFor(method, urlEqualTo("/methods"))); + } + @Test public void failsVerificationWhenAbsentHeaderPresent() { assertThrows( @@ -750,13 +810,7 @@ public void verifiesRequestsViaCustomMatcherRemotely() { verify( 2, - requestMadeFor( - new ValueMatcher() { - @Override - public MatchResult match(Request value) { - return MatchResult.of(value.getUrl().contains("remote-custom-match")); - } - })); + requestMadeFor(value -> MatchResult.of(value.getUrl().contains("remote-custom-match")))); } @Test @@ -912,6 +966,39 @@ public void verifiesRequestsViaRequestMatcherExtensionRemotely() { requestMadeFor( "path-contains-param", Parameters.one("path", "remote-request-matcher-ext"))); } + + @Test + public void verifiesFormParamAbsent() { + String testUrl = "/without/formParam"; + String testFormParam = "test-form-param"; + String testFormValue = "test-form-value"; + HttpEntity requestEntity = + EntityBuilder.create() + .setParameters(new BasicNameValuePair(testFormParam, testFormValue)) + .build(); + stubFor(post(testUrl).withFormParam(testFormParam, equalTo(testFormValue))); + testClient.post(testUrl, requestEntity); + verify( + postRequestedFor(urlEqualTo(testUrl)) + .withFormParam(testFormParam, equalTo(testFormValue)) + .withoutFormParam("absent-form-param")); + } + + @Test + public void failsVerificationWhenAbsentFormParamPresent() { + String testUrl = "/without/formParam"; + String testFormParam = "test-form-param"; + String testFormValue = "test-form-value"; + HttpEntity requestEntity = + EntityBuilder.create() + .setParameters(new BasicNameValuePair(testFormParam, testFormValue)) + .build(); + stubFor(post(testUrl).withFormParam(testFormParam, equalTo(testFormValue))); + testClient.post(testUrl, requestEntity); + assertThrows( + VerificationException.class, + () -> verify(postRequestedFor(urlEqualTo(testUrl)).withoutFormParam(testFormParam))); + } } public static class PathContainsParamRequestMatcher extends RequestMatcherExtension { diff --git a/src/test/java/com/github/tomakehurst/wiremock/WireMockJUnitRuleTest.java b/src/test/java/com/github/tomakehurst/wiremock/WireMockJUnitRuleTest.java index ea0b512c41..dd8062b772 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/WireMockJUnitRuleTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/WireMockJUnitRuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,6 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.http.HttpClientFactory; -import com.github.tomakehurst.wiremock.http.Request; -import com.github.tomakehurst.wiremock.http.RequestListener; -import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.junit.Stubbing; import com.github.tomakehurst.wiremock.junit.WireMockClassRule; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -271,11 +268,8 @@ public static class ListenerTest { public void requestReceivedByListener() { final List urls = new ArrayList(); wireMockRule.addMockServiceRequestListener( - new RequestListener() { - @Override - public void requestReceived(Request request, Response response) { - urls.add(request.getUrl()); - } + (request, response) -> { + urls.add(request.getUrl()); }); wireMockRule.stubFor( get(urlEqualTo("/test/listener")).willReturn(aResponse().withBody("Listener"))); diff --git a/src/test/java/com/github/tomakehurst/wiremock/WireMockServerTests.java b/src/test/java/com/github/tomakehurst/wiremock/WireMockServerTests.java index 76f226a12e..6dfeceb12a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/WireMockServerTests.java +++ b/src/test/java/com/github/tomakehurst/wiremock/WireMockServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,14 @@ public void returnsOptionsWhenCallingGetOptions() { assertThat(wireMockServer.getOptions(), is(options)); } + @Test + public void addFilenameTemplateAsOptionAndValidFormat() { + Options options = options().filenameTemplate("{{{request.url}}}-{{{request.url}}}.json"); + WireMockServer wireMockServer = new WireMockServer(options); + wireMockServer.start(); + assertThat(wireMockServer.getOptions(), is(options)); + } + @Test public void buildsQualifiedHttpUrlFromPath() { WireMockServer wireMockServer = new WireMockServer(options().dynamicPort()); diff --git a/src/test/java/com/github/tomakehurst/wiremock/XmlHandlingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/XmlHandlingAcceptanceTest.java index 0e309bfad5..e8da4bced2 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/XmlHandlingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/XmlHandlingAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ public class XmlHandlingAcceptanceTest { @RegisterExtension public WireMockExtension wm = WireMockExtension.newInstance() - .options(options().dynamicPort().extensions(new ResponseTemplateTransformer(false))) + .options(options().dynamicPort().templatingEnabled(true)) .build(); @RegisterExtension diff --git a/src/test/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginatorTest.java b/src/test/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginatorTest.java index efc62bddf3..7305b36625 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginatorTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/admin/LimitAndOffsetPaginatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.github.tomakehurst.wiremock.admin; -import static com.google.common.primitives.Ints.asList; import static java.util.Collections.emptyList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -25,83 +24,79 @@ import java.util.List; import org.junit.jupiter.api.Test; -public class LimitAndOffsetPaginatorTest { +class LimitAndOffsetPaginatorTest { @Test - public void returnsWholeListWhenBothParametersAreNull() { - List source = asList(1, 2, 3, 4, 5); + void returnsWholeListWhenBothParametersAreNull() { + List source = List.of(1, 2, 3, 4, 5); LimitAndOffsetPaginator paginator = new LimitAndOffsetPaginator<>(source, null, null); List result = paginator.select(); - assertThat(result, is(asList(1, 2, 3, 4, 5))); + assertThat(result, is(List.of(1, 2, 3, 4, 5))); } @Test - public void returnsEmptyListWhenSourceIsEmpty() { + void returnsEmptyListWhenSourceIsEmpty() { List source = emptyList(); LimitAndOffsetPaginator paginator = new LimitAndOffsetPaginator<>(source, null, null); List result = paginator.select(); - assertThat(result, is(Collections.emptyList())); + assertThat(result, is(Collections.emptyList())); } @Test - public void returnsTruncatedListFromStartWhenOnlyLimitIsSpecified() { - List source = asList(1, 2, 3, 4, 5); + void returnsTruncatedListFromStartWhenOnlyLimitIsSpecified() { + List source = List.of(1, 2, 3, 4, 5); LimitAndOffsetPaginator paginator = new LimitAndOffsetPaginator<>(source, 3, null); List result = paginator.select(); - assertThat(result, is(asList(1, 2, 3))); + assertThat(result, is(List.of(1, 2, 3))); } @Test - public void returnsFromOffSetToTheEndWhenOnlyOffsetIsSpecified() { - List source = asList(1, 2, 3, 4, 5); + void returnsFromOffSetToTheEndWhenOnlyOffsetIsSpecified() { + List source = List.of(1, 2, 3, 4, 5); LimitAndOffsetPaginator paginator = new LimitAndOffsetPaginator<>(source, null, 2); List result = paginator.select(); - assertThat(result, is(asList(3, 4, 5))); + assertThat(result, is(List.of(3, 4, 5))); } @Test - public void returnsRangeWhenBothAreSpecified() { - List source = asList(1, 2, 3, 4, 5); + void returnsRangeWhenBothAreSpecified() { + List source = List.of(1, 2, 3, 4, 5); LimitAndOffsetPaginator paginator = new LimitAndOffsetPaginator<>(source, 3, 1); List result = paginator.select(); - assertThat(result, is(asList(2, 3, 4))); + assertThat(result, is(List.of(2, 3, 4))); } @Test - public void returnsToEndOfListWhenTopBoundIsGreaterThanListSize() { - List source = asList(1, 2, 3, 4, 5); + void returnsToEndOfListWhenTopBoundIsGreaterThanListSize() { + List source = List.of(1, 2, 3, 4, 5); LimitAndOffsetPaginator paginator = new LimitAndOffsetPaginator<>(source, 7, 3); List result = paginator.select(); - assertThat(result, is(asList(4, 5))); + assertThat(result, is(List.of(4, 5))); } @Test - public void rejectsNegativeLimit() { + void rejectsNegativeLimit() { assertThrows( IllegalArgumentException.class, - () -> { - new LimitAndOffsetPaginator<>(Collections.emptyList(), -1, 3); - }); + () -> new LimitAndOffsetPaginator<>(Collections.emptyList(), -1, 3)); } @Test - public void rejectsNegativeOffset() { + void rejectsNegativeOffset() { assertThrows( IllegalArgumentException.class, - () -> { - new LimitAndOffsetPaginator<>(Collections.emptyList(), 0, -10); - }); + () -> new LimitAndOffsetPaginator<>(Collections.emptyList(), 0, -10)); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/admin/OldEditStubMappingTaskTest.java b/src/test/java/com/github/tomakehurst/wiremock/admin/OldEditStubMappingTaskTest.java deleted file mode 100644 index ae51ef50a7..0000000000 --- a/src/test/java/com/github/tomakehurst/wiremock/admin/OldEditStubMappingTaskTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2013-2021 Thomas Akehurst - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.github.tomakehurst.wiremock.admin; - -import static com.github.tomakehurst.wiremock.stubbing.StubMapping.buildJsonStringFor; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.github.tomakehurst.wiremock.admin.model.PathParams; -import com.github.tomakehurst.wiremock.admin.tasks.OldEditStubMappingTask; -import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; -import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import java.net.HttpURLConnection; -import org.junit.jupiter.api.Test; - -public class OldEditStubMappingTaskTest { - - private static final StubMapping MOCK_MAPPING = new StubMapping(null, new ResponseDefinition()); - - private Admin mockAdmin = mock(Admin.class); - private Request mockRequest = mock(Request.class); - - private OldEditStubMappingTask editStubMappingTask = new OldEditStubMappingTask(); - - @Test - public void delegatesSavingMappingsToAdmin() { - when(mockRequest.getBodyAsString()).thenReturn(buildJsonStringFor(MOCK_MAPPING)); - - editStubMappingTask.execute(mockAdmin, mockRequest, PathParams.empty()); - - verify(mockAdmin).editStubMapping(any(StubMapping.class)); - } - - @Test - public void returnsNoContentResponse() { - when(mockRequest.getBodyAsString()).thenReturn(buildJsonStringFor(MOCK_MAPPING)); - - ResponseDefinition response = - editStubMappingTask.execute(mockAdmin, mockRequest, PathParams.empty()); - - assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT)); - verify(mockAdmin).editStubMapping(any(StubMapping.class)); - } -} diff --git a/src/test/java/com/github/tomakehurst/wiremock/admin/SaveMappingsTaskTest.java b/src/test/java/com/github/tomakehurst/wiremock/admin/SaveMappingsTaskTest.java index c24be3249e..44529617ce 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/admin/SaveMappingsTaskTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/admin/SaveMappingsTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,17 @@ */ package com.github.tomakehurst.wiremock.admin; +import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.verify; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import com.github.tomakehurst.wiremock.admin.tasks.SaveMappingsTask; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.net.HttpURLConnection; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -31,13 +33,13 @@ public class SaveMappingsTaskTest { private Admin mockAdmin = Mockito.mock(Admin.class); - private Request mockRequest = Mockito.mock(Request.class); + private Request mockRequest = mockRequest(); private SaveMappingsTask saveMappingsTask = new SaveMappingsTask(); @Test public void delegatesSavingMappingsToAdmin() { - saveMappingsTask.execute(mockAdmin, mockRequest, PathParams.empty()); + saveMappingsTask.execute(mockAdmin, ServeEvent.of(mockRequest), PathParams.empty()); verify(mockAdmin).saveMappings(); } @@ -45,7 +47,7 @@ public void delegatesSavingMappingsToAdmin() { @Test public void returnsOkResponse() { ResponseDefinition response = - saveMappingsTask.execute(mockAdmin, mockRequest, PathParams.empty()); + saveMappingsTask.execute(mockAdmin, ServeEvent.of(mockRequest), PathParams.empty()); assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK)); verify(mockAdmin).saveMappings(); diff --git a/src/test/java/com/github/tomakehurst/wiremock/admin/model/QueryParamsTest.java b/src/test/java/com/github/tomakehurst/wiremock/admin/model/QueryParamsTest.java index 15914eb6d6..6d75489109 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/admin/model/QueryParamsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/admin/model/QueryParamsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2021 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import com.github.tomakehurst.wiremock.common.url.QueryParams; import org.junit.jupiter.api.Test; public class QueryParamsTest { diff --git a/src/test/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTaskTest.java b/src/test/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTaskTest.java new file mode 100644 index 0000000000..4e3623a7fe --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/admin/tasks/HealthCheckTaskTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.admin.tasks; + +import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.common.url.PathParams; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import java.net.HttpURLConnection; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class HealthCheckTaskTest { + + private final Admin mockAdmin = Mockito.mock(Admin.class); + private final Request mockRequest = mockRequest(); + private final HealthCheckTask healthCheckTask = new HealthCheckTask(); + + @Test + public void healthy() { + ResponseDefinition response = + healthCheckTask.execute(mockAdmin, ServeEvent.of(mockRequest), PathParams.empty()); + + assertThat(response.getStatus(), is(HttpURLConnection.HTTP_OK)); + assertThat(response.getStatusMessage(), is("Wiremock is ok")); + assertThat( + response.getStatusMessage(), + equalTo(response.getReponseBody().asJson().get("message").asText())); + assertThat(response.getReponseBody().asJson().get("status").asText(), is("healthy")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/archunit/UnusedCodeTest.java b/src/test/java/com/github/tomakehurst/wiremock/archunit/UnusedCodeTest.java index d156f778fc..f27a9e1626 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/archunit/UnusedCodeTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/archunit/UnusedCodeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import static com.tngtech.archunit.base.DescribedPredicate.describe; import static com.tngtech.archunit.base.DescribedPredicate.not; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn; import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -74,6 +75,10 @@ public void check(JavaClass javaClass, ConditionEvents events) { describe( "do not implement interface", clazz -> clazz.getAllRawInterfaces().isEmpty())) .and(describe("do not extend class", clazz -> 1 == clazz.getAllRawSuperclasses().size())) + .and( + not( + assignableTo( + com.github.tomakehurst.wiremock.standalone.WireMockServerRunner.class))) .should(beReferencedClass) .as("should use all classes") .because("unused classes should be removed"); diff --git a/src/test/java/com/github/tomakehurst/wiremock/client/ClientAuthenticationAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/client/ClientAuthenticationAcceptanceTest.java index 99e554f135..8bac94f55f 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/client/ClientAuthenticationAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/client/ClientAuthenticationAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,25 @@ */ package com.github.tomakehurst.wiremock.client; +import static com.github.tomakehurst.wiremock.common.ContentTypes.AUTHORIZATION; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static com.github.tomakehurst.wiremock.http.HttpHeader.httpHeader; import static com.github.tomakehurst.wiremock.testsupport.TestHttpHeader.withHeader; -import static com.google.common.net.HttpHeaders.AUTHORIZATION; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.http.HttpHeader; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.security.*; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; -import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -public class ClientAuthenticationAcceptanceTest { +class ClientAuthenticationAcceptanceTest { private WireMockServer server; private WireMock goodClient; @@ -47,20 +45,10 @@ public void stopServer() { } @Test - public void supportsCustomAuthenticator() { + void supportsCustomAuthenticator() { initialise( - new Authenticator() { - @Override - public boolean authenticate(Request request) { - return request.containsHeader("X-Magic-Header"); - } - }, - new ClientAuthenticator() { - @Override - public List generateAuthHeaders() { - return singletonList(httpHeader("X-Magic-Header", "blah")); - } - }); + request -> request.containsHeader("X-Magic-Header"), + () -> singletonList(httpHeader("X-Magic-Header", "blah"))); WireMockTestClient noAuthClient = new WireMockTestClient(server.port()); @@ -75,38 +63,33 @@ public List generateAuthHeaders() { } @Test - public void supportsBasicAuthenticator() { + void supportsBasicAuthenticator() { initialise( new BasicAuthenticator( new BasicCredentials("user1", "password1"), new BasicCredentials("user2", "password2")), new ClientBasicAuthenticator("user1", "password1")); - goodClient.getServeEvents(); // Expect no exception thrown + assertDoesNotThrow(() -> goodClient.getServeEvents()); } @Test - public void throwsNotAuthorisedExceptionWhenWrongBasicCredentialsProvided() { - assertThrows( - NotAuthorisedException.class, - () -> { - initialise( - new BasicAuthenticator( - new BasicCredentials("user1", "password1"), - new BasicCredentials("user2", "password2")), - new ClientBasicAuthenticator("user1", "password1")); - - badClient = - WireMock.create() - .port(server.port()) - .authenticator(new ClientBasicAuthenticator("user1", "wrong_password")) - .build(); - - badClient.getServeEvents(); - }); + void throwsNotAuthorisedExceptionWhenWrongBasicCredentialsProvided() { + initialise( + new BasicAuthenticator( + new BasicCredentials("user1", "password1"), new BasicCredentials("user2", "password2")), + new ClientBasicAuthenticator("user1", "password1")); + + badClient = + WireMock.create() + .port(server.port()) + .authenticator(new ClientBasicAuthenticator("user1", "wrong_password")) + .build(); + + assertThrows(NotAuthorisedException.class, () -> badClient.getServeEvents()); } @Test - public void supportsBasicAuthenticatorViaStaticDsl() { + void supportsBasicAuthenticatorViaStaticDsl() { initialise( new BasicAuthenticator( new BasicCredentials("user1", "password1"), new BasicCredentials("user2", "password2")), @@ -120,7 +103,7 @@ public void supportsBasicAuthenticatorViaStaticDsl() { } @Test - public void supportsShorthandBasicAuthWithHttps() { + void supportsShorthandBasicAuthWithHttps() { server = new WireMockServer( wireMockConfig() @@ -136,11 +119,11 @@ public void supportsShorthandBasicAuthWithHttps() { .basicAuthenticator("user", "password") .build(); - goodClient.getServeEvents(); + assertDoesNotThrow(() -> goodClient.getServeEvents()); } @Test - public void canRequireHttpsOnAdminApi() { + void canRequireHttpsOnAdminApi() { server = new WireMockServer( wireMockConfig() @@ -160,7 +143,7 @@ public void canRequireHttpsOnAdminApi() { } @Test - public void supportsTokenAuthenticatorViaStaticDsl() { + void supportsTokenAuthenticatorViaStaticDsl() { final String TOKEN = "my_token_123"; initialise(new TokenAuthenticator(TOKEN), new ClientTokenAuthenticator(TOKEN)); diff --git a/src/test/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilderTest.java b/src/test/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilderTest.java index f50b7dd522..732cffcf2d 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilderTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/client/ResponseDefinitionBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.github.tomakehurst.wiremock.client; -import static com.google.common.collect.Lists.newArrayList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -28,13 +27,14 @@ import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import java.nio.charset.StandardCharsets; -import org.apache.commons.codec.binary.Base64; +import java.util.Arrays; +import java.util.Base64; import org.junit.jupiter.api.Test; -public class ResponseDefinitionBuilderTest { +class ResponseDefinitionBuilderTest { @Test - public void withTransformerParameterShouldNotChangeOriginalTransformerParametersValue() { + void withTransformerParameterShouldNotChangeOriginalTransformerParametersValue() { ResponseDefinition originalResponseDefinition = ResponseDefinitionBuilder.responseDefinition() .withTransformerParameter("name", "original") @@ -53,13 +53,14 @@ public void withTransformerParameterShouldNotChangeOriginalTransformerParameters } @Test - public void likeShouldCreateCompleteResponseDefinitionCopy() throws Exception { + void likeShouldCreateCompleteResponseDefinitionCopy() { ResponseDefinition originalResponseDefinition = ResponseDefinitionBuilder.responseDefinition() .withStatus(200) .withStatusMessage("OK") .withBody("some body") - .withBase64Body(Base64.encodeBase64String("some body".getBytes(StandardCharsets.UTF_8))) + .withBase64Body( + Base64.getEncoder().encodeToString("some body".getBytes(StandardCharsets.UTF_8))) .withBodyFile("some_body.json") .withHeader("some header", "some value") .withFixedDelay(100) @@ -77,7 +78,7 @@ public void likeShouldCreateCompleteResponseDefinitionCopy() throws Exception { } @Test - public void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinition() { + void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinition() { ResponseDefinition proxyDefinition = ResponseDefinitionBuilder.responseDefinition().proxiedFrom("http://my.domain").build(); @@ -86,8 +87,7 @@ public void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinit } @Test - public void - proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinitionWithJsonBody() { + void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinitionWithJsonBody() { ResponseDefinition proxyDefinition = ResponseDefinitionBuilder.responseDefinition() .proxiedFrom("http://my.domain") @@ -99,8 +99,7 @@ public void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinit } @Test - public void - proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinitionWithBinaryBody() { + void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinitionWithBinaryBody() { ResponseDefinition proxyDefinition = ResponseDefinitionBuilder.responseDefinition() .proxiedFrom("http://my.domain") @@ -112,7 +111,7 @@ public void proxyResponseDefinitionWithoutProxyInformationIsNotInResponseDefinit } @Test - public void proxyResponseDefinitionWithExtraInformationIsInResponseDefinition() { + void proxyResponseDefinitionWithExtraInformationIsInResponseDefinition() { ResponseDefinition proxyDefinition = ResponseDefinitionBuilder.responseDefinition() .proxiedFrom("http://my.domain") @@ -122,12 +121,12 @@ public void proxyResponseDefinitionWithExtraInformationIsInResponseDefinition() assertThat( proxyDefinition.getAdditionalProxyRequestHeaders(), - equalTo(new HttpHeaders(newArrayList(new HttpHeader("header", "value"))))); + equalTo(new HttpHeaders(Arrays.asList(new HttpHeader("header", "value"))))); assertThat(proxyDefinition.getProxyUrlPrefixToRemove(), equalTo("/remove")); } @Test - public void proxyResponseDefinitionWithExtraInformationIsInResponseDefinitionWithJsonBody() { + void proxyResponseDefinitionWithExtraInformationIsInResponseDefinitionWithJsonBody() { ResponseDefinition proxyDefinition = ResponseDefinitionBuilder.responseDefinition() .proxiedFrom("http://my.domain") @@ -138,12 +137,12 @@ public void proxyResponseDefinitionWithExtraInformationIsInResponseDefinitionWit assertThat( proxyDefinition.getAdditionalProxyRequestHeaders(), - equalTo(new HttpHeaders(newArrayList(new HttpHeader("header", "value"))))); + equalTo(new HttpHeaders(Arrays.asList(new HttpHeader("header", "value"))))); assertThat(proxyDefinition.getProxyUrlPrefixToRemove(), equalTo("/remove")); } @Test - public void proxyResponseDefinitionWithExtraInformationIsInResponseDefinitionWithBinaryBody() { + void proxyResponseDefinitionWithExtraInformationIsInResponseDefinitionWithBinaryBody() { ResponseDefinition proxyDefinition = ResponseDefinitionBuilder.responseDefinition() .proxiedFrom("http://my.domain") @@ -154,7 +153,7 @@ public void proxyResponseDefinitionWithExtraInformationIsInResponseDefinitionWit assertThat( proxyDefinition.getAdditionalProxyRequestHeaders(), - equalTo(new HttpHeaders(newArrayList(new HttpHeader("header", "value"))))); + equalTo(new HttpHeaders(Arrays.asList(new HttpHeader("header", "value"))))); assertThat(proxyDefinition.getProxyUrlPrefixToRemove(), equalTo("/remove")); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/Base64EncoderTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/Base64EncoderTest.java index 045d9ad26e..116fb9bfff 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/Base64EncoderTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/Base64EncoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ public class Base64EncoderTest { public static final String OUTPUT = "MTIzNA=="; @Test - public void testGuavaEncoder() { - Base64Encoder encoder = new GuavaBase64Encoder(); + void testEncoder() { + var encoder = new JdkBase64Encoder(); String encoded = encoder.encode(INPUT.getBytes()); assertThat(encoded, is(OUTPUT)); diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/ClasspathFileSourceTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/ClasspathFileSourceTest.java index 827ee9bc1f..4dea08fe13 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/ClasspathFileSourceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/ClasspathFileSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.File; +import java.net.*; import java.util.List; import org.junit.jupiter.api.Test; @@ -70,6 +72,15 @@ public void readsBinaryFileFromJar() { assertThat("Expected a non zero length file", binaryFile.readContents().length, greaterThan(0)); } + @Test + public void readsBinaryFileFromCustomClassLoader() throws MalformedURLException { + initForCustomClassLoader(); + + BinaryFile binaryFile = classpathFileSource.child("__files").getBinaryFileNamed("stuff.txt"); + + assertThat("Expected a non zero length file", binaryFile.readContents().length, greaterThan(0)); + } + @Test public void readsBinaryFileFromZip() { classpathFileSource = new ClasspathFileSource("zippeddir"); @@ -129,11 +140,17 @@ public void failsSilentlyOnWrites() { classpathFileSource.createIfNecessary(); } - void initForJar() { + private void initForJar() { classpathFileSource = new ClasspathFileSource("META-INF/maven/com.google.guava"); } private void initForFileSystem() { classpathFileSource = new ClasspathFileSource("filesource"); } + + private void initForCustomClassLoader() throws MalformedURLException { + URL[] urls = {new File("src/main/resources/classpath-filesource.jar").toURI().toURL()}; + ClassLoader cl = new URLClassLoader(urls); + classpathFileSource = new ClasspathFileSource(cl, "jar-filesource"); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/FilenameMakerTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/FilenameMakerTest.java new file mode 100644 index 0000000000..873d21ed1f --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/common/FilenameMakerTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.common; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FilenameMakerTest { + + private FilenameMaker filenameMaker; + + @BeforeEach + public void init() { + filenameMaker = new FilenameMaker(); + } + + @Test + public void generatesNameFromStubNameWhenPresent() { + StubMapping mapping = + WireMock.get("/named").withName("This is a NAMED stub").willReturn(ok()).build(); + + assertThat( + filenameMaker.filenameFor(mapping), + is("this-is-a-named-stub-" + mapping.getId() + ".json")); + } + + @Test + public void generatesNameFromStubUrlWhenNameNotPresent() { + FilenameMaker makerWithOwnFormat = new FilenameMaker("{{{method}}}-{{{url}}}.json"); + StubMapping mapping = WireMock.get(urlEqualTo("/named/123/things")).willReturn(ok()).build(); + + assertThat(makerWithOwnFormat.filenameFor(mapping), is("get-named123things.json")); + } + + @Test + public void generatesNameFromStubUrlPathWhenNameNotPresent() { + FilenameMaker makerWithOwnFormat = new FilenameMaker("{{{method}}}-{{{url}}}.json"); + StubMapping mapping = + WireMock.get(urlPathEqualTo("/named/123/things")).willReturn(ok()).build(); + + assertThat(makerWithOwnFormat.filenameFor(mapping), is("get-named123things.json")); + } + + @Test + public void generatesNameFromStubUrlPathTemplateWhenNameNotPresent() { + FilenameMaker makerWithOwnFormat = new FilenameMaker("{{{method}}}-{{{url}}}.json"); + StubMapping mapping = + WireMock.get(urlPathTemplate("/named/{id}/things")).willReturn(ok()).build(); + + assertThat(makerWithOwnFormat.filenameFor(mapping), is("get-namedidthings.json")); + } + + @Test + public void generatesNameFromStubUrlPatternWhenNameNotPresent() { + FilenameMaker makerWithOwnFormat = new FilenameMaker("{{{method}}}-{{{url}}}.json"); + StubMapping mapping = + WireMock.get(urlMatching("/named/([0-9]*)/things")).willReturn(ok()).build(); + + assertThat(makerWithOwnFormat.filenameFor(mapping), is("get-named0-9things.json")); + } + + @Test + public void generatesNameWhenStubUrlIsAnyAndNameNotPresent() { + StubMapping mapping = WireMock.get(anyUrl()).willReturn(ok()).build(); + + FilenameMaker makerWithOwnFormat = new FilenameMaker("{{{id}}}.json"); + + assertThat(makerWithOwnFormat.filenameFor(mapping), is(mapping.getId() + ".json")); + } + + @Test + public void sanitizesUrlWithCharactersSafeForFilenames() { + String output = filenameMaker.sanitizeUrl("/hello/1/2/3__!/ẮČĖ--ace/¥$$/$/and/¿?"); + assertThat(output, is("hello_1_2_3___ace--ace___and")); + } + + @Test + void generatesSanitizedFilename() { + String filename = + filenameMaker.filenameFor( + get("/hello/1/2/3__!/ẮČĖ--ace/¥$$/$/and/¿?").willReturn(ok()).build()); + assertThat(filename, startsWith("get-hello123__--aceand-")); + } + + @Test + public void truncatesWhenResultingNameOver200Chars() { + String output = + filenameMaker.sanitizeUrl( + "/hello/1/2/3__!/ẮČĖ--ace/¥$$/$/andverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuff/¿?"); + assertThat(output.length(), is(200)); + } + + @Test + void includesStubNameWhenPresent() { + StubMapping stub = + Json.read( + "{\n" + + " \"name\": \"This is a NAMED stub\",\n" + + " \"persistent\": true,\n" + + " \"request\": {\n" + + " \"urlPath\": \"/one/two/three\",\n" + + " \"method\": \"GET\"\n" + + " },\n" + + "\n" + + " \"response\": {\n" + + " \"status\": 200\n" + + " }\n" + + "}\n", + StubMapping.class); + String filename = filenameMaker.filenameFor(stub); + + assertThat(filename, is("this-is-a-named-stub-" + stub.getId() + ".json")); + } + + @Test + void handlesAnAllDefaultsStub() { + StubMapping stubMapping = Json.read("{}", StubMapping.class); + + String filename = filenameMaker.filenameFor(stubMapping); + + assertThat(filename, startsWith("any-always-")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/JettySettingsTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/JettySettingsTest.java index 45672220fb..53463f155a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/JettySettingsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/JettySettingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.*; -import com.google.common.base.Optional; +import java.util.Optional; import org.junit.jupiter.api.Test; public class JettySettingsTest { diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java index 929db78d7c..36e272d2bc 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Thomas Akehurst + * Copyright (C) 2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java index e4de32d83b..bfc16b1e45 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Thomas Akehurst + * Copyright (C) 2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/SafeNamesTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/SafeNamesTest.java deleted file mode 100644 index e3710e63bd..0000000000 --- a/src/test/java/com/github/tomakehurst/wiremock/common/SafeNamesTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2017-2021 Thomas Akehurst - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.github.tomakehurst.wiremock.common; - -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.ok; -import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import org.junit.jupiter.api.Test; - -public class SafeNamesTest { - - @Test - public void generatesNameFromStubNameWhenPresent() { - StubMapping mapping = - WireMock.get("/named").withName("This is a NAMED stub").willReturn(ok()).build(); - - assertThat( - SafeNames.makeSafeFileName(mapping), - is("this-is-a-named-stub-" + mapping.getId() + ".json")); - } - - @Test - public void generatesNameFromStubUrlWhenNameNotPresent() { - StubMapping mapping = - WireMock.get(urlMatching("/named/([0-9]*)/things")).willReturn(ok()).build(); - - assertThat( - SafeNames.makeSafeFileName(mapping), is("named0-9things-" + mapping.getId() + ".json")); - } - - @Test - public void generatesNameWhenStubUrlIsAnyAndNameNotPresent() { - StubMapping mapping = WireMock.get(anyUrl()).willReturn(ok()).build(); - - assertThat(SafeNames.makeSafeFileName(mapping), is(mapping.getId() + ".json")); - } - - @Test - public void generatesNameFromNameWithCharactersSafeForFilenames() { - String output = SafeNames.makeSafeName("ẄǏŔe mȎČǨs it!"); - assertThat(output, is("wire-mocks-it")); - } - - @Test - public void doesNothingWhenAlreadySafe() { - String input = "wire-mocks__it--123-4"; - String output = SafeNames.makeSafeName(input); - assertThat(output, is(input)); - } - - @Test - public void generatesNameFromUrlPathWithCharactersSafeForFilenames() { - String output = SafeNames.makeSafeNameFromUrl("/hello/1/2/3__!/ẮČĖ--ace/¥$$/$/and/¿?"); - assertThat(output, is("hello_1_2_3___ace--ace___and")); - } - - @Test - public void truncatesWhenResultingNameOver200Chars() { - String output = - SafeNames.makeSafeNameFromUrl( - "/hello/1/2/3__!/ẮČĖ--ace/¥$$/$/andverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuffandverylongstuff/¿?"); - assertThat(output.length(), is(200)); - } -} diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/ServletContextFileSourceTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/ServletContextFileSourceTest.java index f0d65cebf6..30122a5711 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/ServletContextFileSourceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/ServletContextFileSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,13 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import jakarta.servlet.*; +import jakarta.servlet.ServletRegistration.Dynamic; +import jakarta.servlet.descriptor.JspConfigDescriptor; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.*; -import javax.servlet.*; -import javax.servlet.descriptor.JspConfigDescriptor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,11 +70,7 @@ public void throwsUnsupportedExceptionWhenAttemptingToWrite() { @Test public void throwsUnsupportedExceptionWhenAttemptingToCreate() { - assertThrows( - UnsupportedOperationException.class, - () -> { - fileSource.createIfNecessary(); - }); + assertThrows(UnsupportedOperationException.class, fileSource::createIfNecessary); } private static class MockServletContext implements ServletContext { @@ -332,8 +329,42 @@ public String getVirtualServerName() { @Override public String getContextPath() { - // TODO Auto-generated method stub - return null; + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public Dynamic addJspFile(String servletName, String jspFile) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public int getSessionTimeout() { + return 0; + } + + @Override + public void setSessionTimeout(int sessionTimeout) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public String getRequestCharacterEncoding() { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public void setRequestCharacterEncoding(String encoding) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public String getResponseCharacterEncoding() { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public void setResponseCharacterEncoding(String encoding) { + throw new UnsupportedOperationException("not yet implemented"); } } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSettingsTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSettingsTest.java index 11599c0f16..4bfb6417f0 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSettingsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSettingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,15 @@ public void loadsTrustStoreFromClasspath() { assertNotNull(keyStore); } + @Test + public void loadsTrustStoreOfTypeJCEKS() { + KeyStoreSettings trustStoreSettings = + new KeyStoreSettings(JCEKS_TRUST_STORE_NAME, TRUST_STORE_PASSWORD, "jceks"); + + KeyStore keyStore = trustStoreSettings.loadStore(); + assertNotNull(keyStore); + } + @Test public void loadsTrustStoreFromFilesystem() { KeyStoreSettings trustStoreSettings = diff --git a/src/test/java/com/github/tomakehurst/wiremock/admin/AdminUriTemplateTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/url/PathTemplateTest.java similarity index 63% rename from src/test/java/com/github/tomakehurst/wiremock/admin/AdminUriTemplateTest.java rename to src/test/java/com/github/tomakehurst/wiremock/common/url/PathTemplateTest.java index 41aa2a14a1..d39147322f 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/admin/AdminUriTemplateTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/url/PathTemplateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,27 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.admin; +package com.github.tomakehurst.wiremock.common.url; import static java.lang.String.format; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; -import com.github.tomakehurst.wiremock.admin.model.PathParams; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.junit.jupiter.api.Test; -public class AdminUriTemplateTest { +public class PathTemplateTest { @Test public void extractsSinglePathParameter() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}"); + PathTemplate template = new PathTemplate("/things/{id}"); PathParams pathParams = template.parse("/things/11-22-33"); @@ -45,35 +43,35 @@ public void throwsIllegalArgumentExceptionIfAttemptingParsingOnNonMatchingUrl() assertThrows( IllegalArgumentException.class, () -> { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}"); + PathTemplate template = new PathTemplate("/things/{id}"); template.parse("/things/stuff/11-22-33"); }); } @Test public void matchesWhenUrlIsEquivalentToTemplate() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/otherthings/{subId}"); + PathTemplate template = new PathTemplate("/things/{id}/otherthings/{subId}"); assertThat(template.matches("/things/11-22-33/otherthings/12378"), is(true)); } @Test public void nonMatchWhenUrlIsShorterThanTemplate() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/otherthings/{subId}"); + PathTemplate template = new PathTemplate("/things/{id}/otherthings/{subId}"); assertThat(template.matches("/things/11-22-33/otherthings"), is(false)); } @Test public void nonMatchWhenUrlPartIsMismatch() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/otherthings/{subId}"); + PathTemplate template = new PathTemplate("/things/{id}/otherthings/{subId}"); assertThat(template.matches("/things/11-22-33/other-stuff/1234"), is(false)); } @Test public void rendersWithParameters() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/otherthings/{subId}"); + PathTemplate template = new PathTemplate("/things/{id}/otherthings/{subId}"); PathParams pathParams = new PathParams().add("id", "123").add("subId", "456"); String path = template.render(pathParams); @@ -83,7 +81,7 @@ public void rendersWithParameters() { @Test public void rendersWithoutParameters() { - AdminUriTemplate template = new AdminUriTemplate("/things/stuff"); + PathTemplate template = new PathTemplate("/things/stuff"); String path = template.render(PathParams.empty()); @@ -95,14 +93,14 @@ public void throwsErrorWhenNotAllParametersAreBound() { assertThrows( IllegalArgumentException.class, () -> { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/otherthings/{subId}"); + PathTemplate template = new PathTemplate("/things/{id}/otherthings/{subId}"); template.render(new PathParams().add("id", "123")); }); } @Test public void parseWithWildcardAndOneDepthPath() { - AdminUriTemplate template = new AdminUriTemplate("/things/**"); + PathTemplate template = new PathTemplate("/things/**"); PathParams pathParams = template.parse("/things/stuff"); @@ -111,7 +109,7 @@ public void parseWithWildcardAndOneDepthPath() { @Test public void parseWithWildcardAndTwoDepthPath() { - AdminUriTemplate template = new AdminUriTemplate("/things/**"); + PathTemplate template = new PathTemplate("/things/**"); PathParams pathParams = template.parse("/things/foo/bar"); @@ -120,7 +118,7 @@ public void parseWithWildcardAndTwoDepthPath() { @Test public void parseWithVariableAndWildcardAndTwoDepthPath() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/**"); + PathTemplate template = new PathTemplate("/things/{id}/**"); PathParams pathParams = template.parse("/things/foo/bar"); @@ -130,7 +128,7 @@ public void parseWithVariableAndWildcardAndTwoDepthPath() { @Test public void renderWithWildcardAndOneDepth() { - AdminUriTemplate template = new AdminUriTemplate("/things/**"); + PathTemplate template = new PathTemplate("/things/**"); PathParams pathParams = new PathParams().add("0", "stuff"); String path = template.render(pathParams); @@ -140,7 +138,7 @@ public void renderWithWildcardAndOneDepth() { @Test public void renderWithWildcardAndTwoDepth() { - AdminUriTemplate template = new AdminUriTemplate("/things/**"); + PathTemplate template = new PathTemplate("/things/**"); PathParams pathParams = new PathParams().add("0", "foo/bar"); String path = template.render(pathParams); @@ -150,7 +148,7 @@ public void renderWithWildcardAndTwoDepth() { @Test public void renderWithVariableAndWildcardAndTwoDepthPath() { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/**"); + PathTemplate template = new PathTemplate("/things/{id}/**"); PathParams pathParams = new PathParams().add("id", "foo").add("0", "bar"); String path = template.render(pathParams); @@ -163,7 +161,7 @@ public void throwsErrorWhenNotWildcardParameterIsNotBound() { assertThrows( IllegalArgumentException.class, () -> { - AdminUriTemplate template = new AdminUriTemplate("/things/{id}/**"); + PathTemplate template = new PathTemplate("/things/{id}/**"); template.render(new PathParams().add("id", "123")); }); } @@ -181,9 +179,9 @@ public void checkHashAndEquality() { "/things/{id}/", "/things/{name}/"); - Set uriTemplateSet = new LinkedHashSet<>(); + Set uriTemplateSet = new LinkedHashSet<>(); for (String template : templates) { - AdminUriTemplate uriTemplate = new AdminUriTemplate(template); + PathTemplate uriTemplate = new PathTemplate(template); if (!uriTemplateSet.add(uriTemplate)) { fail(format("Can't add '%s' to '%s'", template, uriTemplateSet)); } @@ -203,13 +201,50 @@ public void checkEquality() { "/things/{id}/", "/things/{name}/"); - List uriTemplates = new ArrayList<>(); + List uriTemplates = new ArrayList<>(); for (String template : templates) { - AdminUriTemplate uriTemplate = new AdminUriTemplate(template); + PathTemplate uriTemplate = new PathTemplate(template); if (uriTemplates.contains(uriTemplate)) { fail(format("Can't add '%s' to '%s'", template, uriTemplates)); } uriTemplates.add(uriTemplate); } } + + @Test + void returnsPathTemplateWithVariablesStrippedOut() { + PathTemplate pathTemplate = new PathTemplate("/one/{first}/two/{second}/three"); + assertThat(pathTemplate.withoutVariables(), is("/one//two//three")); + } + + @Test + void indicatesWhetherAStringCouldBeAPathTemplate() { + assertTrue(PathTemplate.couldBePathTemplate("/things/{id}")); + assertTrue(PathTemplate.couldBePathTemplate("/things/**")); + assertTrue(PathTemplate.couldBePathTemplate("/things/{id}/stuff")); + + assertFalse(PathTemplate.couldBePathTemplate("/things/in/path")); + assertFalse(PathTemplate.couldBePathTemplate("/thing")); + } + + @Test + void correctlyStripsFormatCharactersFromKeysWhenParsing() { + PathTemplate pathTemplate = new PathTemplate("/one/{.first}/two/{;second*}"); + PathParams pathParams = pathTemplate.parse("/one/.3,4,5/two/;second=1;second=2"); + + assertThat(pathParams.get("first"), is(".3,4,5")); + assertThat(pathParams.get("second"), is(";second=1;second=2")); + } + + @Test + void correctlyStripsFormatCharactersFromKeysWhenRendering() { + PathTemplate pathTemplate = new PathTemplate("/one/{.first}/two/{;second*}"); + + PathParams pathParams = + new PathParams().add("first", ".3,4,5").add("second", ";second=1;second=2"); + + String renderedUrl = pathTemplate.render(pathParams); + + assertThat(renderedUrl, is("/one/.3,4,5/two/;second=1;second=2")); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/core/WireMockConfigurationTest.java b/src/test/java/com/github/tomakehurst/wiremock/core/WireMockConfigurationTest.java index a3cba154a0..60b36a6235 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/core/WireMockConfigurationTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/core/WireMockConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertFalse; -import com.google.common.base.Optional; +import java.util.Optional; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.Test; @@ -72,4 +73,11 @@ public void shouldUseQueuedThreadPoolByDefault() { assertThat(threadPool.getMaxThreads(), is(maxThreads)); } + + @Test + public void testProxyPassThroughSetAsFalse() { + WireMockConfiguration wireMockConfiguration = + WireMockConfiguration.wireMockConfig().proxyPassThrough(false); + assertFalse(wireMockConfiguration.getStores().getSettingsStore().get().getProxyPassThrough()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServerTest.java b/src/test/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServerTest.java index a52a22dffe..c9c5d544b7 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/direct/DirectCallHttpServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.github.tomakehurst.wiremock.direct; +import static java.util.Collections.emptyMap; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -22,7 +23,7 @@ import com.github.tomakehurst.wiremock.common.JettySettings; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.http.*; -import com.google.common.base.Optional; +import java.util.Optional; import java.util.concurrent.TimeoutException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -45,7 +46,7 @@ class DirectCallHttpServerTest { @BeforeEach void setup() { when(options.jettySettings()).thenReturn(jettySettings); - when(jettySettings.getStopTimeout()).thenReturn(Optional.absent()); + when(jettySettings.getStopTimeout()).thenReturn(Optional.empty()); server = new DirectCallHttpServer(sleepFacade, options, adminRequestHandler, stubRequestHandler); } @@ -63,7 +64,7 @@ void publicConstructor() { class Start { @Test void doesNothing() { - assertDoesNotThrow(() -> server.start()); + assertDoesNotThrow(server::start); } } @@ -71,7 +72,7 @@ void doesNothing() { class Stop { @Test void doesNothing() { - assertDoesNotThrow(() -> server.start()); + assertDoesNotThrow(server::start); } } @@ -154,18 +155,18 @@ void setup() { doAnswer( (i) -> { HttpResponder responder = i.getArgument(1, HttpResponder.class); - responder.respond(request, response); + responder.respond(request, response, emptyMap()); return null; }) .when(handler()) - .handle(any(), any()); + .handle(any(), any(), any()); actual = handle(request); } @Test void delegatesRequest() { - verify(handler()).handle(eq(request), any()); + verify(handler()).handle(eq(request), any(), any()); } @Test @@ -186,18 +187,18 @@ void setup() { doAnswer( (i) -> { HttpResponder responder = i.getArgument(1, HttpResponder.class); - responder.respond(request, response); + responder.respond(request, response, emptyMap()); return null; }) .when(handler()) - .handle(any(), any()); + .handle(any(), any(), any()); actual = handle(request); } @Test void delegatesRequest() { - verify(handler()).handle(eq(request), any()); + verify(handler()).handle(eq(request), any(), any()); } @Test diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/ParametersTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/ParametersTest.java index 7b0a80ea5c..ebb32f6c0c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/ParametersTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/ParametersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableMap; +import java.time.LocalDate; +import java.util.Map; import org.junit.jupiter.api.Test; public class ParametersTest { @@ -28,31 +29,38 @@ public class ParametersTest { @Test public void convertsParametersToAnObject() { MyData myData = - Parameters.from(ImmutableMap.of("name", "Tom", "num", 27)).as(MyData.class); + Parameters.from(Map.of("name", "Tom", "num", 27, "date", "2023-01-01")).as(MyData.class); assertThat(myData.getName(), is("Tom")); assertThat(myData.getNum(), is(27)); + assertThat(myData.getDate(), is(LocalDate.of(2023, 1, 1))); } @Test public void convertsToParametersFromAnObject() { - MyData myData = new MyData("Mark", 12); + MyData myData = new MyData("Mark", 12, LocalDate.of(2023, 1, 1)); Parameters parameters = Parameters.of(myData); assertThat(parameters.getString("name"), is("Mark")); assertThat(parameters.getInt("num"), is(12)); + assertThat(parameters.getString("date"), is("2023-01-01")); } public static class MyData { private final String name; private final Integer num; + private final LocalDate date; @JsonCreator - public MyData(@JsonProperty("name") String name, @JsonProperty("num") Integer num) { + public MyData( + @JsonProperty("name") String name, + @JsonProperty("num") Integer num, + @JsonProperty("date") LocalDate date) { this.name = name; this.num = num; + this.date = date; } public String getName() { @@ -62,5 +70,9 @@ public String getName() { public Integer getNum() { return num; } + + public LocalDate getDate() { + return date; + } } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapperTest.java index e32638573d..1d8d137c94 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestWrapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,7 @@ import com.github.tomakehurst.wiremock.http.*; import com.github.tomakehurst.wiremock.matching.MockRequest; -import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; -import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; public class RequestWrapperTest { @@ -46,14 +44,10 @@ public void transformsTheUrl() { Request wrappedRequest = RequestWrapper.create() .transformAbsoluteUrl( - new FieldTransformer() { - @Override - public String transform(String existingUrl) { - return existingUrl + existingUrl -> + existingUrl .replace("my.domain", "wiremock.org") - .replace("/original-path", "/new-path"); - } - }) + .replace("/original-path", "/new-path")) .wrap(request); assertThat(wrappedRequest.getUrl(), is("/new-path?one=1&two=2")); @@ -66,13 +60,7 @@ public void transformsAUrlWithNoPath() { Request wrappedRequest = RequestWrapper.create() - .transformAbsoluteUrl( - new FieldTransformer() { - @Override - public String transform(String existingUrl) { - return existingUrl.replace("my.domain", "wiremock.org"); - } - }) + .transformAbsoluteUrl(existingUrl -> existingUrl.replace("my.domain", "wiremock.org")) .wrap(request); assertThat(wrappedRequest.getUrl(), is("")); @@ -106,36 +94,16 @@ public void transformsSpecifiedHeaders() { RequestWrapper.create() .transformHeader( "One", - new FieldTransformer>() { - @Override - public List transform(List headerValues) { - return FluentIterable.from(headerValues) - .transform( - new Function() { - @Override - public String apply(String headerValue) { - return headerValue + "1"; - } - }) - .toList(); - } - }) + headerValues -> + headerValues.stream() + .map(headerValue -> headerValue + "1") + .collect(Collectors.toList())) .transformHeader( "two", - new FieldTransformer>() { - @Override - public List transform(List headerValues) { - return FluentIterable.from(headerValues) - .transform( - new Function() { - @Override - public String apply(String headerValue) { - return headerValue + "2"; - } - }) - .toList(); - } - }) + headerValues -> + headerValues.stream() + .map(headerValue -> headerValue + "2") + .collect(Collectors.toList())) .wrap(request); assertThat(wrappedRequest.getHeader("One"), is("11")); @@ -175,14 +143,7 @@ public void transformsSpecifiedCookies() { Request wrappedRequest = RequestWrapper.create() - .transformCookie( - "One", - new FieldTransformer() { - @Override - public Cookie transform(Cookie cookie) { - return new Cookie(cookie.firstValue() + "1"); - } - }) + .transformCookie("One", cookie -> new Cookie(cookie.firstValue() + "1")) .wrap(request); assertThat(wrappedRequest.getCookies().get("One").firstValue(), is("11")); @@ -195,12 +156,9 @@ public void transformsAStringBody() { Request wrappedRequest = RequestWrapper.create() .transformBody( - new FieldTransformer() { - @Override - public Body transform(Body existingBody) { - String newValue = existingBody.asString().replace("One", "Two"); - return new Body(newValue); - } + existingBody -> { + String newValue = existingBody.asString().replace("One", "Two"); + return new Body(newValue); }) .wrap(request); @@ -216,15 +174,7 @@ public void transformsABinaryBody() { MockRequest request = mockRequest().body(initialBytes); Request wrappedRequest = - RequestWrapper.create() - .transformBody( - new FieldTransformer() { - @Override - public Body transform(Body existingBody) { - return new Body(finalBytes); - } - }) - .wrap(request); + RequestWrapper.create().transformBody(existingBody -> new Body(finalBytes)).wrap(request); assertThat(wrappedRequest.getBody(), is(finalBytes)); assertThat(wrappedRequest.getBodyAsBase64(), is(encodeBase64(finalBytes))); @@ -238,14 +188,10 @@ public void transformsMultiparts() { Request wrappedRequest = RequestWrapper.create() .transformParts( - new FieldTransformer() { - @Override - public Request.Part transform(Request.Part existingPart) { - return existingPart.getName().equals("one") + existingPart -> + existingPart.getName().equals("one") ? mockPart().name("one").body("1111") - : mockPart().name("two").body("2222"); - } - }) + : mockPart().name("two").body("2222")) .wrap(request); assertThat(wrappedRequest.getPart("one").getBody().asString(), is("1111")); diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java index 639f07c4c6..fadedf8c62 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,21 +17,27 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; +import static com.github.tomakehurst.wiremock.stubbing.ServeEventFactory.newPostMatchServeEvent; import static java.time.temporal.ChronoUnit.DAYS; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; -import com.github.jknack.handlebars.EscapingStrategy; -import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Helper; -import com.github.jknack.handlebars.Options; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; -import com.github.tomakehurst.wiremock.common.ClasspathFileSource; import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import java.io.IOException; +import com.github.tomakehurst.wiremock.matching.MockRequest; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import com.github.tomakehurst.wiremock.stubbing.ServeEventFactory; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import com.github.tomakehurst.wiremock.testsupport.ExtensionFactoryUtils; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import java.time.Duration; +import java.time.Instant; import java.time.YearMonth; import java.time.ZonedDateTime; import java.time.temporal.TemporalAdjusters; @@ -49,7 +55,7 @@ public class ResponseTemplateTransformerTest { @BeforeEach public void setup() { - transformer = new ResponseTemplateTransformer(true); + transformer = ExtensionFactoryUtils.buildTemplateTransformer(true); } @Test @@ -171,6 +177,16 @@ public void fullUrl() { assertThat(transformedResponseDef.getBody(), is("URL: /the/entire/path?query1=one&query2=two")); } + @Test + public void clientIp() { + ResponseDefinition transformedResponseDef = + transform( + mockRequest().url("/").clientIp("127.0.0.1"), + aResponse().withBody("IP: {{{request.clientIp}}}")); + + assertThat(transformedResponseDef.getBody(), is("IP: 127.0.0.1")); + } + @Test public void templatizeBodyFile() { ResponseDefinition transformedResponseDef = @@ -246,16 +262,9 @@ public void conditionalHelper() { @Test public void customHelper() { - Helper helper = - new Helper() { - @Override - public Object apply(String context, Options options) throws IOException { - return context.length(); - } - }; + Helper helper = (context, options) -> context.length(); - transformer = - ResponseTemplateTransformer.builder().global(false).helper("string-length", helper).build(); + transformer = ExtensionFactoryUtils.buildTemplateTransformer(false, "string-length", helper); ResponseDefinition transformedResponseDef = transform( @@ -298,10 +307,9 @@ public void proxyBaseUrlWithAdditionalRequestHeader() { @Test public void escapingIsTheDefault() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( mockRequest().url("/json").body("{\"a\": {\"test\": \"look at my 'single quotes'\"}}"), - aResponse().withBody("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}").build(), - noFileSource(), + aResponse().withBody("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}"), Parameters.empty()); assertThat( @@ -311,10 +319,9 @@ public void escapingIsTheDefault() { @Test public void jsonPathValueDefaultsToEmptyString() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( mockRequest().url("/json").body("{\"a\": \"1\"}"), - aResponse().withBody("{{jsonPath request.body '$.b'}}").build(), - noFileSource(), + aResponse().withBody("{{jsonPath request.body '$.b'}}"), Parameters.empty()); assertThat(responseDefinition.getBody(), is("")); } @@ -322,51 +329,51 @@ public void jsonPathValueDefaultsToEmptyString() { @Test public void jsonPathValueDefaultCanBeProvided() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( mockRequest().url("/json").body("{\"a\": \"1\"}"), - aResponse().withBody("{{jsonPath request.body '$.b' default='foo'}}").build(), - noFileSource(), + aResponse().withBody("{{jsonPath request.body '$.b' default='foo'}}"), Parameters.empty()); assertThat(responseDefinition.getBody(), is("foo")); } - @Test - public void escapingCanBeDisabled() { - Handlebars handlebars = new Handlebars().with(EscapingStrategy.NOOP); - ResponseTemplateTransformer transformerWithEscapingDisabled = - ResponseTemplateTransformer.builder().global(true).handlebars(handlebars).build(); - final ResponseDefinition responseDefinition = - transformerWithEscapingDisabled.transform( - mockRequest().url("/json").body("{\"a\": {\"test\": \"look at my 'single quotes'\"}}"), - aResponse().withBody("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}").build(), - noFileSource(), - Parameters.empty()); - - assertThat(responseDefinition.getBody(), is("{\"test\": \"look at my 'single quotes'\"}")); - } - @Test public void transformerParametersAreAppliedToTemplate() throws Exception { ResponseDefinition responseDefinition = - transformer.transform( + transform( mockRequest().url("/json").body("{\"a\": {\"test\": \"look at my 'single quotes'\"}}"), - aResponse().withBody("{\"test\": \"{{parameters.variable}}\"}").build(), - noFileSource(), + aResponse().withBody("{\"test\": \"{{parameters.variable}}\"}"), Parameters.one("variable", "some.value")); assertThat(responseDefinition.getBody(), is("{\"test\": \"some.value\"}")); } + private ResponseDefinition transform( + Request request, ResponseDefinitionBuilder responseDefinitionBuilder, Parameters parameters) { + return transform(this.transformer, request, responseDefinitionBuilder, parameters); + } + + private ResponseDefinition transform( + ResponseDefinitionTransformerV2 transformer, + Request request, + ResponseDefinitionBuilder responseDefinitionBuilder, + Parameters parameters) { + StubMapping stubMapping = + get("/json").willReturn(aResponse().withTransformerParameters(parameters)).build(); + responseDefinitionBuilder.withTransformerParameters(parameters); + ServeEvent serveEvent = + newPostMatchServeEvent( + LoggedRequest.createFrom(request), responseDefinitionBuilder, stubMapping); + return transformer.transform(serveEvent); + } + @Test public void unknownTransformerParametersAreNotCausingIssues() throws Exception { ResponseDefinition responseDefinition = - transformer.transform( + transform( mockRequest().url("/json").body("{\"a\": {\"test\": \"look at my 'single quotes'\"}}"), aResponse() .withBody( - "{\"test1\": \"{{parameters.variable}}\", \"test2\": \"{{parameters.unknown}}\"}") - .build(), - noFileSource(), + "{\"test1\": \"{{parameters.variable}}\", \"test2\": \"{{parameters.unknown}}\"}"), Parameters.one("variable", "some.value")); assertThat(responseDefinition.getBody(), is("{\"test1\": \"some.value\", \"test2\": \"\"}")); @@ -782,7 +789,7 @@ public void clearsTemplateCacheWhenAnyStubRemovedReset() { @Test public void honoursCacheSizeLimit() { - transformer = ResponseTemplateTransformer.builder().maxCacheEntries(3L).build(); + transformer = ExtensionFactoryUtils.buildTemplateTransformer(3L); transform("{{now}} 1"); transform("{{now}} 2"); @@ -795,7 +802,7 @@ public void honoursCacheSizeLimit() { @Test public void honours0CacheSizeLimit() { - transformer = ResponseTemplateTransformer.builder().maxCacheEntries(0L).build(); + transformer = ExtensionFactoryUtils.buildTemplateTransformer(0L); transform("{{now}} 1"); transform("{{now}} 2"); @@ -907,6 +914,26 @@ public void parseJsonReportsInvalidParameterErrors() { assertThat(transform("{{parseJson}}"), is("[ERROR: Missing required JSON string parameter]")); } + @Test + public void parsesEmptyJsonLiteralToAnEmptyMap() { + String result = transform("{{#parseJson 'parsedObj'}}\n" + "{\n" + "}\n" + "{{/parseJson}}\n"); + + assertThat(result, equalToCompressingWhiteSpace("")); + } + + @Test + public void parsesEmptyJsonVariableToAnEmptyMap() { + String result = + transform( + "{{#assign 'json'}}\n" + + "{\n" + + "}\n" + + "{{/assign}}\n" + + "{{parseJson json 'parsedObj'}}\n"); + + assertThat(result, equalToCompressingWhiteSpace("")); + } + @Test public void conditionalBranchingOnStringMatchesRegexInline() { assertThat(transform("{{#if (matches '123' '[0-9]+')}}YES{{/if}}"), is("YES")); @@ -1007,6 +1034,18 @@ public void canParseLocalYear() { assertThat(result, is(expected)); } + @Test + public void canHandleALargeTemplateReasonablyFast() { + String template = "{{#each (range 100000 199999) as |index|}}Line {{index}}\n{{/each}}"; + Instant start = Instant.now(); + String result = transform(template); + Duration timeTaken = Duration.between(start, Instant.now()); + + assertThat(result.substring(0, 100), startsWith("Line 100000\nLine 100001\nLine 100002\n")); + assertThat(result.length(), equalTo(1_200_000)); + assertThat(timeTaken, lessThan(Duration.ofSeconds(5))); + } + private Integer transformToInt(String responseBodyTemplate) { return Integer.parseInt(transform(responseBodyTemplate)); } @@ -1016,7 +1055,11 @@ private Double transformToDouble(String responseBodyTemplate) { } private String transform(String responseBodyTemplate) { - return transform(mockRequest(), aResponse().withBody(responseBodyTemplate)).getBody(); + final ResponseDefinitionBuilder responseDefinitionBuilder = + aResponse().withBody(responseBodyTemplate); + final StubMapping stub = get("/").willReturn(responseDefinitionBuilder).build(); + final MockRequest request = mockRequest(); + return transform(newPostMatchServeEvent(request, responseDefinitionBuilder, stub)).getBody(); } private String transform(String responseBodyTemplate, String requestBody) { @@ -1026,17 +1069,19 @@ private String transform(String responseBodyTemplate, String requestBody) { private ResponseDefinition transform( Request request, ResponseDefinitionBuilder responseDefinitionBuilder) { - return transformer.transform( - request, responseDefinitionBuilder.build(), noFileSource(), Parameters.empty()); + final StubMapping stub = get("/").willReturn(responseDefinitionBuilder).build(); + return transform(newPostMatchServeEvent(request, responseDefinitionBuilder, stub)); + } + + private ResponseDefinition transform(ServeEvent serveEvent) { + return transformer.transform(serveEvent); } private ResponseDefinition transformFromResponseFile( Request request, ResponseDefinitionBuilder responseDefinitionBuilder) { + + final StubMapping stub = get("/").willReturn(responseDefinitionBuilder).build(); return transformer.transform( - request, - responseDefinitionBuilder.build(), - new ClasspathFileSource( - this.getClass().getClassLoader().getResource("templates").getPath()), - Parameters.empty()); + ServeEventFactory.newPostMatchServeEvent(request, responseDefinitionBuilder, stub)); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthorisorTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthorisorTest.java index b629ac18bf..7593a31e7a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthorisorTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/SystemKeyAuthorisorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.google.common.collect.ImmutableSet; +import java.util.Set; import org.junit.jupiter.api.Test; public class SystemKeyAuthorisorTest { @Test public void permitsAllowedKeys() { - SystemKeyAuthoriser authoriser = - new SystemKeyAuthoriser(ImmutableSet.of("allowed_.*", "permitted_.*")); + SystemKeyAuthoriser authoriser = new SystemKeyAuthoriser(Set.of("allowed_.*", "permitted_.*")); assertTrue(authoriser.isPermitted("allowed_key_1")); assertTrue(authoriser.isPermitted("ALLOWED_KEY_2")); @@ -35,8 +34,7 @@ public void permitsAllowedKeys() { @Test public void forbidsNonAllowedKeys() { - SystemKeyAuthoriser authoriser = - new SystemKeyAuthoriser(ImmutableSet.of("allowed_.*", "permitted_.*")); + SystemKeyAuthoriser authoriser = new SystemKeyAuthoriser(Set.of("allowed_.*", "permitted_.*")); assertFalse(authoriser.isPermitted("forbidden_key_1")); assertFalse(authoriser.isPermitted("notallowed_key_2")); diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsCurrentDateHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsCurrentDateHelperTest.java index 33352b4425..681910ae96 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsCurrentDateHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsCurrentDateHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,9 @@ package com.github.tomakehurst.wiremock.extension.responsetemplating.helpers; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.extension.responsetemplating.helpers.HandlebarsHelperTestBase.transform; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; +import static com.github.tomakehurst.wiremock.testsupport.ExtensionFactoryUtils.buildTemplateTransformer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -26,14 +27,13 @@ import com.github.jknack.handlebars.Options; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.common.LocalNotifier; -import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.testsupport.WireMatchers; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,14 +45,14 @@ public class HandlebarsCurrentDateHelperTest { @BeforeEach public void init() { helper = new HandlebarsCurrentDateHelper(); - transformer = new ResponseTemplateTransformer(true); + transformer = buildTemplateTransformer(true); LocalNotifier.set(new ConsoleNotifier(true)); } @Test public void rendersNowDateTime() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of(); + Map optionsHash = Map.of(); Object output = render(optionsHash); @@ -62,8 +62,7 @@ public void rendersNowDateTime() throws Exception { @Test public void rendersNowDateTimeWithCustomFormat() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("format", "yyyy/mm/dd"); + Map optionsHash = Map.of("format", "yyyy/mm/dd"); Object output = render(optionsHash); @@ -75,8 +74,7 @@ public void rendersNowDateTimeWithCustomFormat() throws Exception { public void rendersPassedDateTimeWithDayOffset() throws Exception { String format = "yyyy-MM-dd"; SimpleDateFormat df = new SimpleDateFormat(format); - ImmutableMap optionsHash = - ImmutableMap.of("format", format, "offset", "5 days"); + Map optionsHash = Map.of("format", format, "offset", "5 days"); Object output = render(df.parse("2018-04-16"), optionsHash); @@ -85,8 +83,7 @@ public void rendersPassedDateTimeWithDayOffset() throws Exception { @Test public void rendersNowWithDayOffset() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("offset", "6 months"); + Map optionsHash = Map.of("offset", "6 months"); Object output = render(optionsHash); @@ -95,7 +92,7 @@ public void rendersNowWithDayOffset() throws Exception { @Test public void rendersNowAsUnixEpochInMilliseconds() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of("format", "epoch"); + Map optionsHash = Map.of("format", "epoch"); Date date = new Date(); Object output = render(date, optionsHash); @@ -105,7 +102,7 @@ public void rendersNowAsUnixEpochInMilliseconds() throws Exception { @Test public void rendersNowAsUnixEpochInSeconds() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of("format", "unix"); + Map optionsHash = Map.of("format", "unix"); Date date = new Date(); Object output = render(date, optionsHash); @@ -115,10 +112,7 @@ public void rendersNowAsUnixEpochInSeconds() throws Exception { @Test public void adjustsISO8601ToSpecfiedTimezone() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of( - "offset", "3 days", - "timezone", "Australia/Sydney"); + Map optionsHash = Map.of("offset", "3 days", "timezone", "Australia/Sydney"); Date inputDate = new ISO8601DateFormat().parse("2014-10-09T06:06:01Z"); Object output = render(inputDate, optionsHash); @@ -128,11 +122,9 @@ public void adjustsISO8601ToSpecfiedTimezone() throws Exception { @Test public void adjustsCustomFormatToSpecfiedTimezone() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of( - "offset", "3 days", - "timezone", "Australia/Sydney", - "format", "yyyy-MM-dd HH:mm:ssZ"); + Map optionsHash = + Map.of( + "offset", "3 days", "timezone", "Australia/Sydney", "format", "yyyy-MM-dd HH:mm:ssZ"); Date inputDate = new ISO8601DateFormat().parse("2014-10-09T06:06:01Z"); Object output = render(inputDate, optionsHash); @@ -143,11 +135,10 @@ public void adjustsCustomFormatToSpecfiedTimezone() throws Exception { @Test public void helperIsIncludedInTemplateTransformerWithNowTagName() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/random-value"), - aResponse().withBody("{{now offset='6 days'}}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{now offset='6 days'}}")); String body = responseDefinition.getBody().trim(); assertThat(body, WireMatchers.matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:]+Z$")); @@ -156,11 +147,10 @@ public void helperIsIncludedInTemplateTransformerWithNowTagName() { @Test public void helperIsIncludedInTemplateTransformerWithDateTagName() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/random-value"), - aResponse().withBody("{{date offset='6 days'}}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{date offset='6 days'}}")); String body = responseDefinition.getBody().trim(); assertThat(body, WireMatchers.matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:]+Z$")); @@ -169,23 +159,20 @@ public void helperIsIncludedInTemplateTransformerWithDateTagName() { @Test public void acceptsDateParameterwithDateTagName() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/parsed-date"), - aResponse() - .withBody("{{date (parseDate '2018-05-05T10:11:12Z') offset='-1 days'}}") - .build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{date (parseDate '2018-05-05T10:11:12Z') offset='-1 days'}}")); String body = responseDefinition.getBody().trim(); assertThat(body, is("2018-05-04T10:11:12Z")); } - private Object render(ImmutableMap optionsHash) throws IOException { + private Object render(Map optionsHash) throws IOException { return render(null, optionsHash); } - private Object render(Date context, ImmutableMap optionsHash) throws IOException { + private Object render(Date context, Map optionsHash) throws IOException { return helper.apply( context, new Options.Builder(null, null, null, null, null).setHash(optionsHash).build()); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsHelperTestBase.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsHelperTestBase.java index 9650d7f53b..5378bceca8 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsHelperTestBase.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsHelperTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,20 @@ */ package com.github.tomakehurst.wiremock.extension.responsetemplating.helpers; +import static com.github.tomakehurst.wiremock.stubbing.ServeEventFactory.newPostMatchServeEvent; +import static com.github.tomakehurst.wiremock.testsupport.ExtensionFactoryUtils.buildTemplateTransformer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.jknack.handlebars.Context; import com.github.jknack.handlebars.Helper; import com.github.jknack.handlebars.Options; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; import com.github.tomakehurst.wiremock.extension.responsetemplating.RenderCache; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -38,7 +44,7 @@ public abstract class HandlebarsHelperTestBase { @BeforeEach public void initRenderCache() { - transformer = new ResponseTemplateTransformer(true); + transformer = buildTemplateTransformer(true); renderCache = new RenderCache(); } @@ -104,4 +110,11 @@ protected static Map map(String key, Object value) { map.put(key, value); return map; } + + public static ResponseDefinition transform( + ResponseDefinitionTransformerV2 transformer, + Request request, + ResponseDefinitionBuilder responseDefinitionBuilder) { + return transformer.transform(newPostMatchServeEvent(request, responseDefinitionBuilder)); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelperTest.java index 372689600f..19d5eaf934 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsJsonPathHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,9 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; +import static com.github.tomakehurst.wiremock.testsupport.ExtensionFactoryUtils.buildExtension; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalToJson; +import static java.util.Collections.emptyList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -31,9 +32,10 @@ import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.google.common.collect.ImmutableMap; +import com.github.tomakehurst.wiremock.testsupport.MockWireMockServices; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -51,11 +53,10 @@ public void init() { @Test public void mergesASimpleValueFromRequestIntoResponseBody() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/json").body("{\"a\": {\"test\": \"success\"}}"), - aResponse().withBody("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{\"test\": \"{{jsonPath request.body '$.a.test'}}\"}")); assertThat(responseDefinition.getBody(), is("{\"test\": \"success\"}")); } @@ -63,11 +64,10 @@ public void mergesASimpleValueFromRequestIntoResponseBody() { @Test public void incluesAnErrorInTheResponseBodyWhenTheJsonPathIsInvalid() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/json").body("{\"a\": {\"test\": \"success\"}}"), - aResponse().withBody("{\"test\": \"{{jsonPath request.body '$![bbb'}}\"}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{\"test\": \"{{jsonPath request.body '$![bbb'}}\"}")); assertThat( responseDefinition.getBody(), startsWith("{\"test\": \"" + HandlebarsHelper.ERROR_PREFIX)); @@ -76,7 +76,8 @@ public void incluesAnErrorInTheResponseBodyWhenTheJsonPathIsInvalid() { @Test public void listResultFromJsonPathQueryCanBeUsedByHandlebarsEachHelper() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .url("/json") .body( @@ -95,10 +96,7 @@ public void listResultFromJsonPathQueryCanBeUsedByHandlebarsEachHelper() { + "}"), aResponse() .withBody( - "{{#each (jsonPath request.body '$.items') as |item|}}{{item.name}} {{/each}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#each (jsonPath request.body '$.items') as |item|}}{{item.name}} {{/each}}")); assertThat(responseDefinition.getBody(), is("One Two Three ")); } @@ -106,7 +104,8 @@ public void listResultFromJsonPathQueryCanBeUsedByHandlebarsEachHelper() { @Test public void mapResultFromJsonPathQueryCanBeUsedByHandlebarsEachHelper() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .url("/json") .body( @@ -119,11 +118,7 @@ public void mapResultFromJsonPathQueryCanBeUsedByHandlebarsEachHelper() { + "}"), aResponse() .withBody( - "" - + "{{#each (jsonPath request.body '$.items') as |value key|}}{{key}}: {{value}} {{/each}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#each (jsonPath request.body '$.items') as |value key|}}{{key}}: {{value}} {{/each}}")); assertThat(responseDefinition.getBody(), is("one: 1 two: 2 three: 3 ")); } @@ -131,7 +126,8 @@ public void mapResultFromJsonPathQueryCanBeUsedByHandlebarsEachHelper() { @Test public void singleValueResultFromJsonPathQueryCanBeUsedByHandlebarsIfHelper() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .url("/json") .body( @@ -144,12 +140,8 @@ public void singleValueResultFromJsonPathQueryCanBeUsedByHandlebarsIfHelper() { + "}"), aResponse() .withBody( - "" - + "{{#if (jsonPath request.body '$.items.one')}}One{{/if}}\n" - + "{{#if (jsonPath request.body '$.items.two')}}Two{{/if}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#if (jsonPath request.body '$.items.one')}}One{{/if}}\n" + + "{{#if (jsonPath request.body '$.items.two')}}Two{{/if}}")); assertThat(responseDefinition.getBody(), containsString("One")); assertThat(responseDefinition.getBody(), not(containsString("Two"))); @@ -215,42 +207,42 @@ public void rendersAnEmptyStringWhenJsonValueUndefined() { @Test public void rendersAnEmptyStringWhenJsonValueUndefinedAndOptionsEmpty() throws Exception { - Map options = ImmutableMap.of(); + Map options = Map.of(); String output = render("{\"test\":\"success\"}", "$.test2", options); assertThat(output, is("")); } @Test public void rendersDefaultValueWhenShallowJsonValueUndefined() throws Exception { - Map options = ImmutableMap.of("default", "0"); + Map options = Map.of("default", "0"); String output = render("{}", "$.test", options); assertThat(output, is("0")); } @Test public void rendersDefaultValueWhenDeepJsonValueUndefined() throws Exception { - Map options = ImmutableMap.of("default", "0"); + Map options = Map.of("default", "0"); String output = render("{}", "$.outer.inner[0]", options); assertThat(output, is("0")); } @Test public void rendersDefaultValueWhenJsonValueNull() throws Exception { - Map options = ImmutableMap.of("default", "0"); + Map options = Map.of("default", "0"); String output = render("{\"test\":null}", "$.test", options); assertThat(output, is("0")); } @Test public void ignoresDefaultWhenJsonValueEmpty() throws Exception { - Map options = ImmutableMap.of("default", "0"); + Map options = Map.of("default", "0"); String output = render("{\"test\":\"\"}", "$.test", options); assertThat(output, is("")); } @Test public void ignoresDefaultWhenJsonValueZero() throws Exception { - Map options = ImmutableMap.of("default", "1"); + Map options = Map.of("default", "1"); String output = render("{\"test\":0}", "$.test", options); assertThat(output, is("0")); } @@ -281,23 +273,26 @@ public void rendersAMeaningfulErrorWhenJsonPathIsNull() { @Test public void extractsValueFromAMap() { ResponseTemplateTransformer transformer = - new ResponseTemplateTransformer(true) { - @Override - protected Map addExtraModelElements( - Request request, - ResponseDefinition responseDefinition, - FileSource files, - Parameters parameters) { - return ImmutableMap.of("mapData", ImmutableMap.of("things", "abc")); - } - }; + (ResponseTemplateTransformer) + buildExtension( + new MockWireMockServices(), + services -> + List.of( + new ResponseTemplateTransformer( + services.getTemplateEngine(), true, services.getFiles(), emptyList()) { + @Override + protected Map addExtraModelElements( + Request request, + ResponseDefinition responseDefinition, + FileSource files, + Parameters parameters) { + return Map.of("mapData", Map.of("things", "abc")); + } + })); final ResponseDefinition responseDefinition = - transformer.transform( - mockRequest(), - aResponse().withBody("{{jsonPath mapData '$.things'}}").build(), - noFileSource(), - Parameters.empty()); + transform( + transformer, mockRequest(), aResponse().withBody("{{jsonPath mapData '$.things'}}")); assertThat(responseDefinition.getBody(), is("abc")); } @@ -352,7 +347,7 @@ public void helperCanBeCalledDirectlyWithoutSupplyingRenderCache() throws Except null, new Object[] {"$.stuff"}, null, - new ArrayList(0)); + new ArrayList<>(0)); Object result = helper.apply("{\"stuff\":1}", options); diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsRandomValuesHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsRandomValuesHelperTest.java index c4e324b7eb..036926fab7 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsRandomValuesHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsRandomValuesHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,19 +17,20 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; +import static com.github.tomakehurst.wiremock.stubbing.ServeEventFactory.newPostMatchServeEvent; +import static com.github.tomakehurst.wiremock.testsupport.ExtensionFactoryUtils.buildTemplateTransformer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.jknack.handlebars.Options; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.common.LocalNotifier; -import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.testsupport.WireMatchers; -import com.google.common.collect.ImmutableMap; import java.io.IOException; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,14 +42,14 @@ public class HandlebarsRandomValuesHelperTest { @BeforeEach public void init() { helper = new HandlebarsRandomValuesHelper(); - transformer = new ResponseTemplateTransformer(true); + transformer = buildTemplateTransformer(true); LocalNotifier.set(new ConsoleNotifier(true)); } @Test public void generatesRandomAlphaNumericOfSpecifiedLength() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of("length", 36); + Map optionsHash = Map.of("length", 36); String output = render(optionsHash); @@ -58,8 +59,7 @@ public void generatesRandomAlphaNumericOfSpecifiedLength() throws Exception { @Test public void generatesUppercaseRandomAlphaNumericOfSpecifiedLength() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("length", 36, "uppercase", true); + Map optionsHash = Map.of("length", 36, "uppercase", true); String output = render(optionsHash); @@ -69,8 +69,7 @@ public void generatesUppercaseRandomAlphaNumericOfSpecifiedLength() throws Excep @Test public void generatesRandomAlphabeticOfSpecifiedLength() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("length", 43, "type", "ALPHABETIC", "uppercase", true); + Map optionsHash = Map.of("length", 43, "type", "ALPHABETIC", "uppercase", true); String output = render(optionsHash); @@ -80,8 +79,7 @@ public void generatesRandomAlphabeticOfSpecifiedLength() throws Exception { @Test public void generatesRandomNumericOfSpecifiedLength() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("length", 55, "type", "NUMERIC"); + Map optionsHash = Map.of("length", 55, "type", "NUMERIC"); String output = render(optionsHash); @@ -91,8 +89,7 @@ public void generatesRandomNumericOfSpecifiedLength() throws Exception { @Test public void generatesRandomStringOfSpecifiedLength() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("length", 67, "type", "ALPHANUMERIC_AND_SYMBOLS"); + Map optionsHash = Map.of("length", 67, "type", "ALPHANUMERIC_AND_SYMBOLS"); String output = render(optionsHash); @@ -102,8 +99,7 @@ public void generatesRandomStringOfSpecifiedLength() throws Exception { @Test public void generatesRandomHexadecimalOfSpecifiedLength() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("length", 64, "type", "HEXADECIMAL"); + Map optionsHash = Map.of("length", 64, "type", "HEXADECIMAL"); String output = render(optionsHash); @@ -113,17 +109,16 @@ public void generatesRandomHexadecimalOfSpecifiedLength() throws Exception { @Test public void randomValuesCanBeAssignedToVariables() { - final ResponseDefinition responseDefinition = - this.transformer.transform( + ServeEvent serveEvent = + newPostMatchServeEvent( mockRequest().url("/random-value"), aResponse() .withBody( "{{#assign 'paymentId'}}{{randomValue length=20 type='ALPHANUMERIC' uppercase=true}}{{/assign}}\n" + "{{paymentId}}\n" - + "{{paymentId}}") - .build(), - noFileSource(), - Parameters.empty()); + + "{{paymentId}}")); + + final ResponseDefinition responseDefinition = this.transformer.transform(serveEvent); String[] bodyLines = responseDefinition.getBody().trim().split("\n"); assertThat(bodyLines[0], is(bodyLines[1])); @@ -132,7 +127,7 @@ public void randomValuesCanBeAssignedToVariables() { @Test public void generatesRandomUUID() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of("type", "UUID"); + Map optionsHash = Map.of("type", "UUID"); String output = render(optionsHash); @@ -140,7 +135,7 @@ public void generatesRandomUUID() throws Exception { assertThat(output, WireMatchers.matches("^[a-z0-9\\-]+$")); } - private String render(ImmutableMap optionsHash) throws IOException { + private String render(Map optionsHash) throws IOException { return helper .apply(null, new Options.Builder(null, null, null, null, null).setHash(optionsHash).build()) .toString(); diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsSoapHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsSoapHelperTest.java index adb8595108..4c6ad7a3e5 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsSoapHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsSoapHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; +import static com.github.tomakehurst.wiremock.testsupport.ExtensionFactoryUtils.buildTemplateTransformer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import java.io.IOException; @@ -36,7 +35,7 @@ public class HandlebarsSoapHelperTest extends HandlebarsHelperTestBase { @BeforeEach public void init() { this.helper = new HandlebarsSoapHelper(); - this.transformer = new ResponseTemplateTransformer(true); + this.transformer = buildTemplateTransformer(true); } @Test @@ -51,16 +50,13 @@ public void extractsASimpleBodyValue() throws IOException { @Test public void rendersASimpleValue() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .url("/soap") .body( "success"), - aResponse() - .withBody("{{soapXPath request.body '/a/test/text()'}}") - .build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{soapXPath request.body '/a/test/text()'}}")); assertThat(responseDefinition.getBody(), is("success")); } @@ -68,14 +64,13 @@ public void rendersASimpleValue() { @Test public void negativeTestResponseTemplate() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .url("/soap") .body( "success"), - aResponse().withBody("{{soapXPath request.body '/b/test'}}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{soapXPath request.body '/b/test'}}")); assertThat(responseDefinition.getBody(), is("")); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsXPathHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsXPathHelperTest.java index 6dc4785543..dc93935268 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsXPathHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HandlebarsXPathHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,12 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalToXml; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalToCompressingWhiteSpace; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; -import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import java.io.IOException; import org.junit.jupiter.api.BeforeEach; @@ -42,11 +40,10 @@ public void init() { @Test public void rendersASimpleValue() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/xml").body("success"), - aResponse().withBody("{{xPath request.body '/a/test/text()'}}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{xPath request.body '/a/test/text()'}}")); assertThat(responseDefinition.getBody(), is("success")); } @@ -54,11 +51,10 @@ public void rendersASimpleValue() { @Test public void rendersNothingWhenTheXPathExpressionResolvesNoContent() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/xml").body("success"), - aResponse().withBody("{{xPath request.body '/b/test'}}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{xPath request.body '/b/test'}}")); assertThat(responseDefinition.getBody(), startsWith("")); } @@ -154,7 +150,8 @@ public void rendersXmlWhenElementIsSelected() throws Exception { @Test public void supportsIterationOverNodeListWithEachHelper() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .body( "\n" @@ -165,10 +162,7 @@ public void supportsIterationOverNodeListWithEachHelper() { + ""), aResponse() .withBody( - "{{#each (xPath request.body '/stuff/thing/text()') as |thing|}}{{thing}} {{/each}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#each (xPath request.body '/stuff/thing/text()') as |thing|}}{{thing}} {{/each}}")); assertThat(responseDefinition.getBody(), is("One Two Three ")); } @@ -176,7 +170,8 @@ public void supportsIterationOverNodeListWithEachHelper() { @Test public void supportsIterationOverElementsWithAttributes() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .body( "\n" @@ -187,10 +182,7 @@ public void supportsIterationOverElementsWithAttributes() { + ""), aResponse() .withBody( - "{{#each (xPath request.body '/stuff/thing') as |thing|}}{{{thing.attributes.id}}} {{/each}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#each (xPath request.body '/stuff/thing') as |thing|}}{{{thing.attributes.id}}} {{/each}}")); assertThat(responseDefinition.getBody(), is("1 2 3 ")); } @@ -198,7 +190,8 @@ public void supportsIterationOverElementsWithAttributes() { @Test public void supportsIterationOverNamespacedElements() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .body( "\n" @@ -209,10 +202,7 @@ public void supportsIterationOverNamespacedElements() { + ""), aResponse() .withBody( - "{{#each (xPath request.body '/stuff/thing') as |thing|}}{{{thing.text}}} {{/each}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#each (xPath request.body '/stuff/thing') as |thing|}}{{{thing.text}}} {{/each}}")); assertThat(responseDefinition.getBody(), is("One Two Three ")); } @@ -220,7 +210,8 @@ public void supportsIterationOverNamespacedElements() { @Test public void rendersNamespacedElement() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .body( "\n" @@ -229,9 +220,7 @@ public void rendersNamespacedElement() { + " Two\n" + " Three\n" + ""), - aResponse().withBody("{{{xPath request.body '/stuff'}}}").build(), - noFileSource(), - Parameters.empty()); + aResponse().withBody("{{{xPath request.body '/stuff'}}}")); assertThat( responseDefinition.getBody(), @@ -246,7 +235,8 @@ public void rendersNamespacedElement() { @Test public void rendersElementNames() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest() .body( "\n" @@ -257,10 +247,7 @@ public void rendersElementNames() { + ""), aResponse() .withBody( - "{{#each (xPath request.body '/stuff/*') as |thing|}}{{{thing.name}}} {{/each}}") - .build(), - noFileSource(), - Parameters.empty()); + "{{#each (xPath request.body '/stuff/*') as |thing|}}{{{thing.name}}} {{/each}}")); assertThat(responseDefinition.getBody(), is("one two three ")); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HostnameHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HostnameHelperTest.java index 07c3bb8b77..1a0a2b2031 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HostnameHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/HostnameHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,10 @@ import com.github.jknack.handlebars.Options; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.common.LocalNotifier; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,13 +43,13 @@ public void init() throws UnknownHostException { @Test public void generatesHostname() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of(); + Map optionsHash = Map.of(); String output = render(optionsHash); assertThat(output, equalToCompressingWhiteSpace(hostname)); } - private String render(ImmutableMap optionsHash) throws IOException { + private String render(Map optionsHash) throws IOException { return helper .apply(null, new Options.Builder(null, null, null, null, null).setHash(optionsHash).build()) .toString(); diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseDateHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseDateHelperTest.java index 03e5527066..a4a914c722 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseDateHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseDateHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,12 @@ import com.fasterxml.jackson.databind.util.ISO8601DateFormat; import com.github.jknack.handlebars.Options; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.text.DateFormat; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Date; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,7 +42,7 @@ public void init() { @Test public void parsesAnISO8601DateWhenNoFormatSpecified() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of(); + Map optionsHash = Map.of(); String inputDate = "2018-05-01T01:02:03Z"; Object output = render(inputDate, optionsHash); @@ -54,7 +54,7 @@ public void parsesAnISO8601DateWhenNoFormatSpecified() throws Exception { @Test public void parsesAnRFC1123DateWhenNoFormatSpecified() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of(); + Map optionsHash = Map.of(); String inputDate = "Tue, 01 Jun 2021 15:16:17 GMT"; Object output = render(inputDate, optionsHash); @@ -67,8 +67,7 @@ public void parsesAnRFC1123DateWhenNoFormatSpecified() throws Exception { @Test public void parsesDateWithSuppliedFormat() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("format", "dd/MM/yyyy"); + Map optionsHash = Map.of("format", "dd/MM/yyyy"); String inputDate = "01/02/2003"; Object output = render(inputDate, optionsHash); @@ -80,8 +79,7 @@ public void parsesDateWithSuppliedFormat() throws Exception { @Test public void parsesLocalDateTimeWithSuppliedFormat() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of("format", "dd/MM/yyyy HH:mm:ss"); + Map optionsHash = Map.of("format", "dd/MM/yyyy HH:mm:ss"); String inputDate = "01/02/2003 05:06:07"; Object output = render(inputDate, optionsHash); @@ -93,7 +91,7 @@ public void parsesLocalDateTimeWithSuppliedFormat() throws Exception { @Test public void parsesDateTimeWithEpochFormat() throws Exception { - ImmutableMap optionsHash = ImmutableMap.of("format", "epoch"); + Map optionsHash = Map.of("format", "epoch"); String inputDate = "1577964091000"; Object output = render(inputDate, optionsHash); @@ -103,8 +101,7 @@ public void parsesDateTimeWithEpochFormat() throws Exception { assertThat(((Date) output), is((expectedDate))); } - private Object render(String context, ImmutableMap optionsHash) - throws IOException { + private Object render(String context, Map optionsHash) throws IOException { return helper.apply( context, new Options.Builder(null, null, null, null, null).setHash(optionsHash).build()); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelperTest.java index b5caba622d..0bf38da671 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/ParseJsonHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,16 @@ package com.github.tomakehurst.wiremock.extension.responsetemplating.helpers; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.nullValue; + +import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Options; import com.github.jknack.handlebars.TagType; import com.github.jknack.handlebars.Template; @@ -116,10 +124,54 @@ public void parsesNullJsonIfNotSection() throws Exception { @Test public void parsesEmptyJsonIfSection() throws Exception { + String inputJson = ""; + String variableName = "parsedObject"; + Template template = new Handlebars().compileInline(inputJson); + Options options = + new Options.Builder(null, null, TagType.SECTION, createContext(), template) + .setParams(new Object[] {}) + .build(); + Object output = render(variableName, options); + + // Check that it returns null + assertThat(output, is(nullValue())); + + /* Check that it stores parsed json (an empty map in this case because json is empty) + * in given variable name */ + Object storedData = options.data(variableName); + assertThat(storedData, isA(Map.class)); + Map castedData = (Map) storedData; + assertThat(castedData, is(aMapWithSize(0))); + } + + @Test + public void parsesEmptyJsonWithBracesIfSection() throws Exception { String inputJson = "{}"; - Object output = render(inputJson, new Object[] {}, TagType.SECTION); + String variableName = "parsedObject"; + Template template = new Handlebars().compileInline(inputJson); + Options options = + new Options.Builder(null, null, TagType.SECTION, createContext(), template) + .setParams(new Object[] {}) + .build(); + Object output = render(variableName, options); + + // Check that it returns null + assertThat(output, is(nullValue())); + + /* Check that it stores parsed json (an empty map in this case because json is empty) + * in given variable name */ + Object storedData = options.data(variableName); + assertThat(storedData, isA(Map.class)); + Map castedData = (Map) storedData; + assertThat(castedData, is(aMapWithSize(0))); + } - // Check that it returns empty object + @Test + public void parsesEmptyJsonIfSectionIfVariableNameNull() throws Exception { + String variableName = null; + Object output = render(variableName, new Object[] {}, TagType.SECTION); + + // Check that it returns empty object because variable name is null assertThat(output, instanceOf(Map.class)); Map result = (Map) output; assertThat(result, aMapWithSize(0)); @@ -127,20 +179,68 @@ public void parsesEmptyJsonIfSection() throws Exception { @Test public void parsesEmptyJsonIfNotSection() throws Exception { + String inputJson = ""; + String variableName = "parsedObject"; + Object[] params = {variableName}; + Options options = + new Options.Builder(null, null, TagType.VAR, createContext(), Template.EMPTY) + .setParams(params) + .build(); + Object output = render(inputJson, options); + + // Check that it returns empty object + assertThat(output, is(nullValue())); + + /* Check that it stores parsed json (an empty map in this case because json is empty) + * in given variable name */ + Object storedData = options.data(variableName); + assertThat(storedData, isA(Map.class)); + Map castedData = (Map) storedData; + assertThat(castedData, is(aMapWithSize(0))); + } + + @Test + public void parsesEmptyJsonWithBracesIfNotSection() throws Exception { String inputJson = "{}"; - Object output = render(inputJson, new Object[] {}, TagType.VAR); + String variableName = "parsedObject"; + Object[] params = {variableName}; + Options options = + new Options.Builder(null, null, TagType.VAR, createContext(), Template.EMPTY) + .setParams(params) + .build(); + Object output = render(inputJson, options); // Check that it returns empty object + assertThat(output, is(nullValue())); + + /* Check that it stores parsed json (an empty map in this case because json is empty) + * in given variable name */ + Object storedData = options.data(variableName); + assertThat(storedData, isA(Map.class)); + Map castedData = (Map) storedData; + assertThat(castedData, is(aMapWithSize(0))); + } + + @Test + public void parsesEmptyJsonIfNotSectionIfVariableNameAbsent() throws Exception { + String inputJson = "{}"; + Object output = render(inputJson, new Object[] {}, TagType.VAR); + + // Check that it returns empty object because variable name is null assertThat(output, instanceOf(Map.class)); Map result = (Map) output; assertThat(result, aMapWithSize(0)); } private Object render(Object context, Object[] params, TagType tagType) throws IOException { - return helper.apply( + return render( context, new Options.Builder(null, null, tagType, createContext(), Template.EMPTY) .setParams(params) .build()); } + + private Object render(Object context, Options options) throws IOException { + return helper.apply(context, options); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/RegexExtractHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/RegexExtractHelperTest.java index 15a0e4b02e..9294607fc8 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/RegexExtractHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/RegexExtractHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,11 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.NoFileSource.noFileSource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.common.LocalNotifier; -import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,13 +39,11 @@ public void init() { @Test public void canExtractSingleRegexMatch() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/api/abc,def,ghi"), aResponse() - .withBody("{\"test\": \"{{regexExtract request.path.[1] '([A-Za-z]+)'}}\"}") - .build(), - noFileSource(), - Parameters.empty()); + .withBody("{\"test\": \"{{regexExtract request.path.[1] '([A-Za-z]+)'}}\"}")); assertThat(responseDefinition.getBody(), is("{\"test\": \"abc\"}")); } @@ -55,14 +51,12 @@ public void canExtractSingleRegexMatch() { @Test public void canExtractMultipleRegexMatches() { final ResponseDefinition responseDefinition = - this.transformer.transform( + transform( + transformer, mockRequest().url("/api/abc,def,ghi"), aResponse() .withBody( - "{\"test\": \"{{regexExtract request.path.[1] '([A-Za-z]+)' 'parts'}}{{#each parts}}{{this}} {{/each}}\"}") - .build(), - noFileSource(), - Parameters.empty()); + "{\"test\": \"{{regexExtract request.path.[1] '([A-Za-z]+)' 'parts'}}{{#each parts}}{{this}} {{/each}}\"}")); assertThat(responseDefinition.getBody(), is("{\"test\": \"abc def ghi \"}")); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/SystemValueHelperTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/SystemValueHelperTest.java index aedaec82e2..b1b200c3ab 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/SystemValueHelperTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/helpers/SystemValueHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,12 @@ import com.github.tomakehurst.wiremock.common.ConsoleNotifier; import com.github.tomakehurst.wiremock.common.LocalNotifier; import com.github.tomakehurst.wiremock.extension.responsetemplating.SystemKeyAuthoriser; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import java.io.IOException; +import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ClearSystemProperty; public class SystemValueHelperTest { @@ -33,16 +34,13 @@ public class SystemValueHelperTest { @BeforeEach public void init() { - helper = new SystemValueHelper(new SystemKeyAuthoriser(ImmutableSet.of(".*"))); + helper = new SystemValueHelper(new SystemKeyAuthoriser(Set.of(".*"))); LocalNotifier.set(new ConsoleNotifier(true)); } @Test public void getExistingEnvironmentVariableShouldNotNull() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "PATH", - "type", "ENVIRONMENT"); + Map optionsHash = Map.of("key", "PATH", "type", "ENVIRONMENT"); String output = render(optionsHash); assertNotNull(output); @@ -51,10 +49,7 @@ public void getExistingEnvironmentVariableShouldNotNull() throws Exception { @Test public void getNonExistingEnvironmentVariableShouldNull() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "NON_EXISTING_VAR", - "type", "ENVIRONMENT"); + Map optionsHash = Map.of("key", "NON_EXISTING_VAR", "type", "ENVIRONMENT"); String output = render(optionsHash); assertNull(output); @@ -62,62 +57,49 @@ public void getNonExistingEnvironmentVariableShouldNull() throws Exception { @Test public void getForbiddenEnvironmentVariableShouldReturnError() throws Exception { - helper = new SystemValueHelper(new SystemKeyAuthoriser(ImmutableSet.of("JAVA*"))); + helper = new SystemValueHelper(new SystemKeyAuthoriser(Set.of("JAVA*"))); - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "TEST_VAR", - "type", "ENVIRONMENT"); + Map optionsHash = Map.of("key", "TEST_VAR", "type", "ENVIRONMENT"); String value = render(optionsHash); assertEquals("[ERROR: Access to TEST_VAR is denied]", value); } @Test public void getEmptyKeyShouldReturnError() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "", - "type", "PROPERTY"); + Map optionsHash = Map.of("key", "", "type", "PROPERTY"); String value = render(optionsHash); assertEquals("[ERROR: The key cannot be empty]", value); } @Test + @ClearSystemProperty(key = "test.key") public void getAllowedPropertyShouldSuccess() throws Exception { - helper = new SystemValueHelper(new SystemKeyAuthoriser(ImmutableSet.of("test.*"))); + helper = new SystemValueHelper(new SystemKeyAuthoriser(Set.of("test.*"))); System.setProperty("test.key", "aaa"); assertEquals("aaa", System.getProperty("test.key")); - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "test.key", - "type", "PROPERTY"); + Map optionsHash = Map.of("key", "test.key", "type", "PROPERTY"); String value = render(optionsHash); assertEquals("aaa", value); } @Test + @ClearSystemProperty(key = "test.key") public void getForbiddenPropertyShouldReturnError() throws Exception { - helper = new SystemValueHelper(new SystemKeyAuthoriser(ImmutableSet.of("JAVA.*"))); + helper = new SystemValueHelper(new SystemKeyAuthoriser(Set.of("JAVA.*"))); System.setProperty("test.key", "aaa"); - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "test.key", - "type", "PROPERTY"); + Map optionsHash = Map.of("key", "test.key", "type", "PROPERTY"); String value = render(optionsHash); assertEquals("[ERROR: Access to test.key is denied]", value); } @Test public void getNonExistingSystemPropertyShouldNull() throws Exception { - ImmutableMap optionsHash = - ImmutableMap.of( - "key", "not.existing.prop", - "type", "PROPERTY"); + Map optionsHash = Map.of("key", "not.existing.prop", "type", "PROPERTY"); String output = render(optionsHash); assertNull(output); } - private String render(ImmutableMap optionsHash) throws IOException { + private String render(Map optionsHash) throws IOException { return helper.apply( null, new Options.Builder(null, null, null, null, null).setHash(optionsHash).build()); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/BodyTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/BodyTest.java index 3c09f9c551..1de15080d6 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/http/BodyTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/http/BodyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,18 @@ import static com.github.tomakehurst.wiremock.common.Strings.stringFromBytes; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.IntNode; import com.github.tomakehurst.wiremock.common.Json; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64; import org.junit.jupiter.api.Test; -public class BodyTest { +class BodyTest { @Test - public void constructsFromBytes() { + void constructsFromBytes() { Body body = Body.fromOneOf( "this content".getBytes(), "not this content", new IntNode(1), "lskdjflsjdflks"); @@ -39,7 +40,7 @@ public void constructsFromBytes() { } @Test - public void constructsFromString() { + void constructsFromString() { Body body = Body.fromOneOf(null, "this content", new IntNode(1), "lskdjflsjdflks"); assertThat(body.asString(), is("this content")); @@ -48,7 +49,7 @@ public void constructsFromString() { } @Test - public void constructsFromJson() { + void constructsFromJson() { Body body = Body.fromOneOf(null, null, new IntNode(1), "lskdjflsjdflks"); assertThat(body.asString(), is("1")); @@ -57,8 +58,8 @@ public void constructsFromJson() { } @Test - public void constructsFromBase64() { - byte[] base64Encoded = Base64.encodeBase64("this content".getBytes()); + void constructsFromBase64() { + byte[] base64Encoded = Base64.getEncoder().encodeToString("this content".getBytes()).getBytes(); String encodedText = stringFromBytes(base64Encoded); Body body = Body.fromOneOf(null, null, null, encodedText); @@ -68,10 +69,21 @@ public void constructsFromBase64() { } @Test - public void bodyAsJson() { + void bodyAsJson() { final JsonNode jsonContent = Json.node("{\"name\":\"wiremock\",\"isCool\":true}"); Body body = Body.fromOneOf(null, null, jsonContent, "lskdjflsjdflks"); assertThat(body.asJson(), is(jsonContent)); } + + @Test + void hashCorrectly() { + byte[] primes = {2, 3, 5, 7}; + byte[] primes2 = {2, 3, 5, 7}; + + Body body = new Body(primes); + Body body2 = new Body(primes2); + + assertEquals(body.hashCode(), body2.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/ContentTypeHeaderTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/ContentTypeHeaderTest.java index aae6de6101..511648f0da 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/http/ContentTypeHeaderTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/http/ContentTypeHeaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ import com.github.tomakehurst.wiremock.common.Strings; import com.github.tomakehurst.wiremock.testsupport.MockRequestBuilder; -import com.google.common.base.Optional; import java.nio.charset.StandardCharsets; +import java.util.Optional; import org.junit.jupiter.api.Test; public class ContentTypeHeaderTest { diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java index 207c2af985..80f80b901d 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java @@ -19,6 +19,8 @@ import static com.github.tomakehurst.wiremock.common.NetworkAddressRules.ALLOW_ALL; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static com.github.tomakehurst.wiremock.crypto.X509CertificateVersion.V3; +import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; +import static com.github.tomakehurst.wiremock.stubbing.ServeEventFactory.newPostMatchServeEvent; import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -34,10 +36,9 @@ import com.github.tomakehurst.wiremock.crypto.InMemoryKeyStore; import com.github.tomakehurst.wiremock.crypto.Secret; import com.github.tomakehurst.wiremock.crypto.X509CertificateSpecification; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.store.InMemorySettingsStore; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.verification.LoggedRequest; import java.io.File; import java.io.IOException; @@ -48,14 +49,16 @@ import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; +import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @DisabledForJreRange( @@ -63,6 +66,8 @@ disabledReason = "does not support generating certificates at runtime") public class ProxyResponseRendererTest { + private static final int PROXY_TIMEOUT = 200_000; + @RegisterExtension public WireMockExtension origin = WireMockExtension.newInstance() @@ -164,7 +169,7 @@ void doesNotAddEntityIfEmptyBodyReverseProxy() throws IOException { ServeEvent serveEvent = reverseProxyServeEvent("/proxied"); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() == null)); + Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() == null), ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -175,7 +180,7 @@ void doesNotAddEntityIfEmptyBodyForwardProxy() throws IOException { ServeEvent serveEvent = forwardProxyServeEvent("/proxied"); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() == null)); + Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() == null), ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -187,7 +192,7 @@ void addsEntityIfNotEmptyBodyReverseProxy() throws IOException { serveEvent("/proxied", false, "Text body".getBytes(StandardCharsets.UTF_8)); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null)); + Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -199,7 +204,7 @@ void addsEntityIfNotEmptyBodyForwardProxy() throws IOException { serveEvent("/proxied", true, "Text body".getBytes(StandardCharsets.UTF_8)); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null)); + Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -220,7 +225,7 @@ void addsEmptyEntityIfEmptyBodyForwardProxyPOST() throws IOException { new HttpHeaders(new HttpHeader("Content-Length", "0"))); trustAllProxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null)); + Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); List requests = origin.findAll(postRequestedFor(urlPathMatching("/proxied/empty-post"))); Assertions.assertThat(requests) @@ -247,7 +252,7 @@ void addsEmptyEntityIfEmptyBodyForwardProxyGET() throws IOException { new HttpHeaders(new HttpHeader("Content-Length", "0"))); trustAllProxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null)); + Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); List requests = origin.findAll(getRequestedFor(urlPathMatching("/proxied/empty-get"))); Assertions.assertThat(requests) @@ -256,6 +261,39 @@ void addsEmptyEntityIfEmptyBodyForwardProxyGET() throws IOException { .noneMatch(r -> r.containsHeader("Content-Type")); } + @Test + void usesCorrectProxyRequestTimeout() { + RequestConfig forwardProxyClientRequestConfig = + reflectiveInnerSpyField( + RequestConfig.class, "forwardProxyClient", "defaultConfig", proxyResponseRenderer); + RequestConfig reverseProxyClientRequestConfig = + reflectiveInnerSpyField( + RequestConfig.class, "reverseProxyClient", "defaultConfig", proxyResponseRenderer); + + assertThat( + forwardProxyClientRequestConfig.getResponseTimeout().toMilliseconds(), + is(Long.valueOf(PROXY_TIMEOUT))); + assertThat( + reverseProxyClientRequestConfig.getResponseTimeout().toMilliseconds(), + is(Long.valueOf(PROXY_TIMEOUT))); + } + + private static T reflectiveInnerSpyField( + Class fieldType, String outerFieldName, String innerFieldName, Object object) { + try { + Field outerField = object.getClass().getDeclaredField(outerFieldName); + outerField.setAccessible(true); + Object outerFieldObject = outerField.get(object); + Field innerField = outerFieldObject.getClass().getDeclaredField(innerFieldName); + innerField.setAccessible(true); + T spy = spy(fieldType.cast(innerField.get(outerFieldObject))); + innerField.set(outerFieldObject, spy); + return spy; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + private static T reflectiveSpyField(Class fieldType, String fieldName, Object object) { try { Field field = object.getClass().getDeclaredField(fieldName); @@ -286,23 +324,21 @@ private ServeEvent serveEvent( byte[] body, RequestMethod method, HttpHeaders headers) { + LoggedRequest loggedRequest = - new LoggedRequest( - /* url = */ path, - /* absoluteUrl = */ origin.url(path), - /* method = */ method, - /* clientIp = */ "127.0.0.1", - /* headers = */ headers, - /* cookies = */ new HashMap(), - /* isBrowserProxyRequest = */ isBrowserProxyRequest, - /* loggedDate = */ new Date(), - /* body = */ body, - /* multiparts = */ null, - /* protocol = */ "HTTP/1.1"); + LoggedRequest.createFrom( + mockRequest() + .url(path) + .absoluteUrl(origin.url(path)) + .method(method) + .headers(headers) + .isBrowserProxyRequest(isBrowserProxyRequest) + .body(body) + .protocol("HTTP/1.1")); ResponseDefinition responseDefinition = aResponse().proxiedFrom(origin.baseUrl()).build(); responseDefinition.setOriginalRequest(loggedRequest); - return ServeEvent.of(loggedRequest, responseDefinition, new StubMapping()); + return newPostMatchServeEvent(loggedRequest, responseDefinition); } private File generateKeystore() throws Exception { @@ -344,11 +380,12 @@ private ProxyResponseRenderer buildProxyResponseRenderer( KeyStoreSettings.NO_STORE, /* preserveHostHeader = */ false, /* hostHeaderValue = */ null, - new GlobalSettingsHolder(), + new InMemorySettingsStore(), trustAllProxyTargets, Collections.emptyList(), stubCorsEnabled, - ALLOW_ALL); + ALLOW_ALL, + PROXY_TIMEOUT); } // Just exists to make the compiler happy by having the throws clause diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/ResponseDefinitionTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/ResponseDefinitionTest.java index 0f7b6a3619..754fa628a7 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/http/ResponseDefinitionTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/http/ResponseDefinitionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,10 +28,7 @@ public class ResponseDefinitionTest { public void getProxyUrlGivesBackRequestUrlIfBrowserProxyRequest() { ResponseDefinition response = ResponseDefinition.browserProxy( - MockRequest.mockRequest() - .host("http://my.domain") - .url("/path") - .isBrowserProxyRequest(true)); + MockRequest.mockRequest().host("my.domain").url("/path").isBrowserProxyRequest(true)); assertThat(response.getProxyUrl(), equalTo("http://my.domain/path")); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/StubResponseRendererTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/StubResponseRendererTest.java index 2b94b036e2..04aee99210 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/http/StubResponseRendererTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/http/StubResponseRendererTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,17 @@ package com.github.tomakehurst.wiremock.http; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; +import static com.github.tomakehurst.wiremock.stubbing.ServeEventFactory.newPostMatchServeEvent; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.extension.ResponseTransformer; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; import com.github.tomakehurst.wiremock.global.GlobalSettings; -import com.github.tomakehurst.wiremock.global.GlobalSettingsHolder; +import com.github.tomakehurst.wiremock.store.BlobStore; +import com.github.tomakehurst.wiremock.store.InMemorySettingsStore; +import com.github.tomakehurst.wiremock.store.SettingsStore; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.github.tomakehurst.wiremock.verification.LoggedRequest; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -35,24 +37,27 @@ public class StubResponseRendererTest { private static final int TEST_TIMEOUT = 500; - private FileSource fileSource; - private GlobalSettingsHolder globalSettingsHolder; + private BlobStore filesBlobStore; + private SettingsStore settingsStore; private List responseTransformers; + private List v2ResponseTransformers; private StubResponseRenderer stubResponseRenderer; @BeforeEach public void init() { - fileSource = Mockito.mock(FileSource.class); - globalSettingsHolder = new GlobalSettingsHolder(); + filesBlobStore = Mockito.mock(BlobStore.class); + settingsStore = new InMemorySettingsStore(); responseTransformers = new ArrayList<>(); + v2ResponseTransformers = new ArrayList<>(); stubResponseRenderer = - new StubResponseRenderer(fileSource, globalSettingsHolder, null, responseTransformers); + new StubResponseRenderer( + filesBlobStore, settingsStore, null, responseTransformers, v2ResponseTransformers); } @Test @Timeout(TEST_TIMEOUT) public void endpointFixedDelayShouldOverrideGlobalDelay() throws Exception { - globalSettingsHolder.replaceWith(GlobalSettings.builder().fixedDelay(1000).build()); + settingsStore.set(GlobalSettings.builder().fixedDelay(1000).build()); Response response = stubResponseRenderer.render(createServeEvent(100)); @@ -62,7 +67,7 @@ public void endpointFixedDelayShouldOverrideGlobalDelay() throws Exception { @Test @Timeout(TEST_TIMEOUT) public void globalFixedDelayShouldNotBeOverriddenIfNoEndpointDelaySpecified() throws Exception { - globalSettingsHolder.replaceWith(GlobalSettings.builder().fixedDelay(1000).build()); + settingsStore.set(GlobalSettings.builder().fixedDelay(1000).build()); Response response = stubResponseRenderer.render(createServeEvent(null)); @@ -72,7 +77,7 @@ public void globalFixedDelayShouldNotBeOverriddenIfNoEndpointDelaySpecified() th @Test @Timeout(TEST_TIMEOUT) public void shouldSetGlobalFixedDelayOnResponse() throws Exception { - globalSettingsHolder.replaceWith(GlobalSettings.builder().fixedDelay(1000).build()); + settingsStore.set(GlobalSettings.builder().fixedDelay(1000).build()); Response response = stubResponseRenderer.render(createServeEvent(null)); @@ -89,16 +94,7 @@ public void shouldSetEndpointFixedDelayOnResponse() throws Exception { @Test @Timeout(TEST_TIMEOUT) public void shouldSetEndpointDistributionDelayOnResponse() throws Exception { - globalSettingsHolder.replaceWith( - GlobalSettings.builder() - .delayDistribution( - new DelayDistribution() { - @Override - public long sampleMillis() { - return 123; - } - }) - .build()); + settingsStore.set(GlobalSettings.builder().delayDistribution(() -> 123).build()); Response response = stubResponseRenderer.render(createServeEvent(null)); @@ -108,23 +104,14 @@ public long sampleMillis() { @Test @Timeout(TEST_TIMEOUT) public void shouldCombineFixedDelayDistributionDelay() throws Exception { - globalSettingsHolder.replaceWith( - GlobalSettings.builder() - .delayDistribution( - new DelayDistribution() { - @Override - public long sampleMillis() { - return 123; - } - }) - .build()); + settingsStore.set(GlobalSettings.builder().delayDistribution(() -> 123).build()); Response response = stubResponseRenderer.render(createServeEvent(2000)); assertThat(response.getInitialDelay(), is(2123L)); } private ServeEvent createServeEvent(Integer fixedDelayMillis) { - return ServeEvent.of( - LoggedRequest.createFrom(mockRequest()), + return newPostMatchServeEvent( + mockRequest(), new ResponseDefinition( 0, "", diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListenerTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListenerTest.java new file mode 100644 index 0000000000..77ee414f9b --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/ConsoleNotifyingWiremockNetworkTrafficListenerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.http.trafficlistener; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +public class ConsoleNotifyingWiremockNetworkTrafficListenerTest { + + @Test + public void defaultConstructor_notifiesToSystemOutAndUsesUTF8Charset() { + PrintStream originalOut = System.out; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + + ConsoleNotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new ConsoleNotifyingWiremockNetworkTrafficListener(); + Socket socket = new Socket(); + ByteBuffer byteBuffer = stringToByteBuffer("Hello world", StandardCharsets.UTF_8); + + consoleNotifyingWiremockNetworkTrafficListener.outgoing(socket, byteBuffer); + + assertThat(out.toString(), containsString("Hello world")); + + System.setOut(originalOut); + } + + @Test + public void charsetConstructor_notifiesToSystemOutAndUsesSpecifiedCharset() { + PrintStream originalOut = System.out; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + + ConsoleNotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new ConsoleNotifyingWiremockNetworkTrafficListener(StandardCharsets.UTF_16); + Socket socket = new Socket(); + ByteBuffer byteBuffer = stringToByteBuffer("Hello world", StandardCharsets.UTF_16); + + consoleNotifyingWiremockNetworkTrafficListener.outgoing(socket, byteBuffer); + + assertThat(out.toString(), containsString("Hello world")); + + System.setOut(originalOut); + } + + public static ByteBuffer stringToByteBuffer(String msg, Charset charset) { + return ByteBuffer.wrap(msg.getBytes(charset)); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListenerTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListenerTest.java new file mode 100644 index 0000000000..26502d7bbd --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/http/trafficlistener/NotifyingWiremockNetworkTrafficListenerTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.http.trafficlistener; + +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.github.tomakehurst.wiremock.common.Notifier; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +public class NotifyingWiremockNetworkTrafficListenerTest { + private final Notifier mockNotifier = mock(Notifier.class); + + @Test + public void opened_withSocket_shouldNotifyAtInfo() { + NotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new NotifyingWiremockNetworkTrafficListener(mockNotifier, StandardCharsets.UTF_8); + Socket socket = new Socket(); + + consoleNotifyingWiremockNetworkTrafficListener.opened(socket); + + verify(mockNotifier).info(contains("Opened ")); + } + + @Test + public void closed_withSocket_shouldNotifyAtInfo() { + NotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new NotifyingWiremockNetworkTrafficListener(mockNotifier, StandardCharsets.UTF_8); + Socket socket = new Socket(); + + consoleNotifyingWiremockNetworkTrafficListener.closed(socket); + + verify(mockNotifier).info(contains("Closed ")); + } + + @Test + public void incoming_withBytebufferWithIncompatibleCharset_shouldNotifyBytesOmittedAtInfo() { + NotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new NotifyingWiremockNetworkTrafficListener(mockNotifier, StandardCharsets.UTF_8); + Socket socket = new Socket(); + ByteBuffer byteBuffer = stringToByteBuffer("Hello world", StandardCharsets.UTF_16); + + consoleNotifyingWiremockNetworkTrafficListener.incoming(socket, byteBuffer); + + verify(mockNotifier).error(contains("Incoming bytes omitted.")); + } + + @Test + public void incoming_withBytebufferWithCompatibleCharset_shouldNotifyWithIncomingBytes() { + NotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new NotifyingWiremockNetworkTrafficListener(mockNotifier, StandardCharsets.UTF_8); + Socket socket = new Socket(); + ByteBuffer byteBuffer = stringToByteBuffer("Hello world", StandardCharsets.UTF_8); + + consoleNotifyingWiremockNetworkTrafficListener.incoming(socket, byteBuffer); + + verify(mockNotifier).info(contains("Hello world")); + } + + @Test + public void outgoing_withBytebufferWithIncompatibleCharset_shouldNotifyBytesOmittedAtInfo() { + NotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new NotifyingWiremockNetworkTrafficListener(mockNotifier, StandardCharsets.UTF_8); + Socket socket = new Socket(); + ByteBuffer byteBuffer = stringToByteBuffer("Hello world", StandardCharsets.UTF_16); + + consoleNotifyingWiremockNetworkTrafficListener.outgoing(socket, byteBuffer); + + verify(mockNotifier).error(contains("Outgoing bytes omitted.")); + } + + @Test + public void outgoing_withBytebufferWithCompatibleCharset_shouldNotifyWithIncomingBytes() { + NotifyingWiremockNetworkTrafficListener consoleNotifyingWiremockNetworkTrafficListener = + new NotifyingWiremockNetworkTrafficListener(mockNotifier, StandardCharsets.UTF_8); + Socket socket = new Socket(); + ByteBuffer byteBuffer = stringToByteBuffer("Hello world", StandardCharsets.UTF_8); + + consoleNotifyingWiremockNetworkTrafficListener.outgoing(socket, byteBuffer); + + verify(mockNotifier).info(contains("Hello world")); + } + + public static ByteBuffer stringToByteBuffer(String msg, Charset charset) { + return ByteBuffer.wrap(msg.getBytes(charset)); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerTest.java b/src/test/java/com/github/tomakehurst/wiremock/jetty11/JettyHttpServerTest.java similarity index 86% rename from src/test/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerTest.java rename to src/test/java/com/github/tomakehurst/wiremock/jetty11/JettyHttpServerTest.java index ea3e57d86d..06f7dde8aa 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/jetty9/JettyHttpServerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/jetty11/JettyHttpServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty11; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -25,12 +25,16 @@ import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.core.StubServer; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.github.tomakehurst.wiremock.extension.Extensions; import com.github.tomakehurst.wiremock.http.AdminRequestHandler; import com.github.tomakehurst.wiremock.http.BasicResponseRenderer; import com.github.tomakehurst.wiremock.http.ResponseRenderer; import com.github.tomakehurst.wiremock.http.StubRequestHandler; +import com.github.tomakehurst.wiremock.jetty.JettyHttpServer; +import com.github.tomakehurst.wiremock.jetty.JettyHttpServerFactory; import com.github.tomakehurst.wiremock.security.NoAuthenticator; import com.github.tomakehurst.wiremock.verification.RequestJournal; +import com.github.tomakehurst.wiremock.verification.notmatched.PlainTextStubNotMatchedRenderer; import java.lang.reflect.Field; import java.util.Collections; import org.eclipse.jetty.server.ServerConnector; @@ -52,12 +56,13 @@ public void init() { adminRequestHandler = new AdminRequestHandler( - AdminRoutes.defaults(), + AdminRoutes.forClient(), admin, new BasicResponseRenderer(), new NoAuthenticator(), false, Collections.emptyList(), + Collections.emptyList(), NO_TRUNCATION); stubRequestHandler = new StubRequestHandler( @@ -65,15 +70,18 @@ public void init() { Mockito.mock(ResponseRenderer.class), admin, Collections.emptyMap(), + Collections.emptyMap(), Mockito.mock(RequestJournal.class), Collections.emptyList(), + Collections.emptyList(), false, - NO_TRUNCATION); + NO_TRUNCATION, + new PlainTextStubNotMatchedRenderer(Extensions.NONE)); } @Test public void testStopTimeout() { - long expectedStopTimeout = 500L; + long expectedStopTimeout = 1000L; WireMockConfiguration config = WireMockConfiguration.wireMockConfig().jettyStopTimeout(expectedStopTimeout); diff --git a/src/test/java/com/github/tomakehurst/wiremock/jetty9/MultipartParser.java b/src/test/java/com/github/tomakehurst/wiremock/jetty11/MultipartParser.java similarity index 67% rename from src/test/java/com/github/tomakehurst/wiremock/jetty9/MultipartParser.java rename to src/test/java/com/github/tomakehurst/wiremock/jetty11/MultipartParser.java index cf670d8f8c..1092981252 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/jetty9/MultipartParser.java +++ b/src/test/java/com/github/tomakehurst/wiremock/jetty11/MultipartParser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.tomakehurst.wiremock.jetty9; +package com.github.tomakehurst.wiremock.jetty11; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.google.common.collect.FluentIterable.from; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.servlet.WireMockHttpServletMultipartAdapter; -import com.google.common.base.Function; import java.io.ByteArrayInputStream; import java.util.Collection; -import javax.servlet.http.Part; -import org.eclipse.jetty.util.MultiPartInputStreamParser; +import java.util.stream.Collectors; +import org.eclipse.jetty.server.MultiPartInputStreamParser; public class MultipartParser { @@ -33,15 +31,9 @@ public static Collection parse(byte[] body, String contentType) { MultiPartInputStreamParser parser = new MultiPartInputStreamParser(new ByteArrayInputStream(body), contentType, null, null); try { - return from(parser.getParts()) - .transform( - new Function() { - @Override - public Request.Part apply(Part input) { - return WireMockHttpServletMultipartAdapter.from(input); - } - }) - .toList(); + return parser.getParts().stream() + .map(WireMockHttpServletMultipartAdapter::from) + .collect(Collectors.toList()); } catch (Exception e) { return throwUnchecked(e, Collection.class); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionDeclarativeProgrammaticMixTest.java b/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionDeclarativeProgrammaticMixTest.java new file mode 100644 index 0000000000..e74c0a8f07 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionDeclarativeProgrammaticMixTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.junit5; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class JUnitJupiterExtensionDeclarativeProgrammaticMixTest { + @WireMockTest + public static class TestSaneStaticDefaults { + @RegisterExtension + public static WireMockExtension wms = + WireMockExtension.newInstance().options(wireMockConfig().port(44345)).build(); + + @Test + void programmatic_port_option_used_when_no_port_specified_in_attributes_static() { + final int port = wms.getPort(); + assertThat(port, is(44345)); + } + + @Test + void programmatic_port_is_different_from_declarative_port(WireMockRuntimeInfo wmRuntimeInfo) { + final int declarativePort = wmRuntimeInfo.getHttpPort(); + final int staticMemberPort = wms.getPort(); + assertThat(staticMemberPort, is(not(declarativePort))); + } + + @Test + void wiremockruntimeinfo_always_injects_declarative_instance( + WireMockRuntimeInfo wmRuntimeInfo) { + WireMockRuntimeInfo staticMemberRuntimeInfo = wms.getRuntimeInfo(); + assertThat(wmRuntimeInfo, is(notNullValue())); + assertThat(wmRuntimeInfo, is(not(staticMemberRuntimeInfo))); + } + } + + @WireMockTest(httpPort = 44777) + public static class TestNoStaticOverride { + @RegisterExtension + public static WireMockExtension wms = + WireMockExtension.newInstance().options(wireMockConfig().port(44346)).build(); + + @Test + void programmatic_and_declarative_ports_are_as_defined(WireMockRuntimeInfo wmRuntimeInfo) { + final int declarativePort = wmRuntimeInfo.getHttpPort(); + final int staticMemberPort = wms.getPort(); + + assertThat(staticMemberPort, is(44346)); + assertThat(declarativePort, is(44777)); + } + } + + @WireMockTest + public static class TestSaneInstanceDefaults { + @RegisterExtension + public WireMockExtension wmi = + WireMockExtension.newInstance().options(wireMockConfig().port(44349)).build(); + + @Test + void programmatic_port_option_used_when_no_port_specified_in_attributes_instance() { + final int port = wmi.getPort(); + assertThat(port, is(44349)); + } + + @Test + void programmatic_port_is_different_from_declarative_port(WireMockRuntimeInfo wmRuntimeInfo) { + final int declarativePort = wmRuntimeInfo.getHttpPort(); + final int staticMemberPort = wmi.getPort(); + assertThat(staticMemberPort, is(not(declarativePort))); + } + + @Test + void wiremockruntimeinfo_always_injects_declarative_instance( + WireMockRuntimeInfo wmRuntimeInfo) { + WireMockRuntimeInfo staticMemberRuntimeInfo = wmi.getRuntimeInfo(); + assertThat(wmRuntimeInfo, is(notNullValue())); + assertThat(wmRuntimeInfo, is(not(staticMemberRuntimeInfo))); + } + } + + @WireMockTest(httpPort = 44778) + public static class TestNoInstanceOverride { + @RegisterExtension + public WireMockExtension wmi = + WireMockExtension.newInstance().options(wireMockConfig().port(44351)).build(); + + @Test + void programmatic_port_option_used_when_no_port_specified_in_attributes_instance() { + final int port = wmi.getPort(); + assertThat(port, is(44351)); + } + + @Test + void programmatic_port_is_different_from_declarative_port(WireMockRuntimeInfo wmRuntimeInfo) { + final int declarativePort = wmRuntimeInfo.getHttpPort(); + final int staticMemberPort = wmi.getPort(); + assertThat(staticMemberPort, is(not(declarativePort))); + } + + @Test + void wiremockruntimeinfo_always_injects_declarative_instance( + WireMockRuntimeInfo wmRuntimeInfo) { + WireMockRuntimeInfo staticMemberRuntimeInfo = wmi.getRuntimeInfo(); + assertThat(wmRuntimeInfo, is(notNullValue())); + assertThat(wmRuntimeInfo, is(not(staticMemberRuntimeInfo))); + } + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionNonStaticMultiInstanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionNonStaticMultiInstanceTest.java index 3f1d5cc642..6f399c68c8 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionNonStaticMultiInstanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionNonStaticMultiInstanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.HttpClientFactory; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -53,7 +52,7 @@ public class JUnitJupiterExtensionNonStaticMultiInstanceTest { @RegisterExtension WireMockExtension wm2 = WireMockExtension.newInstance() - .options(wireMockConfig().dynamicPort().extensions(new ResponseTemplateTransformer(true))) + .options(wireMockConfig().dynamicPort().templatingEnabled(true).globalTemplating(true)) .build(); @BeforeEach diff --git a/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionStaticMultiInstanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionStaticMultiInstanceTest.java index aff51fbd0e..5dba789bda 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionStaticMultiInstanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/junit5/JUnitJupiterExtensionStaticMultiInstanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,13 @@ */ package com.github.tomakehurst.wiremock.junit5; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.getAllServeEvents; -import static com.github.tomakehurst.wiremock.client.WireMock.ok; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; import com.github.tomakehurst.wiremock.http.HttpClientFactory; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -53,7 +49,7 @@ public class JUnitJupiterExtensionStaticMultiInstanceTest { @RegisterExtension static WireMockExtension wm2 = WireMockExtension.newInstance() - .options(wireMockConfig().dynamicPort().extensions(new ResponseTemplateTransformer(true))) + .options(wireMockConfig().dynamicPort().templatingEnabled(true).globalTemplating(true)) .build(); @BeforeEach diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/AbsentPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/AbsentPatternTest.java index ab205d4a04..07e2ce7ca5 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/AbsentPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/AbsentPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2021 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,9 @@ package com.github.tomakehurst.wiremock.matching; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import com.github.tomakehurst.wiremock.common.Json; import org.junit.jupiter.api.Test; @@ -25,7 +26,7 @@ public class AbsentPatternTest { @Test - public void correctlyDeserialisesFromJson() { + public void correctlyDeserializesFromJson() { StringValuePattern stringValuePattern = Json.read( "{ \n" + " \"absent\": \"(absent)\" \n" + "}", @@ -34,4 +35,20 @@ public void correctlyDeserialisesFromJson() { assertThat(stringValuePattern, instanceOf(AbsentPattern.class)); assertThat(stringValuePattern.isAbsent(), is(true)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + AbsentPattern a = new AbsentPattern("someString"); + AbsentPattern b = new AbsentPattern("someString"); + AbsentPattern c = new AbsentPattern("someOtherString"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java index e89d69f39f..6d3a20f036 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,7 @@ import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.DateTimeOffset; @@ -167,4 +166,20 @@ public void acceptsJavaLocalDateTimeAsExpected() { AfterDateTimePattern matcher = WireMock.after(LocalDateTime.parse("2020-08-29T00:00:00")); assertTrue(matcher.match("2021-01-01T00:00:00").isExactMatch()); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + AfterDateTimePattern a = WireMock.after(LocalDateTime.parse("2020-08-29T00:00:00")); + AfterDateTimePattern b = WireMock.after(LocalDateTime.parse("2020-08-29T00:00:00")); + AfterDateTimePattern c = WireMock.after(LocalDateTime.parse("2022-01-01T10:10:10")); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java index b9edcc4b85..12e311f077 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.DateTimeUnit; @@ -329,4 +328,20 @@ public void acceptsJavaLocalDateTimeAsExpected() { BeforeDateTimePattern matcher = WireMock.before(LocalDateTime.parse("2020-08-29T00:00:00")); assertTrue(matcher.match("2019-01-01T00:00:00").isExactMatch()); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + BeforeDateTimePattern a = WireMock.before(LocalDateTime.parse("2020-08-29T00:00:00")); + BeforeDateTimePattern b = WireMock.before(LocalDateTime.parse("2020-08-29T00:00:00")); + BeforeDateTimePattern c = WireMock.before(LocalDateTime.parse("2022-01-01T10:10:10")); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPatternPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPatternPatternTest.java index b8228463fb..b26c090aab 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPatternPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/BinaryEqualToPatternPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,19 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Json; -import com.google.common.io.BaseEncoding; +import java.util.Base64; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class BinaryEqualToPatternPatternTest { +class BinaryEqualToPatternPatternTest { @Test - public void returns1ForNonMatch() { + void returns1ForNonMatch() { ValueMatcher pattern = WireMock.binaryEqualTo(new byte[] {1, 2, 3}); byte[] actual = {4, 5, 6}; @@ -39,7 +41,7 @@ public void returns1ForNonMatch() { } @Test - public void returns0WhenExactlyEqual() { + void returns0WhenExactlyEqual() { ValueMatcher pattern = WireMock.binaryEqualTo(new byte[] {1, 2, 3}); byte[] actual = {1, 2, 3}; @@ -50,7 +52,7 @@ public void returns0WhenExactlyEqual() { } @Test - public void returnsNonMatchWheActualIsNull() { + void returnsNonMatchWheActualIsNull() { ValueMatcher pattern = WireMock.binaryEqualTo(new byte[] {1, 2, 3}); byte[] actual = null; @@ -61,9 +63,9 @@ public void returnsNonMatchWheActualIsNull() { } @Test - public void serialisesCorrectly() throws Exception { + void serialisesCorrectly() throws Exception { byte[] expected = {5, 5, 5, 5}; - String base64Expected = BaseEncoding.base64().encode(expected); + String base64Expected = Base64.getEncoder().encodeToString(expected); String expectedJson = "{ \n" + " \"binaryEqualTo\": \"" @@ -75,8 +77,8 @@ public void serialisesCorrectly() throws Exception { @Test @SuppressWarnings("unchecked") - public void deserialisesCorrectly() { - String base64Expected = BaseEncoding.base64().encode(new byte[] {1, 2, 3}); + void deserializesCorrectly() { + String base64Expected = Base64.getEncoder().encodeToString(new byte[] {1, 2, 3}); ContentPattern pattern = Json.read( @@ -90,4 +92,20 @@ public void deserialisesCorrectly() { assertThat(pattern, instanceOf(BinaryEqualToPattern.class)); assertThat(pattern.getExpected(), is(base64Expected)); } + + @Test + void objectsShouldBeEqualOnSameExpectedValue() { + BinaryEqualToPattern a = new BinaryEqualToPattern(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + BinaryEqualToPattern b = new BinaryEqualToPattern(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + BinaryEqualToPattern c = new BinaryEqualToPattern(new byte[] {0, 8, 15}); + + Assertions.assertEquals(a, b); + Assertions.assertEquals(a.hashCode(), b.hashCode()); + Assertions.assertEquals(b, a); + Assertions.assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/ContainsPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/ContainsPatternTest.java index f50bfa18d4..994da809d0 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/ContainsPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/ContainsPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.jupiter.api.Test; @@ -36,4 +35,20 @@ public void returnsNoMatchWhenExpectedValueNotContainedInTestValue() { assertFalse(matchResult.isExactMatch()); assertThat(matchResult.getDistance(), is(1.0)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + ContainsPattern a = new ContainsPattern("someString"); + ContainsPattern b = new ContainsPattern("someString"); + ContainsPattern c = new ContainsPattern("someOtherString"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java index 65c9f3e62d..9d83bc8611 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,7 @@ import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.DateTimeOffset; @@ -169,4 +168,20 @@ public void acceptsJavaLocalDateTimeAsExpected() { WireMock.equalToDateTime(LocalDateTime.parse("2020-08-29T00:00:00")); assertTrue(matcher.match("2020-08-29T00:00:00").isExactMatch()); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + EqualToDateTimePattern a = WireMock.equalToDateTime(LocalDateTime.parse("2020-08-29T00:00:00")); + EqualToDateTimePattern b = WireMock.equalToDateTime(LocalDateTime.parse("2020-08-29T00:00:00")); + EqualToDateTimePattern c = WireMock.equalToDateTime(LocalDateTime.parse("2022-01-10T10:10:10")); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java index 9e10334d4c..4ac61f3bbc 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ package com.github.tomakehurst.wiremock.matching; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.closeTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.Json; import org.json.JSONException; import org.junit.jupiter.api.Test; @@ -591,4 +588,43 @@ public void supportsRegexPlaceholders() { MatchResult nonMatch = new EqualToJsonPattern(expected, false, false).match(actualNonMatching); assertThat(nonMatch.isExactMatch(), is(false)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + EqualToJsonPattern a = + new EqualToJsonPattern( + "{\n" + " \"id\": \"abc\",\n" + " \"name\": \"Tom\"\n" + "}", false, false); + EqualToJsonPattern b = + new EqualToJsonPattern( + "{\n" + " \"id\": \"abc\",\n" + " \"name\": \"Tom\"\n" + "}", false, false); + EqualToJsonPattern c = + new EqualToJsonPattern( + "{\n" + " \"id\": \"123\",\n" + " \"name\": \"Eric\"\n" + "}", false, false); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } + + @Test + void subEventIsReturnedOnJsonParsingError() { + MatchResult match = new EqualToJsonPattern("{}", false, false).match("{ \"wrong"); + + assertThat(match.getSubEvents().size(), is(1)); + Errors.Error error = + match.getSubEvents().stream() + .findFirst() + .get() + .getDataAs(Errors.class) + .getErrors() + .stream() + .findFirst() + .get(); + assertThat(error.getDetail(), startsWith("Unexpected end-of-input")); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToPatternTest.java index 8a9ae24297..d7f0e602fe 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,14 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.fail; import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.common.JsonException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class EqualToPatternTest { @@ -106,4 +108,20 @@ public void failsWithMeaningfulErrorWhenOperatorNotRecognised() { public void noMatchOnNullValue() { assertThat(WireMock.equalTo("this_thing").match(null).isExactMatch(), is(false)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + EqualToPattern a = new EqualToPattern("someString"); + EqualToPattern b = new EqualToPattern("someString"); + EqualToPattern c = new EqualToPattern("someOtherString"); + + Assertions.assertEquals(a, b); + Assertions.assertEquals(a.hashCode(), b.hashCode()); + Assertions.assertEquals(b, a); + Assertions.assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPatternTest.java index d4b34acb2e..a63d9dfe45 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToXmlPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,26 +19,17 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.closeTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.Mockito.verify; import static org.xmlunit.diff.ComparisonType.ATTR_VALUE; import static org.xmlunit.diff.ComparisonType.NAMESPACE_URI; import static org.xmlunit.diff.ComparisonType.SCHEMA_LOCATION; -import com.github.tomakehurst.wiremock.common.ConsoleNotifier; -import com.github.tomakehurst.wiremock.common.Json; -import com.github.tomakehurst.wiremock.common.LocalNotifier; -import com.github.tomakehurst.wiremock.common.Notifier; +import com.github.tomakehurst.wiremock.common.*; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.testsupport.WireMatchers; -import com.google.common.collect.ImmutableSet; import java.util.Locale; import java.util.Set; import org.hamcrest.Matchers; @@ -47,6 +38,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONCompareMode; import org.xmlunit.diff.ComparisonType; public class EqualToXmlPatternTest { @@ -292,7 +284,7 @@ public void returnsExactMatchWhenElementsAreInDifferentOrder() { } @Test - public void returnsNoMatchWhenTagNamesDifferAndContentIsSame() throws Exception { + public void returnsNoMatchWhenTagNamesDifferAndContentIsSame() { final EqualToXmlPattern pattern = new EqualToXmlPattern("Hello"); final MatchResult matchResult = pattern.match("Hello"); @@ -309,7 +301,7 @@ public void logsASensibleErrorMessageWhenActualXmlIsBadlyFormed() { } @Test - public void doesNotFetchDtdBecauseItCouldResultInAFailedMatch() throws Exception { + public void doesNotFetchDtdBecauseItCouldResultInAFailedMatch() { String xmlWithDtdThatCannotBeFetched = ""; EqualToXmlPattern pattern = new EqualToXmlPattern(xmlWithDtdThatCannotBeFetched); @@ -404,8 +396,7 @@ public void deserializesEqualToXmlWithAllParameters() { placeholderClosingDelimiterRegex, equalToXmlPattern.getPlaceholderClosingDelimiterRegex()); assertThat( equalToXmlPattern.getExemptedComparisons(), - Matchers.>is( - ImmutableSet.of(SCHEMA_LOCATION, NAMESPACE_URI, ATTR_VALUE))); + Matchers.>is(Set.of(SCHEMA_LOCATION, NAMESPACE_URI, ATTR_VALUE))); } @Test @@ -421,7 +412,7 @@ public void serializesEqualToXmlWithAllParameters() { enablePlaceholders, placeholderOpeningDelimiterRegex, placeholderClosingDelimiterRegex, - ImmutableSet.of(SCHEMA_LOCATION, NAMESPACE_URI, ATTR_VALUE)); + Set.of(SCHEMA_LOCATION, NAMESPACE_URI, ATTR_VALUE)); String json = Json.write(pattern); @@ -433,8 +424,9 @@ public void serializesEqualToXmlWithAllParameters() { + " \"enablePlaceholders\": true,\n" + " \"placeholderOpeningDelimiterRegex\": \"[\",\n" + " \"placeholderClosingDelimiterRegex\": \"]\",\n" - + " \"exemptedComparisons\": [\"SCHEMA_LOCATION\", \"NAMESPACE_URI\", \"ATTR_VALUE\"]\n" - + "}")); + + " \"exemptedComparisons\": [\"SCHEMA_LOCATION\", \"ATTR_VALUE\", \"NAMESPACE_URI\"]\n" + + "}", + JSONCompareMode.NON_EXTENSIBLE)); } @Test @@ -478,4 +470,53 @@ public void namespaceComparisonCanBeExcluded2() { assertTrue(pattern.match(actual).isExactMatch()); } + + @Test + public void testEquals() { + EqualToXmlPattern a = + new EqualToXmlPattern( + ""); + EqualToXmlPattern b = + new EqualToXmlPattern( + ""); + EqualToXmlPattern c = + new EqualToXmlPattern( + ""); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } + + @Test + void subEventIsReturnedOnXmlParsingError() { + MatchResult match = new EqualToXmlPattern("").match(" + new LogicalAnd( + WireMock.absent(), + WireMock.notMatching("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"))); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/LogicalOrTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/LogicalOrTest.java index e99797c976..ca0b6b2bdf 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/LogicalOrTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/LogicalOrTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Json; @@ -99,4 +99,42 @@ public void returnsDistanceFromClosestMatchWhenNotAnExactMatch() { double expectedDistance = WireMock.equalTo("defgh").match("efgh").getDistance(); assertThat(matchResult.getDistance(), is(expectedDistance)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + LogicalOr a = + new LogicalOr(WireMock.equalTo("A"), WireMock.equalTo("B"), WireMock.equalTo("C")); + LogicalOr b = + new LogicalOr(WireMock.equalTo("A"), WireMock.equalTo("B"), WireMock.equalTo("C")); + LogicalOr c = new LogicalOr(WireMock.equalTo("D"), WireMock.equalTo("E")); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } + + @Test + void correctlyEvaluatesAbsentOrDoesNotMatch() { + LogicalOr matcher = + new LogicalOr( + WireMock.absent(), + WireMock.notMatching("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$")); + + assertThat(matcher.match(null).isExactMatch(), is(true)); + assertThat(matcher.match("some-non-date-string").isExactMatch(), is(true)); + assertThat(matcher.match("2023-02-03T12:11:10Z").isExactMatch(), is(false)); + } + + @Test + void throwsErrorOnConstructionIfOnlyOneMatcherSupplied() { + assertThrows( + IllegalArgumentException.class, + () -> new LogicalOr(WireMock.equalTo("A")), + "LogicalOr must be constructed with at least two matchers"); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchResultTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchResultTest.java index 2552efb3d8..4dcc0dc01d 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchResultTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchResultTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPatternTest.java index 4ff7590bc7..9df43ebfb8 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonPathPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.stubbing.SubEvent.WARNING; +import static com.github.tomakehurst.wiremock.testsupport.ServeEventChecks.checkMessage; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalToJson; import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; @@ -26,10 +28,8 @@ import static org.mockito.Mockito.verify; import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.common.Json; -import com.github.tomakehurst.wiremock.common.JsonException; -import com.github.tomakehurst.wiremock.common.LocalNotifier; -import com.github.tomakehurst.wiremock.common.Notifier; +import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.testsupport.ServeEventChecks; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -100,25 +100,59 @@ public void matchesOnJsonPathsWithFiltersOnNestedObjects() { } @Test - public void providesSensibleNotificationWhenJsonMatchFailsDueToInvalidJson() { - Notifier notifier = setMockNotifier(); + public void providesEventMessageWhenJsonMatchFailsDueToInvalidJson() { + StringValuePattern pattern = WireMock.matchingJsonPath("$.something"); + MatchResult match = pattern.match("Not a JSON document"); + assertFalse(match.isExactMatch(), "Expected the match to fail"); + checkMessage( + match, + WARNING, + "Warning: JSON path expression '$.something' failed to match document 'Not a JSON document' because of error 'Expected to find an object with property ['something'] in path $ but found 'java.lang.String'. This is not a json object according to the JsonProvider: 'com.jayway.jsonpath.spi.json.JsonSmartJsonProvider'.'"); + } + + private static void checkWarningMessageAndEvent( + Notifier notifier, MatchResult match, String warningMessage) { + verify(notifier).info(warningMessage); + checkMessage(match, WARNING, warningMessage); + } + + @Test + public void providesEventMessageWhenJsonMatchFailsDueToMissingAttributeJson() { StringValuePattern pattern = WireMock.matchingJsonPath("$.something"); - assertFalse(pattern.match("Not a JSON document").isExactMatch(), "Expected the match to fail"); - verify(notifier) - .info( - "Warning: JSON path expression '$.something' failed to match document 'Not a JSON document' because of error 'Expected to find an object with property ['something'] in path $ but found 'java.lang.String'. This is not a json object according to the JsonProvider: 'com.jayway.jsonpath.spi.json.JsonSmartJsonProvider'.'"); + MatchResult matchResult = pattern.match("{ \"nothing\": 1 }"); + + assertFalse(matchResult.isExactMatch(), "Expected the match to fail"); + checkMessage( + matchResult, + WARNING, + "Warning: JSON path expression '$.something' failed to match document '{ \"nothing\": 1 }' because of error 'No results for path: $['something']'"); } @Test - public void providesSensibleNotificationWhenJsonMatchFailsDueToMissingAttributeJson() { + void notifiesWhenMatchingBeingSkippedDueToContentProbablyBeingXml() { Notifier notifier = setMockNotifier(); StringValuePattern pattern = WireMock.matchingJsonPath("$.something"); - assertFalse(pattern.match("{ \"nothing\": 1 }").isExactMatch(), "Expected the match to fail"); - verify(notifier) - .info( - "Warning: JSON path expression '$.something' failed to match document '{ \"nothing\": 1 }' because of error 'No results for path: $['something']'"); + MatchResult matchResult = pattern.match(""); + + assertFalse(matchResult.isExactMatch(), "Expected the match to fail"); + checkWarningMessageAndEvent( + notifier, + matchResult, + "Warning: JSON path expression '$.something' failed to match document '' because it's not JSON but probably XML"); + } + + @Test + void subEventsReturnedBySubMatchersAreAddedToServeEvent() { + StringValuePattern pattern = + WireMock.matchingJsonPath("$.something", WireMock.equalToJson("{}")); + MatchResult matchResult = pattern.match("{ \"something\": \"{ \\\"bad:\" }"); + + assertFalse(matchResult.isExactMatch(), "Expected the match to fail"); + ServeEventChecks.checkJsonError( + matchResult, + "Unexpected end-of-input in field name\n at [Source: (String)\"{ \"bad:\"; line: 1, column: 8]"); } @Test @@ -422,6 +456,25 @@ public void matchesCorrectlyWhenSubMatcherIsUsedAndExpressionReturnsASingleItemA assertTrue(result.isExactMatch()); } + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + MatchesJsonPathPattern a = + new MatchesJsonPathPattern("$.searchCriteria[?(@.customerId == '104903')].date"); + MatchesJsonPathPattern b = + new MatchesJsonPathPattern("$.searchCriteria[?(@.customerId == '104903')].date"); + MatchesJsonPathPattern c = + new MatchesJsonPathPattern("$.searchCriteria[?(@.customerId == '1234')].date"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } + private static Notifier setMockNotifier() { final Notifier notifier = Mockito.mock(Notifier.class); LocalNotifier.set(notifier); diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPatternTest.java new file mode 100644 index 0000000000..b2efb67e22 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesJsonSchemaPatternTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import static com.github.tomakehurst.wiremock.client.WireMock.JsonSchemaVersion.V4; +import static com.github.tomakehurst.wiremock.client.WireMock.JsonSchemaVersion.V6; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonSchema; +import static com.github.tomakehurst.wiremock.testsupport.TestFiles.file; +import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Json; +import com.jayway.jsonpath.JsonPath; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class MatchesJsonSchemaPatternTest { + + @Test + void distanceIsProportionateToNumberOfValidationErrors() { + String schema = file("schema-validation/shop-order.schema.json"); + + MatchesJsonSchemaPattern pattern = new MatchesJsonSchemaPattern(schema); + + MatchResult veryBadMatchResult = pattern.match("{}"); + assertThat(veryBadMatchResult.isExactMatch(), is(false)); + assertThat(veryBadMatchResult.getDistance(), closeTo(0.66, 0.01)); + + MatchResult lessBadMatchResult = + pattern.match(file("schema-validation/shop-order.slightly-wrong.json")); + assertThat(lessBadMatchResult.isExactMatch(), is(false)); + assertThat(lessBadMatchResult.getDistance(), closeTo(0.33, 0.01)); + } + + private static List invalidContent() { + return Arrays.asList(null, "", "not json", "{"); + } + + @ParameterizedTest + @MethodSource("invalidContent") + void invalidContentGivesNoMatch(String content) { + String schema = file("schema-validation/shop-order.schema.json"); + + MatchesJsonSchemaPattern pattern = new MatchesJsonSchemaPattern(schema); + + MatchResult veryBadMatchResult = pattern.match(content); + + assertThat(veryBadMatchResult.isExactMatch(), is(false)); + assertThat(veryBadMatchResult.getDistance(), greaterThan(0.33)); + } + + @Test + void serialisesToJsonCorrectlyWithDefaultSchemaVersion() { + String schema = file("schema-validation/shop-order.schema.json"); + MatchesJsonSchemaPattern pattern = new MatchesJsonSchemaPattern(schema); + + String json = Json.write(pattern); + String schemaString = JsonPath.read(json, "$.matchesJsonSchema"); + assertThat(schemaString, jsonEquals(schema)); + } + + @Test + void serialisesToJsonCorrectlyWithProvidedSchemaVersion() { + String schema = file("schema-validation/shop-order.schema.json"); + MatchesJsonSchemaPattern pattern = + new MatchesJsonSchemaPattern(schema, WireMock.JsonSchemaVersion.V4); + + String json = Json.write(pattern); + String schemaString = JsonPath.read(json, "$.matchesJsonSchema"); + assertThat(schemaString, jsonEquals(schema)); + } + + @Test + void deserialisesFromJsonCorrectlyWithDefaultSchemaVersion() { + String schemaJson = + "{\n" + + " \"required\": [\n" + + " \"itemCatalogueId\",\n" + + " \"quantity\"\n" + + " ],\n" + + " \"properties\": {\n" + + " \"itemCatalogueId\": {\n" + + " \"type\": \"string\"\n" + + " },\n" + + " \"quantity\": {\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " \"fastDelivery\": {\n" + + " \"type\": \"boolean\"\n" + + " }\n" + + " }\n" + + " }"; + + String matcherJson = "{\n" + " \"matchesJsonSchema\": " + stringify(schemaJson) + "\n" + "}"; + + MatchesJsonSchemaPattern pattern = Json.read(matcherJson, MatchesJsonSchemaPattern.class); + + assertThat(pattern.getMatchesJsonSchema(), jsonEquals(schemaJson)); + } + + @Test + void deserialisesFromJsonCorrectlyWithProvidedSchemaVersion() { + String schemaJson = + "{\n" + + " \"properties\": {\n" + + " \"itemCatalogueId\": {\n" + + " \"type\": \"string\"\n" + + " }\n" + + " }\n" + + " }"; + + String matcherJson = + "{\n" + + " \"matchesJsonSchema\": " + + stringify(schemaJson) + + ",\n" + + " \"schemaVersion\": \"V6\"\n" + + "}"; + + MatchesJsonSchemaPattern pattern = Json.read(matcherJson, MatchesJsonSchemaPattern.class); + + assertThat(pattern.getSchemaVersion(), is(V6)); + } + + private static final StringValuePattern stringSchema = + matchingJsonSchema( + "{" + "\"type\": \"string\"," + "\"minLength\": 2," + "\"maxLength\": 4" + "}"); + + @ParameterizedTest + @MethodSource("validStrings") + void matchesAString(String toMatch) { + MatchResult match = stringSchema.match(toMatch); + assertThat(match.isExactMatch(), is(true)); + } + + private static Stream validStrings() { + return Stream.of(Arguments.of("\"12\""), Arguments.of("\"123\""), Arguments.of("\"1234\"")); + } + + @ParameterizedTest + @MethodSource("invalidStrings") + void doesNotMatchAnInvalidString(String toMatch) { + MatchResult match = stringSchema.match(toMatch); + + assertThat(match.isExactMatch(), is(false)); + assertThat(match.getDistance(), is(1.0)); + } + + private static Stream invalidStrings() { + return Stream.of( + Arguments.of(""), + Arguments.of("\"\""), + Arguments.of("\"1\""), + Arguments.of("\"12345\""), + Arguments.of("12"), + Arguments.of("123"), + Arguments.of("1234")); + } + + @ParameterizedTest + @MethodSource("simpleRefSchemaMatchingExamples") + void simpleRefMatches(String input) { + String schema = file("schema-validation/has-ref.schema.json"); + + MatchesJsonSchemaPattern pattern = + new MatchesJsonSchemaPattern(schema, WireMock.JsonSchemaVersion.V4); + + MatchResult match = pattern.match(input); + + assertThat(match.isExactMatch(), is(true)); + } + + private static Stream simpleRefSchemaMatchingExamples() { + return Stream.of( + Arguments.of("{ \"things\": [] }"), + Arguments.of("{ \"things\": [ 1 ] }"), + Arguments.of("{ \"things\": [ 1, 2 ] }")); + } + + @ParameterizedTest + @MethodSource("simpleRefSchemaNonMatchingExamples") + void simpleRefRejectsNonMatches(String input) { + String schema = file("schema-validation/has-ref.schema.json"); + + MatchesJsonSchemaPattern pattern = + new MatchesJsonSchemaPattern(schema, WireMock.JsonSchemaVersion.V4); + + MatchResult match = pattern.match(input); + + assertThat(match.isExactMatch(), is(false)); + } + + private static Stream simpleRefSchemaNonMatchingExamples() { + return Stream.of( + Arguments.of("{}"), + Arguments.of("{ \"not_things\": null }"), + Arguments.of("{ \"not_things\": [] }"), + Arguments.of("{ \"things\": null }"), + Arguments.of("{ \"things\": {} }"), + Arguments.of("{ \"things\": 1 }"), + Arguments.of("{ \"things\": [ \"1\" ] }")); + } + + @ParameterizedTest + @Disabled + @MethodSource("recursiveSchemaMatchingExamples") + void recursiveRefExactMatchesCorrectlyMatched(String input) { + String schema = file("schema-validation/recursive.schema.json"); + + MatchesJsonSchemaPattern pattern = new MatchesJsonSchemaPattern(schema, V4); + + MatchResult match = pattern.match(input); + + assertThat(match.isExactMatch(), is(true)); + } + + private static Stream recursiveSchemaMatchingExamples() { + return Stream.of( + Arguments.of("{ \"name\": \"no_children\" }"), + Arguments.of("{ \"name\": \"no_children\", \"children\": null }"), + Arguments.of("{ \"name\": \"no_children\", \"children\": [] }"), + Arguments.of( + "{ \"name\": \"no_grandchildren\", \"children\": [{ \"name\": \"no_children\", \"children\": [] }] }")); + } + + @ParameterizedTest + @Disabled + @MethodSource("recursiveSchemaNonMatchingExamples") + void recursiveRefNonMatchesCorrectlyMatched(String input) { + String schema = file("schema-validation/recursive.schema.json"); + + MatchesJsonSchemaPattern pattern = new MatchesJsonSchemaPattern(schema, V4); + + MatchResult match = pattern.match(input); + + assertThat(match.isExactMatch(), is(false)); + } + + @Test + void corercesNumericActualValueToJsonNumber() { + String schema = file("schema-validation/numeric.schema.json"); + + MatchesJsonSchemaPattern pattern = + new MatchesJsonSchemaPattern(schema, WireMock.JsonSchemaVersion.V4); + + assertThat(pattern.match("5").isExactMatch(), is(true)); + assertThat(pattern.match("0").isExactMatch(), is(true)); + assertThat(pattern.match("100").isExactMatch(), is(true)); + assertThat(pattern.match("10a").isExactMatch(), is(false)); + assertThat(pattern.match("101").isExactMatch(), is(false)); + } + + @Test + void corercesNumericActualValueToJsonString() { + String schema = file("schema-validation/stringy.schema.json"); + + MatchesJsonSchemaPattern pattern = + new MatchesJsonSchemaPattern(schema, WireMock.JsonSchemaVersion.V4); + + assertThat(pattern.match("abcd").isExactMatch(), is(true)); + assertThat(pattern.match("abcde").isExactMatch(), is(true)); + assertThat(pattern.match("abcdef").isExactMatch(), is(false)); + assertThat(pattern.match("0").isExactMatch(), is(false)); + } + + private static Stream recursiveSchemaNonMatchingExamples() { + return Stream.of( + Arguments.of("{}"), + Arguments.of("{ \"not_a_name\": null }"), + Arguments.of("{ \"name\": \"invalid_child\", \"children\": [{}] }"), + Arguments.of( + "{ \"name\": \"invalid_grandchild\", \"children\": [{ \"name\": \"invalid_child\", \"children\": [{}] }] }")); + } + + private static String stringify(String json) { + return "\"" + json.replace("\n", "").replace("\"", "\\\"") + "\""; + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPatternTest.java index f5a3a03774..3ddb5264ba 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MatchesXPathPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,18 @@ package com.github.tomakehurst.wiremock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.stubbing.SubEvent.WARNING; +import static com.github.tomakehurst.wiremock.testsupport.ServeEventChecks.checkJsonError; +import static com.github.tomakehurst.wiremock.testsupport.ServeEventChecks.checkMessage; import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalToJson; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Json; -import com.google.common.collect.ImmutableMap; import java.util.Collections; +import java.util.Map; import org.json.JSONException; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; @@ -96,7 +98,7 @@ public void matchesNamespacedXmlWhenNamespacesSpecified() { StringValuePattern pattern = WireMock.matchingXPath( "//sub:subThing[.='The stuff']", - ImmutableMap.of("sub", "http://subthings", "t", "http://things")); + Map.of("sub", "http://subthings", "t", "http://things")); MatchResult match = pattern.match(xml); assertTrue(match.isExactMatch()); @@ -254,11 +256,7 @@ public void deserialisesCorrectlyWithValuePattern() { @Test public void serialisesCorrectlyWithNamspaces() throws JSONException { MatchesXPathPattern pattern = - new MatchesXPathPattern( - "//*", - ImmutableMap.of( - "one", "http://one.com/", - "two", "http://two.com/")); + new MatchesXPathPattern("//*", Map.of("one", "http://one.com/", "two", "http://two.com/")); String json = Json.write(pattern); @@ -300,4 +298,52 @@ public void serialisesCorrectlyWithValuePattern() { public void noMatchOnNullValue() { assertThat(WireMock.matchingXPath("//*").match(null).isExactMatch(), is(false)); } + + @Test + void reportsErrorWhenActualXmlIsInvalid() { + MatchResult matchResult = new MatchesXPathPattern("/thing").match("{ bad json"); + checkJsonError( + matchResult, + "Unexpected character ('b' (code 98)): was expecting double-quote to start field name\n at [Source: (String)\"{ bad json\"; line: 1, column: 4]"); + } + + @Test + void reportsErrorWhenXPathExpressionIsInvalid() { + MatchResult matchResult = new MatchesXPathPattern("/\\!what?").match(""); + checkMessage( + matchResult, WARNING, "Warning: failed to evaluate the XPath expression /\\!what?"); + } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + MatchesXPathPattern a = new MatchesXPathPattern("/thing"); + MatchesXPathPattern b = new MatchesXPathPattern("/thing"); + MatchesXPathPattern c = new MatchesXPathPattern("/other"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MockMultipart.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MockMultipart.java index 32a08ca5fb..1f204aad0c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MockMultipart.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MockMultipart.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,18 @@ */ package com.github.tomakehurst.wiremock.matching; -import static com.google.common.collect.Lists.newArrayList; - import com.github.tomakehurst.wiremock.http.Body; import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.Request; +import java.util.ArrayList; import java.util.List; import java.util.Objects; public class MockMultipart implements Request.Part { private String name; - private List headers = newArrayList(); + private List headers = new ArrayList<>(); private Body body; public static MockMultipart mockPart() { diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MockRequest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MockRequest.java index 43c6f46522..370233fc5a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MockRequest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MockRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,25 +15,19 @@ */ package com.github.tomakehurst.wiremock.matching; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirstNonNull; import static com.github.tomakehurst.wiremock.common.Strings.bytesFromString; import static com.github.tomakehurst.wiremock.http.HttpHeader.httpHeader; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.tryFind; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Maps.newHashMap; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import com.github.tomakehurst.wiremock.common.Urls; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.http.*; -import com.github.tomakehurst.wiremock.jetty9.MultipartParser; +import com.github.tomakehurst.wiremock.jetty11.MultipartParser; import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; import java.net.URI; -import java.util.Collection; -import java.util.Map; -import java.util.Set; +import java.util.*; public class MockRequest implements Request { @@ -41,12 +35,18 @@ public class MockRequest implements Request { private String host = "my.domain"; private int port = 80; private String url = "/"; + + private String absoluteUrl = null; private RequestMethod method = RequestMethod.ANY; private HttpHeaders headers = new HttpHeaders(); - private Map cookies = newHashMap(); + + private final Map cookies = new HashMap<>(); + private final PathParams pathParams = new PathParams(); private byte[] body; private String clientIp = "1.1.1.1"; private Collection multiparts = null; + + private Map formParameters = new HashMap<>(); private boolean isBrowserProxyRequest = false; private String protocol = "HTTP/1.1"; @@ -74,6 +74,11 @@ public MockRequest url(String url) { return this; } + public MockRequest absoluteUrl(String absoluteUrl) { + this.absoluteUrl = absoluteUrl; + return this; + } + public MockRequest method(RequestMethod method) { this.method = method; return this; @@ -84,6 +89,11 @@ public MockRequest header(String key, String... values) { return this; } + public MockRequest headers(HttpHeaders headers) { + this.headers = headers; + return this; + } + public MockRequest cookie(String key, String... values) { cookies.put(key, new Cookie(asList(values))); return this; @@ -111,13 +121,20 @@ public MockRequest parts(Collection multiparts) { public MockRequest part(MockMultipart part) { if (multiparts == null) { - multiparts = newArrayList(); + multiparts = new ArrayList<>(); } multiparts.add(part); return this; } + public MockRequest formParameters(Map formParameters) { + if (formParameters != null) { + this.formParameters = formParameters; + } + return this; + } + public MockRequest isBrowserProxyRequest(boolean isBrowserProxyRequest) { this.isBrowserProxyRequest = isBrowserProxyRequest; return this; @@ -135,7 +152,8 @@ public String getUrl() { @Override public String getAbsoluteUrl() { - return "http://my.domain" + url; + String portPart = port == 80 || port == 443 ? "" : ":" + port; + return getFirstNonNull(absoluteUrl, String.format("%s://%s%s%s", scheme, host, portPart, url)); } @Override @@ -170,14 +188,10 @@ public String getHeader(String key) { @Override public HttpHeader header(final String key) { - return tryFind( - headers.all(), - new Predicate() { - public boolean apply(HttpHeader input) { - return input.keyEquals(key); - } - }) - .or(HttpHeader.absent(key)); + return headers.all().stream() + .filter(input -> input.keyEquals(key)) + .findFirst() + .orElseGet(() -> HttpHeader.absent(key)); } @Override @@ -211,6 +225,16 @@ public QueryParameter queryParameter(String key) { return queryParams.get(key); } + @Override + public FormParameter formParameter(String key) { + return getFirstNonNull(formParameters.get(key), FormParameter.absent(key)); + } + + @Override + public Map formParameters() { + return formParameters; + } + @Override public byte[] getBody() { return body; @@ -233,7 +257,7 @@ public boolean isBrowserProxyRequest() { @Override public Optional getOriginalRequest() { - return Optional.absent(); + return Optional.empty(); } @Override @@ -258,18 +282,7 @@ public Collection getParts() { @Override public Part getPart(final String name) { return (getParts() != null && name != null) - ? from(multiparts) - .firstMatch( - new Predicate() { - @Override - public boolean apply(Part input) { - if (name.equals(input.getName())) { - return true; - } - return false; - } - }) - .get() + ? multiparts.stream().filter(input -> name.equals(input.getName())).findFirst().orElse(null) : null; } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java index d023fcd9d9..af23a54596 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,7 @@ import static com.github.tomakehurst.wiremock.http.QueryParameter.queryParam; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.http.HttpHeader; diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilderTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilderTest.java index aae0a90339..e098775c92 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilderTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.absent; import static com.github.tomakehurst.wiremock.client.WireMock.containing; import static com.github.tomakehurst.wiremock.client.WireMock.equalToXml; -import static com.google.common.collect.Maps.newLinkedHashMap; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.everyItem; @@ -30,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -70,7 +70,7 @@ public void testBuilderWithNameHeadersAndBody() { .withBody(equalToXml("")) .build(); - Map> headerPatterns = newLinkedHashMap(); + Map> headerPatterns = new LinkedHashMap<>(); headerPatterns.put( "Content-Disposition", asList(MultiValuePattern.of(containing("name=\"name\"")))); headerPatterns.put("X-Header", asList(MultiValuePattern.of(containing("something")))); @@ -97,7 +97,7 @@ public void testBuilderWithoutNameWithHeadersAndBody() { .withBody(equalToXml("")) .build(); - Map> headerPatterns = newLinkedHashMap(); + Map> headerPatterns = new LinkedHashMap<>(); headerPatterns.put("X-Header", asList(MultiValuePattern.of(containing("something")))); headerPatterns.put("X-Other", asList(MultiValuePattern.of(absent()))); // assertThat(headerPatterns.entrySet(), @@ -114,7 +114,7 @@ public void testBuilderWithNameAndOtherContentDispositionHeaderMatcher() { .withHeader("Content-Disposition", containing("filename=\"something\"")) .build(); - Map> headerPatterns = newLinkedHashMap(); + Map> headerPatterns = new LinkedHashMap<>(); headerPatterns.put( "Content-Disposition", asList( diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternTest.java index eea853d3c4..67dd3bd4f2 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MultipartValuePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,8 @@ public void deserialisesCorrectlyWithTypeAllAndSingleHeaderMatcher() { MultipartValuePattern pattern = Json.read(serializedPattern, MultipartValuePattern.class); StringValuePattern headerPattern = - pattern.getHeaders().get("Content-Disposition").getValuePattern(); + ((SingleMatchMultiValuePattern) pattern.getHeaders().get("Content-Disposition")) + .getValuePattern(); assertThat(headerPattern, instanceOf(ContainsPattern.class)); assertThat(headerPattern.getValue(), is("name=\"part1\"")); @@ -117,7 +118,8 @@ public void deserialisesCorrectlyWithANYMatchTypeWithMultipleHeaderAndBodyMatche assertThat( pattern.getBodyPatterns().get(0).getExpected(), WireMatchers.equalToJson(expectedJson)); - MultiValuePattern contentTypeHeaderPattern = pattern.getHeaders().get("Content-Type"); + SingleMatchMultiValuePattern contentTypeHeaderPattern = + (SingleMatchMultiValuePattern) pattern.getHeaders().get("Content-Type"); assertThat(contentTypeHeaderPattern.getValuePattern(), instanceOf(ContainsPattern.class)); assertThat(contentTypeHeaderPattern.getValuePattern().getExpected(), is("application/json")); assertTrue(pattern.isMatchAny()); @@ -157,7 +159,9 @@ public void deserialisesCorrectlyWithHeadersAndBinaryBody() { assertThat(pattern.getName(), is("my_part_name")); assertEquals(pattern.getBodyPatterns().get(0).getExpected(), expectedBinary); assertThat( - pattern.getHeaders().get("Content-Type").getValuePattern().getExpected(), + ((SingleMatchMultiValuePattern) pattern.getHeaders().get("Content-Type")) + .getValuePattern() + .getExpected(), is("application/octet-stream")); assertTrue(pattern.isMatchAll()); assertFalse(pattern.isMatchAny()); @@ -209,6 +213,7 @@ public void equalsShouldReturnTrueOnSameObject() { .build(); assertThat(pattern.equals(pattern), is(true)); + assertEquals(pattern.hashCode(), pattern.hashCode()); } @Test @@ -230,6 +235,7 @@ public void equalsShouldReturnTrueOnIdenticalButNotSameObjects() { .build(); assertThat(patternA.equals(patternB), is(true)); + assertEquals(patternA.hashCode(), patternB.hashCode()); } @Test @@ -250,5 +256,41 @@ public void equalsShouldReturnFalseOnDifferentObjects() { .build(); assertThat(patternA.equals(patternB), is(false)); + assertNotEquals(patternA.hashCode(), patternB.hashCode()); + } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + MultipartValuePattern patternA = + aMultipart() + .withName("title") + .withHeader("X-First-Header", equalTo("One")) + .withHeader("X-Second-Header", matching(".*2")) + .withBody(binaryEqualTo("RG9jdW1lbnQgYm9keSBjb250ZW50cw==")) + .build(); + + MultipartValuePattern patternB = + aMultipart() + .withName("title") + .withHeader("X-First-Header", equalTo("One")) + .withHeader("X-Second-Header", matching(".*2")) + .withBody(binaryEqualTo("RG9jdW1lbnQgYm9keSBjb250ZW50cw==")) + .build(); + + MultipartValuePattern patternC = + aMultipart() + .withName("Description") + .withHeader("X-First-Header", equalTo("Second")) + .withBody(binaryEqualTo("SGVsbG9Xb3JsZA==")) + .build(); + + assertEquals(patternA, patternB); + assertEquals(patternA.hashCode(), patternB.hashCode()); + assertEquals(patternB, patternA); + assertEquals(patternB.hashCode(), patternA.hashCode()); + assertNotEquals(patternA, patternC); + assertNotEquals(patternA.hashCode(), patternC.hashCode()); + assertNotEquals(patternB, patternC); + assertNotEquals(patternB.hashCode(), patternC.hashCode()); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/NegativeContainsPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/NegativeContainsPatternTest.java index 4d54db85bf..b8f11ca011 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/NegativeContainsPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/NegativeContainsPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.jupiter.api.Test; @@ -41,4 +40,20 @@ public void returnsNoMatchWhenWhenExpectedValueWhollyContainedInTestValue() { assertFalse(matchResult.isExactMatch()); assertThat(matchResult.getDistance(), is(1.0)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + NegativeContainsPattern a = new NegativeContainsPattern("doNotContain"); + NegativeContainsPattern b = new NegativeContainsPattern("doNotContain"); + NegativeContainsPattern c = new NegativeContainsPattern("somethingElse"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/NotPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/NotPatternTest.java new file mode 100644 index 0000000000..21a7b3f066 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/NotPatternTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class NotPatternTest { + + @Test + void shouldReturnExactMatchWhenValueIsNull() { + MatchResult matchResult = not(containing("thing")).match(null); + boolean result = matchResult.isExactMatch(); + + assertTrue(result); + } + + @Test + void shouldReturnNoMatchWhenValueIsContainedInTestValue() { + MatchResult matchResult = not(containing("thing")).match("otherthings"); + boolean result = matchResult.isExactMatch(); + double distance = matchResult.getDistance(); + + assertFalse(result); + assertThat(distance, is(1.0)); + } + + @Test + void shouldReturnExactMatchWhenValueIsNotContainedInTestValue() { + MatchResult matchResult = not(containing("thing")).match("otherstuff"); + boolean result = matchResult.isExactMatch(); + + assertTrue(result); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/PathTemplatePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/PathTemplatePatternTest.java new file mode 100644 index 0000000000..219c719c53 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/PathTemplatePatternTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.matching; + +import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import com.github.tomakehurst.wiremock.common.Json; +import org.junit.jupiter.api.Test; + +public class PathTemplatePatternTest { + + @Test + void returns_exact_match_when_path_matches_template() { + PathTemplatePattern pattern = new PathTemplatePattern("/one/{id}/two"); + assertThat(pattern.match("/one/3/two").isExactMatch(), is(true)); + } + + @Test + void returns_no_match_and_low_distance_when_path_almost_matches_template() { + PathTemplatePattern pattern = new PathTemplatePattern("/one/{id}/two"); + + MatchResult matchResult = pattern.match("/on/3/two"); + + assertThat(matchResult.isExactMatch(), is(false)); + assertThat(matchResult.getDistance(), closeTo(0.2, 0.05)); + } + + @Test + void returns_no_match_and_high_distance_when_path_is_very_different_from_template() { + PathTemplatePattern pattern = new PathTemplatePattern("/one/{id}/two"); + + MatchResult matchResult = pattern.match("/totally/different/stuff/and/length"); + + assertThat(matchResult.isExactMatch(), is(false)); + assertThat(matchResult.getDistance(), closeTo(0.9, 0.05)); + } + + @Test + void serialises_correctly() { + PathTemplatePattern pattern = new PathTemplatePattern("/one/{first}/two/{second}"); + + String json = Json.write(pattern); + + assertThat( + json, + jsonEquals("{\n" + " \"matchesPathTemplate\": \"/one/{first}/two/{second}\"\n" + "}")); + } + + @Test + void deserialises_correcltly() { + StringValuePattern pattern = + Json.read( + "{\n" + " \"matchesPathTemplate\": \"/one/{first}/two/{second}\"\n" + "}", + StringValuePattern.class); + + assertThat(pattern, instanceOf(PathTemplatePattern.class)); + assertThat( + ((PathTemplatePattern) pattern).getPathTemplate().toString(), + is("/one/{first}/two/{second}")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/RegexValuePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/RegexValuePatternTest.java index 5eacdb3e2f..19bff710db 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/RegexValuePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/RegexValuePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Json; @@ -51,4 +53,20 @@ public void correctlyDeserialisesMatchesFromJson() { public void noMatchWhenValueIsNull() { assertThat(WireMock.matching(".*").match(null).isExactMatch(), is(false)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + RegexPattern a = new RegexPattern("test"); + RegexPattern b = new RegexPattern("test"); + RegexPattern c = new RegexPattern("anotherTest"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilderTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilderTest.java index cc12db588f..afb65bf5c8 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilderTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aMultipart; import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -31,8 +32,8 @@ import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; public class RequestPatternBuilderTest { @@ -58,11 +59,13 @@ public void likeRequestPatternWithoutCustomMatcher() { 1234, WireMock.urlEqualTo("/foo"), RequestMethod.POST, - ImmutableMap.of("X-Header", MultiValuePattern.of(WireMock.equalTo("bar"))), - ImmutableMap.of("query_param", MultiValuePattern.of(WireMock.equalTo("bar"))), - ImmutableMap.of("cookie", WireMock.equalTo("yum")), + Map.of("X-Header", MultiValuePattern.of(WireMock.equalTo("bar"))), + emptyMap(), + Map.of("query_param", MultiValuePattern.of(WireMock.equalTo("bar"))), + Map.of("form_param", MultiValuePattern.of(WireMock.equalTo("bar"))), + Map.of("cookie", WireMock.equalTo("yum")), new BasicCredentials("user", "pass"), - ImmutableList.>of(WireMock.equalTo("BODY")), + List.of(WireMock.equalTo("BODY")), null, null, null); @@ -116,11 +119,13 @@ public void likeRequestPatternWithoutMultipartMatcher() { 1234, WireMock.urlEqualTo("/foo"), RequestMethod.POST, - ImmutableMap.of("X-Header", MultiValuePattern.of(WireMock.equalTo("bar"))), - ImmutableMap.of("query_param", MultiValuePattern.of(WireMock.equalTo("bar"))), - ImmutableMap.of("cookie", WireMock.equalTo("yum")), + Map.of("X-Header", MultiValuePattern.of(WireMock.equalTo("bar"))), + emptyMap(), + Map.of("query_param", MultiValuePattern.of(WireMock.equalTo("bar"))), + Map.of("form_param", MultiValuePattern.of(WireMock.equalTo("bar"))), + Map.of("cookie", WireMock.equalTo("yum")), new BasicCredentials("user", "pass"), - ImmutableList.>of(WireMock.equalTo("BODY")), + List.of(WireMock.equalTo("BODY")), null, null, asList(multipartPattern)); diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternTest.java index 5bafb98447..8c0198fb5b 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/RequestPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,51 @@ */ package com.github.tomakehurst.wiremock.matching; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aMultipart; +import static com.github.tomakehurst.wiremock.client.WireMock.absent; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.http.RequestMethod.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToXml; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonSchema; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingXPath; +import static com.github.tomakehurst.wiremock.client.WireMock.not; +import static com.github.tomakehurst.wiremock.client.WireMock.notContaining; +import static com.github.tomakehurst.wiremock.client.WireMock.notMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; +import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; +import static com.github.tomakehurst.wiremock.http.RequestMethod.POST; +import static com.github.tomakehurst.wiremock.http.RequestMethod.PUT; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.http.FormParameter; import com.github.tomakehurst.wiremock.http.RequestMethod; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.skyscreamer.jsonassert.JSONAssert; public class RequestPatternTest { @@ -252,6 +281,89 @@ public void bindsToJsonCompatibleWithOriginalRequestPatternWithQueryParams() thr true); } + @Test + public void matchesExactlyWith0DistanceWhenAllRequiredFormParametersMatch() { + RequestPattern requestPattern = + newRequestPattern(PUT, urlPathEqualTo("/my/url")) + .withFormParam("key1", equalTo("value1")) + .withFormParam("key2", equalTo("value2")) + .build(); + + Map formParameters = new HashMap<>(); + formParameters.put("key1", new FormParameter("key1", List.of("value1"))); + formParameters.put("key2", new FormParameter("key1", List.of("value2"))); + MatchResult matchResult = + requestPattern.match( + mockRequest().method(PUT).url("/my/url").formParameters(formParameters)); + assertThat(matchResult.getDistance(), is(0.0)); + assertTrue(matchResult.isExactMatch()); + } + + @Test + public void returnsNon0DistanceWhenRequiredFormParameterMatchDoesNotMatch() { + RequestPattern requestPattern = + newRequestPattern(PUT, urlPathEqualTo("/my/url")) + .withFormParam("key1", equalTo("value1")) + .withFormParam("key2", equalTo("value2")) + .build(); + + Map formParameters = new HashMap<>(); + formParameters.put("key1", new FormParameter("key1", List.of("value555"))); + formParameters.put("key2", new FormParameter("key1", List.of("value78"))); + + MatchResult matchResult = + requestPattern.match( + mockRequest().method(PUT).url("/my/url").formParameters(formParameters)); + assertThat(matchResult.getDistance(), greaterThan(0.0)); + assertFalse(matchResult.isExactMatch()); + } + + @Test + public void bindsToJsonCompatibleWithOriginalRequestPatternWithFormParams() throws Exception { + RequestPattern requestPattern = + newRequestPattern(POST, urlPathEqualTo("/my/url")) + .withFormParam("key1", equalTo("value1")) + .withFormParam("key2", matching("value2")) + .build(); + + String actualJson = Json.write(requestPattern); + + JSONAssert.assertEquals(getFormParameterRequestPatternJson(), actualJson, true); + } + + @Test + public void correctlyDeserializesFormParams() { + RequestPattern requestPattern = + Json.read(getFormParameterRequestPatternJson(), RequestPattern.class); + assertTrue( + requestPattern.getFormParameters().get("key1") instanceof SingleMatchMultiValuePattern); + assertTrue( + requestPattern.getFormParameters().get("key2") instanceof SingleMatchMultiValuePattern); + assertThat( + ((SingleMatchMultiValuePattern) requestPattern.getFormParameters().get("key1")) + .getValuePattern(), + valuePattern(EqualToPattern.class, "value1")); + assertThat( + ((SingleMatchMultiValuePattern) requestPattern.getFormParameters().get("key2")) + .getValuePattern(), + valuePattern(RegexPattern.class, "value2")); + } + + private String getFormParameterRequestPatternJson() { + return "{ \n" + + " \"method\": \"POST\", \n" + + " \"urlPath\": \"/my/url\", \n" + + " \"formParameters\": { \n" + + " \"key1\": { \n" + + " \"equalTo\": \"value1\" \n" + + " }, \n" + + " \"key2\": { \n" + + " \"matches\": \"value2\" \n" + + " } \n" + + " } \n" + + "}"; + } + @Test public void matchesExactlyWith0DistanceWhenBodyPatternsAllMatch() { RequestPattern requestPattern = @@ -447,6 +559,56 @@ public void doesNotMatchWhenRequiredAbsentCookieIsPresent() { assertFalse(matchResult.isExactMatch()); } + private static final StringValuePattern stringSchema = + matchingJsonSchema( + "{" + "\"type\": \"string\"," + "\"minLength\": 2," + "\"maxLength\": 4" + "}"); + + @ParameterizedTest + @MethodSource("validStrings") + void matchesAPathParamAgainstAStringSchema(String toMatch) { + + RequestPattern requestPattern = + newRequestPattern(GET, urlPathTemplate("/foo/{id}")) + .withPathParam("id", stringSchema) + .build(); + + MatchResult matchResult = + requestPattern.match(mockRequest().method(GET).url("/foo/" + toMatch)); + + assertThat(matchResult.isExactMatch(), is(true)); + } + + private static Stream validStrings() { + return Stream.of(Arguments.of("\"12\""), Arguments.of("\"123\""), Arguments.of("\"1234\"")); + } + + @ParameterizedTest + @MethodSource("invalidStrings") + void doesNotMatchAnInvalidString(String toMatch) { + + RequestPattern requestPattern = + newRequestPattern(GET, urlPathTemplate("/foo/{id}")) + .withPathParam("id", stringSchema) + .build(); + + MatchResult matchResult = + requestPattern.match(mockRequest().method(GET).url("/foo/" + toMatch)); + + assertThat(matchResult.isExactMatch(), is(false)); + assertThat(matchResult.getDistance(), closeTo(0.02, 0.01)); + } + + private static Stream invalidStrings() { + return Stream.of( + Arguments.of(""), + Arguments.of("\"\""), + Arguments.of("\"1\""), + Arguments.of("\"12345\""), + Arguments.of("12"), + Arguments.of("123"), + Arguments.of("1234")); + } + static final String ALL_BODY_PATTERNS_EXAMPLE = "{ \n" + " \"url\" : \"/all/body/patterns\", \n" @@ -459,6 +621,7 @@ public void doesNotMatchWhenRequiredAbsentCookieIsPresent() { + " { \"matchesXPath\": \"//thing\" }, \n" + " { \"contains\": \"thin\" }, \n" + " { \"doesNotContain\": \"stuff\" }, \n" + + " { \"not\": { \"contains\": \"thing\" } }, \n" + " { \"matches\": \".*thing.*\" }, \n" + " { \"doesNotMatch\": \"^stuff.+\" } \n" + " ] \n" @@ -478,6 +641,7 @@ public void correctlyDeserialisesBodyPatterns() { valuePattern(MatchesXPathPattern.class, "//thing"), valuePattern(ContainsPattern.class, "thin"), valuePattern(NegativeContainsPattern.class, "stuff"), + valuePattern(NotPattern.class, containing("thing").expectedValue), valuePattern(RegexPattern.class, ".*thing.*"), valuePattern(NegativeRegexPattern.class, "^stuff.+"))); } @@ -493,12 +657,12 @@ public void correctlySerialisesBodyPatterns() throws Exception { .withRequestBody(matchingXPath("//thing")) .withRequestBody(containing("thin")) .withRequestBody(notContaining("stuff")) + .withRequestBody(not(containing("thing"))) .withRequestBody(matching(".*thing.*")) .withRequestBody(notMatching("^stuff.+")) .build(); String json = Json.write(requestPattern); - System.out.println(json); JSONAssert.assertEquals(ALL_BODY_PATTERNS_EXAMPLE, json, true); } @@ -520,4 +684,25 @@ public void describeTo(Description description) { } }; } + + static Matcher> notValuePattern( + final Class patternClass, + final StringValuePattern unexpectedPattern) { + return new TypeSafeDiagnosingMatcher>() { + @Override + protected boolean matchesSafely(ContentPattern item, Description mismatchDescription) { + return item.getClass().equals(patternClass) + && item.getValue().equals(unexpectedPattern.expectedValue); + } + + @Override + public void describeTo(Description description) { + description.appendText( + "a value pattern of type " + + patternClass.getSimpleName() + + " with expected value " + + unexpectedPattern.expectedValue); + } + }; + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/StringValuePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/StringValuePatternTest.java index 786b9b67f3..dd6077ddf8 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/StringValuePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/StringValuePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,87 +15,54 @@ */ package com.github.tomakehurst.wiremock.matching; -import static com.google.common.collect.FluentIterable.from; -import static java.util.Arrays.asList; - import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.reflect.ClassPath; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Set; import org.junit.jupiter.api.Test; public class StringValuePatternTest { @Test public void allSubclassesHaveWorkingToString() throws Exception { - ImmutableSet allClasses = + Set allClasses = ClassPath.from(Thread.currentThread().getContextClassLoader()).getAllClasses(); - FluentIterable> classes = - from(allClasses) - .filter( - new Predicate() { - @Override - public boolean apply(ClassPath.ClassInfo input) { - return input - .getPackageName() - .startsWith("com.github.tomakehurst.wiremock.matching"); - } - }) - .transform( - new Function>() { - @Override - public Class apply(ClassPath.ClassInfo input) { - try { - return input.load(); - } catch (Throwable e) { - return Object.class; - } - } - }) - .filter(assignableFrom(StringValuePattern.class)) - .filter( - new Predicate>() { - @Override - public boolean apply(Class input) { - return !Modifier.isAbstract(input.getModifiers()); - } - }); - - for (Class clazz : classes) { - findConstructorWithStringParamInFirstPosition(clazz); - } + allClasses.stream() + .filter( + classInfo -> + classInfo.getPackageName().startsWith("com.github.tomakehurst.wiremock.matching")) + .map( + input -> { + try { + return input.load(); + } catch (Throwable e) { + return Object.class; + } + }) + .filter(clazz -> clazz.isAssignableFrom(StringValuePattern.class)) + .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) + .forEach(this::findConstructorWithStringParamInFirstPosition); } private Constructor findConstructorWithStringParamInFirstPosition(Class clazz) { - return Iterables.find( - asList(clazz.getConstructors()), - new Predicate>() { - @Override - public boolean apply(Constructor input) { - return input.getParameterTypes().length > 0 - && input.getParameterTypes()[0].equals(String.class) - && input.getParameterAnnotations().length > 0 - && input.getParameterAnnotations()[0].length > 0 - && input - .getParameterAnnotations()[0][0] - .annotationType() - .equals(JsonProperty.class); - } - }); - } - - private static Predicate> assignableFrom(final Class clazz) { - return new Predicate>() { - @Override - public boolean apply(Class aClass) { - return aClass.isAssignableFrom(clazz); - } - }; + return Arrays.stream(clazz.getConstructors()) + .filter( + constructor -> + constructor.getParameterTypes().length > 0 + && constructor.getParameterTypes()[0].equals(String.class) + && constructor.getParameterAnnotations().length > 0 + && constructor.getParameterAnnotations()[0].length > 0 + && constructor + .getParameterAnnotations()[0][0] + .annotationType() + .equals(JsonProperty.class)) + .findFirst() + .orElseThrow( + () -> + new AssertionError( + "No constructor found with @JsonProperty annotated name parameter")); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/UrlPatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/UrlPatternTest.java index 4a03717279..33abda408f 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/UrlPatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/UrlPatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.tomakehurst.wiremock.client.WireMock; import org.junit.jupiter.api.Test; @@ -73,4 +73,20 @@ public void noMatchOnNullValueForUrlRegex() { public void noMatchOnNullValueForUrlPathRegex() { assertThat(WireMock.urlPathMatching("/things/.*").match(null).isExactMatch(), is(false)); } + + @Test + public void objectsShouldBeEqualOnSameExpectedValue() { + UrlPathPattern a = WireMock.urlPathMatching("/things/.*"); + UrlPathPattern b = WireMock.urlPathMatching("/things/.*"); + UrlPathPattern c = WireMock.urlPathMatching("/test/.*"); + + assertEquals(a, b); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(b, a); + assertEquals(b.hashCode(), a.hashCode()); + assertNotEquals(a, c); + assertNotEquals(a.hashCode(), c.hashCode()); + assertNotEquals(b, c); + assertNotEquals(b.hashCode(), c.hashCode()); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFiltersTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFiltersTest.java index c849eb5109..72b57d82af 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFiltersTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/ProxiedServeEventFiltersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.concurrent.LinkedBlockingDeque; import org.junit.jupiter.api.Test; public class ProxiedServeEventFiltersTest { @@ -41,18 +42,18 @@ public void applyWithUniversalRequestPattern() { ServeEvent serveEvent = proxiedServeEvent(mockRequest()); ProxiedServeEventFilters filters = new ProxiedServeEventFilters(RequestPattern.ANYTHING, null, false); - assertTrue(filters.apply(serveEvent)); + assertTrue(filters.test(serveEvent)); // Should default to RequestPattern.ANYTHING when passing null for filters filters = new ProxiedServeEventFilters(null, null, false); - assertTrue(filters.apply(serveEvent)); + assertTrue(filters.test(serveEvent)); } @Test public void applyWithUnproxiedServeEvent() { ServeEvent serveEvent = toServeEvent(null, null, ResponseDefinition.ok()); ProxiedServeEventFilters filters = new ProxiedServeEventFilters(null, null, false); - assertFalse(filters.apply(serveEvent)); + assertFalse(filters.test(serveEvent)); } @Test @@ -61,9 +62,9 @@ public void applyWithMethodPattern() { new ProxiedServeEventFilters(newRequestPattern(GET, anyUrl()).build(), null, false); MockRequest request = mockRequest().method(GET).url("/foo"); - assertTrue(filters.apply(proxiedServeEvent(request))); - assertTrue(filters.apply(proxiedServeEvent(request.url("/bar")))); - assertFalse(filters.apply(proxiedServeEvent(request.method(POST)))); + assertTrue(filters.test(proxiedServeEvent(request))); + assertTrue(filters.test(proxiedServeEvent(request.url("/bar")))); + assertFalse(filters.test(proxiedServeEvent(request.method(POST)))); } @Test @@ -74,10 +75,10 @@ public void applyWithIds() { UUID.fromString("00000000-0000-0000-0000-000000000001")); ProxiedServeEventFilters filters = new ProxiedServeEventFilters(null, ids, false); - assertTrue(filters.apply(proxiedServeEvent(ids.get(0)))); - assertTrue(filters.apply(proxiedServeEvent(ids.get(1)))); + assertTrue(filters.test(proxiedServeEvent(ids.get(0)))); + assertTrue(filters.test(proxiedServeEvent(ids.get(1)))); assertFalse( - filters.apply(proxiedServeEvent(UUID.fromString("00000000-0000-0000-0000-000000000002")))); + filters.test(proxiedServeEvent(UUID.fromString("00000000-0000-0000-0000-000000000002")))); } @Test @@ -87,9 +88,9 @@ public void applyWithMethodAndUrlPattern() { newRequestPattern(GET, urlEqualTo("/foo")).build(), null, false); MockRequest request = mockRequest().method(GET).url("/foo"); - assertTrue(filters.apply(proxiedServeEvent(request))); - assertFalse(filters.apply(proxiedServeEvent(request.url("/bar")))); - assertFalse(filters.apply(proxiedServeEvent(request.method(POST)))); + assertTrue(filters.test(proxiedServeEvent(request))); + assertFalse(filters.test(proxiedServeEvent(request.url("/bar")))); + assertFalse(filters.test(proxiedServeEvent(request.method(POST)))); } @Test @@ -102,11 +103,11 @@ public void applyWithIdsAndMethodPattern() { ProxiedServeEventFilters filters = new ProxiedServeEventFilters(newRequestPattern(GET, anyUrl()).build(), ids, false); - assertTrue(filters.apply(proxiedServeEvent(ids.get(0), request))); + assertTrue(filters.test(proxiedServeEvent(ids.get(0), request))); assertFalse( - filters.apply( + filters.test( proxiedServeEvent(UUID.fromString("00000000-0000-0000-0000-000000000002"), request))); - assertFalse(filters.apply(proxiedServeEvent(ids.get(0), request.method(POST)))); + assertFalse(filters.test(proxiedServeEvent(ids.get(0), request.method(POST)))); } private ServeEvent toServeEvent( @@ -118,7 +119,8 @@ private ServeEvent toServeEvent( responseDefinition, null, true, - Timing.UNTIMED); + Timing.UNTIMED, + new LinkedBlockingDeque<>()); } private ServeEvent proxiedServeEvent(UUID id, MockRequest request) { diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformerTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformerTest.java index 2575f49fc2..3ee041c88f 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/RequestPatternTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestMethod; import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; -import com.google.common.collect.ImmutableMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -59,9 +58,11 @@ public void applyWithHeaders() { .withHeader("X-CaseInsensitive", equalToIgnoreCase("Baz")); Map headers = - ImmutableMap.of( - "X-CaseSensitive", new CaptureHeadersSpec(false), - "X-CaseInsensitive", new CaptureHeadersSpec(true)); + Map.of( + "X-CaseSensitive", + new CaptureHeadersSpec(false), + "X-CaseInsensitive", + new CaptureHeadersSpec(true)); assertEquals( expected.build(), new RequestPatternTransformer(headers, null).apply(request).build()); diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/ResponseDefinitionBodyMatcherDeserializerTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/ResponseDefinitionBodyMatcherDeserializerTest.java index b46fe07eb7..1d7dbeb4d2 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/ResponseDefinitionBodyMatcherDeserializerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/ResponseDefinitionBodyMatcherDeserializerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.tomakehurst.wiremock.common.Json; -import com.google.common.collect.ImmutableMap; import java.util.Map; import org.junit.jupiter.api.Test; @@ -26,14 +25,19 @@ public class ResponseDefinitionBodyMatcherDeserializerTest { @Test public void correctlyParsesFileSize() { final Map testCases = - ImmutableMap.builder() - .put("100", 100L) - .put("1KB", 1024L) - .put("1 kb", 1024L) - .put("1024 K", 1024L * 1024) - .put("10 Mb", 10L * 1024 * 1024) - .put("10.5 GB", Math.round(10.5 * 1024 * 1024 * 1024)) - .build(); + Map.of( + "100", + 100L, + "1KB", + 1024L, + "1 kb", + 1024L, + "1024 K", + 1024L * 1024, + "10 Mb", + 10L * 1024 * 1024, + "10.5 GB", + Math.round(10.5 * 1024 * 1024 * 1024)); for (String input : testCases.keySet()) { Long expected = testCases.get(input); diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractorTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractorTest.java index acc4a536e0..7f78320e09 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractorTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingBodyExtractorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.store.files.FileSourceBlobStore; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,7 +38,7 @@ public class SnapshotStubMappingBodyExtractorTest { @BeforeEach public void init() { filesSource = Mockito.mock(FileSource.class, "filesFileSource"); - bodyExtractor = new SnapshotStubMappingBodyExtractor(filesSource); + bodyExtractor = new SnapshotStubMappingBodyExtractor(new FileSourceBlobStore(filesSource)); } @Test @@ -45,7 +46,7 @@ public void updatesStubMapping() { StubMapping stubMapping = WireMock.get("/foo").willReturn(ok("")).build(); bodyExtractor.extractInPlace(stubMapping); assertThat( - stubMapping.getResponse().getBodyFileName(), is("foo-" + stubMapping.getId() + ".txt")); + stubMapping.getResponse().getBodyFileName(), is("get-foo-" + stubMapping.getId() + ".txt")); assertThat(stubMapping.getResponse().specifiesBodyFile(), is(true)); assertThat(stubMapping.getResponse().specifiesBodyContent(), is(false)); // ignore arguments because this test is only for checking stub mapping changes @@ -56,21 +57,21 @@ public void updatesStubMapping() { public void determinesFileNameProperlyFromUrlWithJson() { StubMapping stubMapping = WireMock.get("/foo/bar.json").willReturn(ok("{}")).build(); bodyExtractor.extractInPlace(stubMapping); - verifyWriteBinaryFile("foobarjson-" + stubMapping.getId() + ".json", "{}"); + verifyWriteBinaryFile("get-foobar.json-" + stubMapping.getId() + ".json", "{}"); } @Test public void determinesFileNameProperlyFromUrlWithText() { StubMapping stubMapping = WireMock.get("/foo/bar.txt").willReturn(ok("")).build(); bodyExtractor.extractInPlace(stubMapping); - verifyWriteBinaryFile("foobartxt-" + stubMapping.getId() + ".txt", ""); + verifyWriteBinaryFile("get-foobar.txt-" + stubMapping.getId() + ".txt", ""); } @Test public void determinesFileNameProperlyFromMimeTypeWithJson() { StubMapping stubMapping = WireMock.get("/foo/bar.txt").willReturn(okJson("{}")).build(); bodyExtractor.extractInPlace(stubMapping); - verifyWriteBinaryFile("foobartxt-" + stubMapping.getId() + ".json", "{}"); + verifyWriteBinaryFile("get-foobar.txt-" + stubMapping.getId() + ".json", "{}"); } @Test diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGeneratorTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGeneratorTest.java index da143dcf00..8775e1b9fd 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGeneratorTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingGeneratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.verification.LoggedRequest; +import java.util.concurrent.LinkedBlockingDeque; import org.junit.jupiter.api.Test; public class SnapshotStubMappingGeneratorTest { @@ -50,6 +51,25 @@ public void apply() { assertThat(actual, is(expected)); } + @Test + public void applyWithStrangePathAndCheckSanitizedState() { + final RequestPatternBuilder requestPatternBuilder = + newRequestPattern().withUrl("hello_1_2_3___ace--ace___and"); + final ResponseDefinition responseDefinition = ResponseDefinition.ok(); + + SnapshotStubMappingGenerator stubMappingTransformer = + new SnapshotStubMappingGenerator( + requestPatternTransformer(requestPatternBuilder), + responseDefinitionTransformer(responseDefinition)); + + StubMapping actual = + stubMappingTransformer.apply(serveEventWithPath("/hello/1/2/3__!/ẮČĖ--ace/¥$$/$/and/¿?")); + StubMapping expected = new StubMapping(requestPatternBuilder.build(), responseDefinition); + expected.setId(actual.getId()); + + assertThat(actual, is(expected)); + } + private static RequestPatternTransformer requestPatternTransformer( final RequestPatternBuilder requestPatternBuilder) { return new RequestPatternTransformer(null, null) { @@ -70,6 +90,10 @@ public ResponseDefinition apply(LoggedResponse response) { }; } + private static ServeEvent serveEventWithPath(String path) { + return ServeEvent.of(LoggedRequest.createFrom(aRequest().withUrl(path).build())); + } + private static ServeEvent serveEvent() { return new ServeEvent( null, @@ -78,6 +102,7 @@ private static ServeEvent serveEvent() { null, LoggedResponse.from(Response.notConfigured(), UNLIMITED), false, - Timing.UNTIMED); + Timing.UNTIMED, + new LinkedBlockingDeque<>()); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessorTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessorTest.java index 8a32dae17c..2ff0ac597f 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessorTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingPostProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +18,29 @@ import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.MatchResult; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.collect.ImmutableList; import java.util.List; import org.junit.jupiter.api.Test; public class SnapshotStubMappingPostProcessorTest { - private static final List TEST_STUB_MAPPINGS = - ImmutableList.of( + + // NOTE: testStubMappings is not deeply immutable, as StubMappings are mutable, and to preserve + // hermeticity must be an instance rather than a class variable. + private final List testStubMappings = + List.of( WireMock.get("/foo").build(), WireMock.get("/bar").build(), WireMock.get("/foo").build()); @Test - public void processFiltersRepeatedRequestsWhenNotRecordingScenarios() { + public void processWithRecordRepeatsAsScenariosFalseShouldFilterRepeatedRequests() { final List actual = new SnapshotStubMappingPostProcessor(false, noopTransformerRunner(), null, null) - .process(TEST_STUB_MAPPINGS); + .process(testStubMappings); assertThat(actual, hasSize(2)); assertThat(actual.get(0).getRequest().getUrl(), equalTo("/foo")); @@ -44,7 +48,7 @@ public void processFiltersRepeatedRequestsWhenNotRecordingScenarios() { } @Test - public void processRunsTransformers() { + public void processWithTransformerShouldTransformStubMappingRequestUrls() { SnapshotStubMappingTransformerRunner transformerRunner = new SnapshotStubMappingTransformerRunner(null) { @Override @@ -58,7 +62,7 @@ public StubMapping apply(StubMapping stubMapping) { final List actual = new SnapshotStubMappingPostProcessor(false, transformerRunner, null, null) - .process(TEST_STUB_MAPPINGS); + .process(testStubMappings); assertThat(actual, hasSize(2)); assertThat(actual.get(0).getRequest().getUrl(), equalTo("/foo/transformed")); @@ -66,13 +70,41 @@ public StubMapping apply(StubMapping stubMapping) { } @Test - public void processExtractsBodiesWhenMatched() { + public void + processWithRecordRepeatsAsScenariosAndTransformerShouldRunTransformerBeforeScenarioProcessor() { + SnapshotStubMappingTransformerRunner transformerRunner = + new SnapshotStubMappingTransformerRunner(null) { + @Override + public StubMapping apply(StubMapping stubMapping) { + // Return StubMapping with "/transformed" at the end of the original URL + String url = stubMapping.getRequest().getUrl(); + return new StubMapping( + newRequestPattern().withUrl(url + "/transformed").build(), ResponseDefinition.ok()); + } + }; + + final List actual = + new SnapshotStubMappingPostProcessor(true, transformerRunner, null, null) + .process(testStubMappings); + + assertThat(actual, hasSize(3)); + assertThat(actual.get(0).getRequest().getUrl(), equalTo("/foo/transformed")); + assertThat(actual.get(1).getRequest().getUrl(), equalTo("/bar/transformed")); + assertThat(actual.get(2).getRequest().getUrl(), equalTo("/foo/transformed")); + + assertTrue(actual.get(0).isInScenario()); + assertFalse(actual.get(1).isInScenario()); + assertTrue(actual.get(2).isInScenario()); + } + + @Test + public void processWithBodyExtractMatcherAndBodyExtractorShouldExtractsBodiesWhenMatched() { final ResponseDefinitionBodyMatcher bodyMatcher = new ResponseDefinitionBodyMatcher(0, 0) { @Override public MatchResult match(ResponseDefinition responseDefinition) { // Only match the second stub mapping - return responseDefinition == TEST_STUB_MAPPINGS.get(1).getResponse() + return responseDefinition == testStubMappings.get(1).getResponse() ? MatchResult.exactMatch() : MatchResult.noMatch(); } @@ -89,7 +121,7 @@ public void extractInPlace(StubMapping stubMapping) { final List actual = new SnapshotStubMappingPostProcessor( false, noopTransformerRunner(), bodyMatcher, bodyExtractor) - .process(TEST_STUB_MAPPINGS); + .process(testStubMappings); assertThat(actual, hasSize(2)); // Should've only modified second stub mapping diff --git a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunnerTest.java b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunnerTest.java index 15a8ace99c..0c613837ec 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunnerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/recording/SnapshotStubMappingTransformerRunnerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.extension.StubMappingTransformer; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.testsupport.GlobalStubMappingTransformer; import com.github.tomakehurst.wiremock.testsupport.NonGlobalStubMappingTransformer; -import com.google.common.collect.Lists; -import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.Test; public class SnapshotStubMappingTransformerRunnerTest { @@ -31,9 +29,7 @@ public class SnapshotStubMappingTransformerRunnerTest { @Test public void applyWithNoTransformers() { - StubMapping result = - new SnapshotStubMappingTransformerRunner(new ArrayList()) - .apply(stubMapping); + StubMapping result = new SnapshotStubMappingTransformerRunner(List.of()).apply(stubMapping); assertEquals(stubMapping, result); } @@ -42,8 +38,7 @@ public void applyWithNoTransformers() { public void applyWithUnregisteredNonGlobalTransformer() { // Should not apply the transformer as it isn't registered StubMapping result = - new SnapshotStubMappingTransformerRunner( - Lists.newArrayList(new NonGlobalStubMappingTransformer())) + new SnapshotStubMappingTransformerRunner(List.of(new NonGlobalStubMappingTransformer())) .apply(stubMapping); assertEquals(stubMapping, result); @@ -53,8 +48,8 @@ public void applyWithUnregisteredNonGlobalTransformer() { public void applyWithRegisteredNonGlobalTransformer() { StubMapping result = new SnapshotStubMappingTransformerRunner( - Lists.newArrayList(new NonGlobalStubMappingTransformer()), - Lists.newArrayList("nonglobal-transformer"), + List.of(new NonGlobalStubMappingTransformer()), + List.of("nonglobal-transformer"), null, null) .apply(stubMapping); @@ -65,8 +60,7 @@ public void applyWithRegisteredNonGlobalTransformer() { @Test public void applyWithGlobalTransformer() { StubMapping result = - new SnapshotStubMappingTransformerRunner( - Lists.newArrayList(new GlobalStubMappingTransformer())) + new SnapshotStubMappingTransformerRunner(List.of(new GlobalStubMappingTransformer())) .apply(stubMapping); assertEquals("/?transformed=global", result.getRequest().getUrl()); diff --git a/src/test/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptionsTest.java b/src/test/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptionsTest.java index 2758a2c509..0b7ef27142 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptionsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/standalone/CommandLineOptionsTest.java @@ -23,18 +23,22 @@ import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.github.tomakehurst.wiremock.client.BasicCredentials; -import com.github.tomakehurst.wiremock.common.*; +import com.github.tomakehurst.wiremock.common.ClasspathFileSource; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.common.Limit; +import com.github.tomakehurst.wiremock.common.NetworkAddressRules; +import com.github.tomakehurst.wiremock.common.ProxySettings; +import com.github.tomakehurst.wiremock.common.SingleRootFileSource; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings; import com.github.tomakehurst.wiremock.core.MappingsSaver; import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer; -import com.github.tomakehurst.wiremock.extension.StubLifecycleListener; -import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.*; import com.github.tomakehurst.wiremock.http.CaseInsensitiveKey; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; @@ -42,9 +46,8 @@ import com.github.tomakehurst.wiremock.matching.MatchResult; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; import com.github.tomakehurst.wiremock.security.Authenticator; -import com.google.common.base.Optional; import java.util.Collections; -import java.util.Map; +import java.util.Optional; import org.junit.jupiter.api.Test; public class CommandLineOptionsTest { @@ -374,41 +377,29 @@ public void preventsRecordingWhenRequestJournalDisabled() { }); } - @Test - public void returnsExtensionsSpecifiedAsClassNames() { - CommandLineOptions options = - new CommandLineOptions( - "--extensions", - "com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$ResponseDefinitionTransformerExt1,com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$ResponseDefinitionTransformerExt2,com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$RequestExt1"); - Map extensions = - options.extensionsOfType(ResponseDefinitionTransformer.class); - assertThat(extensions.entrySet(), hasSize(2)); - assertThat( - extensions.get("ResponseDefinitionTransformer_One"), - instanceOf(ResponseDefinitionTransformerExt1.class)); - assertThat( - extensions.get("ResponseDefinitionTransformer_Two"), - instanceOf(ResponseDefinitionTransformerExt2.class)); - } - @Test public void returnsRequestMatcherExtensionsSpecifiedAsClassNames() { CommandLineOptions options = new CommandLineOptions( "--extensions", "com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$RequestExt1,com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$ResponseDefinitionTransformerExt1"); - Map extensions = - options.extensionsOfType(RequestMatcherExtension.class); - assertThat(extensions.entrySet(), hasSize(1)); - assertThat(extensions.get("RequestMatcherExtension_One"), instanceOf(RequestExt1.class)); + + ExtensionDeclarations extensionDeclarations = options.getDeclaredExtensions(); + + assertThat(extensionDeclarations.getClassNames(), hasSize(2)); + assertThat( + extensionDeclarations.getClassNames(), + hasItems( + "com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$RequestExt1", + "com.github.tomakehurst.wiremock.standalone.CommandLineOptionsTest$ResponseDefinitionTransformerExt1")); } @Test public void returnsEmptySetForNoExtensionsSpecifiedAsClassNames() { CommandLineOptions options = new CommandLineOptions(); - Map extensions = - options.extensionsOfType(RequestMatcherExtension.class); - assertThat(extensions.entrySet(), hasSize(0)); + ExtensionDeclarations extensionDeclarations = options.getDeclaredExtensions(); + + assertThat(extensionDeclarations.getClassNames(), hasSize(0)); } @Test @@ -422,19 +413,24 @@ public void returnsAConsoleNotifyingListenerWhenOptionPresent() { @Test public void enablesGlobalResponseTemplating() { CommandLineOptions options = new CommandLineOptions("--global-response-templating"); - Map extensions = - options.extensionsOfType(ResponseTemplateTransformer.class); - assertThat(extensions.entrySet(), hasSize(1)); - assertThat(extensions.get("response-template").applyGlobally(), is(true)); + assertThat(options.getResponseTemplatingEnabled(), is(true)); + assertThat(options.getResponseTemplatingGlobal(), is(true)); } @Test public void enablesLocalResponseTemplating() { CommandLineOptions options = new CommandLineOptions("--local-response-templating"); - Map extensions = - options.extensionsOfType(ResponseTemplateTransformer.class); - assertThat(extensions.entrySet(), hasSize(1)); - assertThat(extensions.get("response-template").applyGlobally(), is(false)); + assertThat(options.getResponseTemplatingEnabled(), is(true)); + assertThat(options.getResponseTemplatingGlobal(), is(false)); + } + + @Test + public void configuresMaxTemplateCacheEntriesIfSpecified() { + CommandLineOptions options = + new CommandLineOptions("--global-response-templating", "--max-template-cache-entries", "5"); + + assertThat(options.getResponseTemplatingGlobal(), is(true)); + assertThat(options.getMaxTemplateCacheEntries(), is(5L)); } @Test @@ -496,38 +492,30 @@ public void setsDefaultNumberOfAsynchronousResponseThreads() { } @Test - public void configuresMaxTemplateCacheEntriesIfSpecified() { + public void configuresPermittedSystemKeysIfSpecified() { CommandLineOptions options = - new CommandLineOptions("--global-response-templating", "--max-template-cache-entries", "5"); - Map extensions = - options.extensionsOfType(ResponseTemplateTransformer.class); - ResponseTemplateTransformer transformer = extensions.get(ResponseTemplateTransformer.NAME); - - assertThat(transformer.getMaxCacheEntries(), is(5L)); + new CommandLineOptions( + "--global-response-templating", "--permitted-system-keys", "java*,path*"); + assertThat(options.getTemplatePermittedSystemKeys(), hasItems("java*", "path*")); } @Test - public void configuresMaxTemplateCacheEntriesToNullIfNotSpecified() { - CommandLineOptions options = new CommandLineOptions("--global-response-templating"); - Map extensions = - options.extensionsOfType(ResponseTemplateTransformer.class); - ResponseTemplateTransformer transformer = extensions.get(ResponseTemplateTransformer.NAME); - - assertThat(transformer.getMaxCacheEntries(), nullValue()); + public void configureFileTemplatesWithRightFormat() { + CommandLineOptions options = + new CommandLineOptions("--filename-template={{{method}}}-{{{path}}}-{{{id}}}.json"); + assertNotNull(options.getFilenameMaker()); } @Test - public void configuresPermittedSystemKeysIfSpecified() { - CommandLineOptions options = - new CommandLineOptions( - "--global-response-templating", "--permitted-system-keys", "java*,path*"); - assertThat(options.getPermittedSystemKeys(), hasItems("java*", "path*")); + public void configureFileTemplatesWithWrongFormat() { + assertThrows( + Exception.class, () -> new CommandLineOptions("--filename-template={{method}}}.json")); } @Test public void returnsEmptyPermittedKeysIfNotSpecified() { CommandLineOptions options = new CommandLineOptions("--global-response-templating"); - assertThat(options.getPermittedSystemKeys(), emptyCollectionOf(String.class)); + assertThat(options.getTemplatePermittedSystemKeys(), emptyCollectionOf(String.class)); } @Test @@ -679,20 +667,6 @@ public void toStringWithTrustProxyTarget() { assertThat(options, matchesMultiLine(".*trust-proxy-target: *localhost, example\\.com.*")); } - @Test - public void returnsTheSameInstanceOfTemplatingExtensionForEveryInterfaceImplemented() { - CommandLineOptions options = new CommandLineOptions("--local-response-templating"); - - Object one = - options.extensionsOfType(StubLifecycleListener.class).get(ResponseTemplateTransformer.NAME); - Object two = - options - .extensionsOfType(ResponseDefinitionTransformer.class) - .get(ResponseTemplateTransformer.NAME); - - assertSame(one, two); - } - @Test public void fileSourceDefaultsToSingleRootFileSource() { CommandLineOptions options = new CommandLineOptions(); @@ -769,7 +743,44 @@ void proxyTargetRules() { assertThat(proxyTargetRules.isAllowed("localhost"), is(false)); } + @Test + void proxyTimeout() { + CommandLineOptions options = new CommandLineOptions("--proxy-timeout", "5000"); + + int proxyTimeout = options.proxyTimeout(); + + assertThat(proxyTimeout, is(5000)); + } + + @Test + void defaultProxyTimeout() { + CommandLineOptions options = new CommandLineOptions(); + + int proxyTimeout = options.proxyTimeout(); + + assertThat(proxyTimeout, is(Options.DEFAULT_TIMEOUT)); + } + + @Test + void testProxyPassThroughOptionPassedAsFalse() { + CommandLineOptions options = new CommandLineOptions("--proxy-pass-through", "false"); + assertFalse(options.getStores().getSettingsStore().get().getProxyPassThrough()); + } + + @Test + void testProxyPassThroughOptionPassedAsTrue() { + CommandLineOptions options = new CommandLineOptions("--proxy-pass-through", "true"); + assertTrue(options.getStores().getSettingsStore().get().getProxyPassThrough()); + } + + @Test + void testProxyPassThroughOptionDefaultToTrue() { + CommandLineOptions options = new CommandLineOptions(); + assertTrue(options.getStores().getSettingsStore().get().getProxyPassThrough()); + } + public static class ResponseDefinitionTransformerExt1 extends ResponseDefinitionTransformer { + @Override public ResponseDefinition transform( Request request, @@ -786,6 +797,7 @@ public String getName() { } public static class ResponseDefinitionTransformerExt2 extends ResponseDefinitionTransformer { + @Override public ResponseDefinition transform( Request request, diff --git a/src/test/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSourceTest.java b/src/test/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSourceTest.java index 9cf188d2d5..ce117fc220 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSourceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,19 +18,24 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.testsupport.TestFiles.filePath; -import static com.google.common.base.Charsets.UTF_8; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import com.github.tomakehurst.wiremock.common.ClasspathFileSource; import com.github.tomakehurst.wiremock.common.NotWritableException; import com.github.tomakehurst.wiremock.common.SingleRootFileSource; +import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.stubbing.InMemoryStubMappings; +import com.github.tomakehurst.wiremock.stubbing.StoreBackedStubMappings; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.io.Files; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.List; import org.apache.commons.io.FileUtils; import org.hamcrest.Matchers; @@ -42,7 +47,7 @@ public class JsonFileMappingsSourceTest { @TempDir public File tempDir; - InMemoryStubMappings stubMappings; + StoreBackedStubMappings stubMappings; JsonFileMappingsSource source; File stubMappingFile; @@ -53,26 +58,32 @@ public void init() throws Exception { private void configureWithMultipleMappingFile() throws Exception { stubMappingFile = File.createTempFile("multi", ".json", tempDir); - Files.copy(new File(filePath("multi-stub/multi.json")), stubMappingFile); + Files.copy( + Paths.get(filePath("multi-stub/multi.json")), + stubMappingFile.toPath(), + StandardCopyOption.REPLACE_EXISTING); load(); } private void configureWithSingleMappingFile() throws Exception { stubMappingFile = File.createTempFile("single", ".json", tempDir); - Files.copy(new File(filePath("multi-stub/single.json")), stubMappingFile); + Files.copy( + Paths.get(filePath("multi-stub/single.json")), + stubMappingFile.toPath(), + StandardCopyOption.REPLACE_EXISTING); load(); } private void load() { - source = new JsonFileMappingsSource(new SingleRootFileSource(tempDir)); + source = new JsonFileMappingsSource(new SingleRootFileSource(tempDir), new FilenameMaker()); source.loadMappingsInto(stubMappings); } @Test public void loadsMappingsViaClasspathFileSource() { ClasspathFileSource fileSource = new ClasspathFileSource("jar-filesource"); - JsonFileMappingsSource source = new JsonFileMappingsSource(fileSource); - InMemoryStubMappings stubMappings = new InMemoryStubMappings(); + JsonFileMappingsSource source = new JsonFileMappingsSource(fileSource, new FilenameMaker()); + StoreBackedStubMappings stubMappings = new InMemoryStubMappings(); source.loadMappingsInto(stubMappings); @@ -86,7 +97,8 @@ public void loadsMappingsViaClasspathFileSource() { @Test public void stubMappingFilesAreWrittenWithInsertionIndex() throws Exception { - JsonFileMappingsSource source = new JsonFileMappingsSource(new SingleRootFileSource(tempDir)); + JsonFileMappingsSource source = + new JsonFileMappingsSource(new SingleRootFileSource(tempDir), new FilenameMaker()); StubMapping stub = get("/saveable").willReturn(ok()).build(); source.save(stub); @@ -97,6 +109,21 @@ public void stubMappingFilesAreWrittenWithInsertionIndex() throws Exception { assertThat(savedStub, containsString("\"insertionIndex\" : 0")); } + @Test + public void stubMappingFilesWithOwnFileTemplateFormat() { + JsonFileMappingsSource source = + new JsonFileMappingsSource( + new SingleRootFileSource(tempDir), + new FilenameMaker("{{{request.method}}}-{{{request.url}}}.json")); + + StubMapping stub = get("/saveable").willReturn(ok()).build(); + source.save(stub); + + File savedFile = tempDir.listFiles()[0]; + + assertEquals(savedFile.getName(), "get-saveable.json"); + } + @Test public void refusesToRemoveStubMappingContainedInMultiFile() throws Exception { configureWithMultipleMappingFile(); diff --git a/src/test/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSourceTest.java b/src/test/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSourceTest.java new file mode 100644 index 0000000000..8176220f3e --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/store/files/BlobStoreFileSourceTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022-2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.store.files; + +import static com.github.tomakehurst.wiremock.testsupport.TestFiles.filePath; +import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.fileNamed; +import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.hasExactlyIgnoringOrder; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import com.github.tomakehurst.wiremock.common.TextFile; +import com.github.tomakehurst.wiremock.store.BlobStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class BlobStoreFileSourceTest { + + static final String ROOT_PATH = filePath("filesource"); + + BlobStoreFileSource fileSource; + + @BeforeEach + void setup() { + BlobStore blobStore = new FileSourceBlobStore(ROOT_PATH); + fileSource = new BlobStoreFileSource(blobStore); + } + + @SuppressWarnings("unchecked") + @Test + void list_all_files() { + List files = fileSource.listFilesRecursively(); + + assertThat( + files, + hasExactlyIgnoringOrder( + fileNamed("one"), + fileNamed("two"), + fileNamed("three"), + fileNamed("four"), + fileNamed("five"), + fileNamed("six"), + fileNamed("seven"), + fileNamed("eight"), + fileNamed("deepfile.json"))); + } + + @Test + void get_single_file_bytes() { + byte[] expected = "{}".getBytes(); + assertThat(fileSource.getBinaryFileNamed("subdir/deepfile.json").readContents(), is(expected)); + } + + @Test + void get_single_stream() throws Exception { + byte[] expected = "{}".getBytes(); + byte[] actual = + fileSource.getBinaryFileNamed("subdir/deepfile.json").getStream().readAllBytes(); + assertThat(actual, is(expected)); + } + + @Test + void write_binary_file(@TempDir Path tempDir) throws Exception { + BlobStore blobStore = new FileSourceBlobStore(tempDir.toString()); + fileSource = new BlobStoreFileSource(blobStore); + + byte[] contents = "{}".getBytes(); + fileSource.writeBinaryFile("folder/file.json", contents); + + byte[] actual = Files.readAllBytes(tempDir.resolve("folder/file.json")); + assertThat(actual, is(contents)); + } + + @Test + void write_text_file(@TempDir Path tempDir) throws Exception { + BlobStore blobStore = new FileSourceBlobStore(tempDir.toString()); + fileSource = new BlobStoreFileSource(blobStore); + + String contents = "{}"; + fileSource.writeTextFile("folder/text-file.json", contents); + + String actual = new String(Files.readAllBytes(tempDir.resolve("folder/text-file.json"))); + assertThat(actual, is(contents)); + } + + @Test + void delete_file(@TempDir Path tempDir) { + BlobStore blobStore = new FileSourceBlobStore(tempDir.toString()); + fileSource = new BlobStoreFileSource(blobStore); + + String filePath = "folder/tmp-file.json"; + fileSource.writeTextFile(filePath, "{}"); + assertThat(tempDir.resolve(filePath).toFile().exists(), is(true)); + + fileSource.deleteFile(filePath); + assertThat(tempDir.resolve(filePath).toFile().exists(), is(false)); + } + + @Test + void delete_all_files(@TempDir Path tempDir) { + BlobStore blobStore = new FileSourceBlobStore(tempDir.toString()); + fileSource = new BlobStoreFileSource(blobStore); + + String filePath1 = "folder/tmp-file.json"; + String filePath2 = "root-tmp-file.json"; + fileSource.writeTextFile(filePath1, "{}"); + fileSource.writeTextFile(filePath2, "{}"); + + assertThat(tempDir.resolve(filePath1).toFile().exists(), is(true)); + assertThat(tempDir.resolve(filePath2).toFile().exists(), is(true)); + + blobStore.clear(); + assertThat(tempDir.resolve(filePath1).toFile().exists(), is(false)); + assertThat(tempDir.resolve(filePath2).toFile().exists(), is(false)); + } + + @Test + void text_file_path() { + // fileSource.getPath() + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/AdminRequestHandlerTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/AdminRequestHandlerTest.java index 7dc50b71b3..1e31908686 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/AdminRequestHandlerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/AdminRequestHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,12 +57,13 @@ public void init() { handler = new AdminRequestHandler( - AdminRoutes.defaults(), + AdminRoutes.forClient(), admin, new BasicResponseRenderer(), new NoAuthenticator(), false, Collections.emptyList(), + Collections.emptyList(), new DataTruncationSettings(Limit.UNLIMITED)); } @@ -70,7 +71,7 @@ public void init() { public void shouldSaveMappingsWhenSaveCalled() { Request request = aRequest().withUrl("/mappings/save").withMethod(POST).build(); - handler.handle(request, httpResponder); + handler.handle(request, httpResponder, null); Response response = httpResponder.response; assertThat(response.getStatus(), is(HTTP_OK)); @@ -81,7 +82,7 @@ public void shouldSaveMappingsWhenSaveCalled() { public void shouldClearMappingsJournalAndRequestDelayWhenResetCalled() { Request request = aRequest().withUrl("/reset").withMethod(POST).build(); - handler.handle(request, httpResponder); + handler.handle(request, httpResponder, null); Response response = httpResponder.response; assertThat(response.getStatus(), is(HTTP_OK)); @@ -90,9 +91,9 @@ public void shouldClearMappingsJournalAndRequestDelayWhenResetCalled() { @Test public void shouldClearJournalWhenResetRequestsCalled() { - Request request = aRequest().withUrl("/requests/reset").withMethod(POST).build(); + Request request = aRequest().withUrl("/requests").withMethod(DELETE).build(); - handler.handle(request, httpResponder); + handler.handle(request, httpResponder, null); Response response = httpResponder.response; assertThat(response.getStatus(), is(HTTP_OK)); @@ -117,7 +118,8 @@ public void shouldReturnCountOfMatchingRequests() { .withMethod(POST) .withBody(REQUEST_PATTERN_SAMPLE) .build(), - httpResponder); + httpResponder, + null); Response response = httpResponder.response; assertThat(response.getStatus(), is(HTTP_OK)); @@ -133,7 +135,8 @@ public void shouldReturnCountOfMatchingRequests() { public void shouldUpdateGlobalSettings() { handler.handle( aRequest().withUrl("/settings").withMethod(POST).withBody(GLOBAL_SETTINGS_JSON).build(), - httpResponder); + httpResponder, + null); GlobalSettings expectedSettings = GlobalSettings.builder().fixedDelay(2000).build(); verify(admin).updateGlobalSettings(expectedSettings); diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryMappingsTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryMappingsTest.java index 643b11cc5b..c36b773a5c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryMappingsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryMappingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ public class InMemoryMappingsTest { - private InMemoryStubMappings mappings; + private StoreBackedStubMappings mappings; @BeforeEach public void init() { @@ -56,7 +56,7 @@ public void correctlyAcceptsMappingAndReturnsCorrespondingResponse() { new ResponseDefinition(204, ""))); Request request = aRequest().withMethod(PUT).withUrl("/some/resource").build(); - ResponseDefinition response = mappings.serveFor(request).getResponseDefinition(); + ResponseDefinition response = mappings.serveFor(ServeEvent.of(request)).getResponseDefinition(); assertThat(response.getStatus(), is(204)); } @@ -69,7 +69,7 @@ public void returnsNotFoundWhenMethodIncorrect() { new ResponseDefinition(204, ""))); Request request = aRequest().withMethod(POST).withUrl("/some/resource").build(); - ResponseDefinition response = mappings.serveFor(request).getResponseDefinition(); + ResponseDefinition response = mappings.serveFor(ServeEvent.of(request)).getResponseDefinition(); assertThat(response.getStatus(), is(HTTP_NOT_FOUND)); } @@ -82,7 +82,7 @@ public void returnsNotFoundWhenUrlIncorrect() { new ResponseDefinition(204, ""))); Request request = aRequest().withMethod(PUT).withUrl("/some/bad/resource").build(); - ResponseDefinition response = mappings.serveFor(request).getResponseDefinition(); + ResponseDefinition response = mappings.serveFor(ServeEvent.of(request)).getResponseDefinition(); assertThat(response.getStatus(), is(HTTP_NOT_FOUND)); } @@ -90,7 +90,7 @@ public void returnsNotFoundWhenUrlIncorrect() { @Test public void returnsNotConfiguredResponseForUnmappedRequest() { Request request = aRequest().withMethod(OPTIONS).withUrl("/not/mapped").build(); - ResponseDefinition response = mappings.serveFor(request).getResponseDefinition(); + ResponseDefinition response = mappings.serveFor(ServeEvent.of(request)).getResponseDefinition(); assertThat(response.getStatus(), is(HTTP_NOT_FOUND)); assertThat(response.wasConfigured(), is(false)); } @@ -109,7 +109,8 @@ public void returnsMostRecentlyInsertedResponseIfTwoOrMoreMatch() { ResponseDefinition response = mappings - .serveFor(aRequest().withMethod(GET).withUrl("/duplicated/resource").build()) + .serveFor( + ServeEvent.of(aRequest().withMethod(GET).withUrl("/duplicated/resource").build())) .getResponseDefinition(); assertThat(response.getStatus(), is(201)); @@ -148,10 +149,12 @@ public void returnsMappingInScenarioOnlyWhenStateIsCorrect() { Request secondGet = aRequest("secondGet").withMethod(GET).withUrl("/scenario/resource").build(); assertThat( - mappings.serveFor(firstGet).getResponseDefinition().getBody(), is("Initial content")); - mappings.serveFor(put); + mappings.serveFor(ServeEvent.of(firstGet)).getResponseDefinition().getBody(), + is("Initial content")); + mappings.serveFor(ServeEvent.of(put)); assertThat( - mappings.serveFor(secondGet).getResponseDefinition().getBody(), is("Modified content")); + mappings.serveFor(ServeEvent.of(secondGet)).getResponseDefinition().getBody(), + is("Modified content")); } @Test @@ -166,7 +169,8 @@ public void returnsMappingInScenarioWithNoRequiredState() { Request request = aRequest().withMethod(GET).withUrl("/scenario/resource").build(); assertThat( - mappings.serveFor(request).getResponseDefinition().getBody(), is("Expected content")); + mappings.serveFor(ServeEvent.of(request)).getResponseDefinition().getBody(), + is("Expected content")); } @Test @@ -189,14 +193,19 @@ public void supportsResetOfAllScenariosState() { mappings.addMapping(putMapping); mappings.serveFor( - aRequest("put /scenario/resource").withMethod(PUT).withUrl("/scenario/resource").build()); + ServeEvent.of( + aRequest("put /scenario/resource") + .withMethod(PUT) + .withUrl("/scenario/resource") + .build())); ResponseDefinition response = mappings .serveFor( - aRequest("1st get /scenario/resource") - .withMethod(GET) - .withUrl("/scenario/resource") - .build()) + ServeEvent.of( + aRequest("1st get /scenario/resource") + .withMethod(GET) + .withUrl("/scenario/resource") + .build())) .getResponseDefinition(); assertThat(response.wasConfigured(), is(false)); @@ -205,10 +214,11 @@ public void supportsResetOfAllScenariosState() { response = mappings .serveFor( - aRequest("2nd get /scenario/resource") - .withMethod(GET) - .withUrl("/scenario/resource") - .build()) + ServeEvent.of( + aRequest("2nd get /scenario/resource") + .withMethod(GET) + .withUrl("/scenario/resource") + .build())) .getResponseDefinition(); assertThat(response.getBody(), is("Desired content")); } @@ -225,9 +235,10 @@ public void scenariosShouldBeResetWhenMappingsAreReset() { mappings.addMapping(secondMapping); Request request = aRequest().withMethod(POST).withUrl("/scenario/resource").build(); - mappings.serveFor(request); + mappings.serveFor(ServeEvent.of(request)); assertThat( - mappings.serveFor(request).getResponseDefinition().getBody(), is("Modified content")); + mappings.serveFor(ServeEvent.of(request)).getResponseDefinition().getBody(), + is("Modified content")); mappings.reset(); @@ -236,7 +247,8 @@ public void scenariosShouldBeResetWhenMappingsAreReset() { mappings.addMapping(thirdMapping); assertThat( - mappings.serveFor(request).getResponseDefinition().getBody(), is("Starting content")); + mappings.serveFor(ServeEvent.of(request)).getResponseDefinition().getBody(), + is("Starting content")); } private StubMapping aBasicMappingInScenario(String body) { diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java index da53d75022..92052b58da 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java @@ -32,7 +32,7 @@ public class InMemoryStubMappingsTest { - private InMemoryStubMappings inMemoryStubMappings; + private StoreBackedStubMappings inMemoryStubMappings; @BeforeEach public void setUp() throws Exception { diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ResponseDefinitionTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ResponseDefinitionTest.java index 55fb00dbdd..60e01db259 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ResponseDefinitionTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ResponseDefinitionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import com.github.tomakehurst.wiremock.http.Fault; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; @@ -53,7 +53,7 @@ public void copyProducesEqualObject() { "http://base.com", null, Fault.EMPTY_RESPONSE, - ImmutableList.of("transformer-1"), + List.of("transformer-1"), Parameters.one("name", "Jeff"), true); diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java index 7da42f49ed..624c4a826d 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ public class ScenariosTest { @BeforeEach public void init() { - scenarios = new Scenarios(); + scenarios = new InMemoryScenarios(); } @Test @@ -163,6 +163,26 @@ public void removesScenarioCompletelyWhenNoMoreMappingsReferToItDueToNameChange( assertThat(scenarios.getByName("one"), nullValue()); } + @Test + public void stubMappingCanStopBeingInScenario() { + StubMapping oldMapping = + get("/scenarios/1") + .inScenario("one") + .whenScenarioStateIs(STARTED) + .willSetStateTo("step_2") + .willReturn(ok()) + .build(); + scenarios.onStubMappingAdded(oldMapping); + + assertThat(scenarios.getByName("one"), notNullValue()); + + StubMapping newMapping = get("/scenarios/1").willReturn(ok()).build(); + + scenarios.onStubMappingUpdated(oldMapping, newMapping); + + assertThat(scenarios.getByName("one"), nullValue()); + } + @Test public void modifiesScenarioStateWhenStubServed() { StubMapping mapping1 = diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ServeEventFactory.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ServeEventFactory.java new file mode 100644 index 0000000000..637f9a602e --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ServeEventFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.stubbing; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.verification.LoggedRequest; + +public class ServeEventFactory { + + public static ServeEvent newPostMatchServeEvent( + Request request, ResponseDefinitionBuilder responseDefinitionBuilder) { + StubMapping stubMapping = + WireMock.any(WireMock.anyUrl()).willReturn(responseDefinitionBuilder).build(); + return newPostMatchServeEvent(request, responseDefinitionBuilder, stubMapping); + } + + public static ServeEvent newPostMatchServeEvent( + Request request, ResponseDefinition responseDefinition) { + StubMapping stubMapping = WireMock.any(WireMock.anyUrl()).build(); + stubMapping.setResponse(responseDefinition); + return newPostMatchServeEvent(request, responseDefinition, stubMapping); + } + + public static ServeEvent newPostMatchServeEvent( + Request request, + ResponseDefinitionBuilder responseDefinitionBuilder, + StubMapping stubMapping) { + return newPostMatchServeEvent(request, responseDefinitionBuilder.build(), stubMapping); + } + + public static ServeEvent newPostMatchServeEvent( + Request request, ResponseDefinition responseDefinition, StubMapping stubMapping) { + return new ServeEvent(LoggedRequest.createFrom(request), stubMapping, responseDefinition); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorderTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorderTest.java index e797f78305..69e39ab919 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorderTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,9 +21,8 @@ import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; import static com.github.tomakehurst.wiremock.http.RequestMethod.POST; import static com.github.tomakehurst.wiremock.http.Response.response; -import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.equalToJson; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Lists.transform; +import static com.github.tomakehurst.wiremock.testsupport.WireMatchers.*; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; import static org.mockito.ArgumentMatchers.any; @@ -32,42 +31,53 @@ import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.skyscreamer.jsonassert.JSONCompareMode.STRICT_ORDER; -import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.common.IdGenerator; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.http.*; import com.github.tomakehurst.wiremock.matching.MockMultipart; import com.github.tomakehurst.wiremock.matching.RequestPattern; +import com.github.tomakehurst.wiremock.store.BlobStore; import com.github.tomakehurst.wiremock.testsupport.MockRequestBuilder; import com.github.tomakehurst.wiremock.verification.VerificationResult; import java.util.*; +import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONCompareMode; +/** + * @deprecated this test is for the legacy recorder which will be removed before 3.x is out of beta + */ +@Deprecated public class StubMappingJsonRecorderTest { private StubMappingJsonRecorder listener; - private FileSource mappingsFileSource; - private FileSource filesFileSource; + + private BlobStore mappingsBlobStore; + private BlobStore filesBlobStore; + private Admin admin; @BeforeEach public void init() { - mappingsFileSource = mock(FileSource.class, "mappingsFileSource"); - filesFileSource = mock(FileSource.class, "filesFileSource"); + mappingsBlobStore = mock(BlobStore.class, "mappingsBlobStore"); + filesBlobStore = mock(BlobStore.class, "filesBlobStore"); + admin = mock(Admin.class); - constructRecordingListener(Collections.emptyList()); + constructRecordingListener(Collections.emptyList()); } private void constructRecordingListener(List headersToRecord) { listener = new StubMappingJsonRecorder( - mappingsFileSource, - filesFileSource, + mappingsBlobStore, + filesBlobStore, admin, - transform(headersToRecord, TO_CASE_INSENSITIVE_KEYS)); + headersToRecord.stream() + .map(TO_CASE_INSENSITIVE_KEYS) + .collect(Collectors.toUnmodifiableList())); listener.setIdGenerator(fixedIdGenerator("1$2!3")); } @@ -96,12 +106,12 @@ public void writesMappingFileAndCorrespondingBodyFileOnRequest() { listener.requestReceived(request, response); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( (eq("mapping-recorded-content-1$2!3.json")), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING, STRICT_ORDER))); - verify(filesFileSource) - .writeBinaryFile( + argThat(equalToBinaryJson(SAMPLE_REQUEST_MAPPING, STRICT_ORDER))); + verify(filesBlobStore) + .put( (eq("body-recorded-content-1$2!3.txt")), (eq("Recorded body content".getBytes(UTF_8)))); } @@ -142,13 +152,12 @@ public void addsResponseHeaders() { listener.requestReceived(request, response); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( (eq("mapping-headered-content-1$2!3.json")), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING_WITH_HEADERS, STRICT_ORDER))); - verify(filesFileSource) - .writeBinaryFile( - "body-headered-content-1$2!3.txt", "Recorded body content".getBytes(UTF_8)); + argThat(equalToBinaryJson(SAMPLE_REQUEST_MAPPING_WITH_HEADERS, STRICT_ORDER))); + verify(filesBlobStore) + .put("body-headered-content-1$2!3.txt", "Recorded body content".getBytes(UTF_8)); } @Test @@ -160,8 +169,8 @@ public void doesNotWriteFileIfRequestAlreadyReceived() { new MockRequestBuilder().withMethod(RequestMethod.GET).withUrl("/headered/content").build(), response().fromProxy(true).status(200).build()); - verifyNoInteractions(mappingsFileSource); - verifyNoInteractions(filesFileSource); + verifyNoInteractions(mappingsBlobStore); + verifyNoInteractions(mappingsBlobStore); } @Test @@ -175,8 +184,8 @@ public void doesNotWriteFileIfResponseNotFromProxy() { new MockRequestBuilder().withMethod(RequestMethod.GET).withUrl("/headered/content").build(), response); - verifyNoInteractions(mappingsFileSource); - verifyNoInteractions(filesFileSource); + verifyNoInteractions(mappingsBlobStore); + verifyNoInteractions(filesBlobStore); } private static final String SAMPLE_REQUEST_MAPPING_WITH_BODY = @@ -210,10 +219,10 @@ public void includesBodyInRequestPatternIfInRequest() { listener.requestReceived( request, response().status(200).body("anything").fromProxy(true).build()); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( (any(String.class)), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING_WITH_BODY, STRICT_ORDER))); + argThat(equalToBinaryJson(SAMPLE_REQUEST_MAPPING_WITH_BODY, STRICT_ORDER))); } private static final String SAMPLE_REQUEST_MAPPING_WITH_REQUEST_HEADERS_1 = @@ -249,7 +258,7 @@ public void includesBodyInRequestPatternIfInRequest() { + "} "; private static final List MATCHING_REQUEST_HEADERS = - new ArrayList(Arrays.asList("Accept")); + new ArrayList<>(Collections.singletonList("Accept")); @Test public void includesHeadersInRequestPatternIfHeaderMatchingEnabled() { @@ -275,14 +284,16 @@ public void includesHeadersInRequestPatternIfHeaderMatchingEnabled() { listener.requestReceived(request1, response().status(200).fromProxy(true).build()); listener.requestReceived(request2, response().status(200).fromProxy(true).build()); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( (any(String.class)), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING_WITH_REQUEST_HEADERS_1, STRICT_ORDER))); - verify(mappingsFileSource) - .writeTextFile( + argThat( + equalToBinaryJson(SAMPLE_REQUEST_MAPPING_WITH_REQUEST_HEADERS_1, STRICT_ORDER))); + verify(mappingsBlobStore) + .put( (any(String.class)), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING_WITH_REQUEST_HEADERS_2, STRICT_ORDER))); + argThat( + equalToBinaryJson(SAMPLE_REQUEST_MAPPING_WITH_REQUEST_HEADERS_2, STRICT_ORDER))); } private static final String SAMPLE_REQUEST_MAPPING_WITH_JSON_BODY = @@ -317,10 +328,10 @@ public void matchesBodyOnEqualToJsonIfJsonInRequestContentTypeHeader() { listener.requestReceived( request, response().status(200).body("anything").fromProxy(true).build()); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( (any(String.class)), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING_WITH_JSON_BODY, STRICT_ORDER))); + argThat(equalToBinaryJson(SAMPLE_REQUEST_MAPPING_WITH_JSON_BODY, STRICT_ORDER))); } private static final String SAMPLE_REQUEST_MAPPING_WITH_XML_BODY = @@ -353,10 +364,10 @@ public void matchesBodyOnEqualToXmlIfXmlInRequestContentTypeHeader() { listener.requestReceived( request, response().status(200).body("anything").fromProxy(true).build()); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( (any(String.class)), - argThat(equalToJson(SAMPLE_REQUEST_MAPPING_WITH_XML_BODY, STRICT_ORDER))); + argThat(equalToBinaryJson(SAMPLE_REQUEST_MAPPING_WITH_XML_BODY, STRICT_ORDER))); } private static final String GZIP_REQUEST_MAPPING = @@ -397,12 +408,12 @@ public void decompressesGzippedResponseBodyAndRemovesContentEncodingHeader() { listener.requestReceived(request, response); - verify(mappingsFileSource) - .writeTextFile( - (eq("mapping-gzipped-content-1$2!3.json")), argThat(equalToJson(GZIP_REQUEST_MAPPING))); - verify(filesFileSource) - .writeBinaryFile( - (eq("body-gzipped-content-1$2!3.txt")), (eq("Recorded body content".getBytes(UTF_8)))); + verify(mappingsBlobStore) + .put( + (eq("mapping-gzipped-content-1$2!3.json")), + argThat(equalToBinaryJson(GZIP_REQUEST_MAPPING, JSONCompareMode.STRICT))); + verify(filesBlobStore) + .put((eq("body-gzipped-content-1$2!3.txt")), (eq("Recorded body content".getBytes(UTF_8)))); } private static final String MULTIPART_REQUEST_MAPPING = @@ -478,44 +489,82 @@ public void multipartRequestProcessing() { listener.requestReceived( request, response().status(200).body("anything").fromProxy(true).build()); - verify(mappingsFileSource) - .writeTextFile( + verify(mappingsBlobStore) + .put( eq("mapping-multipart-content-1$2!3.json"), - argThat(equalToJson(MULTIPART_REQUEST_MAPPING, STRICT_ORDER))); + argThat(equalToBinaryJson(MULTIPART_REQUEST_MAPPING, STRICT_ORDER))); } + private static final String BAD_MULTIPART_REQUEST_MAPPING = + "{ \n" + + " \"id\": \"41544750-0c69-3fd7-93b1-f79499f987c3\", \n" + + " \"uuid\": \"41544750-0c69-3fd7-93b1-f79499f987c3\", \n" + + " \"request\": { \n" + + " \"url\": \"/multipart/content\", \n" + + " \"method\": \"POST\" \n" + + " }, \n" + + " \"response\": { \n" + + " \"status\": 200, \n" + + " \"bodyFileName\": \"body-multipart-content-1$2!3.txt\" \n" + + " } \n" + + "}"; + @Test - public void detectsJsonExtensionFromFileExtension() throws Exception { + public void multipartRequestProcessingWithNonFileMultipart() { + when(admin.countRequestsMatching((any(RequestPattern.class)))) + .thenReturn(VerificationResult.withCount(0)); + + Request request = + new MockRequestBuilder() + .withMethod(RequestMethod.POST) + .withHeader("Content-Type", "multipart/form-data; boundary=aBoundary") + .withUrl("/multipart/content") + .withBody("--aBoundary\r\nContent") + .withMultiparts(null) // this is what request.getParts returns when parsing doesn't work + .build(); + doReturn(true).when(request).isMultipart(); + + listener.requestReceived( + request, response().status(200).body("anything").fromProxy(true).build()); + + verify(mappingsBlobStore) + .put( + eq("mapping-multipart-content-1$2!3.json"), + argThat(bytesEqualToJson(BAD_MULTIPART_REQUEST_MAPPING, STRICT_ORDER))); + } + + @Test + public void detectsJsonExtensionFromFileExtension() { assertResultingFileExtension("/my/file.json", "json"); } @Test - public void detectsGifExtensionFromFileExtension() throws Exception { + public void detectsGifExtensionFromFileExtension() { assertResultingFileExtension("/my/file.gif", "gif"); } @Test - public void detectsXmlExtensionFromResponseContentTypeHeader() throws Exception { + public void detectsXmlExtensionFromResponseContentTypeHeader() { assertResultingFileExtension("/noext", "xml", "application/xml"); } @Test - public void detectsJsonExtensionFromResponseContentTypeHeader() throws Exception { + public void detectsJsonExtensionFromResponseContentTypeHeader() { assertResultingFileExtension("/noext", "json", "application/json"); } @Test - public void detectsJsonExtensionFromCustomResponseContentTypeHeader() throws Exception { + public void detectsJsonExtensionFromCustomResponseContentTypeHeader() { assertResultingFileExtension("/noext", "json", "application/vnd.api+json"); } @Test - public void detectsJpegExtensionFromResponseContentTypeHeader() throws Exception { + public void detectsJpegExtensionFromResponseContentTypeHeader() { assertResultingFileExtension("/noext", "jpeg", "image/jpeg"); } @Test - public void detectsIcoExtensionFromResponseContentTypeHeader() throws Exception { + public void detectsIcoExtensionFromResponseContentTypeHeader() { assertResultingFileExtension("/noext", "ico", "image/x-icon"); } @@ -536,18 +585,16 @@ public void sanitisesFilenamesBySwappingSymbolsForUnderscores() { listener.requestReceived(request, response); - verify(filesFileSource) - .writeBinaryFile( - argThat(containsString("body-my_oddly__named_file-url")), (any(byte[].class))); + verify(filesBlobStore) + .put(argThat(containsString("body-my_oddly__named_file-url")), (any(byte[].class))); } - private void assertResultingFileExtension(String url, final String expectedExension) - throws Exception { + private void assertResultingFileExtension(String url, final String expectedExension) { assertResultingFileExtension(url, expectedExension, null); } private void assertResultingFileExtension( - String url, final String expectedExension, String contentTypeHeader) throws Exception { + String url, final String expectedExension, String contentTypeHeader) { when(admin.countRequestsMatching((any(RequestPattern.class)))) .thenReturn(VerificationResult.withCount(0)); Request request = new MockRequestBuilder().withMethod(RequestMethod.GET).withUrl(url).build(); @@ -565,16 +612,11 @@ private void assertResultingFileExtension( listener.requestReceived(request, response); - verify(filesFileSource) - .writeBinaryFile(argThat(endsWith("." + expectedExension)), (any(byte[].class))); + verify(filesBlobStore).put(argThat(endsWith("." + expectedExension)), (any(byte[].class))); } private IdGenerator fixedIdGenerator(final String id) { - return new IdGenerator() { - public String generate() { - return id; - } - }; + return () -> id; } private static Request.Part createPart( diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubRequestHandlerTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubRequestHandlerTest.java deleted file mode 100644 index c4f3e47dbc..0000000000 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/StubRequestHandlerTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2011-2022 Thomas Akehurst - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.github.tomakehurst.wiremock.stubbing; - -import static com.github.tomakehurst.wiremock.common.DataTruncationSettings.NO_TRUNCATION; -import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; -import static com.github.tomakehurst.wiremock.http.Response.response; -import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; -import static com.github.tomakehurst.wiremock.testsupport.MockRequestBuilder.aRequest; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.core.StubServer; -import com.github.tomakehurst.wiremock.extension.PostServeAction; -import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; -import com.github.tomakehurst.wiremock.http.*; -import com.github.tomakehurst.wiremock.testsupport.MockHttpResponder; -import com.github.tomakehurst.wiremock.testsupport.TestNotifier; -import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.github.tomakehurst.wiremock.verification.RequestJournal; -import java.util.Collections; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class StubRequestHandlerTest { - - private StubServer stubServer; - private ResponseRenderer responseRenderer; - private MockHttpResponder httpResponder; - private Admin admin; - private RequestJournal requestJournal; - - private StubRequestHandler requestHandler; - - @BeforeEach - public void init() { - stubServer = mock(StubServer.class); - responseRenderer = mock(ResponseRenderer.class); - httpResponder = new MockHttpResponder(); - admin = mock(Admin.class); - requestJournal = mock(RequestJournal.class); - - requestHandler = - new StubRequestHandler( - stubServer, - responseRenderer, - admin, - Collections.emptyMap(), - requestJournal, - Collections.emptyList(), - false, - NO_TRUNCATION); - } - - @Test - public void returnsResponseIndicatedByMappings() { - when(stubServer.serveStubFor(any(Request.class))) - .thenReturn( - ServeEvent.of( - mockRequest().protocol("HTTP/2").asLoggedRequest(), - new ResponseDefinition(200, "Body content"))); - - Response mockResponse = - response() - .status(200) - .body("Body content") - .headers( - new HttpHeaders( - new HttpHeader("Content-Type", "application/json"), - new HttpHeader("Matched-Stub-Id", "123"))) - .build(); - when(responseRenderer.render(any(ServeEvent.class))).thenReturn(mockResponse); - - Request request = - aRequest().withUrl("/the/required/resource").withMethod(GET).withProtocol("HTTP/2").build(); - requestHandler.handle(request, httpResponder); - Response response = httpResponder.response; - - assertThat(response.getStatus(), is(200)); - assertThat(response.getBodyAsString(), is("Body content")); - assertThat( - response.toString(), - is("HTTP/2 200\nContent-Type: [application/json]\nMatched-Stub-Id: [123]\n")); - } - - @Test - public void shouldNotifyListenersOnRequest() { - final Request request = aRequest().build(); - final RequestListener listener = mock(RequestListener.class); - requestHandler.addRequestListener(listener); - - doReturn(ServeEvent.of(LoggedRequest.createFrom(request), ResponseDefinition.notConfigured())) - .when(stubServer) - .serveStubFor(request); - when(responseRenderer.render(any(ServeEvent.class))).thenReturn(new Response.Builder().build()); - - requestHandler.handle(request, httpResponder); - verify(listener).requestReceived(eq(request), any(Response.class)); - } - - @Test - public void shouldLogInfoOnRequest() { - final Request request = aRequest().withUrl("/").withMethod(GET).withClientIp("1.2.3.5").build(); - - doReturn(ServeEvent.forUnmatchedRequest(LoggedRequest.createFrom(request))) - .when(stubServer) - .serveStubFor(request); - when(responseRenderer.render(any(ServeEvent.class))).thenReturn(new Response.Builder().build()); - - TestNotifier notifier = TestNotifier.createAndSet(); - - requestHandler.handle(request, httpResponder); - notifier.revert(); - - assertThat(notifier.getErrorMessages().isEmpty(), is(true)); - assertThat(notifier.getInfoMessages().size(), is(1)); - assertThat(notifier.getInfoMessages().get(0), containsString("1.2.3.5 - GET /")); - } -} diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/ExtensionFactoryUtils.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/ExtensionFactoryUtils.java new file mode 100644 index 0000000000..013f229ec9 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/ExtensionFactoryUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.testsupport; + +import static java.util.Collections.emptyList; + +import com.github.jknack.handlebars.Helper; +import com.github.tomakehurst.wiremock.common.ClasspathFileSource; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.extension.ExtensionFactory; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; +import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformerTest; +import java.util.List; +import java.util.Map; + +public class ExtensionFactoryUtils { + + public static ResponseTemplateTransformer buildTemplateTransformer(boolean global) { + return (ResponseTemplateTransformer) + buildExtension( + new MockWireMockServices(), + services -> + List.of( + new ResponseTemplateTransformer( + services.getTemplateEngine(), global, services.getFiles(), emptyList()))); + } + + public static ResponseTemplateTransformer buildTemplateTransformer( + boolean global, String helperName, Helper helper) { + return (ResponseTemplateTransformer) + buildExtension( + new MockWireMockServices().setHelpers(Map.of(helperName, helper)), + services -> + List.of( + new ResponseTemplateTransformer( + services.getTemplateEngine(), global, services.getFiles(), emptyList()))); + } + + public static ResponseTemplateTransformer buildTemplateTransformer(Long maxCacheEntries) { + return (ResponseTemplateTransformer) + buildExtension( + new MockWireMockServices().setMaxCacheEntries(maxCacheEntries), + services -> + List.of( + new ResponseTemplateTransformer( + services.getTemplateEngine(), false, services.getFiles(), emptyList()))); + } + + public static Extension buildExtension( + MockWireMockServices wireMockServices, ExtensionFactory factory) { + FileSource fileSource = + new ClasspathFileSource( + ResponseTemplateTransformerTest.class + .getClassLoader() + .getResource("templates") + .getPath()); + wireMockServices.setFileSource(fileSource); + return factory.create(wireMockServices).stream().findFirst().get(); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpResponder.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpResponder.java index c4f2829387..e72770bcfe 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpResponder.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpResponder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,14 @@ import com.github.tomakehurst.wiremock.http.HttpResponder; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; +import java.util.Map; public class MockHttpResponder implements HttpResponder { public Response response; @Override - public void respond(Request request, Response response) { + public void respond(Request request, Response response, Map attributes) { this.response = response; } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java index 2d1b12cd60..c08c5aca31 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2022 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,20 @@ */ package com.github.tomakehurst.wiremock.testsupport; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.http.HttpUpgradeHandler; +import jakarta.servlet.http.Part; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -23,20 +37,6 @@ import java.util.Enumeration; import java.util.Locale; import java.util.Map; -import javax.servlet.AsyncContext; -import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import javax.servlet.http.HttpUpgradeHandler; -import javax.servlet.http.Part; public class MockHttpServletRequest implements HttpServletRequest { diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockRequestBuilder.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockRequestBuilder.java index 8c69d8c3e1..0f1b0d3e5a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockRequestBuilder.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,16 +17,10 @@ import static com.github.tomakehurst.wiremock.http.HttpHeader.httpHeader; import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Maps.newHashMap; -import static com.google.common.collect.Sets.newLinkedHashSet; import static org.mockito.Mockito.when; import com.github.tomakehurst.wiremock.http.*; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import org.mockito.Mockito; public class MockRequestBuilder { @@ -34,12 +28,14 @@ public class MockRequestBuilder { private String url = "/"; private RequestMethod method = GET; private String clientIp = "x.x.x.x"; - private List individualHeaders = newArrayList(); - private Map cookies = newHashMap(); - private List queryParameters = newArrayList(); + private List individualHeaders = new ArrayList<>(); + private Map cookies = new HashMap<>(); + private List queryParameters = new ArrayList<>(); + + private List formParameters = new ArrayList<>(); private String body = ""; private String bodyAsBase64 = ""; - private Collection multiparts = newArrayList(); + private Collection multiparts = new ArrayList<>(); private String protocol = "HTTP/1.1"; private boolean browserProxyRequest = false; @@ -69,6 +65,11 @@ public MockRequestBuilder withQueryParameter(String key, String... values) { return this; } + public MockRequestBuilder withFormParameter(String key, String... values) { + formParameters.add(new FormParameter(key, Arrays.asList(values))); + return this; + } + public MockRequestBuilder withMethod(RequestMethod method) { this.method = method; return this; @@ -138,10 +139,14 @@ public Request build() { when(request.queryParameter(queryParameter.key())).thenReturn(queryParameter); } + for (FormParameter formParameter : formParameters) { + when(request.formParameter(formParameter.key())).thenReturn(formParameter); + } + when(request.header(Mockito.any(String.class))).thenReturn(httpHeader("key", "value")); when(request.getHeaders()).thenReturn(headers); - when(request.getAllHeaderKeys()).thenReturn(newLinkedHashSet(headers.keys())); + when(request.getAllHeaderKeys()).thenReturn(new LinkedHashSet<>(headers.keys())); when(request.containsHeader(Mockito.any(String.class))).thenReturn(false); when(request.getCookies()).thenReturn(cookies); when(request.getBody()).thenReturn(body.getBytes()); diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockWireMockServices.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockWireMockServices.java new file mode 100644 index 0000000000..afeca2aee3 --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockWireMockServices.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.testsupport; + +import static java.util.Collections.emptyMap; + +import com.github.jknack.handlebars.Helper; +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.core.Options; +import com.github.tomakehurst.wiremock.extension.Extensions; +import com.github.tomakehurst.wiremock.extension.WireMockServices; +import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine; +import com.github.tomakehurst.wiremock.store.Stores; +import com.google.common.base.Suppliers; +import java.util.Map; +import java.util.function.Supplier; + +public class MockWireMockServices implements WireMockServices { + + private FileSource fileSource = new NoFileSource(); + + private Map> helpers = emptyMap(); + private Long maxCacheEntries = null; + private Supplier templateEngine = + Suppliers.memoize(() -> new TemplateEngine(helpers, maxCacheEntries, null, false)); + + @Override + public Admin getAdmin() { + return null; + } + + @Override + public Stores getStores() { + return null; + } + + @Override + public FileSource getFiles() { + return fileSource; + } + + @Override + public Options getOptions() { + return null; + } + + @Override + public Extensions getExtensions() { + return null; + } + + @Override + public TemplateEngine getTemplateEngine() { + return templateEngine.get(); + } + + public MockWireMockServices setFileSource(FileSource fileSource) { + this.fileSource = fileSource; + return this; + } + + public MockWireMockServices setHelpers(Map> helpers) { + this.helpers = helpers; + return this; + } + + public MockWireMockServices setMaxCacheEntries(Long maxCacheEntries) { + this.maxCacheEntries = maxCacheEntries; + return this; + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/ServeEventChecks.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/ServeEventChecks.java new file mode 100644 index 0000000000..fabc06ad5b --- /dev/null +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/ServeEventChecks.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.tomakehurst.wiremock.testsupport; + +import static com.github.tomakehurst.wiremock.common.Strings.normaliseLineBreaks; +import static com.github.tomakehurst.wiremock.stubbing.SubEvent.JSON_ERROR; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.fail; + +import com.github.tomakehurst.wiremock.common.Errors; +import com.github.tomakehurst.wiremock.common.Message; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.matching.MatchResult; + +public class ServeEventChecks { + + public static void assertMessageSubEventPresent(Admin admin, String type, String message) { + admin.getServeEvents().getServeEvents().stream() + .findFirst() + .ifPresentOrElse( + serveEvent -> { + assertThat(serveEvent.getSubEvents(), hasSize(1)); + serveEvent.getSubEvents().stream() + .findFirst() + .ifPresentOrElse( + subEvent -> { + assertThat(subEvent.getType(), is(type)); + assertThat( + normaliseLineBreaks(subEvent.getDataAs(Message.class).getMessage()), + is(normaliseLineBreaks(message))); + }, + () -> fail("No sub events found")); + }, + () -> fail("No serve events found")); + } + + public static void checkMessage(MatchResult matchResult, String type, String message) { + matchResult.getSubEvents().stream() + .filter(subEvent -> subEvent.getType().equals(type)) + .findFirst() + .ifPresentOrElse( + subEvent -> + assertThat( + normaliseLineBreaks(subEvent.getDataAs(Message.class).getMessage()), + is(normaliseLineBreaks(message))), + () -> fail("No sub event of type " + type + " found")); + } + + public static void checkJsonError(MatchResult matchResult, String detailMessage) { + matchResult.getSubEvents().stream() + .filter(subEvent -> subEvent.getType().equals(JSON_ERROR)) + .findFirst() + .ifPresentOrElse( + subEvent -> + assertThat( + normaliseLineBreaks(subEvent.getDataAs(Errors.class).first().getDetail()), + is(normaliseLineBreaks(detailMessage))), + () -> fail("No sub event of type JSON_ERROR found")); + } +} diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/TestFiles.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/TestFiles.java index 7eda406839..e902ceac49 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/TestFiles.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/TestFiles.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.github.tomakehurst.wiremock.testsupport; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.base.Charsets; import com.google.common.io.Resources; import java.io.File; import java.io.IOException; @@ -28,6 +28,7 @@ public class TestFiles { public static final String TRUST_STORE_PASSWORD = "mytruststorepassword"; public static final String TRUST_STORE_NAME = getTrustStoreRelativeName(); + public static final String JCEKS_TRUST_STORE_NAME = "test-truststore.jceks"; public static final String TRUST_STORE_PATH = filePath(TRUST_STORE_NAME); public static final String KEY_STORE_PATH = filePath("test-keystore"); public static final String KEY_STORE_WITH_CA_PATH = filePath("test-keystore-with-ca"); @@ -44,7 +45,7 @@ public static String defaultTestFilesRoot() { public static String file(String path) { try { - String text = Resources.toString(Resources.getResource(path), Charsets.UTF_8); + String text = Resources.toString(Resources.getResource(path), UTF_8); if (SystemUtils.IS_OS_WINDOWS) { text = text.replaceAll("\\r\\n", "\n").replaceAll("\\r", "\n"); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMatchers.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMatchers.java index 726e65f9d0..0d9ac9c901 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMatchers.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,31 +17,32 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; -import static com.google.common.collect.Iterables.*; -import static java.lang.System.lineSeparator; import static java.util.Arrays.asList; import static java.util.regex.Pattern.DOTALL; import static java.util.regex.Pattern.MULTILINE; +import com.github.tomakehurst.wiremock.common.Strings; import com.github.tomakehurst.wiremock.common.TextFile; import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.matching.UrlPattern; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.commons.io.FileUtils; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -58,7 +59,7 @@ public class WireMatchers { public static Matcher equalToJson(final String expectedJson) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description desc) { @@ -77,9 +78,31 @@ public boolean matchesSafely(String actualJson) { }; } + public static Matcher bytesEqualToJson( + final String expectedJson, final JSONCompareMode jsonCompareMode) { + return new TypeSafeMatcher<>() { + + @Override + public void describeTo(Description desc) { + desc.appendText("Expected:\n" + expectedJson); + } + + @Override + public boolean matchesSafely(byte[] actualJson) { + try { + JSONAssert.assertEquals( + expectedJson, Strings.stringFromBytes(actualJson), jsonCompareMode); + return true; + } catch (Throwable e) { + return false; + } + } + }; + } + public static Matcher equalToJson( final String expectedJson, final JSONCompareMode jsonCompareMode) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description desc) { @@ -98,8 +121,30 @@ public boolean matchesSafely(String actualJson) { }; } + public static Matcher equalToBinaryJson( + final String expectedJson, final JSONCompareMode jsonCompareMode) { + return new TypeSafeMatcher<>() { + + @Override + public void describeTo(Description desc) { + desc.appendText("Expected:\n" + expectedJson); + } + + @Override + public boolean matchesSafely(byte[] actualJson) { + try { + JSONAssert.assertEquals( + expectedJson, Strings.stringFromBytes(actualJson), jsonCompareMode); + return true; + } catch (Throwable e) { + return false; + } + } + }; + } + public static Matcher equalToXml(final String expected) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override protected boolean matchesSafely(String value) { Diff diff = @@ -121,7 +166,7 @@ public void describeTo(Description description) { } public static Matcher matches(final String regex) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description description) { @@ -136,7 +181,7 @@ public boolean matchesSafely(String actual) { } public static Matcher matchesMultiLine(final String regex) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description description) { @@ -151,7 +196,7 @@ public boolean matchesSafely(String actual) { } public static Matcher> hasExactly(final Matcher... items) { - return new TypeSafeMatcher>() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description desc) { @@ -173,7 +218,7 @@ public boolean matchesSafely(Iterable actual) { } public static Matcher> hasExactlyIgnoringOrder(final Matcher... items) { - return new TypeSafeMatcher>() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description desc) { @@ -182,12 +227,16 @@ public void describeTo(Description desc) { @Override public boolean matchesSafely(Iterable actual) { - if (size(actual) != items.length) { + if (StreamSupport.stream(actual.spliterator(), false).count() != items.length) { return false; } for (final Matcher matcher : items) { - if (find(actual, isMatchFor(matcher), null) == null) { + if (StreamSupport.stream(actual.spliterator(), false) + .filter(isMatchFor(matcher)) + .findAny() + .orElse(null) + == null) { return false; } } @@ -198,15 +247,11 @@ public boolean matchesSafely(Iterable actual) { } private static Predicate isMatchFor(final Matcher matcher) { - return new Predicate() { - public boolean apply(T input) { - return matcher.matches(input); - } - }; + return matcher::matches; } public static Matcher fileNamed(final String name) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public void describeTo(Description desc) { @@ -221,7 +266,7 @@ public boolean matchesSafely(TextFile textFile) { } public static Matcher isAfter(final String dateString) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public boolean matchesSafely(Date date) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -241,7 +286,7 @@ public void describeTo(Description description) { } public static Matcher isToday() { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public boolean matchesSafely(Date date) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); @@ -257,7 +302,7 @@ public void describeTo(Description description) { } public static Matcher header(final String key, final String value) { - return new TypeSafeMatcher() { + return new TypeSafeMatcher<>() { @Override public boolean matchesSafely(HttpHeader httpHeader) { return httpHeader.key().equals(key) && httpHeader.containsValue(value); @@ -271,44 +316,27 @@ public void describeTo(Description description) { } public static Matcher hasFileContaining(final String... contents) { - return new TypeSafeDiagnosingMatcher() { + return new TypeSafeDiagnosingMatcher<>() { @Override protected boolean matchesSafely(Path path, Description mismatchDescription) { - List files = asList(path.toFile().listFiles()); + List files = asList(Objects.requireNonNull(path.toFile().listFiles())); boolean matched = - any( - files, - new Predicate() { - @Override - public boolean apply(File file) { - final String fileContents = fileContents(file); - return all( - asList(contents), - new Predicate() { - @Override - public boolean apply(String input) { - return fileContents.contains(input); - } - }); - } - }); - - if (files.size() == 0) { + files.stream() + .anyMatch( + file -> { + final String fileContents = fileContents(file); + + return Arrays.stream(contents).allMatch(fileContents::contains); + }); + + if (files.isEmpty()) { mismatchDescription.appendText("there were no files in " + path); } if (!matched) { String allFileContents = - Joiner.on("\n\n") - .join( - transform( - files, - new Function() { - @Override - public String apply(File input) { - return fileContents(input); - } - })); + files.stream().map(WireMatchers::fileContents).collect(Collectors.joining("\n\n")); + mismatchDescription.appendText(allFileContents); } @@ -317,14 +345,14 @@ public String apply(File input) { @Override public void describeTo(Description description) { - description.appendText("a file containing all of: " + Joiner.on(", ").join(contents)); + description.appendText("a file containing all of: " + String.join(", ", contents)); } }; } public static Matcher equalsMultiLine(final String expected) { - String normalisedExpected = normaliseLineBreaks(expected); - return new IsEqual(normalisedExpected) { + String normalisedExpected = Strings.normaliseLineBreaks(expected); + return new IsEqual<>(normalisedExpected) { @Override public boolean matches(Object actualValue) { return super.matches(actualValue.toString()); @@ -332,10 +360,6 @@ public boolean matches(Object actualValue) { }; } - private static String normaliseLineBreaks(String s) { - return s.replace("\n", lineSeparator()); - } - private static String fileContents(File input) { try { return FileUtils.readFileToString(input, StandardCharsets.UTF_8); @@ -345,12 +369,7 @@ private static String fileContents(File input) { } public static Predicate withUrl(final String url) { - return new Predicate() { - @Override - public boolean apply(StubMapping input) { - return url.equals(input.getRequest().getUrl()); - } - }; + return input -> url.equals(input.getRequest().getUrl()); } public static TypeSafeDiagnosingMatcher stubMappingWithUrl(final String url) { @@ -359,7 +378,7 @@ public static TypeSafeDiagnosingMatcher stubMappingWithUrl(final St public static TypeSafeDiagnosingMatcher stubMappingWithUrl( final UrlPattern urlPattern) { - return new TypeSafeDiagnosingMatcher() { + return new TypeSafeDiagnosingMatcher<>() { @Override public void describeTo(Description description) { description.appendText("a stub mapping with a request URL matching " + urlPattern); @@ -373,27 +392,26 @@ protected boolean matchesSafely(StubMapping item, Description mismatchDescriptio } public static ServeEvent findServeEventWithUrl(List serveEvents, final String url) { - return find( - serveEvents, - new Predicate() { - @Override - public boolean apply(ServeEvent input) { - return url.equals(input.getRequest().getUrl()); - } - }); + return serveEvents.stream() + .filter(input -> url.equals(input.getRequest().getUrl())) + .findAny() + .orElseThrow(NoSuchElementException::new); } public static StubMapping findMappingWithUrl(List stubMappings, final String url) { - return find(stubMappings, withUrl(url)); + return stubMappings.stream() + .filter(withUrl(url)) + .findAny() + .orElseThrow(NoSuchElementException::new); } public static List findMappingsWithUrl( List stubMappings, final String url) { - return ImmutableList.copyOf(filter(stubMappings, withUrl(url))); + return stubMappings.stream().filter(withUrl(url)).collect(Collectors.toUnmodifiableList()); } public static TypeSafeDiagnosingMatcher isInAScenario() { - return new TypeSafeDiagnosingMatcher() { + return new TypeSafeDiagnosingMatcher<>() { @Override public void describeTo(Description description) { description.appendText("a stub mapping with a scenario name"); diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockResponse.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockResponse.java index 96aa1450f8..c4d70c2b4a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockResponse.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,12 @@ package com.github.tomakehurst.wiremock.testsupport; import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArrayAndCloseStream; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Iterables.getFirst; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirst; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Multimap; -import java.nio.charset.Charset; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; @@ -30,7 +30,7 @@ public class WireMockResponse { private final ClassicHttpResponse httpResponse; private final byte[] content; - public WireMockResponse(ClassicHttpResponse httpResponse) { + public WireMockResponse(CloseableHttpResponse httpResponse) { this.httpResponse = httpResponse; content = getEntityAsByteArrayAndCloseStream(httpResponse); } @@ -43,7 +43,7 @@ public String content() { if (content == null) { return null; } - return new String(content, Charset.forName(UTF_8.name())); + return new String(content, UTF_8); } public byte[] binaryContent() { diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockTestClient.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockTestClient.java index 9d75c98027..44197b9b5b 100755 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockTestClient.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockTestClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,28 +16,30 @@ package com.github.tomakehurst.wiremock.testsupport; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static com.github.tomakehurst.wiremock.common.Strings.isNullOrEmpty; import static com.github.tomakehurst.wiremock.http.MimeType.JSON; -import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.HttpURLConnection.HTTP_CREATED; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON; import static org.apache.hc.core5.http.ContentType.APPLICATION_XML; import static org.apache.hc.core5.http.ContentType.DEFAULT_BINARY; +import com.github.tomakehurst.wiremock.common.Exceptions; +import com.github.tomakehurst.wiremock.common.Json; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; -import java.security.cert.X509Certificate; import java.util.Collection; +import java.util.UUID; import javax.net.ssl.SSLContext; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; -import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.classic.methods.*; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.auth.BasicScheme; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory; @@ -53,14 +55,12 @@ import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.ssl.SSLContexts; -import org.apache.hc.core5.ssl.TrustStrategy; public class WireMockTestClient { private static final String LOCAL_WIREMOCK_ROOT = "http://%s:%d%s"; - private static final String LOCAL_WIREMOCK_NEW_RESPONSE_URL = "http://%s:%d/__admin/mappings/new"; - private static final String LOCAL_WIREMOCK_EDIT_RESPONSE_URL = - "http://%s:%d/__admin/mappings/edit"; + private static final String LOCAL_WIREMOCK_NEW_RESPONSE_URL = "http://%s:%d/__admin/mappings"; + private static final String LOCAL_WIREMOCK_EDIT_RESPONSE_URL = "http://%s:%d/__admin/mappings/%s"; private static final String LOCAL_WIREMOCK_RESET_DEFAULT_MAPPINS_URL = "http://%s:%d/__admin/mappings/reset"; private static final String LOCAL_WIREMOCK_SNAPSHOT_PATH = "/__admin/recordings/snapshot"; @@ -68,9 +68,13 @@ public class WireMockTestClient { private int port; private String address; + private final CloseableHttpClient client; + public WireMockTestClient(int port, String address) { this.port = port; this.address = address; + + this.client = httpClient(); } public WireMockTestClient(int port) { @@ -89,8 +93,8 @@ private String newMappingUrl() { return String.format(LOCAL_WIREMOCK_NEW_RESPONSE_URL, address, port); } - private String editMappingUrl() { - return String.format(LOCAL_WIREMOCK_EDIT_RESPONSE_URL, address, port); + private String editMappingUrl(UUID stubId) { + return String.format(LOCAL_WIREMOCK_EDIT_RESPONSE_URL, address, port, stubId); } private String resetDefaultMappingsUrl() { @@ -114,7 +118,17 @@ public WireMockResponse getViaProxy(String url, int proxyPort) { public WireMockResponse getViaProxy(String url, int proxyPort, String scheme) { URI targetUri = URI.create(url); HttpHost proxy = new HttpHost(scheme, address, proxyPort); - HttpClient httpClientUsingProxy = + + HttpHost target = new HttpHost(targetUri.getScheme(), targetUri.getHost(), targetUri.getPort()); + HttpGet req = + new HttpGet( + targetUri.getPath() + + (isNullOrEmpty(targetUri.getQuery()) ? "" : "?" + targetUri.getQuery())); + req.removeHeaders("Host"); + + System.out.println("executing request to " + targetUri + "(" + target + ") via " + proxy); + + try (CloseableHttpClient httpClientUsingProxy = HttpClientBuilder.create() .disableAuthCaching() .disableAutomaticRetries() @@ -129,22 +143,13 @@ public WireMockResponse getViaProxy(String url, int proxyPort, String scheme) { .build()) .build()) .setProxy(proxy) - .build(); + .build()) { - try { - HttpHost target = - new HttpHost(targetUri.getScheme(), targetUri.getHost(), targetUri.getPort()); - HttpGet req = - new HttpGet( - targetUri.getPath() - + (isNullOrEmpty(targetUri.getQuery()) ? "" : "?" + targetUri.getQuery())); - req.removeHeaders("Host"); - - System.out.println("executing request to " + targetUri + "(" + target + ") via " + proxy); - ClassicHttpResponse httpResponse = httpClientUsingProxy.execute(target, req); - return new WireMockResponse(httpResponse); + try (CloseableHttpResponse httpResponse = httpClientUsingProxy.execute(target, req)) { + return new WireMockResponse(httpResponse); + } } catch (IOException ioe) { - throw new RuntimeException(ioe); + return Exceptions.throwUnchecked(ioe, WireMockResponse.class); } } @@ -205,6 +210,10 @@ public WireMockResponse postJson(String url, String body, TestHttpHeader... head return executeMethodAndConvertExceptions(httpPost, headers); } + public WireMockResponse putJson(String url, String body, TestHttpHeader... headers) { + return putWithBody(url, body, APPLICATION_JSON.getMimeType(), headers); + } + public WireMockResponse postXml(String url, String body, TestHttpHeader... headers) { HttpPost httpPost = new HttpPost(mockServiceUrlFor(url)); httpPost.setEntity(new StringEntity(body, APPLICATION_XML)); @@ -244,8 +253,9 @@ public void addResponse(String responseSpecJson, String charset) { } public void editMapping(String mappingSpecJson) { - int status = postJsonAndReturnStatus(editMappingUrl(), mappingSpecJson); - if (status != HTTP_NO_CONTENT) { + StubMapping stubMapping = Json.read(mappingSpecJson, StubMapping.class); + int status = putJsonAndReturnStatus(editMappingUrl(stubMapping.getId()), mappingSpecJson); + if (status != HTTP_OK) { throw new RuntimeException("Returned status code was " + status); } } @@ -275,7 +285,26 @@ private int postJsonAndReturnStatus(String url, String json, String charset) { if (json != null) { post.setEntity(new StringEntity(json, ContentType.create(JSON.toString(), charset))); } - ClassicHttpResponse httpResponse = httpClient().execute(post); + ClassicHttpResponse httpResponse = client.execute(post); + return httpResponse.getCode(); + } catch (RuntimeException re) { + throw re; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private int putJsonAndReturnStatus(String url, String json) { + return putJsonAndReturnStatus(url, json, "utf-8"); + } + + private int putJsonAndReturnStatus(String url, String json, String charset) { + HttpPut post = new HttpPut(url); + try { + if (json != null) { + post.setEntity(new StringEntity(json, ContentType.create(JSON.toString(), charset))); + } + ClassicHttpResponse httpResponse = client.execute(post); return httpResponse.getCode(); } catch (RuntimeException re) { throw re; @@ -294,8 +323,9 @@ private WireMockResponse executeMethodAndConvertExceptions( for (TestHttpHeader header : headers) { httpRequest.addHeader(header.getName(), header.getValue()); } - ClassicHttpResponse httpResponse = httpClient().execute(httpRequest); - return new WireMockResponse(httpResponse); + try (CloseableHttpResponse httpResponse = client.execute(httpRequest)) { + return new WireMockResponse(httpResponse); + } } catch (IOException ioe) { throw new RuntimeException(ioe); } @@ -303,7 +333,6 @@ private WireMockResponse executeMethodAndConvertExceptions( public WireMockResponse getWithPreemptiveCredentials( String url, int port, String username, String password) { - CloseableHttpClient httpClient = HttpClients.createDefault(); BasicScheme basicAuth = new BasicScheme(); basicAuth.initPreemptive(new UsernamePasswordCredentials(username, password.toCharArray())); @@ -312,9 +341,9 @@ public WireMockResponse getWithPreemptiveCredentials( HttpHost target = new HttpHost("localhost", port); localContext.resetAuthExchange(target, basicAuth); - try { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpGet httpget = new HttpGet(url); - ClassicHttpResponse response = httpClient.execute(target, httpget, localContext); + CloseableHttpResponse response = httpClient.execute(target, httpget, localContext); return new WireMockResponse(response); } catch (IOException e) { return throwUnchecked(e, WireMockResponse.class); @@ -327,9 +356,10 @@ public WireMockResponse request(final String methodName, String url, TestHttpHea return executeMethodAndConvertExceptions(httpRequest, headers); } - public WireMockResponse request(final String methodName, String url, String body, TestHttpHeader... headers) { + public WireMockResponse request( + final String methodName, String url, String body, TestHttpHeader... headers) { HttpUriRequest httpRequest = - new HttpUriRequestBase(methodName, URI.create(mockServiceUrlFor(url))); + new HttpUriRequestBase(methodName, URI.create(mockServiceUrlFor(url))); httpRequest.setEntity(new StringEntity(body)); return executeMethodAndConvertExceptions(httpRequest, headers); } @@ -343,6 +373,8 @@ private static CloseableHttpClient httpClient() { .disableContentCompression() .setConnectionManager( PoolingHttpClientConnectionManagerBuilder.create() + .setMaxConnPerRoute(1000) + .setMaxConnTotal(1000) .setConnectionFactory( new ManagedHttpClientConnectionFactory( null, CharCodingConfig.custom().setCharset(UTF_8).build(), null)) @@ -355,10 +387,8 @@ private static SSLContext buildTrustWireMockDefaultCertificateSSLContext() { return SSLContexts.custom() .loadTrustMaterial( null, - new TrustStrategy() { - @Override - public boolean isTrusted(X509Certificate[] chain, String authType) { - return chain[0].getSubjectDN().getName().startsWith("CN=Tom Akehurst") + (chain, authType) -> + chain[0].getSubjectDN().getName().startsWith("CN=Tom Akehurst") || chain[0] .getSubjectDN() .getName() @@ -367,9 +397,7 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { && chain[1] .getSubjectDN() .getName() - .equals("CN=WireMock Local Self Signed Root Certificate"); - } - }) + .equals("CN=WireMock Local Self Signed Root Certificate")) .build(); } catch (Exception e) { return throwUnchecked(e, SSLContext.class); diff --git a/src/test/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournalTest.java b/src/test/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournalTest.java index e004653ef2..9e2b02035c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournalTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/verification/InMemoryRequestJournalTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,6 @@ import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -41,15 +39,14 @@ public class InMemoryRequestJournalTest { @BeforeEach public void createTestRequests() { - serveEvent1 = ServeEvent.of(createFrom(aRequest("log1").withUrl("/logging1").build()), null); - serveEvent2 = ServeEvent.of(createFrom(aRequest("log2").withUrl("/logging2").build()), null); - serveEvent3 = ServeEvent.of(createFrom(aRequest("log3").withUrl("/logging3").build()), null); + serveEvent1 = ServeEvent.of(createFrom(aRequest("log1").withUrl("/logging1").build())); + serveEvent2 = ServeEvent.of(createFrom(aRequest("log2").withUrl("/logging2").build())); + serveEvent3 = ServeEvent.of(createFrom(aRequest("log3").withUrl("/logging3").build())); } @Test public void returnsAllLoggedRequestsWhenNoJournalSizeLimit() { - RequestJournal journal = - new InMemoryRequestJournal(Optional.absent(), NO_CUSTOM_MATCHERS); + RequestJournal journal = new InMemoryRequestJournal(null, NO_CUSTOM_MATCHERS); journal.requestReceived(serveEvent1); journal.requestReceived(serveEvent1); @@ -65,8 +62,8 @@ public void returnsAllLoggedRequestsWhenNoJournalSizeLimit() { public void resettingTheJournalClearsAllEntries() throws Exception { LoggedRequest loggedRequest = createFrom(aRequest().withUrl("/for/logging").build()); - RequestJournal journal = new InMemoryRequestJournal(Optional.of(1), NO_CUSTOM_MATCHERS); - journal.requestReceived(ServeEvent.of(loggedRequest, null)); + RequestJournal journal = new InMemoryRequestJournal(1, NO_CUSTOM_MATCHERS); + journal.requestReceived(ServeEvent.of(loggedRequest)); assertThat(journal.countRequestsMatching(everything()), is(1)); journal.reset(); assertThat(journal.countRequestsMatching(everything()), is(0)); @@ -74,7 +71,7 @@ public void resettingTheJournalClearsAllEntries() throws Exception { @Test public void discardsOldRequestsWhenJournalSizeIsLimited() throws Exception { - RequestJournal journal = new InMemoryRequestJournal(Optional.of(2), NO_CUSTOM_MATCHERS); + RequestJournal journal = new InMemoryRequestJournal(2, NO_CUSTOM_MATCHERS); journal.requestReceived(serveEvent1); journal.requestReceived(serveEvent2); @@ -91,9 +88,7 @@ public void discardsOldRequestsWhenJournalSizeIsLimited() throws Exception { @Test public void matchesRequestWithCustomMatcherDefinition() throws Exception { - RequestJournal journal = - new InMemoryRequestJournal( - Optional.absent(), ImmutableMap.of(ALWAYS.getName(), ALWAYS)); + RequestJournal journal = new InMemoryRequestJournal(null, Map.of(ALWAYS.getName(), ALWAYS)); journal.requestReceived(serveEvent1); journal.requestReceived(serveEvent2); diff --git a/src/test/java/com/github/tomakehurst/wiremock/verification/LoggedRequestTest.java b/src/test/java/com/github/tomakehurst/wiremock/verification/LoggedRequestTest.java index 082ee78e12..7fdad4e69d 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/verification/LoggedRequestTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/verification/LoggedRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2022 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ import com.github.tomakehurst.wiremock.http.Cookie; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.RequestMethod; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.util.Date; import java.util.List; @@ -103,9 +102,7 @@ public void headerMatchingIsCaseInsensitive() { public void jsonRepresentation() throws Exception { HttpHeaders headers = new HttpHeaders(httpHeader("Accept-Language", "en-us,en;q=0.5")); Map cookies = - ImmutableMap.of( - "first_cookie", new Cookie("yum"), - "monster_cookie", new Cookie("COOKIIIEESS")); + Map.of("first_cookie", new Cookie("yum"), "monster_cookie", new Cookie("COOKIIIEESS")); Date loggedDate = Dates.parse(DATE); diff --git a/src/test/java/com/github/tomakehurst/wiremock/verification/NearMissCalculatorTest.java b/src/test/java/com/github/tomakehurst/wiremock/verification/NearMissCalculatorTest.java index 1630f2cb39..cf9a40af32 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/verification/NearMissCalculatorTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/verification/NearMissCalculatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import static com.github.tomakehurst.wiremock.http.RequestMethod.PUT; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; +import static com.github.tomakehurst.wiremock.stubbing.ServeEventFactory.newPostMatchServeEvent; import static com.github.tomakehurst.wiremock.verification.NearMissCalculator.NEAR_MISS_COUNT; -import static com.google.common.collect.FluentIterable.from; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; @@ -31,12 +31,10 @@ import com.github.tomakehurst.wiremock.client.MappingBuilder; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.github.tomakehurst.wiremock.stubbing.Scenarios; -import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import com.github.tomakehurst.wiremock.stubbing.StubMappings; -import com.google.common.base.Function; +import com.github.tomakehurst.wiremock.stubbing.*; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,7 +50,7 @@ public class NearMissCalculatorTest { public void init() { stubMappings = mock(StubMappings.class); requestJournal = mock(RequestJournal.class); - scenarios = new Scenarios(); + scenarios = new InMemoryScenarios(); nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); } @@ -158,30 +156,16 @@ public void returns0NearMissesForSingleRequestPatternWhenNoRequestsLogged() { private void givenStubMappings(final MappingBuilder... mappingBuilders) { final List mappings = - from(mappingBuilders) - .transform( - new Function() { - @Override - public StubMapping apply(MappingBuilder input) { - return input.build(); - } - }) - .toList(); + Arrays.stream(mappingBuilders).map(MappingBuilder::build).collect(Collectors.toList()); + when(stubMappings.getAll()).thenReturn(mappings); } private void givenRequests(final Request... requests) { final List serveEvents = - from(requests) - .transform( - new Function() { - @Override - public ServeEvent apply(Request request) { - return ServeEvent.of( - LoggedRequest.createFrom(request), new ResponseDefinition()); - } - }) - .toList(); + Arrays.stream(requests) + .map(request -> newPostMatchServeEvent(request, new ResponseDefinition())) + .collect(Collectors.toList()); when(requestJournal.getAllServeEvents()).thenReturn(serveEvents); } diff --git a/src/test/java/com/github/tomakehurst/wiremock/verification/diff/DiffTest.java b/src/test/java/com/github/tomakehurst/wiremock/verification/diff/DiffTest.java index b9aa2fc51e..aa7f6ffe77 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/verification/diff/DiffTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/verification/diff/DiffTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.matching.MatchResult; -import com.github.tomakehurst.wiremock.matching.ValueMatcher; import com.github.tomakehurst.wiremock.stubbing.Scenario; import org.junit.jupiter.api.Test; @@ -161,6 +159,8 @@ public void showsRequestBody() { "ANY\n" + "/thing\n" + "\n" + + "[equalToJson]" + + lineSeparator() + "{" + lineSeparator() + " \"outer\" : {" @@ -177,6 +177,7 @@ public void showsRequestBody() { "ANY\n" + "/thing\n" + "\n" + + lineSeparator() + "{" + lineSeparator() + " \"outer\" : { }" @@ -200,6 +201,7 @@ public void prettyPrintsJsonRequestBody() { "ANY\n" + "/thing\n" + "\n" + + "[equalToJson]\n" + "{" + lineSeparator() + " \"outer\" : {" @@ -216,6 +218,7 @@ public void prettyPrintsJsonRequestBody() { "ANY\n" + "/thing\n" + "\n" + + lineSeparator() + "{" + lineSeparator() + " \"outer\" : { }" @@ -286,6 +289,7 @@ public void prettyPrintsXml() { "ANY\n" + "/thing\n" + "\n" + + "[equalToXml]\n" + "" + lineSeparator() + " " @@ -298,7 +302,7 @@ public void prettyPrintsXml() { + lineSeparator(), "ANY\n" + "/thing\n" - + "\n" + + "\n\n" + "" + lineSeparator() + " " @@ -367,13 +371,7 @@ public void indicatesThatAnInlineCustomMatcherDidNotMatch() { Diff diff = new Diff( newRequestPattern(GET, urlEqualTo("/thing")) - .andMatching( - new ValueMatcher() { - @Override - public MatchResult match(Request value) { - return MatchResult.noMatch(); - } - }) + .andMatching(value -> MatchResult.noMatch()) .build(), mockRequest().method(GET).url("/thing")); @@ -410,7 +408,8 @@ public void handlesAbsentRequestBody() { diff.toString(), is( junitStyleDiffMessage( - "POST\n" + "/thing\n\n" + "(absent)", "POST\n" + "/thing\n\n" + "not absent"))); + "POST\n" + "/thing\n\n[absent]\n" + "(absent)", + "POST\n" + "/thing\n\n" + "\nnot absent"))); } @Test @@ -525,4 +524,38 @@ public void includeSchemeIfSpecified() { junitStyleDiffMessage( "https\n" + "ANY\n" + "/thing\n", "http\n" + "ANY\n" + "/thing\n"))); } + + @Test + public void handleExceptionGettingExpressionResultDueToEmptyBody() { + Diff diff = + new Diff( + newRequestPattern(ANY, urlEqualTo("/thing")) + .withRequestBody(matchingJsonPath("$.accountNum", equalTo("1234"))) + .build(), + mockRequest().url("/thing").body("")); + + assertThat( + diff.toString(), + is( + junitStyleDiffMessage( + "ANY\n" + "/thing\n" + "\n" + "$.accountNum [equalTo] 1234", + "ANY\n" + "/thing\n" + "\n"))); + } + + @Test + public void handleExceptionGettingExpressionResultDueToNonJson() { + Diff diff = + new Diff( + newRequestPattern(ANY, urlEqualTo("/thing")) + .withRequestBody(matchingJsonPath("$.accountNum", equalTo("1234"))) + .build(), + mockRequest().url("/thing").body("not json")); + + assertThat( + diff.toString(), + is( + junitStyleDiffMessage( + "ANY\n" + "/thing\n" + "\n" + "$.accountNum [equalTo] 1234", + "ANY\n" + "/thing\n" + "\n" + "not json"))); + } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRendererTest.java b/src/test/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRendererTest.java index c05ac4f4e5..bdca7c8d1c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRendererTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/verification/diff/PlainTextDiffRendererTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2021 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,11 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.common.Json.prettyPrint; +import static com.github.tomakehurst.wiremock.common.Strings.normaliseLineBreaks; +import static com.github.tomakehurst.wiremock.http.RequestMethod.ANY; import static com.github.tomakehurst.wiremock.http.RequestMethod.GET; import static com.github.tomakehurst.wiremock.http.RequestMethod.POST; +import static com.github.tomakehurst.wiremock.http.RequestMethod.PUT; import static com.github.tomakehurst.wiremock.matching.MockMultipart.mockPart; import static com.github.tomakehurst.wiremock.matching.MockRequest.mockRequest; import static com.github.tomakehurst.wiremock.matching.RequestPatternBuilder.newRequestPattern; @@ -27,15 +30,23 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.http.FormParameter; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.matching.MatchResult; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; -import com.github.tomakehurst.wiremock.matching.ValueMatcher; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.commons.lang3.SystemUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.*; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.condition.OS; public class PlainTextDiffRendererTest { @@ -123,6 +134,36 @@ public void rendersWithDifferingQueryParameters() { assertThat(output, equalsMultiLine(file("not-found-diff-sample_query.txt"))); } + @Test + public void rendersWithDifferingFormParameters() { + Diff diff = + new Diff( + put(urlPathEqualTo("/thing")) + .withName("Query params diff") + .withFormParam("one", equalTo("1")) + .withFormParam("two", containing("two things")) + .withFormParam("three", matching("[a-z]{5}")) + .build(), + mockRequest() + .method(PUT) + .url("/thing") + .formParameters(getFormParameters()) + .header("Content-Type", "application/x-www-form-urlencoded")); + + String output = diffRenderer.render(diff); + System.out.println(output); + + assertThat(output, equalsMultiLine(file("not-found-diff-sample_form.txt"))); + } + + private Map getFormParameters() { + Map formParameters = new HashMap<>(); + formParameters.put("one", new FormParameter("one", List.of("2"))); + formParameters.put("two", new FormParameter("two", List.of("wrong things"))); + formParameters.put("three", new FormParameter("three", List.of("abcde"))); + return formParameters; + } + @Test public void wrapsLargeJsonBodiesAppropriately() { Diff diff = @@ -168,14 +209,17 @@ public void wrapsLargeJsonBodiesAppropriately() { + "}"))); String output = diffRenderer.render(diff); - System.out.println(output); // Ugh. The joys of Microsoft's line ending innovations. String expected = SystemUtils.IS_OS_WINDOWS ? file("not-found-diff-sample_large_json_windows.txt") : file("not-found-diff-sample_large_json.txt"); - assertThat(output, equalsMultiLine(expected)); + + System.out.println("expected:\n" + expected); + System.out.println("actual:\n" + output); + + assertThat(normaliseLineBreaks(output), equalsMultiLine(expected)); } @Test @@ -198,7 +242,11 @@ public void wrapsLargeXmlBodiesAppropriatelyJre11() { @EnabledOnOs(value = OS.WINDOWS, disabledReason = "Wrap differs per OS") public void wrapsLargeXmlBodiesAppropriatelyJre11Windows() { String output = wrapsLargeXmlBodiesAppropriately(); - assertThat(output, equalsMultiLine(file("not-found-diff-sample_large_xml_jre11_windows.txt"))); + + String expected = file("not-found-diff-sample_large_xml_jre11_windows.txt"); + System.out.println("expected:\n" + expected); + System.out.println("output:\n" + output); + assertThat(output, equalsMultiLine(expected)); } private String wrapsLargeXmlBodiesAppropriately() { @@ -329,6 +377,34 @@ public void showsUrlRegexUnescapedMessage() { assertThat(output, equalsMultiLine(file("not-found-diff-sample_url-pattern.txt"))); } + @Test + public void showsUrlTemplateNonMatchMessage() { + Diff diff = + new Diff( + get(urlPathTemplate("/contacts/{contactId}")).build(), + mockRequest().method(GET).url("/contracts/12345")); + + String output = diffRenderer.render(diff); + System.out.println(output); + + assertThat(output, equalsMultiLine(file("not-found-diff-sample_url-template.txt"))); + } + + @Test + public void showsUrlPathParametersNonMatchMessage() { + Diff diff = + new Diff( + get(urlPathTemplate("/contacts/{contactId}")) + .withPathParam("contactId", equalTo("123")) + .build(), + mockRequest().method(GET).url("/contacts/345")); + + String output = diffRenderer.render(diff); + System.out.println(output); + + assertThat(output, equalsMultiLine(file("not-found-diff-sample_url-path-parameters.txt"))); + } + @Test public void showsMultipartDifference() { Diff diff = @@ -387,13 +463,7 @@ public void showsErrorInDiffWhenInlineCustomMatcherNotSatisfiedInMixedStub() { new Diff( post("/thing") .withName("Standard and custom matched stub") - .andMatching( - new ValueMatcher() { - @Override - public MatchResult match(Request value) { - return MatchResult.noMatch(); - } - }) + .andMatching(value -> MatchResult.noMatch()) .build(), mockRequest().method(POST).url("/thing")); @@ -420,18 +490,65 @@ public void showsErrorInDiffWhenNamedCustomMatcherNotSatisfiedInMixedStub() { output, equalsMultiLine(file("not-found-diff-sample_mixed-matchers-named-custom.txt"))); } + @Test + public void showsErrorInDiffWhenExactMatchForMultipleValuesInQueryParamNotSatisfiedInStub() { + Diff diff = + new Diff( + get(urlPathEqualTo("/thing")).withQueryParam("q", havingExactly("1", "2", "3")).build(), + mockRequest().method(GET).url("/thing?q=2")); + + String output = diffRenderer.render(diff); + assertThat( + output, + equalsMultiLine( + file("not-found-diff-sample_exactmatch-for-multiple-values-query-param.txt"))); + } + + @Test + public void showsErrorInDiffWhenIncludesMatchForMultipleValuesInQueryParamNotSatisfiedInStub() { + Diff diff = + new Diff( + get(urlPathEqualTo("/thing")).withQueryParam("q", including("1", "2", "3")).build(), + mockRequest().method(GET).url("/thing?q=1")); + + String output = diffRenderer.render(diff); + assertThat( + output, + equalsMultiLine( + file("not-found-diff-sample_includematch-for-multiple-values-query-param.txt"))); + } + + @Test + public void showsErrorInDiffWhenExactMatchForMultipleValuesInHeaderNotSatisfiedInStub() { + Diff diff = + new Diff( + get(urlPathEqualTo("/thing")).withHeader("q", havingExactly("1", "2", "3")).build(), + mockRequest().method(GET).url("/thing").header("q", "1")); + + String output = diffRenderer.render(diff); + assertThat( + output, + equalsMultiLine(file("not-found-diff-sample_exactmatch-for-multiple-values-header.txt"))); + } + + @Test + public void showsErrorInDiffWhenIncludesMatchForMultipleValuesInHeaderNotSatisfiedInStub() { + Diff diff = + new Diff( + get(urlPathEqualTo("/thing")).withHeader("q", including("1", "2", "3")).build(), + mockRequest().method(GET).url("/thing").header("q", "1")); + + String output = diffRenderer.render(diff); + assertThat( + output, + equalsMultiLine(file("not-found-diff-sample_includematch-for-multiple-values-header.txt"))); + } + @Test public void showsAppropriateErrorInDiffWhenCustomMatcherIsUsedExclusively() { Diff diff = new Diff( - requestMatching( - new ValueMatcher() { - @Override - public MatchResult match(Request value) { - return MatchResult.noMatch(); - } - }) - .build(), + requestMatching(value -> MatchResult.noMatch()).build(), mockRequest().method(POST).url("/thing")); String output = diffRenderer.render(diff); @@ -451,6 +568,58 @@ public void handlesUrlsWithQueryStringAndNoPath() { System.out.println(output); } + @Test + void showsErrorInDiffWhenBodyDoesNotMatchJsonSchema() { + Diff diff = + new Diff( + post("/thing") + .withName("JSON schema stub") + .withRequestBody(matchingJsonSchema(file("schema-validation/new-pet.schema.json"))) + .build(), + mockRequest() + .url("/thing") + .method(POST) + .body(file("schema-validation/new-pet.invalid.json"))); + + String output = diffRenderer.render(diff); + + assertThat( + normaliseLineBreaks(output), + equalsMultiLine(file("not-found-diff-sample_json-schema.txt"))); + } + + @Test + public void showsErrorInDiffWhenBodyIsEmptyAndPathExpressionResult() { + Diff diff = + new Diff( + newRequestPattern(ANY, urlEqualTo("/thing")) + .withRequestBody(matchingJsonPath("$.accountNum", equalTo("1234"))) + .build(), + mockRequest().url("/thing").body("")); + + String output = diffRenderer.render(diff); + + assertThat( + normaliseLineBreaks(output), + equalsMultiLine(file("not-found-diff-sample_json-path-no-body.txt"))); + } + + @Test + public void showsErrorInDiffWhenBodyIsNotJsonAndPathExpressionResult() { + Diff diff = + new Diff( + newRequestPattern(ANY, urlEqualTo("/thing")) + .withRequestBody(matchingJsonPath("$.accountNum", equalTo("1234"))) + .build(), + mockRequest().url("/thing").body("not json")); + + String output = diffRenderer.render(diff); + + assertThat( + normaliseLineBreaks(output), + equalsMultiLine(file("not-found-diff-sample_json-path-body-not-json.txt"))); + } + public static class MyCustomMatcher extends RequestMatcherExtension { @Override diff --git a/src/test/java/ignored/Examples.java b/src/test/java/ignored/Examples.java index 4a0060d6ab..73a9779a90 100644 --- a/src/test/java/ignored/Examples.java +++ b/src/test/java/ignored/Examples.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2021 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import com.github.tomakehurst.wiremock.common.Json; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.requestfilter.FieldTransformer; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterAction; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestWrapper; import com.github.tomakehurst.wiremock.extension.requestfilter.StubRequestFilter; @@ -45,8 +44,8 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.testsupport.WireMockResponse; import com.github.tomakehurst.wiremock.verification.LoggedRequest; -import com.google.common.collect.ImmutableMap; import java.util.List; +import java.util.Map; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -167,6 +166,17 @@ public void verifyWithoutHeader() { }); } + @Test + public void verifyWithoutQueryParam() { + assertThrows( + VerificationException.class, + () -> { + verify( + getRequestedFor(urlPathEqualTo("without/queryParam")) + .withoutQueryParam("test-param")); + }); + } + @Test public void findingRequests() { List requests = findAll(putRequestedFor(urlMatching("/api/.*"))); @@ -297,14 +307,14 @@ public void transformerParameters() { .willReturn( aResponse() .withTransformerParameter("newValue", 66) - .withTransformerParameter("inner", ImmutableMap.of("thing", "value")))); + .withTransformerParameter("inner", Map.of("thing", "value")))); System.out.println( get(urlEqualTo("/transform")) .willReturn( aResponse() .withTransformerParameter("newValue", 66) - .withTransformerParameter("inner", ImmutableMap.of("thing", "value"))) + .withTransformerParameter("inner", Map.of("thing", "value"))) .build()); } @@ -706,13 +716,7 @@ public static class UrlAndHeadersModifyingFilter extends StubRequestFilter { public RequestFilterAction filter(Request request) { Request wrappedRequest = RequestWrapper.create() - .transformAbsoluteUrl( - new FieldTransformer() { - @Override - public String transform(String url) { - return url + "extraparam=123"; - } - }) + .transformAbsoluteUrl(url -> url + "extraparam=123") .addHeader("X-Custom-Header", "headerval") .wrap(request); diff --git a/src/test/java/ignored/MassiveNearMissTest.java b/src/test/java/ignored/MassiveNearMissTest.java index a1569b2912..eb25f65383 100644 --- a/src/test/java/ignored/MassiveNearMissTest.java +++ b/src/test/java/ignored/MassiveNearMissTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,10 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.testsupport.WireMockTestClient; -import com.google.common.base.Joiner; import com.google.common.base.Stopwatch; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -69,7 +69,8 @@ public void timeToCalculateBigNearMissDiffXml() { if (i > drop) sum += time; } - System.out.printf("Times:\n%s\n", Joiner.on("\n").join(times)); + System.out.printf( + "Times:\n%s\n", times.stream().map(Object::toString).collect(Collectors.joining("\n"))); long mean = sum / (reps - drop); System.out.printf("Mean: %dms\n", mean); } @@ -122,7 +123,8 @@ public void timeToCalculateBigNearMissDiffJson() { if (i > drop) sum += time; } - System.out.printf("Times:\n%s\n", Joiner.on("\n").join(times)); + System.out.printf( + "Times:\n%s\n", times.stream().map(Object::toString).collect(Collectors.joining("\n"))); long mean = sum / (reps - drop); System.out.printf("Mean: %dms\n", mean); } diff --git a/src/test/resources/filesource/subdir/deepfile.json b/src/test/resources/filesource/subdir/deepfile.json index 0967ef424b..9e26dfeeb6 100644 --- a/src/test/resources/filesource/subdir/deepfile.json +++ b/src/test/resources/filesource/subdir/deepfile.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/src/test/resources/frozen/do-not-throw-generic-exception b/src/test/resources/frozen/do-not-throw-generic-exception index 85642178fa..963a7938ef 100644 --- a/src/test/resources/frozen/do-not-throw-generic-exception +++ b/src/test/resources/frozen/do-not-throw-generic-exception @@ -1,5 +1,4 @@ -Constructor (java.lang.String)> calls constructor (java.lang.String)> in (ClasspathFileSource.java:73) -Method calls constructor (java.lang.Throwable)> in (WireMockServer.java:170) +Method calls constructor (java.lang.Throwable)> in (WireMockServer.java:178) Method calls constructor (java.lang.String)> in (AbstractFileSource.java:138) Method calls constructor (java.lang.String)> in (AbstractFileSource.java:140) Method calls constructor (java.lang.Throwable)> in (AbstractFileSource.java:180) @@ -7,11 +6,8 @@ Method calls constructor (java.lang.String)> in (ClasspathFileSource.java:212) Method calls constructor (java.lang.String)> in (ClasspathFileSource.java:214) Method calls constructor (java.lang.String)> in (ClasspathFileSource.java:114) -Method calls constructor (java.lang.Throwable)> in (HttpClientUtils.java:52) Method calls constructor (java.lang.Throwable)> in (HttpClientUtils.java:37) -Method calls constructor (java.lang.Throwable)> in (StreamSources.java:51) -Method calls constructor (java.lang.String)> in (JettyHttpServer.java:208) -Method calls constructor (java.lang.Throwable)> in (JettyHttpServer.java:198) +Method calls constructor (java.lang.String)> in (JettyHttpServer.java:230) +Method calls constructor (java.lang.Throwable)> in (JettyHttpServer.java:220) Method calls constructor (java.lang.Throwable)> in (WireMockHttpServletRequestAdapter.java:144) -Method calls constructor (java.lang.Throwable)> in (CommandLineOptions.java:241) -Method calls constructor (java.lang.String)> in (InMemoryStubMappings.java:150) \ No newline at end of file +Method calls constructor (java.lang.Throwable)> in (CommandLineOptions.java:241) \ No newline at end of file diff --git a/src/test/resources/frozen/unused-methods b/src/test/resources/frozen/unused-methods index 6d0ec2bd71..547e2e77ee 100644 --- a/src/test/resources/frozen/unused-methods +++ b/src/test/resources/frozen/unused-methods @@ -76,3 +76,9 @@ Method is unreferenced in (StubMappingJsonRecorder.java:198) Method is unreferenced in (JournalBasedResult.java:30) Method is unreferenced in (Diff.java:361) +Method is unreferenced in (WireMock.java:63) +Method is unreferenced in (Json.java:39) +Method is unreferenced in (Xml.java:180) +Method is unreferenced in (Xml.java:197) +Method is unreferenced in (XmlNode.java:43) +Method is unreferenced in (XmlNode.java:52) diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 0000000000..589bcff566 --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %msg%n + + + + + + + + + + + + diff --git a/src/test/resources/not-found-diff-sample_ascii-narrow.txt b/src/test/resources/not-found-diff-sample_ascii-narrow.txt index cd6bc165e3..c4a01d923e 100644 --- a/src/test/resources/not-found-diff-sample_ascii-narrow.txt +++ b/src/test/resources/not-found-diff-sample_ascii-narrow.txt @@ -18,7 +18,8 @@ X-My-Header [contains] : correct | X-My-Header: wrong value <<<<< Header does value | Accept [matches] : text/plain.* | Accept: text/plain | -{ | { <<<<< Body does not match +[equalToJson] | <<<<< Body does not match +{ | { "thing" : { | "thing" : { "stuff" : [ 1, 2, 3 ] | "nothing" : { } } | } diff --git a/src/test/resources/not-found-diff-sample_ascii.txt b/src/test/resources/not-found-diff-sample_ascii.txt index 6243a3c9cd..92014c0065 100644 --- a/src/test/resources/not-found-diff-sample_ascii.txt +++ b/src/test/resources/not-found-diff-sample_ascii.txt @@ -15,7 +15,8 @@ POST | POST X-My-Header [contains] : correct value | X-My-Header: wrong value <<<<< Header does not match Accept [matches] : text/plain.* | Accept: text/plain | -{ | { <<<<< Body does not match +[equalToJson] | <<<<< Body does not match +{ | { "thing" : { | "thing" : { "stuff" : [ 1, 2, 3 ] | "nothing" : { } } | } diff --git a/src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-header.txt b/src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-header.txt new file mode 100644 index 0000000000..e7f21cedcc --- /dev/null +++ b/src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-header.txt @@ -0,0 +1,15 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +GET | GET +[path] /thing | /thing + | +q exactly [equalTo 1 AND equalTo 2 AND equalTo 3] | q: 1 <<<<< Header does not match + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-query-param.txt b/src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-query-param.txt new file mode 100644 index 0000000000..c7b4f4cebc --- /dev/null +++ b/src/test/resources/not-found-diff-sample_exactmatch-for-multiple-values-query-param.txt @@ -0,0 +1,15 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +GET | GET +[path] /thing | /thing?q=2 + | +Query: q exactly [equalTo 1 AND equalTo 2 AND equalTo 3] | q: 2 <<<<< Query does not match + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_form.txt b/src/test/resources/not-found-diff-sample_form.txt new file mode 100644 index 0000000000..b21b9f19bd --- /dev/null +++ b/src/test/resources/not-found-diff-sample_form.txt @@ -0,0 +1,19 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +Query params diff | + | +PUT | PUT +[path] /thing | /thing + | +Form: one = 1 | one: 2 <<<<< Form data does not match +Form: two [contains] two things | two: wrong things <<<<< Form data does not match +Form: three [matches] [a-z]{5} | three: abcde + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_includematch-for-multiple-values-header.txt b/src/test/resources/not-found-diff-sample_includematch-for-multiple-values-header.txt new file mode 100644 index 0000000000..d87bd5f70a --- /dev/null +++ b/src/test/resources/not-found-diff-sample_includematch-for-multiple-values-header.txt @@ -0,0 +1,15 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +GET | GET +[path] /thing | /thing + | +q including [equalTo 1 AND equalTo 2 AND equalTo 3] | q: 1 <<<<< Header does not match + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_includematch-for-multiple-values-query-param.txt b/src/test/resources/not-found-diff-sample_includematch-for-multiple-values-query-param.txt new file mode 100644 index 0000000000..6738f84b9e --- /dev/null +++ b/src/test/resources/not-found-diff-sample_includematch-for-multiple-values-query-param.txt @@ -0,0 +1,16 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +GET | GET +[path] /thing | /thing?q=1 + | +Query: q including [equalTo 1 AND equalTo 2 AND equalTo | q: 1 <<<<< Query does not match +3] | + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_json-path-body-not-json.txt b/src/test/resources/not-found-diff-sample_json-path-body-not-json.txt new file mode 100644 index 0000000000..e9560f6c88 --- /dev/null +++ b/src/test/resources/not-found-diff-sample_json-path-body-not-json.txt @@ -0,0 +1,14 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +ANY | ANY +/thing | /thing + | +$.accountNum [equalTo] 1234 | not json <<<<< Body does not match + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_json-path-no-body.txt b/src/test/resources/not-found-diff-sample_json-path-no-body.txt new file mode 100644 index 0000000000..80269ed0b2 --- /dev/null +++ b/src/test/resources/not-found-diff-sample_json-path-no-body.txt @@ -0,0 +1,14 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +ANY | ANY +/thing | /thing + | +$.accountNum [equalTo] 1234 | <<<<< Body is not present + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_json-schema.txt b/src/test/resources/not-found-diff-sample_json-schema.txt new file mode 100644 index 0000000000..dd58d06dc2 --- /dev/null +++ b/src/test/resources/not-found-diff-sample_json-schema.txt @@ -0,0 +1,28 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +JSON schema stub | + | +POST | POST +/thing | /thing + | +[matchesJsonSchema] | <<<<< Body does not match +{ | { + "type" : "object", | "handle": "Rex" + "required" : [ "name" ], | } + "properties" : { | + "name" : { | + "type" : "string" | + }, | + "tag" : { | + "type" : "string" | + } | + } | +} | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_large_json.txt b/src/test/resources/not-found-diff-sample_large_json.txt index d7038322b1..c4d7df5c98 100644 --- a/src/test/resources/not-found-diff-sample_large_json.txt +++ b/src/test/resources/not-found-diff-sample_large_json.txt @@ -14,19 +14,21 @@ POST | POST | Accept: text/plain | Accept: text/plain | -{ | { <<<<< Body does not match +[equalToJson] | <<<<< Body does not match +{ | { "one" : { | "one" : { "two" : { | "two" : { - "three" : { | "three" : { -"four" : { | "four" : { - "five" : { | "five" : { - "six" : | "six" : "totally_the_wrong_value" -"superduperlongvaluethatshouldwrapokregardless_superduper | } -longvaluethatshouldwrapokregardless_superduperlongvalueth | } -atshouldwrapokregardless_superduperlongvaluethatshouldwra | } -pokregardless" | } - } | } - } | } + "three" : | "three" : { +{ | "four" : { + "four" : { | "five" : { + "five" : { | "six" : "totally_the_wrong_value" +"six" : | } +"superduperlongvaluethatshouldwrapokregardless_superduper | } +longvaluethatshouldwrapokregardless_superduperlongvalueth | } +atshouldwrapokregardless_superduperlongvaluethatshouldwra | } +pokregardless" | } + } | } + } | } | } | } | diff --git a/src/test/resources/not-found-diff-sample_large_json_windows.txt b/src/test/resources/not-found-diff-sample_large_json_windows.txt index 0fc2bffb0b..eb434abe77 100644 --- a/src/test/resources/not-found-diff-sample_large_json_windows.txt +++ b/src/test/resources/not-found-diff-sample_large_json_windows.txt @@ -14,13 +14,14 @@ POST | POST | Accept: text/plain | Accept: text/plain | -{ | { <<<<< Body does not match +[equalToJson] | <<<<< Body does not match +{ | { "one" : { | "one" : { "two" : { | "two" : { - "three" : { | "three" : { -"four" : { | "four" : { +"three" : { | "three" : { + "four" : { | "four" : { "five" : { | "five" : { - "six" : | "six" : "totally_the_wrong_value" +"six" : | "six" : "totally_the_wrong_value" "superduperlongvaluethatshouldwrapokregardless_superduper | } longvaluethatshouldwrapokregardless_superduperlongvalueth | } atshouldwrapokregardless_superduperlongvaluethatshouldwra | } diff --git a/src/test/resources/not-found-diff-sample_large_xml_jre11.txt b/src/test/resources/not-found-diff-sample_large_xml_jre11.txt index 54793ea3a0..b3036c6501 100644 --- a/src/test/resources/not-found-diff-sample_large_xml_jre11.txt +++ b/src/test/resources/not-found-diff-sample_large_xml_jre11.txt @@ -12,10 +12,10 @@ and let us see exactly how that looks when it is done | POST | POST /thing | /thing | - | <<<<< Body does not match - | - | id="2"> +[equalToXml] | <<<<< Body does not match + | + | | id="2"> | | | diff --git a/src/test/resources/not-found-diff-sample_large_xml_jre11_windows.txt b/src/test/resources/not-found-diff-sample_large_xml_jre11_windows.txt index 2e7ee3da20..c856d8e7e0 100644 --- a/src/test/resources/not-found-diff-sample_large_xml_jre11_windows.txt +++ b/src/test/resources/not-found-diff-sample_large_xml_jre11_windows.txt @@ -12,21 +12,23 @@ and let us see exactly how that looks when it is done | POST | POST /thing | /thing | - | <<<<< Body does not match - | - | - | - | - | - | - Super wrong bit of text | Super long bit of text that -that should push it way over the length limit! | should push it way over the length limit! - | - | - | - | - | - | - | +[equalToXml] | <<<<< Body does not match + | + | + | + +id="3"> | + | + | Super long bit of text that + | should push it way over the length limit! + Super wrong bit of text | +that should push it way over the length limit! | + | + | + | + | + | + | + | | ----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_multipart.txt b/src/test/resources/not-found-diff-sample_multipart.txt index ac6f1a9a42..35aff9f726 100644 --- a/src/test/resources/not-found-diff-sample_multipart.txt +++ b/src/test/resources/not-found-diff-sample_multipart.txt @@ -17,7 +17,8 @@ POST | POST Content-Disposition [contains] : name="part_one" | <<<<< Header is not present X-My-Stuff [contains] : stuff_parts | X-My-Stuff: wrong value <<<<< Header does not match | -Some expected text.* | Wrong body <<<<< Body does not match +[matches] | <<<<< Body does not match +Some expected text.* | Wrong body | [/Multipart] | [/part_one] | @@ -27,7 +28,8 @@ Some expected text.* | Wrong body Content-Disposition [contains] : name="part_one" | <<<<< Header is not present X-My-Stuff [contains] : stuff_parts | <<<<< Header is not present | -Some expected text.* | Correct body <<<<< Body does not match +[matches] | <<<<< Body does not match +Some expected text.* | Correct body | [/Multipart] | [/part_two] | @@ -36,7 +38,8 @@ Some expected text.* | Correct body | X-More [contains] : stuff_parts | <<<<< Header is not present | -Correct body | Wrong body <<<<< Body does not match +[equalTo] | <<<<< Body does not match +Correct body | Wrong body | [/Multipart] | [/part_one] | @@ -45,6 +48,8 @@ Correct body | Wrong body | X-More [contains] : stuff_parts | <<<<< Header is not present | +[equalTo] | <<<<< Body does not match +Correct body | Correct body | [/Multipart] | [/part_two] | diff --git a/src/test/resources/not-found-diff-sample_url-path-parameters.txt b/src/test/resources/not-found-diff-sample_url-path-parameters.txt new file mode 100644 index 0000000000..343ba2b86d --- /dev/null +++ b/src/test/resources/not-found-diff-sample_url-path-parameters.txt @@ -0,0 +1,15 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +GET | GET +[path template] /contacts/{contactId} | /contacts/345 + | +Path parameter: contactId = 123 | contactId: 345 <<<<< Path parameter does not match + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/not-found-diff-sample_url-template.txt b/src/test/resources/not-found-diff-sample_url-template.txt new file mode 100644 index 0000000000..c9eaada834 --- /dev/null +++ b/src/test/resources/not-found-diff-sample_url-template.txt @@ -0,0 +1,13 @@ + + Request was not matched + ======================= + +----------------------------------------------------------------------------------------------------------------------- +| Closest stub | Request | +----------------------------------------------------------------------------------------------------------------------- + | +GET | GET +[path template] /contacts/{contactId} | /contracts/12345 <<<<< URL does not match + | + | +----------------------------------------------------------------------------------------------------------------------- diff --git a/src/test/resources/remoteloader/__files/body.xml b/src/test/resources/remoteloader/__files/body.xml new file mode 100644 index 0000000000..d264440900 --- /dev/null +++ b/src/test/resources/remoteloader/__files/body.xml @@ -0,0 +1 @@ +Buy milkCancel newspaper subscription \ No newline at end of file diff --git a/src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json b/src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json new file mode 100644 index 0000000000..e669b2a304 --- /dev/null +++ b/src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json @@ -0,0 +1,37 @@ +{"mappings": [ + { + "request": { + "method": "GET", + "url": "/todo/items" + }, + "response": { + "body": "Buy milk<\/item><\/items>", + "status": 200 + }, + "requiredScenarioState": "Started", + "scenarioName": "To do list" + }, + { + "request": { + "method": "POST", + "bodyPatterns": [{"contains": "Cancel newspaper subscription"}], + "url": "/todo/items" + }, + "newScenarioState": "Cancel newspaper item added", + "response": {"status": 201}, + "requiredScenarioState": "Started", + "scenarioName": "To do list" + }, + { + "request": { + "method": "GET", + "url": "/todo/items" + }, + "response": { + "bodyFileName": "body.xml", + "status": 200 + }, + "requiredScenarioState": "Cancel newspaper item added", + "scenarioName": "To do list" + } +]} diff --git a/src/test/resources/schema-validation/draft-7.invalid.json b/src/test/resources/schema-validation/draft-7.invalid.json new file mode 100644 index 0000000000..e25f68dedf --- /dev/null +++ b/src/test/resources/schema-validation/draft-7.invalid.json @@ -0,0 +1,9 @@ +{ + "required": "level", + "properties": { + "level": { + "type": "integer", + "exclusiveMaximum": 10 + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/draft-7.schema.json b/src/test/resources/schema-validation/draft-7.schema.json new file mode 100644 index 0000000000..7f3e6c7980 --- /dev/null +++ b/src/test/resources/schema-validation/draft-7.schema.json @@ -0,0 +1,11 @@ +{ + "required": [ + "level" + ], + "properties": { + "level": { + "type": "integer", + "exclusiveMaximum": 10 + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/has-ref.schema.json b/src/test/resources/schema-validation/has-ref.schema.json new file mode 100644 index 0000000000..4ddf4a78b9 --- /dev/null +++ b/src/test/resources/schema-validation/has-ref.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "required": [ "things" ], + "properties": { + "things": { + "type": "array", + "items": { "$ref": "#/definitions/single" } + } + }, + "definitions": { + "single": { + "type": "integer" + } + } +} diff --git a/src/test/resources/schema-validation/invalid.schema.json b/src/test/resources/schema-validation/invalid.schema.json new file mode 100644 index 0000000000..942675ae60 --- /dev/null +++ b/src/test/resources/schema-validation/invalid.schema.json @@ -0,0 +1,3 @@ +{ + "properties": [1,2,3] +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/new-pet.invalid.json b/src/test/resources/schema-validation/new-pet.invalid.json new file mode 100644 index 0000000000..e1352ebc33 --- /dev/null +++ b/src/test/resources/schema-validation/new-pet.invalid.json @@ -0,0 +1,3 @@ +{ + "handle": "Rex" +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/new-pet.json b/src/test/resources/schema-validation/new-pet.json new file mode 100644 index 0000000000..4334dbe9f8 --- /dev/null +++ b/src/test/resources/schema-validation/new-pet.json @@ -0,0 +1,4 @@ +{ + "name": "Rex", + "tag": "dog" +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/new-pet.schema.json b/src/test/resources/schema-validation/new-pet.schema.json new file mode 100644 index 0000000000..6954e0c639 --- /dev/null +++ b/src/test/resources/schema-validation/new-pet.schema.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/new-pet.unparseable.json b/src/test/resources/schema-validation/new-pet.unparseable.json new file mode 100644 index 0000000000..07aed11528 --- /dev/null +++ b/src/test/resources/schema-validation/new-pet.unparseable.json @@ -0,0 +1,3 @@ +{ + "name": "Rex", + "tag": "dog" diff --git a/src/test/resources/schema-validation/numeric.schema.json b/src/test/resources/schema-validation/numeric.schema.json new file mode 100644 index 0000000000..bef1bf5917 --- /dev/null +++ b/src/test/resources/schema-validation/numeric.schema.json @@ -0,0 +1,4 @@ +{ + "type": "number", + "maximum": 100 +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/recursive.schema.json b/src/test/resources/schema-validation/recursive.schema.json new file mode 100644 index 0000000000..02cf40ad14 --- /dev/null +++ b/src/test/resources/schema-validation/recursive.schema.json @@ -0,0 +1,16 @@ +{ + "#ref": "#/schemas/Person", + "schemas": { + "Person": { + "name": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "#ref": "#/schemas/Person" + } + } + } + } +} diff --git a/src/test/resources/schema-validation/shop-order.schema.json b/src/test/resources/schema-validation/shop-order.schema.json new file mode 100644 index 0000000000..98ac07f651 --- /dev/null +++ b/src/test/resources/schema-validation/shop-order.schema.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "required": [ + "itemCatalogueId", + "quantity" + ], + "properties": { + "itemCatalogueId": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "fastDelivery": { + "type": "boolean" + } + } +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/shop-order.slightly-wrong.json b/src/test/resources/schema-validation/shop-order.slightly-wrong.json new file mode 100644 index 0000000000..e5e7711321 --- /dev/null +++ b/src/test/resources/schema-validation/shop-order.slightly-wrong.json @@ -0,0 +1,5 @@ +{ + "itemCatalogueId": "abc123", + "quantity": 1, + "fastDelivery": "yesplease" +} \ No newline at end of file diff --git a/src/test/resources/schema-validation/stringy.schema.json b/src/test/resources/schema-validation/stringy.schema.json new file mode 100644 index 0000000000..e175fdc539 --- /dev/null +++ b/src/test/resources/schema-validation/stringy.schema.json @@ -0,0 +1,4 @@ +{ + "type": "string", + "maxLength": 5 +} \ No newline at end of file diff --git a/src/test/resources/test-truststore.jceks b/src/test/resources/test-truststore.jceks new file mode 100644 index 0000000000000000000000000000000000000000..582004cef5c9ca78e29eca7290af396700d0b6e7 GIT binary patch literal 3152 zcmc)MX*kqt9|!Q+$P6ZHWRH-2W*QlUDNA-@tf2-o)KNk?d=;Y}} z@FD{MzzD9U7aRZpWC=lqEFc6Jp)!m>2v8ct2}B%*FirK68b8-FrGUU-RxE@Hc`EHZTK)u-cc+vW*9vobo7Qfk+Uz(nXLs59o3b;h{494@nkja+PzF~ecZfJ@2J z;Pi{^Ae!zA!I+j8b3%Ng7bosxmunJR2RP7$^}}_J+`jBYXPh`QQ_Z>Ztb}9ZlEOpl z)+)XXaH+R(j{B)zb8@)jy%39n%3Kb5Et&;asj{6Q)6Ac7JKFBp{T0i?Csr2~NSv6p zwJ*&;n7wj1`TEkd3I-iCZ>NKg|E*GOExkbj{Vm$2AhBYa_b%2wZP!2B=Llg}BJ+)F zgKU?2^5+x|tFtevv>%!UqPo_Tl+K&!GpERh3bZ0hWJVJw!Vqz%Zt6OqSvvI1I~TAt zl89QOCNiVGTdN_6Hs7cpEr8pRwg_D3ePpP{GOiir+(;h}v04ep-;#*DlJT0fyWD3- zUrSTSA1-_H^ppV7DEEVR4&TcvsElwc&wDwQ84;YySas}fDL&^zr_OfCzSL|kJR5D` zr@G$W?SZ>fr&^u2v9xxFZ*xoe^IM3W9Yxa!lHz`;`evojlqBzfzDO66o^%d>jN2c+ z$(Nxm5-_P>lPOmvp;E=J^zyF1U%GgZMMI*fmDHnB+;EW3M&Hw&Y3`9zi+DX61R5+y z^=@S_g6xCitQ#o?YD5-enMF~N!BLs8k{@m5{dVLx(^$r?DC2aU3aL5ErLJt zS#(8ei?+@EnN_wfsNchC73z-m9U#Tc4cU~9H<}qT2z82{C(|Tj1NtxTE^da`V}$rM z79wy*rYRu<8cR%|QNFduM<;USZuPF}&%UcYyIW1w=!pTyMqbo=!$u?Yby_iC;OxD{ zYp|&_Ex)me?{j_zp*owh3qchN996fN>?a4#WZ_DoQQF+qP<`1V8M6I&Qd*fC8U<_N zty0fYk9uM;-Jg_P7Q-Vdsn$UVXUx3dPGzgBO?8B-7K~mC)M_!oQAiGr=?(%A+Ov>b zx^&)0%J<*gu*DbILFKln8sRw2efo+1jWzXISM#XMPzM5-aQ(tuXmXG1BKexsHh)q( z=R}yGUGQf{m%_a~jiF+&hxzNn${qX(fdB?;c}0XW001T-s9-k)6?8rq3<82c z5VA=*BZ8F?DoxX+ZLk7?%nSeo0m+PjGJ-82jGQ0~^Iw696J)H3WJADzR1R?-GQ)ck z++19Lm0_G9GhHML0`{ZK%n8;zV}uky@cl?&7EWi9o0pfHlPlJj;OvSg%R70K5JEhx zNHh|O#2`>8Bm!-F5ET(9K?TA&Ma)q;v*b#qmABj$}>i3j~6p<}uvqvT}L>G#rFcJt%(r+d`lt4H*ElPT-ccYu@u4lREk_X6DyNhenDbXVl|6^oBInoEYSTIEF&XpX$xMMueT z)~veCX0tCp;2?WFMq-Msd+$HLYmIrQw2M)dc4k799?&~@iLj7;ulSJ(&Q1hhGV;%N zNY9JEL`ENoj64uY`YF;N!Fv23vqbp)a>vMd$imCR%iG`U*ACOa?Ht(rb%q^r_y<53 zCzHRMFM;Ik!S;t#PN=816W-I++mHO0PZScRj8;7G ziT;D@&-njo>c44yb)lA9Ys@EtTCJ|BFS=(@6cq0A(K6E9PZ)nwE8x`R*LYwWpI(qO zSBjXGu)nXx@>Mei{;RhXzUW#HSX`^mf{4AIG{5xJLupMcfW6H*ruq9wY|G%&OA67| zT`SVueKCN%8l49d2-a*_y+qZhtwr_Z+@zr4c|r{10AJL|bI&HCd@l=T!bGXw zdWRv6zix1Sl;lafD7v4TGE%4$Jg!;RDwp8R{rH&;cg`M9Z6f{p#l6QS4)Isfb0@XN z4j1mD`{@a;_Wwz1!ilS1&S%mZSv8{Hf#bIHRd3^1{&B%C0-WO??L!B0pm(}B0C1tmq`hi4Q;?b>uXyIBX z@zE;TL{;r^Bbfz(UDov*FTut7I#JO?Lg^d-0l}#YGkT(pVUe!Bq{6Nu_Z(zrWMtO{ z7Zdv=4d1pzCSkR-;cwsO&a&2zlFHh+V$Xz{tzfvZlMJYzTN>WCg4& z(uvP_cT}5l1F5RPwx{&_kjDdDc(}J%f|aP%h9O%Mmw^KI)cAApuZcTdaXBifhq}w9 GBK`$mxFm1@ literal 0 HcmV?d00001 diff --git a/test-extension/build.gradle b/test-extension/build.gradle new file mode 100644 index 0000000000..3f9c6e802e --- /dev/null +++ b/test-extension/build.gradle @@ -0,0 +1,24 @@ +buildscript { + repositories { + maven { + url "https://oss.sonatype.org" + } + mavenCentral() + } +} + +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' +} + +group 'org.wiremock.example' + +dependencies { + compileOnly project(":") +} + +shadowJar { + archiveBaseName.set('wiremock-test-extension') + archiveClassifier.set('') +} diff --git a/test-extension/src/main/java/org/wiremock/FactoryLoaderTestExtension.java b/test-extension/src/main/java/org/wiremock/FactoryLoaderTestExtension.java new file mode 100644 index 0000000000..57753d7896 --- /dev/null +++ b/test-extension/src/main/java/org/wiremock/FactoryLoaderTestExtension.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wiremock; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.core.Admin; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public class FactoryLoaderTestExtension implements ResponseDefinitionTransformerV2 { + + private final Admin admin; + + public FactoryLoaderTestExtension(Admin admin) { + this.admin = admin; + } + + @Override + public String getName() { + return "loader-test"; + } + + @Override + public boolean applyGlobally() { + return false; + } + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + final int requestCount = admin.getServeEvents().getServeEvents().size(); + return ResponseDefinitionBuilder.responseDefinition() + .withStatus(200) + .withBody("Request count " + requestCount) + .build(); + } +} diff --git a/test-extension/src/main/java/org/wiremock/InstanceLoaderTestExtension.java b/test-extension/src/main/java/org/wiremock/InstanceLoaderTestExtension.java new file mode 100644 index 0000000000..bd60c78054 --- /dev/null +++ b/test-extension/src/main/java/org/wiremock/InstanceLoaderTestExtension.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wiremock; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformerV2; +import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +public class InstanceLoaderTestExtension implements ResponseDefinitionTransformerV2 { + + @Override + public String getName() { + return "instance-loader-test"; + } + + @Override + public boolean applyGlobally() { + return false; + } + + @Override + public ResponseDefinition transform(ServeEvent serveEvent) { + return ResponseDefinitionBuilder.responseDefinition() + .withStatus(200) + .withBody("Expected stuff") + .build(); + } +} diff --git a/test-extension/src/main/java/org/wiremock/LoaderTestExtensionFactory.java b/test-extension/src/main/java/org/wiremock/LoaderTestExtensionFactory.java new file mode 100644 index 0000000000..c1c0ca879c --- /dev/null +++ b/test-extension/src/main/java/org/wiremock/LoaderTestExtensionFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wiremock; + +import com.github.tomakehurst.wiremock.extension.Extension; +import com.github.tomakehurst.wiremock.extension.ExtensionFactory; +import com.github.tomakehurst.wiremock.extension.WireMockServices; +import java.util.List; + +public class LoaderTestExtensionFactory implements ExtensionFactory { + + @Override + public List create(WireMockServices services) { + return List.of(new FactoryLoaderTestExtension(services.getAdmin())); + } +} diff --git a/test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.Extension b/test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.Extension new file mode 100644 index 0000000000..fe4496185b --- /dev/null +++ b/test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.Extension @@ -0,0 +1 @@ +org.wiremock.InstanceLoaderTestExtension \ No newline at end of file diff --git a/test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.ExtensionFactory b/test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.ExtensionFactory new file mode 100644 index 0000000000..4588b1f03f --- /dev/null +++ b/test-extension/src/main/resources/META-INF/services/com.github.tomakehurst.wiremock.extension.ExtensionFactory @@ -0,0 +1 @@ +org.wiremock.LoaderTestExtensionFactory \ No newline at end of file diff --git a/test-extension/test-extension.jar b/test-extension/test-extension.jar new file mode 100644 index 0000000000000000000000000000000000000000..1037313ea070a562bb84d6e3bc17dcc935490b47 GIT binary patch literal 3596 zcmb_f2{_bS8y}f!h;*B=-w1_k86$C3gUB|<*h4CeG1iNi8C#1)V~vRHp@_=Zw`(ms zgNTSmpApIu*(XcBaVyo-{qFPJ=R5P9nKRGx{(kR!&iTLZ`}-RpnSl@h00aU60IKu} z0POhd8vtOT|DrTZRHYGmnlj7)gYOIr{dUao(+zm(lMerCh*H%9vd0k!9YgGZ+ivO*KQv6KREo>hW)dRm!k*P#SP>9gRw_L zicKLi05D76l{BNVCK`ix^YGDiL)&6KOt3h-hBqGTigR>xg<}3d<8Y+YE?!^_$Q4n? zO2-Np$5B*{LH-MrLuIhA6CizJ&y!3C;{cgd(IlZW6$2raMUC^w^WdxIs-Wb}clCIz zT+o)U4i=hi$?pz0HMe1j}eR>jc5AOkxL25X%A{lX~~H$GZ#gEU1U-}Y`G7A$FwRV+P}85-QYtdyQu zD1eHvmJT=}+E(s7BBv-igHA!aK6ct~n|Ec!IWiD=CU(tTWVxLK?TgE65zeth=;)gj z!f2d!@o7`fMN($Q3!m4{AmRq3D?ZdzFJDu-&a+nGS@uz87oqFb<;d7KFC(}eDIwhY z*_pz#A=*YV9K+9-lsw!)w~z&IjH_Fmy@!XaGZq8-&dPMpLr&@z^BOv9$9U-bPgL;G9svp?mkvMiUZ+PEV!PIis!1@0YPP zGJ%wwDtKsWWy5i7Mb!tZtf;XktcbS$iN*bx$im7wA5W9JwkSO)4NxX9e zh!8t5xCq~Q=)?^jIlinpJ8qm%ls_K2(WCrA+XFAeBzq#S@*tc2d((b=jdP;TCM|l5 zg1i0TGTD)pg!vGtM<_R?a%bGJ2}X0*>&w<%cRRHVoC>Us52mPt*K^$Fq z>o+F&eQAnZ*0`!iFJcZm0RSjt@*`Yvc(f}9`}f%&gLLaTxvzOM$t!G)^)N*I=Dc=F zS1*Xg9i*0hG)>1e$|07ytfUB0N~nseo_T%3{VG!RS49zJh)r?@N6B@Q?6`y_?<<=J zo`$bat*i(FEQco)n_yOthukUk8!uw^mR3&hu{jnE+O$*| zx;%klkybfcm$iUNlsR#u;#hJu7l-Y7GElBU>=ZGPbS<|;lDXS#rnF={C~++}pVJL2 zqc%k8spH6;=V$wn+cj0CznF8Tq>Q$=3_jyUVn3i=i(~;`g1uk3tqvjS%A4=b9sAlUsfe53DM{aXIiEx+!S^fS{vB7vu&FWJsuP;=6-8~INNBm6=DbhmEa@A z2Q!p6s0R~U$6uW+w5X@SA2 zQ@r(4h8DSE5A?Y~B=u+aJRDg9aI-w79o+#34j}ax%q)XvwRu6HahwbSDxg0 z$eGtLrk;2qeN5dFFjwqCMf~ceT`4Np??-l@p=fty0#TVNDMH+OQ4cVMfk`X_cO616 z&GA2zqxoFbmA!bC!Za@?ACrA9O)WHRktEvqFvFNvB5v=5`Z47WYF~ch%Tpga8J^5u zP3p-$CIG;U-sFC0OFwxcKX2EKjLW;NqKk!J|VtlieH5! zgoi^mP-sGdAkaBCOO6OTqA)&Naylfc5`N?^5+3{K^UGP!bd&HXZTo4}u$fz?ELxNs z2ct!)1)kMa^(lyk+{rNoTy0>l{&7F8bOLKh>`;#pmINF8<>IwBdMiE|a$0Tug-?X) z=WDg)!b=IstZ`>`7V7;_3lbqITzMsG$K(e^%Zx}9cV~*YJF5h3uY7MomX^I177^S|k;} zw&fj&vJbLOZNsGY`SK<=5iB;qfF_vUZQrXbYMi5|ONo;(s|wBmn=cI z6sJ#(pCT}BE>L?%yaT}oig$BCJ7XOP9ymPoi*|xyx7E+K4kF9w^h-XK(&^~X@9bM> zmxLnPk$Ow5`nm{dR)jRvog|LvkfaMbMo|b13g7b^JqR)S)ct=2^EnB=kLkx)a7bNf ztIR_nU0O2jfH42Pq@~A2ANv6B{!+W!C(HQS)(*E}-^g7?hAqSwExn~` zKaDNNp7iCG#{FN2EsgtWY#|cq%Pqv0Tl)dZsCd7CGyIzwq5q_R8KYwCr(dZX=-c`( zs(kC7Mf5A`j q|02&AT{7m+SC`7HKMVO^xcq$i2FRVew%A$epTqPQrEWSu0PtU3WqZy5 literal 0 HcmV?d00001 diff --git a/ui/package-lock.json b/ui/package-lock.json index 2d84548fdc..490b1b9023 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "wiremock-ui-resources", - "version": "2.33.2", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/ui/package.json b/ui/package.json index bf1b2e8f44..d8bf082b20 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "wiremock-ui-resources", - "version": "2.35.1", + "version": "3.0.4", "description": "WireMock UI resources processor", "engines": { "node": ">= 0.10.0" diff --git a/wiremock-webhooks-extension/build.gradle b/wiremock-webhooks-extension/build.gradle index 43f6df37ba..f403a46046 100644 --- a/wiremock-webhooks-extension/build.gradle +++ b/wiremock-webhooks-extension/build.gradle @@ -31,36 +31,45 @@ dependencies { implementation("com.github.jknack:handlebars-helpers:$versions.handlebars") { exclude group: 'org.mozilla', module: 'rhino' } - implementation "com.google.guava:guava:$versions.guava" - implementation "org.apache.httpcomponents.client5:httpclient5:5.1.3" + implementation "com.google.guava:guava:$versions.guava", { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + } + implementation "org.apache.httpcomponents.client5:httpclient5:5.2.1" testImplementation project(":") - testImplementation(platform('org.junit:junit-bom:5.9.1')) + testImplementation(platform('org.junit:junit-bom:5.10.0')) testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.hamcrest:hamcrest-core:2.2" testImplementation "org.hamcrest:hamcrest-library:2.2" testImplementation 'org.awaitility:awaitility:4.2.0' } +jar.enabled = false + shadowJar { - baseName = 'wiremock-webhooks-extension' - classifier = '' + archiveBaseName.set('wiremock-webhooks-extension') + archiveClassifier.set('') relocate "org.apache", 'wiremock.webhooks.org.apache' + relocate "org.slf4j", "wiremock.webhooks.org.slf4j" + relocate "com.google", 'wiremock.webhooks.com.google' + relocate "com.fasterxml.jackson", 'wiremock.webhooks.com.fasterxml.jackson' + relocate "org.checkerframework", "wiremock.webhooks.org.checkerframework" + relocate "com.github.jknack", "wiremock.webhooks.com.github.jknack" } task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier.set('sources') from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier.set('javadoc') from javadoc.destinationDir } task testJar(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier.set('tests') from sourceSets.test.output } @@ -84,7 +93,7 @@ publishing { publications { main(MavenPublication) { publication -> - artifactId = "${jar.baseName}" + artifactId = "${jar.getArchiveBaseName().get()}" project.shadow.component(publication) artifact sourcesJar @@ -100,8 +109,12 @@ publishing { } } -publish.dependsOn shadowJar +project.tasks.signMainPublication.dependsOn jar task release { dependsOn clean, shadowJar, publishAllPublicationsToMavenRepository } + +task localRelease { + dependsOn clean, assemble, publishToMavenLocal +} diff --git a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/WebhookDefinition.java b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/WebhookDefinition.java index 26476c9df8..b44e532683 100644 --- a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/WebhookDefinition.java +++ b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/WebhookDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Thomas Akehurst + * Copyright (C) 2021-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.wiremock.webhooks; import static com.github.tomakehurst.wiremock.common.Encoding.decodeBase64; -import static com.google.common.collect.Lists.newArrayList; import static java.util.Collections.singletonList; import com.fasterxml.jackson.annotation.*; @@ -175,7 +174,7 @@ public WebhookDefinition withHeaders(List headers) { public WebhookDefinition withHeader(String key, String... values) { if (headers == null) { - headers = newArrayList(); + headers = new ArrayList<>(); } headers.add(new HttpHeader(key, values)); diff --git a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java index 4f722c4dd0..5e2f881e42 100644 --- a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java +++ b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java @@ -62,7 +62,15 @@ private Webhooks( this.httpClient = httpClient; this.transformers = transformers; - this.templateEngine = new TemplateEngine(Collections.emptyMap(), null, Collections.emptySet()); + this.templateEngine = TemplateEngine.defaultTemplateEngine(); + } + + private Webhooks(List transformers, NetworkAddressRules targetAddressRules) { + this(Executors.newScheduledThreadPool(10), createHttpClient(targetAddressRules), transformers); + } + + public Webhooks(NetworkAddressRules targetAddressRules) { + this(new ArrayList<>(), targetAddressRules); } private Webhooks(List transformers, NetworkAddressRules targetAddressRules) { diff --git a/wiremock-webhooks-extension/src/test/java/testsupport/WireMockResponse.java b/wiremock-webhooks-extension/src/test/java/testsupport/WireMockResponse.java index 962ba155be..ed1ac97917 100644 --- a/wiremock-webhooks-extension/src/test/java/testsupport/WireMockResponse.java +++ b/wiremock-webhooks-extension/src/test/java/testsupport/WireMockResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,11 @@ package testsupport; import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArrayAndCloseStream; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.collect.Iterables.getFirst; +import static com.github.tomakehurst.wiremock.common.ParameterUtils.getFirst; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Multimap; -import java.nio.charset.Charset; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; @@ -43,7 +42,7 @@ public String content() { if (content == null) { return null; } - return new String(content, Charset.forName(UTF_8.name())); + return new String(content, UTF_8); } public byte[] binaryContent() { diff --git a/wiremock-webhooks-extension/src/test/java/testsupport/WireMockTestClient.java b/wiremock-webhooks-extension/src/test/java/testsupport/WireMockTestClient.java index 817a3aaaed..ef26f65350 100755 --- a/wiremock-webhooks-extension/src/test/java/testsupport/WireMockTestClient.java +++ b/wiremock-webhooks-extension/src/test/java/testsupport/WireMockTestClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2021 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package testsupport; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; +import static com.github.tomakehurst.wiremock.common.Strings.isNullOrEmpty; import static com.github.tomakehurst.wiremock.http.MimeType.JSON; -import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.HttpURLConnection.HTTP_CREATED; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; From e6c85e3bc47b28786f656cb0dce02f876795ea5b Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 15:25:53 +0200 Subject: [PATCH 2/9] Adjustments after migration to 3.x.x --- build.gradle | 2 +- .../admin/tasks/DisableProxyTask.java | 6 +-- .../wiremock/admin/tasks/EnableProxyTask.java | 6 +-- .../admin/tasks/GetProxyConfigTask.java | 6 +-- .../admin/tasks/GetStubFilesTask.java | 6 +-- .../wiremock/core/ProxyHandler.java | 4 +- .../wiremock/core/WireMockApp.java | 4 +- .../wiremock/core/WireMockConfiguration.java | 39 +++++-------------- .../wiremock/http/StubRequestHandler.java | 4 +- .../wiremock/jetty/JettyHttpServer.java | 34 ++++++++-------- .../wiremock/jetty/websockets/Message.java | 2 +- .../jetty/websockets/WebSocketEndpoint.java | 6 +-- .../standalone/JsonFileMappingsSource.java | 9 ++--- 13 files changed, 52 insertions(+), 76 deletions(-) diff --git a/build.gradle b/build.gradle index 22ed597698..705d456073 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ dependencies { api "org.eclipse.jetty:jetty-alpn-java-server" api "org.eclipse.jetty:jetty-alpn-java-client" api "org.eclipse.jetty:jetty-alpn-client" - api "org.eclipse.jetty.websocket:javax-websocket-server-impl" + api "org.eclipse.jetty.websocket:websocket-jakarta-server" api "io.jsonwebtoken:jjwt-api:0.10.6" api "io.jsonwebtoken:jjwt-impl:0.10.6" diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java index c6286e5e3e..91e5e048e0 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java @@ -1,10 +1,10 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.UUID; @@ -13,7 +13,7 @@ */ public class DisableProxyTask implements AdminTask { @Override - public ResponseDefinition execute(final Admin admin, final Request request, final PathParams pathParams) { + public ResponseDefinition execute(final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { final String idString = pathParams.get("id"); final UUID id = UUID.fromString(idString); admin.disableProxy(id); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java index b108833c7c..e6df968b27 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java @@ -1,10 +1,10 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import java.util.UUID; @@ -13,7 +13,7 @@ */ public class EnableProxyTask implements AdminTask { @Override - public ResponseDefinition execute(final Admin admin, final Request request, final PathParams pathParams) { + public ResponseDefinition execute(final Admin admin, ServeEvent serveEvent, final PathParams pathParams) { final String idString = pathParams.get("id"); final UUID id = UUID.fromString(idString); admin.enableProxy(id); diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java index de928ed7e8..4414824135 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java @@ -1,17 +1,17 @@ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; /** * @author Christopher Holomek */ public class GetProxyConfigTask implements AdminTask { @Override - public ResponseDefinition execute(final Admin admin, final Request request, final PathParams pathParams) { + public ResponseDefinition execute(final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { return ResponseDefinition.okForJson(admin.getProxyConfig()); } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java index d68679c5ed..e8681aeadc 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java @@ -24,17 +24,17 @@ import java.util.List; import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.admin.model.PathParams; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.common.TextFile; import com.github.tomakehurst.wiremock.core.Admin; -import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.ResponseDefinition; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetStubFilesTask implements AdminTask { @Override - public ResponseDefinition execute(final Admin admin, final Request request, final PathParams pathParams) { + public ResponseDefinition execute(final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { try { final FileSource fileSource = admin.getOptions().filesRoot().child(FILES_ROOT); final TextFile textFile = fileSource.getTextFileNamed(pathParams.get("0")); diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java b/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java index fbf42b1bdf..994fa59daa 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java @@ -2,8 +2,8 @@ import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; import com.github.tomakehurst.wiremock.http.ResponseDefinition; -import com.github.tomakehurst.wiremock.jetty9.websockets.Message; -import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; +import com.github.tomakehurst.wiremock.jetty.websockets.Message; +import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import java.util.HashMap; diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java index 4db77c4c1d..5940279446 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java @@ -28,8 +28,8 @@ import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; import com.github.tomakehurst.wiremock.global.GlobalSettings; import com.github.tomakehurst.wiremock.http.*; -import com.github.tomakehurst.wiremock.jetty9.websockets.Message; -import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; +import com.github.tomakehurst.wiremock.jetty.websockets.Message; +import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension; import com.github.tomakehurst.wiremock.matching.RequestPattern; import com.github.tomakehurst.wiremock.matching.StringValuePattern; diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java index 4e4126d8de..642c8eb793 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockConfiguration.java @@ -23,19 +23,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import com.github.tomakehurst.wiremock.common.AsynchronousResponseSettings; -import com.github.tomakehurst.wiremock.common.BrowserProxySettings; -import com.github.tomakehurst.wiremock.common.ClasspathFileSource; -import com.github.tomakehurst.wiremock.common.DataTruncationSettings; -import com.github.tomakehurst.wiremock.common.FileSource; -import com.github.tomakehurst.wiremock.common.HttpsSettings; -import com.github.tomakehurst.wiremock.common.JettySettings; -import com.github.tomakehurst.wiremock.common.Limit; -import com.github.tomakehurst.wiremock.common.NetworkAddressRules; -import com.github.tomakehurst.wiremock.common.Notifier; -import com.github.tomakehurst.wiremock.common.ProxySettings; -import com.github.tomakehurst.wiremock.common.SingleRootFileSource; -import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.github.tomakehurst.wiremock.common.*; import com.github.tomakehurst.wiremock.common.filemaker.FilenameMaker; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings; import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSourceFactory; @@ -373,7 +361,9 @@ public WireMockConfiguration disableRequestJournal() { } @Deprecated - /** @deprecated use {@link #maxRequestJournalEntries(int)} instead */ + /** + * @deprecated use {@link #maxRequestJournalEntries(int)} instead + */ public WireMockConfiguration maxRequestJournalEntries( Optional maxRequestJournalEntries) { this.maxRequestJournalEntries = maxRequestJournalEntries; @@ -517,22 +507,6 @@ public WireMockConfiguration limitProxyTargets(NetworkAddressRules proxyTargetRu return this; } - public WireMockConfiguration disableOptimizeXmlFactoriesLoading( - boolean disableOptimizeXmlFactoriesLoading) { - this.disableOptimizeXmlFactoriesLoading = disableOptimizeXmlFactoriesLoading; - return this; - } - - public WireMockConfiguration maxLoggedResponseSize(int maxSize) { - this.responseBodySizeLimit = new Limit(maxSize); - return this; - } - - public WireMockConfiguration limitProxyTargets(NetworkAddressRules proxyTargetRules) { - this.proxyTargetRules = proxyTargetRules; - return this; - } - public WireMockConfiguration proxyTimeout(int proxyTimeout) { this.proxyTimeout = proxyTimeout; return this; @@ -558,6 +532,11 @@ public WireMockConfiguration withTemplateEscapingDisabled(boolean templateEscapi return this; } + public WireMockConfiguration withMaxTemplateCacheEntries(Long maxTemplateCacheEntries) { + this.maxTemplateCacheEntries = maxTemplateCacheEntries; + return this; + } + @Override public int portNumber() { return portNumber; diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java index 5cb2a8c694..a3a92b824c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java @@ -25,8 +25,8 @@ import com.github.tomakehurst.wiremock.extension.*; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; -import com.github.tomakehurst.wiremock.jetty9.websockets.Message; -import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; +import com.github.tomakehurst.wiremock.jetty.websockets.Message; +import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.SubEvent; import com.github.tomakehurst.wiremock.verification.RequestJournal; diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java index 0f89e13af4..08e86d790c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java @@ -27,7 +27,7 @@ import com.github.tomakehurst.wiremock.http.RequestHandler; import com.github.tomakehurst.wiremock.http.StubRequestHandler; import com.github.tomakehurst.wiremock.http.trafficlistener.WiremockNetworkTrafficListener; -import com.github.tomakehurst.wiremock.jetty9.websockets.WebSocketEndpoint; +import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.servlet.*; import com.google.common.io.Resources; import jakarta.servlet.DispatcherType; @@ -59,7 +59,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlets.CrossOriginFilter; -import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; +import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; public abstract class JettyHttpServer implements HttpServer { @@ -364,25 +364,25 @@ private ServletContextHandler addAdminContext( ServletHolder webapp = adminContext.addServlet(DefaultServlet.class, "/webapp/*"); webapp.setAsyncSupported(false); - WebSocketServerContainerInitializer.configure(adminContext, (servletContext, serverContainer) -> { - serverContainer.addEndpoint(WebSocketEndpoint.class); - }); + JakartaWebSocketServletContainerInitializer.configure(adminContext,(servletContext, serverContainer) -> { + serverContainer.addEndpoint(WebSocketEndpoint.class); + }); - final RewriteHandler rewrite = new RewriteHandler(); - rewrite.setRewriteRequestURI(true); - rewrite.setRewritePathInfo(true); + final RewriteHandler rewrite = new RewriteHandler(); + rewrite.setRewriteRequestURI(true); + rewrite.setRewritePathInfo(true); - RewriteRegexRule rewriteRule = new RewriteRegexRule(); - rewriteRule.setRegex("/webapp/(mappings|matched|unmatched|state).*"); - rewriteRule.setReplacement("/index.html"); - rewrite.addRule(rewriteRule); + RewriteRegexRule rewriteRule = new RewriteRegexRule(); + rewriteRule.setRegex("/webapp/(mappings|matched|unmatched|state).*"); + rewriteRule.setReplacement("/index.html"); + rewrite.addRule(rewriteRule); - adminContext.insertHandler(rewrite); + adminContext.insertHandler(rewrite); - ServletHolder servletHolder = adminContext.addServlet(WireMockHandlerDispatchingServlet.class, "/"); - servletHolder.setInitParameter(RequestHandler.HANDLER_CLASS_KEY, AdminRequestHandler.class.getName()); - adminContext.setAttribute(AdminRequestHandler.class.getName(), adminRequestHandler); - adminContext.setAttribute(Notifier.KEY, notifier); + ServletHolder servletHolder = adminContext.addServlet(WireMockHandlerDispatchingServlet.class, "/"); + servletHolder.setInitParameter(RequestHandler.HANDLER_CLASS_KEY, AdminRequestHandler.class.getName()); + adminContext.setAttribute(AdminRequestHandler.class.getName(), adminRequestHandler); + adminContext.setAttribute(Notifier.KEY, notifier); adminContext.setAttribute(MultipartRequestConfigurer.KEY, buildMultipartRequestConfigurer()); diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java index 87d15e406e..b8123768f4 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java @@ -1,4 +1,4 @@ -package com.github.tomakehurst.wiremock.jetty9.websockets; +package com.github.tomakehurst.wiremock.jetty.websockets; /** * @author Christopher Holomek diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java index 0818674eac..9f3ea8f371 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java @@ -1,7 +1,7 @@ -package com.github.tomakehurst.wiremock.jetty9.websockets; +package com.github.tomakehurst.wiremock.jetty.websockets; -import javax.websocket.*; -import javax.websocket.server.ServerEndpoint; +import jakarta.websocket.*; +import jakarta.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java index ff9d076ce7..158af249c6 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java @@ -23,11 +23,8 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.stubbing.StubMappingCollection; import com.github.tomakehurst.wiremock.stubbing.StubMappings; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; + +import java.util.*; import java.util.stream.Collectors; public class JsonFileMappingsSource implements MappingsSource { @@ -63,7 +60,7 @@ public void save(StubMapping stubMapping) { // TODO: This allows async between folder definition and actual file. Not sure if good or bad yet. final String folderDefinition = getFolderDefinition(stubMapping); if (folderDefinition != null) { - fileMetadata = new StubMappingFileMetadata(folderDefinition.replaceFirst("/", "") + "/" + SafeNames.makeSafeFileName(stubMapping), false); + fileMetadata = new StubMappingFileMetadata(folderDefinition.replaceFirst("/", "") + "/" + filenameMaker.filenameFor(stubMapping), false); } else { fileMetadata = new StubMappingFileMetadata(filenameMaker.filenameFor(stubMapping), false); } From 0d74c71c58d6f9f1608c66b34566820ce8e009c0 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 15:48:58 +0200 Subject: [PATCH 3/9] Some refactoring and formatting. --- .../wiremock/core/WireMockApp.java | 350 +++++++++--------- .../wiremock/gui/GuiConstants.java | 18 + .../wiremock/gui/GuiServeEventListener.java | 30 ++ .../wiremock/http/StubRequestHandler.java | 9 - 4 files changed, 226 insertions(+), 181 deletions(-) create mode 100644 src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java create mode 100644 src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java index 5940279446..717f285b70 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java @@ -27,6 +27,7 @@ import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; import com.github.tomakehurst.wiremock.global.GlobalSettings; +import com.github.tomakehurst.wiremock.gui.GuiServeEventListener; import com.github.tomakehurst.wiremock.http.*; import com.github.tomakehurst.wiremock.jetty.websockets.Message; import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; @@ -40,8 +41,10 @@ import com.github.tomakehurst.wiremock.store.Stores; import com.github.tomakehurst.wiremock.stubbing.*; import com.github.tomakehurst.wiremock.verification.*; + import java.util.*; import java.util.stream.Collectors; + import org.apache.commons.lang3.mutable.MutableBoolean; public class WireMockApp implements StubServer, Admin { @@ -88,38 +91,38 @@ public WireMockApp(Options options, Container container) { this.settingsStore = stores.getSettingsStore(); extensions = - new Extensions( - options.getDeclaredExtensions(), - this, - options, - stores, - options.filesRoot().child(FILES_ROOT)); + new Extensions( + options.getDeclaredExtensions(), + this, + options, + stores, + options.filesRoot().child(FILES_ROOT)); extensions.load(); Map customMatchers = - extensions.ofType(RequestMatcherExtension.class); + extensions.ofType(RequestMatcherExtension.class); requestJournal = - options.requestJournalDisabled() - ? new DisabledRequestJournal() - : new StoreBackedRequestJournal( - options.maxRequestJournalEntries().orElse(null), - customMatchers, - stores.getRequestJournalStore()); + options.requestJournalDisabled() + ? new DisabledRequestJournal() + : new StoreBackedRequestJournal( + options.maxRequestJournalEntries().orElse(null), + customMatchers, + stores.getRequestJournalStore()); scenarios = new InMemoryScenarios(stores.getScenariosStore()); stubMappings = - new StoreBackedStubMappings( - stores.getStubStore(), - scenarios, - customMatchers, - extensions.ofType(ResponseDefinitionTransformer.class), - extensions.ofType(ResponseDefinitionTransformerV2.class), - stores.getFilesBlobStore(), - List.copyOf(extensions.ofType(StubLifecycleListener.class).values())); + new StoreBackedStubMappings( + stores.getStubStore(), + scenarios, + customMatchers, + extensions.ofType(ResponseDefinitionTransformer.class), + extensions.ofType(ResponseDefinitionTransformerV2.class), + stores.getFilesBlobStore(), + List.copyOf(extensions.ofType(StubLifecycleListener.class).values())); nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); recorder = - new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); + new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); globalSettingsListeners = List.copyOf(extensions.ofType(GlobalSettingsListener.class).values()); this.container = container; @@ -127,16 +130,16 @@ public WireMockApp(Options options, Container container) { } public WireMockApp( - boolean browserProxyingEnabled, - MappingsLoader defaultMappingsLoader, - MappingsSaver mappingsSaver, - boolean requestJournalDisabled, - Integer maxRequestJournalEntries, - Map transformers, - Map v2transformers, - Map requestMatchers, - FileSource rootFileSource, - Container container) { + boolean browserProxyingEnabled, + MappingsLoader defaultMappingsLoader, + MappingsSaver mappingsSaver, + boolean requestJournalDisabled, + Integer maxRequestJournalEntries, + Map transformers, + Map v2transformers, + Map requestMatchers, + FileSource rootFileSource, + Container container) { this.proxyHandler = new ProxyHandler(this); @@ -147,98 +150,102 @@ public WireMockApp( this.mappingsSaver = mappingsSaver; this.settingsStore = stores.getSettingsStore(); requestJournal = - requestJournalDisabled - ? new DisabledRequestJournal() - : new StoreBackedRequestJournal( - maxRequestJournalEntries, requestMatchers, stores.getRequestJournalStore()); + requestJournalDisabled + ? new DisabledRequestJournal() + : new StoreBackedRequestJournal( + maxRequestJournalEntries, requestMatchers, stores.getRequestJournalStore()); scenarios = new InMemoryScenarios(stores.getScenariosStore()); stubMappings = - new StoreBackedStubMappings( - stores.getStubStore(), - scenarios, - requestMatchers, - transformers, - v2transformers, - stores.getFilesBlobStore(), - Collections.emptyList()); + new StoreBackedStubMappings( + stores.getStubStore(), + scenarios, + requestMatchers, + transformers, + v2transformers, + stores.getFilesBlobStore(), + Collections.emptyList()); this.container = container; nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); recorder = - new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); + new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); globalSettingsListeners = Collections.emptyList(); loadDefaultMappings(); } public AdminRequestHandler buildAdminRequestHandler() { AdminRoutes adminRoutes = - AdminRoutes.forServer(extensions.ofType(AdminApiExtension.class).values(), stores); + AdminRoutes.forServer(extensions.ofType(AdminApiExtension.class).values(), stores); return new AdminRequestHandler( - adminRoutes, - this, - new BasicResponseRenderer(), - options.getAdminAuthenticator(), - options.getHttpsRequiredForAdminApi(), - getAdminRequestFilters(), - getV2AdminRequestFilters(), - options.getDataTruncationSettings()); + adminRoutes, + this, + new BasicResponseRenderer(), + options.getAdminAuthenticator(), + options.getHttpsRequiredForAdminApi(), + getAdminRequestFilters(), + getV2AdminRequestFilters(), + options.getDataTruncationSettings()); } public StubRequestHandler buildStubRequestHandler() { Map postServeActions = extensions.ofType(PostServeAction.class); - Map serveEventListeners = - extensions.ofType(ServeEventListener.class); + + Map concatenatedMap = new HashMap<>(extensions.ofType(ServeEventListener.class)); + concatenatedMap.put("wiremock-gui", new GuiServeEventListener()); + + Map serveEventListeners = Collections.unmodifiableMap(concatenatedMap); + BrowserProxySettings browserProxySettings = options.browserProxySettings(); return new StubRequestHandler( - this, - new StubResponseRenderer( - options.getStores().getFilesBlobStore(), - settingsStore, - new ProxyResponseRenderer( - options.proxyVia(), - options.httpsSettings().trustStore(), - options.shouldPreserveHostHeader(), - options.proxyHostHeader(), - settingsStore, - browserProxySettings.trustAllProxyTargets(), - browserProxySettings.trustedProxyTargets(), - options.getStubCorsEnabled(), - options.getProxyTargetRules(), - options.proxyTimeout()), - List.copyOf(extensions.ofType(ResponseTransformer.class).values()), - List.copyOf(extensions.ofType(ResponseTransformerV2.class).values())), - this, - postServeActions, - serveEventListeners, - requestJournal, - getStubRequestFilters(), - getV2StubRequestFilters(), - options.getStubRequestLoggingDisabled(), - options.getDataTruncationSettings(), - options.getNotMatchedRendererFactory().apply(extensions)); + this, + new StubResponseRenderer( + options.getStores().getFilesBlobStore(), + settingsStore, + new ProxyResponseRenderer( + options.proxyVia(), + options.httpsSettings().trustStore(), + options.shouldPreserveHostHeader(), + options.proxyHostHeader(), + settingsStore, + browserProxySettings.trustAllProxyTargets(), + browserProxySettings.trustedProxyTargets(), + options.getStubCorsEnabled(), + options.getProxyTargetRules(), + options.proxyTimeout()), + List.copyOf(extensions.ofType(ResponseTransformer.class).values()), + List.copyOf(extensions.ofType(ResponseTransformerV2.class).values())), + this, + postServeActions, + serveEventListeners, + requestJournal, + getStubRequestFilters(), + getV2StubRequestFilters(), + options.getStubRequestLoggingDisabled(), + options.getDataTruncationSettings(), + options.getNotMatchedRendererFactory().apply(extensions)); } private List getAdminRequestFilters() { return extensions.ofType(RequestFilter.class).values().stream() - .filter(RequestFilter::applyToAdmin) - .collect(Collectors.toList()); + .filter(RequestFilter::applyToAdmin) + .collect(Collectors.toList()); } private List getV2AdminRequestFilters() { return extensions.ofType(RequestFilterV2.class).values().stream() - .filter(RequestFilterV2::applyToAdmin) - .collect(Collectors.toList()); + .filter(RequestFilterV2::applyToAdmin) + .collect(Collectors.toList()); } private List getStubRequestFilters() { return extensions.ofType(RequestFilter.class).values().stream() - .filter(RequestFilter::applyToStubs) - .collect(Collectors.toList()); + .filter(RequestFilter::applyToStubs) + .collect(Collectors.toList()); } private List getV2StubRequestFilters() { return extensions.ofType(RequestFilterV2.class).values().stream() - .filter(RequestFilterV2::applyToStubs) - .collect(Collectors.toList()); + .filter(RequestFilterV2::applyToStubs) + .collect(Collectors.toList()); } private void loadDefaultMappings() { @@ -254,46 +261,46 @@ public ServeEvent serveStubFor(ServeEvent initialServeEvent) { ServeEvent serveEvent = stubMappings.serveFor(initialServeEvent); if (serveEvent.isNoExactMatch() - && browserProxyingEnabled - && serveEvent.getRequest().isBrowserProxyRequest() - && getGlobalSettings().getSettings().getProxyPassThrough()) { + && browserProxyingEnabled + && serveEvent.getRequest().isBrowserProxyRequest() + && getGlobalSettings().getSettings().getProxyPassThrough()) { return ServeEvent.ofUnmatched( - serveEvent.getRequest(), ResponseDefinition.browserProxy(serveEvent.getRequest())); + serveEvent.getRequest(), ResponseDefinition.browserProxy(serveEvent.getRequest())); } return serveEvent; } - @Override - public void addStubMapping(StubMapping stubMapping) { - if (stubMapping.getId() == null) { - stubMapping.setId(UUID.randomUUID()); - } - - stubMappings.addMapping(stubMapping); - if (stubMapping.shouldBePersisted()) { - this.mappingsSaver.save(stubMapping); - } + @Override + public void addStubMapping(StubMapping stubMapping) { + if (stubMapping.getId() == null) { + stubMapping.setId(UUID.randomUUID()); + } - WebSocketEndpoint.broadcast(Message.MAPPINGS); + stubMappings.addMapping(stubMapping); + if (stubMapping.shouldBePersisted()) { + this.mappingsSaver.save(stubMapping); } + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } + @Override public void removeStubMapping(StubMapping stubMapping) { stubMappings - .get(stubMapping.getId()) - .ifPresent( - stubToDelete -> { - if (stubToDelete.shouldBePersisted()) { - mappingsSaver.remove(stubToDelete); - } - }); - - stubMappings.removeMapping(stubMapping); - - this.proxyHandler.removeProxyConfig(stubMapping.getUuid()); - WebSocketEndpoint.broadcast(Message.MAPPINGS); - } + .get(stubMapping.getId()) + .ifPresent( + stubToDelete -> { + if (stubToDelete.shouldBePersisted()) { + mappingsSaver.remove(stubToDelete); + } + }); + + stubMappings.removeMapping(stubMapping); + + this.proxyHandler.removeProxyConfig(stubMapping.getUuid()); + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } @Override public void removeStubMapping(UUID id) { @@ -302,15 +309,15 @@ public void removeStubMapping(UUID id) { @Override public void editStubMapping(final StubMapping stubMapping) { - this.stubMappings.editMapping(stubMapping); + this.stubMappings.editMapping(stubMapping); if (stubMapping.shouldBePersisted()) { this.mappingsSaver.save(stubMapping); - } - - this.proxyHandler.removeProxyConfig(stubMapping.getUuid()); - WebSocketEndpoint.broadcast(Message.MAPPINGS); } + this.proxyHandler.removeProxyConfig(stubMapping.getUuid()); + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } + @Override public ListStubMappingsResult listAllStubMappings() { return new ListStubMappingsResult(LimitAndOffsetPaginator.none(this.stubMappings.getAll())); @@ -339,9 +346,9 @@ public void resetAll() { public void resetRequests() { this.requestJournal.reset(); - WebSocketEndpoint.broadcast(Message.UNMATCHED); - WebSocketEndpoint.broadcast(Message.MATCHED); - } + WebSocketEndpoint.broadcast(Message.UNMATCHED); + WebSocketEndpoint.broadcast(Message.MATCHED); + } @Override public void resetToDefaultMappings() { @@ -349,25 +356,24 @@ public void resetToDefaultMappings() { this.resetRequests(); this.loadDefaultMappings(); - this.proxyHandler.clear(); - - WebSocketEndpoint.broadcast(Message.MAPPINGS); - } + this.proxyHandler.clear(); + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } @Override public void resetScenarios() { this.stubMappings.resetScenarios(); - WebSocketEndpoint.broadcast(Message.SCENARIO); - } + WebSocketEndpoint.broadcast(Message.SCENARIO); + } @Override public void resetMappings() { this.mappingsSaver.removeAll(); this.stubMappings.reset(); - this.proxyHandler.clear(); - WebSocketEndpoint.broadcast(Message.MAPPINGS); - } + this.proxyHandler.clear(); + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } @Override public GetServeEventsResult getServeEvents() { @@ -381,7 +387,7 @@ public GetServeEventsResult getServeEvents(ServeEventQuery query) { return GetServeEventsResult.requestJournalEnabled(LimitAndOffsetPaginator.none(serveEvents)); } catch (RequestJournalDisabledException e) { return GetServeEventsResult.requestJournalDisabled( - LimitAndOffsetPaginator.none(requestJournal.getAllServeEvents())); + LimitAndOffsetPaginator.none(requestJournal.getAllServeEvents())); } } @@ -401,22 +407,22 @@ public VerificationResult countRequestsMatching(final RequestPattern requestPatt @Override public FindRequestsResult findRequestsMatching(final RequestPattern requestPattern) { - try { - final List requests = this.requestJournal.getRequestsMatching(requestPattern); + try { + final List requests = this.requestJournal.getRequestsMatching(requestPattern); return FindRequestsResult.withRequests(requests); } catch (final RequestJournalDisabledException e) { - return FindRequestsResult.withRequestJournalDisabled(); - } + return FindRequestsResult.withRequestJournalDisabled(); } + } @Override public FindRequestsResult findUnmatchedRequests() { try { List requests = - requestJournal.getAllServeEvents().stream() - .filter(ServeEvent::isNoExactMatch) - .map(ServeEvent::getRequest) - .collect(Collectors.toList()); + requestJournal.getAllServeEvents().stream() + .filter(ServeEvent::isNoExactMatch) + .map(ServeEvent::getRequest) + .collect(Collectors.toList()); return FindRequestsResult.withRequests(requests); } catch (RequestJournalDisabledException e) { return FindRequestsResult.withRequestJournalDisabled(); @@ -435,18 +441,18 @@ public FindServeEventsResult removeServeEventsMatching(RequestPattern requestPat @Override public FindServeEventsResult removeServeEventsForStubsMatchingMetadata( - StringValuePattern metadataPattern) { + StringValuePattern metadataPattern) { return new FindServeEventsResult( - requestJournal.removeServeEventsForStubsMatchingMetadata(metadataPattern)); + requestJournal.removeServeEventsForStubsMatchingMetadata(metadataPattern)); } @Override public FindNearMissesResult findNearMissesForUnmatchedRequests() { List nearMisses = new ArrayList<>(); List unmatchedServeEvents = - requestJournal.getAllServeEvents().stream() - .filter(ServeEvent::isNoExactMatch) - .collect(Collectors.toList()); + requestJournal.getAllServeEvents().stream() + .filter(ServeEvent::isNoExactMatch) + .collect(Collectors.toList()); for (final ServeEvent serveEvent : unmatchedServeEvents) { nearMisses.addAll(this.nearMissCalculator.findNearestTo(serveEvent.getRequest())); @@ -524,23 +530,23 @@ public ProxyConfig getProxyConfig() { @Override public void enableProxy(final UUID id) { - this.proxyHandler.enableProxyUrl(id); - } + this.proxyHandler.enableProxyUrl(id); + } @Override public void disableProxy(final UUID id) { - this.proxyHandler.disableProxyUrl(id); - } + this.proxyHandler.disableProxyUrl(id); + } @Override public SnapshotRecordResult snapshotRecord() { - return this.snapshotRecord(RecordSpec.DEFAULTS); + return this.snapshotRecord(RecordSpec.DEFAULTS); } @Override public SnapshotRecordResult snapshotRecord(final RecordSpecBuilder spec) { - return this.snapshotRecord(spec.build()); - } + return this.snapshotRecord(spec.build()); + } @Override public SnapshotRecordResult snapshotRecord(final RecordSpec recordSpec) { @@ -551,31 +557,31 @@ public SnapshotRecordResult snapshotRecord(final RecordSpec recordSpec) { public void startRecording(final String targetBaseUrl) { this.recorder.startRecording(RecordSpec.forBaseUrl(targetBaseUrl)); - WebSocketEndpoint.broadcast(Message.RECORDING); + WebSocketEndpoint.broadcast(Message.RECORDING); } @Override public void startRecording(final RecordSpec recordSpec) { - this.recorder.startRecording(recordSpec); + this.recorder.startRecording(recordSpec); - WebSocketEndpoint.broadcast(Message.RECORDING); - } + WebSocketEndpoint.broadcast(Message.RECORDING); + } - @Override - public void startRecording(final RecordSpecBuilder recordSpec) { - this.recorder.startRecording(recordSpec.build()); + @Override + public void startRecording(final RecordSpecBuilder recordSpec) { + this.recorder.startRecording(recordSpec.build()); - WebSocketEndpoint.broadcast(Message.RECORDING); - } + WebSocketEndpoint.broadcast(Message.RECORDING); + } @Override public SnapshotRecordResult stopRecording() { final SnapshotRecordResult result = this.recorder.stopRecording(); - WebSocketEndpoint.broadcast(Message.RECORDING); + WebSocketEndpoint.broadcast(Message.RECORDING); - return result; - } + return result; + } @Override public RecordingStatusResult getRecordingStatus() { @@ -585,14 +591,14 @@ public RecordingStatusResult getRecordingStatus() { @Override public ListStubMappingsResult findAllStubsByMetadata(final StringValuePattern pattern) { return new ListStubMappingsResult( - LimitAndOffsetPaginator.none(this.stubMappings.findByMetadata(pattern))); + LimitAndOffsetPaginator.none(this.stubMappings.findByMetadata(pattern))); } @Override public void removeStubsByMetadata(final StringValuePattern pattern) { - final List foundMappings = this.stubMappings.findByMetadata(pattern); + final List foundMappings = this.stubMappings.findByMetadata(pattern); for (final StubMapping mapping : foundMappings) { - this.removeStubMapping(mapping); + this.removeStubMapping(mapping); } } @@ -600,7 +606,7 @@ public void removeStubsByMetadata(final StringValuePattern pattern) { public void importStubs(StubImport stubImport) { List mappings = stubImport.getMappings(); StubImport.Options importOptions = - getFirstNonNull(stubImport.getImportOptions(), StubImport.Options.DEFAULTS); + getFirstNonNull(stubImport.getImportOptions(), StubImport.Options.DEFAULTS); for (int i = mappings.size() - 1; i >= 0; i--) { StubMapping mapping = mappings.get(i); diff --git a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java new file mode 100644 index 0000000000..596a4dd275 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java @@ -0,0 +1,18 @@ +package com.github.tomakehurst.wiremock.gui; + +/** + * @author Christopher Holomek + * @since 17.09.2023 + */ +public final class GuiConstants { + + public static final String EXTENSION_NAME = "wiremock-gui"; + + /** + * Hide constructor + */ + private GuiConstants() { + // Hide constructor + } + +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java new file mode 100644 index 0000000000..f5ca80c0c0 --- /dev/null +++ b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java @@ -0,0 +1,30 @@ +package com.github.tomakehurst.wiremock.gui; + +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ServeEventListener; +import com.github.tomakehurst.wiremock.jetty.websockets.Message; +import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; + +import static com.github.tomakehurst.wiremock.gui.GuiConstants.EXTENSION_NAME; + +/** + * @author Christopher Holomek + * @since 17.09.2023 + */ +public class GuiServeEventListener implements ServeEventListener { + + @Override + public void beforeResponseSent(ServeEvent serveEvent, Parameters parameters) { + if (serveEvent.getWasMatched()) { + WebSocketEndpoint.broadcast(Message.MATCHED); + } else { + WebSocketEndpoint.broadcast(Message.UNMATCHED); + } + } + + @Override + public String getName() { + return EXTENSION_NAME; + } +} diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java index a3a92b824c..694b13f2bd 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java @@ -25,8 +25,6 @@ import com.github.tomakehurst.wiremock.extension.*; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilter; import com.github.tomakehurst.wiremock.extension.requestfilter.RequestFilterV2; -import com.github.tomakehurst.wiremock.jetty.websockets.Message; -import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import com.github.tomakehurst.wiremock.stubbing.SubEvent; import com.github.tomakehurst.wiremock.verification.RequestJournal; @@ -88,13 +86,6 @@ protected void beforeResponseSent(ServeEvent serveEvent, Response response) { } requestJournal.requestReceived(serveEvent); - - if (serveEvent.getWasMatched()) { - WebSocketEndpoint.broadcast(Message.MATCHED); - } else { - WebSocketEndpoint.broadcast(Message.UNMATCHED); - } - triggerListeners(BEFORE_RESPONSE_SENT, serveEvent); } From 0eef2edec12540fbcc957bae5f6d3760b64ab0e6 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 15:51:14 +0200 Subject: [PATCH 4/9] release only with jdk 11 --- .github/workflows/create-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index f0d3f937c8..b0b8b842e1 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - jdk: [ 8 ] + jdk: [ 11 ] runs-on: ${{ matrix.os }} env: JDK_VERSION: ${{ matrix.jdk }} From bab0f0bd15d57bbcd8d6b593537ad7c17e162fa6 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 16:23:08 +0200 Subject: [PATCH 5/9] try to fix tests --- .github/workflows/build-and-test.yml | 1 + .../wiremock/gui/GuiConstants.java | 18 --- .../wiremock/gui/GuiServeEventListener.java | 2 +- .../java/org/wiremock/webhooks/Webhooks.java | 134 ++++++++---------- 4 files changed, 65 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9fe9eca878..a14ac38a40 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -57,6 +57,7 @@ jobs: # run: ./gradlew check sonarqube --stacktrace --no-daemon - name: Test WireMock + if: ${{ !(matrix.os == 'ubuntu-latest' && matrix.jdk == 11 && github.event_name == 'push') }} run: ./gradlew check --stacktrace --no-daemon # - name: Archive WireMock test report - ${{ matrix.os }} JDK ${{ matrix.jdk }} diff --git a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java deleted file mode 100644 index 596a4dd275..0000000000 --- a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiConstants.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.tomakehurst.wiremock.gui; - -/** - * @author Christopher Holomek - * @since 17.09.2023 - */ -public final class GuiConstants { - - public static final String EXTENSION_NAME = "wiremock-gui"; - - /** - * Hide constructor - */ - private GuiConstants() { - // Hide constructor - } - -} diff --git a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java index f5ca80c0c0..d7efab7641 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java @@ -25,6 +25,6 @@ public void beforeResponseSent(ServeEvent serveEvent, Parameters parameters) { @Override public String getName() { - return EXTENSION_NAME; + return "wiremock-gui"; } } diff --git a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java index 5e2f881e42..f6b3860b34 100644 --- a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java +++ b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java @@ -55,9 +55,9 @@ public class Webhooks extends PostServeAction { private final TemplateEngine templateEngine; private Webhooks( - ScheduledExecutorService scheduler, - CloseableHttpClient httpClient, - List transformers) { + ScheduledExecutorService scheduler, + CloseableHttpClient httpClient, + List transformers) { this.scheduler = scheduler; this.httpClient = httpClient; this.transformers = transformers; @@ -73,14 +73,6 @@ public Webhooks(NetworkAddressRules targetAddressRules) { this(new ArrayList<>(), targetAddressRules); } - private Webhooks(List transformers, NetworkAddressRules targetAddressRules) { - this(Executors.newScheduledThreadPool(10), createHttpClient(targetAddressRules), transformers); - } - - public Webhooks(NetworkAddressRules targetAddressRules) { - this(new ArrayList<>(), targetAddressRules); - } - @JsonCreator public Webhooks() { this(NetworkAddressRules.ALLOW_ALL); @@ -92,23 +84,23 @@ public Webhooks(WebhookTransformer... transformers) { private static CloseableHttpClient createHttpClient(NetworkAddressRules targetAddressRules) { return HttpClientBuilder.create() - .disableAuthCaching() - .disableAutomaticRetries() - .disableCookieManagement() - .disableRedirectHandling() - .disableContentCompression() - .setConnectionManager( - PoolingHttpClientConnectionManagerBuilder.create() - .setDnsResolver(new NetworkAddressRulesAdheringDnsResolver(targetAddressRules)) - .setDefaultSocketConfig( - SocketConfig.custom().setSoTimeout(Timeout.ofMilliseconds(30000)).build()) - .setMaxConnPerRoute(1000) - .setMaxConnTotal(1000) - .setValidateAfterInactivity(TimeValue.ofSeconds(5)) // TODO Verify duration - .build()) - .setConnectionReuseStrategy((request, response, context) -> false) - .setKeepAliveStrategy((response, context) -> TimeValue.ZERO_MILLISECONDS) - .build(); + .disableAuthCaching() + .disableAutomaticRetries() + .disableCookieManagement() + .disableRedirectHandling() + .disableContentCompression() + .setConnectionManager( + PoolingHttpClientConnectionManagerBuilder.create() + .setDnsResolver(new NetworkAddressRulesAdheringDnsResolver(targetAddressRules)) + .setDefaultSocketConfig( + SocketConfig.custom().setSoTimeout(Timeout.ofMilliseconds(30000)).build()) + .setMaxConnPerRoute(1000) + .setMaxConnTotal(1000) + .setValidateAfterInactivity(TimeValue.ofSeconds(5)) // TODO Verify duration + .build()) + .setConnectionReuseStrategy((request, response, context) -> false) + .setKeepAliveStrategy((response, context) -> TimeValue.ZERO_MILLISECONDS) + .build(); } @Override @@ -118,7 +110,7 @@ public String getName() { @Override public void doAction( - final ServeEvent serveEvent, final Admin admin, final Parameters parameters) { + final ServeEvent serveEvent, final Admin admin, final Parameters parameters) { final Notifier notifier = notifier(); WebhookDefinition definition; @@ -137,58 +129,58 @@ public void doAction( final WebhookDefinition finalDefinition = definition; scheduler.schedule( - () -> { - try (CloseableHttpResponse response = httpClient.execute(request)) { - notifier.info( - String.format( - "Webhook %s request to %s returned status %s\n\n%s", - finalDefinition.getMethod(), - finalDefinition.getUrl(), - response.getCode(), - EntityUtils.toString(response.getEntity()))); - } catch (ProhibitedNetworkAddressException e) { - notifier.error("The target webhook address is denied in WireMock's configuration."); - } catch (Exception e) { - notifier.error( - String.format( - "Failed to fire webhook %s %s", - finalDefinition.getMethod(), finalDefinition.getUrl()), - e); - } - }, - finalDefinition.getDelaySampleMillis(), - MILLISECONDS); + () -> { + try (CloseableHttpResponse response = httpClient.execute(request)) { + notifier.info( + String.format( + "Webhook %s request to %s returned status %s\n\n%s", + finalDefinition.getMethod(), + finalDefinition.getUrl(), + response.getCode(), + EntityUtils.toString(response.getEntity()))); + } catch (ProhibitedNetworkAddressException e) { + notifier.error("The target webhook address is denied in WireMock's configuration."); + } catch (Exception e) { + notifier.error( + String.format( + "Failed to fire webhook %s %s", + finalDefinition.getMethod(), finalDefinition.getUrl()), + e); + } + }, + finalDefinition.getDelaySampleMillis(), + MILLISECONDS); } private WebhookDefinition applyTemplating( - WebhookDefinition webhookDefinition, ServeEvent serveEvent) { + WebhookDefinition webhookDefinition, ServeEvent serveEvent) { final Map model = new HashMap<>(); model.put( - "parameters", - webhookDefinition.getExtraParameters() != null - ? webhookDefinition.getExtraParameters() - : Collections.emptyMap()); + "parameters", + webhookDefinition.getExtraParameters() != null + ? webhookDefinition.getExtraParameters() + : Collections.emptyMap()); model.put("originalRequest", RequestTemplateModel.from(serveEvent.getRequest())); WebhookDefinition renderedWebhookDefinition = - webhookDefinition - .withUrl(renderTemplate(model, webhookDefinition.getUrl())) - .withMethod(renderTemplate(model, webhookDefinition.getMethod())) - .withHeaders( - webhookDefinition.getHeaders().all().stream() - .map( - header -> - new HttpHeader( - header.key(), - header.values().stream() - .map(value -> renderTemplate(model, value)) - .collect(toList()))) - .collect(toList())); + webhookDefinition + .withUrl(renderTemplate(model, webhookDefinition.getUrl())) + .withMethod(renderTemplate(model, webhookDefinition.getMethod())) + .withHeaders( + webhookDefinition.getHeaders().all().stream() + .map( + header -> + new HttpHeader( + header.key(), + header.values().stream() + .map(value -> renderTemplate(model, value)) + .collect(toList()))) + .collect(toList())); if (webhookDefinition.getBody() != null) { renderedWebhookDefinition = - webhookDefinition.withBody(renderTemplate(model, webhookDefinition.getBody())); + webhookDefinition.withBody(renderTemplate(model, webhookDefinition.getBody())); } return renderedWebhookDefinition; @@ -200,7 +192,7 @@ private String renderTemplate(Object context, String value) { private static ClassicHttpRequest buildRequest(WebhookDefinition definition) { final ClassicRequestBuilder requestBuilder = - ClassicRequestBuilder.create(definition.getMethod()).setUri(definition.getUrl()); + ClassicRequestBuilder.create(definition.getMethod()).setUri(definition.getUrl()); for (HttpHeader header : definition.getHeaders().all()) { for (String value : header.values()) { @@ -210,7 +202,7 @@ private static ClassicHttpRequest buildRequest(WebhookDefinition definition) { if (definition.getRequestMethod().hasEntity() && definition.hasBody()) { requestBuilder.setEntity( - new ByteArrayEntity(definition.getBinaryBody(), ContentType.DEFAULT_BINARY)); + new ByteArrayEntity(definition.getBinaryBody(), ContentType.DEFAULT_BINARY)); } return requestBuilder.build(); From 9f7042aa93e5a44076e64198f4dcad64587a7931 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 16:33:19 +0200 Subject: [PATCH 6/9] fix formatting... --- .../java/org/wiremock/webhooks/Webhooks.java | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java index f6b3860b34..d704cb7e82 100644 --- a/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java +++ b/wiremock-webhooks-extension/src/main/java/org/wiremock/webhooks/Webhooks.java @@ -55,9 +55,9 @@ public class Webhooks extends PostServeAction { private final TemplateEngine templateEngine; private Webhooks( - ScheduledExecutorService scheduler, - CloseableHttpClient httpClient, - List transformers) { + ScheduledExecutorService scheduler, + CloseableHttpClient httpClient, + List transformers) { this.scheduler = scheduler; this.httpClient = httpClient; this.transformers = transformers; @@ -84,23 +84,23 @@ public Webhooks(WebhookTransformer... transformers) { private static CloseableHttpClient createHttpClient(NetworkAddressRules targetAddressRules) { return HttpClientBuilder.create() - .disableAuthCaching() - .disableAutomaticRetries() - .disableCookieManagement() - .disableRedirectHandling() - .disableContentCompression() - .setConnectionManager( - PoolingHttpClientConnectionManagerBuilder.create() - .setDnsResolver(new NetworkAddressRulesAdheringDnsResolver(targetAddressRules)) - .setDefaultSocketConfig( - SocketConfig.custom().setSoTimeout(Timeout.ofMilliseconds(30000)).build()) - .setMaxConnPerRoute(1000) - .setMaxConnTotal(1000) - .setValidateAfterInactivity(TimeValue.ofSeconds(5)) // TODO Verify duration - .build()) - .setConnectionReuseStrategy((request, response, context) -> false) - .setKeepAliveStrategy((response, context) -> TimeValue.ZERO_MILLISECONDS) - .build(); + .disableAuthCaching() + .disableAutomaticRetries() + .disableCookieManagement() + .disableRedirectHandling() + .disableContentCompression() + .setConnectionManager( + PoolingHttpClientConnectionManagerBuilder.create() + .setDnsResolver(new NetworkAddressRulesAdheringDnsResolver(targetAddressRules)) + .setDefaultSocketConfig( + SocketConfig.custom().setSoTimeout(Timeout.ofMilliseconds(30000)).build()) + .setMaxConnPerRoute(1000) + .setMaxConnTotal(1000) + .setValidateAfterInactivity(TimeValue.ofSeconds(5)) // TODO Verify duration + .build()) + .setConnectionReuseStrategy((request, response, context) -> false) + .setKeepAliveStrategy((response, context) -> TimeValue.ZERO_MILLISECONDS) + .build(); } @Override @@ -110,7 +110,7 @@ public String getName() { @Override public void doAction( - final ServeEvent serveEvent, final Admin admin, final Parameters parameters) { + final ServeEvent serveEvent, final Admin admin, final Parameters parameters) { final Notifier notifier = notifier(); WebhookDefinition definition; @@ -129,58 +129,58 @@ public void doAction( final WebhookDefinition finalDefinition = definition; scheduler.schedule( - () -> { - try (CloseableHttpResponse response = httpClient.execute(request)) { - notifier.info( - String.format( - "Webhook %s request to %s returned status %s\n\n%s", - finalDefinition.getMethod(), - finalDefinition.getUrl(), - response.getCode(), - EntityUtils.toString(response.getEntity()))); - } catch (ProhibitedNetworkAddressException e) { - notifier.error("The target webhook address is denied in WireMock's configuration."); - } catch (Exception e) { - notifier.error( - String.format( - "Failed to fire webhook %s %s", - finalDefinition.getMethod(), finalDefinition.getUrl()), - e); - } - }, - finalDefinition.getDelaySampleMillis(), - MILLISECONDS); + () -> { + try (CloseableHttpResponse response = httpClient.execute(request)) { + notifier.info( + String.format( + "Webhook %s request to %s returned status %s\n\n%s", + finalDefinition.getMethod(), + finalDefinition.getUrl(), + response.getCode(), + EntityUtils.toString(response.getEntity()))); + } catch (ProhibitedNetworkAddressException e) { + notifier.error("The target webhook address is denied in WireMock's configuration."); + } catch (Exception e) { + notifier.error( + String.format( + "Failed to fire webhook %s %s", + finalDefinition.getMethod(), finalDefinition.getUrl()), + e); + } + }, + finalDefinition.getDelaySampleMillis(), + MILLISECONDS); } private WebhookDefinition applyTemplating( - WebhookDefinition webhookDefinition, ServeEvent serveEvent) { + WebhookDefinition webhookDefinition, ServeEvent serveEvent) { final Map model = new HashMap<>(); model.put( - "parameters", - webhookDefinition.getExtraParameters() != null - ? webhookDefinition.getExtraParameters() - : Collections.emptyMap()); + "parameters", + webhookDefinition.getExtraParameters() != null + ? webhookDefinition.getExtraParameters() + : Collections.emptyMap()); model.put("originalRequest", RequestTemplateModel.from(serveEvent.getRequest())); WebhookDefinition renderedWebhookDefinition = - webhookDefinition - .withUrl(renderTemplate(model, webhookDefinition.getUrl())) - .withMethod(renderTemplate(model, webhookDefinition.getMethod())) - .withHeaders( - webhookDefinition.getHeaders().all().stream() - .map( - header -> - new HttpHeader( - header.key(), - header.values().stream() - .map(value -> renderTemplate(model, value)) - .collect(toList()))) - .collect(toList())); + webhookDefinition + .withUrl(renderTemplate(model, webhookDefinition.getUrl())) + .withMethod(renderTemplate(model, webhookDefinition.getMethod())) + .withHeaders( + webhookDefinition.getHeaders().all().stream() + .map( + header -> + new HttpHeader( + header.key(), + header.values().stream() + .map(value -> renderTemplate(model, value)) + .collect(toList()))) + .collect(toList())); if (webhookDefinition.getBody() != null) { renderedWebhookDefinition = - webhookDefinition.withBody(renderTemplate(model, webhookDefinition.getBody())); + webhookDefinition.withBody(renderTemplate(model, webhookDefinition.getBody())); } return renderedWebhookDefinition; @@ -192,7 +192,7 @@ private String renderTemplate(Object context, String value) { private static ClassicHttpRequest buildRequest(WebhookDefinition definition) { final ClassicRequestBuilder requestBuilder = - ClassicRequestBuilder.create(definition.getMethod()).setUri(definition.getUrl()); + ClassicRequestBuilder.create(definition.getMethod()).setUri(definition.getUrl()); for (HttpHeader header : definition.getHeaders().all()) { for (String value : header.values()) { @@ -202,7 +202,7 @@ private static ClassicHttpRequest buildRequest(WebhookDefinition definition) { if (definition.getRequestMethod().hasEntity() && definition.hasBody()) { requestBuilder.setEntity( - new ByteArrayEntity(definition.getBinaryBody(), ContentType.DEFAULT_BINARY)); + new ByteArrayEntity(definition.getBinaryBody(), ContentType.DEFAULT_BINARY)); } return requestBuilder.build(); From 7156e9bba190c300a42a85c493a38931f7ba92e2 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 16:36:57 +0200 Subject: [PATCH 7/9] more formatting --- .../tomakehurst/wiremock/WireMockServer.java | 184 ++++++------ .../wiremock/admin/AdminRoutes.java | 20 +- .../admin/model/SingleStubMappingResult.java | 2 +- .../admin/tasks/DisableProxyTask.java | 31 +- .../wiremock/admin/tasks/EnableProxyTask.java | 31 +- .../admin/tasks/GetProxyConfigTask.java | 24 +- .../admin/tasks/GetStubFilesTask.java | 29 +- .../wiremock/client/HttpAdminClient.java | 205 ++++++++------ .../tomakehurst/wiremock/common/Metadata.java | 2 +- .../common/ServletContextFileSource.java | 2 +- .../wiremock/common/StreamSources.java | 2 +- .../common/ssl/KeyStoreSourceFactory.java | 2 +- .../tomakehurst/wiremock/core/Admin.java | 64 +++-- .../tomakehurst/wiremock/core/Options.java | 4 +- .../wiremock/core/ProxyHandler.java | 239 +++++++++------- .../wiremock/core/WireMockApp.java | 233 +++++++-------- .../wiremock/extension/PostServeAction.java | 10 +- .../ResponseDefinitionTransformer.java | 4 +- .../extension/ResponseTransformer.java | 4 +- .../requestfilter/RequestFilter.java | 4 +- .../responsetemplating/RequestLine.java | 4 +- .../wiremock/gui/GuiServeEventListener.java | 17 +- .../wiremock/http/StubRequestHandler.java | 18 +- .../http/ssl/CertificateAuthority.java | 17 +- .../DefaultMultipartRequestConfigurer.java | 2 +- .../jetty/JettyFaultInjectorFactory.java | 2 +- .../wiremock/jetty/JettyHttpServer.java | 113 ++++---- .../jetty/JettyHttpServerFactory.java | 2 +- .../wiremock/jetty/NotFoundHandler.java | 2 +- .../jetty/QueuedThreadPoolFactory.java | 2 +- .../wiremock/jetty/websockets/Message.java | 41 ++- .../jetty/websockets/WebSocketEndpoint.java | 71 +++-- ...ertificateGeneratingSslContextFactory.java | 2 +- .../wiremock/jetty11/SslContexts.java | 7 +- ...WritableFileOrClasspathKeyStoreSource.java | 2 +- .../wiremock/recording/RecorderState.java | 2 +- .../servlet/ContentTypeSettingFilter.java | 2 +- .../servlet/FaultInjectorFactory.java | 2 +- .../servlet/MultipartRequestConfigurer.java | 2 +- .../wiremock/servlet/NoFaultInjector.java | 2 +- .../servlet/NoFaultInjectorFactory.java | 2 +- .../standalone/JsonFileMappingsSource.java | 265 +++++++++--------- .../standalone/WireMockServerRunner.java | 23 +- .../wiremock/stubbing/InMemoryScenarios.java | 2 +- .../wiremock/stubbing/Scenarios.java | 2 +- .../stubbing/StubMappingJsonRecorder.java | 4 +- .../wiremock/GzipAcceptanceTest.java | 2 +- .../wiremock/Http2ClientFactory.java | 2 +- .../MultipartBodyMatchingAcceptanceTest.java | 8 +- .../ResponseTransformerAcceptanceTest.java | 2 +- .../common/NetworkAddressRangeTest.java | 2 +- .../common/NetworkAddressRulesTest.java | 2 +- .../http/ProxyResponseRendererTest.java | 44 ++- .../wiremock/matching/EqualToJsonTest.java | 7 +- .../stubbing/InMemoryStubMappingsTest.java | 2 +- .../wiremock/stubbing/ScenariosTest.java | 2 +- .../testsupport/MockHttpServletRequest.java | 2 +- 57 files changed, 1002 insertions(+), 779 deletions(-) diff --git a/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java b/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java index cc4653db3d..aa8f7a654b 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/WireMockServer.java @@ -56,8 +56,8 @@ public class WireMockServer implements Container, Stubbing, Admin { private final WireMockApp wireMockApp; private final StubRequestHandler stubRequestHandler; - private final HttpServer httpServer; - private final Notifier notifier; + private final HttpServer httpServer; + private final Notifier notifier; protected final Options options; @@ -71,7 +71,7 @@ public WireMockServer(final Options options) { this.stubRequestHandler = this.wireMockApp.buildStubRequestHandler(); final HttpServerFactory httpServerFactory = options.httpServerFactory(); - this.httpServer = + this.httpServer = httpServerFactory.buildHttpServer( options, this.wireMockApp.buildAdminRequestHandler(), this.stubRequestHandler); @@ -79,87 +79,106 @@ public WireMockServer(final Options options) { } public WireMockServer( - final int port, final Integer httpsPort, final FileSource fileSource, final boolean enableBrowserProxying, - final ProxySettings proxySettings, final Notifier notifier) { - this(wireMockConfig() - .port(port) - .httpsPort(httpsPort) - .fileSource(fileSource) - .enableBrowserProxying(enableBrowserProxying) - .proxyVia(proxySettings) - .notifier(notifier)); - } + final int port, + final Integer httpsPort, + final FileSource fileSource, + final boolean enableBrowserProxying, + final ProxySettings proxySettings, + final Notifier notifier) { + this( + wireMockConfig() + .port(port) + .httpsPort(httpsPort) + .fileSource(fileSource) + .enableBrowserProxying(enableBrowserProxying) + .proxyVia(proxySettings) + .notifier(notifier)); + } - public WireMockServer(final int port, final FileSource fileSource, final boolean enableBrowserProxying, - final ProxySettings proxySettings) { - this(wireMockConfig() - .port(port) - .fileSource(fileSource) - .enableBrowserProxying(enableBrowserProxying) - .proxyVia(proxySettings)); - } + public WireMockServer( + final int port, + final FileSource fileSource, + final boolean enableBrowserProxying, + final ProxySettings proxySettings) { + this( + wireMockConfig() + .port(port) + .fileSource(fileSource) + .enableBrowserProxying(enableBrowserProxying) + .proxyVia(proxySettings)); + } - public WireMockServer(final int port, final FileSource fileSource, final boolean enableBrowserProxying) { - this(wireMockConfig() - .port(port) - .fileSource(fileSource) - .enableBrowserProxying(enableBrowserProxying)); - } + public WireMockServer( + final int port, final FileSource fileSource, final boolean enableBrowserProxying) { + this( + wireMockConfig() + .port(port) + .fileSource(fileSource) + .enableBrowserProxying(enableBrowserProxying)); + } public WireMockServer(final int port) { - this(wireMockConfig().port(port)); - } + this(wireMockConfig().port(port)); + } public WireMockServer(final int port, final Integer httpsPort) { this(wireMockConfig().port(port).httpsPort(httpsPort)); } - public WireMockServer() { - this(wireMockConfig()); - } + public WireMockServer() { + this(wireMockConfig()); + } - public WireMockServer(String filenameTemplate) { + public WireMockServer(String filenameTemplate) { this(wireMockConfig().filenameTemplate(filenameTemplate)); - }public void loadMappingsUsing(final MappingsLoader mappingsLoader) { - this.wireMockApp.loadMappingsUsing(mappingsLoader); - } + } + + public void loadMappingsUsing(final MappingsLoader mappingsLoader) { + this.wireMockApp.loadMappingsUsing(mappingsLoader); + } public void addMockServiceRequestListener(RequestListener listener) { stubRequestHandler.addRequestListener(listener); } - public void enableRecordMappings(final FileSource mappingsFileSource, final FileSource filesFileSource) { - this.addMockServiceRequestListener( + public void enableRecordMappings( + final FileSource mappingsFileSource, final FileSource filesFileSource) { + this.addMockServiceRequestListener( new StubMappingJsonRecorder( new FileSourceBlobStore(mappingsFileSource), new FileSourceBlobStore(filesFileSource), this.wireMockApp, this.options.matchingHeaders())); this.notifier.info("Recording mappings to " + mappingsFileSource.getPath()); - } + } public void stop() { this.httpServer.stop(); - } + } - public void start() { + public void start() { // Try to ensure this is warmed up on the main thread so that it's inherited by worker threads - Json.getObjectMapper(); try { - this.httpServer.start(); + Json.getObjectMapper(); + try { + this.httpServer.start(); } catch (final Exception e) { - throw new FatalStartupException(e); - } + throw new FatalStartupException(e); } + } - /** - * Gracefully shutdown the server. - *

+ /** + * Gracefully shutdown the server. + * + *

+ * *

This method assumes it is being called as the result of an incoming HTTP request. */ @Override public void shutdown() { final WireMockServer server = this; - final Thread shutdownThread = new Thread(() -> { + final Thread shutdownThread = + new Thread( + () -> { try { // We have to sleep briefly to finish serving the shutdown request before stopping // the server, as @@ -167,10 +186,9 @@ public void shutdown() { // See http://stackoverflow.com/questions/4650713 Thread.sleep(100); } catch (final InterruptedException e) { - throw new RuntimeException(e); - } - server.stop(); - + throw new RuntimeException(e); + } + server.stop(); }); shutdownThread.start(); } @@ -207,8 +225,8 @@ public String url(String path) { public String baseUrl() { final boolean https = options.httpsSettings().enabled(); - final String protocol = https ? "https" : "http"; - final int port = https ? httpsPort() : port(); + final String protocol = https ? "https" : "http"; + final int port = https ? httpsPort() : port(); return String.format("%s://localhost:%d", protocol, port); } @@ -229,17 +247,17 @@ public StubMapping stubFor(final MappingBuilder mappingBuilder) { @Override public void editStub(final MappingBuilder mappingBuilder) { - this.client.editStubMapping(mappingBuilder); + this.client.editStubMapping(mappingBuilder); } @Override public void removeStub(final MappingBuilder mappingBuilder) { - this.client.removeStubMapping(mappingBuilder); + this.client.removeStubMapping(mappingBuilder); } @Override public void removeStub(final StubMapping stubMapping) { - this.client.removeStubMapping(stubMapping); + this.client.removeStubMapping(stubMapping); } @Override @@ -259,12 +277,12 @@ public List findStubMappingsByMetadata(final StringValuePattern pat @Override public void removeStubMappingsByMetadata(final StringValuePattern pattern) { - this.client.removeStubsByMetadataPattern(pattern); + this.client.removeStubsByMetadataPattern(pattern); } @Override public void removeStubMapping(final StubMapping stubMapping) { - this.wireMockApp.removeStubMapping(stubMapping); + this.wireMockApp.removeStubMapping(stubMapping); } @Override @@ -274,12 +292,12 @@ public void removeStubMapping(UUID id) { @Override public void verify(final RequestPatternBuilder requestPatternBuilder) { - this.client.verifyThat(requestPatternBuilder); + this.client.verifyThat(requestPatternBuilder); } @Override public void verify(final int count, final RequestPatternBuilder requestPatternBuilder) { - this.client.verifyThat(count, requestPatternBuilder); + this.client.verifyThat(count, requestPatternBuilder); } @Override @@ -300,7 +318,7 @@ public List getAllServeEvents() { @Override public void setGlobalFixedDelay(final int milliseconds) { - this.client.setGlobalFixedDelayVariable(milliseconds); + this.client.setGlobalFixedDelayVariable(milliseconds); } @Override @@ -325,12 +343,12 @@ public List findNearMissesFor(final LoggedRequest loggedRequest) { @Override public void addStubMapping(final StubMapping stubMapping) { - this.wireMockApp.addStubMapping(stubMapping); + this.wireMockApp.addStubMapping(stubMapping); } @Override public void editStubMapping(final StubMapping stubMapping) { - this.wireMockApp.editStubMapping(stubMapping); + this.wireMockApp.editStubMapping(stubMapping); } @Override @@ -421,7 +439,7 @@ public FindServeEventsResult removeServeEventsForStubsMatchingMetadata( @Override public void updateGlobalSettings(final GlobalSettings newSettings) { - this.wireMockApp.updateGlobalSettings(newSettings); + this.wireMockApp.updateGlobalSettings(newSettings); } @Override @@ -456,17 +474,17 @@ public FindNearMissesResult findTopNearMissesFor(final RequestPattern requestPat @Override public void startRecording(final String targetBaseUrl) { - this.wireMockApp.startRecording(targetBaseUrl); + this.wireMockApp.startRecording(targetBaseUrl); } @Override public void startRecording(final RecordSpec spec) { - this.wireMockApp.startRecording(spec); + this.wireMockApp.startRecording(spec); } @Override public void startRecording(final RecordSpecBuilder recordSpec) { - this.wireMockApp.startRecording(recordSpec); + this.wireMockApp.startRecording(recordSpec); } @Override @@ -502,17 +520,17 @@ public Options getOptions() { @Override public void shutdownServer() { this.shutdown(); - } + } - @Override - public ProxyConfig getProxyConfig() { - return this.wireMockApp.getProxyConfig(); - } + @Override + public ProxyConfig getProxyConfig() { + return this.wireMockApp.getProxyConfig(); + } - @Override - public void enableProxy(final UUID id) { - this.wireMockApp.enableProxy(id); - } + @Override + public void enableProxy(final UUID id) { + this.wireMockApp.enableProxy(id); + } @Override public void disableProxy(final UUID id) { @@ -520,13 +538,13 @@ public void disableProxy(final UUID id) { } @Override - public ListStubMappingsResult findAllStubsByMetadata(final StringValuePattern pattern) { - return this.wireMockApp.findAllStubsByMetadata(pattern); - } + public ListStubMappingsResult findAllStubsByMetadata(final StringValuePattern pattern) { + return this.wireMockApp.findAllStubsByMetadata(pattern); + } - @Override + @Override public void removeStubsByMetadata(final StringValuePattern pattern) { - this.wireMockApp.removeStubsByMetadata(pattern); + this.wireMockApp.removeStubsByMetadata(pattern); } @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java b/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java index aac61d34da..5ce049a8e1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/AdminRoutes.java @@ -118,7 +118,7 @@ private void initDefaultRoutes(final Router router) { router.add(GET, "/proxy", GetProxyConfigTask.class); router.add(PUT, "/proxy/{id}", EnableProxyTask.class); router.add(DELETE, "/proxy/{id}", DisableProxyTask.class); - } + } protected void initAdditionalRoutes(final Router routeBuilder) { for (final AdminApiExtension apiExtension : this.apiExtensions) { @@ -153,22 +153,24 @@ public RouteBuilder() { @Override public void add( - final RequestMethod method, final String urlTemplate, final Class taskClass) { - try { - final AdminTask task = taskClass.getDeclaredConstructor().newInstance(); + final RequestMethod method, + final String urlTemplate, + final Class taskClass) { + try { + final AdminTask task = taskClass.getDeclaredConstructor().newInstance(); this.add(requestSpec(method, urlTemplate), task); } catch (final Exception e) { - throwUnchecked(e); - } - } + throwUnchecked(e); + } + } @Override public void add(final RequestMethod method, final String urlTemplate, final AdminTask task) { - this.add(requestSpec(method, urlTemplate), task); + this.add(requestSpec(method, urlTemplate), task); } public void add(final RequestSpec requestSpec, final AdminTask task) { - this.builder.put(requestSpec, task); + this.builder.put(requestSpec, task); } ImmutableBiMap build() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java b/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java index 3a2c499bca..4a53cd3055 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/model/SingleStubMappingResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java index 91e5e048e0..8611b0cb9d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/DisableProxyTask.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; @@ -5,18 +20,18 @@ import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; - import java.util.UUID; /** * @author Christopher Holomek */ public class DisableProxyTask implements AdminTask { - @Override - public ResponseDefinition execute(final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { - final String idString = pathParams.get("id"); - final UUID id = UUID.fromString(idString); - admin.disableProxy(id); - return ResponseDefinition.ok(); - } + @Override + public ResponseDefinition execute( + final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { + final String idString = pathParams.get("id"); + final UUID id = UUID.fromString(idString); + admin.disableProxy(id); + return ResponseDefinition.ok(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java index e6df968b27..575a0e8dc0 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/EnableProxyTask.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; @@ -5,18 +20,18 @@ import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; - import java.util.UUID; /** * @author Christopher Holomek */ public class EnableProxyTask implements AdminTask { - @Override - public ResponseDefinition execute(final Admin admin, ServeEvent serveEvent, final PathParams pathParams) { - final String idString = pathParams.get("id"); - final UUID id = UUID.fromString(idString); - admin.enableProxy(id); - return ResponseDefinition.ok(); - } + @Override + public ResponseDefinition execute( + final Admin admin, ServeEvent serveEvent, final PathParams pathParams) { + final String idString = pathParams.get("id"); + final UUID id = UUID.fromString(idString); + admin.enableProxy(id); + return ResponseDefinition.ok(); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java index 4414824135..9fa6a7b9d8 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetProxyConfigTask.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.admin.tasks; import com.github.tomakehurst.wiremock.admin.AdminTask; @@ -10,8 +25,9 @@ * @author Christopher Holomek */ public class GetProxyConfigTask implements AdminTask { - @Override - public ResponseDefinition execute(final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { - return ResponseDefinition.okForJson(admin.getProxyConfig()); - } + @Override + public ResponseDefinition execute( + final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { + return ResponseDefinition.okForJson(admin.getProxyConfig()); + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java index e8681aeadc..f4b4cbeec1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java +++ b/src/main/java/com/github/tomakehurst/wiremock/admin/tasks/GetStubFilesTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,30 +17,25 @@ import static com.github.tomakehurst.wiremock.core.WireMockApp.FILES_ROOT; -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import com.github.tomakehurst.wiremock.admin.AdminTask; -import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.common.Errors; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.common.TextFile; +import com.github.tomakehurst.wiremock.common.url.PathParams; import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; public class GetStubFilesTask implements AdminTask { - @Override - public ResponseDefinition execute(final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { - try { - final FileSource fileSource = admin.getOptions().filesRoot().child(FILES_ROOT); - final TextFile textFile = fileSource.getTextFileNamed(pathParams.get("0")); - return ResponseDefinition.okForJson(textFile.readContentsAsString()); - } catch (final Exception e) { - return ResponseDefinition.badRequest(Errors.single(60, "Could not find specified file.")); - } + @Override + public ResponseDefinition execute( + final Admin admin, final ServeEvent serveEvent, final PathParams pathParams) { + try { + final FileSource fileSource = admin.getOptions().filesRoot().child(FILES_ROOT); + final TextFile textFile = fileSource.getTextFileNamed(pathParams.get("0")); + return ResponseDefinition.okForJson(textFile.readContentsAsString()); + } catch (final Exception e) { + return ResponseDefinition.badRequest(Errors.single(60, "Could not find specified file.")); } + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java b/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java index d910a89d36..84c52af6c9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java +++ b/src/main/java/com/github/tomakehurst/wiremock/client/HttpAdminClient.java @@ -81,45 +81,59 @@ public HttpAdminClient(final String host, final int port, final String urlPathPr this("http", host, port, urlPathPrefix); } - public HttpAdminClient(final String scheme, final String host, final int port, final String urlPathPrefix) { + public HttpAdminClient( + final String scheme, final String host, final int port, final String urlPathPrefix) { this(scheme, host, port, urlPathPrefix, null, null, 0, noClientAuthenticator()); } public HttpAdminClient( - final String scheme, final String host, final int port, final String urlPathPrefix, final String hostHeader) { - this(scheme, host, port, urlPathPrefix, hostHeader, null, 0, noClientAuthenticator()); - } + final String scheme, + final String host, + final int port, + final String urlPathPrefix, + final String hostHeader) { + this(scheme, host, port, urlPathPrefix, hostHeader, null, 0, noClientAuthenticator()); + } public HttpAdminClient( final String scheme, - final String host, - final int port, - final String urlPathPrefix, - final String hostHeader, - final String proxyHost, - final int proxyPort) { - this(scheme, host, port, urlPathPrefix, hostHeader, proxyHost, proxyPort, noClientAuthenticator()); - } + final String host, + final int port, + final String urlPathPrefix, + final String hostHeader, + final String proxyHost, + final int proxyPort) { + this( + scheme, + host, + port, + urlPathPrefix, + hostHeader, + proxyHost, + proxyPort, + noClientAuthenticator()); + } public HttpAdminClient( final String scheme, - final String host, - final int port, - final String urlPathPrefix, - final String hostHeader, - final String proxyHost, - final int proxyPort, - final ClientAuthenticator authenticator) { - this.scheme = scheme; - this.host = host; - this.port = port; - this.urlPathPrefix = urlPathPrefix; - this.hostHeader = hostHeader; - this.authenticator = authenticator; + final String host, + final int port, + final String urlPathPrefix, + final String hostHeader, + final String proxyHost, + final int proxyPort, + final ClientAuthenticator authenticator) { + this.scheme = scheme; + this.host = host; + this.port = port; + this.urlPathPrefix = urlPathPrefix; + this.hostHeader = hostHeader; + this.authenticator = authenticator; this.adminRoutes = AdminRoutes.forClient(); - this.httpClient = HttpClientFactory.createClient(this.createProxySettings(proxyHost, proxyPort)); + this.httpClient = + HttpClientFactory.createClient(this.createProxySettings(proxyHost, proxyPort)); } public HttpAdminClient(final String host, final int port) { @@ -132,9 +146,10 @@ private static StringEntity jsonStringEntity(final String json) { @Override public void addStubMapping(final StubMapping stubMapping) { - if (stubMapping.getRequest().hasInlineCustomMatcher()) { - throw new AdminException("Custom matchers can't be used when administering a remote WireMock server. " - + "Use WireMockRule.stubFor() or WireMockServer.stubFor() to administer the local instance."); + if (stubMapping.getRequest().hasInlineCustomMatcher()) { + throw new AdminException( + "Custom matchers can't be used when administering a remote WireMock server. " + + "Use WireMockRule.stubFor() or WireMockServer.stubFor() to administer the local instance."); } this.executeRequest( @@ -153,7 +168,7 @@ public void editStubMapping(StubMapping stubMapping) { @Override public void removeStubMapping(StubMapping stubbMapping) { - this.postJsonAssertOkAndReturnBody( + this.postJsonAssertOkAndReturnBody( this.urlFor(RemoveMatchingStubMappingTask.class), Json.write(stubbMapping)); } @@ -168,12 +183,13 @@ public void removeStubMapping(UUID id) { @Override public ListStubMappingsResult listAllStubMappings() { return this.executeRequest( - this.adminRoutes.requestSpecForTask(GetAllStubMappingsTask.class), ListStubMappingsResult.class); + this.adminRoutes.requestSpecForTask(GetAllStubMappingsTask.class), + ListStubMappingsResult.class); } @Override public SingleStubMappingResult getStubMapping(UUID id) { - return this.executeRequest( + return this.executeRequest( this.adminRoutes.requestSpecForTask(GetStubMappingTask.class), PathParams.single("id", id), SingleStubMappingResult.class); @@ -234,7 +250,7 @@ public GetServeEventsResult getServeEvents(ServeEventQuery query) { @Override public SingleServedStubResult getServedStub(final UUID id) { - return this.executeRequest( + return this.executeRequest( this.adminRoutes.requestSpecForTask(GetServedStubTask.class), PathParams.single("id", id), SingleServedStubResult.class); @@ -242,7 +258,8 @@ public SingleServedStubResult getServedStub(final UUID id) { @Override public VerificationResult countRequestsMatching(final RequestPattern requestPattern) { - final String body = this.postJsonAssertOkAndReturnBody( + final String body = + this.postJsonAssertOkAndReturnBody( this.urlFor(GetRequestCountTask.class), Json.write(requestPattern)); return VerificationResult.from(body); } @@ -256,7 +273,8 @@ public FindRequestsResult findRequestsMatching(RequestPattern requestPattern) { @Override public FindRequestsResult findUnmatchedRequests() { - final String body = this.getJsonAssertOkAndReturnBody(this.urlFor(FindUnmatchedRequestsTask.class)); + final String body = + this.getJsonAssertOkAndReturnBody(this.urlFor(FindUnmatchedRequestsTask.class)); return Json.read(body, FindRequestsResult.class); } @@ -287,15 +305,15 @@ public FindServeEventsResult removeServeEventsForStubsMatchingMetadata( @Override public FindNearMissesResult findNearMissesForUnmatchedRequests() { - final String body = this.getJsonAssertOkAndReturnBody(this.urlFor(FindNearMissesForUnmatchedTask.class)); + final String body = + this.getJsonAssertOkAndReturnBody(this.urlFor(FindNearMissesForUnmatchedTask.class)); return Json.read(body, FindNearMissesResult.class); } @Override public GetScenariosResult getAllScenarios() { return this.executeRequest( - this.adminRoutes.requestSpecForTask(GetAllScenariosTask.class), - GetScenariosResult.class); + this.adminRoutes.requestSpecForTask(GetAllScenariosTask.class), GetScenariosResult.class); } @Override @@ -317,7 +335,8 @@ public void setScenarioState(String name, String state) { @Override public FindNearMissesResult findTopNearMissesFor(final LoggedRequest loggedRequest) { - final String body = this.postJsonAssertOkAndReturnBody( + final String body = + this.postJsonAssertOkAndReturnBody( this.urlFor(FindNearMissesForRequestTask.class), Json.write(loggedRequest)); return Json.read(body, FindNearMissesResult.class); @@ -325,7 +344,8 @@ public FindNearMissesResult findTopNearMissesFor(final LoggedRequest loggedReque @Override public FindNearMissesResult findTopNearMissesFor(final RequestPattern requestPattern) { - final String body = this.postJsonAssertOkAndReturnBody( + final String body = + this.postJsonAssertOkAndReturnBody( this.urlFor(FindNearMissesForRequestPatternTask.class), Json.write(requestPattern)); return Json.read(body, FindNearMissesResult.class); @@ -333,14 +353,13 @@ public FindNearMissesResult findTopNearMissesFor(final RequestPattern requestPat @Override public void updateGlobalSettings(final GlobalSettings settings) { - this.postJsonAssertOkAndReturnBody(this.urlFor(GlobalSettingsUpdateTask.class), Json.write(settings)); + this.postJsonAssertOkAndReturnBody( + this.urlFor(GlobalSettingsUpdateTask.class), Json.write(settings)); } @Override public SnapshotRecordResult snapshotRecord() { - final String body = this.postJsonAssertOkAndReturnBody( - this.urlFor(SnapshotTask.class), - ""); + final String body = this.postJsonAssertOkAndReturnBody(this.urlFor(SnapshotTask.class), ""); return Json.read(body, SnapshotRecordResult.class); } @@ -352,35 +371,32 @@ public SnapshotRecordResult snapshotRecord(final RecordSpecBuilder spec) { @Override public SnapshotRecordResult snapshotRecord(final RecordSpec spec) { - final String body = this.postJsonAssertOkAndReturnBody( - this.urlFor(SnapshotTask.class), - Json.write(spec)); + final String body = + this.postJsonAssertOkAndReturnBody(this.urlFor(SnapshotTask.class), Json.write(spec)); return Json.read(body, SnapshotRecordResult.class); } @Override public void startRecording(final String targetBaseUrl) { - this.startRecording(RecordSpec.forBaseUrl(targetBaseUrl)); + this.startRecording(RecordSpec.forBaseUrl(targetBaseUrl)); } @Override public void startRecording(final RecordSpec recordSpec) { - this.postJsonAssertOkAndReturnBody( - this.urlFor(StartRecordingTask.class), - Json.write(recordSpec)); - } + this.postJsonAssertOkAndReturnBody( + this.urlFor(StartRecordingTask.class), Json.write(recordSpec)); + } @Override public void startRecording(final RecordSpecBuilder recordSpec) { - this.startRecording(recordSpec.build()); + this.startRecording(recordSpec.build()); } @Override public SnapshotRecordResult stopRecording() { - final String body = this.postJsonAssertOkAndReturnBody( - this.urlFor(StopRecordingTask.class), - ""); + final String body = + this.postJsonAssertOkAndReturnBody(this.urlFor(StopRecordingTask.class), ""); return Json.read(body, SnapshotRecordResult.class); } @@ -388,7 +404,8 @@ public SnapshotRecordResult stopRecording() { @Override public RecordingStatusResult getRecordingStatus() { return this.executeRequest( - this.adminRoutes.requestSpecForTask(GetRecordingStatusTask.class), RecordingStatusResult.class); + this.adminRoutes.requestSpecForTask(GetRecordingStatusTask.class), + RecordingStatusResult.class); } @Override @@ -402,34 +419,35 @@ public void shutdownServer() { } @Override - public ProxyConfig getProxyConfig() { - return this.executeRequest(this.adminRoutes.requestSpecForTask(GetProxyConfigTask.class), ProxyConfig.class); - } + public ProxyConfig getProxyConfig() { + return this.executeRequest( + this.adminRoutes.requestSpecForTask(GetProxyConfigTask.class), ProxyConfig.class); + } - @Override - public void enableProxy(final UUID id) { - this.postJsonAssertOkAndReturnBody(this.urlFor(EnableProxyTask.class), null); - } + @Override + public void enableProxy(final UUID id) { + this.postJsonAssertOkAndReturnBody(this.urlFor(EnableProxyTask.class), null); + } - @Override - public void disableProxy(final UUID id) { - this.postJsonAssertOkAndReturnBody(this.urlFor(DisableProxyTask.class), null); - } + @Override + public void disableProxy(final UUID id) { + this.postJsonAssertOkAndReturnBody(this.urlFor(DisableProxyTask.class), null); + } - @Override + @Override public ListStubMappingsResult findAllStubsByMetadata(final StringValuePattern pattern) { - return this.executeRequest( - this.adminRoutes.requestSpecForTask(FindStubMappingsByMetadataTask.class), - pattern, - ListStubMappingsResult.class); + return this.executeRequest( + this.adminRoutes.requestSpecForTask(FindStubMappingsByMetadataTask.class), + pattern, + ListStubMappingsResult.class); } @Override public void removeStubsByMetadata(final StringValuePattern pattern) { - this.executeRequest( - this.adminRoutes.requestSpecForTask(RemoveStubMappingsByMetadataTask.class), - pattern, - Void.class); + this.executeRequest( + this.adminRoutes.requestSpecForTask(RemoveStubMappingsByMetadataTask.class), + pattern, + Void.class); } @Override @@ -456,9 +474,9 @@ private ProxySettings createProxySettings(final String proxyHost, final int prox } private String postJsonAssertOkAndReturnBody(final String url, final String json) { - final HttpPost post = new HttpPost(url); - if (json != null) { - post.setEntity(HttpAdminClient.jsonStringEntity(json)); + final HttpPost post = new HttpPost(url); + if (json != null) { + post.setEntity(HttpAdminClient.jsonStringEntity(json)); } return safelyExecuteRequest(url, post); @@ -474,15 +492,16 @@ private String putJsonAssertOkAndReturnBody(String url, String json) { } protected String getJsonAssertOkAndReturnBody(final String url) { - final HttpGet get = new HttpGet(url); - return this.safelyExecuteRequest(url, get); + final HttpGet get = new HttpGet(url); + return this.safelyExecuteRequest(url, get); } private void executeRequest(final RequestSpec requestSpec) { - this.executeRequest(requestSpec, PathParams.empty(), null, Void.class); + this.executeRequest(requestSpec, PathParams.empty(), null, Void.class); } - private R executeRequest(final RequestSpec requestSpec, final B requestBody, final Class responseType) { + private R executeRequest( + final RequestSpec requestSpec, final B requestBody, final Class responseType) { return this.executeRequest(requestSpec, PathParams.empty(), requestBody, responseType); } @@ -492,7 +511,7 @@ private R executeRequest(final RequestSpec requestSpec, final Class re private R executeRequest( final RequestSpec requestSpec, final PathParams pathParams, final Class responseType) { - return this.executeRequest(requestSpec, pathParams, null, responseType); + return this.executeRequest(requestSpec, pathParams, null, responseType); } private R executeRequest( @@ -532,10 +551,10 @@ private String safelyExecuteRequest(String url, ClassicHttpRequest request) { final List httpHeaders = this.authenticator.generateAuthHeaders(); for (final HttpHeader header : httpHeaders) { - for (final String value : header.values()) { - request.addHeader(header.key(), value); - } - } + for (final String value : header.values()) { + request.addHeader(header.key(), value); + } + } try (final CloseableHttpResponse response = this.httpClient.execute(request)) { final int statusCode = response.getCode(); @@ -549,10 +568,10 @@ private String safelyExecuteRequest(String url, ClassicHttpRequest request) { } final String body = getEntityAsStringAndCloseStream(response); - if (HttpStatus.isClientError(statusCode)) { - final Errors errors = Json.read(body, Errors.class); - throw ClientError.fromErrors(errors); - } + if (HttpStatus.isClientError(statusCode)) { + final Errors errors = Json.read(body, Errors.class); + throw ClientError.fromErrors(errors); + } return body; } catch (final Exception e) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java b/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java index 8e6ccc5374..37cf76e32f 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/Metadata.java @@ -57,7 +57,7 @@ public List getList(String key) { return checkPresenceValidityAndCast(key, List.class); } - public Map getMap(String key) { + public Map getMap(String key) { return checkPresenceValidityAndCast(key, Map.class); } diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java b/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java index 24215f5cc8..ed5272938a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ServletContextFileSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java b/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java index dcb56744a5..0e4346962e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/StreamSources.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2022 Thomas Akehurst + * Copyright (C) 2018-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java b/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java index 2111987a2c..4b97c590d1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/common/ssl/KeyStoreSourceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java b/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java index 64ed10df9f..06d93c0b36 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/Admin.java @@ -30,39 +30,41 @@ public interface Admin { - void addStubMapping(StubMapping stubMapping); + void addStubMapping(StubMapping stubMapping); - void editStubMapping(StubMapping stubMapping); + void editStubMapping(StubMapping stubMapping); - void removeStubMapping(StubMapping stubbMapping); + void removeStubMapping(StubMapping stubbMapping); - void removeStubMapping(UUID id); + void removeStubMapping(UUID id); ListStubMappingsResult listAllStubMappings(); - SingleStubMappingResult getStubMapping(UUID id); + SingleStubMappingResult getStubMapping(UUID id); - void saveMappings(); + void saveMappings(); - void resetRequests(); + void resetRequests(); - void resetScenarios(); + void resetScenarios(); - void resetMappings(); + void resetMappings(); - void resetAll(); + void resetAll(); - void resetToDefaultMappings(); + void resetToDefaultMappings(); - GetServeEventsResult getServeEvents(); + GetServeEventsResult getServeEvents(); - GetServeEventsResult getServeEvents(ServeEventQuery query); SingleServedStubResult getServedStub(UUID id); + GetServeEventsResult getServeEvents(ServeEventQuery query); - VerificationResult countRequestsMatching(RequestPattern requestPattern); + SingleServedStubResult getServedStub(UUID id); - FindRequestsResult findRequestsMatching(RequestPattern requestPattern); + VerificationResult countRequestsMatching(RequestPattern requestPattern); - FindRequestsResult findUnmatchedRequests(); + FindRequestsResult findRequestsMatching(RequestPattern requestPattern); + + FindRequestsResult findUnmatchedRequests(); void removeServeEvent(UUID eventId); @@ -70,11 +72,11 @@ public interface Admin { FindServeEventsResult removeServeEventsForStubsMatchingMetadata(StringValuePattern pattern); - FindNearMissesResult findTopNearMissesFor(LoggedRequest loggedRequest); + FindNearMissesResult findTopNearMissesFor(LoggedRequest loggedRequest); - FindNearMissesResult findTopNearMissesFor(RequestPattern requestPattern); + FindNearMissesResult findTopNearMissesFor(RequestPattern requestPattern); - FindNearMissesResult findNearMissesForUnmatchedRequests(); + FindNearMissesResult findNearMissesForUnmatchedRequests(); GetScenariosResult getAllScenarios(); @@ -84,21 +86,21 @@ public interface Admin { void updateGlobalSettings(GlobalSettings settings); - SnapshotRecordResult snapshotRecord(); + SnapshotRecordResult snapshotRecord(); - SnapshotRecordResult snapshotRecord(RecordSpec spec); + SnapshotRecordResult snapshotRecord(RecordSpec spec); - SnapshotRecordResult snapshotRecord(RecordSpecBuilder spec); + SnapshotRecordResult snapshotRecord(RecordSpecBuilder spec); - void startRecording(String targetBaseUrl); + void startRecording(String targetBaseUrl); - void startRecording(RecordSpec spec); + void startRecording(RecordSpec spec); - void startRecording(RecordSpecBuilder recordSpec); + void startRecording(RecordSpecBuilder recordSpec); - SnapshotRecordResult stopRecording(); + SnapshotRecordResult stopRecording(); - RecordingStatusResult getRecordingStatus(); + RecordingStatusResult getRecordingStatus(); Options getOptions(); @@ -106,13 +108,13 @@ public interface Admin { ProxyConfig getProxyConfig(); - void enableProxy(UUID id); + void enableProxy(UUID id); - void disableProxy(UUID id); + void disableProxy(UUID id); - ListStubMappingsResult findAllStubsByMetadata(StringValuePattern pattern); + ListStubMappingsResult findAllStubsByMetadata(StringValuePattern pattern); - void removeStubsByMetadata(StringValuePattern pattern); + void removeStubsByMetadata(StringValuePattern pattern); void importStubs(StubImport stubImport); diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/Options.java b/src/main/java/com/github/tomakehurst/wiremock/core/Options.java index 82f7c68488..05f0cbcc82 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/Options.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/Options.java @@ -57,7 +57,9 @@ enum ChunkedEncodingPolicy { int containerThreads(); - /** @deprecated use {@link BrowserProxySettings#enabled()} */ + /** + * @deprecated use {@link BrowserProxySettings#enabled()} + */ @Deprecated boolean browserProxyingEnabled(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java b/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java index 994fa59daa..211baac7ab 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/ProxyHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.core; import com.github.tomakehurst.wiremock.admin.model.SingleStubMappingResult; @@ -5,7 +20,6 @@ import com.github.tomakehurst.wiremock.jetty.websockets.Message; import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.stubbing.StubMapping; - import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -16,127 +30,148 @@ * @author Christopher Holomek */ public class ProxyHandler { - private final Map proxyUrl = new ConcurrentHashMap<>(); - - private final Admin admin; - - public ProxyHandler(final Admin admin) { - this.admin = admin; - } + private final Map proxyUrl = new ConcurrentHashMap<>(); - public void clear() { - this.proxyUrl.clear(); - } + private final Admin admin; - public void removeProxyConfig(final UUID uuid) { - this.proxyUrl.remove(uuid); - } + public ProxyHandler(final Admin admin) { + this.admin = admin; + } - public void disableProxyUrl(final UUID uuid) { - final SingleStubMappingResult result = this.admin.getStubMapping(uuid); + public void clear() { + this.proxyUrl.clear(); + } - if (!result.isPresent()) { - return; - } + public void removeProxyConfig(final UUID uuid) { + this.proxyUrl.remove(uuid); + } - final StubMapping mapping = result.getItem(); + public void disableProxyUrl(final UUID uuid) { + final SingleStubMappingResult result = this.admin.getStubMapping(uuid); - if (mapping.getResponse().isProxyResponse() && !this.proxyUrl.containsKey(uuid)) { - this.storeProxyUrl(mapping); - this.disableProxyUrlInMapping(mapping); - } - WebSocketEndpoint.broadcast(Message.MAPPINGS); + if (!result.isPresent()) { + return; } - public void enableProxyUrl(final UUID uuid) { - final SingleStubMappingResult result = this.admin.getStubMapping(uuid); - - if (!result.isPresent()) { - return; - } + final StubMapping mapping = result.getItem(); - final StubMapping mapping = result.getItem(); - - if (this.proxyUrl.containsKey(uuid)) { - this.enableProxyUrlInMapping(mapping); - this.removeProxyUrl(mapping); - } - WebSocketEndpoint.broadcast(Message.MAPPINGS); + if (mapping.getResponse().isProxyResponse() && !this.proxyUrl.containsKey(uuid)) { + this.storeProxyUrl(mapping); + this.disableProxyUrlInMapping(mapping); } + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } - private void disableProxyUrlInMapping(final StubMapping mapping) { - - final ResponseDefinition response = mapping.getResponse(); - final ResponseDefinition copy = this.copyResponseDefinition(response, null); + public void enableProxyUrl(final UUID uuid) { + final SingleStubMappingResult result = this.admin.getStubMapping(uuid); - //we are manipulation the mapping directly instead of editing. - //The editing function would replace the complete stubmapping. Not sure if this is a good idea. - mapping.setResponse(copy); + if (!result.isPresent()) { + return; } - private void enableProxyUrlInMapping(final StubMapping mapping) { - - final ResponseDefinition response = mapping.getResponse(); - final ResponseDefinition copy = this.copyResponseDefinition(response, this.proxyUrl.get(mapping.getUuid())); - - //we are manipulation the mapping directly instead of editing. - //The editing function would replace the complete stubmapping. Not sure if this is a good idea. - mapping.setResponse(copy); - } + final StubMapping mapping = result.getItem(); - private void storeProxyUrl(final StubMapping mapping) { - this.proxyUrl.put(mapping.getUuid(), mapping.getResponse().getProxyBaseUrl()); + if (this.proxyUrl.containsKey(uuid)) { + this.enableProxyUrlInMapping(mapping); + this.removeProxyUrl(mapping); } - - private void removeProxyUrl(final StubMapping mapping) { - this.proxyUrl.remove(mapping.getUuid()); + WebSocketEndpoint.broadcast(Message.MAPPINGS); + } + + private void disableProxyUrlInMapping(final StubMapping mapping) { + + final ResponseDefinition response = mapping.getResponse(); + final ResponseDefinition copy = this.copyResponseDefinition(response, null); + + // we are manipulation the mapping directly instead of editing. + // The editing function would replace the complete stubmapping. Not sure if this is a good idea. + mapping.setResponse(copy); + } + + private void enableProxyUrlInMapping(final StubMapping mapping) { + + final ResponseDefinition response = mapping.getResponse(); + final ResponseDefinition copy = + this.copyResponseDefinition(response, this.proxyUrl.get(mapping.getUuid())); + + // we are manipulation the mapping directly instead of editing. + // The editing function would replace the complete stubmapping. Not sure if this is a good idea. + mapping.setResponse(copy); + } + + private void storeProxyUrl(final StubMapping mapping) { + this.proxyUrl.put(mapping.getUuid(), mapping.getResponse().getProxyBaseUrl()); + } + + private void removeProxyUrl(final StubMapping mapping) { + this.proxyUrl.remove(mapping.getUuid()); + } + + private ResponseDefinition copyResponseDefinition( + final ResponseDefinition response, final String proxyUrl) { + final ResponseDefinition copy; + if (response.getByteBodyIfBinary() != null) { + // Binary body + copy = + new ResponseDefinition( + response.getStatus(), + response.getStatusMessage(), + response.getByteBody(), + response.getJsonBody(), + response.getBase64Body(), + response.getBodyFileName(), + response.getHeaders(), + response.getAdditionalProxyRequestHeaders(), + response.getFixedDelayMilliseconds(), + response.getDelayDistribution(), + response.getChunkedDribbleDelay(), + // proxy url + proxyUrl, + response.getProxyUrlPrefixToRemove(), + // end + response.getFault(), + response.getTransformers(), + response.getTransformerParameters(), + response.wasConfigured()); + } else { + // String body + copy = + new ResponseDefinition( + response.getStatus(), + response.getStatusMessage(), + response.getBody(), + response.getJsonBody(), + response.getBase64Body(), + response.getBodyFileName(), + response.getHeaders(), + response.getAdditionalProxyRequestHeaders(), + response.getFixedDelayMilliseconds(), + response.getDelayDistribution(), + response.getChunkedDribbleDelay(), + // proxy url + proxyUrl, + response.getProxyUrlPrefixToRemove(), + // end + response.getFault(), + response.getTransformers(), + response.getTransformerParameters(), + response.wasConfigured()); } - private ResponseDefinition copyResponseDefinition(final ResponseDefinition response, final String proxyUrl) { - final ResponseDefinition copy; - if (response.getByteBodyIfBinary() != null) { - //Binary body - copy = new ResponseDefinition(response.getStatus(), response.getStatusMessage(), - response.getByteBody(), response.getJsonBody(), response.getBase64Body(), response.getBodyFileName(), response.getHeaders(), - response.getAdditionalProxyRequestHeaders(), response.getFixedDelayMilliseconds(), - response.getDelayDistribution(), - response.getChunkedDribbleDelay(), - //proxy url - proxyUrl, - response.getProxyUrlPrefixToRemove(), - //end - response.getFault(), response.getTransformers(), response.getTransformerParameters(), - response.wasConfigured()); - } else { - //String body - copy = new ResponseDefinition(response.getStatus(), response.getStatusMessage(), - response.getBody(), response.getJsonBody(), response.getBase64Body(), response.getBodyFileName(), response.getHeaders(), - response.getAdditionalProxyRequestHeaders(), response.getFixedDelayMilliseconds(), - response.getDelayDistribution(), - response.getChunkedDribbleDelay(), - //proxy url - proxyUrl, - response.getProxyUrlPrefixToRemove(), - //end - response.getFault(), response.getTransformers(), response.getTransformerParameters(), - response.wasConfigured()); - } - - return copy; - } + return copy; + } - public Map getConfig() { - final Set keys = this.proxyUrl.keySet(); + public Map getConfig() { + final Set keys = this.proxyUrl.keySet(); - final HashMap copy = new HashMap<>(); + final HashMap copy = new HashMap<>(); - for (final UUID uuid : keys) { - final String s = this.proxyUrl.get(uuid); - if (s != null) { - copy.put(uuid.toString(), s); - } - } - return copy; + for (final UUID uuid : keys) { + final String s = this.proxyUrl.get(uuid); + if (s != null) { + copy.put(uuid.toString(), s); + } } - + return copy; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java index 717f285b70..2e8eb04760 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java +++ b/src/main/java/com/github/tomakehurst/wiremock/core/WireMockApp.java @@ -41,10 +41,8 @@ import com.github.tomakehurst.wiremock.store.Stores; import com.github.tomakehurst.wiremock.stubbing.*; import com.github.tomakehurst.wiremock.verification.*; - import java.util.*; import java.util.stream.Collectors; - import org.apache.commons.lang3.mutable.MutableBoolean; public class WireMockApp implements StubServer, Admin { @@ -91,38 +89,38 @@ public WireMockApp(Options options, Container container) { this.settingsStore = stores.getSettingsStore(); extensions = - new Extensions( - options.getDeclaredExtensions(), - this, - options, - stores, - options.filesRoot().child(FILES_ROOT)); + new Extensions( + options.getDeclaredExtensions(), + this, + options, + stores, + options.filesRoot().child(FILES_ROOT)); extensions.load(); Map customMatchers = - extensions.ofType(RequestMatcherExtension.class); + extensions.ofType(RequestMatcherExtension.class); requestJournal = - options.requestJournalDisabled() - ? new DisabledRequestJournal() - : new StoreBackedRequestJournal( - options.maxRequestJournalEntries().orElse(null), - customMatchers, - stores.getRequestJournalStore()); + options.requestJournalDisabled() + ? new DisabledRequestJournal() + : new StoreBackedRequestJournal( + options.maxRequestJournalEntries().orElse(null), + customMatchers, + stores.getRequestJournalStore()); scenarios = new InMemoryScenarios(stores.getScenariosStore()); stubMappings = - new StoreBackedStubMappings( - stores.getStubStore(), - scenarios, - customMatchers, - extensions.ofType(ResponseDefinitionTransformer.class), - extensions.ofType(ResponseDefinitionTransformerV2.class), - stores.getFilesBlobStore(), - List.copyOf(extensions.ofType(StubLifecycleListener.class).values())); + new StoreBackedStubMappings( + stores.getStubStore(), + scenarios, + customMatchers, + extensions.ofType(ResponseDefinitionTransformer.class), + extensions.ofType(ResponseDefinitionTransformerV2.class), + stores.getFilesBlobStore(), + List.copyOf(extensions.ofType(StubLifecycleListener.class).values())); nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); recorder = - new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); + new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); globalSettingsListeners = List.copyOf(extensions.ofType(GlobalSettingsListener.class).values()); this.container = container; @@ -130,16 +128,16 @@ public WireMockApp(Options options, Container container) { } public WireMockApp( - boolean browserProxyingEnabled, - MappingsLoader defaultMappingsLoader, - MappingsSaver mappingsSaver, - boolean requestJournalDisabled, - Integer maxRequestJournalEntries, - Map transformers, - Map v2transformers, - Map requestMatchers, - FileSource rootFileSource, - Container container) { + boolean browserProxyingEnabled, + MappingsLoader defaultMappingsLoader, + MappingsSaver mappingsSaver, + boolean requestJournalDisabled, + Integer maxRequestJournalEntries, + Map transformers, + Map v2transformers, + Map requestMatchers, + FileSource rootFileSource, + Container container) { this.proxyHandler = new ProxyHandler(this); @@ -150,102 +148,104 @@ public WireMockApp( this.mappingsSaver = mappingsSaver; this.settingsStore = stores.getSettingsStore(); requestJournal = - requestJournalDisabled - ? new DisabledRequestJournal() - : new StoreBackedRequestJournal( - maxRequestJournalEntries, requestMatchers, stores.getRequestJournalStore()); + requestJournalDisabled + ? new DisabledRequestJournal() + : new StoreBackedRequestJournal( + maxRequestJournalEntries, requestMatchers, stores.getRequestJournalStore()); scenarios = new InMemoryScenarios(stores.getScenariosStore()); stubMappings = - new StoreBackedStubMappings( - stores.getStubStore(), - scenarios, - requestMatchers, - transformers, - v2transformers, - stores.getFilesBlobStore(), - Collections.emptyList()); + new StoreBackedStubMappings( + stores.getStubStore(), + scenarios, + requestMatchers, + transformers, + v2transformers, + stores.getFilesBlobStore(), + Collections.emptyList()); this.container = container; nearMissCalculator = new NearMissCalculator(stubMappings, requestJournal, scenarios); recorder = - new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); + new Recorder(this, extensions, stores.getFilesBlobStore(), stores.getRecorderStateStore()); globalSettingsListeners = Collections.emptyList(); loadDefaultMappings(); } public AdminRequestHandler buildAdminRequestHandler() { AdminRoutes adminRoutes = - AdminRoutes.forServer(extensions.ofType(AdminApiExtension.class).values(), stores); + AdminRoutes.forServer(extensions.ofType(AdminApiExtension.class).values(), stores); return new AdminRequestHandler( - adminRoutes, - this, - new BasicResponseRenderer(), - options.getAdminAuthenticator(), - options.getHttpsRequiredForAdminApi(), - getAdminRequestFilters(), - getV2AdminRequestFilters(), - options.getDataTruncationSettings()); + adminRoutes, + this, + new BasicResponseRenderer(), + options.getAdminAuthenticator(), + options.getHttpsRequiredForAdminApi(), + getAdminRequestFilters(), + getV2AdminRequestFilters(), + options.getDataTruncationSettings()); } public StubRequestHandler buildStubRequestHandler() { Map postServeActions = extensions.ofType(PostServeAction.class); - Map concatenatedMap = new HashMap<>(extensions.ofType(ServeEventListener.class)); + Map concatenatedMap = + new HashMap<>(extensions.ofType(ServeEventListener.class)); concatenatedMap.put("wiremock-gui", new GuiServeEventListener()); - Map serveEventListeners = Collections.unmodifiableMap(concatenatedMap); + Map serveEventListeners = + Collections.unmodifiableMap(concatenatedMap); BrowserProxySettings browserProxySettings = options.browserProxySettings(); return new StubRequestHandler( - this, - new StubResponseRenderer( - options.getStores().getFilesBlobStore(), - settingsStore, - new ProxyResponseRenderer( - options.proxyVia(), - options.httpsSettings().trustStore(), - options.shouldPreserveHostHeader(), - options.proxyHostHeader(), - settingsStore, - browserProxySettings.trustAllProxyTargets(), - browserProxySettings.trustedProxyTargets(), - options.getStubCorsEnabled(), - options.getProxyTargetRules(), - options.proxyTimeout()), - List.copyOf(extensions.ofType(ResponseTransformer.class).values()), - List.copyOf(extensions.ofType(ResponseTransformerV2.class).values())), - this, - postServeActions, - serveEventListeners, - requestJournal, - getStubRequestFilters(), - getV2StubRequestFilters(), - options.getStubRequestLoggingDisabled(), - options.getDataTruncationSettings(), - options.getNotMatchedRendererFactory().apply(extensions)); + this, + new StubResponseRenderer( + options.getStores().getFilesBlobStore(), + settingsStore, + new ProxyResponseRenderer( + options.proxyVia(), + options.httpsSettings().trustStore(), + options.shouldPreserveHostHeader(), + options.proxyHostHeader(), + settingsStore, + browserProxySettings.trustAllProxyTargets(), + browserProxySettings.trustedProxyTargets(), + options.getStubCorsEnabled(), + options.getProxyTargetRules(), + options.proxyTimeout()), + List.copyOf(extensions.ofType(ResponseTransformer.class).values()), + List.copyOf(extensions.ofType(ResponseTransformerV2.class).values())), + this, + postServeActions, + serveEventListeners, + requestJournal, + getStubRequestFilters(), + getV2StubRequestFilters(), + options.getStubRequestLoggingDisabled(), + options.getDataTruncationSettings(), + options.getNotMatchedRendererFactory().apply(extensions)); } private List getAdminRequestFilters() { return extensions.ofType(RequestFilter.class).values().stream() - .filter(RequestFilter::applyToAdmin) - .collect(Collectors.toList()); + .filter(RequestFilter::applyToAdmin) + .collect(Collectors.toList()); } private List getV2AdminRequestFilters() { return extensions.ofType(RequestFilterV2.class).values().stream() - .filter(RequestFilterV2::applyToAdmin) - .collect(Collectors.toList()); + .filter(RequestFilterV2::applyToAdmin) + .collect(Collectors.toList()); } private List getStubRequestFilters() { return extensions.ofType(RequestFilter.class).values().stream() - .filter(RequestFilter::applyToStubs) - .collect(Collectors.toList()); + .filter(RequestFilter::applyToStubs) + .collect(Collectors.toList()); } private List getV2StubRequestFilters() { return extensions.ofType(RequestFilterV2.class).values().stream() - .filter(RequestFilterV2::applyToStubs) - .collect(Collectors.toList()); + .filter(RequestFilterV2::applyToStubs) + .collect(Collectors.toList()); } private void loadDefaultMappings() { @@ -261,11 +261,11 @@ public ServeEvent serveStubFor(ServeEvent initialServeEvent) { ServeEvent serveEvent = stubMappings.serveFor(initialServeEvent); if (serveEvent.isNoExactMatch() - && browserProxyingEnabled - && serveEvent.getRequest().isBrowserProxyRequest() - && getGlobalSettings().getSettings().getProxyPassThrough()) { + && browserProxyingEnabled + && serveEvent.getRequest().isBrowserProxyRequest() + && getGlobalSettings().getSettings().getProxyPassThrough()) { return ServeEvent.ofUnmatched( - serveEvent.getRequest(), ResponseDefinition.browserProxy(serveEvent.getRequest())); + serveEvent.getRequest(), ResponseDefinition.browserProxy(serveEvent.getRequest())); } return serveEvent; @@ -288,13 +288,13 @@ public void addStubMapping(StubMapping stubMapping) { @Override public void removeStubMapping(StubMapping stubMapping) { stubMappings - .get(stubMapping.getId()) - .ifPresent( - stubToDelete -> { - if (stubToDelete.shouldBePersisted()) { - mappingsSaver.remove(stubToDelete); - } - }); + .get(stubMapping.getId()) + .ifPresent( + stubToDelete -> { + if (stubToDelete.shouldBePersisted()) { + mappingsSaver.remove(stubToDelete); + } + }); stubMappings.removeMapping(stubMapping); @@ -387,7 +387,7 @@ public GetServeEventsResult getServeEvents(ServeEventQuery query) { return GetServeEventsResult.requestJournalEnabled(LimitAndOffsetPaginator.none(serveEvents)); } catch (RequestJournalDisabledException e) { return GetServeEventsResult.requestJournalDisabled( - LimitAndOffsetPaginator.none(requestJournal.getAllServeEvents())); + LimitAndOffsetPaginator.none(requestJournal.getAllServeEvents())); } } @@ -399,7 +399,8 @@ public SingleServedStubResult getServedStub(final UUID id) { @Override public VerificationResult countRequestsMatching(final RequestPattern requestPattern) { try { - return VerificationResult.withCount(this.requestJournal.countRequestsMatching(requestPattern)); + return VerificationResult.withCount( + this.requestJournal.countRequestsMatching(requestPattern)); } catch (final RequestJournalDisabledException e) { return VerificationResult.withRequestJournalDisabled(); } @@ -419,10 +420,10 @@ public FindRequestsResult findRequestsMatching(final RequestPattern requestPatte public FindRequestsResult findUnmatchedRequests() { try { List requests = - requestJournal.getAllServeEvents().stream() - .filter(ServeEvent::isNoExactMatch) - .map(ServeEvent::getRequest) - .collect(Collectors.toList()); + requestJournal.getAllServeEvents().stream() + .filter(ServeEvent::isNoExactMatch) + .map(ServeEvent::getRequest) + .collect(Collectors.toList()); return FindRequestsResult.withRequests(requests); } catch (RequestJournalDisabledException e) { return FindRequestsResult.withRequestJournalDisabled(); @@ -441,18 +442,18 @@ public FindServeEventsResult removeServeEventsMatching(RequestPattern requestPat @Override public FindServeEventsResult removeServeEventsForStubsMatchingMetadata( - StringValuePattern metadataPattern) { + StringValuePattern metadataPattern) { return new FindServeEventsResult( - requestJournal.removeServeEventsForStubsMatchingMetadata(metadataPattern)); + requestJournal.removeServeEventsForStubsMatchingMetadata(metadataPattern)); } @Override public FindNearMissesResult findNearMissesForUnmatchedRequests() { List nearMisses = new ArrayList<>(); List unmatchedServeEvents = - requestJournal.getAllServeEvents().stream() - .filter(ServeEvent::isNoExactMatch) - .collect(Collectors.toList()); + requestJournal.getAllServeEvents().stream() + .filter(ServeEvent::isNoExactMatch) + .collect(Collectors.toList()); for (final ServeEvent serveEvent : unmatchedServeEvents) { nearMisses.addAll(this.nearMissCalculator.findNearestTo(serveEvent.getRequest())); @@ -591,7 +592,7 @@ public RecordingStatusResult getRecordingStatus() { @Override public ListStubMappingsResult findAllStubsByMetadata(final StringValuePattern pattern) { return new ListStubMappingsResult( - LimitAndOffsetPaginator.none(this.stubMappings.findByMetadata(pattern))); + LimitAndOffsetPaginator.none(this.stubMappings.findByMetadata(pattern))); } @Override @@ -606,7 +607,7 @@ public void removeStubsByMetadata(final StringValuePattern pattern) { public void importStubs(StubImport stubImport) { List mappings = stubImport.getMappings(); StubImport.Options importOptions = - getFirstNonNull(stubImport.getImportOptions(), StubImport.Options.DEFAULTS); + getFirstNonNull(stubImport.getImportOptions(), StubImport.Options.DEFAULTS); for (int i = mappings.size() - 1; i >= 0; i--) { StubMapping mapping = mappings.get(i); diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java b/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java index 4d2185b893..7a8526d4d7 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/PostServeAction.java @@ -18,7 +18,9 @@ import com.github.tomakehurst.wiremock.core.Admin; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -/** @deprecated Use {@link ServeEventListener} instead. */ +/** + * @deprecated Use {@link ServeEventListener} instead. + */ @Deprecated public abstract class PostServeAction implements Extension { @@ -30,7 +32,8 @@ public abstract class PostServeAction implements Extension { * @param admin WireMock's admin functions * @param parameters the parameters passed to the extension from the stub mapping */ - public void doAction(ServeEvent serveEvent, Admin admin, Parameters parameters) {}; + public void doAction(ServeEvent serveEvent, Admin admin, Parameters parameters) {} + ; /** * Do something after a request has been served. Called when this extension is applied to a @@ -39,5 +42,6 @@ public abstract class PostServeAction implements Extension { * @param serveEvent the serve event, including the request and the response definition * @param admin WireMock's admin functions */ - public void doGlobalAction(ServeEvent serveEvent, Admin admin) {}; + public void doGlobalAction(ServeEvent serveEvent, Admin admin) {} + ; } diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java index 8d6fe4ed8c..22d4a918da 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseDefinitionTransformer.java @@ -20,7 +20,9 @@ import com.github.tomakehurst.wiremock.http.ResponseDefinition; @Deprecated -/** @deprecated Use {@link ResponseDefinitionTransformerV2} instead */ +/** + * @deprecated Use {@link ResponseDefinitionTransformerV2} instead + */ public abstract class ResponseDefinitionTransformer extends AbstractTransformer { diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java index 086622946f..3c5c6dca85 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/ResponseTransformer.java @@ -20,7 +20,9 @@ import com.github.tomakehurst.wiremock.http.Response; @Deprecated -/** @deprecated Use {@link ResponseTransformerV2} instead */ +/** + * @deprecated Use {@link ResponseTransformerV2} instead + */ public abstract class ResponseTransformer extends AbstractTransformer { @Override diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java index 42fe76a39c..f31a01446b 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/requestfilter/RequestFilter.java @@ -19,7 +19,9 @@ import com.github.tomakehurst.wiremock.http.Request; @Deprecated -/** @deprecated Use {@link RequestFilterV2} instead */ +/** + * @deprecated Use {@link RequestFilterV2} instead + */ public interface RequestFilter extends Extension { RequestFilterAction filter(Request request); diff --git a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java index ebfea4195d..5db0198c73 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java +++ b/src/main/java/com/github/tomakehurst/wiremock/extension/responsetemplating/RequestLine.java @@ -27,7 +27,9 @@ import java.util.stream.Collectors; @Deprecated -/** @deprecated Use the accessors on {@link RequestTemplateModel} */ +/** + * @deprecated Use the accessors on {@link RequestTemplateModel} + */ public class RequestLine { private final RequestMethod method; private final String scheme; diff --git a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java index d7efab7641..43eceead9d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java +++ b/src/main/java/com/github/tomakehurst/wiremock/gui/GuiServeEventListener.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.gui; import com.github.tomakehurst.wiremock.extension.Parameters; @@ -6,8 +21,6 @@ import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import static com.github.tomakehurst.wiremock.gui.GuiConstants.EXTENSION_NAME; - /** * @author Christopher Holomek * @since 17.09.2023 diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java index 694b13f2bd..deb0a49c78 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/StubRequestHandler.java @@ -35,12 +35,12 @@ public class StubRequestHandler extends AbstractRequestHandler { - private final StubServer stubServer; - private final Admin admin; - private final Map postServeActions; - private final Map serveEventListeners; + private final StubServer stubServer; + private final Admin admin; + private final Map postServeActions; + private final Map serveEventListeners; private final RequestJournal requestJournal; - private final boolean loggingDisabled; + private final boolean loggingDisabled; private final NotMatchedRenderer notMatchedRenderer; @@ -74,10 +74,10 @@ public ServeEvent handleRequest(ServeEvent initialServeEvent) { return serveEvent; } - @Override - protected boolean logRequests() { - return !loggingDisabled; - } + @Override + protected boolean logRequests() { + return !loggingDisabled; + } @Override protected void beforeResponseSent(ServeEvent serveEvent, Response response) { diff --git a/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java b/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java index d12065f6a4..e1aa231b12 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java +++ b/src/main/java/com/github/tomakehurst/wiremock/http/ssl/CertificateAuthority.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,8 +76,11 @@ public static CertificateAuthority generateCertificateAuthority() } private static X509CertImpl selfSign(X509CertInfo info, PrivateKey privateKey, String sigAlg) - throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { + throws CertificateException, + NoSuchAlgorithmException, + InvalidKeyException, + NoSuchProviderException, + SignatureException { X509CertImpl certificate = new X509CertImpl(info); certificate.sign(privateKey, sigAlg); return certificate; @@ -151,8 +154,12 @@ CertChainAndKey generateCertificate(String keyType, SNIHostName hostName) } private X509CertImpl sign(X509CertInfo info) - throws CertificateException, IOException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { + throws CertificateException, + IOException, + NoSuchAlgorithmException, + InvalidKeyException, + NoSuchProviderException, + SignatureException { X509Certificate issuerCertificate = certificateChain[0]; info.set(X509CertInfo.ISSUER, issuerCertificate.getSubjectDN()); diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java index d6ac513291..0bcee34464 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/DefaultMultipartRequestConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java index 0599b216df..3a82842a72 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyFaultInjectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2022 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java index 08e86d790c..cbc8755f53 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServer.java @@ -61,10 +61,9 @@ import org.eclipse.jetty.servlets.CrossOriginFilter; import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; - public abstract class JettyHttpServer implements HttpServer { private static final String FILES_URL_MATCH = String.format("/%s/*", WireMockApp.FILES_ROOT); - private static final String[] GZIPPABLE_METHODS = new String[]{"POST", "PUT", "PATCH", "DELETE"}; + private static final String[] GZIPPABLE_METHODS = new String[] {"POST", "PUT", "PATCH", "DELETE"}; private static final MutableBoolean STRICT_HTTP_HEADERS_APPLIED = new MutableBoolean(false); protected final Server jettyServer; @@ -75,8 +74,8 @@ public abstract class JettyHttpServer implements HttpServer { public JettyHttpServer( final Options options, - final AdminRequestHandler adminRequestHandler, - final StubRequestHandler stubRequestHandler) { + final AdminRequestHandler adminRequestHandler, + final StubRequestHandler stubRequestHandler) { if (!options.getDisableStrictHttpHeaders() && STRICT_HTTP_HEADERS_APPLIED.isFalse()) { System.setProperty("org.eclipse.jetty.http.HttpGenerator.STRICT", String.valueOf(true)); STRICT_HTTP_HEADERS_APPLIED.setTrue(); @@ -120,15 +119,16 @@ public JettyHttpServer( this.finalizeSetup(options); } - protected void applyAdditionalServerConfiguration(Server jettyServer, Options options) { - } + protected void applyAdditionalServerConfiguration(Server jettyServer, Options options) {} protected HandlerCollection createHandler( - final Options options, final AdminRequestHandler adminRequestHandler, - final StubRequestHandler stubRequestHandler) { + final Options options, + final AdminRequestHandler adminRequestHandler, + final StubRequestHandler stubRequestHandler) { final Notifier notifier = options.notifier(); - final ServletContextHandler adminContext = this.addAdminContext(adminRequestHandler, notifier); - final ServletContextHandler mockServiceContext = this.addMockServiceContext( + final ServletContextHandler adminContext = this.addAdminContext(adminRequestHandler, notifier); + final ServletContextHandler mockServiceContext = + this.addMockServiceContext( stubRequestHandler, options.filesRoot(), options.getAsynchronousResponseSettings(), @@ -175,9 +175,9 @@ private void addGZipHandler( } } - protected void finalizeSetup(final Options options) { - if (options.jettySettings().getStopTimeout().isEmpty()) { - this.jettyServer.setStopTimeout(1000); + protected void finalizeSetup(final Options options) { + if (options.jettySettings().getStopTimeout().isEmpty()) { + this.jettyServer.setStopTimeout(1000); } } @@ -200,20 +200,20 @@ public void start() { try { this.jettyServer.start(); } catch (final Exception e) { - throw new RuntimeException(e); - } - final long timeout = System.currentTimeMillis() + 30000; - while (!this.jettyServer.isStarted()) { + throw new RuntimeException(e); + } + final long timeout = System.currentTimeMillis() + 30000; + while (!this.jettyServer.isStarted()) { try { Thread.sleep(100); } catch (final InterruptedException e) { - // no-op - } - if (System.currentTimeMillis() > timeout) { - throw new RuntimeException("Server took too long to start up."); - } - } + // no-op + } + if (System.currentTimeMillis() > timeout) { + throw new RuntimeException("Server took too long to start up."); + } } + } @Override public void stop() { @@ -274,7 +274,8 @@ private ServletContextHandler addMockServiceContext( boolean stubCorsEnabled, boolean browserProxyingEnabled, Notifier notifier) { - final ServletContextHandler mockServiceContext = new ServletContextHandler(this.jettyServer, "/"); + final ServletContextHandler mockServiceContext = + new ServletContextHandler(this.jettyServer, "/"); mockServiceContext.setInitParameter("org.eclipse.jetty.servlet.Default.maxCacheSize", "0"); mockServiceContext.setInitParameter( @@ -290,7 +291,8 @@ private ServletContextHandler addMockServiceContext( mockServiceContext.setAttribute( Options.ChunkedEncodingPolicy.class.getName(), chunkedEncodingPolicy); mockServiceContext.setAttribute("browserProxyingEnabled", browserProxyingEnabled); - final ServletHolder servletHolder = mockServiceContext.addServlet(WireMockHandlerDispatchingServlet.class, "/"); + final ServletHolder servletHolder = + mockServiceContext.addServlet(WireMockHandlerDispatchingServlet.class, "/"); servletHolder.setInitOrder(1); servletHolder.setInitParameter( RequestHandler.HANDLER_CLASS_KEY, StubRequestHandler.class.getName()); @@ -310,20 +312,25 @@ private ServletContextHandler addMockServiceContext( MultipartRequestConfigurer.KEY, buildMultipartRequestConfigurer()); final MimeTypes mimeTypes = new MimeTypes(); - mimeTypes.addMimeMapping("json", "application/json"); - mimeTypes.addMimeMapping("html", "text/html"); - mimeTypes.addMimeMapping("xml", "application/xml"); - mimeTypes.addMimeMapping("txt", "text/plain"); - mockServiceContext.setMimeTypes(mimeTypes); - mockServiceContext.setWelcomeFiles(new String[]{"index.json", "index.html", "index.xml", "index.txt"}); + mimeTypes.addMimeMapping("json", "application/json"); + mimeTypes.addMimeMapping("html", "text/html"); + mimeTypes.addMimeMapping("xml", "application/xml"); + mimeTypes.addMimeMapping("txt", "text/plain"); + mockServiceContext.setMimeTypes(mimeTypes); + mockServiceContext.setWelcomeFiles( + new String[] {"index.json", "index.html", "index.xml", "index.txt"}); NotFoundHandler errorHandler = new NotFoundHandler(mockServiceContext); mockServiceContext.setErrorHandler(errorHandler); mockServiceContext.addFilter( - ContentTypeSettingFilter.class, JettyHttpServer.FILES_URL_MATCH, EnumSet.of(DispatcherType.FORWARD)); + ContentTypeSettingFilter.class, + JettyHttpServer.FILES_URL_MATCH, + EnumSet.of(DispatcherType.FORWARD)); mockServiceContext.addFilter( - TrailingSlashFilter.class, JettyHttpServer.FILES_URL_MATCH, EnumSet.allOf(DispatcherType.class)); + TrailingSlashFilter.class, + JettyHttpServer.FILES_URL_MATCH, + EnumSet.allOf(DispatcherType.class)); if (stubCorsEnabled) { addCorsFilter(mockServiceContext); @@ -333,17 +340,18 @@ private ServletContextHandler addMockServiceContext( } private ServletContextHandler addAdminContext( - final AdminRequestHandler adminRequestHandler, - final Notifier notifier) { - final ServletContextHandler adminContext = new ServletContextHandler(this.jettyServer, ADMIN_CONTEXT_ROOT); + final AdminRequestHandler adminRequestHandler, final Notifier notifier) { + final ServletContextHandler adminContext = + new ServletContextHandler(this.jettyServer, ADMIN_CONTEXT_ROOT); adminContext.setInitParameter("org.eclipse.jetty.servlet.Default.maxCacheSize", "0"); final String javaVendor = System.getProperty("java.vendor"); - if (javaVendor != null && javaVendor.toLowerCase().contains("android")) { - //Special case for Android, fixes IllegalArgumentException("resource assets not found."): - // The Android ClassLoader apparently does not resolve directories. - // Furthermore, lib assets will be merged into a single asset directory when a jar file is// assimilated into an apk. + if (javaVendor != null && javaVendor.toLowerCase().contains("android")) { + // Special case for Android, fixes IllegalArgumentException("resource assets not found."): + // The Android ClassLoader apparently does not resolve directories. + // Furthermore, lib assets will be merged into a single asset directory when a jar file is// + // assimilated into an apk. // As resources can be addressed like "assets/swagger-ui/index.html", a static path element // will suffice. adminContext.setInitParameter("org.eclipse.jetty.servlet.Default.resourceBase", "assets"); @@ -364,9 +372,11 @@ private ServletContextHandler addAdminContext( ServletHolder webapp = adminContext.addServlet(DefaultServlet.class, "/webapp/*"); webapp.setAsyncSupported(false); - JakartaWebSocketServletContainerInitializer.configure(adminContext,(servletContext, serverContainer) -> { - serverContainer.addEndpoint(WebSocketEndpoint.class); - }); + JakartaWebSocketServletContainerInitializer.configure( + adminContext, + (servletContext, serverContainer) -> { + serverContainer.addEndpoint(WebSocketEndpoint.class); + }); final RewriteHandler rewrite = new RewriteHandler(); rewrite.setRewriteRequestURI(true); @@ -379,8 +389,10 @@ private ServletContextHandler addAdminContext( adminContext.insertHandler(rewrite); - ServletHolder servletHolder = adminContext.addServlet(WireMockHandlerDispatchingServlet.class, "/"); - servletHolder.setInitParameter(RequestHandler.HANDLER_CLASS_KEY, AdminRequestHandler.class.getName()); + ServletHolder servletHolder = + adminContext.addServlet(WireMockHandlerDispatchingServlet.class, "/"); + servletHolder.setInitParameter( + RequestHandler.HANDLER_CLASS_KEY, AdminRequestHandler.class.getName()); adminContext.setAttribute(AdminRequestHandler.class.getName(), adminRequestHandler); adminContext.setAttribute(Notifier.KEY, notifier); @@ -420,28 +432,29 @@ protected MultipartRequestConfigurer buildMultipartRequestConfigurer() { private static class NetworkTrafficListenerAdapter implements NetworkTrafficListener { private final WiremockNetworkTrafficListener wiremockNetworkTrafficListener; - NetworkTrafficListenerAdapter(final WiremockNetworkTrafficListener wiremockNetworkTrafficListener) { + NetworkTrafficListenerAdapter( + final WiremockNetworkTrafficListener wiremockNetworkTrafficListener) { this.wiremockNetworkTrafficListener = wiremockNetworkTrafficListener; } @Override public void opened(final Socket socket) { - this.wiremockNetworkTrafficListener.opened(socket); + this.wiremockNetworkTrafficListener.opened(socket); } @Override public void incoming(final Socket socket, final ByteBuffer bytes) { - this.wiremockNetworkTrafficListener.incoming(socket, bytes); + this.wiremockNetworkTrafficListener.incoming(socket, bytes); } @Override public void outgoing(final Socket socket, final ByteBuffer bytes) { - this.wiremockNetworkTrafficListener.outgoing(socket, bytes); + this.wiremockNetworkTrafficListener.outgoing(socket, bytes); } @Override public void closed(final Socket socket) { - this.wiremockNetworkTrafficListener.closed(socket); + this.wiremockNetworkTrafficListener.closed(socket); } } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java index 0099cd95e0..6f7b7ff63d 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/JettyHttpServerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java index dc339ec4d1..b65df95b58 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/NotFoundHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java index 763e7cae6e..7c6ea552e2 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/QueuedThreadPoolFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java index b8123768f4..4b4a4e62a1 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/Message.java @@ -1,23 +1,38 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.jetty.websockets; /** * @author Christopher Holomek */ public enum Message { - MAPPINGS("mappings"), - UNMATCHED("unmatched"), - MATCHED("matched"), - RECORDING("recording"), - SCENARIO("scenario"), - ; + MAPPINGS("mappings"), + UNMATCHED("unmatched"), + MATCHED("matched"), + RECORDING("recording"), + SCENARIO("scenario"), + ; - private final String message; + private final String message; - Message(final String message) { - this.message = message; - } + Message(final String message) { + this.message = message; + } - public String getMessage() { - return this.message; - } + public String getMessage() { + return this.message; + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java index 9f3ea8f371..2bc324b18c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty/websockets/WebSocketEndpoint.java @@ -1,50 +1,65 @@ +/* + * Copyright (C) 2023 Thomas Akehurst + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.github.tomakehurst.wiremock.jetty.websockets; +import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; + import jakarta.websocket.*; import jakarta.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier; - /** * @author Christopher Holomek */ @ServerEndpoint("/events") public class WebSocketEndpoint { - private static final Set sessions = new CopyOnWriteArraySet<>(); + private static final Set sessions = new CopyOnWriteArraySet<>(); - @OnOpen - public void onWebSocketConnect(final Session session) { - WebSocketEndpoint.sessions.add(session); - } + @OnOpen + public void onWebSocketConnect(final Session session) { + WebSocketEndpoint.sessions.add(session); + } - @OnMessage - public void onWebSocketText(final String message) { - notifier().info("Received TEXT message: " + message); - } + @OnMessage + public void onWebSocketText(final String message) { + notifier().info("Received TEXT message: " + message); + } - @OnClose - public void onWebSocketClose(final CloseReason reason, final Session session) { - WebSocketEndpoint.sessions.remove(session); - } + @OnClose + public void onWebSocketClose(final CloseReason reason, final Session session) { + WebSocketEndpoint.sessions.remove(session); + } - @OnError - public void onWebSocketError(final Session session, final Throwable cause) { - // WebSocketEndpoint.sessions.remove(session); - } + @OnError + public void onWebSocketError(final Session session, final Throwable cause) { + // WebSocketEndpoint.sessions.remove(session); + } - public static void broadcast(final Message message) { - for (final Session session : WebSocketEndpoint.sessions) { - synchronized (session) { // we need to synchronize the messages send to client. - try { - session.getBasicRemote().sendText(message.getMessage()); - } catch (final IOException e) { - notifier().error("Could not broadcast websocket message", e); - } - } + public static void broadcast(final Message message) { + for (final Session session : WebSocketEndpoint.sessions) { + synchronized (session) { // we need to synchronize the messages send to client. + try { + session.getBasicRemote().sendText(message.getMessage()); + } catch (final IOException e) { + notifier().error("Could not broadcast websocket message", e); } + } } + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java index cf33996794..bc09c6a2e9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/CertificateGeneratingSslContextFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java index ef2bedd791..04ba487331 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/SslContexts.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,10 @@ private static X509KeyStore toX509KeyStore(KeyStoreSettings browserProxyCaKeySto } private static X509KeyStore buildKeyStore(KeyStoreSettings browserProxyCaKeyStore) - throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, + throws KeyStoreException, + IOException, + NoSuchAlgorithmException, + CertificateException, CertificateGenerationUnsupportedException { final CertificateAuthority certificateAuthority = CertificateAuthority.generateCertificateAuthority(); diff --git a/src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java b/src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java index b596da2fe8..5757e2fed0 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/jetty11/WritableFileOrClasspathKeyStoreSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Thomas Akehurst + * Copyright (C) 2020-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java b/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java index b869732c51..43b796111a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java +++ b/src/main/java/com/github/tomakehurst/wiremock/recording/RecorderState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java index 2b325ca537..dc091d7cdc 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/ContentTypeSettingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2022 Thomas Akehurst + * Copyright (C) 2011-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java index 85161a8ae1..902cd069e9 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/FaultInjectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2022 Thomas Akehurst + * Copyright (C) 2013-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java index c167cad5fe..aea51f7247 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/MultipartRequestConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java index cc5d1cccf4..6ec9d1cccf 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjector.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java index 6663f4546f..a16f279d6e 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java +++ b/src/main/java/com/github/tomakehurst/wiremock/servlet/NoFaultInjectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java index 158af249c6..2113d732b4 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/JsonFileMappingsSource.java @@ -23,169 +23,176 @@ import com.github.tomakehurst.wiremock.stubbing.StubMapping; import com.github.tomakehurst.wiremock.stubbing.StubMappingCollection; import com.github.tomakehurst.wiremock.stubbing.StubMappings; - import java.util.*; import java.util.stream.Collectors; public class JsonFileMappingsSource implements MappingsSource { - private static final String WIREMOCK_GUI_KEY = "wiremock-gui"; - private static final String DIR_KEY = "folder"; + private static final String WIREMOCK_GUI_KEY = "wiremock-gui"; + private static final String DIR_KEY = "folder"; - private final FileSource mappingsFileSource; - private final Map fileNameMap; + private final FileSource mappingsFileSource; + private final Map fileNameMap; private final FilenameMaker filenameMaker; - public JsonFileMappingsSource(FileSource mappingsFileSource, FilenameMaker filenameMaker) { - this.mappingsFileSource = mappingsFileSource; + public JsonFileMappingsSource(FileSource mappingsFileSource, FilenameMaker filenameMaker) { + this.mappingsFileSource = mappingsFileSource; this.filenameMaker = Objects.requireNonNullElseGet(filenameMaker, FilenameMaker::new); - fileNameMap = new HashMap<>(); + fileNameMap = new HashMap<>(); + } + + @Override + public void save(List stubMappings) { + for (StubMapping mapping : stubMappings) { + if (mapping != null && mapping.isDirty()) { + save(mapping); + } } - - @Override - public void save(List stubMappings) { - for (StubMapping mapping : stubMappings) { - if (mapping != null && mapping.isDirty()) { - save(mapping); - } - } + } + + @Override + public void save(StubMapping stubMapping) { + StubMappingFileMetadata fileMetadata = fileNameMap.get(stubMapping.getId()); + if (fileMetadata == null) { + // we use gui folder definition to change path as sub directory from root. + // Only when not saved yet. + // TODO: This allows async between folder definition and actual file. Not sure if good or bad + // yet. + final String folderDefinition = getFolderDefinition(stubMapping); + if (folderDefinition != null) { + fileMetadata = + new StubMappingFileMetadata( + folderDefinition.replaceFirst("/", "") + + "/" + + filenameMaker.filenameFor(stubMapping), + false); + } else { + fileMetadata = new StubMappingFileMetadata(filenameMaker.filenameFor(stubMapping), false); + } } - @Override - public void save(StubMapping stubMapping) { - StubMappingFileMetadata fileMetadata = fileNameMap.get(stubMapping.getId()); - if (fileMetadata == null) { - // we use gui folder definition to change path as sub directory from root. - // Only when not saved yet. - // TODO: This allows async between folder definition and actual file. Not sure if good or bad yet. - final String folderDefinition = getFolderDefinition(stubMapping); - if (folderDefinition != null) { - fileMetadata = new StubMappingFileMetadata(folderDefinition.replaceFirst("/", "") + "/" + filenameMaker.filenameFor(stubMapping), false); - } else { - fileMetadata = new StubMappingFileMetadata(filenameMaker.filenameFor(stubMapping), false); - } - } - - if (fileMetadata.multi) { - throw new NotWritableException( - "Stubs loaded from multi-mapping files are read-only, and therefore cannot be saved"); - } + if (fileMetadata.multi) { + throw new NotWritableException( + "Stubs loaded from multi-mapping files are read-only, and therefore cannot be saved"); + } + mappingsFileSource.writeTextFile(fileMetadata.path, writePrivate(stubMapping)); - mappingsFileSource.writeTextFile(fileMetadata.path, writePrivate(stubMapping)); + fileNameMap.put(stubMapping.getId(), fileMetadata); + stubMapping.setDirty(false); + } - fileNameMap.put(stubMapping.getId(), fileMetadata); - stubMapping.setDirty(false); + @Override + public void remove(StubMapping stubMapping) { + StubMappingFileMetadata fileMetadata = fileNameMap.get(stubMapping.getId()); + if (fileMetadata.multi) { + throw new NotWritableException( + "Stubs loaded from multi-mapping files are read-only, and therefore cannot be removed"); } - @Override - public void remove(StubMapping stubMapping) { - StubMappingFileMetadata fileMetadata = fileNameMap.get(stubMapping.getId()); - if (fileMetadata.multi) { - throw new NotWritableException( - "Stubs loaded from multi-mapping files are read-only, and therefore cannot be removed"); - } + mappingsFileSource.deleteFile(fileMetadata.path); + fileNameMap.remove(stubMapping.getId()); + } - mappingsFileSource.deleteFile(fileMetadata.path); - fileNameMap.remove(stubMapping.getId()); + @Override + public void removeAll() { + if (anyFilesAreMultiMapping()) { + throw new NotWritableException( + "Some stubs were loaded from multi-mapping files which are read-only, so remove all cannot be performed"); } - @Override - public void removeAll() { - if (anyFilesAreMultiMapping()) { - throw new NotWritableException( - "Some stubs were loaded from multi-mapping files which are read-only, so remove all cannot be performed"); - } - - for (StubMappingFileMetadata fileMetadata : fileNameMap.values()) { - mappingsFileSource.deleteFile(fileMetadata.path); - } - fileNameMap.clear(); + for (StubMappingFileMetadata fileMetadata : fileNameMap.values()) { + mappingsFileSource.deleteFile(fileMetadata.path); } + fileNameMap.clear(); + } - private boolean anyFilesAreMultiMapping() { - return - fileNameMap.values().stream().anyMatch(input -> input.multi); - } - + private boolean anyFilesAreMultiMapping() { + return fileNameMap.values().stream().anyMatch(input -> input.multi); + } - @Override - public void loadMappingsInto(StubMappings stubMappings) { - if (!mappingsFileSource.exists()) { - return; - } + @Override + public void loadMappingsInto(StubMappings stubMappings) { + if (!mappingsFileSource.exists()) { + return; + } - List mappingFiles = - mappingsFileSource.listFilesRecursively().stream() + List mappingFiles = + mappingsFileSource.listFilesRecursively().stream() .filter(byFileExtension("json")) .collect(Collectors.toList()); - for (TextFile mappingFile : mappingFiles) { - try { - StubMappingCollection stubCollection = - Json.read(mappingFile.readContentsAsString(), StubMappingCollection.class); - for (StubMapping mapping : stubCollection.getMappingOrMappings()) { - mapping.setDirty(false); - createGuiFolderStructure(mapping, mappingFile); - stubMappings.addMapping(mapping); - StubMappingFileMetadata fileMetadata = - new StubMappingFileMetadata(mappingFile.getPath(), stubCollection.isMulti()); - fileNameMap.put(mapping.getId(), fileMetadata); - } - } catch (JsonException e) { - throw new MappingFileException(mappingFile.getPath(), e.getErrors().first().getDetail()); - } + for (TextFile mappingFile : mappingFiles) { + try { + StubMappingCollection stubCollection = + Json.read(mappingFile.readContentsAsString(), StubMappingCollection.class); + for (StubMapping mapping : stubCollection.getMappingOrMappings()) { + mapping.setDirty(false); + createGuiFolderStructure(mapping, mappingFile); + stubMappings.addMapping(mapping); + StubMappingFileMetadata fileMetadata = + new StubMappingFileMetadata(mappingFile.getPath(), stubCollection.isMulti()); + fileNameMap.put(mapping.getId(), fileMetadata); } + } catch (JsonException e) { + throw new MappingFileException(mappingFile.getPath(), e.getErrors().first().getDetail()); + } } + } - private boolean hasFolderDefinition(final StubMapping mapping) { - Metadata metadata = mapping.getMetadata(); - if (metadata == null) { - return false; - } - return metadata.getMap(WIREMOCK_GUI_KEY) != null && metadata.getMap(WIREMOCK_GUI_KEY).get(DIR_KEY) != null && - metadata.getMap(WIREMOCK_GUI_KEY).get(DIR_KEY) instanceof String; + private boolean hasFolderDefinition(final StubMapping mapping) { + Metadata metadata = mapping.getMetadata(); + if (metadata == null) { + return false; } - - private String getFolderDefinition(final StubMapping mapping) { - if (hasFolderDefinition(mapping)) { - return (String) mapping.getMetadata().getMap(WIREMOCK_GUI_KEY).get(DIR_KEY); - } - return null; + return metadata.getMap(WIREMOCK_GUI_KEY) != null + && metadata.getMap(WIREMOCK_GUI_KEY).get(DIR_KEY) != null + && metadata.getMap(WIREMOCK_GUI_KEY).get(DIR_KEY) instanceof String; + } + + private String getFolderDefinition(final StubMapping mapping) { + if (hasFolderDefinition(mapping)) { + return (String) mapping.getMetadata().getMap(WIREMOCK_GUI_KEY).get(DIR_KEY); + } + return null; + } + + private void createGuiFolderStructure(StubMapping mapping, TextFile mappingFile) { + Metadata metadata = mapping.getMetadata(); + if (metadata == null) { + metadata = new Metadata(); + } else if (hasFolderDefinition(mapping)) { + // skip files which contain a folder definition already. + // TODO: This allows async between folder definition and actual file. Not sure if good or bad + // yet. + return; } - private void createGuiFolderStructure(StubMapping mapping, TextFile mappingFile) { - Metadata metadata = mapping.getMetadata(); - if (metadata == null) { - metadata = new Metadata(); - } else if (hasFolderDefinition(mapping)) { - // skip files which contain a folder definition already. - // TODO: This allows async between folder definition and actual file. Not sure if good or bad yet. - return; - } - - String path = mappingFile.getUri().getSchemeSpecificPart() - .replace(this.mappingsFileSource.getUri().getSchemeSpecificPart(), "") - .replace(".json", ""); - final int index = path.lastIndexOf('/'); - if (index != -1) { - path = "/" + path.substring(0, path.lastIndexOf('/')); - } else { - return; - } - - LinkedHashMap guiData = new LinkedHashMap<>(); - metadata.put(WIREMOCK_GUI_KEY, guiData); - guiData.put(DIR_KEY, path); - mapping.setMetadata(metadata); + String path = + mappingFile + .getUri() + .getSchemeSpecificPart() + .replace(this.mappingsFileSource.getUri().getSchemeSpecificPart(), "") + .replace(".json", ""); + final int index = path.lastIndexOf('/'); + if (index != -1) { + path = "/" + path.substring(0, path.lastIndexOf('/')); + } else { + return; } - private static class StubMappingFileMetadata { - final String path; - final boolean multi; + LinkedHashMap guiData = new LinkedHashMap<>(); + metadata.put(WIREMOCK_GUI_KEY, guiData); + guiData.put(DIR_KEY, path); + mapping.setMetadata(metadata); + } - public StubMappingFileMetadata(String path, boolean multi) { - this.path = path; - this.multi = multi; - } + private static class StubMappingFileMetadata { + final String path; + final boolean multi; + + public StubMappingFileMetadata(String path, boolean multi) { + this.path = path; + this.multi = multi; } + } } diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java index 0c66390a29..590a57ca53 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java +++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/WireMockServerRunner.java @@ -35,18 +35,18 @@ public class WireMockServerRunner { private static final String BANNER = - "\n" - + "\u001B[34m██ ██ ██ ██████ ███████ \u001B[33m███ ███ ██████ ██████ ██ ██ \n" + "\n" + + "\u001B[34m██ ██ ██ ██████ ███████ \u001B[33m███ ███ ██████ ██████ ██ ██ \n" + "\u001B[34m██ ██ ██ ██ ██ ██ \u001B[33m████ ████ ██ ██ ██ ██ ██ \n" + "\u001B[34m██ █ ██ ██ ██████ █████ \u001B[33m██ ████ ██ ██ ██ ██ █████ \n" + "\u001B[34m██ ███ ██ ██ ██ ██ ██ \u001B[33m██ ██ ██ ██ ██ ██ ██ ██ \n" + "\u001B[34m ███ ███ ██ ██ ██ ███████ \u001B[33m██ ██ ██████ ██████ ██ ██ \n" - + "\n\u001B[0m" - + "----------------------------------------------------------------\n" - + "| Cloud: https://wiremock.io/cloud |\n" - + "| |\n" - + "| Slack: https://slack.wiremock.org |\n" - + "----------------------------------------------------------------"; + + "\n\u001B[0m" + + "----------------------------------------------------------------\n" + + "| Cloud: https://wiremock.io/cloud |\n" + + "| |\n" + + "| Slack: https://slack.wiremock.org |\n" + + "----------------------------------------------------------------"; private WireMockServer wireMockServer; @@ -130,16 +130,15 @@ public void println(Object o) { private void addProxyMapping(final String baseUrl) { wireMockServer.loadMappingsUsing( - stubMappings -> { + stubMappings -> { RequestPattern requestPattern = newRequestPattern(ANY, anyUrl()).build(); ResponseDefinition responseDef = responseDefinition().proxiedFrom(baseUrl).build(); StubMapping proxyBasedMapping = new StubMapping(requestPattern, responseDef); proxyBasedMapping.setPriority( - 10); // Make it low priority so that existing stubs will take precedence + 10); // Make it low priority so that existing stubs will take precedence stubMappings.addMapping(proxyBasedMapping); - } - ); + }); } public void stop() { diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java index a205109696..e91e640e3a 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/InMemoryScenarios.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java index 54bc9e2baa..56a506a02c 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/Scenarios.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java index 2f31956515..ad973c9da0 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/StubMappingJsonRecorder.java @@ -33,7 +33,9 @@ import java.util.UUID; import java.util.stream.Collectors; -/** @deprecated this is the legacy recorder and will be removed before 3.x is out of beta */ +/** + * @deprecated this is the legacy recorder and will be removed before 3.x is out of beta + */ @Deprecated public class StubMappingJsonRecorder implements RequestListener { diff --git a/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java index b8dbb1eb68..3c821ee0c0 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/GzipAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Thomas Akehurst + * Copyright (C) 2015-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java b/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java index baf270bf4e..a32d3ad51f 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java +++ b/src/test/java/com/github/tomakehurst/wiremock/Http2ClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Thomas Akehurst + * Copyright (C) 2019-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java index 9f18ea2b75..c3f5361a1e 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/MultipartBodyMatchingAcceptanceTest.java @@ -84,7 +84,9 @@ public void handlesAbsenceOfPartsInAMultipartRequest() throws Exception { assertThat(response.getCode(), is(404)); } - /** @see #1047 */ + /** + * @see #1047 + */ @Test public void acceptsAMultipartMixedRequestContainingATextAndAFilePart() throws Exception { stubFor( @@ -109,7 +111,9 @@ public void acceptsAMultipartMixedRequestContainingATextAndAFilePart() throws Ex assertThat(EntityUtils.toString(response.getEntity()), response.getCode(), is(200)); } - /** @see #1047 */ + /** + * @see #1047 + */ @Test public void acceptsAMultipartRelatedRequestContainingATextAndAFilePart() throws Exception { stubFor( diff --git a/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java index 1a6f5ac890..0e79c774f4 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/ResponseTransformerAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Thomas Akehurst + * Copyright (C) 2014-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java index 36e272d2bc..929db78d7c 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRangeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java index bfc16b1e45..e4de32d83b 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/common/NetworkAddressRulesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Thomas Akehurst + * Copyright (C) 2022-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java b/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java index 80f80b901d..ba2b370984 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/http/ProxyResponseRendererTest.java @@ -169,7 +169,10 @@ void doesNotAddEntityIfEmptyBodyReverseProxy() throws IOException { ServeEvent serveEvent = reverseProxyServeEvent("/proxied"); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() == null), ArgumentMatchers.any(HttpClientResponseHandler.class)); + Mockito.verify(clientSpy) + .execute( + argThat(request -> request.getEntity() == null), + ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -180,7 +183,10 @@ void doesNotAddEntityIfEmptyBodyForwardProxy() throws IOException { ServeEvent serveEvent = forwardProxyServeEvent("/proxied"); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() == null), ArgumentMatchers.any(HttpClientResponseHandler.class)); + Mockito.verify(clientSpy) + .execute( + argThat(request -> request.getEntity() == null), + ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -192,7 +198,10 @@ void addsEntityIfNotEmptyBodyReverseProxy() throws IOException { serveEvent("/proxied", false, "Text body".getBytes(StandardCharsets.UTF_8)); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); + Mockito.verify(clientSpy) + .execute( + argThat(request -> request.getEntity() != null), + ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -204,7 +213,10 @@ void addsEntityIfNotEmptyBodyForwardProxy() throws IOException { serveEvent("/proxied", true, "Text body".getBytes(StandardCharsets.UTF_8)); proxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); + Mockito.verify(clientSpy) + .execute( + argThat(request -> request.getEntity() != null), + ArgumentMatchers.any(HttpClientResponseHandler.class)); } @Test @@ -225,7 +237,10 @@ void addsEmptyEntityIfEmptyBodyForwardProxyPOST() throws IOException { new HttpHeaders(new HttpHeader("Content-Length", "0"))); trustAllProxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); + Mockito.verify(clientSpy) + .execute( + argThat(request -> request.getEntity() != null), + ArgumentMatchers.any(HttpClientResponseHandler.class)); List requests = origin.findAll(postRequestedFor(urlPathMatching("/proxied/empty-post"))); Assertions.assertThat(requests) @@ -252,7 +267,10 @@ void addsEmptyEntityIfEmptyBodyForwardProxyGET() throws IOException { new HttpHeaders(new HttpHeader("Content-Length", "0"))); trustAllProxyResponseRenderer.render(serveEvent); - Mockito.verify(clientSpy).execute(argThat(request -> request.getEntity() != null), ArgumentMatchers.any(HttpClientResponseHandler.class)); + Mockito.verify(clientSpy) + .execute( + argThat(request -> request.getEntity() != null), + ArgumentMatchers.any(HttpClientResponseHandler.class)); List requests = origin.findAll(getRequestedFor(urlPathMatching("/proxied/empty-get"))); Assertions.assertThat(requests) @@ -348,11 +366,11 @@ private File generateKeystore() throws Exception { CertificateSpecification certificateSpecification = new X509CertificateSpecification( - /* version = */ V3, - /* subject = */ "CN=localhost", - /* issuer = */ "CN=wiremock.org", - /* notBefore = */ new Date(), - /* notAfter = */ new Date(System.currentTimeMillis() + (365L * 24 * 60 * 60 * 1000))); + /* version= */ V3, + /* subject= */ "CN=localhost", + /* issuer= */ "CN=wiremock.org", + /* notBefore= */ new Date(), + /* notAfter= */ new Date(System.currentTimeMillis() + (365L * 24 * 60 * 60 * 1000))); KeyPair keyPair = generateKeyPair(); ks.addPrivateKey("wiremock", keyPair, certificateSpecification.certificateFor(keyPair)); @@ -378,8 +396,8 @@ private ProxyResponseRenderer buildProxyResponseRenderer( return new ProxyResponseRenderer( ProxySettings.NO_PROXY, KeyStoreSettings.NO_STORE, - /* preserveHostHeader = */ false, - /* hostHeaderValue = */ null, + /* preserveHostHeader= */ false, + /* hostHeaderValue= */ null, new InMemorySettingsStore(), trustAllProxyTargets, Collections.emptyList(), diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java index 4ac61f3bbc..c8efbd03dc 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToJsonTest.java @@ -617,12 +617,7 @@ void subEventIsReturnedOnJsonParsingError() { assertThat(match.getSubEvents().size(), is(1)); Errors.Error error = - match.getSubEvents().stream() - .findFirst() - .get() - .getDataAs(Errors.class) - .getErrors() - .stream() + match.getSubEvents().stream().findFirst().get().getDataAs(Errors.class).getErrors().stream() .findFirst() .get(); assertThat(error.getDetail(), startsWith("Unexpected end-of-input")); diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java index 92052b58da..acfb28fcc5 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/InMemoryStubMappingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Thomas Akehurst + * Copyright (C) 2016-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java index 624c4a826d..91bb91ceb0 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/stubbing/ScenariosTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2022 Thomas Akehurst + * Copyright (C) 2017-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java index c08c5aca31..3fd7f72b40 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/testsupport/MockHttpServletRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2022 Thomas Akehurst + * Copyright (C) 2012-2023 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 2093960f367245c4fba31ed9c7d5c7104d78f0f3 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 17:31:19 +0200 Subject: [PATCH 8/9] Fix issue that scenarios are not updated properly. Prepare release note for release. --- RELEASE-NOTES.md | 3 ++- .../tomakehurst/wiremock/stubbing/AbstractScenarios.java | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 7f22477032..b99a1e776f 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1 +1,2 @@ -Merge upstream wiremock version 2.35.1 +Update to wiremock 3.0.4 + diff --git a/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java b/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java index 666e894adb..be3dcd72b7 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java +++ b/src/main/java/com/github/tomakehurst/wiremock/stubbing/AbstractScenarios.java @@ -19,6 +19,8 @@ import static java.util.stream.Collectors.toList; import com.github.tomakehurst.wiremock.admin.NotFoundException; +import com.github.tomakehurst.wiremock.jetty.websockets.Message; +import com.github.tomakehurst.wiremock.jetty.websockets.WebSocketEndpoint; import com.github.tomakehurst.wiremock.store.ScenariosStore; import java.util.List; @@ -106,6 +108,7 @@ public void onStubServed(StubMapping mapping) { && (mapping.getRequiredScenarioState() == null || scenario.getState().equals(mapping.getRequiredScenarioState()))) { Scenario newScenario = scenario.setState(mapping.getNewScenarioState()); + WebSocketEndpoint.broadcast(Message.SCENARIO); store.put(scenarioName, newScenario); } } From d00c67798050f9ea9299873882ccb36ac3ed6b71 Mon Sep 17 00:00:00 2001 From: holomekc Date: Sun, 17 Sep 2023 18:13:12 +0200 Subject: [PATCH 9/9] "Fix" flaky test --- .../responsetemplating/ResponseTemplateTransformerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java index fadedf8c62..0b441c468a 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/extension/responsetemplating/ResponseTemplateTransformerTest.java @@ -1043,7 +1043,7 @@ public void canHandleALargeTemplateReasonablyFast() { assertThat(result.substring(0, 100), startsWith("Line 100000\nLine 100001\nLine 100002\n")); assertThat(result.length(), equalTo(1_200_000)); - assertThat(timeTaken, lessThan(Duration.ofSeconds(5))); + assertThat(timeTaken, lessThan(Duration.ofSeconds(8))); } private Integer transformToInt(String responseBodyTemplate) {