Skip to content

Commit 8dd3dca

Browse files
committed
Leading app changes to reproduce the issue
1 parent aedf418 commit 8dd3dca

4 files changed

Lines changed: 97 additions & 3 deletions

File tree

app/single-tenant/personal-space/demoapp/db/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<dependency>
1717
<groupId>com.sap.cds</groupId>
1818
<artifactId>sdm</artifactId>
19-
<version>1.0.0-RC1</version>
19+
<version>1.10.0</version>
2020
</dependency>
2121
</dependencies>
2222

app/single-tenant/personal-space/demoapp/srv/pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<dependency>
1919
<groupId>com.sap.cds</groupId>
2020
<artifactId>sdm</artifactId>
21-
<version>1.0.0-RC1</version>
21+
<version>1.10.0</version>
2222
</dependency>
2323

2424

@@ -47,6 +47,12 @@
4747
<scope>test</scope>
4848
</dependency>
4949

50+
<dependency>
51+
<groupId>org.springframework.security</groupId>
52+
<artifactId>spring-security-test</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
5056
<dependency>
5157
<groupId>com.h2database</groupId>
5258
<artifactId>h2</artifactId>

app/single-tenant/personal-space/demoapp/srv/src/main/resources/application.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,14 @@ spring:
88
schema-locations: classpath:schema-h2.sql
99
data-source:
1010
auto-config:
11-
enabled: false
11+
enabled: false
12+
cds:
13+
security:
14+
mock.users:
15+
admin:
16+
password: admin
17+
roles:
18+
- admin
19+
- system-user
20+
user:
21+
password: user
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package customer.demoapp.handlers;
2+
3+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
5+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
6+
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
10+
import org.springframework.boot.test.context.SpringBootTest;
11+
import org.springframework.test.web.servlet.MockMvc;
12+
13+
/**
14+
* Reproduces the SDM 1.10.0 regression:
15+
*
16+
* SDMReadAttachmentsHandler.populateUploadableFlags (new @After handler in 1.10.0) fires after
17+
* every successful read on any entity whose composition has @SDM.Attachments. It then calls
18+
* DBQuery.getAttachmentsForUPID, which unconditionally SELECTs "IsActiveEntity" — a column that
19+
* only exists on draft-enabled entities. CatalogService.Books is a non-draft, @readonly
20+
* projection, so its Books.attachments entity has no IsActiveEntity element. The CQN
21+
* resolution throws CdsElementNotFoundException, which surfaces as HTTP 500.
22+
*
23+
* Expected (broken on SDM 1.10.0 without the fix): GET /odata/v4/CatalogService/Books → 500
24+
* Expected (after the fix): GET /odata/v4/CatalogService/Books → 200
25+
*
26+
* The seed CSV ships 5 books, so the response always has rows — exactly the condition that
27+
* triggers populateUploadableFlags (it returns early on empty data).
28+
*/
29+
@SpringBootTest
30+
@AutoConfigureMockMvc
31+
class CatalogServiceNonDraftReadIT {
32+
33+
// CatalogService is bound at the default CDS OData endpoint path.
34+
// The CAP Spring Boot starter mounts OData services under /odata/v4/<ServiceName>.
35+
private static final String BOOKS_URI = "/odata/v4/CatalogService/Books";
36+
37+
@Autowired
38+
private MockMvc mockMvc;
39+
40+
/**
41+
* A plain, unauthenticated GET on the non-draft CatalogService.Books must return 200
42+
* and at least one book row. On SDM 1.10.0 (unpatched) this returns 500 because
43+
* populateUploadableFlags tries to SELECT IsActiveEntity from a non-draft attachment entity.
44+
*/
45+
@Test
46+
void nonDraftReadOfBooksWithAttachmentsMustReturn200() throws Exception {
47+
mockMvc.perform(get(BOOKS_URI))
48+
.andExpect(status().isOk())
49+
.andExpect(jsonPath("$.value").isArray())
50+
.andExpect(jsonPath("$.value[0].ID").exists());
51+
}
52+
53+
/**
54+
* Same call with $top=1 — verifies it is not the 0-row early-exit case that happens to pass.
55+
* populateUploadableFlags does `if (data == null || data.isEmpty()) return;`, so an empty
56+
* result would never hit the crash. This test ensures at least one row is returned AND
57+
* processed without error.
58+
*/
59+
@Test
60+
void nonDraftReadWithTopOneStillReturns200() throws Exception {
61+
mockMvc.perform(get(BOOKS_URI + "?$top=1"))
62+
.andExpect(status().isOk())
63+
.andExpect(jsonPath("$.value").isArray())
64+
.andExpect(jsonPath("$.value[0].ID").exists());
65+
}
66+
67+
/**
68+
* Filtering to a row that is known to exist (Wuthering Heights is in the seed CSV).
69+
* Exercises the exact code path: a query that returns exactly 1 row goes through
70+
* populateUploadableFlags fully, hitting getAttachmentsForUPID once per facet.
71+
*/
72+
@Test
73+
void nonDraftReadWithFilterStillReturns200() throws Exception {
74+
mockMvc.perform(get(BOOKS_URI + "?$filter=stock gt 0"))
75+
.andExpect(status().isOk())
76+
.andExpect(jsonPath("$.value").isArray());
77+
}
78+
}

0 commit comments

Comments
 (0)