Skip to content

Commit 4fa2026

Browse files
committed
- Doc edits
- Refactor tests Signed-off-by: Gopal S Akshintala <[email protected]>
1 parent 5799b2b commit 4fa2026

File tree

10 files changed

+118
-146
lines changed

10 files changed

+118
-146
lines changed

README.adoc

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -377,32 +377,68 @@ image:pq-exe-logging.gif[Monitor Execution]
377377
[#_type_safety_with_flexible_json_pojo_marshallingserialization_and_unmarshallingdeserialization]
378378
=== Type Safety with flexible JSON <- -> POJO marshalling/serialization and unmarshalling/deserialization
379379

380-
* ReṼoman internally uses a modern JSON library called https://github.com/square/moshi[**Moshi**]
381-
* There may be a POJO that inherits or contains legacy types that are hard or impossible to serialize. ReṼoman lets you serialize only types that matter, through `globalSkipTypes`, where you can filter out these legacy types from Marshalling/Unmarshalling
382-
* The JSON structure may not align with the POJO, and you may need a _Custom Type Adapter_ for Marshalling/Unmarshalling. Moshi has it covered for you with its advanced adapter mechanism and ReṼoman accepts Moshi adapters via:
383-
** `requestConfig` — For types present as part of request payload for qualified Steps
384-
** `responseConfig` — For types present as part of response payload for qualified Steps. You can configure separate adapters for success and error response. Use bundled static factory methods like `unmarshallSuccessResponse()` and `unmarshallErrorResponse()` for expressiveness.
380+
Postman operates purely with JSON.
381+
When interoperating Postman with JVM,
382+
unmarshalling/deserialization of JSON into a POJO and vice versa helps in writing Type-safe JVM code and enhances the debugging experience on JVM.
383+
ReṼoman internally uses a modern JSON library called https://github.com/square/moshi[**Moshi**].
384+
Simple types whose JSON structure aligns with the POJO data structure can directly be converted.
385+
But when the JSON structure may not align with the POJO,
386+
you may need a _Custom Type Adapter_ for Marshalling to JSON or Unmarshalling from JSON.
387+
Moshi has it covered for you with its advanced adapter mechanism and ReṼoman accepts Moshi adapters.
388+
Checkout these methods that help us interop between Postman and JVM:
389+
390+
==== `globalSkipTypes()`
391+
392+
There may be a POJO that inherits or contains legacy types that are hard or impossible to serialize.
393+
ReṼoman lets you serialize only types that matter, through `globalSkipTypes`,
394+
where you can filter out these legacy types from Marshalling/Unmarshalling
395+
396+
==== `requestConfig()`
397+
398+
* Configure Moshi adapters to unmarshall/deserialize _Request_ JSON payload to a POJO on certain steps.
399+
* You may use the bundled static factory methods named `RequestConfig.unmarshallResponse()` for expressiveness.
400+
* You can pass a `PreTxnStepPick` which is a `Predicate` used
401+
to qualify a step whose Request JSON payload needs to be unmarshalled/deserialized.
402+
* If you have set up `requestConfig()` once, wherever you wish to access the request in your <<#_pre_step_and_post_step_hooks,Pre-Step Hooks>>, you can call `stepReport.responseInfo.get().<TypeT>getTypedTxnObj()` which returns a Strong type to conveniently assert.
403+
* If you don't pass anything in this config, Moshi unmarshalls into default data structures like `LinkedHashMap`
404+
405+
==== `responseConfig()`
406+
407+
* Configure Moshi adapters to unmarshall/deserialize _Response_ JSON payload to a POJO on certain steps.
408+
* You can configure separate adapters for success and error response. Success or Error response is determined by default with HTTP Status Code (SUCCESSFUL: `200 < = statusCode < = 299`).
409+
* Use bundled static factory methods like `ResponseConfig.unmarshallSuccessResponse()` and `ResponseConfig.unmarshallErrorResponse()` for expressiveness.
410+
* You can pass a `PostTxnStepPick` which is a `Predicate` used
411+
to qualify a step whose Response JSON payload needs to be unmarshalled/deserialized.
412+
* If you have set up `responseConfig()` once, wherever you wish to access or assert the response in your <<#_pre_step_and_post_step_hooks,Post-Step Hooks>>, you can call `stepReport.responseInfo.get().<TypeT>getTypedTxnObj()` which returns a Strong type to conveniently assert.
413+
* If you don't pass anything in this config, Moshi unmarshalls into default data structures like `LinkedHashMap`
385414

386415
[TIP]
387416
====
388-
Success or Error response is determined by default with HTTP Status Code (SUCCESSFUL = `200 <=statusCode <=299`). There may be a scenario
417+
* There may be a scenario
389418
that you cannot depend on HTTP Status Code to distinguish between Success or Error.
390-
In such a case,
419+
* In such a case,
391420
you can leverage a bundled Moshi factory link:{sourcedir}/com/salesforce/revoman/input/json/factories/DiMorphicAdapter.kt[DiMorphicAdapter]
392-
that deals with it dynamically.
393-
Refer link:{sourcedir}/com/salesforce/revoman/input/json/adapters/CompositeGraphResponse.kt[CompositeGraphResponse]
394-
for an example usage
421+
that deals with it dynamically based on a `boolean` attribute value in the response JSON.
422+
See `DiMorphicAdapter` in action from the test `compositeGraphResponseDiMorphicMarshallUnmarshall()` in link:{testdir}/com/salesforce/revoman/input/json/JsonPojoUtilsTest.java[JsonPojoUtilsTest].
423+
* You may also refer to link:https://square.github.io/moshi/1.x/moshi-adapters/adapters/com.squareup.moshi.adapters/-polymorphic-json-adapter-factory/index.html[PolymorphicJsonAdapterFactory] for Marshalling/Unmarshalling based on `String` type attribute.
395424
====
396425

397-
** `globalCustomTypeAdapters` — For types present as part of request/response payload anywhere
398-
* ReṼoman also comes bundled with link:{sourcedir}/com/salesforce/revoman/input/json/JsonReaderUtils.kt[JSON Reader utils] and link:{sourcedir}/com/salesforce/revoman/input/json/JsonWriterUtils.kt[JSON Writer utils] to help build Moshi adapters.
426+
==== `globalCustomTypeAdapters()`
399427

400-
TIP: Refer link:{integrationtestdir}/com/salesforce/revoman/integration/core/pq/adapters/ConnectInputRepWithGraphAdapter.java[ConnectInputRepWithGraphAdapter]
401-
for an advanced adapter use-case
428+
These adapters are used for marshalling/unmarshalling request/response payload for any step.
429+
These compliment the custom adapters setup in `requestConfig()` or `responseConfig()`
430+
431+
==== JSON Reader/Writer Utils to build Moshi adapters
432+
433+
ReṼoman also comes
434+
bundled with link:{sourcedir}/com/salesforce/revoman/input/json/JsonReaderUtils.kt[JSON Reader utils] and link:{sourcedir}/com/salesforce/revoman/input/json/JsonWriterUtils.kt[JSON Writer utils]
435+
to help build Moshi adapters.
436+
437+
TIP: Refer link:{integrationtestdir}/com/salesforce/revoman/integration/core/pq/adapters/ConnectInputRepWithGraphAdapter.java[ConnectInputRepWithGraphAdapter] on how these utils come in handy in building an advanced Moshi adapter
402438

403439
==== JSON POJO Utils
404440

405-
The bundled link:{sourcedir}/com/salesforce/revoman/input/json/JsonPojoUtils.kt[JSON POJO Utils] can be used to directly convert JSON to POJO and vice versa.
441+
The bundled link:{sourcedir}/com/salesforce/revoman/input/json/JsonPojoUtils.kt[JSON POJO Utils] can be used to directly to convert JSON to POJO and vice versa.
406442

407443
[TIP]
408444
====
@@ -412,6 +448,12 @@ See in Action:
412448
* link:{integrationtestdir}/com/salesforce/revoman/input/json/JsonPojoUtils2Test.java[JsonPojoUtils2Test]
413449
====
414450

451+
==== `rundown.mutableEnv.getTypedObj()`
452+
453+
You can read an environment variable value as a Strong type.
454+
See it in action: `getTypedObj()`
455+
test from link:{testdir}/com/salesforce/revoman/output/postman/PostmanEnvironmentTest.java[PostmanEnvironmentTest]
456+
415457
=== Execution Control
416458

417459
The configuration offers methods through which the execution strategy can be controlled without making any changes to the template:
@@ -430,8 +472,8 @@ A hook lets you fiddle with the execution by plugging in your custom JVM code be
430472
You can pass a `PreTxnStepPick/PostTxnStepPick` which is a `Predicate` used
431473
to qualify a step for Pre-Step/Post-Step Hook respectively.
432474
ReṼoman comes
433-
bundled with some predicates under the namespace `PreTxnStepPick.PickUtils`/`PostTxnStepPick.PickUtils` e.g `beforeStepContainingURIPathOfAny`,
434-
`afterStepName` etc. If those don't fit your needs, you can write your own custom predicates like below:
475+
bundled with some predicates under the namespace `PreTxnStepPick.PickUtils`/`PostTxnStepPick.PickUtils` e.g `beforeStepContainingURIPathOfAny()`,
476+
`afterStepName()` etc. If those don't fit your needs, you can write your own custom predicates like below:
435477

436478
[source,java,indent=0,tabsize=2,options="nowrap"]
437479
----
@@ -457,7 +499,7 @@ Add them to the config as below:
457499
)
458500
----
459501

460-
* You can do things like assertion on the rundown, response validation,
502+
* You can do things like assertion on the rundown, <<#_response_validations,Response Validations>>,
461503
or even <<#_mutable_environment,mutate the environment>> with a value you programmatically derived,
462504
such that the execution of later steps picks up those changes.
463505
* Reserve hooks for plugging in your custom code or asserting and fail-fast in the middle of execution.
@@ -475,6 +517,7 @@ Instead, you have a Java utility to generate/create it.
475517
You can invoke the utility in a pre-hook of a step and set the value in `rundown.mutableEnv`,
476518
so the later steps can pick up value for `+{{xyzId}}+` variable from the environment.
477519

520+
[#_response_validations]
478521
==== Response Validations
479522

480523
* Post-Hooks are the best place to validate response right after the step.
@@ -517,7 +560,7 @@ image::node_modules.png[]
517560
* If `node_modules` is ignored on your git repo, you can force-add to check in using the command `git add -all -f <path>/node_modules`
518561
====
519562

520-
CAUTION: The recommendation is not to add too much code in <<Pre-req and Post-res scripts>>, as they not intuitive to troubleshoot through debugging. Use it for simple operations like set environment variables and use <<#_pre_step_and_post_step_hooks,Pre-Step /Post-Step Hooks>> for any non-trivial operations, which are intuitive to debug.
563+
CAUTION: The recommendation is not to add too much code in <<Pre-req and Post-res scripts>>, as it's not intuitive to troubleshoot through debugging. Use it for simple operations that can be understood without debugging, and use <<#_pre_step_and_post_step_hooks,Pre-Step /Post-Step Hooks>> for any non-trivial operations, which are intuitive to debug.
521564

522565
[#_mutable_environment]
523566
=== Mutable Environment

src/integrationTest/java/com/salesforce/revoman/integration/pokemon/AllPokemon.kt

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/integrationTest/java/com/salesforce/revoman/integration/pokemon/PokemonTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.salesforce.revoman.output.report.Step;
2828
import com.salesforce.revoman.output.report.StepReport;
2929
import com.salesforce.revoman.output.report.TxnInfo;
30+
import java.util.List;
3031
import java.util.Map;
3132
import org.http4k.core.Request;
3233
import org.jetbrains.annotations.NotNull;
@@ -93,7 +94,7 @@ public void accept(
9394
public void accept(@NotNull StepReport stepReport, @NotNull Rundown rundown) {
9495
assertThat(rundown.mutableEnv).containsEntry("limit", String.valueOf(newLimit));
9596
final var results =
96-
stepReport.responseInfo.get().<AllPokemon>getTypedTxnObj().getResults();
97+
stepReport.responseInfo.get().<AllPokemon>getTypedTxnObj().results;
9798
assertThat(results.size()).isEqualTo(newLimit);
9899
}
99100
});
@@ -145,4 +146,8 @@ public void accept(@NotNull StepReport stepReport, @NotNull Rundown rundown) {
145146
Mockito.verify(preLogHook, times(1)).accept(any(), any(), any());
146147
Mockito.verify(postLogHook, times(1)).accept(any(), any());
147148
}
149+
150+
public record AllPokemon(int count, String next, String previous, List<Result> results) {
151+
public record Result(String name, String url) {}
152+
}
148153
}

src/main/kotlin/com/salesforce/revoman/internal/json/MoshiReVoman.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,13 @@ import org.http4k.format.ThrowableAdapter
3333
import org.http4k.format.asConfigurable
3434
import org.http4k.format.withStandardMappings
3535

36-
internal lateinit var moshiReVoman: Moshi
37-
3836
@JvmOverloads
3937
internal fun initMoshi(
4038
customAdapters: List<Any> = emptyList(),
4139
customAdaptersWithType: Map<Type, List<Either<JsonAdapter<out Any>, Factory>>> = emptyMap(),
4240
typesToIgnore: Set<Class<out Any>> = emptySet(),
4341
): ConfigurableMoshi {
4442
val moshiBuilder = buildMoshi(customAdapters, customAdaptersWithType, typesToIgnore)
45-
moshiReVoman = moshiBuilder.build()
4643
return object : ConfigurableMoshi(moshiBuilder) {}
4744
}
4845

src/main/kotlin/com/salesforce/revoman/output/report/TxnInfo.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ constructor(
7272
override fun toString(): String {
7373
val prefix =
7474
when (httpMsg) {
75-
is Request -> "️RequestInfo ~~>"
76-
is Response -> "️ResponseInfo <~~"
75+
is Request -> "️ RequestInfo ~~>"
76+
is Response -> "️ ResponseInfo <~~"
7777
else -> "TxnInfo"
7878
}
7979
return "$prefix\nType=$txnObjType\nObj=$txnObj\n$httpMsg"

src/test/java/com/salesforce/revoman/input/json/JsonPojoUtilsTest.java

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,48 +32,46 @@
3232
class JsonPojoUtilsTest {
3333

3434
@Test
35-
@DisplayName("DiMorphic CompositeGraph Success Response --> POJO --> JSON")
36-
void compositeGraphSuccessResponseMarshallUnmarshall() throws JSONException {
37-
final var graphSuccessResponseJsonStr =
38-
readFileInResourcesToString("composite/graph/resp/graph-success-response.json");
35+
@DisplayName("DiMorphic CompositeGraph Success/Error Response --> POJO --> JSON")
36+
void compositeGraphResponseDiMorphicMarshallUnmarshall() throws JSONException {
37+
// JSON --> POJO
38+
final var jsonFileConfig =
39+
JsonFile.<CompositeGraphResponse>unmarshall()
40+
.pojoType(CompositeGraphResponse.class)
41+
.customAdapter(CompositeGraphResponse.ADAPTER);
42+
3943
final var successGraphResponse =
40-
JsonPojoUtils.<CompositeGraphResponse>jsonToPojo(
41-
CompositeGraphResponse.class,
42-
graphSuccessResponseJsonStr,
43-
List.of(CompositeGraphResponse.ADAPTER));
44+
JsonPojoUtils.jsonFileToPojo(
45+
jsonFileConfig.jsonFilePath("composite/graph/resp/graph-success-response.json").done());
4446
assertThat(successGraphResponse.getGraphs().get(0)).isInstanceOf(SuccessGraph.class);
45-
final var successGraphResponseUnmarshalled =
46-
JsonPojoUtils.pojoToJson(
47-
Pojo.marshall()
48-
.pojoType(CompositeGraphResponse.class)
49-
.pojo(successGraphResponse)
50-
.customAdapter(CompositeGraphResponse.ADAPTER)
51-
.done());
52-
JSONAssert.assertEquals(
53-
graphSuccessResponseJsonStr, successGraphResponseUnmarshalled, JSONCompareMode.STRICT);
54-
}
5547

56-
@Test
57-
@DisplayName("DiMorphic CompositeGraph Error Response --> POJO --> JSON")
58-
void compositeGraphErrorResponseMarshallUnmarshall() throws JSONException {
59-
final var graphErrorResponseJsonStr =
60-
readFileInResourcesToString("composite/graph/resp/graph-error-response.json");
6148
final var errorGraphResponse =
62-
JsonPojoUtils.<CompositeGraphResponse>jsonToPojo(
63-
CompositeGraphResponse.class,
64-
graphErrorResponseJsonStr,
65-
List.of(CompositeGraphResponse.ADAPTER));
49+
JsonPojoUtils.jsonFileToPojo(
50+
jsonFileConfig.jsonFilePath("composite/graph/resp/graph-error-response.json").done());
6651
final var errorGraph = errorGraphResponse.getGraphs().get(0);
6752
assertThat(errorGraph).isInstanceOf(ErrorGraph.class);
6853
assertThat(((ErrorGraph) errorGraph).firstErrorResponseBody.getErrorCode())
6954
.isEqualTo("DUPLICATE_VALUE");
55+
56+
// POJO --> JSON
57+
final var pojoToJsonConfig =
58+
Pojo.<CompositeGraphResponse>marshall()
59+
.pojoType(CompositeGraphResponse.class)
60+
.customAdapter(CompositeGraphResponse.ADAPTER);
61+
62+
final var successGraphResponseUnmarshalled =
63+
JsonPojoUtils.pojoToJson(pojoToJsonConfig.pojo(successGraphResponse).done());
64+
JSONAssert.assertEquals(
65+
readFileInResourcesToString("composite/graph/resp/graph-success-response.json"),
66+
successGraphResponseUnmarshalled,
67+
JSONCompareMode.STRICT);
68+
7069
final var errorGraphResponseUnmarshalled =
71-
JsonPojoUtils.pojoToJson(
72-
CompositeGraphResponse.class,
73-
errorGraphResponse,
74-
List.of(CompositeGraphResponse.ADAPTER));
70+
JsonPojoUtils.pojoToJson(pojoToJsonConfig.pojo(errorGraphResponse).done());
7571
JSONAssert.assertEquals(
76-
graphErrorResponseJsonStr, errorGraphResponseUnmarshalled, JSONCompareMode.STRICT);
72+
readFileInResourcesToString("composite/graph/resp/graph-error-response.json"),
73+
errorGraphResponseUnmarshalled,
74+
JSONCompareMode.STRICT);
7775
}
7876

7977
@DisplayName("toJson: SObjectGraphRequest POJO --> PQ Payload JSON")

src/test/kotlin/com/salesforce/revoman/internal/postman/PostmanTest.kt

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/test/kotlin/com/salesforce/revoman/internal/postman/RegexReplacerTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,27 @@ import com.salesforce.revoman.input.config.CustomDynamicVariableGenerator
1111
import com.squareup.moshi.Moshi
1212
import com.squareup.moshi.adapter
1313
import io.kotest.matchers.equals.shouldNotBeEqual
14+
import io.kotest.matchers.maps.shouldContain
1415
import io.kotest.matchers.maps.shouldContainAll
1516
import io.mockk.mockk
1617
import org.junit.jupiter.api.Test
1718

1819
class RegexReplacerTest {
20+
@Test
21+
fun `unmarshall Env File with Regex and Dynamic variable`() {
22+
val epoch = System.currentTimeMillis().toString()
23+
val dummyDynamicVariableGenerator = { r: String, _: PostmanSDK ->
24+
if (r == "\$epoch") epoch else null
25+
}
26+
val regexReplacer = RegexReplacer(emptyMap(), dummyDynamicVariableGenerator)
27+
val pm = PostmanSDK(mockk(), null, regexReplacer)
28+
pm.environment.putAll(
29+
mergeEnvs(setOf("env-with-regex.json"), emptyList(), mutableMapOf("un" to "userName"))
30+
)
31+
val envWithVariablesReplaced = regexReplacer.replaceVariablesInEnv(pm)
32+
envWithVariablesReplaced shouldContain ("userName" to "user-$epoch@xyz.com")
33+
}
34+
1935
@OptIn(ExperimentalStdlibApi::class)
2036
@Test
2137
fun `dynamic variables - Body + dynamic env`() {

0 commit comments

Comments
 (0)