Skip to content

Commit 5753c7c

Browse files
committed
feat: ability to store different stubs for different clients
1 parent e381e01 commit 5753c7c

File tree

5 files changed

+191
-53
lines changed

5 files changed

+191
-53
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 Eugene Terekhov
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package ewc.utilities.testableio.core;
26+
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
/**
31+
* The class that keeps track of all the stubs for all the clients.
32+
*
33+
* @since 0.1
34+
*/
35+
public class GenericIoStub {
36+
/**
37+
* The default stubs for all clients.
38+
*/
39+
private final SingleClientStubs common = new SingleClientStubs();
40+
41+
/**
42+
* The stubs for each client.
43+
*/
44+
private final Map<String, SingleClientStubs> stubs = new HashMap<>();
45+
46+
/**
47+
* Returns the next response for the given query.
48+
*
49+
* @param query The query for which to get the next response.
50+
* @return The next response object.
51+
*/
52+
public GenericResponse<?> nextResponseFor(final GenericRequest<?> query) {
53+
return this.stubs
54+
.getOrDefault(query.clientId(), this.common)
55+
.nextResponseFor(query);
56+
}
57+
58+
/**
59+
* Adds a default stub for all clients.
60+
*
61+
* @param query The query ID for which to add the stub.
62+
* @param response The stub response to be returned for the specified query.
63+
*/
64+
public void addDefaultStub(final String query, final GenericResponse<?> response) {
65+
this.common.setSingleResponseFor(query, response);
66+
}
67+
68+
/**
69+
* Adds a stub for a specific client.
70+
*
71+
* @param client The client ID for which to add the stub.
72+
* @param query The query ID for which to add the stub.
73+
* @param response The stub response to be returned for the specified query.
74+
*/
75+
public void addStubForClient(
76+
final String client, final String query, final GenericResponse<?> response
77+
) {
78+
this.stubs.putIfAbsent(client, new SingleClientStubs());
79+
this.stubs.get(client).setSingleResponseFor(query, response);
80+
}
81+
}

src/main/java/ewc/utilities/testableio/core/GenericResponse.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,22 @@
3232
* GenericResponse is a class that represents a response object with generic contents.
3333
*
3434
* @param <T> The type of the contents of the response. This can be any type of object, such as a
35-
* string, JSON object, or any other data structure.
35+
* string, JSON object, or any other data structure.
3636
* @param contents The contents of the response. This can be any type of object, such as a string,
37-
* JSON object, or any other data structure.
37+
* JSON object, or any other data structure.
3838
* @param delay The delay in milliseconds before the response is sent. This can be used to
39-
* simulate network latency or processing time. Is expressed in milliseconds and
40-
* expected to be positive or zero.
39+
* simulate network latency or processing time. Is expressed in milliseconds and
40+
* expected to be positive or zero.
4141
* @param metadata The metadata associated with the response. This can include any additional
42-
* information that is relevant to the response but not necessarily part of the
43-
* response data itself.
42+
* information that is relevant to the response but not necessarily part of the
43+
* response data itself.
4444
* @since 0.1
4545
*/
4646
public record GenericResponse<T>(@NonNull T contents, int delay, Map<String, Object> metadata) {
47+
public GenericResponse(final @NonNull T contents) {
48+
this(contents, 0, Map.of());
49+
}
50+
4751
/**
4852
* This method allows you to transform the contents of the response to any class using a
4953
* provided transformer function.

src/main/java/ewc/utilities/testableio/core/SingleClientStubs.java

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class SingleClientStubs {
3838
/**
3939
* The storage for all the configured responses.
4040
*/
41-
private final Map<String, SingleQueryResponses> responses = new HashMap<>();
41+
private final Map<String, SingleQueryResponses> stubs = new HashMap<>();
4242

4343
/**
4444
* Returns the next response for the given query.
@@ -48,37 +48,22 @@ class SingleClientStubs {
4848
*/
4949
@SneakyThrows
5050
public GenericResponse<?> nextResponseFor(final GenericRequest<?> query) {
51-
final String key;
52-
if (this.responses.containsKey(SingleClientStubs.responseKeyFor(query))) {
53-
key = SingleClientStubs.responseKeyFor(query);
54-
} else {
55-
key = query.queryId();
56-
}
57-
if (!this.responses.containsKey(key)) {
51+
final String key = query.queryId();
52+
if (!this.stubs.containsKey(key)) {
5853
throw new NoSuchElementException("No responses configured");
5954
}
60-
final GenericResponse<?> response = this.responses.get(key).next();
55+
final GenericResponse<?> response = this.stubs.get(key).next();
6156
if (response.delay() > 0) {
6257
Thread.sleep(response.delay());
6358
}
6459
return response;
6560
}
6661

67-
public void setDefaultResponsesFor(final String query, final SingleQueryResponses response) {
68-
this.responses.put(query, response);
69-
}
70-
71-
public void setResponsesFor(
72-
final String client, final String query, final SingleQueryResponses response
73-
) {
74-
this.responses.put(SingleClientStubs.responseKeyFor(client, query), response);
75-
}
76-
77-
private static String responseKeyFor(final GenericRequest<?> request) {
78-
return SingleClientStubs.responseKeyFor(request.clientId(), request.queryId());
62+
public void setSingleResponseFor(final String query, final GenericResponse<?> response) {
63+
this.stubs.put(query, new SingleQueryResponses(query, response));
7964
}
8065

81-
private static String responseKeyFor(final String client, final String query) {
82-
return "%s::%s".formatted(client, query);
66+
public void setMultipleResponsesFor(final String query, final GenericResponse<?>... responses) {
67+
this.stubs.put(query, new SingleQueryResponses(query, responses));
8368
}
8469
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 Eugene Terekhov
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package ewc.utilities.testableio.core;
26+
27+
import ewc.utilities.testableio.utils.MockRequest;
28+
import org.assertj.core.api.Assertions;
29+
import org.junit.jupiter.api.Test;
30+
31+
/**
32+
* The unit-tests for {@link GenericIoStub} class.
33+
*
34+
* @since 0.1
35+
*/
36+
final class GenericIoStubTest {
37+
/**
38+
* The request for the default (unspecified) client.
39+
*/
40+
private static final GenericRequest<String> ANY_CLIENT =
41+
new MockRequest().clientUnknown();
42+
43+
/**
44+
* The request for a specific client.
45+
*/
46+
private static final GenericRequest<String> SPECIFIC_CLIENT =
47+
new MockRequest().assignedToClient();
48+
49+
/**
50+
* The response for the default (unspecified) client.
51+
*/
52+
private static final GenericResponse<String> DEFAULT_RESPONSE =
53+
new GenericResponse<>("test default");
54+
55+
/**
56+
* The response for the specific client.
57+
*/
58+
private static final GenericResponse<String> SPECIFIC_RESPONSE =
59+
new GenericResponse<>("client-specific response");
60+
61+
@Test
62+
void createDefaultStub() {
63+
final GenericIoStub target = new GenericIoStub();
64+
target.addDefaultStub("getItemRecommendations", GenericIoStubTest.DEFAULT_RESPONSE);
65+
Assertions.assertThat(target.nextResponseFor(GenericIoStubTest.ANY_CLIENT))
66+
.isEqualTo(GenericIoStubTest.DEFAULT_RESPONSE);
67+
Assertions.assertThat(target.nextResponseFor(GenericIoStubTest.SPECIFIC_CLIENT))
68+
.isEqualTo(GenericIoStubTest.DEFAULT_RESPONSE);
69+
}
70+
71+
@Test
72+
void createInfiniteStubForSpecificClient() {
73+
final GenericIoStub target = new GenericIoStub();
74+
target.addDefaultStub("getItemRecommendations", GenericIoStubTest.DEFAULT_RESPONSE);
75+
target.addStubForClient(
76+
"12345",
77+
"getItemRecommendations",
78+
GenericIoStubTest.SPECIFIC_RESPONSE
79+
);
80+
Assertions.assertThat(target.nextResponseFor(GenericIoStubTest.ANY_CLIENT))
81+
.isEqualTo(GenericIoStubTest.DEFAULT_RESPONSE);
82+
Assertions.assertThat(target.nextResponseFor(GenericIoStubTest.SPECIFIC_CLIENT))
83+
.isEqualTo(GenericIoStubTest.SPECIFIC_RESPONSE);
84+
}
85+
}

src/test/java/ewc/utilities/testableio/core/SingleClientStubsTest.java

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,36 +55,19 @@ void throwsIfNoResponsesConfigured() {
5555
@Test
5656
void returnsTheResponseThatCorrespondsToTheRequest() {
5757
final SingleClientStubs target = new SingleClientStubs();
58-
target.setDefaultResponsesFor(
59-
"getHomePage",
60-
new SingleQueryResponses(
61-
"home",
62-
new GenericResponse<>("Home page", 0, Map.of())
63-
)
58+
target.setSingleResponseFor(
59+
"getHomePage", new GenericResponse<>("Home page", 0, Map.of())
6460
);
65-
target.setDefaultResponsesFor(
66-
"getItemRecommendations",
67-
new SingleQueryResponses(
68-
"recommendations",
69-
new GenericResponse<>("Authorization page", 1000, Map.of())
70-
)
61+
target.setSingleResponseFor(
62+
"getItemRecommendations", new GenericResponse<>("Recommendations page", 1000, Map.of())
7163
);
72-
target.setResponsesFor(
73-
"12345",
74-
"getItemRecommendations",
75-
new SingleQueryResponses(
76-
"recommendations",
77-
new GenericResponse<>("Recommendations page", 0, Map.of())
78-
)
79-
);
80-
final MockRequest request = new MockRequest();
81-
Assertions.assertThat(target.nextResponseFor(request.assignedToClient()))
64+
Assertions.assertThat(target.nextResponseFor(new MockRequest().clientUnknown()))
8265
.isNotNull()
8366
.extracting("contents").isEqualTo("Recommendations page");
8467
final long now = System.currentTimeMillis();
85-
Assertions.assertThat(target.nextResponseFor(request.clientUnknown()))
68+
Assertions.assertThat(target.nextResponseFor(new MockRequest().assignedToClient()))
8669
.isNotNull()
87-
.extracting("contents").isEqualTo("Authorization page");
70+
.extracting("contents").isEqualTo("Recommendations page");
8871
Assertions.assertThat(System.currentTimeMillis() - now)
8972
.isCloseTo(1000, Assertions.withinPercentage(5));
9073
}

0 commit comments

Comments
 (0)