Merge branch 'main' into add/customBuildTask

This commit is contained in:
Lothar Bender
2021-12-06 10:10:14 +01:00
25 changed files with 174 additions and 184 deletions

View File

@@ -37,7 +37,7 @@
"title": "Authors service" "title": "Authors service"
}, },
{ {
"file": "bookstore/db/sqlite/index.cds", "file": "fiori/db/sqlite/index.cds",
"description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n", "description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n",
"selection": { "selection": {
"start": { "start": {
@@ -52,8 +52,8 @@
"title": "SQLite implementation" "title": "SQLite implementation"
}, },
{ {
"file": "bookstore/db/hana/index.cds", "file": "fiori/db/hana/index.cds",
"description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _bookstore_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.", "description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _fiori_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.",
"selection": { "selection": {
"start": { "start": {
"line": 7, "line": 7,
@@ -67,25 +67,34 @@
"title": "SAP HANA implementation" "title": "SAP HANA implementation"
}, },
{ {
"file": "bookstore/package.json", "file": "fiori/package.json",
"description": "#### Configuration\n\nThe `cds` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile.", "description": "#### Configuration\n\nThe `cds.requires` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\n\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile. `db-ext` is a pseudo datasource, its name doesn't matter.\n\nSee [`cds.resolve`](https://cap.cloud.sap/docs/node.js/cds-compile#cds-resolve) to learn more about how models are found.",
"line": 12, "selection": {
"start": {
"line": 41,
"character": 1
},
"end": {
"line": 48,
"character": 1
}
},
"title": "Configuration" "title": "Configuration"
}, },
{ {
"file": "bookstore/package.json", "file": "fiori/package.json",
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch bookstore\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n", "description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
"line": 30, "line": 43,
"title": "Run with SQLite" "title": "Run with SQLite"
}, },
{ {
"file": "bookstore/package.json", "file": "fiori/package.json",
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd bookstore; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd bookstore; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.", "description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
"line": 33, "line": 46,
"title": "Run with SAP HANA" "title": "Run with SAP HANA"
}, },
{ {
"file": "bookstore/test/requests.http", "file": "fiori/test/requests.http",
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).", "description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
"line": 72, "line": 72,
"selection": { "selection": {
@@ -104,6 +113,5 @@
"title": "Wrap-up", "title": "Wrap-up",
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA." "description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
} }
], ]
"ref": "main"
} }

View File

@@ -3,7 +3,3 @@ service AdminService @(requires:'admin') {
entity Books as projection on my.Books; entity Books as projection on my.Books;
entity Authors as projection on my.Authors; entity Authors as projection on my.Authors;
} }
//Since ID is computed, we can hide the popup for ID on Create
annotate AdminService.Books with { ID @Core.Computed; }
annotate AdminService.Authors with { ID @Core.Computed; }

View File

@@ -1,42 +1,16 @@
using {sap.capire.bookshop as my} from '../db/schema'; using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
service CatalogService @(path : '/browse') { /** For displaying lists of Books */
@readonly entity ListOfBooks as projection on Books
excluding { descr };
/** /** For display in details pages */
* For displaying lists of Books @readonly entity Books as projection on my.Books { *,
*/ author.name as author
@readonly } excluding { createdBy, modifiedBy };
entity ListOfBooks as projection on Books excluding {
descr
};
/** @requires: 'authenticated-user'
* For display in details pages action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
*/ event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
@readonly
entity Books as projection on my.Books {
* , author.name as authorName
} excluding {
createdBy,
modifiedBy
};
@readonly
entity Authors as projection on my.Authors {
* , books : redirected to Books
} excluding {
createdBy,
modifiedBy
};
@requires : 'authenticated-user'
action submitOrder(book : Books:ID, quantity : Integer) returns {
stock : Integer
};
event OrderedBook : {
book : Books:ID;
quantity : Integer;
buyer : String
};
} }

View File

@@ -7,13 +7,12 @@ class CatalogService extends cds.ApplicationService { init(){
// Reduce stock of ordered books if available stock suffices // Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => { this.on ('submitOrder', async req => {
const {book,quantity} = req.data const {book,quantity} = req.data
if (quantity < 1) return req.reject (400,`quantity has to be 1 or more`)
let {stock} = await SELECT `stock` .from (Books,book) let {stock} = await SELECT `stock` .from (Books,book)
if (stock >= quantity) { if (quantity > stock) return req.reject (409,`${quantity} exceeds stock for book #${book}`)
await UPDATE (Books,book) .with (`stock -=`, quantity) await UPDATE (Books,book) .with ({ stock: stock -= quantity })
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id }) await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
return { stock } return { stock }
}
else return req.error (409,`${quantity} exceeds stock for book #${book}`)
}) })
// Add some discount for overstocked books // Add some discount for overstocked books

View File

@@ -32,6 +32,19 @@ GET {{server}}/admin/Authors?
# &sap-language=de # &sap-language=de
Authorization: Basic alice: Authorization: Basic alice:
### ------------------------------------------------------------------------
# Create Author
POST {{server}}/admin/Authors
Content-Type: application/json;IEEE754Compatible=true
Authorization: Basic alice:
{
"ID": 112,
"name": "Shakespeeeeere",
"age": 22
}
### ------------------------------------------------------------------------ ### ------------------------------------------------------------------------
# Create book # Create book
POST {{server}}/admin/Books POST {{server}}/admin/Books

View File

@@ -1,8 +0,0 @@
using { sap.capire.bookshop } from '@capire/bookshop';
// Forward-declare calculated fields to be filled in database-specific ways
// TODO find a better way to have 'default' fields that still can be overwritten.
extend bookshop.Authors with {
virtual age: Integer;
virtual lifetime: String;
}

View File

@@ -1,4 +1,2 @@
namespace sap.capire.bookshop; //> important for reflection namespace sap.capire.bookshop; //> important for reflection
using from './srv/mashup';
using from '@capire/bookshop';
using from './db/schema';

View File

@@ -25,13 +25,7 @@
"[production]": { "kind": "enterprise-messaging" } "[production]": { "kind": "enterprise-messaging" }
}, },
"db": { "db": {
"kind": "sql", "kind": "sql"
"[development]": {
"model": "db/sqlite"
},
"[production]": {
"model": "db/hana"
}
} }
}, },
"log": { "service": true } "log": { "service": true }

View File

@@ -0,0 +1,47 @@
using {AdminService} from '@capire/bookshop';
annotate AdminService.Authors with @odata.draft.enabled;
////////////////////////////////////////////////////////////////////////////
//
// Authors Object Page
//
annotate AdminService.Authors with @(UI : {
HeaderInfo : {
TypeName : 'Author',
TypeNamePlural : 'Authors',
Description : {Value : lifetime}
},
Facets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Books}',
Target : 'books/@UI.LineItem'
},
],
FieldGroup #Details : {Data : [
{Value : placeOfBirth},
{Value : placeOfDeath},
{Value : dateOfBirth},
{Value : dateOfDeath},
{
Value : age,
Label : '{i18n>Age}'
},
]},
});
// Workaround to avoid errors for unknown db-specific calculated fields above
extend sap.capire.bookshop.Authors with {
virtual age : Integer;
virtual lifetime : String;
}
// Workaround for Fiori popup for asking user to enter a new UUID on Create
annotate AdminService.Authors with { ID @Core.Computed; }

View File

@@ -3,15 +3,15 @@
"sap.app": { "sap.app": {
"id": "authors", "id": "authors",
"type": "application", "type": "application",
"title": "Browse Authors", "title": "Manage Authors",
"description": "Sample Application", "description": "Sample Application",
"i18n": "i18n/i18n.properties", "i18n": "i18n/i18n.properties",
"applicationVersion": { "applicationVersion": {
"version": "1.0.0" "version": "1.0.0"
}, },
"dataSources": { "dataSources": {
"CatalogService": { "AdminService": {
"uri": "/browse/", "uri": "/admin/",
"type": "OData", "type": "OData",
"settings": { "settings": {
"odataVersion": "4.0" "odataVersion": "4.0"
@@ -41,7 +41,7 @@
"subTitle": "{{appSubTitle}}", "subTitle": "{{appSubTitle}}",
"icon": "sap-icon://SAP-icons-TNT/user", "icon": "sap-icon://SAP-icons-TNT/user",
"indicatorDataSource": { "indicatorDataSource": {
"dataSource": "CatalogService", "dataSource": "AdminService",
"path": "Authors/$count", "path": "Authors/$count",
"refresh": 1800 "refresh": 1800
} }
@@ -62,7 +62,7 @@
"uri": "i18n/i18n.properties" "uri": "i18n/i18n.properties"
}, },
"": { "": {
"dataSource": "CatalogService", "dataSource": "AdminService",
"settings": { "settings": {
"synchronizationMode": "None", "synchronizationMode": "None",
"operationMode": "Server", "operationMode": "Server",

View File

@@ -40,27 +40,6 @@ annotate AdminService.Books with @(
} }
); );
annotate AdminService.Authors with @(
UI: {
HeaderInfo: {
Description: {Value: lifetime}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
],
FieldGroup#Details: {
Data: [
{Value: placeOfBirth},
{Value: placeOfDeath},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: age, Label: '{i18n>Age}'},
]
},
}
);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@@ -93,3 +72,6 @@ using { sap } from '@sap/cds/common';
extend service AdminService { extend service AdminService {
@readonly entity Languages as projection on sap.common.Languages; @readonly entity Languages as projection on sap.common.Languages;
} }
// Workaround for Fiori popup for asking user to enter a new UUID on Create
annotate AdminService.Books with { ID @Core.Computed; }

View File

@@ -1,6 +1,6 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) { sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
"use strict"; "use strict";
return AppComponent.extend("admin.Component", { return AppComponent.extend("books.Component", {
metadata: { manifest: "json" } metadata: { manifest: "json" }
}); });
}); });

View File

@@ -1,7 +1,7 @@
{ {
"_version": "1.8.0", "_version": "1.8.0",
"sap.app": { "sap.app": {
"id": "admin", "id": "books",
"type": "application", "type": "application",
"title": "Manage Books", "title": "Manage Books",
"description": "Sample Application", "description": "Sample Application",

View File

@@ -19,14 +19,6 @@
"title": "Browse Books", "title": "Browse Books",
"targetURL": "#Books-display" "targetURL": "#Books-display"
} }
},
{
"id": "BrowseAuthors",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Authors",
"targetURL": "#Authors-display"
}
} }
] ]
}, },
@@ -45,6 +37,14 @@
"targetURL": "#Books-manage" "targetURL": "#Books-manage"
} }
}, },
{
"id": "ManageAuthors",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Authors",
"targetURL": "#Authors-display"
}
},
{ {
"id": "ManageOrders", "id": "ManageOrders",
"tileType": "sap.ushell.ui.tile.StaticTile", "tileType": "sap.ushell.ui.tile.StaticTile",
@@ -75,10 +75,10 @@
"signature": { "signature": {
"parameters": { "parameters": {
"Books.ID": { "Books.ID": {
"renameTo": "ID" "renameTo": "ID"
}, },
"Authors.books.ID": { "Authors.books.ID": {
"renameTo": "ID" "renameTo": "ID"
} }
}, },
"additionalParameters": "ignored" "additionalParameters": "ignored"
@@ -104,7 +104,7 @@
"resolutionResult": { "resolutionResult": {
"applicationType": "SAPUI5", "applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=authors", "additionalInformation": "SAPUI5.Component=authors",
"url": "/authors/webapp" "url": "/admin-authors/webapp"
} }
}, },
"ManageBooks": { "ManageBooks": {
@@ -117,8 +117,8 @@
}, },
"resolutionResult": { "resolutionResult": {
"applicationType": "SAPUI5", "applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=admin", "additionalInformation": "SAPUI5.Component=books",
"url": "/admin/webapp" "url": "/admin-books/webapp"
} }
}, },
"ManageOrders": { "ManageOrders": {

View File

@@ -1,30 +0,0 @@
using CatalogService from '@capire/bookshop';
////////////////////////////////////////////////////////////////////////////
//
// Authors Object Page
//
annotate CatalogService.Authors with @(UI : {
HeaderInfo : {
TypeName : 'Author',
TypeNamePlural : 'Authors',
Description : {Value : name}
},
HeaderFacets : [{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Description}',
Target : '@UI.FieldGroup#Descr'
}, ],
Facets : [{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : 'books/@UI.LineItem'
}, ],
FieldGroup #Descr : {Data : [
{Value : name},
{Value : dateOfBirth},
{Value : dateOfDeath},
{Value : placeOfBirth},
{Value : placeOfDeath},
]},
});

View File

@@ -8,7 +8,7 @@ annotate CatalogService.Books with @(UI : {
HeaderInfo : { HeaderInfo : {
TypeName : 'Book', TypeName : 'Book',
TypeNamePlural : 'Books', TypeNamePlural : 'Books',
Description : {Value : authorName} Description : {Value : author}
}, },
HeaderFacets : [{ HeaderFacets : [{
$Type : 'UI.ReferenceFacet', $Type : 'UI.ReferenceFacet',
@@ -47,7 +47,7 @@ annotate CatalogService.Books with @(UI : {
Label : '{i18n>Title}' Label : '{i18n>Title}'
}, },
{ {
Value : author.ID, Value : author,
Label : '{i18n>Author}' Label : '{i18n>Author}'
}, },
{Value : genre.name}, {Value : genre.name},

View File

@@ -2,8 +2,8 @@
This model controls what gets served to Fiori frontends... This model controls what gets served to Fiori frontends...
*/ */
using from './admin/fiori-service'; using from './admin-authors/fiori-service';
using from './admin-books/fiori-service';
using from './browse/fiori-service'; using from './browse/fiori-service';
using from './authors/fiori-service';
using from './common'; using from './common';
using from '@capire/bookstore/srv/mashup'; using from '@capire/bookstore/srv/mashup';

View File

@@ -2,7 +2,7 @@
// Add Author.age and .lifetime with a DB-specific function // Add Author.age and .lifetime with a DB-specific function
// //
using { AdminService } from '../schema'; using { AdminService } from '@capire/bookshop';
extend projection AdminService.Authors with { extend projection AdminService.Authors with {
YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer, YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer,

View File

@@ -2,7 +2,7 @@
// Add Author.age and .lifetime with a DB-specific function // Add Author.age and .lifetime with a DB-specific function
// //
using { AdminService } from '../schema'; using { AdminService } from '@capire/bookshop';
extend projection AdminService.Authors with { extend projection AdminService.Authors with {
strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age: Integer, strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age: Integer,

View File

@@ -25,9 +25,26 @@
"model": "@capire/orders" "model": "@capire/orders"
}, },
"messaging": { "messaging": {
"[production]": { "kind": "enterprise-messaging" }, "[production]": {
"[development]": { "kind": "file-based-messaging" }, "kind": "enterprise-messaging"
"[hybrid!]": { "kind": "enterprise-messaging-shared" } },
"[development]": {
"kind": "file-based-messaging"
},
"[hybrid!]": {
"kind": "enterprise-messaging-shared"
}
},
"db": {
"kind": "sql"
},
"db-ext": {
"[development]": {
"model": "db/sqlite"
},
"[production]": {
"model": "db/hana"
}
}, },
"hana": { "hana": {
"deploy-format": "hdbtable" "deploy-format": "hdbtable"

View File

@@ -11,39 +11,39 @@ describe('Localized Data', () => {
}) })
it('supports sap-language param', async () => { it('supports sap-language param', async () => {
const { data } = await GET(`/browse/Books?$select=title,authorName` + '&sap-language=de') const { data } = await GET(`/browse/Books?$select=title,author` + '&sap-language=de')
expect(data.value).to.containSubset([ expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', authorName: 'Emily Brontë' }, { title: 'Sturmhöhe', author: 'Emily Brontë' },
{ title: 'Jane Eyre', authorName: 'Charlotte Brontë' }, { title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ title: 'The Raven', authorName: 'Edgar Allen Poe' }, { title: 'The Raven', author: 'Edgar Allen Poe' },
{ title: 'Eleonora', authorName: 'Edgar Allen Poe' }, { title: 'Eleonora', author: 'Edgar Allen Poe' },
{ title: 'Catweazle', authorName: 'Richard Carpenter' }, { title: 'Catweazle', author: 'Richard Carpenter' },
]) ])
}) })
it('supports accept-language header', async () => { it('supports accept-language header', async () => {
const { data } = await GET(`/browse/Books?$select=title,authorName`, { const { data } = await GET(`/browse/Books?$select=title,author`, {
headers: { 'Accept-Language': 'de' }, headers: { 'Accept-Language': 'de' },
}) })
expect(data.value).to.containSubset([ expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', authorName: 'Emily Brontë' }, { title: 'Sturmhöhe', author: 'Emily Brontë' },
{ title: 'Jane Eyre', authorName: 'Charlotte Brontë' }, { title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ title: 'The Raven', authorName: 'Edgar Allen Poe' }, { title: 'The Raven', author: 'Edgar Allen Poe' },
{ title: 'Eleonora', authorName: 'Edgar Allen Poe' }, { title: 'Eleonora', author: 'Edgar Allen Poe' },
{ title: 'Catweazle', authorName: 'Richard Carpenter' }, { title: 'Catweazle', author: 'Richard Carpenter' },
]) ])
}) })
it('supports queries with $expand', async () => { it('supports queries with $expand', async () => {
const { data } = await GET(`/browse/Books?&$select=title,authorName&$expand=currency`, { const { data } = await GET(`/browse/Books?&$select=title,author&$expand=currency`, {
headers: { 'Accept-Language': 'de' }, headers: { 'Accept-Language': 'de' },
}) })
expect(data.value).to.containSubset([ expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', authorName: 'Emily Brontë', currency: { name: 'Pfund' } }, { title: 'Sturmhöhe', author: 'Emily Brontë', currency: { name: 'Pfund' } },
{ title: 'Jane Eyre', authorName: 'Charlotte Brontë', currency: { name: 'Pfund' } }, { title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } },
{ title: 'The Raven', authorName: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } }, { title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Eleonora', authorName: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } }, { title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Catweazle', authorName: 'Richard Carpenter', currency: { name: 'Yen' } }, { title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Yen' } },
]) ])
}) })

View File

@@ -19,13 +19,13 @@ describe('OData Protocol', () => {
it('supports $search in multiple fields', async () => { it('supports $search in multiple fields', async () => {
const { data } = await GET `/browse/Books ${{ const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,authorName` }, params: { $search: 'Po', $select: `title,author` },
}}` }}`
expect(data.value).to.eql([ expect(data.value).to.eql([
{ ID: 201, title: 'Wuthering Heights', authorName: 'Emily Brontë' }, { ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', authorName: 'Charlotte Brontë' }, { ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ ID: 251, title: 'The Raven', authorName: 'Edgar Allen Poe' }, { ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
{ ID: 252, title: 'Eleonora', authorName: 'Edgar Allen Poe' }, { ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
]) ])
}) })