Skip to content

Commit 12e1918

Browse files
author
Gopal S Akshintala
committed
BIG: Support for JetBrains HTTP client templates
Signed-off-by: Gopal S Akshintala <[email protected]> Signed-off-by: Gopal S Akshintala <[email protected]>
1 parent 76b3b41 commit 12e1918

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2904
-109
lines changed

.cursor/style.mdc

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,24 @@
22
alwaysApply: true
33
---
44

5-
- Use this codebase's functional Kotlin style: pure functions, sequences, immutable state, when expressions
6-
- Reduce state mutation as much as possible. Prefer state transformation
7-
- While generating Kotlin code, specify all variable types and functional return types explicitly
8-
95
# Functional Programming Patterns
106

11-
- Keep functional code simple: avoid nested functions, unnecessary data classes, and over-engineered chains
7+
- Reduce state mutation as much as possible. Prefer state transformation
128
- Follow the functional style from the existing codebase
139
- Chain operations using sequences: prefer `.asSequence()` for multiple transformations
10+
- Avoid imperative loops if possible
1411
- Build immutable data flow: pass state through parameters, return new state instead of mutating
1512
- Use functional combinators: `map`, `filter`, `flatMap`, `fold`, `firstOrNull` over loops
1613
- Prefer single-expression functions that return directly from when/if expressions
1714
- Prefer direct recursion with parameters over complex state objects
15+
- Don't over-engineer functional programming code
1816

1917
# Kotlin-Specific Patterns
20-
- Always use `when` expressions over if-else chains
18+
- Always use `when` expressions over `if-else` chains
2119
- Use ranges: `downTo`, `until`, `in`, `indices` for iterations
2220
- String operations: use `substring(range)` with IntRange, not separate indices
2321
- Choose `firstOrNull` over complex sequence chains when finding first valid element
2422
- Collection operations: use `+` operator for adding to immutable collections
2523
- Use `?.let { }` with elvis `?:` for clean null handling
2624
- Use `firstOrNull()` with elvis `?:` for fallback values
25+
- While generating Kotlin code, specify all variable types and functional return types explicitly. Use Named parameters only in ambiguous situations

.idea/jsLibraryMappings.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

qodana.yaml

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

src/integrationTest/java/com/salesforce/revoman/integration/core/bt2bs/MilestoneBillingE2ETest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
import com.salesforce.revoman.ReVoman;
1515
import java.util.Map;
1616
import kotlin.collections.CollectionsKt;
17+
import org.junit.jupiter.api.Disabled;
1718
import org.junit.jupiter.api.Test;
1819

20+
@Disabled("This needs local core server")
1921
class MilestoneBillingE2ETest {
2022

2123
@Test

src/integrationTest/java/com/salesforce/revoman/integration/core/pq/PQE2EWithSMTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.List;
3333
import java.util.Map;
3434
import kotlin.random.Random;
35+
import org.junit.jupiter.api.Disabled;
3536
import org.junit.jupiter.api.Test;
3637
import org.slf4j.Logger;
3738
import org.slf4j.LoggerFactory;
@@ -48,6 +49,7 @@
4849
*
4950
* <p>- TODO: Add a mock server setup for this test.
5051
*/
52+
@Disabled("This needs local core server")
5153
class PQE2EWithSMTest {
5254
private static final Logger LOGGER = LoggerFactory.getLogger(PQE2EWithSMTest.class);
5355
private static final String PQ_COLLECTION_PATH = "pm-templates/core/pq";
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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.pokemon;
9+
10+
import static com.google.common.truth.Truth.assertThat;
11+
import static com.salesforce.revoman.input.config.HookConfig.post;
12+
import static com.salesforce.revoman.input.config.HookConfig.pre;
13+
import static com.salesforce.revoman.input.config.KickDef.intoMap;
14+
import static com.salesforce.revoman.input.config.ResponseConfig.unmarshallResponse;
15+
import static com.salesforce.revoman.input.config.StepPick.PostTxnStepPick.afterStepContainingHeader;
16+
import static com.salesforce.revoman.input.config.StepPick.PostTxnStepPick.afterStepContainingURIPathOfAny;
17+
import static com.salesforce.revoman.input.config.StepPick.PostTxnStepPick.afterStepName;
18+
import static com.salesforce.revoman.input.config.StepPick.PreTxnStepPick.beforeStepContainingHeader;
19+
import static com.salesforce.revoman.input.config.StepPick.PreTxnStepPick.beforeStepName;
20+
import static org.assertj.vavr.api.VavrAssertions.assertThat;
21+
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.Mockito.times;
23+
24+
import com.salesforce.revoman.ReVoman;
25+
import com.salesforce.revoman.input.config.HookConfig.StepHook.PostStepHook;
26+
import com.salesforce.revoman.input.config.HookConfig.StepHook.PreStepHook;
27+
import com.salesforce.revoman.input.config.Kick;
28+
import com.salesforce.revoman.integration.core.adapters.IDAdapter;
29+
import com.salesforce.revoman.integration.core.pq.connect.response.ID;
30+
import com.salesforce.revoman.output.Rundown;
31+
import com.salesforce.revoman.output.report.Step;
32+
import com.salesforce.revoman.output.report.StepReport;
33+
import com.salesforce.revoman.output.report.TxnInfo;
34+
import com.salesforce.revoman.output.report.failure.HookFailure.PostStepHookFailure;
35+
import com.squareup.moshi.Json;
36+
import java.util.List;
37+
import java.util.Map;
38+
import kotlin.Pair;
39+
import kotlin.collections.MapsKt;
40+
import org.http4k.core.Request;
41+
import org.jetbrains.annotations.NotNull;
42+
import org.junit.jupiter.api.Test;
43+
import org.mockito.Mockito;
44+
import org.slf4j.Logger;
45+
import org.slf4j.LoggerFactory;
46+
47+
class PokemonHttpTest {
48+
49+
private static final String HTTP_TEMPLATE_PATH = "http-templates/pokemon/pokemon.http";
50+
private static final String HTTP_ENVIRONMENT_PATH = "http-templates/pokemon/http-client.env.json";
51+
private static final String ALL_POKEMON_STEP = "all-pokemon";
52+
private static final String ENV_LIMIT = "limit";
53+
private static final int LIMIT = 3;
54+
private static final int OFFSET = 0;
55+
private static final Logger LOGGER = LoggerFactory.getLogger(PokemonHttpTest.class);
56+
private static final RuntimeException RUNTIME_EXCEPTION =
57+
new RuntimeException("This won't interrupt the execution as `haltOnAnyFailure` is not set");
58+
59+
@Test
60+
void pokemonHttp() {
61+
final var newLimit = 1;
62+
final var dynamicEnvironment1 = Map.of("offset", OFFSET);
63+
final var dynamicEnvironment2 =
64+
MapsKt.mapOf(new Pair<>(ENV_LIMIT, LIMIT), new Pair<>("null", null));
65+
@SuppressWarnings("Convert2Lambda")
66+
final var preLogHook =
67+
Mockito.spy(
68+
new PreStepHook() {
69+
@Override
70+
public void accept(
71+
@NotNull Step currentStep,
72+
@NotNull TxnInfo<Request> ignore1,
73+
@NotNull Rundown ignore2) {
74+
LOGGER.info("Picked `preLogHook` before stepName: {}", currentStep);
75+
}
76+
});
77+
@SuppressWarnings("Convert2Lambda")
78+
final var postLogHook =
79+
Mockito.spy(
80+
new PostStepHook() {
81+
@Override
82+
public void accept(@NotNull StepReport stepReport, @NotNull Rundown ignore) {
83+
LOGGER.info("Picked `postLogHook` after stepName: {}", stepReport.step.displayName);
84+
throw RUNTIME_EXCEPTION;
85+
}
86+
});
87+
@SuppressWarnings("Convert2Lambda")
88+
final var preStepHookBeforeStepName =
89+
Mockito.spy(
90+
new PreStepHook() {
91+
@Override
92+
public void accept(
93+
@NotNull Step ignore1,
94+
@NotNull TxnInfo<Request> ignore2,
95+
@NotNull Rundown rundown) {
96+
rundown.mutableEnv.set(ENV_LIMIT, newLimit);
97+
}
98+
});
99+
@SuppressWarnings("Convert2Lambda")
100+
final var postStepHookAfterStepName =
101+
Mockito.spy(
102+
new PostStepHook() {
103+
@Override
104+
public void accept(@NotNull StepReport stepReport, @NotNull Rundown rundown) {
105+
assertThat(rundown.mutableEnv).containsEntry(ENV_LIMIT, newLimit);
106+
final var results =
107+
stepReport.responseInfo.get().<AllPokemon>getTypedTxnObj().results;
108+
assertThat(results.size()).isEqualTo(newLimit);
109+
}
110+
});
111+
@SuppressWarnings("Convert2Lambda")
112+
final var postStepHookAfterURIPath =
113+
Mockito.spy(
114+
new PostStepHook() {
115+
@Override
116+
public void accept(@NotNull StepReport stepReport, @NotNull Rundown rundown) {
117+
LOGGER.info(
118+
"Picked `postStepHookAfterURIPath` after stepName: {} with raw URI: {}",
119+
stepReport.step.displayName,
120+
stepReport.step.rawPMStep.getRequest().url);
121+
final var id =
122+
stepReport
123+
.responseInfo
124+
.map(
125+
ri ->
126+
ri.<Color>getTypedTxnObj(Color.class, List.of(new IDAdapter()))
127+
.id)
128+
.getOrNull();
129+
assertThat(id.id()).isEqualTo(rundown.mutableEnv.get("id"));
130+
}
131+
});
132+
final var config =
133+
Kick.configure()
134+
.templatePath(HTTP_TEMPLATE_PATH)
135+
.environmentPath(HTTP_ENVIRONMENT_PATH)
136+
.responseConfig(unmarshallResponse(afterStepName(ALL_POKEMON_STEP), AllPokemon.class))
137+
.hooks(
138+
pre(beforeStepName(ALL_POKEMON_STEP), preStepHookBeforeStepName),
139+
post(afterStepName(ALL_POKEMON_STEP), postStepHookAfterStepName),
140+
post(afterStepContainingURIPathOfAny("pokemon-color"), postStepHookAfterURIPath),
141+
pre(beforeStepContainingHeader("preLog"), preLogHook),
142+
post(afterStepContainingHeader("postLog"), postLogHook))
143+
.dynamicEnvironment(dynamicEnvironment1)
144+
.off();
145+
final var pokeRundown =
146+
ReVoman.revUp(
147+
config.overrideDynamicEnvironment(intoMap(dynamicEnvironment1, dynamicEnvironment2)));
148+
149+
final var postHookFailure = pokeRundown.firstUnIgnoredUnsuccessfulStepReport().failure;
150+
assertThat(postHookFailure).containsLeftInstanceOf(PostStepHookFailure.class);
151+
assertThat(postHookFailure.getLeft().getFailure()).isEqualTo(RUNTIME_EXCEPTION);
152+
assertThat(pokeRundown.stepReports).hasSize(5);
153+
assertThat(pokeRundown.mutableEnv)
154+
.containsExactlyEntriesIn(
155+
MapsKt.mapOf(
156+
new Pair<>("offset", OFFSET),
157+
new Pair<>(ENV_LIMIT, newLimit),
158+
new Pair<>("baseUrl", "https://pokeapi.co/api/v2"),
159+
new Pair<>("id", "1"),
160+
new Pair<>("pokemonName", "bulbasaur"),
161+
new Pair<>("color", "black"),
162+
new Pair<>("gender", "female"),
163+
new Pair<>("ability", "stench"),
164+
new Pair<>("nature", "hardy"),
165+
new Pair<>("null", null)));
166+
Mockito.verify(preStepHookBeforeStepName, times(1)).accept(any(), any(), any());
167+
Mockito.verify(postStepHookAfterStepName, times(1)).accept(any(), any());
168+
Mockito.verify(postStepHookAfterURIPath, times(1)).accept(any(), any());
169+
Mockito.verify(preLogHook, times(1)).accept(any(), any(), any());
170+
Mockito.verify(postLogHook, times(1)).accept(any(), any());
171+
}
172+
173+
public record AllPokemon(int count, String next, String previous, List<Result> results) {
174+
public record Result(String name, String url) {}
175+
}
176+
177+
public record Color(
178+
ID id,
179+
String name,
180+
List<Name> names,
181+
@Json(name = "pokemon_species") List<PokemonSpecies> pokemonSpecies) {
182+
public record Name(Language language, String name) {
183+
public record Language(String name, String url) {}
184+
}
185+
186+
public record PokemonSpecies(String name, String url) {}
187+
}
188+
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import static com.salesforce.revoman.input.config.StepPick.PostTxnStepPick.afterStepName;
1818
import static com.salesforce.revoman.input.config.StepPick.PreTxnStepPick.beforeStepContainingHeader;
1919
import static com.salesforce.revoman.input.config.StepPick.PreTxnStepPick.beforeStepName;
20-
import static org.assertj.vavr.api.VavrAssertions.assertThat;
20+
21+
import com.google.common.truth.Truth;
22+
import org.assertj.vavr.api.VavrAssertions;
2123
import static org.mockito.ArgumentMatchers.any;
2224
import static org.mockito.Mockito.times;
2325

@@ -146,7 +148,7 @@ public void accept(@NotNull StepReport stepReport, @NotNull Rundown rundown) {
146148
config.overrideDynamicEnvironment(intoMap(dynamicEnvironment1, dynamicEnvironment2)));
147149

148150
final var postHookFailure = pokeRundown.firstUnIgnoredUnsuccessfulStepReport().failure;
149-
assertThat(postHookFailure).containsLeftInstanceOf(PostStepHookFailure.class);
151+
VavrAssertions.assertThat(postHookFailure).containsLeftInstanceOf(PostStepHookFailure.class);
150152
assertThat(postHookFailure.getLeft().getFailure()).isEqualTo(RUNTIME_EXCEPTION);
151153
assertThat(pokeRundown.stepReports).hasSize(5);
152154
assertThat(pokeRundown.mutableEnv)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dev" : {
3+
"baseUrl": "https://pokeapi.co/api/v2"
4+
}
5+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
@offset = 0
2+
@limit = 3
3+
4+
### all-pokemon
5+
< {%
6+
client.global.set("limit", 1);
7+
%}
8+
GET {{baseUrl}}/pokemon?offset={{offset}}&limit={{limit}}
9+
10+
> {%
11+
var jsonData = typeof response.body === "string" ? JSON.parse(response.body) : response.body;
12+
var pokemon = jsonData.results[0];
13+
var pokemonName = pokemon.name;
14+
client.global.set("pokemonName", pokemonName);
15+
var urlParts = pokemon.url.split("/").filter(part => part.length > 0);
16+
var id = urlParts[urlParts.length - 1];
17+
client.global.set("id", id);
18+
%}
19+
20+
###
21+
### pokemon-color
22+
GET {{baseUrl}}/pokemon-color/{{id}}
23+
postLog: true
24+
25+
> {%
26+
var jsonData = typeof response.body === "string" ? JSON.parse(response.body) : response.body;
27+
client.global.set("color", jsonData.name);
28+
%}
29+
30+
###
31+
### pokemon-gender
32+
GET {{baseUrl}}/gender/{{id}}
33+
34+
> {%
35+
var jsonData = typeof response.body === "string" ? JSON.parse(response.body) : response.body;
36+
client.global.set("gender", jsonData.name);
37+
%}
38+
39+
###
40+
### pokemon-ability
41+
GET {{baseUrl}}/ability/{{id}}
42+
43+
> {%
44+
const jsonData = typeof response.body === "string" ? JSON.parse(response.body) : response.body;
45+
client.global.set("ability", jsonData.name);
46+
%}
47+
48+
###
49+
### nature
50+
GET {{baseUrl}}/nature/{{id}}
51+
preLog: true
52+
53+
> {%
54+
var jsonData = typeof response.body === "string" ? JSON.parse(response.body) : response.body;
55+
client.global.set("nature", jsonData.name);
56+
%}

0 commit comments

Comments
 (0)