├── .asciidoctor └── docinfo.html ├── .asciidoctorconfig ├── .editorconfig ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .idea ├── GitLink.xml ├── cody_history.xml ├── copyright │ ├── oss_sf.xml │ └── profiles_settings.xml ├── detekt.xml ├── git_toolbox_blame.xml ├── git_toolbox_prj.xml ├── icon.png ├── kotlin-statistics.xml ├── kotlinc.xml ├── ktfmt.xml ├── misc.xml └── vcs.xml ├── BUILD.bazel ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.adoc ├── LICENSE ├── MODULE.bazel ├── MODULE.bazel.lock ├── README.adoc ├── SECURITY.md ├── WORKSPACE ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── Config.kt │ ├── preDef.kt │ ├── revoman.kt-conventions.gradle.kts │ ├── revoman.publishing-conventions.gradle.kts │ └── revoman.root-conventions.gradle.kts ├── detekt ├── baseline.xml └── config.yml ├── docs ├── images │ ├── cognitive-complexity.png │ ├── failure-hierarchy.png │ ├── hybrid-tool.png │ ├── manual-to-automation.png │ ├── mutable-env.png │ ├── node_modules.png │ ├── postman-run.png │ ├── pq-exe-logging.gif │ ├── pq-revoman-test-time.png │ ├── resfulapi-dev-pm.png │ ├── revoman-demo-thumbnail.png │ ├── rundown.png │ ├── step-procedure.png │ └── step-report.png ├── release-notes │ └── revoman-2.0.adoc └── revoman.exe.log ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── js ├── package-lock.json └── package.json ├── libs.versions.toml ├── qodana.yaml ├── renovate.json ├── settings.gradle.kts └── src ├── integrationTest ├── java │ └── com │ │ └── salesforce │ │ └── revoman │ │ ├── input │ │ └── json │ │ │ └── JsonPojoUtils2Test.java │ │ └── integration │ │ ├── core │ │ ├── CoreUtils.java │ │ ├── adapters │ │ │ ├── ConnectInputRepWithGraphAdapter.java │ │ │ └── IDAdapter.java │ │ ├── bt2bs │ │ │ ├── MilestoneBillingE2ETest.java │ │ │ └── ReVomanConfigForBT2BS.java │ │ └── pq │ │ │ ├── PQE2EWithSMTest.java │ │ │ └── connect │ │ │ ├── request │ │ │ ├── ConnectInputRepresentationWithGraph.java │ │ │ ├── ObjectGraphInputRepresentation.java │ │ │ ├── ObjectInputRepresentationMap.java │ │ │ ├── ObjectWithReferenceInputRepresentation.java │ │ │ ├── ObjectWithReferenceInputRepresentationList.java │ │ │ ├── PlaceQuoteInputRepresentation.java │ │ │ └── PricingPreferenceEnum.java │ │ │ └── response │ │ │ ├── ID.java │ │ │ ├── PlaceQuoteErrorResponseRepresentation.java │ │ │ └── PlaceQuoteOutputRepresentation.java │ │ ├── pokemon │ │ └── PokemonTest.java │ │ └── restfulapidev │ │ └── RestfulAPIDevTest.java ├── kotlin │ └── com │ │ └── salesforce │ │ └── revoman │ │ └── integration │ │ ├── apigee │ │ └── ApigeeKtTest.kt │ │ └── restfulapidev │ │ └── RestfulAPIDevKtTest.kt └── resources │ ├── json │ ├── pq-payload.json │ ├── pq-response.json │ └── pricing-pref.json │ ├── log4j2.xml │ └── pm-templates │ ├── apigee │ └── apigee.postman_collection.json │ ├── core │ ├── milestone │ │ ├── bmp-create-runtime.postman_collection.json │ │ ├── env.postman_environment.json │ │ ├── milestone-setup.postman_collection.json │ │ └── persona-creation-and-setup.postman_collection.json │ └── pq │ │ ├── [sm] pq.postman_collection.json │ │ ├── [sm] user-creation-with-ps-and-setup-pq.postman_collection.json │ │ ├── pq-env.postman_environment.json │ │ └── pre-salesRep.postman_collection.json │ ├── pokemon │ ├── pokemon.postman_collection.json │ └── pokemon.postman_environment.json │ └── restfulapidev │ ├── postman-cli-reports │ └── restful-api.dev-2025-04-14-13-16-43.json │ ├── restful-api.dev.postman_collection.json │ └── restful-api.dev.postman_environment.json ├── main ├── kotlin │ └── com │ │ └── salesforce │ │ └── revoman │ │ ├── ReVoman.kt │ │ ├── input │ │ ├── FileUtils.kt │ │ ├── PostExeHook.kt │ │ ├── config │ │ │ ├── HookConfig.kt │ │ │ ├── KickDef.kt │ │ │ ├── RequestConfig.kt │ │ │ ├── ResponseConfig.kt │ │ │ └── StepPick.kt │ │ └── json │ │ │ ├── FnTypes.kt │ │ │ ├── JsonPojoUtils.kt │ │ │ ├── JsonReaderUtils.kt │ │ │ ├── JsonWriterUtils.kt │ │ │ ├── adapters │ │ │ └── salesforce │ │ │ │ ├── CompositeGraphRequest.kt │ │ │ │ ├── CompositeGraphResponse.kt │ │ │ │ ├── CompositeResponse.kt │ │ │ │ └── Constants.kt │ │ │ └── factories │ │ │ └── DiMorphicAdapter.kt │ │ ├── internal │ │ ├── exe │ │ │ ├── ExeUtils.kt │ │ │ ├── HttpRequest.kt │ │ │ ├── PmJsEval.kt │ │ │ ├── PostStepHook.kt │ │ │ ├── PreStepHook.kt │ │ │ ├── UnmarshallRequest.kt │ │ │ └── UnmarshallResponse.kt │ │ ├── json │ │ │ ├── MoshiReVoman.kt │ │ │ ├── adapters │ │ │ │ ├── BigDecimalAdapter.kt │ │ │ │ ├── EpochAdapter.kt │ │ │ │ ├── TypeAdapter.kt │ │ │ │ └── UUIDAdapter.kt │ │ │ └── factories │ │ │ │ ├── AlwaysSerializeNullsFactory.kt │ │ │ │ ├── CaseInsensitiveEnumAdapter.kt │ │ │ │ └── IgnoreTypesFactory.kt │ │ └── postman │ │ │ ├── DynamicVariableGenerator.kt │ │ │ ├── PostmanSDK.kt │ │ │ ├── RegexReplacer.kt │ │ │ └── template │ │ │ ├── Auth.kt │ │ │ ├── Environment.kt │ │ │ └── Template.kt │ │ └── output │ │ ├── ExeType.kt │ │ ├── Rundown.kt │ │ ├── postman │ │ └── PostmanEnvironment.kt │ │ └── report │ │ ├── Step.kt │ │ ├── StepReport.kt │ │ ├── TxnInfo.kt │ │ └── failure │ │ ├── ExeFailure.kt │ │ ├── HookFailure.kt │ │ ├── HttpStatusUnsuccessful.kt │ │ ├── RequestFailure.kt │ │ └── ResponseFailure.kt └── resources │ └── log4j2.xml └── test ├── java └── com │ └── salesforce │ └── revoman │ ├── input │ └── json │ │ ├── JsonPojoUtilsTest.java │ │ ├── adapters │ │ └── SObjectGraphRequestMarshaller.java │ │ └── pojo │ │ └── SObjectGraphRequest.java │ └── output │ └── postman │ └── PostmanEnvironmentTest.java ├── kotlin └── com │ └── salesforce │ └── revoman │ ├── input │ ├── EvalJsTest.kt │ ├── FileUtilsTest.kt │ └── config │ │ └── KickTest.kt │ ├── internal │ ├── ExeUtilsTest.kt │ └── postman │ │ └── RegexReplacerTest.kt │ └── output │ └── report │ ├── PostmanEnvironmentKtTest.kt │ ├── StepReportTest.kt │ └── TxnInfoTest.kt └── resources ├── composite ├── graph │ ├── req │ │ └── graph-request.json │ └── resp │ │ ├── graph-response-error.json │ │ └── graph-response-success.json └── query │ └── resp │ ├── query-response-all-error.json │ ├── query-response-all-success.json │ └── query-response-partial-success.json ├── env-from-revoman.json ├── env-with-regex.json ├── json ├── nested-bean.json └── pq-graph-req-masked.json └── pm-templates ├── steps-with-folders.json └── steps-without-folders.postman_collection.json /.asciidoctor/docinfo.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /.asciidoctorconfig: -------------------------------------------------------------------------------- 1 | :experimental: 2 | :linkcss: 3 | :docinfodir: {asciidoctorconfigdir}/.asciidoctor 4 | :docinfo: shared 5 | :icons: font 6 | ifdef::env-github[] 7 | :tip-caption: :bulb: 8 | :note-caption: :information_source: 9 | :important-caption: :heavy_exclamation_mark: 10 | :caution-caption: :fire: 11 | :warning-caption: :warning: 12 | endif::[] 13 | :listing-caption: Snippet 14 | :sectnums: 15 | :source-linenums-option: 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: 'Build and Scan' 2 | on: push 3 | jobs: 4 | gradle: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: actions/setup-java@v4 9 | with: 10 | distribution: jetbrains 11 | java-version: 17 12 | 13 | - name: 'Setup Gradle' 14 | uses: gradle/gradle-build-action@v3 15 | 16 | - name: 'Gradle test' 17 | run: ./gradlew test integrationTest --tests com.salesforce.revoman.integration.pokemon.PokemonTest 18 | -------------------------------------------------------------------------------- /.idea/GitLink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/cody_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.idea/copyright/oss_sf.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/detekt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 13 | -------------------------------------------------------------------------------- /.idea/git_toolbox_blame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/.idea/icon.png -------------------------------------------------------------------------------- /.idea/kotlin-statistics.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /.idea/ktfmt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@rules_java//java:defs.bzl", "java_import") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | java_import( 6 | name = "com_salesforce_revoman_revoman", 7 | jars = glob( 8 | ["build/libs/revoman-root-*.jar"], 9 | allow_empty = False, 10 | ), 11 | srcjar = glob( 12 | ["build/libs/revoman-root-*-sources.jar"], 13 | allow_empty = False, 14 | )[0], 15 | ) 16 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | #GUSINFO:Rev Delphinus,Rev Delphinus Trust 2 | * 3 | # Comment line immediately above ownership line is reserved for related other information. Please be careful while editing. 4 | #ECCN:Open Source 5D002 5 | @overfullstack 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.adoc: -------------------------------------------------------------------------------- 1 | = Contributing 2 | Gopal S Akshintala 3 | :Revision: 1.0 4 | ifdef::env-github[] 5 | :tip-caption: :bulb: 6 | :note-caption: :information_source: 7 | :important-caption: :heavy_exclamation_mark: 8 | :caution-caption: :fire: 9 | :warning-caption: :warning: 10 | endif::[] 11 | :icons: font 12 | ifdef::env-github[] 13 | :tip-caption: :bulb: 14 | :note-caption: :information_source: 15 | :important-caption: :heavy_exclamation_mark: 16 | :caution-caption: :fire: 17 | :warning-caption: :warning: 18 | endif::[] 19 | :hide-uri-scheme: 20 | :sourcedir: src/main/java 21 | :imagesdir: images 22 | :toc: 23 | 24 | == Source-code Setup 25 | 26 | === Install Java 27 | 28 | It needs JDK 17 installed in your system. 29 | Recommendation is to do it via https://sdkman.io/install[SDKMAN]. 30 | After you install SDKMAN, 31 | run `sdk list java` -> Pick Identifier for your favorite java distribution -> Run `sdk install java ` 32 | to install Java. For example: 33 | 34 | [source,bash] 35 | ---- 36 | sdk install java 17.0.14-amzn 37 | ---- 38 | 39 | === Build with Gradle 40 | 41 | This is a simple Gradle project and has its own Gradle wrapper. Nothing to install, run just this command: 42 | 43 | [source,bash] 44 | ---- 45 | ./gradlew clean build 46 | ---- 47 | 48 | TIP: You *don't* need a local Gradle installation as the `gradlew` (Gradle wrapper) takes care of everything. But if you wish to install Gradle locally, the recommendation is to do it via https://sdkman.io/install[SDKMAN]. After you install SDKMAN, run `sdk install gradle` to install Gradle 49 | 50 | You can run/debug the existing unit tests or write your own to play with the tool. 51 | 52 | === Kotlin 53 | 54 | * The code-base is a mix of Java and Kotlin. 55 | If you're a Java developer and new to Kotlin, don't worry, Kotlin is a JVM language and can be used anywhere Java is used. 56 | In fact, it has got the reputation of *"Better Java!"*. 57 | * A typical Java developer can ramp up on Kotlin in less than a week. These resources can help catalyze your ramp-up: 58 | ** https://www.coursera.org/learn/kotlin-for-java-developers[*Kotlin for Java Developers | Coursera*], a free course 59 | ** https://www.kotlinprimer.com/[The Kotlin Primer], tailor-made to facilitate Kotlin adoption inside Java-centric organizations 60 | * If you use Intellij, Kotlin plugin comes bundled. 61 | Similar development aids should be present for other code editors too. 62 | 63 | == Code Formatting 64 | 65 | This repo uses https://github.com/diffplug/spotless[*Spotless*] for formatting files. Please run `./gradlew spotlessApply` before check-in to fix any formatting errors. 66 | 67 | TIP: If you're on Intellij, replace your kbd:[Cmd+Shift+L] habit with kbd:[Ctrl]-kbd:[Ctrl] 68 | and run `./gradlew spotlessApply` (Or the respective action if you're on VS Code/Eclipse). 69 | It may be slow for the first run, but subsequent runs should be faster. 70 | 71 | == Manual publishing 72 | 73 | === Versioning Strategy 74 | 75 | ==== 76 | .. 77 | ==== 78 | 79 | * A = Broke something on purpose (Breaking API change) 80 | * B = Profit (Feature / Improvement) 81 | * C = Broke something by accident (Bug) 82 | 83 | Follow the Versioning Strategy to increment version link:buildSrc/{sourcedir}/Config.kt[here] 84 | 85 | * For SNAPSHOT releases, add a `-SNAPSHOT` at the end of version number 86 | * Run this command to publish it to Nexus: 87 | 88 | [source,bash] 89 | ---- 90 | ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository -Dorg.gradle.parallel=false --no-configuration-cache 91 | ---- 92 | 93 | * You can monitor for the new version jar to reflect in link:https://repo1.maven.org/maven2/com/salesforce/revoman/revoman/[Maven Central]. It usually takes less than 30 minutes. 94 | 95 | == Code of Conduct 96 | Please follow our link:CODE_OF_CONDUCT.md[Code of Conduct] 97 | 98 | == License 99 | By contributing your code, 100 | you agree to license your contribution under the terms of our project link:LICENSE[] 101 | and to sign the https://cla.salesforce.com/sign-cla[Salesforce CLA] 102 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Bazel now uses Bzlmod by default to manage external dependencies. 3 | # Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel. 4 | # 5 | # For more details, please check https://github.com/bazelbuild/bazel/issues/18958 6 | ############################################################################### 7 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security 2 | 3 | Please report any security issue to [security@salesforce.com](mailto:security@salesforce.com) 4 | as soon as it is discovered. This library limits its runtime dependencies in 5 | order to reduce the total cost of ownership as much as can be, but all consumers 6 | should remain vigilant and have their security stakeholders review all third-party 7 | products (3PP) like this one and their dependencies. 8 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/WORKSPACE -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | plugins { 9 | id("revoman.root-conventions") 10 | id("revoman.publishing-conventions") 11 | id("revoman.kt-conventions") 12 | alias(libs.plugins.moshix) 13 | alias(libs.plugins.node.gradle) 14 | alias(libs.plugins.kover) 15 | alias(libs.plugins.nexus.publish) 16 | alias(libs.plugins.gradle.taskinfo) 17 | } 18 | 19 | val mockitoAgent = configurations.create("mockitoAgent") 20 | 21 | dependencies { 22 | api(platform(libs.http4k.bom)) 23 | api(libs.bundles.http4k) 24 | api(libs.moshix.adapters) 25 | api(libs.java.vavr) 26 | api(libs.kotlin.vavr) 27 | api(libs.arrow.core) 28 | api(libs.kotlinx.datetime) 29 | implementation(libs.bundles.kotlin.logging) 30 | implementation(libs.pprint) 31 | implementation(libs.graal.js) 32 | implementation(libs.kotlin.faker) 33 | implementation(libs.underscore) 34 | implementation(libs.okio.jvm) 35 | implementation(libs.spring.beans) 36 | kapt(libs.immutables.value) 37 | compileOnly(libs.immutables.builder) 38 | compileOnly(libs.immutables.value.annotations) 39 | compileOnly(libs.jetbrains.annotations) 40 | testImplementation(libs.truth) 41 | testImplementation(libs.json.assert) 42 | mockitoAgent(libs.mockito.core) { isTransitive = false } 43 | testImplementation(libs.mockk) 44 | } 45 | 46 | testing { 47 | suites { 48 | val test by getting(JvmTestSuite::class) { useJUnitJupiter(libs.versions.junit.get()) } 49 | 50 | register("integrationTest") { 51 | dependencies { 52 | implementation(project()) 53 | implementation(libs.truth) 54 | implementation(libs.mockito.core) 55 | implementation(libs.spring.beans) 56 | implementation(libs.json.assert) 57 | implementation(libs.assertj.vavr) 58 | } 59 | } 60 | } 61 | } 62 | 63 | node { nodeProjectDir = file("${project.projectDir}/js") } 64 | 65 | tasks { 66 | check { dependsOn(npmInstall) } 67 | test { dependsOn(npmInstall) } 68 | } 69 | 70 | kover { reports { total { html { onCheck = true } } } } 71 | 72 | moshi { enableSealed = true } 73 | 74 | nexusPublishing { this.repositories { sonatype { stagingProfileId = STAGING_PROFILE_ID } } } 75 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | plugins { `kotlin-dsl` } 9 | 10 | repositories { 11 | mavenCentral() 12 | gradlePluginPortal() 13 | maven("https://oss.sonatype.org/content/repositories/snapshots") 14 | } 15 | 16 | dependencies { 17 | implementation(libs.kotlin.gradle) 18 | implementation(libs.spotless.gradle) 19 | implementation(libs.detekt.gradle) 20 | implementation(libs.testLogger.gradle) 21 | } 22 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | dependencyResolutionManagement { 9 | versionCatalogs { create("libs") { from(files("../libs.versions.toml")) } } 10 | 11 | pluginManagement { 12 | repositories { 13 | mavenCentral() 14 | gradlePluginPortal() 15 | google() 16 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots") 17 | maven("https://oss.sonatype.org/content/repositories/snapshots") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Config.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | const val GROUP_ID = "com.salesforce.revoman" 9 | const val VERSION = "0.8.0" 10 | const val ARTIFACT_ID = "revoman" 11 | const val STAGING_PROFILE_ID = "1ea0a23e61ba7d" 12 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/preDef.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | import org.gradle.api.artifacts.ExternalModuleDependencyBundle 9 | import org.gradle.api.artifacts.VersionCatalog 10 | import org.gradle.api.artifacts.VersionConstraint 11 | import org.gradle.api.provider.Property 12 | import org.gradle.api.provider.Provider 13 | import org.gradle.plugin.use.PluginDependency 14 | 15 | val Provider.pluginId: String 16 | get() = get().pluginId 17 | 18 | infix fun Property.by(value: T) { 19 | set(value) 20 | } 21 | 22 | internal val VersionCatalog.jdk: VersionConstraint 23 | get() = getVersion("jdk") 24 | 25 | internal val VersionCatalog.kotestBundle: Provider 26 | get() = getBundle("kotest") 27 | 28 | private fun VersionCatalog.getLibrary(library: String) = findLibrary(library).get() 29 | 30 | private fun VersionCatalog.getBundle(bundle: String) = findBundle(bundle).get() 31 | 32 | private fun VersionCatalog.getPlugin(plugin: String) = findPlugin(plugin).get() 33 | 34 | private fun VersionCatalog.getVersion(plugin: String) = findVersion(plugin).get() 35 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/revoman.kt-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | plugins { 9 | kotlin("jvm") 10 | kotlin("kapt") 11 | } 12 | 13 | val libs: VersionCatalog = extensions.getByType().named("libs") 14 | 15 | dependencies { testImplementation(libs.kotestBundle) } 16 | 17 | kapt { 18 | useBuildCache = true 19 | } 20 | 21 | kotlin { 22 | jvmToolchain(libs.jdk.toString().toInt()) 23 | compilerOptions { 24 | freeCompilerArgs.addAll("-Xjvm-default=all", "-Xcontext-receivers", "-Xconsistent-data-class-copy-visibility", "-Xmulti-dollar-interpolation") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/revoman.publishing-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | 9 | plugins { 10 | `maven-publish` 11 | signing 12 | `java-library` 13 | } 14 | 15 | group = GROUP_ID 16 | 17 | version = VERSION 18 | 19 | description = "ReVoman - A template-driven API automation tool for JVM (Java/Kotlin)" 20 | 21 | repositories { mavenCentral() } 22 | 23 | java { 24 | withJavadocJar() 25 | withSourcesJar() 26 | } 27 | 28 | publishing { 29 | publications.create("revoman") { 30 | artifactId = ARTIFACT_ID 31 | from(components["java"]) 32 | pom { 33 | name.set("revoman") 34 | description.set(project.description) 35 | url.set("https://github.com/salesforce-misc/ReVoman") 36 | inceptionYear.set("2023") 37 | licenses { 38 | license { 39 | name.set("The Apache License, Version 2.0") 40 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 41 | } 42 | } 43 | developers { 44 | developer { 45 | id.set("overfullstack") 46 | name.set("Gopal S Akshintala") 47 | email.set("gopalakshintala@gmail.com") 48 | } 49 | } 50 | scm { 51 | connection.set("scm:git:https://github.com/salesforce-misc/ReVoman") 52 | developerConnection.set("scm:git:git@github.com/salesforce-misc/ReVoman.git") 53 | url.set("https://github.com/salesforce-misc/ReVoman") 54 | } 55 | } 56 | } 57 | } 58 | 59 | signing { sign(publishing.publications["revoman"]) } 60 | 61 | tasks { 62 | javadoc { 63 | // TODO 22/05/21 gopala.akshintala: Turn this on after writing all javadocs 64 | isFailOnError = false 65 | options.encoding("UTF-8") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/revoman.root-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | import com.adarshr.gradle.testlogger.theme.ThemeType.MOCHA 9 | import com.diffplug.spotless.LineEnding.PLATFORM_NATIVE 10 | import com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep.XML 11 | import io.gitlab.arturbosch.detekt.Detekt 12 | 13 | plugins { 14 | id("com.diffplug.spotless") 15 | id("io.gitlab.arturbosch.detekt") 16 | id("com.adarshr.test-logger") 17 | } 18 | 19 | version = VERSION 20 | 21 | group = GROUP_ID 22 | 23 | description = "ReVoman - A Template-driven API automation tool for JVM (Java/Kotlin)" 24 | 25 | repositories { 26 | mavenCentral() 27 | maven("https://oss.sonatype.org/content/repositories/snapshots") 28 | } 29 | 30 | spotless { 31 | lineEndings = PLATFORM_NATIVE 32 | kotlin { 33 | ktfmt().googleStyle() 34 | target("src/*/kotlin/**/*.kt", "src/*/java/**/*.kt") 35 | trimTrailingWhitespace() 36 | endWithNewline() 37 | targetExclude("build/**", ".gradle/**", "generated/**", "bin/**", "out/**", "tmp/**") 38 | } 39 | kotlinGradle { 40 | ktfmt().googleStyle() 41 | trimTrailingWhitespace() 42 | endWithNewline() 43 | targetExclude("build/**", ".gradle/**", "generated/**", "bin/**", "out/**", "tmp/**") 44 | } 45 | java { 46 | toggleOffOn() 47 | target("src/*/java/**/*.java") 48 | importOrder() 49 | removeUnusedImports() 50 | googleJavaFormat() 51 | trimTrailingWhitespace() 52 | leadingSpacesToTabs(2) 53 | endWithNewline() 54 | targetExclude("build/**", ".gradle/**", "generated/**", "bin/**", "out/**", "tmp/**") 55 | } 56 | format("documentation") { 57 | target("*.md", "*.adoc") 58 | trimTrailingWhitespace() 59 | leadingSpacesToTabs(2) 60 | endWithNewline() 61 | } 62 | } 63 | 64 | detekt { 65 | parallel = true 66 | buildUponDefaultConfig = true 67 | baseline = file("$rootDir/detekt/baseline.xml") 68 | config.setFrom(file("$rootDir/detekt/config.yml")) 69 | } 70 | 71 | testlogger.theme = MOCHA 72 | 73 | tasks.withType().configureEach { reports { xml.required.set(true) } } 74 | -------------------------------------------------------------------------------- /detekt/baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TooManyFunctions:ResponseConfig.kt$ResponseConfig$Companion 5 | TooManyFunctions:PostmanEnvironment.kt$PostmanEnvironment<ValueT : Any?> : MutableMap 6 | TooManyFunctions:ExeUtils.kt$com.salesforce.revoman.internal.exe.ExeUtils.kt 7 | LongMethod:ReVoman.kt$ReVoman$private fun executeStepsSerially( pmStepsFlattened: List<Pair<Step, Item>>, kick: Kick, moshiReVoman: MoshiReVoman, ): List<StepReport> 8 | SpreadOperator:Step.kt$Step$(*FOLDER_DELIMITER.toCharArray()) 9 | SpreadOperator:CaseInsensitiveEnumAdapter.kt$CaseInsensitiveEnumAdapter$(*nameStrings.toTypedArray()) 10 | TooManyFunctions:JsonWriterUtils.kt$com.salesforce.revoman.input.json.JsonWriterUtils.kt 11 | TooManyFunctions:JsonReaderUtils.kt$com.salesforce.revoman.input.json.JsonReaderUtils.kt 12 | TooManyFunctions:KickDef.kt$KickDef 13 | MagicNumber:Constants.kt$400 14 | MagicNumber:Constants.kt$499 15 | MagicNumber:Constants.kt$200 16 | MagicNumber:Constants.kt$299 17 | FunctionNaming:RestfulAPIDevKtTest.kt$RestfulAPIDevKtTest$@Test fun `execute restful-api dev pm collection`() 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /detekt/config.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 999 3 | comments: 4 | active: false 5 | style: 6 | MaxLineLength: 7 | active: false 8 | -------------------------------------------------------------------------------- /docs/images/cognitive-complexity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/cognitive-complexity.png -------------------------------------------------------------------------------- /docs/images/failure-hierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/failure-hierarchy.png -------------------------------------------------------------------------------- /docs/images/hybrid-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/hybrid-tool.png -------------------------------------------------------------------------------- /docs/images/manual-to-automation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/manual-to-automation.png -------------------------------------------------------------------------------- /docs/images/mutable-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/mutable-env.png -------------------------------------------------------------------------------- /docs/images/node_modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/node_modules.png -------------------------------------------------------------------------------- /docs/images/postman-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/postman-run.png -------------------------------------------------------------------------------- /docs/images/pq-exe-logging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/pq-exe-logging.gif -------------------------------------------------------------------------------- /docs/images/pq-revoman-test-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/pq-revoman-test-time.png -------------------------------------------------------------------------------- /docs/images/resfulapi-dev-pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/resfulapi-dev-pm.png -------------------------------------------------------------------------------- /docs/images/revoman-demo-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/revoman-demo-thumbnail.png -------------------------------------------------------------------------------- /docs/images/rundown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/rundown.png -------------------------------------------------------------------------------- /docs/images/step-procedure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/step-procedure.png -------------------------------------------------------------------------------- /docs/images/step-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/docs/images/step-report.png -------------------------------------------------------------------------------- /docs/release-notes/revoman-2.0.adoc: -------------------------------------------------------------------------------- 1 | = Key Features 2 | 3 | * Core compatibility 4 | ** Persona-based user creation and test-utils to recalculate PSG 5 | ** Async operation Support 6 | * Pre- / Post-Hooks can be configured for each step, which can act as callbacks, especially useful for Async operations 7 | * Enhance Input config 8 | ** Add parameters like runOnlyStep, skipSteps, haltOnAnyErrorExceptSteps etc. 9 | * Enhance variable support 10 | ** Custom Dynamic variables for use cases like Parametric testing 11 | ** Add Recursive variable support both for Template and Environment 12 | ** Add more dynamic variables like $epoch, $randomFutureDate etc. 13 | * Enhance JS support for Test scripts. 14 | * Enhance error handling 15 | * Step name is represented through its full folder path 16 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #################################################################################################### 2 | # Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | # Apache License Version 2.0 4 | # For full license text, see the LICENSE file in the repo root or 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | #################################################################################################### 7 | 8 | org.gradle.parallel=true 9 | org.gradle.caching=true 10 | org.gradle.configuration-cache=true 11 | org.gradle.configuration-cache.parallel=true 12 | org.gradle.configuration-cache.problems=warn 13 | org.gradle.jvmargs=-XX:+UseParallelGC 14 | 15 | moshix.generateProguardRules=false 16 | 17 | kapt.classloaders.cache.size=1 18 | kapt.include.compile.classpath=false 19 | kapt.incremental.apt=false 20 | kapt.use.k2=true 21 | 22 | kotest.framework.classpath.scanning.autoscan.disable=true 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salesforce-misc/ReVoman/8465cb048ddc3ef1404b8ef414b5a18528e5e8cc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /js/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "events": "^3.3.0", 9 | "lodash": "^4.17.21", 10 | "moment": "2.30.1", 11 | "stream": "^0.0.3", 12 | "timers": "^0.1.1", 13 | "xml2js": "^0.6.2" 14 | } 15 | }, 16 | "node_modules/component-emitter": { 17 | "version": "2.0.0", 18 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", 19 | "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", 20 | "license": "MIT", 21 | "engines": { 22 | "node": ">=18" 23 | }, 24 | "funding": { 25 | "url": "https://github.com/sponsors/sindresorhus" 26 | } 27 | }, 28 | "node_modules/events": { 29 | "version": "3.3.0", 30 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 31 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 32 | "license": "MIT", 33 | "engines": { 34 | "node": ">=0.8.x" 35 | } 36 | }, 37 | "node_modules/lodash": { 38 | "version": "4.17.21", 39 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 40 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 41 | "license": "MIT" 42 | }, 43 | "node_modules/moment": { 44 | "version": "2.30.1", 45 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", 46 | "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", 47 | "license": "MIT", 48 | "engines": { 49 | "node": "*" 50 | } 51 | }, 52 | "node_modules/sax": { 53 | "version": "1.4.1", 54 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", 55 | "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", 56 | "license": "ISC" 57 | }, 58 | "node_modules/stream": { 59 | "version": "0.0.3", 60 | "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", 61 | "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", 62 | "license": "MIT", 63 | "dependencies": { 64 | "component-emitter": "^2.0.0" 65 | } 66 | }, 67 | "node_modules/timers": { 68 | "version": "0.1.1", 69 | "resolved": "https://registry.npmjs.org/timers/-/timers-0.1.1.tgz", 70 | "integrity": "sha512-pkJC8uIP/gxDHxNQUBUbjHyl6oZfT+ofn7tbaHW+CFIUjI+Q2MBbHcx1JSBQfhDaTcO9bNg328q0i7Vk5PismQ==", 71 | "license": "MIT" 72 | }, 73 | "node_modules/xml2js": { 74 | "version": "0.6.2", 75 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", 76 | "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", 77 | "license": "MIT", 78 | "dependencies": { 79 | "sax": ">=0.6.0", 80 | "xmlbuilder": "~11.0.0" 81 | }, 82 | "engines": { 83 | "node": ">=4.0.0" 84 | } 85 | }, 86 | "node_modules/xmlbuilder": { 87 | "version": "11.0.1", 88 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", 89 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", 90 | "license": "MIT", 91 | "engines": { 92 | "node": ">=4.0" 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "events": "^3.3.0", 4 | "lodash": "^4.17.21", 5 | "moment": "2.30.1", 6 | "stream": "^0.0.3", 7 | "timers": "^0.1.1", 8 | "xml2js": "^0.6.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------# 2 | # Qodana analysis is configured by qodana.yaml file # 3 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html # 4 | #-------------------------------------------------------------------------------# 5 | version: "1.0" 6 | 7 | #Specify inspection profile for code analysis 8 | profile: 9 | name: qodana.recommended 10 | 11 | #Enable inspections 12 | #include: 13 | # - name: 14 | 15 | #Disable inspections 16 | exclude: 17 | - name: DataClassPrivateConstructor 18 | - name: UnusedSymbol 19 | # paths: 20 | # - 21 | 22 | projectJDK: '11' #(Applied in CI/CD pipeline) 23 | 24 | #Execute shell command before Qodana execution (Applied in CI/CD pipeline) 25 | #bootstrap: |+ 26 | # update-alternatives --list java 27 | # java --version 28 | # ./gradlew kaptKotlin 29 | 30 | #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) 31 | #plugins: 32 | # - id: #(plugin id can be found at https://plugins.jetbrains.com) 33 | 34 | #Specify Qodana linter for analysis (Applied in CI/CD pipeline) 35 | linter: jetbrains/qodana-jvm:latest 36 | 37 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended" 4 | ], 5 | "automerge": true, 6 | "commitBodyTable": true, 7 | "packageRules": [ 8 | { 9 | "matchUpdateTypes": [ 10 | "major", 11 | "minor", 12 | "patch" 13 | ], 14 | "groupName": "all dependencies", 15 | "groupSlug": "all" 16 | }, 17 | { 18 | "matchPackageNames": [ 19 | "org.graalvm.sdk:graal-sdk", 20 | "org.graalvm.js:js", 21 | "org.http4k:http4k-bom", 22 | "org.http4k:http4k-core", 23 | "org.http4k:http4k-client-apache", 24 | "org.http4k:http4k-format-moshi" 25 | ], 26 | "enabled": false 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | plugins { id("com.gradle.develocity") version "4.0.2" } 9 | 10 | dependencyResolutionManagement { 11 | versionCatalogs { create("libs") { from(files("libs.versions.toml")) } } 12 | pluginManagement { 13 | repositories { 14 | mavenCentral() 15 | gradlePluginPortal() 16 | google() 17 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots") 18 | maven("https://oss.sonatype.org/content/repositories/snapshots") 19 | } 20 | } 21 | } 22 | 23 | val isCI = !System.getenv("CI").isNullOrEmpty() 24 | 25 | develocity { 26 | buildScan { 27 | publishing.onlyIf { 28 | it.buildResult.failures.isNotEmpty() && !System.getenv("CI").isNullOrEmpty() 29 | } 30 | uploadInBackground.set(!isCI) 31 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" 32 | termsOfUseAgree = "yes" 33 | } 34 | } 35 | 36 | rootProject.name = "revoman-root" 37 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/adapters/IDAdapter.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.adapters; 9 | 10 | import com.salesforce.revoman.integration.core.pq.connect.response.ID; 11 | import com.squareup.moshi.FromJson; 12 | import com.squareup.moshi.ToJson; 13 | 14 | // * NOTE 10 Mar 2024 gopala.akshintala: Custom Type Adapter 15 | public class IDAdapter { 16 | public static final IDAdapter INSTANCE = new IDAdapter(); 17 | 18 | @FromJson 19 | ID fromJson(String id) { 20 | return new ID(id); 21 | } 22 | 23 | @ToJson 24 | String toJson(ID id) { 25 | return id.id(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/bt2bs/MilestoneBillingE2ETest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 salesforce.com, inc. 3 | * All Rights Reserved 4 | * Company Confidential 5 | */ 6 | 7 | package com.salesforce.revoman.integration.core.bt2bs; 8 | 9 | import static com.google.common.truth.Truth.assertThat; 10 | import static com.salesforce.revoman.integration.core.bt2bs.ReVomanConfigForBT2BS.MILESTONE_CONFIG; 11 | import static com.salesforce.revoman.integration.core.bt2bs.ReVomanConfigForBT2BS.MILESTONE_SETUP_CONFIG; 12 | import static com.salesforce.revoman.integration.core.bt2bs.ReVomanConfigForBT2BS.PERSONA_CREATION_AND_SETUP_CONFIG; 13 | 14 | import com.salesforce.revoman.ReVoman; 15 | import java.util.Map; 16 | import kotlin.collections.CollectionsKt; 17 | import org.junit.jupiter.api.Test; 18 | 19 | class MilestoneBillingE2ETest { 20 | 21 | @Test 22 | void testMilestoneBillingE2E() { 23 | final var mbRundown = 24 | ReVoman.revUp( 25 | (rundown, ignore) -> 26 | assertThat(rundown.firstUnIgnoredUnsuccessfulStepReport()).isNull(), 27 | PERSONA_CREATION_AND_SETUP_CONFIG, 28 | MILESTONE_SETUP_CONFIG, 29 | MILESTONE_CONFIG); 30 | assertThat(CollectionsKt.last(mbRundown).mutableEnv) 31 | .containsAtLeastEntriesIn( 32 | Map.of( 33 | "billingMilestonePlan1Status", "Completely Billed", 34 | "billingMilestonePlanItem1Status", "Invoiced", 35 | "billingSchedule1Status", "CompletelyBilled", 36 | "invoice1Status", "Posted", 37 | "invoice2Status", "Posted")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/bt2bs/ReVomanConfigForBT2BS.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 salesforce.com, inc. 3 | * All Rights Reserved 4 | * Company Confidential 5 | */ 6 | 7 | package com.salesforce.revoman.integration.core.bt2bs; 8 | 9 | import static com.salesforce.revoman.input.config.HookConfig.post; 10 | import static com.salesforce.revoman.input.config.StepPick.PostTxnStepPick.afterStepContainingHeader; 11 | import static com.salesforce.revoman.input.config.StepPick.PostTxnStepPick.afterStepContainingURIPathOfAny; 12 | import static com.salesforce.revoman.integration.core.CoreUtils.ASSERT_COMPOSITE_GRAPH_RESPONSE_SUCCESS; 13 | import static com.salesforce.revoman.integration.core.CoreUtils.ASSERT_COMPOSITE_RESPONSE_SUCCESS; 14 | import static com.salesforce.revoman.integration.core.CoreUtils.unmarshallCompositeGraphResponse; 15 | import static com.salesforce.revoman.integration.core.CoreUtils.unmarshallCompositeResponse; 16 | import static com.salesforce.revoman.output.ExeType.HTTP_STATUS; 17 | 18 | import com.salesforce.revoman.input.config.HookConfig; 19 | import com.salesforce.revoman.input.config.Kick; 20 | import com.salesforce.revoman.integration.core.adapters.IDAdapter; 21 | 22 | public final class ReVomanConfigForBT2BS { 23 | 24 | private ReVomanConfigForBT2BS() {} 25 | 26 | static final String ENV_PATH = "pm-templates/core/milestone/env.postman_environment.json"; 27 | static final String IGNORE_HTTP_STATUS_UNSUCCESSFUL = "ignoreHTTPStatusUnsuccessful"; 28 | static final String NODE_MODULE_RELATIVE_PATH = "js"; 29 | 30 | // ## User creation and Setup 31 | static final String COLLECTION_PATH = "pm-templates/core/milestone/"; 32 | private static final String PERSONA_CREATION_AND_SETUP_COLLECTION_PATH = 33 | COLLECTION_PATH + "persona-creation-and-setup.postman_collection.json"; 34 | static final Kick PERSONA_CREATION_AND_SETUP_CONFIG = 35 | Kick.configure() 36 | .templatePath(PERSONA_CREATION_AND_SETUP_COLLECTION_PATH) 37 | .environmentPath(ENV_PATH) 38 | .responseConfig(unmarshallCompositeGraphResponse(), unmarshallCompositeResponse()) 39 | .hooks(ASSERT_COMPOSITE_GRAPH_RESPONSE_SUCCESS, ASSERT_COMPOSITE_RESPONSE_SUCCESS) 40 | .nodeModulesPath(NODE_MODULE_RELATIVE_PATH) 41 | .haltOnFailureOfTypeExcept( 42 | HTTP_STATUS, afterStepContainingHeader(IGNORE_HTTP_STATUS_UNSUCCESSFUL)) 43 | .insecureHttp(true) 44 | .off(); 45 | 46 | // ## Hooks 47 | static final String PST = "connect/rev/sales-transaction/actions/place"; 48 | static final String STANDALONE_INVOICE_IA = 49 | "commerce/invoicing/invoices/collection/actions/generate"; 50 | static final String ASSETIZE_IA = "actions/standard/createOrUpdateAssetFromOrder"; 51 | static final String AMEND_API = "connect/revenue-management/assets/actions/amend"; 52 | static final String CANCEL_API = "connect/revenue-management/assets/actions/cancel"; 53 | public static final HookConfig MEMQ_AWAIT = 54 | post( 55 | afterStepContainingURIPathOfAny( 56 | PST, STANDALONE_INVOICE_IA, ASSETIZE_IA, AMEND_API, CANCEL_API), 57 | (ignore1, ignore2) -> Thread.sleep(5000)); 58 | 59 | // ## Milestone Setup Config 60 | private static final String MB_SETUP_POSTMAN_COLLECTION_PATH = 61 | COLLECTION_PATH + "milestone-setup.postman_collection.json"; 62 | static final Kick MILESTONE_SETUP_CONFIG = 63 | Kick.configure() 64 | .templatePath(MB_SETUP_POSTMAN_COLLECTION_PATH) 65 | .haltOnFailureOfTypeExcept( 66 | HTTP_STATUS, afterStepContainingHeader(IGNORE_HTTP_STATUS_UNSUCCESSFUL)) 67 | .responseConfig(unmarshallCompositeGraphResponse(), unmarshallCompositeResponse()) 68 | .hooks(ASSERT_COMPOSITE_GRAPH_RESPONSE_SUCCESS, ASSERT_COMPOSITE_RESPONSE_SUCCESS) 69 | .haltOnFailureOfTypeExcept( 70 | HTTP_STATUS, afterStepContainingHeader(IGNORE_HTTP_STATUS_UNSUCCESSFUL)) 71 | .globalCustomTypeAdapter(IDAdapter.INSTANCE) 72 | .nodeModulesPath(NODE_MODULE_RELATIVE_PATH) 73 | .off(); 74 | 75 | // ## Milestone Config 76 | private static final String MB_POSTMAN_COLLECTION_PATH = 77 | COLLECTION_PATH + "bmp-create-runtime.postman_collection.json"; 78 | static final Kick MILESTONE_CONFIG = 79 | Kick.configure() 80 | .templatePath(MB_POSTMAN_COLLECTION_PATH) 81 | .responseConfig(unmarshallCompositeGraphResponse(), unmarshallCompositeResponse()) 82 | .hooks( 83 | MEMQ_AWAIT, 84 | ASSERT_COMPOSITE_GRAPH_RESPONSE_SUCCESS, 85 | ASSERT_COMPOSITE_RESPONSE_SUCCESS) 86 | .haltOnFailureOfTypeExcept( 87 | HTTP_STATUS, afterStepContainingHeader(IGNORE_HTTP_STATUS_UNSUCCESSFUL)) 88 | .globalCustomTypeAdapter(IDAdapter.INSTANCE) 89 | .nodeModulesPath(NODE_MODULE_RELATIVE_PATH) 90 | .off(); 91 | } 92 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/ConnectInputRepresentationWithGraph.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | public interface ConnectInputRepresentationWithGraph { 11 | 12 | void setGraph(ObjectGraphInputRepresentation graph); 13 | 14 | ObjectGraphInputRepresentation getGraph(); 15 | } 16 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/ObjectGraphInputRepresentation.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | public class ObjectGraphInputRepresentation { 11 | 12 | private String graphId; 13 | 14 | private ObjectWithReferenceInputRepresentationList records; 15 | 16 | private boolean isSetGraphId; 17 | private boolean isSetRecords; 18 | 19 | public String getGraphId() { 20 | return this.graphId; 21 | } 22 | 23 | public void setGraphId(String graphId) { 24 | this.graphId = graphId; 25 | this.isSetGraphId = true; 26 | } 27 | 28 | public ObjectWithReferenceInputRepresentationList getRecords() { 29 | return this.records; 30 | } 31 | 32 | public void setRecords(ObjectWithReferenceInputRepresentationList records) { 33 | this.records = records; 34 | this.isSetRecords = true; 35 | } 36 | 37 | public boolean _isSetGraphId() { 38 | return this.isSetGraphId; 39 | } 40 | 41 | public boolean _isSetRecords() { 42 | return this.isSetRecords; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/ObjectInputRepresentationMap.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public class ObjectInputRepresentationMap { 14 | 15 | private Map recordBody; 16 | private boolean isSetRecordBody; 17 | 18 | public ObjectInputRepresentationMap() { 19 | this.recordBody = new HashMap<>(); 20 | } 21 | 22 | public Map getRecordBody() { 23 | return this.recordBody; 24 | } 25 | 26 | public void setRecordBody(Map recordBody) { 27 | this.recordBody = recordBody; 28 | this.isSetRecordBody = true; 29 | } 30 | 31 | public boolean _isSetRecordBody() { 32 | return this.isSetRecordBody; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/ObjectWithReferenceInputRepresentation.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | public class ObjectWithReferenceInputRepresentation { 11 | 12 | private String referenceId; 13 | private ObjectInputRepresentationMap record; 14 | 15 | private boolean isSetReferenceId; 16 | private boolean isSetRecord; 17 | 18 | public ObjectInputRepresentationMap getRecord() { 19 | return this.record; 20 | } 21 | 22 | public void setRecord(ObjectInputRepresentationMap record) { 23 | this.record = record; 24 | this.isSetRecord = true; 25 | } 26 | 27 | public String getReferenceId() { 28 | return this.referenceId; 29 | } 30 | 31 | public void setReferenceId(String referenceId) { 32 | this.referenceId = referenceId; 33 | this.isSetReferenceId = true; 34 | } 35 | 36 | public boolean _isSetRecord() { 37 | return this.isSetRecord; 38 | } 39 | 40 | public boolean _isSetReferenceId() { 41 | return this.isSetReferenceId; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/ObjectWithReferenceInputRepresentationList.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class ObjectWithReferenceInputRepresentationList { 14 | 15 | private List recordsList; 16 | private boolean isSetRecordsList; 17 | 18 | public ObjectWithReferenceInputRepresentationList() { 19 | super(); 20 | this.recordsList = new ArrayList<>(); 21 | } 22 | 23 | public List getRecordsList() { 24 | return this.recordsList; 25 | } 26 | 27 | public void setRecordsList(List recordsList) { 28 | this.recordsList = recordsList; 29 | this.isSetRecordsList = true; 30 | } 31 | 32 | public boolean _isSetRecordsList() { 33 | return this.isSetRecordsList; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/PlaceQuoteInputRepresentation.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | public class PlaceQuoteInputRepresentation implements ConnectInputRepresentationWithGraph { 11 | 12 | private PricingPreferenceEnum pricingPref; 13 | private Boolean doAsync; 14 | private ObjectGraphInputRepresentation graph; 15 | private boolean isSetGraph; 16 | 17 | public Boolean getDoAsync() { 18 | return doAsync; 19 | } 20 | 21 | public ObjectGraphInputRepresentation getGraph() { 22 | return this.graph; 23 | } 24 | 25 | public void setGraph(ObjectGraphInputRepresentation graph) { 26 | this.graph = graph; 27 | this.isSetGraph = true; 28 | } 29 | 30 | public boolean _isSetGraph() { 31 | return this.isSetGraph; 32 | } 33 | 34 | private boolean isSetPricingPref; 35 | 36 | private boolean isSetDoAsync; 37 | 38 | public PricingPreferenceEnum getPricingPref() { 39 | return pricingPref; 40 | } 41 | 42 | public void setPricingPref(PricingPreferenceEnum pricingPref) { 43 | this.pricingPref = pricingPref; 44 | this.isSetPricingPref = true; 45 | } 46 | 47 | public void setDoAsync(Boolean doAsync) { 48 | this.doAsync = doAsync; 49 | this.isSetDoAsync = true; 50 | } 51 | 52 | public boolean _isSetPricingPref() { 53 | return this.isSetPricingPref; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/request/PricingPreferenceEnum.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.request; 9 | 10 | public enum PricingPreferenceEnum { 11 | Force, 12 | Skip, 13 | System; 14 | } 15 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/response/ID.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.response; 9 | 10 | public record ID(String id) {} 11 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/response/PlaceQuoteErrorResponseRepresentation.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.response; 9 | 10 | public class PlaceQuoteErrorResponseRepresentation { 11 | private final String referenceId; 12 | private final String errorCode; 13 | private final String message; 14 | 15 | public PlaceQuoteErrorResponseRepresentation( 16 | final String referenceId, final String errorCode, final String message) { 17 | super(); 18 | this.referenceId = referenceId; 19 | this.errorCode = errorCode; 20 | this.message = message; 21 | } 22 | 23 | public String getMessage() { 24 | return message; 25 | } 26 | 27 | public String getErrorCode() { 28 | return errorCode; 29 | } 30 | 31 | public String getReferenceId() { 32 | return referenceId; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/core/pq/connect/response/PlaceQuoteOutputRepresentation.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.core.pq.connect.response; 9 | 10 | import java.util.List; 11 | 12 | public class PlaceQuoteOutputRepresentation { 13 | private final String requestIdentifier; 14 | private String statusURL; 15 | private final ID quoteId; 16 | private final Boolean success; 17 | private final List responseError; 18 | 19 | public PlaceQuoteOutputRepresentation( 20 | String requestIdentifier, 21 | String statusURL, 22 | ID quoteId, 23 | Boolean success, 24 | List responseError) { 25 | super(); 26 | this.requestIdentifier = requestIdentifier; 27 | this.statusURL = statusURL; 28 | this.success = success; 29 | this.responseError = responseError; 30 | this.quoteId = quoteId; 31 | } 32 | 33 | public String getRequestIdentifier() { 34 | return this.requestIdentifier; 35 | } 36 | 37 | public String getStatusURL() { 38 | return this.statusURL; 39 | } 40 | 41 | public void setStatusURL(String statusURL) { 42 | this.statusURL = statusURL; 43 | } 44 | 45 | public Boolean getSuccess() { 46 | return this.success; 47 | } 48 | 49 | public List getResponseError() { 50 | return this.responseError; 51 | } 52 | 53 | public ID getQuoteId() { 54 | return quoteId; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/integrationTest/java/com/salesforce/revoman/integration/restfulapidev/RestfulAPIDevTest.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.integration.restfulapidev; 9 | 10 | import static com.google.common.truth.Truth.assertThat; 11 | 12 | import com.salesforce.revoman.ReVoman; 13 | import com.salesforce.revoman.input.config.Kick; 14 | import org.junit.jupiter.api.DisplayName; 15 | import org.junit.jupiter.api.Test; 16 | 17 | class RestfulAPIDevTest { 18 | private static final String PM_COLLECTION_PATH = 19 | "pm-templates/restfulapidev/restful-api.dev.postman_collection.json"; 20 | private static final String PM_ENVIRONMENT_PATH = 21 | "pm-templates/restfulapidev/restful-api.dev.postman_environment.json"; 22 | 23 | // tag::revoman-simple-demo[] 24 | @Test 25 | @DisplayName("restful-api.dev") 26 | void restfulApiDev() { 27 | final var rundown = 28 | ReVoman.revUp( // <1> 29 | Kick.configure() 30 | .templatePath(PM_COLLECTION_PATH) // <2> 31 | .environmentPath(PM_ENVIRONMENT_PATH) // <3> 32 | .nodeModulesPath("js") 33 | .off()); 34 | assertThat(rundown.firstUnIgnoredUnsuccessfulStepReport()).isNull(); // <4> 35 | assertThat(rundown.stepReports).hasSize(4); // <5> 36 | } 37 | // end::revoman-simple-demo[] 38 | } 39 | -------------------------------------------------------------------------------- /src/integrationTest/kotlin/com/salesforce/revoman/integration/apigee/ApigeeKtTest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.integration.apigee 9 | 10 | import com.google.common.truth.Truth.assertThat 11 | import com.salesforce.revoman.ReVoman 12 | import com.salesforce.revoman.input.config.Kick 13 | import org.junit.jupiter.api.Test 14 | 15 | class ApigeeKtTest { 16 | @Test 17 | fun `xml2js apigee`() { 18 | val rundown = 19 | ReVoman.revUp(Kick.configure().templatePath(PM_COLLECTION_PATH).nodeModulesPath("js").off()) 20 | assertThat(rundown.stepReports).hasSize(1) 21 | assertThat(rundown.firstUnsuccessfulStepReport).isNull() 22 | assertThat(rundown.mutableEnv["city"]).isEqualTo("San Jose") 23 | } 24 | 25 | companion object { 26 | private const val PM_COLLECTION_PATH = "pm-templates/apigee/apigee.postman_collection.json" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/integrationTest/kotlin/com/salesforce/revoman/integration/restfulapidev/RestfulAPIDevKtTest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.integration.restfulapidev 9 | 10 | import com.google.common.truth.Truth.assertThat 11 | import com.salesforce.revoman.ReVoman 12 | import com.salesforce.revoman.input.config.Kick 13 | import org.junit.jupiter.api.Test 14 | 15 | class RestfulAPIDevKtTest { 16 | @Test 17 | fun `execute restful-api dev pm collection`() { 18 | val rundown = 19 | ReVoman.revUp( 20 | // <1> 21 | Kick.configure() 22 | .templatePath(PM_COLLECTION_PATH) // <2> 23 | .environmentPath(PM_ENVIRONMENT_PATH) // <3> 24 | .nodeModulesPath("js") 25 | .off() 26 | ) 27 | assertThat(rundown.firstUnsuccessfulStepReport).isNull() 28 | assertThat(rundown.stepReports).hasSize(4) 29 | } 30 | 31 | companion object { 32 | private const val PM_COLLECTION_PATH = 33 | "pm-templates/restfulapidev/restful-api.dev.postman_collection.json" 34 | private const val PM_ENVIRONMENT_PATH = 35 | "pm-templates/restfulapidev/restful-api.dev.postman_environment.json" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/integrationTest/resources/json/pq-payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "pricingPref" : "System", 3 | "doAsync" : true, 4 | "graph" : { 5 | "graphId" : "pq-create-with-bundles", 6 | "records" : [ 7 | { 8 | "referenceId" : "refQuote", 9 | "record" : { 10 | "attributes" : { 11 | "type" : "Quote", 12 | "method" : "POST" 13 | }, 14 | "Name" : "Quote_{{$randomCompanyName}}", 15 | "OpportunityId" : "{{opportunityId}}" 16 | } 17 | }, 18 | { 19 | "referenceId" : "refQuoteItem1", 20 | "record" : { 21 | "attributes" : { 22 | "type" : "QuoteLineItem", 23 | "method" : "POST" 24 | }, 25 | "QuoteId" : "@{refQuote.id}", 26 | "PricebookEntryId" : "{{evergreenPriceBookEntryId}}", 27 | "Product2Id" : "{{evergreenProductId}}", 28 | "Quantity" : 2.0, 29 | "UnitPrice" : 25.0, 30 | "PeriodBoundary" : "Anniversary", 31 | "BillingFrequency" : "Monthly", 32 | "StartDate" : "{{$currentDate}}" 33 | } 34 | }, 35 | { 36 | "referenceId" : "refQuoteItem2", 37 | "record" : { 38 | "attributes" : { 39 | "type" : "QuoteLineItem", 40 | "method" : "POST" 41 | }, 42 | "QuoteId" : "@{refQuote.id}", 43 | "PricebookEntryId" : "{{termedPriceBookEntryId}}", 44 | "Product2Id" : "{{termedProductId}}", 45 | "Quantity" : 2.0, 46 | "UnitPrice" : 25.0, 47 | "EndDate" : "{{$randomFutureDate}}", 48 | "PeriodBoundary" : "DayOfPeriod", 49 | "PeriodBoundaryDay" : 4, 50 | "BillingFrequency" : "Monthly", 51 | "StartDate" : "{{$currentDate}}" 52 | } 53 | }, 54 | { 55 | "referenceId" : "refQuoteItem3", 56 | "record" : { 57 | "attributes" : { 58 | "type" : "QuoteLineItem", 59 | "method" : "POST" 60 | }, 61 | "QuoteId" : "@{refQuote.id}", 62 | "PricebookEntryId" : "{{oneTimePriceBookEntryId}}", 63 | "Product2Id" : "{{oneTimeProductId}}", 64 | "Quantity" : 2.0, 65 | "UnitPrice" : 25.0, 66 | "StartDate" : "{{$currentDate}}", 67 | "BillingFrequency" : null, 68 | "PeriodBoundary" : null 69 | } 70 | }, 71 | { 72 | "referenceId" : "refQuoteItem4", 73 | "record" : { 74 | "attributes" : { 75 | "type" : "QuoteLineItem", 76 | "method" : "POST" 77 | }, 78 | "QuoteId" : "@{refQuote.id}", 79 | "PricebookEntryId" : "{{oneTimePriceBookEntryId}}", 80 | "Product2Id" : "{{oneTimeProductId}}", 81 | "Quantity" : 2.0, 82 | "UnitPrice" : 25.0, 83 | "StartDate" : "{{$currentDate}}" 84 | } 85 | }, 86 | { 87 | "referenceId" : "refQLR1", 88 | "record" : { 89 | "attributes" : { 90 | "type" : "QuoteLineRelationship", 91 | "method" : "POST" 92 | }, 93 | "MainQuoteLineId" : "@{refQuoteItem1.id}", 94 | "AssociatedQuoteLineId" : "@{refQuoteItem4.id}", 95 | "ProductRelationshipTypeId" : "{{productRelationshipTypeId}}", 96 | "AssociatedQuoteLinePricing" : "IncludedInBundlePrice" 97 | } 98 | } 99 | ] 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/integrationTest/resources/json/pq-response.json: -------------------------------------------------------------------------------- 1 | { 2 | "quoteId": "0Q0xx0000004E2mCAE", 3 | "requestIdentifier": "95Txx0000004E60", 4 | "responseError": [], 5 | "statusURL": "/services/data/v58.0/sobjects/RevenueAsyncOperation/95Txx0000004E60EAE", 6 | "success": true 7 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/json/pricing-pref.json: -------------------------------------------------------------------------------- 1 | { 2 | "pricingPref" : "SYSTEM" 3 | } 4 | -------------------------------------------------------------------------------- /src/integrationTest/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/integrationTest/resources/pm-templates/apigee/apigee.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "00262e9b-48de-4d72-bea9-5db5c77029cb", 4 | "name": "Apigee", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "23827434" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Apigee", 11 | "event": [ 12 | { 13 | "listen": "prerequest", 14 | "script": { 15 | "exec": [ 16 | "" 17 | ], 18 | "type": "text/javascript", 19 | "packages": {} 20 | } 21 | }, 22 | { 23 | "listen": "test", 24 | "script": { 25 | "exec": [ 26 | "var xml2js = require('xml2js')", 27 | "xml2js.parseString(pm.response.text(), { explicitArray: false }, (_, jsonResponse) => {", 28 | " let result = jsonResponse.root.city", 29 | " pm.environment.set(\"city\", result)", 30 | "})" 31 | ], 32 | "type": "text/javascript", 33 | "packages": {} 34 | } 35 | } 36 | ], 37 | "request": { 38 | "method": "GET", 39 | "header": [], 40 | "url": { 41 | "raw": "https://mocktarget.apigee.net/xml", 42 | "protocol": "https", 43 | "host": [ 44 | "mocktarget", 45 | "apigee", 46 | "net" 47 | ], 48 | "path": [ 49 | "xml" 50 | ] 51 | } 52 | }, 53 | "response": [] 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/pm-templates/core/milestone/env.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "eca3a642-ef81-42ae-9e29-e9e5065c2390", 3 | "name": "bt2bs", 4 | "values": [ 5 | { 6 | "key" : "baseUrl", 7 | "value" : "https://dlt000005y8w62ai.test1.my.pc-rnd.salesforce.com", 8 | "type" : "default", 9 | "enabled" : true 10 | }, 11 | { 12 | "key" : "username", 13 | "value" : "asisrenewal@salesforce.com", 14 | "type" : "default", 15 | "enabled" : true 16 | }, 17 | { 18 | "key" : "password", 19 | "value" : "test1234", 20 | "type" : "default", 21 | "enabled" : true 22 | }, 23 | { 24 | "key": "commonUserPassword", 25 | "value": "test1234", 26 | "type": "default", 27 | "enabled": true 28 | }, 29 | { 30 | "key": "$unitPrice", 31 | "value": "63.00", 32 | "type": "default", 33 | "enabled": true 34 | }, 35 | { 36 | "key": "secretToken-admin", 37 | "value": "", 38 | "type": "default", 39 | "enabled": true 40 | }, 41 | { 42 | "key": "secretToken", 43 | "value": "", 44 | "type": "default", 45 | "enabled": true 46 | }, 47 | { 48 | "key": "secretToken-billingAdmin", 49 | "value": "", 50 | "type": "default", 51 | "enabled": true 52 | }, 53 | { 54 | "key": "secretToken-taxAdmin", 55 | "value": "", 56 | "type": "default", 57 | "enabled": true 58 | }, 59 | { 60 | "key": "secretToken-productAndPricingAdmin", 61 | "value": "", 62 | "type": "default", 63 | "enabled": true 64 | }, 65 | { 66 | "key": "secretToken-salesRep", 67 | "value": "", 68 | "type": "default", 69 | "enabled": true 70 | } 71 | ], 72 | "_postman_variable_scope": "environment", 73 | "_postman_exported_at": "2022-07-28T10:35:45.674Z", 74 | "_postman_exported_using": "Postman/9.20.0-canary" 75 | } 76 | -------------------------------------------------------------------------------- /src/integrationTest/resources/pm-templates/core/pq/pq-env.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "eca3a642-ef81-42ae-9e29-e9e5065c2390", 3 | "name" : "pq", 4 | "values" : [ 5 | { 6 | "key" : "baseUrl", 7 | "value" : "https://gopalaaksh-wsl3:6101", 8 | "type" : "default", 9 | "enabled" : true 10 | }, 11 | { 12 | "key" : "username", 13 | "value" : "linux-sm@252.org", 14 | "type" : "default", 15 | "enabled" : true 16 | }, 17 | { 18 | "key" : "password", 19 | "value" : "123456", 20 | "type" : "default", 21 | "enabled" : true 22 | }, 23 | { 24 | "key" : "commonUserPassword", 25 | "value" : "test1234", 26 | "type" : "default", 27 | "enabled" : true 28 | }, 29 | { 30 | "key" : "secretToken-admin", 31 | "value" : "", 32 | "type" : "default", 33 | "enabled" : true 34 | }, 35 | { 36 | "key" : "secretToken-billingAdmin", 37 | "value" : "", 38 | "type" : "default", 39 | "enabled" : true 40 | }, 41 | { 42 | "key" : "secretToken-taxAdmin", 43 | "value" : "", 44 | "type" : "default", 45 | "enabled" : true 46 | }, 47 | { 48 | "key" : "secretToken-productAndPricingAdmin", 49 | "value" : "", 50 | "type" : "default", 51 | "enabled" : true 52 | }, 53 | { 54 | "key" : "secretToken-salesRep", 55 | "value" : "", 56 | "type" : "default", 57 | "enabled" : true 58 | } 59 | ], 60 | "_postman_variable_scope" : "environment", 61 | "_postman_exported_at" : "2022-07-28T10:35:45.674Z", 62 | "_postman_exported_using" : "Postman/9.20.0-canary" 63 | } 64 | -------------------------------------------------------------------------------- /src/integrationTest/resources/pm-templates/pokemon/pokemon.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "535cddb4-a44b-4237-bf59-f27c334158af", 3 | "name" : "Pokemon", 4 | "values" : [ 5 | { 6 | "key" : "baseUrl", 7 | "value" : "https://pokeapi.co/api/v2", 8 | "type" : "default", 9 | "enabled" : true 10 | } 11 | ], 12 | "_postman_variable_scope" : "environment", 13 | "_postman_exported_at" : "2022-03-31T10:46:04.026Z", 14 | "_postman_exported_using" : "Postman/9.12.2-canary" 15 | } 16 | -------------------------------------------------------------------------------- /src/integrationTest/resources/pm-templates/restfulapidev/restful-api.dev.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "8e2843e3-f3e9-4c55-a49d-89429fd02a83", 4 | "name": "restful-api.dev", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "23827434" 7 | }, 8 | "item": [ 9 | { 10 | "name": "all-objects", 11 | "event": [ 12 | { 13 | "listen": "prerequest", 14 | "script": { 15 | "exec": [ 16 | "" 17 | ], 18 | "type": "text/javascript" 19 | } 20 | } 21 | ], 22 | "request": { 23 | "method": "GET", 24 | "header": [], 25 | "url": { 26 | "raw": "https://{{uri}}/objects", 27 | "protocol": "https", 28 | "host": [ 29 | "{{uri}}" 30 | ], 31 | "path": [ 32 | "objects" 33 | ] 34 | } 35 | }, 36 | "response": [] 37 | }, 38 | { 39 | "name": "add-object", 40 | "event": [ 41 | { 42 | "listen": "test", 43 | "script": { 44 | "exec": [ 45 | "var responseJson = pm.response.json();", 46 | "pm.environment.set(\"objId\", responseJson.id);", 47 | "pm.environment.set(\"data\", responseJson.data)", 48 | "pm.environment.set(\"productName\", responseJson.name)" 49 | ], 50 | "type": "text/javascript", 51 | "packages": {} 52 | } 53 | }, 54 | { 55 | "listen": "prerequest", 56 | "script": { 57 | "exec": [ 58 | "var moment = require('moment')", 59 | "var _ = require('lodash')", 60 | "", 61 | "pm.environment.set(\"$currentYear\", moment().year())", 62 | "pm.environment.set(\"$randomPrice\", _.random(1, 1000))" 63 | ], 64 | "type": "text/javascript", 65 | "packages": {} 66 | } 67 | } 68 | ], 69 | "request": { 70 | "method": "POST", 71 | "header": [], 72 | "body": { 73 | "mode": "raw", 74 | "raw": "{\n \"name\": \"{{$randomProduct}}\", // Dynamic variable\n \"data\": {\n \"year\": {{$currentYear}}, // Variable set via Pre-req\n \"price\": {{$randomPrice}}, // Variable set via Pre-req\n \"CPU model\": null\n }\n}", 75 | "options": { 76 | "raw": { 77 | "language": "json" 78 | } 79 | } 80 | }, 81 | "url": { 82 | "raw": "https://{{uri}}/objects", 83 | "protocol": "https", 84 | "host": [ 85 | "{{uri}}" 86 | ], 87 | "path": [ 88 | "objects" 89 | ] 90 | } 91 | }, 92 | "response": [] 93 | }, 94 | { 95 | "name": "update-object", 96 | "event": [ 97 | { 98 | "listen": "test", 99 | "script": { 100 | "exec": [ 101 | "var responseJson = pm.response.json();", 102 | "pm.environment.set(\"objId\", responseJson.id);", 103 | "pm.environment.set(\"data\", responseJson.data)" 104 | ], 105 | "type": "text/javascript", 106 | "packages": {} 107 | } 108 | }, 109 | { 110 | "listen": "prerequest", 111 | "script": { 112 | "exec": [ 113 | "var moment = require('moment')", 114 | "var _ = require('lodash')", 115 | "", 116 | "pm.environment.set(\"$currentYear\", moment().year())", 117 | "pm.environment.set(\"$randomPrice\", _.random(1, 1000))" 118 | ], 119 | "type": "text/javascript", 120 | "packages": {} 121 | } 122 | } 123 | ], 124 | "request": { 125 | "method": "PATCH", 126 | "header": [], 127 | "body": { 128 | "mode": "raw", 129 | "raw": "{\n \"name\": \"updated - {{productName}}\" // Update Name\n}", 130 | "options": { 131 | "raw": { 132 | "language": "json" 133 | } 134 | } 135 | }, 136 | "url": { 137 | "raw": "https://{{uri}}/objects/{{objId}}", 138 | "protocol": "https", 139 | "host": [ 140 | "{{uri}}" 141 | ], 142 | "path": [ 143 | "objects", 144 | "{{objId}}" 145 | ] 146 | } 147 | }, 148 | "response": [] 149 | }, 150 | { 151 | "name": "get-object-by-id", 152 | "event": [ 153 | { 154 | "listen": "prerequest", 155 | "script": { 156 | "exec": [ 157 | "console.log(pm.environment.get(\"data\"))" 158 | ], 159 | "type": "text/javascript", 160 | "packages": {} 161 | } 162 | } 163 | ], 164 | "request": { 165 | "method": "GET", 166 | "header": [], 167 | "url": { 168 | "raw": "https://{{uri}}/objects/{{objId}}", 169 | "protocol": "https", 170 | "host": [ 171 | "{{uri}}" 172 | ], 173 | "path": [ 174 | "objects", 175 | "{{objId}}" 176 | ] 177 | } 178 | }, 179 | "response": [] 180 | } 181 | ] 182 | } -------------------------------------------------------------------------------- /src/integrationTest/resources/pm-templates/restfulapidev/restful-api.dev.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "cf14fd0d-74be-4069-b526-0c89d19c18bc", 3 | "name" : "restful-api.dev", 4 | "values" : [ 5 | { 6 | "key" : "uri", 7 | "value" : "api.restful-api.dev", 8 | "type" : "default", 9 | "enabled" : true 10 | } 11 | ], 12 | "_postman_variable_scope" : "environment", 13 | "_postman_exported_at" : "2024-01-19T12:59:06.198Z", 14 | "_postman_exported_using" : "Postman/10.19.15-canary01" 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/FileUtils.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | @file:JvmName("FileUtils") 9 | 10 | package com.salesforce.revoman.input 11 | 12 | import java.io.File 13 | import java.io.InputStream 14 | import okio.BufferedSource 15 | import okio.FileSystem.Companion.RESOURCES 16 | import okio.FileSystem.Companion.SYSTEM 17 | import okio.Path.Companion.toPath 18 | import okio.buffer 19 | import okio.source 20 | 21 | fun bufferFile(filePath: String): BufferedSource = 22 | filePath.toPath().let { (if (it.isAbsolute) SYSTEM else RESOURCES).source(it).buffer() } 23 | 24 | fun readFileToString(filePath: String): String = bufferFile(filePath).readUtf8() 25 | 26 | fun bufferInputStream(inputStream: InputStream): BufferedSource = inputStream.source().buffer() 27 | 28 | fun readInputStreamToString(inputStream: InputStream): String = 29 | bufferInputStream(inputStream).readUtf8() 30 | 31 | fun bufferFile(file: File): BufferedSource = file.source().buffer() 32 | 33 | fun readFileToString(file: File): String = bufferFile(file).readUtf8() 34 | 35 | fun writeToFile(filePath: String, content: String) = 36 | SYSTEM.write(filePath.toPath()) { writeUtf8(content) } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/PostExeHook.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input 9 | 10 | import com.salesforce.revoman.output.Rundown 11 | 12 | fun interface PostExeHook { 13 | @Throws(Throwable::class) fun accept(currentRundown: Rundown, rundowns: List) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/config/HookConfig.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.config 9 | 10 | import com.salesforce.revoman.input.config.HookConfig.StepHook.PostStepHook 11 | import com.salesforce.revoman.input.config.HookConfig.StepHook.PreStepHook 12 | import com.salesforce.revoman.input.config.StepPick.PostTxnStepPick 13 | import com.salesforce.revoman.input.config.StepPick.PreTxnStepPick 14 | import com.salesforce.revoman.output.Rundown 15 | import com.salesforce.revoman.output.report.Step 16 | import com.salesforce.revoman.output.report.StepReport 17 | import com.salesforce.revoman.output.report.TxnInfo 18 | import org.http4k.core.Request 19 | 20 | @ExposedCopyVisibility 21 | data class HookConfig private constructor(val pick: StepPick, val stepHook: StepHook) { 22 | sealed interface StepHook { 23 | fun interface PreStepHook : StepHook { 24 | @Throws(Throwable::class) 25 | fun accept(currentStep: Step, requestInfo: TxnInfo, rundown: Rundown) 26 | } 27 | 28 | fun interface PostStepHook : StepHook { 29 | @Throws(Throwable::class) fun accept(currentStepReport: StepReport, rundown: Rundown) 30 | } 31 | } 32 | 33 | companion object { 34 | @JvmStatic fun pre(pick: PreTxnStepPick, hook: PreStepHook): HookConfig = HookConfig(pick, hook) 35 | 36 | @JvmStatic 37 | fun post(pick: PostTxnStepPick, hook: PostStepHook): HookConfig = HookConfig(pick, hook) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/config/KickDef.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.config 9 | 10 | import com.salesforce.revoman.input.config.StepPick.ExeStepPick 11 | import com.salesforce.revoman.input.config.StepPick.PostTxnStepPick 12 | import com.salesforce.revoman.input.config.StepPick.PreTxnStepPick 13 | import com.salesforce.revoman.output.ExeType 14 | import com.salesforce.revoman.output.Rundown 15 | import com.salesforce.revoman.output.report.StepReport 16 | import com.squareup.moshi.JsonAdapter 17 | import com.squareup.moshi.JsonAdapter.Factory 18 | import io.vavr.control.Either 19 | import java.io.InputStream 20 | import java.lang.reflect.Type 21 | import java.util.Collections.disjoint 22 | import org.immutables.value.Value 23 | import org.immutables.value.Value.Style.ImplementationVisibility.PUBLIC 24 | 25 | @Config 26 | @Value.Immutable 27 | internal interface KickDef { 28 | // * NOTE 29/10/23 gopala.akshintala: `List` coz it allows adding a template twice, 29 | // as there can be use-cases to execute the same template twice 30 | fun templatePaths(): List 31 | 32 | fun templateInputStreams(): List 33 | 34 | fun environmentPaths(): Set 35 | 36 | fun environmentInputStreams(): List 37 | 38 | fun dynamicEnvironments(): List> 39 | 40 | fun nodeModulesPath(): String? 41 | 42 | @Value.Derived 43 | fun dynamicEnvironmentsFlattened(): Map = 44 | if (dynamicEnvironments().isEmpty()) emptyMap() 45 | else dynamicEnvironments().reduce { acc, map -> acc + map } 46 | 47 | fun customDynamicVariableGenerators(): Map 48 | 49 | @Value.Default fun haltOnAnyFailure(): Boolean = false 50 | 51 | fun haltOnFailureOfTypeExcept(): Map 52 | 53 | fun runOnlySteps(): List 54 | 55 | fun skipSteps(): List 56 | 57 | fun hooks(): List 58 | 59 | @Value.Derived fun preStepHooks(): List = hooks().filter { it.pick is PreTxnStepPick } 60 | 61 | @Value.Derived 62 | fun postStepHooks(): List = hooks().filter { it.pick is PostTxnStepPick } 63 | 64 | // * NOTE 29/10/23 gopala.akshintala: requestConfig/responseConfig are decoupled from pre-step / 65 | // post-step hook to allow setting up unmarshalling to strong types on the final rundown for 66 | // post-execution assertions agnostic of whether the step has any hooks 67 | 68 | fun requestConfig(): Set 69 | 70 | @Value.Derived 71 | fun customTypeAdaptersFromRequestConfig(): Map, Factory>> = 72 | requestConfig() 73 | .filter { it.customTypeAdapter != null } 74 | .associate { it.requestType to it.customTypeAdapter!! } 75 | 76 | fun responseConfig(): Set 77 | 78 | @Value.Derived 79 | fun pickToResponseConfig(): Map> = 80 | responseConfig().groupBy { it.ifSuccess } 81 | 82 | @Value.Derived 83 | fun customTypeAdaptersFromResponseConfig(): Map, Factory>> = 84 | responseConfig() 85 | .filter { it.customTypeAdapter != null } 86 | .associate { it.responseType to it.customTypeAdapter!! } 87 | 88 | fun globalCustomTypeAdapters(): List 89 | 90 | fun globalSkipTypes(): Set> 91 | 92 | @Value.Default fun insecureHttp(): Boolean = false 93 | 94 | @Value.Check 95 | fun validateConfig() { 96 | require(!haltOnAnyFailure() || (haltOnAnyFailure() && haltOnFailureOfTypeExcept().isEmpty())) { 97 | "`haltOnAnyFailureExcept` should NOT be set when `haltOnAnyFailure` is set to true" 98 | } 99 | require(disjoint(runOnlySteps(), skipSteps())) { 100 | "`runOnlySteps` and `skipSteps` cannot contain same step names" 101 | } 102 | } 103 | } 104 | 105 | fun interface CustomDynamicVariableGenerator { 106 | fun generate(variableName: String, currentStepReport: StepReport, rundown: Rundown): String 107 | } 108 | 109 | @Target(AnnotationTarget.CLASS) 110 | @Retention(AnnotationRetention.SOURCE) 111 | @Value.Style( 112 | typeImmutable = "*", 113 | typeAbstract = ["*Def"], 114 | builder = "configure", 115 | build = "off", 116 | depluralize = true, 117 | add = "*", 118 | put = "*", 119 | with = "override*", 120 | visibility = PUBLIC, 121 | ) 122 | private annotation class Config 123 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/config/RequestConfig.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.config 9 | 10 | import com.salesforce.revoman.input.config.StepPick.PreTxnStepPick 11 | import com.squareup.moshi.JsonAdapter 12 | import io.vavr.control.Either 13 | import io.vavr.kotlin.left 14 | import io.vavr.kotlin.right 15 | import java.lang.reflect.Type 16 | 17 | data class RequestConfig 18 | internal constructor( 19 | val preTxnStepPick: PreTxnStepPick, 20 | val requestType: Type, 21 | val customTypeAdapter: Either, JsonAdapter.Factory>? = null, 22 | ) { 23 | companion object { 24 | @JvmStatic 25 | fun unmarshallRequest(preTxnStepPick: PreTxnStepPick, requestType: Type): RequestConfig = 26 | RequestConfig(preTxnStepPick, requestType) 27 | 28 | @JvmStatic 29 | fun unmarshallRequest( 30 | preTxnStepPick: PreTxnStepPick, 31 | requestType: Type, 32 | customTypeAdapter: JsonAdapter, 33 | ): RequestConfig = RequestConfig(preTxnStepPick, requestType, left(customTypeAdapter)) 34 | 35 | @JvmStatic 36 | fun unmarshallRequest( 37 | preTxnStepPick: PreTxnStepPick, 38 | requestType: Type, 39 | customTypeAdapterFactory: JsonAdapter.Factory, 40 | ): RequestConfig = RequestConfig(preTxnStepPick, requestType, right(customTypeAdapterFactory)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/config/ResponseConfig.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.config 9 | 10 | import com.salesforce.revoman.input.config.StepPick.PostTxnStepPick 11 | import com.squareup.moshi.JsonAdapter 12 | import io.vavr.control.Either 13 | import io.vavr.kotlin.left 14 | import io.vavr.kotlin.right 15 | import java.lang.reflect.Type 16 | 17 | data class ResponseConfig 18 | internal constructor( 19 | val postTxnStepPick: PostTxnStepPick, 20 | val ifSuccess: Boolean?, 21 | val responseType: Type, 22 | val customTypeAdapter: Either, JsonAdapter.Factory>? = null, 23 | ) { 24 | companion object { 25 | @JvmStatic 26 | fun unmarshallResponse(postTxnStepPick: PostTxnStepPick, successType: Type): ResponseConfig = 27 | ResponseConfig(postTxnStepPick, null, successType) 28 | 29 | @JvmStatic 30 | fun unmarshallResponse( 31 | postTxnStepPick: PostTxnStepPick, 32 | successType: Type, 33 | customTypeAdapter: JsonAdapter, 34 | ): ResponseConfig = ResponseConfig(postTxnStepPick, null, successType, left(customTypeAdapter)) 35 | 36 | @JvmStatic 37 | fun unmarshallResponse( 38 | postTxnStepPick: PostTxnStepPick, 39 | successType: Type, 40 | customTypeAdapterFactory: JsonAdapter.Factory, 41 | ): ResponseConfig = 42 | ResponseConfig(postTxnStepPick, null, successType, right(customTypeAdapterFactory)) 43 | 44 | @JvmStatic 45 | fun unmarshallSuccessResponse( 46 | postTxnStepPick: PostTxnStepPick, 47 | successType: Type, 48 | ): ResponseConfig = ResponseConfig(postTxnStepPick, true, successType) 49 | 50 | @JvmStatic 51 | fun unmarshallSuccessResponse( 52 | postTxnStepPick: PostTxnStepPick, 53 | successType: Type, 54 | customTypeAdapter: JsonAdapter, 55 | ): ResponseConfig = ResponseConfig(postTxnStepPick, true, successType, left(customTypeAdapter)) 56 | 57 | @JvmStatic 58 | fun unmarshallSuccessResponse( 59 | postTxnStepPick: PostTxnStepPick, 60 | successType: Type, 61 | customTypeAdapterFactory: JsonAdapter.Factory, 62 | ): ResponseConfig = 63 | ResponseConfig(postTxnStepPick, true, successType, right(customTypeAdapterFactory)) 64 | 65 | @JvmStatic 66 | fun unmarshallErrorResponse( 67 | postTxnStepPick: PostTxnStepPick, 68 | successType: Type, 69 | ): ResponseConfig = ResponseConfig(postTxnStepPick, false, successType) 70 | 71 | @JvmStatic 72 | fun unmarshallErrorResponse( 73 | postTxnStepPick: PostTxnStepPick, 74 | successType: Type, 75 | customTypeAdapter: JsonAdapter, 76 | ): ResponseConfig = ResponseConfig(postTxnStepPick, false, successType, left(customTypeAdapter)) 77 | 78 | @JvmStatic 79 | fun unmarshallErrorResponse( 80 | postTxnStepPick: PostTxnStepPick, 81 | successType: Type, 82 | customTypeAdapterFactory: JsonAdapter.Factory, 83 | ): ResponseConfig = 84 | ResponseConfig(postTxnStepPick, false, successType, right(customTypeAdapterFactory)) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/FnTypes.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.json 9 | 10 | fun interface NestedNodeWriter { 11 | @Throws(Throwable::class) fun write(t: T) 12 | } 13 | 14 | fun interface NestedNodeReader { 15 | @Throws(Throwable::class) fun read(t: T, s: String) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/JsonReaderUtils.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | @file:JvmName("JsonReaderUtils") 9 | 10 | package com.salesforce.revoman.input.json 11 | 12 | import com.squareup.moshi.JsonAdapter 13 | import com.squareup.moshi.JsonReader 14 | import com.squareup.moshi.Moshi 15 | import org.springframework.beans.BeanUtils 16 | 17 | fun nextString(reader: JsonReader): String = reader.nextString() 18 | 19 | fun nextBoolean(reader: JsonReader): Boolean = reader.nextBoolean() 20 | 21 | fun nextInt(reader: JsonReader): Int = reader.nextInt() 22 | 23 | fun nextLong(reader: JsonReader): Long = reader.nextLong() 24 | 25 | fun skipValue(reader: JsonReader) = reader.skipValue() 26 | 27 | fun nextName(reader: JsonReader): String = reader.nextName() 28 | 29 | fun objR(supplier: () -> T, reader: JsonReader, block: NestedNodeReader): T = 30 | with(reader) { 31 | beginObject() 32 | val item = supplier() 33 | while (hasNext()) { 34 | block.read(item, nextName()) 35 | } 36 | endObject() 37 | item 38 | } 39 | 40 | fun JsonReader.objR(supplier: () -> T, block: T.(String) -> Unit): T = 41 | objR(supplier, this, block) 42 | 43 | fun listR(supplier: () -> T, reader: JsonReader, fn: NestedNodeReader): List? = 44 | reader.skipNullOr { 45 | val items = mutableListOf() 46 | beginArray() 47 | while (hasNext()) items += objR(supplier, this, fn) 48 | endArray() 49 | items 50 | } 51 | 52 | fun JsonReader.anyMapR(): Map? = skipNullOr { 53 | beginObject() 54 | val map = mutableMapOf() 55 | while (hasNext()) map += nextName() to readJsonValue() 56 | endObject() 57 | map 58 | } 59 | 60 | private fun JsonReader.skipNullOr(fn: JsonReader.() -> T): T? = 61 | if (peek() == JsonReader.Token.NULL) skipValue().let { null } else fn() 62 | 63 | fun JsonReader.readProps(pojoType: Class, bean: T, fieldName: String, moshi: Moshi) = 64 | skipNullOr { 65 | val propType: Class<*> = BeanUtils.findPropertyType(fieldName, pojoType) 66 | // * NOTE 15 Feb 2024 gopala.akshintala: Since data type info gets lost with JSON, 67 | // dynamicJsonAdapter cannot be used 68 | val delegate: JsonAdapter = moshi.adapter(propType) 69 | BeanUtils.getPropertyDescriptor(pojoType, fieldName) 70 | ?.writeMethod 71 | ?.invoke(bean, delegate.fromJson(this)) 72 | } 73 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/JsonWriterUtils.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | @file:JvmName("JsonWriterUtils") 9 | 10 | package com.salesforce.revoman.input.json 11 | 12 | import com.squareup.moshi.JsonAdapter 13 | import com.squareup.moshi.JsonWriter 14 | import org.springframework.beans.BeanUtils 15 | 16 | fun objW(name: String, obj: T?, writer: JsonWriter, fn: NestedNodeWriter): Unit = 17 | with(writer) { 18 | name(name) 19 | objW(obj, writer, fn) 20 | } 21 | 22 | fun JsonWriter.objW(name: String, obj: T?, fn: T.() -> Unit): Unit = objW(name, obj, this, fn) 23 | 24 | fun objW(obj: T?, writer: JsonWriter, fn: NestedNodeWriter): Unit = 25 | with(writer) { 26 | if (obj == null) { 27 | nullValue() 28 | } else { 29 | beginObject() 30 | fn.write(obj) 31 | endObject() 32 | } 33 | } 34 | 35 | fun JsonWriter.objW(obj: T?, fn: T.() -> Unit): Unit = objW(obj, this, fn) 36 | 37 | fun string(name: String, value: String?, writer: JsonWriter): Unit = writer.string(name, value) 38 | 39 | fun JsonWriter.string(name: String, value: String?) { 40 | name(name) 41 | value?.also(::value) ?: nullValue() 42 | } 43 | 44 | fun bool(name: String, value: Boolean?, writer: JsonWriter): Unit = 45 | with(writer) { 46 | name(name) 47 | value?.also(::value) ?: nullValue() 48 | } 49 | 50 | fun JsonWriter.bool(name: String, value: Boolean?) { 51 | name(name) 52 | value?.also(::value) ?: nullValue() 53 | } 54 | 55 | fun integer(name: String, value: Int?, writer: JsonWriter): Unit = writer.integer(name, value) 56 | 57 | fun JsonWriter.integer(name: String, value: Int?) { 58 | name(name) 59 | value?.also(::value) ?: nullValue() 60 | } 61 | 62 | fun doubl(name: String, value: Double?, writer: JsonWriter): Unit = writer.doubl(name, value) 63 | 64 | fun JsonWriter.doubl(name: String, value: Double?) { 65 | name(name) 66 | value?.also(::value) ?: nullValue() 67 | } 68 | 69 | fun lng(name: String, value: Long?, writer: JsonWriter): Unit = writer.lng(name, value) 70 | 71 | fun JsonWriter.lng(name: String, value: Long?) { 72 | name(name) 73 | value?.also(::value) ?: nullValue() 74 | } 75 | 76 | fun listW( 77 | name: String, 78 | list: List?, 79 | writer: JsonWriter, 80 | block: NestedNodeWriter, 81 | ): JsonWriter = 82 | with(writer) { 83 | name(name) 84 | listW(list, this, block) 85 | } 86 | 87 | fun listW(list: List?, writer: JsonWriter, fn: NestedNodeWriter): JsonWriter = 88 | with(writer) { 89 | when (list) { 90 | null -> nullValue() 91 | else -> { 92 | beginArray() 93 | list.forEach(fn::write) 94 | endArray() 95 | } 96 | } 97 | } 98 | 99 | fun JsonWriter.mapW(map: Map?, dynamicJsonAdapter: JsonAdapter) { 100 | map?.forEach { (key: String, value: Any?) -> 101 | name(key) 102 | dynamicJsonAdapter.toJson(this, value) 103 | } ?: nullValue() 104 | } 105 | 106 | // * NOTE 15 Mar 2025 gopala.akshintala: BeanUtils can read even the private fields with a getter 107 | fun JsonWriter.writeProps( 108 | pojoType: Class, 109 | bean: T, 110 | excludePropTypes: Set>, 111 | dynamicJsonAdapter: JsonAdapter, 112 | ) = 113 | BeanUtils.getPropertyDescriptors(pojoType) 114 | .asSequence() 115 | .filterNot { 116 | it.propertyType.name == "java.lang.Class" || excludePropTypes.contains(it.propertyType) 117 | } 118 | .forEach { 119 | checkNotNull(it.readMethod) { 120 | throw IllegalStateException("Please add a getter for the Property: `${it.name}`") 121 | } 122 | name(it.name) 123 | dynamicJsonAdapter.toJson(this, it.readMethod(bean)) 124 | } 125 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/adapters/salesforce/CompositeGraphRequest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.json.adapters.salesforce 9 | 10 | import com.squareup.moshi.JsonClass 11 | 12 | @JsonClass(generateAdapter = true) 13 | data class CompositeGraphRequest(val graphs: List) { 14 | @JsonClass(generateAdapter = true) 15 | data class Graph(val compositeRequest: List, val graphId: String) { 16 | @JsonClass(generateAdapter = true) 17 | data class CompositeRequest( 18 | val body: Map, 19 | val method: String, 20 | val referenceId: String, 21 | val url: String, 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/adapters/salesforce/CompositeGraphResponse.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.json.adapters.salesforce 9 | 10 | import com.salesforce.revoman.input.json.adapters.salesforce.CompositeGraphResponse.Graph.ErrorGraph 11 | import com.salesforce.revoman.input.json.adapters.salesforce.CompositeGraphResponse.Graph.ErrorGraph.ErrorGraphResponse.CompositeErrorResponse 12 | import com.salesforce.revoman.input.json.adapters.salesforce.CompositeGraphResponse.Graph.ErrorGraph.ErrorGraphResponse.CompositeErrorResponse.Body 13 | import com.salesforce.revoman.input.json.adapters.salesforce.CompositeGraphResponse.Graph.SuccessGraph 14 | import com.salesforce.revoman.input.json.factories.DiMorphicAdapter 15 | import com.squareup.moshi.Json 16 | import com.squareup.moshi.JsonClass 17 | 18 | @JsonClass(generateAdapter = true) 19 | data class CompositeGraphResponse(val graphs: List) { 20 | sealed interface Graph { 21 | val graphId: String 22 | val isSuccessful: Boolean 23 | 24 | @JsonClass(generateAdapter = true) 25 | data class SuccessGraph( 26 | override val graphId: String, 27 | val graphResponse: GraphResponse, 28 | override val isSuccessful: Boolean, 29 | ) : Graph { 30 | @JsonClass(generateAdapter = true) 31 | data class GraphResponse(val compositeResponse: List) { 32 | @JsonClass(generateAdapter = true) 33 | data class CompositeResponse( 34 | val body: Body, 35 | val httpHeaders: HttpHeaders, 36 | val httpStatusCode: Int, 37 | val referenceId: String, 38 | ) { 39 | @JsonClass(generateAdapter = true) 40 | data class Body(val errors: List, val id: String, val success: Boolean) 41 | 42 | @JsonClass(generateAdapter = true) 43 | data class HttpHeaders(@Json(name = "Location") val location: String) 44 | } 45 | } 46 | } 47 | 48 | @JsonClass(generateAdapter = true) 49 | data class ErrorGraph( 50 | override val graphId: String, 51 | val graphResponse: ErrorGraphResponse, 52 | override val isSuccessful: Boolean, 53 | ) : Graph { 54 | @Json(ignore = true) 55 | @get:JvmName("errorResponses") 56 | val errorResponses: List by lazy { 57 | graphResponse.compositeResponse.filter { 58 | it.httpStatusCode !in SUCCESSFUL_HTTP_STATUSES && 59 | it.body.firstOrNull()?.let { error -> 60 | error.errorCode == PROCESSING_HALTED || 61 | error.message == OPERATION_IN_TRANSACTION_FAILED_ERROR 62 | } != true 63 | } 64 | } 65 | 66 | @Json(ignore = true) 67 | @get:JvmName("firstErrorReferenceId") 68 | val firstErrorReferenceId: String? by lazy { errorResponses.firstOrNull()?.referenceId } 69 | 70 | @Json(ignore = true) 71 | @get:JvmName("firstErrorResponse") 72 | val firstErrorResponse: CompositeErrorResponse? by lazy { errorResponses.firstOrNull() } 73 | 74 | @Json(ignore = true) 75 | @get:JvmName("firstErrorResponseBody") 76 | val firstErrorResponseBody: Body? by lazy { 77 | errorResponses.firstOrNull()?.body?.firstOrNull() 78 | } 79 | 80 | @JsonClass(generateAdapter = true) 81 | data class ErrorGraphResponse(val compositeResponse: List) { 82 | @JsonClass(generateAdapter = true) 83 | data class CompositeErrorResponse( 84 | val body: List, 85 | val httpHeaders: HttpHeaders, 86 | val httpStatusCode: Int, 87 | val referenceId: String, 88 | ) { 89 | @JsonClass(generateAdapter = true) 90 | data class Body(val errorCode: String, val fields: List?, val message: String) 91 | 92 | @JsonClass(generateAdapter = true) class HttpHeaders 93 | } 94 | } 95 | } 96 | } 97 | 98 | companion object { 99 | @JvmField 100 | val ADAPTER = 101 | DiMorphicAdapter.of( 102 | Graph::class.java, 103 | "isSuccessful", 104 | { it.nextBoolean() }, 105 | SuccessGraph::class.java, 106 | ErrorGraph::class.java, 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/adapters/salesforce/Constants.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.json.adapters.salesforce 9 | 10 | internal val SUCCESSFUL_HTTP_STATUSES = 200..299 11 | internal const val PROCESSING_HALTED = "PROCESSING_HALTED" 12 | internal const val OPERATION_IN_TRANSACTION_FAILED_ERROR = 13 | "The transaction was rolled back since another operation in the same transaction failed." 14 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/input/json/factories/DiMorphicAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.json.factories 9 | 10 | import com.squareup.moshi.JsonAdapter 11 | import com.squareup.moshi.JsonDataException 12 | import com.squareup.moshi.JsonReader 13 | import com.squareup.moshi.JsonReader.Options 14 | import com.squareup.moshi.JsonWriter 15 | import com.squareup.moshi.Moshi 16 | import com.squareup.moshi.rawType 17 | import java.lang.reflect.Type 18 | 19 | class DiMorphicAdapter 20 | private constructor( 21 | private val labelKey: String, 22 | private val successAdapter: Triple<(JsonReader) -> Boolean, Type, JsonAdapter>, 23 | private val errorAdapter: Pair>, 24 | ) : JsonAdapter() { 25 | override fun fromJson(reader: JsonReader): Any? { 26 | val readerAtLabelKey = findLabelValue(reader.peekJson()) 27 | val jsonAdapter = 28 | if (readerAtLabelKey.use(successAdapter.first)) successAdapter.third else errorAdapter.second 29 | return jsonAdapter.fromJson(reader) 30 | } 31 | 32 | private fun findLabelValue(reader: JsonReader): JsonReader { 33 | reader.beginObject() 34 | while (reader.hasNext()) { 35 | if (reader.selectName(Options.of(labelKey)) == -1) { 36 | reader.skipName() 37 | reader.skipValue() 38 | } else { 39 | return reader 40 | } 41 | } 42 | throw JsonDataException("Missing label for $labelKey") 43 | } 44 | 45 | override fun toJson(writer: JsonWriter, value: Any?) { 46 | val type: Class<*> = value!!.javaClass 47 | when (type) { 48 | successAdapter.second -> successAdapter.third.toJson(writer, value) 49 | errorAdapter.first -> errorAdapter.second.toJson(writer, value) 50 | } 51 | } 52 | 53 | companion object { 54 | @JvmStatic 55 | fun of( 56 | baseType: Type, 57 | labelKey: String, 58 | successPredicate: (JsonReader) -> Boolean, 59 | successType: Type, 60 | errorType: Type, 61 | ): Factory = 62 | object : Factory { 63 | override fun create( 64 | type: Type, 65 | annotations: Set, 66 | moshi: Moshi, 67 | ): JsonAdapter<*>? { 68 | if (type.rawType != baseType || annotations.isNotEmpty()) { 69 | return null 70 | } 71 | return DiMorphicAdapter( 72 | labelKey, 73 | Triple(successPredicate, successType, moshi.adapter(successType)), 74 | errorType to moshi.adapter(errorType), 75 | ) 76 | .nullSafe() 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/exe/HttpRequest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.exe 9 | 10 | import arrow.core.Either 11 | import com.salesforce.revoman.internal.json.MoshiReVoman 12 | import com.salesforce.revoman.output.ExeType.HTTP_REQUEST 13 | import com.salesforce.revoman.output.report.Step 14 | import com.salesforce.revoman.output.report.TxnInfo 15 | import com.salesforce.revoman.output.report.failure.RequestFailure.HttpRequestFailure 16 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient 17 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder 18 | import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager 19 | import org.apache.hc.client5.http.socket.ConnectionSocketFactory 20 | import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory.INSTANCE 21 | import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory 22 | import org.apache.hc.core5.http.config.RegistryBuilder 23 | import org.apache.hc.core5.ssl.SSLContextBuilder 24 | import org.http4k.client.ApacheClient 25 | import org.http4k.core.HttpHandler 26 | import org.http4k.core.Request 27 | import org.http4k.core.Response 28 | import org.http4k.core.then 29 | import org.http4k.filter.DebuggingFilters 30 | 31 | @JvmSynthetic 32 | internal fun fireHttpRequest( 33 | currentStep: Step, 34 | httpRequest: Request, 35 | insecureHttp: Boolean, 36 | moshiReVoman: MoshiReVoman, 37 | ): Either> = 38 | runCatching(currentStep, HTTP_REQUEST) { 39 | // * NOTE gopala.akshintala 06/08/22: Preparing httpClient for each step, 40 | // * as there can be intermediate auths 41 | // ! TODO 29/01/24 gopala.akshintala: When would bearer token size be > 1? 42 | prepareHttpClient(insecureHttp)(httpRequest) 43 | } 44 | .mapLeft { HttpRequestFailure(it, TxnInfo(httpMsg = httpRequest, moshiReVoman = moshiReVoman)) } 45 | .map { TxnInfo(httpMsg = it, moshiReVoman = moshiReVoman) } 46 | 47 | private fun prepareHttpClient(insecureHttp: Boolean): HttpHandler = 48 | DebuggingFilters.PrintRequestAndResponse() 49 | .then(if (insecureHttp) ApacheClient(client = insecureApacheHttpClient()) else ApacheClient()) 50 | 51 | /** Only for Testing. DO NOT USE IN PROD */ 52 | private fun insecureApacheHttpClient(): CloseableHttpClient = 53 | SSLContextBuilder() 54 | .loadTrustMaterial(null) { _, _ -> true } 55 | .build() 56 | .run { 57 | HttpClientBuilder.create() 58 | .setConnectionManager( 59 | PoolingHttpClientConnectionManager( 60 | RegistryBuilder.create() 61 | .register("http", INSTANCE) 62 | .register("https", SSLConnectionSocketFactory(this) { _, _ -> true }) 63 | .build() 64 | ) 65 | ) 66 | .build() 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/exe/PmJsEval.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.exe 9 | 10 | import arrow.core.Either 11 | import arrow.core.Either.Right 12 | import com.salesforce.revoman.internal.postman.PostmanSDK 13 | import com.salesforce.revoman.internal.postman.template.Item 14 | import com.salesforce.revoman.internal.postman.template.Request 15 | import com.salesforce.revoman.output.ExeType.POST_RES_JS 16 | import com.salesforce.revoman.output.ExeType.PRE_REQ_JS 17 | import com.salesforce.revoman.output.report.Step 18 | import com.salesforce.revoman.output.report.StepReport 19 | import com.salesforce.revoman.output.report.failure.RequestFailure.PreReqJSFailure 20 | import com.salesforce.revoman.output.report.failure.ResponseFailure.PostResJSFailure 21 | 22 | @JvmSynthetic 23 | internal fun executePreReqJS( 24 | currentStep: Step, 25 | itemWithRegex: Item, 26 | pm: PostmanSDK, 27 | ): Either { 28 | val preReqJS = 29 | itemWithRegex.event?.find { it.listen == "prerequest" }?.script?.exec?.joinToString("\n") 30 | return if (!preReqJS.isNullOrBlank()) { 31 | runCatching(currentStep, PRE_REQ_JS) { 32 | executePreReqJSWithPolyglot(preReqJS, itemWithRegex.request, pm) 33 | } 34 | .mapLeft { PreReqJSFailure(it, pm.currentStepReport.requestInfo!!.get()) } 35 | } else { 36 | Right(Unit) 37 | } 38 | } 39 | 40 | private fun executePreReqJSWithPolyglot(preReqJS: String, pmRequest: Request, pm: PostmanSDK) { 41 | pm.request = pm.from(pmRequest) 42 | pm.evaluateJS(preReqJS) 43 | } 44 | 45 | @JvmSynthetic 46 | internal fun executePostResJS( 47 | currentStep: Step, 48 | item: Item, 49 | pm: PostmanSDK, 50 | ): Either { 51 | val postResJs = item.event?.find { it.listen == "test" }?.script?.exec?.joinToString("\n") 52 | return if (!postResJs.isNullOrBlank()) { 53 | runCatching(currentStep, POST_RES_JS) { 54 | executePostResJSWithPolyglot(postResJs, item.request, pm.currentStepReport, pm) 55 | } 56 | .mapLeft { 57 | PostResJSFailure( 58 | it, 59 | pm.currentStepReport.requestInfo!!.get(), 60 | pm.currentStepReport.responseInfo!!.get(), 61 | ) 62 | } 63 | } else { 64 | Right(Unit) 65 | } 66 | } 67 | 68 | private fun executePostResJSWithPolyglot( 69 | postResJS: String, 70 | pmRequest: Request, 71 | stepReport: StepReport, 72 | pm: PostmanSDK, 73 | ) { 74 | val httpResponse = stepReport.responseInfo!!.get().httpMsg 75 | pm.setRequestAndResponse(pm.from(pmRequest), httpResponse) 76 | pm.evaluateJS(postResJS, mapOf("responseBody" to httpResponse.bodyString())) 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/exe/PostStepHook.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.exe 9 | 10 | import com.salesforce.revoman.input.config.HookConfig 11 | import com.salesforce.revoman.input.config.HookConfig.StepHook.PostStepHook 12 | import com.salesforce.revoman.input.config.Kick 13 | import com.salesforce.revoman.input.config.StepPick.PostTxnStepPick 14 | import com.salesforce.revoman.internal.postman.PostmanSDK 15 | import com.salesforce.revoman.output.ExeType.POST_STEP_HOOK 16 | import com.salesforce.revoman.output.Rundown 17 | import com.salesforce.revoman.output.report.StepReport 18 | import com.salesforce.revoman.output.report.failure.HookFailure.PostStepHookFailure 19 | import io.github.oshai.kotlinlogging.KotlinLogging 20 | 21 | @JvmSynthetic 22 | internal fun postStepHookExe(kick: Kick, pm: PostmanSDK): PostStepHookFailure? = 23 | pickPostStepHooks(kick.postStepHooks(), pm.currentStepReport, pm.rundown) 24 | .map { postStepHook -> 25 | runCatching(pm.currentStepReport.step, POST_STEP_HOOK) { 26 | postStepHook.accept(pm.currentStepReport, pm.rundown) 27 | } 28 | .mapLeft { 29 | PostStepHookFailure( 30 | it, 31 | pm.currentStepReport.requestInfo!!.get(), 32 | pm.currentStepReport.responseInfo!!.get(), 33 | ) 34 | } 35 | } 36 | .firstOrNull { it.isLeft() } 37 | ?.leftOrNull() 38 | 39 | private fun pickPostStepHooks( 40 | postStepHooks: List, 41 | currentStepReport: StepReport, 42 | rundown: Rundown, 43 | ): Sequence = 44 | postStepHooks 45 | .asSequence() 46 | .filter { (it.pick as PostTxnStepPick).pick(currentStepReport, rundown) } 47 | .map { it.stepHook as PostStepHook } 48 | .also { 49 | if (it.iterator().hasNext()) { 50 | logger.info { "${currentStepReport.step} Picked Post hook count : ${it.count()}" } 51 | currentStepReport.step.postStepHookCount = it.count() 52 | } 53 | } 54 | 55 | private val logger = KotlinLogging.logger {} 56 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/exe/PreStepHook.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.exe 9 | 10 | import com.salesforce.revoman.input.config.HookConfig 11 | import com.salesforce.revoman.input.config.HookConfig.StepHook.PreStepHook 12 | import com.salesforce.revoman.input.config.Kick 13 | import com.salesforce.revoman.input.config.StepPick.PreTxnStepPick 14 | import com.salesforce.revoman.internal.postman.PostmanSDK 15 | import com.salesforce.revoman.output.ExeType.PRE_STEP_HOOK 16 | import com.salesforce.revoman.output.Rundown 17 | import com.salesforce.revoman.output.report.Step 18 | import com.salesforce.revoman.output.report.TxnInfo 19 | import com.salesforce.revoman.output.report.failure.HookFailure.PreStepHookFailure 20 | import io.github.oshai.kotlinlogging.KotlinLogging 21 | import org.http4k.core.Request 22 | 23 | @JvmSynthetic 24 | internal fun preStepHookExe( 25 | currentStep: Step, 26 | kick: Kick, 27 | requestInfo: TxnInfo, 28 | pm: PostmanSDK, 29 | ): PreStepHookFailure? = 30 | pickPreStepHooks(kick.preStepHooks(), currentStep, requestInfo, pm.rundown) 31 | .map { preStepHook -> 32 | runCatching(currentStep, PRE_STEP_HOOK) { 33 | preStepHook.accept(currentStep, requestInfo, pm.rundown) 34 | } 35 | .mapLeft { PreStepHookFailure(it, requestInfo) } 36 | } 37 | .firstOrNull { it.isLeft() } 38 | ?.leftOrNull() 39 | 40 | @JvmSynthetic 41 | private fun pickPreStepHooks( 42 | preStepHooks: List, 43 | currentStep: Step, 44 | requestInfo: TxnInfo, 45 | rundown: Rundown, 46 | ): Sequence = 47 | preStepHooks 48 | .asSequence() 49 | .filter { (it.pick as PreTxnStepPick).pick(currentStep, requestInfo, rundown) } 50 | .map { it.stepHook as PreStepHook } 51 | .also { 52 | if (it.iterator().hasNext()) { 53 | logger.info { "$currentStep Picked Pre hook count : ${it.count()}" } 54 | currentStep.preStepHookCount = it.count() 55 | } 56 | } 57 | 58 | private val logger = KotlinLogging.logger {} 59 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/exe/UnmarshallRequest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.exe 9 | 10 | import arrow.core.Either 11 | import arrow.core.Either.Right 12 | import com.salesforce.revoman.input.config.Kick 13 | import com.salesforce.revoman.internal.json.MoshiReVoman 14 | import com.salesforce.revoman.internal.postman.PostmanSDK 15 | import com.salesforce.revoman.output.ExeType.UNMARSHALL_REQUEST 16 | import com.salesforce.revoman.output.report.Step 17 | import com.salesforce.revoman.output.report.TxnInfo 18 | import com.salesforce.revoman.output.report.failure.RequestFailure.UnmarshallRequestFailure 19 | import io.exoquery.pprint 20 | import io.github.oshai.kotlinlogging.KotlinLogging 21 | import java.lang.reflect.Type 22 | import org.http4k.core.ContentType.Companion.APPLICATION_JSON 23 | import org.http4k.core.Request 24 | import org.http4k.lens.contentType 25 | 26 | @JvmSynthetic 27 | internal fun unmarshallRequest( 28 | currentStep: Step, 29 | pmRequest: com.salesforce.revoman.internal.postman.template.Request, 30 | kick: Kick, 31 | moshiReVoman: MoshiReVoman, 32 | pm: PostmanSDK, 33 | ): Either> { 34 | val httpRequest = pmRequest.toHttpRequest(moshiReVoman) 35 | return when { 36 | httpRequest.bodyString().isNotBlank() && 37 | APPLICATION_JSON.value.equals(httpRequest.contentType()?.value, true) -> { 38 | val requestType: Type = 39 | kick 40 | .requestConfig() 41 | .firstOrNull { 42 | it.preTxnStepPick.pick( 43 | currentStep, 44 | TxnInfo(httpMsg = httpRequest, moshiReVoman = moshiReVoman), 45 | pm.rundown, 46 | ) 47 | } 48 | ?.also { logger.info { "$currentStep RequestConfig found : ${pprint(it)}" } } 49 | ?.requestType ?: Any::class.java 50 | runCatching(currentStep, UNMARSHALL_REQUEST) { 51 | pmRequest.body?.let { body -> moshiReVoman.fromJson(body.raw, requestType) } 52 | } 53 | .mapLeft { 54 | UnmarshallRequestFailure( 55 | it, 56 | TxnInfo(txnObjType = requestType, httpMsg = httpRequest, moshiReVoman = moshiReVoman), 57 | ) 58 | } 59 | .map { 60 | TxnInfo( 61 | txnObjType = requestType, 62 | txnObj = it, 63 | httpMsg = httpRequest, 64 | moshiReVoman = moshiReVoman, 65 | ) 66 | } 67 | } 68 | else -> { 69 | // ! TODO 15/10/23 gopala.akshintala: xml2Json 70 | logger.info { 71 | "$currentStep Blank Request body or content-type ${httpRequest.contentType()?.value} didn't match ${APPLICATION_JSON.value}" 72 | } 73 | Right(TxnInfo(isJson = false, httpMsg = httpRequest, moshiReVoman = moshiReVoman)) 74 | } 75 | } 76 | } 77 | 78 | private val logger = KotlinLogging.logger {} 79 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/exe/UnmarshallResponse.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.exe 9 | 10 | import arrow.core.Either 11 | import arrow.core.Either.Right 12 | import com.salesforce.revoman.input.config.Kick 13 | import com.salesforce.revoman.internal.json.MoshiReVoman 14 | import com.salesforce.revoman.internal.postman.PostmanSDK 15 | import com.salesforce.revoman.output.ExeType.UNMARSHALL_RESPONSE 16 | import com.salesforce.revoman.output.report.TxnInfo 17 | import com.salesforce.revoman.output.report.failure.ResponseFailure.UnmarshallResponseFailure 18 | import io.exoquery.pprint 19 | import io.github.oshai.kotlinlogging.KotlinLogging 20 | import java.lang.reflect.Type 21 | import org.http4k.core.ContentType.Companion.APPLICATION_JSON 22 | import org.http4k.core.Response 23 | import org.http4k.lens.contentType 24 | 25 | @JvmSynthetic 26 | internal fun unmarshallResponse( 27 | kick: Kick, 28 | moshiReVoman: MoshiReVoman, 29 | pm: PostmanSDK, 30 | ): Either> { 31 | val httpResponse = pm.currentStepReport.responseInfo!!.get().httpMsg 32 | return when { 33 | httpResponse.bodyString().isNotBlank() && 34 | APPLICATION_JSON.value.equals(httpResponse.contentType()?.value, true) -> { 35 | val httpStatus = httpResponse.status.successful 36 | val responseConfig = 37 | (kick.pickToResponseConfig()[httpStatus].orEmpty() + 38 | kick.pickToResponseConfig()[null].orEmpty()) 39 | .let { 40 | it.firstOrNull { pick -> pick.postTxnStepPick.pick(pm.currentStepReport, pm.rundown) } 41 | } 42 | val currentStep = pm.currentStepReport.step 43 | val responseType: Type = 44 | responseConfig 45 | ?.also { logger.info { "$currentStep ResponseConfig found : ${pprint(it)}" } } 46 | ?.responseType ?: Any::class.java 47 | val requestInfo = pm.currentStepReport.requestInfo!!.get() 48 | runCatching(currentStep, UNMARSHALL_RESPONSE) { 49 | moshiReVoman.fromJson(httpResponse.bodyString(), responseType) 50 | } 51 | .mapLeft { 52 | UnmarshallResponseFailure( 53 | it, 54 | requestInfo, 55 | TxnInfo(txnObjType = responseType, httpMsg = httpResponse, moshiReVoman = moshiReVoman), 56 | ) 57 | } 58 | .map { 59 | TxnInfo( 60 | txnObjType = responseType, 61 | txnObj = it, 62 | httpMsg = httpResponse, 63 | moshiReVoman = moshiReVoman, 64 | ) 65 | } 66 | } 67 | else -> { 68 | logger.info { 69 | "${pm.currentStepReport.step} Blank Response body or ${httpResponse.contentType()?.value} didn't match ${APPLICATION_JSON.value}" 70 | } 71 | Right(TxnInfo(isJson = false, httpMsg = httpResponse, moshiReVoman = moshiReVoman)) 72 | } 73 | } 74 | } 75 | 76 | private val logger = KotlinLogging.logger {} 77 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/adapters/BigDecimalAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.adapters 9 | 10 | import com.squareup.moshi.FromJson 11 | import com.squareup.moshi.ToJson 12 | import java.math.BigDecimal 13 | 14 | object BigDecimalAdapter { 15 | @ToJson fun toJson(value: BigDecimal) = value.toDouble() 16 | 17 | @FromJson fun fromJson(string: String) = BigDecimal(string) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/adapters/EpochAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.adapters 9 | 10 | import com.squareup.moshi.FromJson 11 | import com.squareup.moshi.JsonAdapter 12 | import com.squareup.moshi.JsonReader 13 | import java.util.Date 14 | import kotlinx.datetime.Instant 15 | import kotlinx.datetime.toJavaInstant 16 | 17 | object EpochAdapter { 18 | @FromJson 19 | fun fromJson(reader: JsonReader, delegate: JsonAdapter): Date? { 20 | val epoch = reader.nextString() 21 | return if (epoch.matches("\\d+".toRegex())) { 22 | Date.from(Instant.fromEpochMilliseconds(epoch.toLong()).toJavaInstant()) 23 | } else { 24 | delegate.fromJsonValue(epoch) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/adapters/TypeAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.adapters 9 | 10 | import com.squareup.moshi.FromJson 11 | import com.squareup.moshi.ToJson 12 | import java.lang.reflect.Type 13 | 14 | object TypeAdapter { 15 | @ToJson fun toJson(type: Type): String = type.toString() 16 | 17 | @FromJson fun fromJson(ignore: String): Type? = null 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/adapters/UUIDAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.adapters 9 | 10 | import com.squareup.moshi.FromJson 11 | import com.squareup.moshi.ToJson 12 | import java.util.UUID 13 | 14 | object UUIDAdapter { 15 | @ToJson fun toJson(uuid: UUID): String = uuid.toString() 16 | 17 | @FromJson fun fromJson(uuidStr: String): UUID = UUID.fromString(uuidStr) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/factories/AlwaysSerializeNullsFactory.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.factories 9 | 10 | import com.salesforce.revoman.internal.json.AlwaysSerializeNulls 11 | import com.squareup.moshi.JsonAdapter 12 | import com.squareup.moshi.Moshi 13 | import com.squareup.moshi.rawType 14 | import java.lang.reflect.Type 15 | 16 | internal class AlwaysSerializeNullsFactory : JsonAdapter.Factory { 17 | override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { 18 | val rawType: Class<*> = type.rawType 19 | if (!rawType.isAnnotationPresent(AlwaysSerializeNulls::class.java)) { 20 | return null 21 | } 22 | val delegate: JsonAdapter = moshi.nextAdapter(this, type, annotations) 23 | return delegate.serializeNulls() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/factories/CaseInsensitiveEnumAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.factories 9 | 10 | import com.squareup.moshi.JsonAdapter 11 | import com.squareup.moshi.JsonDataException 12 | import com.squareup.moshi.JsonReader 13 | import com.squareup.moshi.JsonWriter 14 | import com.squareup.moshi.Moshi 15 | import com.squareup.moshi.internal.Util 16 | import com.squareup.moshi.rawType 17 | import java.lang.reflect.Type 18 | 19 | /** 20 | * ************************************************************************************************ 21 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 22 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 23 | * http://www.apache.org/licenses/LICENSE-2.0 24 | * ************************************************************************************************ 25 | */ 26 | /** 27 | * ************************************************************************************************ 28 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 29 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 30 | * http://www.apache.org/licenses/LICENSE-2.0 31 | * ************************************************************************************************ 32 | */ 33 | internal class CaseInsensitiveEnumAdapter>(private val enumType: Class) : 34 | JsonAdapter() { 35 | private val nameStrings = 36 | enumType.getEnumConstants().map { Util.jsonName(it.name, enumType.getField(it.name)) } 37 | private val options = JsonReader.Options.of(*nameStrings.toTypedArray()) 38 | 39 | override fun fromJson(reader: JsonReader): T { 40 | val index = reader.selectString(options) 41 | return if (index != -1) { 42 | enumType.getEnumConstants()[index] 43 | } else if (reader.peek() != JsonReader.Token.STRING) { 44 | throw JsonDataException("Expected a string but was ${reader.peek()} at path ${reader.path}") 45 | } else { 46 | val value = reader.nextString() 47 | enumType.enumConstants.firstOrNull { it.name.compareTo(value, ignoreCase = true) == 0 } 48 | ?: throw JsonDataException( 49 | "Expected one of $nameStrings but was $value at path ${reader.path}" 50 | ) 51 | } 52 | } 53 | 54 | override fun toJson(writer: JsonWriter, value: T?) { 55 | value?.also { writer.value(nameStrings[it.ordinal]) } ?: writer.nullValue() 56 | } 57 | 58 | companion object { 59 | @JvmField 60 | val FACTORY = 61 | object : Factory { 62 | override fun create( 63 | type: Type, 64 | annotations: Set, 65 | moshi: Moshi, 66 | ): JsonAdapter<*>? { 67 | val rawType: Class<*> = type.rawType 68 | if (!rawType.isEnum) { 69 | return null 70 | } 71 | return CaseInsensitiveEnumAdapter(rawType as Class>) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/json/factories/IgnoreTypesFactory.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.json.factories 9 | 10 | import com.squareup.moshi.JsonAdapter 11 | import com.squareup.moshi.JsonAdapter.Factory 12 | import com.squareup.moshi.JsonReader 13 | import com.squareup.moshi.JsonWriter 14 | import com.squareup.moshi.Moshi 15 | import java.lang.reflect.Type 16 | 17 | internal class IgnoreTypesFactory(private val typesToIgnore: Set) : Factory { 18 | override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*>? { 19 | return if (typesToIgnore.contains(type)) { 20 | object : JsonAdapter() { 21 | override fun fromJson(reader: JsonReader): Type? { 22 | reader.skipValue() 23 | return null 24 | } 25 | 26 | override fun toJson(writer: JsonWriter, value: Type?) { 27 | writer.nullValue() 28 | } 29 | } 30 | } else null 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/postman/DynamicVariableGenerator.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.postman 9 | 10 | import io.github.serpro69.kfaker.faker 11 | import java.time.LocalDate 12 | import java.util.* 13 | import kotlin.random.Random.Default.nextBoolean 14 | import kotlin.random.Random.Default.nextInt 15 | import kotlin.random.Random.Default.nextLong 16 | import kotlinx.datetime.Clock 17 | 18 | private val faker = faker {} 19 | 20 | /** 21 | * @see Postman 23 | * Variables 24 | * 25 | * This may not be an exhaustive list of all dynamic variables supported by Postman. We keep 26 | * adding on the need-basis so it will grow over time. If what is need is not present here, You 27 | * may either contribute or use @see Custom Dynamic 29 | * Variables 30 | */ 31 | private val dynamicVariableGenerators: Map String> = 32 | mapOf( 33 | // Common 34 | $$"$guid" to { UUID.randomUUID().toString() }, 35 | $$"$timestamp" to { Clock.System.now().epochSeconds.toString() }, 36 | $$"$isoTimestamp" to { Clock.System.now().toString() }, 37 | $$"$randomUUID" to { UUID.randomUUID().toString() }, 38 | // Text, numbers, and colors 39 | $$"$randomAlphaNumeric" to { randomAlphanumeric(1) }, 40 | $$"$randomBoolean" to { nextBoolean().toString() }, 41 | $$"$randomInt" to { nextInt(0, Int.MAX_VALUE).toString() }, 42 | $$"$randomColor" to faker.color::name, 43 | $$"$randomHexColor" to { "#${getRandomHex()}${getRandomHex()}${getRandomHex()}" }, 44 | // Internet and IP addresses 45 | $$"$randomIP" to faker.internet::iPv4Address, 46 | $$"$randomIPV6" to faker.internet::iPv6Address, 47 | $$"$randomMACAddress" to { faker.internet.macAddress() }, 48 | $$"$randomPassword" to { randomAlphanumeric(15) }, 49 | // Names 50 | $$"$randomFirstName" to faker.name::firstName, 51 | $$"$randomLastName" to faker.name::lastName, 52 | $$"$randomUserName" to { faker.name.firstName() + faker.name.lastName() }, 53 | // Phone, address, and location 54 | $$"$randomCity" to faker.address::city, 55 | // Grammar 56 | $$"$randomAdjective" to faker.adjective::positive, 57 | $$"$randomWord" to faker.lorem::words, 58 | // Business 59 | $$"$randomCompanyName" to faker.company::name, 60 | $$"$randomProduct" to faker.industrySegments::sector, 61 | // Domains, emails, and usernames 62 | $$"$randomEmail" to { faker.internet.email() }, 63 | // Date time 64 | $$"$currentDate" to { LocalDate.now().toString() }, 65 | $$"$randomFutureDate" to 66 | { 67 | LocalDate.now().let { it.plusDays(nextLong(1, it.lengthOfYear().toLong())).toString() } 68 | }, 69 | ) 70 | 71 | fun getRandomHex() = nextInt(255).toString(16).uppercase() 72 | 73 | private val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9') 74 | 75 | fun randomAlphanumeric(length: Int) = 76 | (1..length).map { charPool[nextInt(0, charPool.size)] }.joinToString("") 77 | 78 | private val dynamicVariableGeneratorsWithPM: Map String> = 79 | mapOf($$"$currentRequestName" to { it.info.requestName }) 80 | 81 | internal fun dynamicVariableGenerator(key: String, pm: PostmanSDK): String? = 82 | dynamicVariableGenerators[key]?.invoke() ?: dynamicVariableGeneratorsWithPM[key]?.invoke(pm) 83 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/postman/RegexReplacer.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.postman 9 | 10 | import com.salesforce.revoman.input.config.CustomDynamicVariableGenerator 11 | import com.salesforce.revoman.internal.postman.template.Auth.Bearer 12 | import com.salesforce.revoman.internal.postman.template.Item 13 | import com.salesforce.revoman.internal.postman.template.Request 14 | 15 | private const val VARIABLE_KEY = "variableKey" 16 | private val postManVariableRegex = "\\{\\{(?<$VARIABLE_KEY>[^{}]*?)}}".toRegex() 17 | 18 | class RegexReplacer( 19 | private val customDynamicVariableGenerators: Map = 20 | emptyMap(), 21 | private val dynamicVariableGenerator: (String, PostmanSDK) -> String? = ::dynamicVariableGenerator, 22 | ) { 23 | /** 24 | * ## Order of Variable resolution 25 | * - Custom Dynamic Variables 26 | * - Dynamic variables 27 | * - Environment built during execution 28 | * - Dynamic Environment supplied through config 29 | * - Postman Environment supplied as a file through config 30 | */ 31 | internal fun replaceVariablesRecursively(stringWithRegex: String?, pm: PostmanSDK): String? = 32 | stringWithRegex?.let { 33 | postManVariableRegex.replace(it) { matchResult -> 34 | val variableKey = matchResult.groups[VARIABLE_KEY]?.value!! 35 | customDynamicVariableGenerators[variableKey] 36 | ?.let { cdvg -> 37 | replaceVariablesRecursively( 38 | cdvg.generate(variableKey, pm.currentStepReport, pm.rundown), 39 | pm, 40 | ) 41 | } 42 | ?.also { value -> pm.environment[variableKey] = value } 43 | ?: replaceVariablesRecursively(dynamicVariableGenerator(variableKey, pm), pm)?.also { 44 | value -> 45 | pm.environment[variableKey] = value 46 | } 47 | ?: (if (pm.environment[variableKey] is String) 48 | replaceVariablesRecursively(pm.environment[variableKey] as String, pm) 49 | else pm.environment[variableKey].toString()) 50 | ?.also { value -> pm.environment[variableKey] = value } 51 | ?: matchResult.value 52 | } 53 | } 54 | 55 | internal fun replaceVariablesInPmItem(item: Item, pm: PostmanSDK): Item = 56 | item.copy(request = replaceVariablesInRequestRecursively(item.request, pm)) 57 | 58 | private fun replaceVariablesInBearer(bearer: Bearer?, pm: PostmanSDK): Bearer? = 59 | bearer?.copy(value = replaceVariablesRecursively(bearer.value, pm)!!) 60 | 61 | internal fun replaceVariablesInRequestRecursively(request: Request, pm: PostmanSDK): Request = 62 | request.copy( 63 | auth = 64 | request.auth?.copy( 65 | bearer = listOfNotNull(replaceVariablesInBearer(request.auth.bearer.firstOrNull(), pm)) 66 | ), 67 | header = 68 | request.header.map { header -> 69 | header.copy( 70 | key = replaceVariablesRecursively(header.key, pm) ?: header.key, 71 | value = replaceVariablesRecursively(header.value, pm) ?: header.value, 72 | ) 73 | }, 74 | url = 75 | request.url.copy(raw = replaceVariablesRecursively(request.url.raw, pm) ?: request.url.raw), 76 | body = 77 | request.body?.copy( 78 | raw = replaceVariablesRecursively(request.body.raw, pm) ?: request.body.raw 79 | ), 80 | ) 81 | 82 | internal fun replaceVariablesInEnv(pm: PostmanSDK): Map = 83 | pm.environment 84 | .toMap() 85 | .entries 86 | .associateBy( 87 | { replaceVariablesRecursively(it.key, pm)!! }, 88 | { 89 | if (it.value is String?) replaceVariablesRecursively(it.value as String?, pm) 90 | else it.value 91 | }, 92 | ) 93 | } 94 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/postman/template/Auth.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.postman.template 9 | 10 | import com.squareup.moshi.JsonClass 11 | 12 | @JsonClass(generateAdapter = true) 13 | data class Auth(val bearer: List, val type: String) { 14 | // ! TODO 09/01/24 gopala.akshintala: Support other ways to authorize 15 | @JsonClass(generateAdapter = true) 16 | data class Bearer(val key: String, val type: String, val value: String) 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/postman/template/Environment.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.postman.template 9 | 10 | import com.salesforce.revoman.input.bufferFile 11 | import com.salesforce.revoman.input.bufferInputStream 12 | import com.salesforce.revoman.internal.json.AlwaysSerializeNulls 13 | import com.salesforce.revoman.internal.json.MoshiReVoman 14 | import com.squareup.moshi.JsonClass 15 | import com.squareup.moshi.Moshi 16 | import com.squareup.moshi.adapter 17 | import java.io.InputStream 18 | import kotlin.collections.plus 19 | 20 | @JsonClass(generateAdapter = true) 21 | internal data class Environment(val name: String?, val values: List) { 22 | @AlwaysSerializeNulls 23 | @JsonClass(generateAdapter = true) 24 | internal data class EnvValue(val key: String, val value: String?, val enabled: Boolean) 25 | 26 | companion object { 27 | const val POSTMAN_ENV_NAME = "env-from-revoman" 28 | 29 | fun fromMap(envMap: Map, moshiReVoman: MoshiReVoman): Environment { 30 | val values = 31 | envMap.entries.map { (key, value) -> 32 | val valueStr = 33 | when (value) { 34 | is String -> value 35 | // * NOTE 08 Mar 2025 gopala.akshintala: To be consistent with Postman app behavior 36 | null -> "null" 37 | else -> moshiReVoman.toJson(value) 38 | } 39 | EnvValue(key, valueStr, true) 40 | } 41 | return Environment(POSTMAN_ENV_NAME, values) 42 | } 43 | 44 | @OptIn(ExperimentalStdlibApi::class) 45 | internal fun mergeEnvs( 46 | pmEnvironmentPaths: Set, 47 | pmEnvironmentInputStreams: List, 48 | dynamicEnvironment: Map, 49 | ): Map { 50 | val envAdapter = Moshi.Builder().build().adapter() 51 | val envFromEnvFiles = 52 | (pmEnvironmentPaths.map { bufferFile(it) } + 53 | pmEnvironmentInputStreams.map { bufferInputStream(it) }) 54 | .flatMap { envWithRegex -> 55 | envAdapter.fromJson(envWithRegex)?.values?.filter { it.enabled } ?: emptyList() 56 | } 57 | .associate { it.key to it.value } 58 | // * NOTE 10/09/23 gopala.akshintala: dynamicEnvironment keys replace envFromEnvFiles when 59 | // clashed 60 | return envFromEnvFiles + dynamicEnvironment 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/internal/postman/template/Template.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal.postman.template 9 | 10 | import com.salesforce.revoman.internal.json.MoshiReVoman 11 | import com.squareup.moshi.JsonClass 12 | import io.github.oshai.kotlinlogging.KotlinLogging 13 | import java.util.regex.Pattern 14 | import org.http4k.core.ContentType.Companion.APPLICATION_JSON 15 | import org.http4k.core.ContentType.Companion.Text 16 | import org.http4k.core.Method 17 | import org.http4k.core.Uri 18 | import org.http4k.core.queryParametersEncoded 19 | import org.http4k.core.with 20 | import org.http4k.lens.Header.CONTENT_TYPE 21 | import org.http4k.lens.bearerAuth 22 | 23 | @JsonClass(generateAdapter = true) 24 | internal data class Template(val item: List, val auth: Auth?) 25 | 26 | @JsonClass(generateAdapter = true) 27 | data class Item( 28 | val name: String = "", 29 | val item: List? = null, 30 | val request: Request = Request(), 31 | val event: List? = null, 32 | ) { 33 | @JvmField val httpMethod = request.method 34 | } 35 | 36 | @JsonClass(generateAdapter = true) 37 | data class Event(val listen: String, val script: Script) { 38 | @JsonClass(generateAdapter = true) data class Script(val exec: List) 39 | } 40 | 41 | @JsonClass(generateAdapter = true) data class Header(val key: String, val value: String) 42 | 43 | @JsonClass(generateAdapter = true) data class Url(val raw: String = "") 44 | 45 | @JsonClass(generateAdapter = true) data class Body(val mode: String, val raw: String) 46 | 47 | @JsonClass(generateAdapter = true) 48 | data class Request( 49 | @JvmField val auth: Auth? = null, 50 | @JvmField val method: String = "", 51 | @JvmField val header: List
= emptyList(), 52 | @JvmField val url: Url = Url(), 53 | @JvmField val body: Body? = null, 54 | @JvmField val event: List? = null, 55 | ) { 56 | internal fun toHttpRequest(moshiReVoman: MoshiReVoman?): org.http4k.core.Request { 57 | val uri = Uri.of(url.raw.trim()).queryParametersEncoded() 58 | var contentTypeHeader = 59 | header.firstOrNull { CONTENT_TYPE.meta.name.equals(it.key, true) }?.value?.let { Text(it) } 60 | val cleansedRawBody = 61 | body?.raw?.trim()?.let { 62 | when { 63 | it.isBlank() -> it 64 | else -> 65 | // ! TODO 15 Mar 2025 gopala.akshintala: Detect the right content type if absent 66 | when { 67 | contentTypeHeader?.value == null || 68 | (APPLICATION_JSON.value.equals(contentTypeHeader.value, true) && 69 | containsComments(it)) -> { 70 | runCatching { moshiReVoman?.jsonToObjToPrettyJson(it, true) ?: it } 71 | .onSuccess { 72 | if (contentTypeHeader == null) { 73 | logger.info { 74 | "Detected JSON Content type, adding $APPLICATION_JSON as content-type Header" 75 | } 76 | contentTypeHeader = APPLICATION_JSON 77 | } 78 | } 79 | .getOrDefault(it) 80 | } 81 | else -> it 82 | } 83 | } 84 | } ?: "" 85 | val request = 86 | org.http4k.core 87 | .Request(Method.valueOf(method), uri) 88 | .headers(header.map { it.key.trim() to it.value.trim() }) 89 | .body(cleansedRawBody) 90 | val requestWithAuth = 91 | auth?.bearer?.firstOrNull()?.value?.let { 92 | // * NOTE 11 May 2025 gopala.akshintala: Auth in headers will be overridden by Auth from 93 | // request, to align with Postman app behavior 94 | request.removeHeader("Authorization").bearerAuth(it) 95 | } ?: request 96 | return contentTypeHeader?.let { requestWithAuth.with(CONTENT_TYPE of it) } ?: requestWithAuth 97 | } 98 | 99 | companion object { 100 | private val COMMENT_PATTERN = Pattern.compile("//.*|/\\*[\\s\\S]*?\\*/") 101 | 102 | // * NOTE 24 Mar 2025 gopala.akshintala: This may give false positives when a string like 103 | // "https://..." is present in JSON key or value 104 | fun containsComments(str: String): Boolean = 105 | COMMENT_PATTERN.matcher(str).find().also { 106 | if (it) logger.info { "String may contain Comments" } 107 | } 108 | } 109 | } 110 | 111 | private val logger = KotlinLogging.logger {} 112 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/ExeType.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output 9 | 10 | enum class ExeType(private val exeName: String) { 11 | UNMARSHALL_REQUEST("unmarshall-request"), 12 | PRE_STEP_HOOK("pre-step-hook"), 13 | PRE_REQ_JS("pre-req-js"), 14 | HTTP_REQUEST("http-request"), 15 | HTTP_STATUS("http-status"), 16 | POST_RES_JS("post-res-js"), 17 | UNMARSHALL_RESPONSE("unmarshall-response"), 18 | POST_STEP_HOOK("post-step-hook"); 19 | 20 | override fun toString(): String { 21 | return exeName 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/Rundown.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output 9 | 10 | import com.salesforce.revoman.input.config.StepPick.PostTxnStepPick 11 | import com.salesforce.revoman.output.postman.PostmanEnvironment 12 | import com.salesforce.revoman.output.report.Folder.Companion.FOLDER_DELIMITER 13 | import com.salesforce.revoman.output.report.StepReport 14 | 15 | data class Rundown( 16 | @JvmField val stepReports: List = emptyList(), 17 | @JvmField val mutableEnv: PostmanEnvironment, 18 | private val haltOnFailureOfTypeExcept: Map, 19 | @JvmField val providedStepsToExecuteCount: Int, 20 | ) { 21 | @get:JvmName("immutableEnv") val immutableEnv: Map by lazy { mutableEnv.toMap() } 22 | 23 | @get:JvmName("executedStepCount") val executedStepCount: Int by lazy { stepReports.size } 24 | 25 | @get:JvmName("httpFailureStepCount") 26 | val httpFailureStepCount: Int by lazy { stepReports.count { !it.isHttpStatusSuccessful } } 27 | 28 | @get:JvmName("unsuccessfulStepCount") 29 | val unsuccessfulStepCount: Int by lazy { stepReports.count { !it.isSuccessful } } 30 | 31 | @get:JvmName("executionFailureStepCount") 32 | val executionFailureStepCount: Int by lazy { stepReports.count { it.failure?.isLeft ?: false } } 33 | 34 | @get:JvmName("firstUnsuccessfulStepReport") 35 | val firstUnsuccessfulStepReport: StepReport? by lazy { 36 | stepReports.firstOrNull { !it.isSuccessful } 37 | } 38 | 39 | @get:JvmName("firstUnIgnoredUnsuccessfulStepReport") 40 | val firstUnIgnoredUnsuccessfulStepReport: StepReport? by lazy { 41 | stepReports.firstOrNull { stepReport -> 42 | !stepReport.isSuccessful && !isStepIgnoredForFailure(stepReport, this) 43 | } 44 | } 45 | 46 | @get:JvmName("areAllStepsSuccessful") 47 | val areAllStepsSuccessful: Boolean by lazy { stepReports.all { it.isSuccessful } } 48 | 49 | @get:JvmName("areAllStepsExceptIgnoredSuccessful") 50 | val areAllStepsExceptIgnoredSuccessful: Boolean by lazy { 51 | stepReports.all { it.isSuccessful || !isStepIgnoredForFailure(it, this) } 52 | } 53 | 54 | fun reportsForStepsInFolder(folderName: String): List = 55 | stepReports.filter { it.step.name.contains("$folderName$FOLDER_DELIMITER") } 56 | 57 | fun areAllStepsInFolderSuccessful(folderName: String): Boolean = 58 | reportsForStepsInFolder(folderName).all { it?.isSuccessful == true } 59 | 60 | fun reportForStepName(stepName: String): StepReport? = 61 | stepReports.firstOrNull { it.step.stepNameMatches(stepName) } 62 | 63 | fun filterReportExcludingStepsWithName(stepNames: Set): List = 64 | stepReports.filter { r -> !stepNames.any { r.step.stepNameMatches(it) } } 65 | 66 | fun filterReportIncludingStepsWithName(stepNames: Set): List = 67 | stepReports.filter { r -> stepNames.any { r.step.stepNameMatches(it) } } 68 | 69 | companion object { 70 | fun isStepIgnoredForFailure(stepReport: StepReport, rundown: Rundown): Boolean = 71 | rundown.haltOnFailureOfTypeExcept 72 | .asSequence() 73 | .map { (exeType, postTxnPick) -> 74 | stepReport.exeTypeForFailure == exeType && 75 | (postTxnPick?.pick(stepReport, rundown) ?: false) 76 | } 77 | .any { it } 78 | } 79 | } 80 | 81 | fun List.endsWith(list: List): Boolean = 82 | list.isNotEmpty() && list.size < size && subList(lastIndex - list.lastIndex, size) == list 83 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/Step.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report 9 | 10 | import com.salesforce.revoman.internal.postman.template.Item 11 | import com.salesforce.revoman.output.endsWith 12 | import com.salesforce.revoman.output.report.Folder.Companion.FOLDER_DELIMITER 13 | import java.util.Collections.indexOfSubList 14 | 15 | data class Step( 16 | @JvmField val index: String, 17 | @JvmField val rawPMStep: Item, 18 | @JvmField val parentFolder: Folder? = null, 19 | ) { 20 | @JvmField val name: String = rawPMStep.name 21 | @JvmField var preStepHookCount: Int = 0 22 | @JvmField var postStepHookCount: Int = 0 23 | @JvmField 24 | val path = parentFolder?.let { "$it$STEP_NAME_SEPARATOR$name$STEP_NAME_TERMINATOR" } ?: name 25 | @JvmField 26 | val displayName = "$index$INDEX_SEPARATOR${rawPMStep.httpMethod}$HTTP_METHOD_SEPARATOR$path" 27 | 28 | @JvmField val isInRoot: Boolean = parentFolder == null 29 | 30 | fun isInFolder(folderPath: String): Boolean = 31 | parentFolder?.let { 32 | indexOfSubList( 33 | it.path.map { f -> f.name }, 34 | folderPath.trim(*FOLDER_DELIMITER.toCharArray()).split(FOLDER_DELIMITER), 35 | ) != -1 36 | } ?: folderPath.isBlank() 37 | 38 | fun stepNameMatches(stepName: String): Boolean = 39 | name == stepName || displayName == stepName || pathEndsWith(stepName) 40 | 41 | private fun pathEndsWith(semiStepPath: String): Boolean { 42 | val stepNameFromPath: List = semiStepPath.split(STEP_NAME_SEPARATOR) 43 | if (stepNameFromPath.size != 2 || name != stepNameFromPath[1]) { 44 | return false 45 | } 46 | val folderPath = stepNameFromPath[0].split(FOLDER_DELIMITER) 47 | return parentFolder?.path?.endsWith(folderPath) ?: folderPath.isEmpty() 48 | } 49 | 50 | override fun toString(): String = displayName 51 | 52 | companion object { 53 | const val HTTP_METHOD_SEPARATOR = " ~~> " 54 | const val INDEX_SEPARATOR = " ### " 55 | const val STEP_NAME_SEPARATOR = "<|||" 56 | const val STEP_NAME_TERMINATOR = "|||>" 57 | } 58 | } 59 | 60 | data class Folder 61 | @JvmOverloads 62 | constructor( 63 | @JvmField val name: String, 64 | @JvmField val parent: Folder? = null, 65 | @JvmField val subFolders: MutableList = mutableListOf(), 66 | ) { 67 | @JvmField val isRoot: Boolean = parent == null 68 | @JvmField val parentPath: List = parent?.parentPath?.plus(parent) ?: emptyList() 69 | @JvmField val path: List = parentPath + this 70 | 71 | override fun toString(): String = 72 | when { 73 | isRoot -> name 74 | else -> 75 | parentPath.joinToString(separator = FOLDER_DELIMITER, postfix = FOLDER_DELIMITER) { 76 | it.name 77 | } + name 78 | } 79 | 80 | companion object { 81 | const val FOLDER_DELIMITER = "|>" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/TxnInfo.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report 9 | 10 | import com.salesforce.revoman.internal.json.MoshiReVoman 11 | import com.squareup.moshi.JsonAdapter 12 | import com.squareup.moshi.rawType 13 | import io.vavr.control.Either 14 | import java.lang.reflect.Type 15 | import java.util.Collections.indexOfSubList 16 | import org.http4k.core.HttpMessage 17 | import org.http4k.core.Request 18 | import org.http4k.core.Response 19 | 20 | data class TxnInfo 21 | @JvmOverloads 22 | constructor( 23 | @JvmField val isJson: Boolean = true, 24 | @JvmField val txnObjType: Type? = if (isJson) Any::class.java else null, 25 | @JvmField val txnObj: Any? = null, 26 | @JvmField val httpMsg: HttpMsgT, 27 | val moshiReVoman: MoshiReVoman, 28 | ) { 29 | @JvmOverloads 30 | fun getTypedTxnObj( 31 | targetType: Type = this.txnObjType ?: Any::class.java, 32 | customAdapters: List = emptyList(), 33 | customAdaptersWithType: Map, JsonAdapter.Factory>> = 34 | emptyMap(), 35 | typesToIgnore: Set = emptySet(), 36 | ): PojoT? = 37 | when { 38 | // ! TODO 15/10/23 gopala.akshintala: xml2Json 39 | txnObj == null -> null 40 | targetType.rawType.isInstance(txnObj) -> txnObj 41 | targetType == String::class.java -> txnObj 42 | !isJson -> 43 | throw IllegalCallerException("Non JSON (like XML) marshalling to POJO is not yet supported") 44 | else -> { 45 | moshiReVoman.addAdapters(customAdapters, customAdaptersWithType, typesToIgnore) 46 | moshiReVoman.fromJson(httpMsg.bodyString(), targetType) 47 | } 48 | } 49 | as PojoT? 50 | 51 | @JvmOverloads 52 | inline fun getTxnObj( 53 | customAdapters: List = emptyList(), 54 | customAdaptersWithType: Map, JsonAdapter.Factory>> = 55 | emptyMap(), 56 | typesToIgnore: Set = emptySet(), 57 | ): PojoT? = 58 | when { 59 | txnObj == null -> null 60 | txnObj is PojoT -> txnObj 61 | PojoT::class == String::class -> txnObj 62 | !isJson -> 63 | throw IllegalCallerException("Non JSON (like XML) marshalling to POJO is not yet supported") 64 | else -> { 65 | moshiReVoman.addAdapters(customAdapters, customAdaptersWithType, typesToIgnore) 66 | moshiReVoman.fromJson(httpMsg.bodyString()) 67 | } 68 | } 69 | as PojoT? 70 | 71 | fun containsHeader(key: String): Boolean = httpMsg.headers.toMap().containsKey(key) 72 | 73 | fun containsHeader(key: String, value: String): Boolean = httpMsg.headers.contains(key to value) 74 | 75 | fun getHeaderValue(key: String): String? = httpMsg.header(key) 76 | 77 | override fun toString(): String { 78 | val prefix = 79 | when (httpMsg) { 80 | is Request -> "⬆️ Request Info ~~>" 81 | is Response -> "⬇️ Response Info <~~" 82 | else -> "TxnInfo" 83 | } 84 | return "\n$prefix\nType=$txnObjType\nObj=$txnObj\n$httpMsg" 85 | } 86 | 87 | companion object { 88 | @JvmStatic fun TxnInfo.getURIPath(): String = httpMsg.uri.path 89 | 90 | @JvmStatic 91 | fun TxnInfo.uriPathContains(path: String): Boolean = 92 | indexOfSubList(httpMsg.uri.path.trim('/').split("/"), path.trim('/').split("/")) != -1 93 | 94 | @JvmStatic 95 | fun TxnInfo.uriPathEndsWith(path: String): Boolean { 96 | val sourcePath = httpMsg.uri.path.trim('/').split("/") 97 | val targetPath = path.trim('/').split("/") 98 | val indexOfSubList = indexOfSubList(sourcePath, targetPath) 99 | return indexOfSubList != -1 && indexOfSubList + targetPath.lastIndex == sourcePath.lastIndex 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/failure/ExeFailure.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report.failure 9 | 10 | import com.salesforce.revoman.output.ExeType 11 | 12 | sealed class ExeFailure { 13 | abstract val exeType: ExeType 14 | abstract val failure: Throwable 15 | } 16 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/failure/HookFailure.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report.failure 9 | 10 | import com.salesforce.revoman.output.ExeType.POST_STEP_HOOK 11 | import com.salesforce.revoman.output.ExeType.PRE_STEP_HOOK 12 | import com.salesforce.revoman.output.report.TxnInfo 13 | import org.http4k.core.Request 14 | import org.http4k.core.Response 15 | 16 | sealed class HookFailure : ExeFailure() { 17 | 18 | data class PreStepHookFailure( 19 | override val failure: Throwable, 20 | @JvmField val requestInfo: TxnInfo, 21 | ) : HookFailure() { 22 | override val exeType = PRE_STEP_HOOK 23 | } 24 | 25 | data class PostStepHookFailure( 26 | override val failure: Throwable, 27 | @JvmField val requestInfo: TxnInfo, 28 | @JvmField val responseInfo: TxnInfo, 29 | ) : HookFailure() { 30 | override val exeType = POST_STEP_HOOK 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/failure/HttpStatusUnsuccessful.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report.failure 9 | 10 | import com.salesforce.revoman.output.ExeType.HTTP_STATUS 11 | import com.salesforce.revoman.output.report.TxnInfo 12 | import org.http4k.core.Request 13 | import org.http4k.core.Response 14 | 15 | data class HttpStatusUnsuccessful( 16 | @JvmField val requestInfo: TxnInfo, 17 | @JvmField val responseInfo: TxnInfo, 18 | ) { 19 | @JvmField val exeType = HTTP_STATUS 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/failure/RequestFailure.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report.failure 9 | 10 | import com.salesforce.revoman.output.ExeType.HTTP_REQUEST 11 | import com.salesforce.revoman.output.ExeType.PRE_REQ_JS 12 | import com.salesforce.revoman.output.ExeType.UNMARSHALL_REQUEST 13 | import com.salesforce.revoman.output.report.TxnInfo 14 | import org.http4k.core.Request 15 | 16 | sealed class RequestFailure : ExeFailure() { 17 | abstract val requestInfo: TxnInfo 18 | 19 | data class PreReqJSFailure( 20 | override val failure: Throwable, 21 | override val requestInfo: TxnInfo, 22 | ) : RequestFailure() { 23 | override val exeType = PRE_REQ_JS 24 | } 25 | 26 | data class UnmarshallRequestFailure( 27 | override val failure: Throwable, 28 | override val requestInfo: TxnInfo, 29 | ) : RequestFailure() { 30 | override val exeType = UNMARSHALL_REQUEST 31 | } 32 | 33 | data class HttpRequestFailure( 34 | override val failure: Throwable, 35 | override val requestInfo: TxnInfo, 36 | ) : RequestFailure() { 37 | override val exeType = HTTP_REQUEST 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/com/salesforce/revoman/output/report/failure/ResponseFailure.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.output.report.failure 9 | 10 | import com.salesforce.revoman.output.ExeType.POST_RES_JS 11 | import com.salesforce.revoman.output.ExeType.UNMARSHALL_RESPONSE 12 | import com.salesforce.revoman.output.report.TxnInfo 13 | import org.http4k.core.Request 14 | import org.http4k.core.Response 15 | 16 | sealed class ResponseFailure : ExeFailure() { 17 | abstract val requestInfo: TxnInfo 18 | abstract val responseInfo: TxnInfo 19 | 20 | data class PostResJSFailure( 21 | override val failure: Throwable, 22 | override val requestInfo: TxnInfo, 23 | override val responseInfo: TxnInfo, 24 | ) : ResponseFailure() { 25 | override val exeType = POST_RES_JS 26 | } 27 | 28 | data class UnmarshallResponseFailure( 29 | override val failure: Throwable, 30 | override val requestInfo: TxnInfo, 31 | override val responseInfo: TxnInfo, 32 | ) : ResponseFailure() { 33 | override val exeType = UNMARSHALL_RESPONSE 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/java/com/salesforce/revoman/input/json/adapters/SObjectGraphRequestMarshaller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 salesforce.com, inc. 3 | * All Rights Reserved 4 | * Company Confidential 5 | */ 6 | 7 | package com.salesforce.revoman.input.json.adapters; 8 | 9 | import static com.salesforce.revoman.input.json.JsonWriterUtils.listW; 10 | import static com.salesforce.revoman.input.json.JsonWriterUtils.mapW; 11 | import static com.salesforce.revoman.input.json.JsonWriterUtils.objW; 12 | import static com.salesforce.revoman.input.json.JsonWriterUtils.string; 13 | import static java.util.Collections.emptySet; 14 | 15 | import com.salesforce.revoman.input.json.pojo.SObjectGraphRequest; 16 | import com.squareup.moshi.FromJson; 17 | import com.squareup.moshi.JsonAdapter; 18 | import com.squareup.moshi.JsonReader; 19 | import com.squareup.moshi.JsonWriter; 20 | import com.squareup.moshi.ToJson; 21 | import java.util.Map; 22 | import java.util.Set; 23 | import kotlin.collections.CollectionsKt; 24 | import kotlin.collections.MapsKt; 25 | 26 | /** A light-weight Marshaller/Deserializer to convert SObjectGraphRequest --> JSON */ 27 | public class SObjectGraphRequestMarshaller { 28 | 29 | static final String MASK = "******"; 30 | private final Map paramsToWrite; 31 | private final Set fieldNamesToMaskValues; 32 | 33 | private SObjectGraphRequestMarshaller( 34 | Map paramsToWrite, Set fieldNamesToMaskValues) { 35 | this.paramsToWrite = paramsToWrite; 36 | this.fieldNamesToMaskValues = fieldNamesToMaskValues; 37 | } 38 | 39 | /** 40 | * @param paramsToWrite Any extra params to insert inside the JSON, at the same level and outside 41 | * `graph` 42 | */ 43 | public static SObjectGraphRequestMarshaller adapter(Map paramsToWrite) { 44 | return new SObjectGraphRequestMarshaller(paramsToWrite, emptySet()); 45 | } 46 | 47 | public static SObjectGraphRequestMarshaller adapter( 48 | Map paramsToWrite, Set fieldNamesToMaskValues) { 49 | return new SObjectGraphRequestMarshaller(paramsToWrite, fieldNamesToMaskValues); 50 | } 51 | 52 | /** Marshals SObjectGraphRequest to PQ payload */ 53 | @SuppressWarnings("unused") 54 | @ToJson 55 | public void toJson( 56 | JsonWriter writer, 57 | SObjectGraphRequest sObjectGraphRequest, 58 | JsonAdapter dynamicJsonAdapter) { 59 | objW( 60 | sObjectGraphRequest, 61 | writer, 62 | sogr -> { 63 | mapW(writer, paramsToWrite, dynamicJsonAdapter); 64 | objW( 65 | "graph", 66 | sogr, 67 | writer, 68 | sog -> { 69 | string("graphId", sog.getGraphId(), writer); 70 | listW( 71 | "records", 72 | sog.getRecords(), 73 | writer, 74 | sObj -> 75 | objW( 76 | sObj, 77 | writer, 78 | sowrr -> { 79 | string("referenceId", sowrr.getReferenceId(), writer); 80 | writer.name("record"); 81 | final var fieldNameToValue = sowrr.getRecord().getFields(); 82 | final var fieldNameToValueMaskedConditionally = 83 | MapsKt.mapValues( 84 | fieldNameToValue, 85 | // * NOTE 17 Apr 2024 gopala.akshintala: Case insensitive 86 | // comparison, 87 | // because SObjectGraph is Case Insensitive 88 | entry -> 89 | CollectionsKt.any( 90 | fieldNamesToMaskValues, 91 | fieldName -> 92 | fieldName.equalsIgnoreCase(entry.getKey())) 93 | ? MASK 94 | : entry.getValue()); 95 | dynamicJsonAdapter.toJson( 96 | writer, fieldNameToValueMaskedConditionally); 97 | })); 98 | }); 99 | }); 100 | } 101 | 102 | @FromJson 103 | public SObjectGraphRequest fromJson(JsonReader ignore) { 104 | return null; // noop 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/com/salesforce/revoman/input/json/pojo/SObjectGraphRequest.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.input.json.pojo; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class SObjectGraphRequest { 14 | 15 | private final String graphId; 16 | private final List records; 17 | 18 | public SObjectGraphRequest(String graphId, List records) { 19 | this.graphId = graphId; 20 | this.records = records; 21 | } 22 | 23 | public String getGraphId() { 24 | return this.graphId; 25 | } 26 | 27 | public List getRecords() { 28 | return this.records; 29 | } 30 | 31 | public static class Entity { 32 | private final Map fields; 33 | 34 | public Entity(Map fields) { 35 | this.fields = fields; 36 | } 37 | 38 | public Map getFields() { 39 | return fields; 40 | } 41 | } 42 | 43 | public static class SObjectWithReferenceRequest { 44 | private final String referenceId; 45 | private final Entity record; 46 | 47 | public SObjectWithReferenceRequest(String referenceId, Entity record) { 48 | this.referenceId = referenceId; 49 | this.record = record; 50 | } 51 | 52 | public String getReferenceId() { 53 | return this.referenceId; 54 | } 55 | 56 | public Entity getRecord() { 57 | return this.record; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/salesforce/revoman/output/postman/PostmanEnvironmentTest.java: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: 3 | * Apache License Version 2.0 4 | * For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | **************************************************************************************************/ 7 | 8 | package com.salesforce.revoman.output.postman; 9 | 10 | import static com.google.common.truth.Truth.assertThat; 11 | 12 | import com.squareup.moshi.Types; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | import org.junit.jupiter.api.DisplayName; 18 | import org.junit.jupiter.api.Test; 19 | 20 | class PostmanEnvironmentTest { 21 | @Test 22 | @DisplayName("Multi level filtering") 23 | void multiLevelFiltering() { 24 | final var postmanEnv = 25 | new PostmanEnvironment<>( 26 | Map.of( 27 | "standardPricebookId", "fakeStandardPricebookId", 28 | "accessToken", "fakeAccessToken", 29 | "salesRepPsgId", "fakePsgId", 30 | "salesRepUserId", "fakeUserId", 31 | "mockTaxAdapterId", "mockTaxAdapterId")); 32 | final var filteredEnv = 33 | postmanEnv 34 | .mutableEnvCopyExcludingKeys(String.class, Set.of("standardPricebookId")) 35 | .mutableEnvCopyWithKeysEndingWith(String.class, "Id") 36 | .mutableEnvCopyWithKeysNotEndingWith(String.class, "PsgId", "UserId"); 37 | assertThat(filteredEnv) 38 | .containsExactlyEntriesIn(Map.of("mockTaxAdapterId", "mockTaxAdapterId")); 39 | } 40 | 41 | @Test 42 | @DisplayName("get Typed Obj") 43 | void getTypedObj() { 44 | final var env = 45 | io.vavr.collection.HashMap.of( 46 | "key1", 47 | 1, 48 | "key2", 49 | "2", 50 | "key3", 51 | List.of(1, 2, 3), 52 | "key4", 53 | Map.of("4", 4), 54 | "key5", 55 | new ArrayList<>(List.of("a", "b", "c")), 56 | "key6", 57 | """ 58 | { 59 | "attributes": { 60 | "type": "BillingSchedule", 61 | "url": "/services/data/v64.0/sobjects/BillingSchedule/44bSG000000WOyTYAW" 62 | }, 63 | "Id": "44bSG000000WOyTYAW", 64 | "Status": "ReadyForInvoicing", 65 | "NextBillingDate": "2024-10-07", 66 | "NextChargeFromDate": "2024-10-07", 67 | "BilledThroughPeriod": null, 68 | "CancellationDate": null, 69 | "TotalAmount": 89.99, 70 | "Category": "Original" 71 | } 72 | """, 73 | "key7", 74 | "some string", 75 | "key8", 76 | null); 77 | final var pm = new PostmanEnvironment<>(env.toJavaMap()); 78 | assertThat(pm.getTypedObj("key1", Integer.class)).isEqualTo(env.get("key1").get()); 79 | assertThat(pm.getTypedObj("key2", String.class)).isEqualTo(env.get("key2").get()); 80 | assertThat( 81 | pm.>getTypedObj( 82 | "key3", Types.newParameterizedType(List.class, Integer.class))) 83 | .containsExactlyElementsIn((Iterable) env.get("key3").get()); 84 | assertThat( 85 | pm.>getTypedObj( 86 | "key4", Types.newParameterizedType(Map.class, String.class, Integer.class))) 87 | .containsExactlyEntriesIn((Map) env.get("key4").get()); 88 | assertThat( 89 | pm.>getTypedObj( 90 | "key5", Types.newParameterizedType(List.class, String.class))) 91 | .containsExactlyElementsIn((Iterable) env.get("key5").get()); 92 | assertThat( 93 | pm.>getTypedObj( 94 | "key6", Types.newParameterizedType(Map.class, String.class, Object.class))) 95 | .isInstanceOf(Map.class); 96 | assertThat(pm.getTypedObj("key7", String.class)).isEqualTo(env.get("key7").get()); 97 | assertThat(pm.getTypedObj("key8", Object.class)).isNull(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/kotlin/com/salesforce/revoman/input/FileUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input 9 | 10 | import io.kotest.matchers.string.shouldNotBeBlank 11 | import java.io.File 12 | import org.junit.jupiter.api.Test 13 | 14 | class FileUtilsTest { 15 | @Test 16 | fun `read file from resources to string`() { 17 | readFileToString("env-with-regex.json").shouldNotBeBlank() 18 | } 19 | 20 | @Test 21 | fun `read file to string`() { 22 | val file = File("src/test/resources/env-with-regex.json") 23 | readFileToString(file).shouldNotBeBlank() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/kotlin/com/salesforce/revoman/input/config/KickTest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.input.config 9 | 10 | import com.salesforce.revoman.output.ExeType.HTTP_STATUS 11 | import io.kotest.assertions.throwables.shouldThrow 12 | import org.junit.jupiter.api.Test 13 | 14 | class KickTest { 15 | @Test 16 | fun `'haltOnAnyFailureExceptForSteps' should be null when 'haltOnAnyFailure' is set to True`() { 17 | shouldThrow { 18 | Kick.configure() 19 | .haltOnAnyFailure(true) 20 | .haltOnFailureOfTypeExcept(HTTP_STATUS) { _, _ -> true } 21 | .off() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/kotlin/com/salesforce/revoman/internal/ExeUtilsTest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * ************************************************************************************************ 3 | * Copyright (c) 2023, Salesforce, Inc. All rights reserved. SPDX-License-Identifier: Apache License 4 | * Version 2.0 For full license text, see the LICENSE file in the repo root or 5 | * http://www.apache.org/licenses/LICENSE-2.0 6 | * ************************************************************************************************ 7 | */ 8 | package com.salesforce.revoman.internal 9 | 10 | import com.salesforce.revoman.input.bufferFile 11 | import com.salesforce.revoman.internal.exe.deepFlattenItems 12 | import com.salesforce.revoman.internal.postman.template.Template 13 | import com.salesforce.revoman.output.report.Folder.Companion.FOLDER_DELIMITER 14 | import com.salesforce.revoman.output.report.Step.Companion.HTTP_METHOD_SEPARATOR 15 | import com.salesforce.revoman.output.report.Step.Companion.INDEX_SEPARATOR 16 | import com.salesforce.revoman.output.report.Step.Companion.STEP_NAME_SEPARATOR 17 | import com.salesforce.revoman.output.report.Step.Companion.STEP_NAME_TERMINATOR 18 | import com.squareup.moshi.Moshi 19 | import com.squareup.moshi.adapter 20 | import io.kotest.matchers.collections.shouldContainExactly 21 | import io.kotest.matchers.shouldBe 22 | import org.junit.jupiter.api.Test 23 | 24 | class ExeUtilsTest { 25 | 26 | @OptIn(ExperimentalStdlibApi::class) 27 | val steps = 28 | deepFlattenItems( 29 | Moshi.Builder() 30 | .build() 31 | .adapter