Skip to content

Commit 4d58810

Browse files
authored
Simplify Feature Toggles integration (#209)
- Reuse existing mock users instead of introducing new ones - Avoid introducing another disocunt concept and reuse the existing one
1 parent 6728ed1 commit 4d58810

File tree

6 files changed

+53
-84
lines changed

6 files changed

+53
-84
lines changed

mtx/sidecar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"scripts": {
3434
"start": "cds run",
3535
"build": "npm install && npx cds build ../.. --for mtx-sidecar && cd gen && npm install",
36-
"startAsync": "forever start -o out.log -e err.log -c node node_modules/@sap/cds/bin/cds.js run --profile development",
36+
"startAsync": "forever start -o out.log -e err.log node_modules/.bin/cds run",
3737
"stopAsync": "forever stopall"
3838
}
3939
}

srv/src/main/java/my/bookshop/FeatureToggleProvider.java renamed to srv/src/main/java/my/bookshop/config/CustomFeatureToggleProvider.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package my.bookshop;
1+
package my.bookshop.config;
22

33
import java.util.HashMap;
44
import java.util.Map;
@@ -11,10 +11,9 @@
1111
import com.sap.cds.services.request.UserInfo;
1212
import com.sap.cds.services.runtime.FeatureTogglesInfoProvider;
1313

14-
// When application executed locally, the toggles can be switched via application.yaml
1514
@Component
16-
@Profile("cloud")
17-
public class FeatureToggleProvider implements FeatureTogglesInfoProvider {
15+
@Profile("cloud") // locally, feature toggles are configured directly with mock users
16+
public class CustomFeatureToggleProvider implements FeatureTogglesInfoProvider {
1817

1918
@Override
2019
public FeatureTogglesInfo get(UserInfo userInfo, ParameterInfo parameterInfo) {

srv/src/main/java/my/bookshop/handlers/CatalogServiceHandler.java

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
import static cds.gen.catalogservice.CatalogService_.BOOKS;
44

5-
import java.math.BigDecimal;
6-
import java.math.RoundingMode;
75
import java.util.List;
86
import java.util.Optional;
97
import java.util.Set;
108
import java.util.stream.Collectors;
119
import java.util.stream.Stream;
1210

11+
import org.springframework.beans.factory.annotation.Qualifier;
12+
import org.springframework.stereotype.Component;
13+
1314
import com.sap.cds.Result;
1415
import com.sap.cds.Struct;
1516
import com.sap.cds.ql.Insert;
@@ -30,9 +31,7 @@
3031
import com.sap.cds.services.handler.annotations.ServiceName;
3132
import com.sap.cds.services.messages.Messages;
3233
import com.sap.cds.services.persistence.PersistenceService;
33-
34-
import org.springframework.beans.factory.annotation.Qualifier;
35-
import org.springframework.stereotype.Component;
34+
import com.sap.cds.services.request.FeatureTogglesInfo;
3635

3736
import cds.gen.catalogservice.AddReviewContext;
3837
import cds.gen.catalogservice.Books;
@@ -59,20 +58,19 @@
5958
class CatalogServiceHandler implements EventHandler {
6059

6160
private final PersistenceService db;
62-
6361
private final DraftService reviewService;
6462

6563
private final Messages messages;
66-
64+
private final FeatureTogglesInfo featureToggles;
6765
private final RatingCalculator ratingCalculator;
68-
6966
private final CqnAnalyzer analyzer;
7067

7168
CatalogServiceHandler(PersistenceService db, @Qualifier(ReviewService_.CDS_NAME) DraftService reviewService,
72-
Messages messages, RatingCalculator ratingCalculator, CdsModel model) {
69+
Messages messages, FeatureTogglesInfo featureToggles, RatingCalculator ratingCalculator, CdsModel model) {
7370
this.db = db;
7471
this.reviewService = reviewService;
7572
this.messages = messages;
73+
this.featureToggles = featureToggles;
7674
this.ratingCalculator = ratingCalculator;
7775
this.analyzer = CqnAnalyzer.create(model);
7876
}
@@ -137,7 +135,7 @@ public void afterAddReview(AddReviewContext context) {
137135
public void discountBooks(Stream<Books> books) {
138136
books.filter(b -> b.getTitle() != null).forEach(b -> {
139137
loadStockIfNotSet(b);
140-
discountBooksWithMoreThan111Stock(b);
138+
discountBooksWithMoreThan111Stock(b, featureToggles.isEnabled("discount"));
141139
});
142140
}
143141

@@ -188,20 +186,9 @@ public void onSubmitOrder(SubmitOrderContext context) {
188186
}
189187
}
190188

191-
@After
192-
protected void addDiscount(CdsReadEventContext context) {
193-
if (context.getFeatureTogglesInfo().isEnabled("discount")) {
194-
context.getResult().listOf(Books.class).forEach(book -> {
195-
if (book.getPrice() != null) {
196-
book.setPrice(book.getPrice().multiply(BigDecimal.valueOf(0.9)).setScale(2, RoundingMode.FLOOR));
197-
}
198-
});
199-
}
200-
}
201-
202-
private void discountBooksWithMoreThan111Stock(Books b) {
189+
private void discountBooksWithMoreThan111Stock(Books b, boolean premium) {
203190
if (b.getStock() != null && b.getStock() > 111) {
204-
b.setTitle(String.format("%s -- 11%% discount", b.getTitle()));
191+
b.setTitle(String.format("%s -- %s%% discount", b.getTitle(), premium ? 14 : 11));
205192
}
206193
}
207194

srv/src/main/resources/application.yaml

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ cds:
9292
name: "myself"
9393
suffix: "/api"
9494

95+
---
96+
spring:
97+
config.activate.on-profile: ft
98+
cds:
99+
model.provider.url: http://localhost:4004
100+
security.mock.users:
101+
admin:
102+
features:
103+
- isbn
104+
- discount
105+
user:
106+
features:
107+
- isbn
108+
95109
---
96110
spring:
97111
config.activate.on-profile: local-mtxs
@@ -109,24 +123,3 @@ spring:
109123
cds:
110124
data-source:
111125
auto-config.enabled: false
112-
---
113-
spring:
114-
config.activate.on-profile: ft
115-
cds:
116-
model:
117-
provider:
118-
url: http://localhost:4004
119-
datasource:
120-
csv-initialization-mode: "always"
121-
security:
122-
defaultRestrictionLevel: "any"
123-
mock.users:
124-
- name: carol
125-
features:
126-
- isbn
127-
- name: erin
128-
features:
129-
- isbn
130-
- discount
131-
- name: fred
132-
features:

srv/src/test/java/my/bookshop/FeatureToggles_IT.java renamed to srv/src/test/java/my/bookshop/FeatureTogglesIT.java

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,57 @@
11
package my.bookshop;
22

3-
import static org.assertj.core.api.Assertions.assertThat;
3+
import static org.hamcrest.CoreMatchers.containsString;
44
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
55
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
66
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
77

8-
import java.util.Collections;
9-
108
import org.junit.jupiter.api.Test;
119
import org.springframework.beans.factory.annotation.Autowired;
1210
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
1311
import org.springframework.boot.test.context.SpringBootTest;
1412
import org.springframework.security.test.context.support.WithMockUser;
1513
import org.springframework.test.context.ActiveProfiles;
1614
import org.springframework.test.web.servlet.MockMvc;
17-
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
18-
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
19-
20-
import com.sap.cds.reflect.CdsEntity;
21-
import com.sap.cds.reflect.CdsModel;
22-
import com.sap.cds.services.request.FeatureTogglesInfo;
23-
import com.sap.cds.services.runtime.CdsRuntime;
24-
25-
import cds.gen.catalogservice.Books_;
2615

2716
// This test case is executable only when MTX sidecar is running.
28-
@ActiveProfiles("ft")
17+
@ActiveProfiles({"default", "ft"})
2918
@AutoConfigureMockMvc
3019
@SpringBootTest
31-
class FeatureToggles_IT {
20+
class FeatureTogglesIT {
3221

33-
private static final String ENDPOINT = "/api/browse/Books(%s)";
22+
private static final String ENDPOINT = "/api/browse/Books(aebdfc8a-0dfa-4468-bd36-48aabd65e663)";
3423

3524
@Autowired
3625
private MockMvc client;
3726

38-
@Autowired
39-
CdsRuntime runtime;
40-
4127
@Test
42-
@WithMockUser("fred") // This user has all feature toggles disabled
28+
@WithMockUser("authenticated") // This user has all feature toggles disabled
4329
void withoutToggles_basicModelVisible() throws Exception {
4430
// Elements are not visible and not changed by the event handler
45-
client.perform(get(String.format(ENDPOINT, "4a519e61-3c3a-4bd9-ab12-d7e0c5329933")))
31+
client.perform(get(ENDPOINT))
4632
.andExpect(status().isOk())
4733
.andExpect(jsonPath("$.isbn").doesNotExist())
48-
.andExpect(jsonPath("$.price").value(15));
34+
.andExpect(jsonPath("$.title").value(containsString("11%")));
4935
}
5036

5137
@Test
52-
@WithMockUser("erin") // This user has all feature toggles enabled
38+
@WithMockUser("admin") // This user has all feature toggles enabled
5339
void togglesOn_extensionsAndChangesAreVisible() throws Exception {
5440
// Elements are visible and changed by the event handler
55-
client.perform(get(String.format(ENDPOINT, "4a519e61-3c3a-4bd9-ab12-d7e0c5329933")))
41+
client.perform(get(ENDPOINT))
5642
.andExpect(status().isOk())
57-
.andExpect(jsonPath("$.isbn").value("978-3473523023"))
58-
.andExpect(jsonPath("$.price").value(13.50));
43+
.andExpect(jsonPath("$.isbn").value("979-8669820985"))
44+
.andExpect(jsonPath("$.title").value(containsString("14%")));
5945
}
6046

6147
@Test
62-
@WithMockUser("carol") // This user has only 'isbn' toggle enabled
48+
@WithMockUser("user") // This user has only 'isbn' toggle enabled
6349
void toggleIsbnOn_extensionsAndChangesAreVisible() throws Exception {
6450
// Elements are visible
65-
client.perform(get(String.format(ENDPOINT, "4a519e61-3c3a-4bd9-ab12-d7e0c5329933")))
51+
client.perform(get(ENDPOINT))
6652
.andExpect(status().isOk())
67-
.andExpect(jsonPath("$.isbn").value("978-3473523023"))
68-
.andExpect(jsonPath("$.price").value(15));
53+
.andExpect(jsonPath("$.isbn").value("979-8669820985"))
54+
.andExpect(jsonPath("$.title").value(containsString("11%")));
6955
}
7056

7157
}

srv/src/test/java/my/bookshop/handlers/CatalogServiceHandlerTest.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44

55
import java.util.stream.Stream;
66

7-
import com.sap.cds.reflect.CdsModel;
8-
import com.sap.cds.services.draft.DraftService;
9-
import com.sap.cds.services.messages.Messages;
10-
import com.sap.cds.services.persistence.PersistenceService;
11-
127
import org.junit.jupiter.api.Test;
138
import org.junit.jupiter.api.extension.ExtendWith;
149
import org.mockito.Mock;
1510
import org.mockito.junit.jupiter.MockitoExtension;
1611

12+
import com.sap.cds.reflect.CdsModel;
13+
import com.sap.cds.services.draft.DraftService;
14+
import com.sap.cds.services.messages.Messages;
15+
import com.sap.cds.services.persistence.PersistenceService;
16+
import com.sap.cds.services.request.FeatureTogglesInfo;
17+
1718
import cds.gen.catalogservice.Books;
1819
import my.bookshop.RatingCalculator;
1920

@@ -30,6 +31,9 @@ public class CatalogServiceHandlerTest {
3031
@Mock
3132
private Messages messages;
3233

34+
@Mock
35+
private FeatureTogglesInfo featureToggles;
36+
3337
@Mock
3438
private RatingCalculator ratingCalculator;
3539

@@ -45,7 +49,7 @@ public void testDiscountHandler() {
4549
book2.setTitle("Book 2");
4650
book2.setStock(200);
4751

48-
CatalogServiceHandler handler = new CatalogServiceHandler(db, reviewService, messages, ratingCalculator, model);
52+
CatalogServiceHandler handler = new CatalogServiceHandler(db, reviewService, messages, featureToggles, ratingCalculator, model);
4953
handler.discountBooks(Stream.of(book1, book2));
5054

5155
assertEquals("Book 1", book1.getTitle(), "Book 1 was discounted");

0 commit comments

Comments
 (0)