Compare commits

...

7 Commits

Author SHA1 Message Date
Marc Becker
7bb08b2e59 Adapt checkpoint to exercise update 2020-02-14 15:20:55 +00:00
indu sankar
a0ca06fe45 Testcases for products-service,bookstore in java (#11)
* Testcases for products-service,bookstore
* Put back 'sap.capire.bookstore-Books_texts.csv' file, ignored it in DataLoader.
2019-12-13 09:39:48 +01:00
Marc Becker
1c18ea5105 Set CDS version to 3.17.4 in bookstore 2019-10-02 10:13:13 +00:00
Marc Becker
1c79c2e4f2 Set CDS version to 3.17.4 in products-service 2019-10-02 10:11:57 +00:00
Adrian Görler
c124137ab7 Use SQL timestamp literal format
Only the SQL timestamp literal format works with SQLite on JavaScript and Java
as well as with HANA.
2019-09-23 16:14:13 +02:00
Adrian Görler
4838de7f71 Update sap.capire.bookstore-Books.csv 2019-09-23 14:38:22 +02:00
Marc Becker
d631b5106e CAA160 final state 2019-09-18 07:50:21 +00:00
15 changed files with 585 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
ID;title;descr;author_ID;stock;price;currency_code;category_ID ID;title;descr;author_ID;stock;price;currency_code;category_ID;createdAt
abed2f7a-c50e-4bc5-89fd-9a00a54b4b16;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";6061f01b-7fa6-43c3-a6d7-12adb9d25198;12;11.11;GBP;9 abed2f7a-c50e-4bc5-89fd-9a00a54b4b16;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";6061f01b-7fa6-43c3-a6d7-12adb9d25198;12;11.11;GBP;9;2019-09-20 10:10:10.000
6869961b-c533-42b7-9d60-30fae4f46ab6;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";2db38772-235e-432c-92a0-ce09ca340728;11;12.34;GBP;10 6869961b-c533-42b7-9d60-30fae4f46ab6;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";2db38772-235e-432c-92a0-ce09ca340728;11;12.34;GBP;10;2019-09-20 10:10:10.000
fd0c5fda-8811-4e20-bcff-3a776abc290a;The Raven;"“The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";286ae4b1-b845-4af8-9827-4eb04fe5f380;333;13.13;USD;1 fd0c5fda-8811-4e20-bcff-3a776abc290a;The Raven;"“The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";286ae4b1-b845-4af8-9827-4eb04fe5f380;333;13.13;USD;1;2019-09-20 10:10:10.000
b7bca6dd-0497-465e-9a5a-56f244174c8c;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";286ae4b1-b845-4af8-9827-4eb04fe5f380;555;14.14;USD;5 b7bca6dd-0497-465e-9a5a-56f244174c8c;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";286ae4b1-b845-4af8-9827-4eb04fe5f380;555;14.14;USD;5;2019-09-20 10:10:10.000
5e69a718-d86b-4461-8953-0edadcd87960;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;cbfec09e-0ff4-4cfc-adf1-0a37500da750;22;15.15;EUR;3 5e69a718-d86b-4461-8953-0edadcd87960;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;cbfec09e-0ff4-4cfc-adf1-0a37500da750;22;15.15;EUR;3;2019-09-20 10:10:10.000
1 ID title descr author_ID stock price currency_code category_ID createdAt
2 abed2f7a-c50e-4bc5-89fd-9a00a54b4b16 Wuthering Heights Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym "Ellis Bell". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850. 6061f01b-7fa6-43c3-a6d7-12adb9d25198 12 11.11 GBP 9 2019-09-20 10:10:10.000
3 6869961b-c533-42b7-9d60-30fae4f46ab6 Jane Eyre Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name "Currer Bell", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism. 2db38772-235e-432c-92a0-ce09ca340728 11 12.34 GBP 10 2019-09-20 10:10:10.000
4 fd0c5fda-8811-4e20-bcff-3a776abc290a The Raven “The Raven" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word "Nevermore". The poem makes use of folk, mythological, religious, and classical references. 286ae4b1-b845-4af8-9827-4eb04fe5f380 333 13.13 USD 1 2019-09-20 10:10:10.000
5 b7bca6dd-0497-465e-9a5a-56f244174c8c Eleonora "Eleonora" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively "happy" ending. 286ae4b1-b845-4af8-9827-4eb04fe5f380 555 14.14 USD 5 2019-09-20 10:10:10.000
6 5e69a718-d86b-4461-8953-0edadcd87960 Catweazle Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts. cbfec09e-0ff4-4cfc-adf1-0a37500da750 22 15.15 EUR 3 2019-09-20 10:10:10.000

7
bookstore/manifest.yml Normal file
View File

@@ -0,0 +1,7 @@
---
applications:
- name: bookstore
path: srv/target/bookstore-1.0-SNAPSHOT.jar
random-route: true
services:
- bookstore-hana

View File

@@ -10,11 +10,13 @@
"deploy": "cds deploy" "deploy": "cds deploy"
}, },
"dependencies": { "dependencies": {
"@sap/cds": "^3.0.0", "@sap/cds": "3.21.2",
"@sap/capire-products": "file:sap-capire-products-1.0.0.tgz" "@sap/capire-products": "file:sap-capire-products-1.0.0.tgz",
"hdb": "^0.17.1"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "^4.1.0" "@sap/hdi-deploy": "3.7.0",
"sqlite3": "^4.1.1"
}, },
"cds": { "cds": {
"build": { "build": {

View File

@@ -16,9 +16,9 @@
<!-- DEPENDENCIES VERSION --> <!-- DEPENDENCIES VERSION -->
<jdk.version>1.8</jdk.version> <jdk.version>1.8</jdk.version>
<cds.services.version>1.0.1</cds.services.version> <cds.services.version>1.2.0</cds.services.version>
<cds4j.version>1.2.1</cds4j.version> <cds4j.version>1.6.0</cds4j.version>
<spring.boot.version>2.1.7.RELEASE</spring.boot.version> <spring.boot.version>2.2.3.RELEASE</spring.boot.version>
<node.version>v10.4.1</node.version> <node.version>v10.4.1</node.version>
<node.url>https://nodejs.org/dist/</node.url> <node.url>https://nodejs.org/dist/</node.url>
@@ -128,6 +128,7 @@
<version>${spring.boot.version}</version> <version>${spring.boot.version}</version>
<configuration> <configuration>
<skip>true</skip> <skip>true</skip>
<fork>false</fork>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -21,10 +21,25 @@
<artifactId>cds-starter-spring-boot-odata</artifactId> <artifactId>cds-starter-spring-boot-odata</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-hana</artifactId>
</dependency>
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-cloudfoundry</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -79,6 +94,7 @@
<version>${cds4j.version}</version> <version>${cds4j.version}</version>
<configuration> <configuration>
<outputDirectory>${project.basedir}/src/gen</outputDirectory> <outputDirectory>${project.basedir}/src/gen</outputDirectory>
<basePackage>cds.gen</basePackage>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>

View File

@@ -0,0 +1,105 @@
package com.sap.teched.cap.bookstore.handlers;
import java.math.BigDecimal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.Update;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.cds.CdsService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.Before;
import com.sap.cds.services.handler.annotations.ServiceName;
import com.sap.cds.services.persistence.PersistenceService;
import cds.gen.ordersservice.OrderItems;
import cds.gen.ordersservice.Orders;
import cds.gen.sap.capire.bookstore.Books;
import cds.gen.sap.capire.bookstore.Books_;
import cds.gen.sap.capire.bookstore.OrderItems_;
@Component
@ServiceName("OrdersService")
public class OrdersService implements EventHandler {
@Autowired
PersistenceService db;
@Before(event = CdsService.EVENT_CREATE, entity = "OrdersService.OrderItems")
public void validateBookAndDecreaseStock(List<OrderItems> items) {
for (OrderItems item : items) {
String bookId = item.getBookId();
Integer amount = item.getAmount();
// check if the book that should be ordered is existing
CqnSelect sel = Select.from(Books_.class).columns(b -> b.stock()).where(b -> b.ID().eq(bookId));
Books book = db.run(sel).first(Books.class).orElseThrow(() -> new ServiceException(ErrorStatuses.NOT_FOUND, "Book does not exist"));
// check if order could be fulfilled
int stock = book.getStock();
if (stock < amount) {
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Not enough books on stock");
}
// update the book with the new stock, means minus the order amount
book.setStock(stock - amount);
CqnUpdate update = Update.entity(Books_.class).data(book).where(b -> b.ID().eq(bookId));
db.run(update);
}
}
@Before(event = CdsService.EVENT_CREATE, entity = "OrdersService.Orders")
public void validateBookAndDecreaseStockViaOrders(List<Orders> orders) {
for(Orders order : orders) {
if(order.getItems() != null) {
validateBookAndDecreaseStock(order.getItems());
}
}
}
@After(event = { CdsService.EVENT_READ, CdsService.EVENT_CREATE }, entity = "OrdersService.OrderItems")
public void calculateNetAmount(List<OrderItems> items) {
for (OrderItems item : items) {
String bookId = item.getBookId();
// get the book that was ordered
CqnSelect sel = Select.from(Books_.class).where(b -> b.ID().eq(bookId));
Books book = db.run(sel).single(Books.class);
// calculate and set net amount
item.setNetAmount(book.getPrice().multiply(new BigDecimal(item.getAmount())));
}
}
@After(event = { CdsService.EVENT_READ, CdsService.EVENT_CREATE }, entity = "OrdersService.Orders")
public void calculateTotal(List<Orders> orders) {
for (Orders order : orders) {
// calculate net amount for expanded items
if(order.getItems() != null) {
calculateNetAmount(order.getItems());
}
// get all items of the order
CqnSelect selItems = Select.from(OrderItems_.class).where(i -> i.parent().ID().eq(order.getId()));
List<OrderItems> allItems = db.run(selItems).listOf(OrderItems.class);
// calculate net amount of all items
calculateNetAmount(allItems);
// calculate and set the orders total
BigDecimal total = new BigDecimal(0);
for(OrderItems item : allItems) {
total = total.add(item.getNetAmount());
}
order.setTotal(total);
}
}
}

View File

@@ -0,0 +1,165 @@
package com.sap.teched.cap.bookstore.handlers;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import cds.gen.sap.capire.bookstore.OrderItems;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class orderServiceTest {
private static final String ODATA_V4 = "/odata/v4";
private static final String BOOKS_URI = ODATA_V4 + "/BooksService/Books";
private static final String ORDERS_URI = ODATA_V4 + "/OrdersService/Orders";
private static final String ORDER_ITEMS_URI = ODATA_V4 + "/OrdersService/OrderItems";
@Autowired
private MockMvc mockMvc;
@Test
public void testGetBooks() throws Exception {
mockMvc.perform(get(BOOKS_URI).content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.value[0].title", is("Wuthering Heights")));
}
@Test
public void testCreateOrderItem() throws Exception {
final String data = "{\"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\"book_ID\": \"abed2f7a-c50e-4bc5-89fd-9a00a54b4b16\", \"amount\": 1 }";
mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(get(ORDER_ITEMS_URI).content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.value[0].parent_ID", is("12345678-aaaa-bbbb-dddd-ddddeeeeffef")));
}
@Test
public void tesValidateBookAndDecreaseStock() throws Exception {
final String data = "{\"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffee\",\"book_ID\": \"5e69a718-d86b-4461-8953-0edadcd87960\", \"amount\": 20 }";
mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isBadRequest());
}
@Test
public void tesCalculateNetAmount() throws Exception {
final String data = "{\"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffbb\",\"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\", \"amount\": 10 }";
MvcResult result = mockMvc.perform(post(ORDER_ITEMS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andReturn();
JSONObject jsonObj = new JSONObject(result.getResponse().getContentAsString());
String id = jsonObj.get("ID").toString();
mockMvc.perform(get(ORDER_ITEMS_URI+ "(" + id + ")").content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.netAmount", is(141.4)));
}
@Test
public void testCreateOrder() throws Exception {
final String orderData = "{\r\n" +
" \"items\": [{\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"abed2f7a-c50e-4bc5-89fd-9a00a54b4b16\",\r\n" +
" \"amount\": 1\r\n" +
" }, {\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\",\r\n" +
" \"amount\": 1\r\n" +
" }]\r\n" +
"}";
mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(
orderData)) .andExpect(status().isCreated());
}
@Test
public void testCalculateTotal() throws Exception {
final String orderData = "{\r\n" +
" \"items\": [{\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"abed2f7a-c50e-4bc5-89fd-9a00a54b4b16\",\r\n" +
" \"amount\": 1\r\n" +
" }, {\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\",\r\n" +
" \"amount\": 2\r\n" +
" }]\r\n" +
"}";
MvcResult result = mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(orderData))
.andReturn();
JSONObject jsonObj = new JSONObject(result.getResponse().getContentAsString());
String id = jsonObj.get("ID").toString();
mockMvc.perform(get(ORDERS_URI+ "(" + id + ")").content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.total", is(39.39)));
}
@Test
public void testValidateBookAndDecreaseStockViaOrders() throws Exception {
final String orderData = "{\r\n" +
" \"items\": [{\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"fd0c5fda-8811-4e20-bcff-3a776abc290a\",\r\n" +
" \"amount\": 300\r\n" +
" }, {\r\n" +
" \"parent_ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\",\r\n" +
" \"book_ID\": \"b7bca6dd-0497-465e-9a5a-56f244174c8c\",\r\n" +
" \"amount\": 1\r\n" +
" }]\r\n" +
"}";
mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(orderData))
.andExpect(status().isCreated());
mockMvc.perform(post(ORDERS_URI).contentType(APPLICATION_JSON_UTF8).content(orderData))
.andExpect(status().isBadRequest());
}
}

View File

@@ -0,0 +1,82 @@
package com.sap.teched.cap.bookstore.testUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sap.cds.ql.Insert;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsSimpleType;
import com.sap.cds.services.persistence.PersistenceService;
public class CsvUploader {
private final CdsModel model;
private final PersistenceService ds;
public CsvUploader(CdsModel model, PersistenceService ds) {
this.model = model;
this.ds = ds;
}
void insertContent(File file) throws FileNotFoundException, IOException {
String fileName = file.getName();
String entityName = fileName.replace("-", ".").substring(0, fileName.length() - 4);
CdsEntity entity = model.getEntity(entityName);
List<Map<String, Object>> data = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line = br.readLine();
CdsElement[] headers = getHeaders(entity, line);
while ((line = br.readLine()) != null) {
Map<String, Object> row = new HashMap<>();
String[] values = line.split(";");
for (int i = 0; i < headers.length; i++) {
CdsElement element = headers[i];
String typeName = element.getType().as(CdsSimpleType.class).getQualifiedName();
final Object value;
String txt = values[i];
if ("".equals(txt)) {
continue;
}
switch (typeName) {
case "cds.Integer":
value = Integer.valueOf(txt);
break;
case "cds.Date":
value = LocalDate.parse(txt);
break;
default:
value = txt;
}
row.put(element.getName(), value);
}
data.add(row);
}
}
Insert insert = Insert.into(entity).entries(data);
ds.run(insert);
}
private CdsElement[] getHeaders(CdsEntity entity, String line) {
String[] elementNames = line.split(";");
CdsElement[] result = new CdsElement[elementNames.length];
for (int i = 0; i < elementNames.length; i++) {
result[i] = entity.getElement(elementNames[i]);
}
return result;
}
}

View File

@@ -0,0 +1,45 @@
package com.sap.teched.cap.bookstore.testUtil;
import java.io.File;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.sap.cds.services.persistence.PersistenceService;
import com.sap.cds.services.runtime.CdsRuntime;
@Component
public class DataLoader implements ApplicationRunner {
@Autowired
private CdsRuntime runtime;
public void run(ApplicationArguments args) {
PersistenceService ds = (PersistenceService) runtime.getServiceCatalog()
.getService(PersistenceService.DEFAULT_NAME);
runtime.runInRequestContext(null, r -> {
runtime.runInChangeSetContext(c -> {
File folder = new File("../db/data");
CsvUploader uploader = new CsvUploader(runtime.getCdsModel(), ds);
for (File file : folder.listFiles()) {
try {
if(file.getName().equals("sap.capire.bookstore-Books_texts.csv"))
continue;
uploader.insertContent(file);
} catch (RuntimeException | IOException e) {
throw new RuntimeException("exception reading file " + file.getName(), e);
}
}
return null;
});
return null;
});
}
}

View File

@@ -0,0 +1,7 @@
---
spring:
profiles: default
datasource:
url: "jdbc:sqlite::memory:"
driver-class-name: org.sqlite.JDBC
initialization-mode: always

View File

@@ -10,10 +10,10 @@
"deploy": "cds deploy" "deploy": "cds deploy"
}, },
"dependencies": { "dependencies": {
"@sap/cds": "^3.0.0" "@sap/cds": "3.21.2"
}, },
"devDependencies": { "devDependencies": {
"sqlite3": "^4.1.0" "sqlite3": "^4.1.1"
}, },
"cds": { "cds": {
"build": { "build": {

View File

@@ -16,9 +16,9 @@
<!-- DEPENDENCIES VERSION --> <!-- DEPENDENCIES VERSION -->
<jdk.version>1.8</jdk.version> <jdk.version>1.8</jdk.version>
<cds.services.version>1.0.1</cds.services.version> <cds.services.version>1.2.0</cds.services.version>
<cds4j.version>1.2.1</cds4j.version> <cds4j.version>1.6.0</cds4j.version>
<spring.boot.version>2.1.7.RELEASE</spring.boot.version> <spring.boot.version>2.2.3.RELEASE</spring.boot.version>
<node.version>v10.4.1</node.version> <node.version>v10.4.1</node.version>
<node.url>https://nodejs.org/dist/</node.url> <node.url>https://nodejs.org/dist/</node.url>
@@ -128,6 +128,7 @@
<version>${spring.boot.version}</version> <version>${spring.boot.version}</version>
<configuration> <configuration>
<skip>true</skip> <skip>true</skip>
<fork>false</fork>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@@ -25,6 +25,11 @@
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -79,6 +84,7 @@
<version>${cds4j.version}</version> <version>${cds4j.version}</version>
<configuration> <configuration>
<outputDirectory>${project.basedir}/src/gen</outputDirectory> <outputDirectory>${project.basedir}/src/gen</outputDirectory>
<basePackage>cds.gen</basePackage>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>

View File

@@ -0,0 +1,124 @@
package com.sap.teched.cap.productsservice;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class productServiceTest {
private static final String ODATA_V4 = "/odata/v4";
private static final String PRODUCTS_URI = ODATA_V4 + "/AdminService/Products";
private static final String CATEGORIES_URI = ODATA_V4 + "/AdminService/Categories";
private static final String CURRENCIES_URI = ODATA_V4 + "/AdminService/Currencies";
@Autowired
private MockMvc mockMvc;
@Test
public void testProducts() throws Exception {
mockMvc.perform(get(PRODUCTS_URI).content("")).andExpect(status().isOk());
}
@Test
public void testPostProducts() throws Exception {
final String data = "{\"ID\": \"12345678-aaaa-bbbb-cccc-ddddeeeeffff\", \"title\": \"Awesome product\", \"descr\": \"It is really really awesome!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
}
@Test
public void testGetProduct() throws Exception {
final String id = "12345611-aaaa-bbbb-cccc-ddddeeeeffef";
final String data = "{\"ID\": \"12345611-aaaa-bbbb-cccc-ddddeeeeffef\", \"title\": \"Awesome book\", \"descr\": \"It is an awesome book!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(get(PRODUCTS_URI + "(" + id + ")").content("")).andExpect(status().isOk())
.andExpect(jsonPath("$.title", is("Awesome book")));
}
@Test
public void testEditProduct() throws Exception {
final String id = "12345678-aaaa-bbbb-dddd-ddddeeeeffef";
final String data = "{\"ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\", \"title\": \"Awesome book\", \"descr\": \"It is an awesome book!\"}";
final String changedData = "{\"ID\": \"12345678-aaaa-bbbb-dddd-ddddeeeeffef\", \"title\": \"Awesome book\", \"descr\": \"It is a good book! I love it\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(put(PRODUCTS_URI + "(" + id + ")").contentType(APPLICATION_JSON_UTF8).content(changedData))
.andExpect(status().isOk()).andExpect(jsonPath("$.modifiedAt", is(not(nullValue()))));
}
@Test
public void testDeleteProduct() throws Exception {
final String id = "12345678-aaaa-bbbb-cccc-ddddeeeeffdd";
final String data = "{\"ID\": \"12345678-aaaa-bbbb-cccc-ddddeeeeffdd\", \"title\": \"Bad book\", \"descr\": \"It is a very bad book!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(delete(PRODUCTS_URI + "(" + id + ")").content("")).andExpect(status().isNoContent());
mockMvc.perform(get(PRODUCTS_URI + "(" + id + ")").content("")).andExpect(status().isNotFound());
}
@Test
public void testGetProductWhenProductIDNotPresent() throws Exception {
final String data = "{\"ID\": \"12345678-aaaa-bbbb-cccc-ddddeeeeffaa\", \"title\": \"Another book\", \"descr\": \"It is bad book!\"}";
mockMvc.perform(post(PRODUCTS_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(get(PRODUCTS_URI + "(123)").content("")).andExpect(status().isBadRequest());
}
@Test
public void testPostCategories() throws Exception {
final String data = "{\"ID\": 2, \"parent_ID\": 1, \"name\": \"Fiction\"}";
mockMvc.perform(post(CATEGORIES_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
}
@Test
public void testEditCategories() throws Exception {
final String data = "{\"ID\": 3, \"parent_ID\": 1, \"name\": \"Fiction\"}";
final String changedData = "{\"ID\": 3, \"name\": \"Science Fiction\"}";
mockMvc.perform(post(CATEGORIES_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
mockMvc.perform(put(CATEGORIES_URI + "(3)").contentType(APPLICATION_JSON_UTF8).content(changedData))
.andExpect(status().isOk());
}
@Test
public void testPostCurrencies() throws Exception {
final String data = "{\"code\": \"USD\", \"symbol\": \"$\", \"name\": \"US Dollar\", \"descr\": \"United States Dollar\"}";
mockMvc.perform(post(CURRENCIES_URI).contentType(APPLICATION_JSON_UTF8).content(data))
.andExpect(status().isCreated());
}
}

View File

@@ -0,0 +1,7 @@
---
spring:
profiles: default
datasource:
url: "jdbc:sqlite::memory:"
driver-class-name: org.sqlite.JDBC
initialization-mode: always