|
| 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