chore: Merged fiori into bookstore and tests into subprojects

This commit is contained in:
Daniel Hutzel
2025-07-17 13:54:32 +02:00
parent dcb0c0394a
commit c10ca17546
83 changed files with 2817 additions and 538 deletions

View File

@@ -0,0 +1,53 @@
const cds = require("@sap/cds");
const { expect } = cds.test(
"serve",
"CatalogService",
"--from",
"@capire/bookshop,@capire/common",
"--in-memory"
);
describe("Consuming actions locally", () => {
let cats, CatalogService, Books, stockBefore;
const BOOK_ID = 251;
const QUANTITY = 1;
before("bootstrap the database", async () => {
CatalogService = cds.services.CatalogService;
expect(CatalogService).not.to.be.undefined;
Books = CatalogService.entities.Books;
expect(Books).not.to.be.undefined;
cats = await cds.connect.to("CatalogService");
});
beforeEach(async () => {
// Read the stock before the action is called
stockBefore = (await cats.get(Books, BOOK_ID)).stock;
});
it("calls unbound actions - basic variant using srv.send", async () => {
// Use a managed transaction to create a continuation with an authenticated user
const res1 = await cats.tx({ user: "alice" }, () => {
return cats.send("submitOrder", { book: BOOK_ID, quantity: QUANTITY });
});
expect(res1.stock).to.eql(stockBefore - QUANTITY);
});
it("calls unbound actions - named args variant", async () => {
// Use a managed transaction to create a continuation with an authenticated user
const res2 = await cats.tx({ user: "alice" }, () => {
return cats.submitOrder({ book: BOOK_ID, quantity: QUANTITY });
});
expect(res2.stock).to.eql(stockBefore - QUANTITY);
});
it("calls unbound actions - positional args variant", async () => {
// Use a managed transaction to create a continuation with an authenticated user
const res3 = await cats.tx({ user: "alice" }, () => {
return cats.submitOrder(BOOK_ID, QUANTITY);
});
expect(res3.stock).to.eql(stockBefore - QUANTITY);
});
});

View File

@@ -0,0 +1,82 @@
const cds = require('@sap/cds')
const { expect } = cds.test ('@capire/bookshop')
cds.User.default = cds.User.privileged // disable auth checks
describe('cap/samples - Consuming Services locally', () => {
it('bootstrapped the database successfully', ()=>{
const { AdminService } = cds.services
const { Authors } = AdminService.entities
expect(AdminService).to.exist
expect(Authors).to.exist
})
it('supports targets as strings or reflected defs', async () => {
const AdminService = await cds.connect.to('AdminService')
const { Authors } = AdminService.entities
expect (await SELECT.from(Authors))
// .to.eql(await SELECT.from('Authors'))
.to.eql(await AdminService.read(Authors))
.to.eql(await AdminService.read('Authors'))
.to.eql(await AdminService.run(SELECT.from(Authors)))
.to.eql(await AdminService.run(SELECT.from('Authors')))
})
it('allows reading from local services using cds.ql', async () => {
const AdminService = await cds.connect.to('AdminService')
const authors = await AdminService.read (`Authors`, a => {
a.name,
a.books((b) => {
b.title,
b.currency((c) => {
c.name, c.symbol
})
})
}).where(`name like`, 'E%')
expect(authors).to.containSubset([
{
name: 'Emily Brontë',
books: [
{
title: 'Wuthering Heights',
currency: { name: 'British Pound', symbol: '£' },
},
],
},
{
name: 'Edgar Allen Poe',
books: [
{ title: 'The Raven', currency: { name: 'US Dollar', symbol: '$' } },
{ title: 'Eleonora', currency: { name: 'US Dollar', symbol: '$' } },
],
},
])
})
it('provides CRUD-style convenience methods', async () => {})
it('uses same methods for all kind of services, including dbs', async () => {
const srv = await cds.connect.to('AdminService')
const db = await cds.connect.to('db')
const { Authors } = srv.entities
const projection = (a) => {
a.name,
a.books((b) => {
b.title,
b.currency((c) => {
c.name, c.symbol
})
})
}
const query1 = SELECT.from(Authors, projection).where(`name like`, 'E%')
const query2 = cds.read(Authors, projection).where(`name like`, 'E%')
expect(await cds.run(query1))
.to.eql(await db.run(query1))
.to.eql(await srv.run(query1))
.to.eql(await srv.read(Authors, projection).where(`name like`, 'E%'))
.to.eql(await cds.run(query2))
.to.eql(await db.run(query2))
.to.eql(await srv.run(query2))
.to.eql(await db.read(Authors, projection).where(`name like`, 'E%'))
})
})

View File

@@ -0,0 +1,15 @@
const cds = require('@sap/cds')
const { GET, POST, expect } = cds.test(__dirname+'/..')
cds.User.default = cds.User.Privileged // hard core monkey patch
describe('cap/samples - Custom Handlers', () => {
it('should reject out-of-stock orders', async () => {
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith(
/409 - 5 exceeds stock for book #201/)
const { data } = await GET`/admin/Books/201/stock/$value`
expect(data).to.equal(2)
})
})

View File

@@ -0,0 +1,123 @@
const cds = require('@sap/cds')
const { expect } = cds.test.in(__dirname,'..','..')
describe('cap/samples - Hierarchical Data', ()=>{
const csn = CDL`
entity Categories {
key ID : Integer;
name : String;
children : Composition of many Categories on children.parent = $self;
parent : Association to Categories;
}
`
const model = cds.compile.for.nodejs(csn)
const {Categories:Cats} = model.definitions
before ('bootstrap sqlite in-memory db...', async()=>{
await cds.deploy (csn) .to ('sqlite::memory:') // REVISIT: cds.compile.to.sql should accept cds.compiled.for.nodejs models
expect (cds.db) .to.exist
expect (cds.db.model) .to.exist
})
it ('supports deeply nested inserts', ()=> INSERT.into (Cats,
{ ID:100, name:'Some Cats...', children:[
{ ID:101, name:'Cat', children:[
{ ID:102, name:'Kitty', children:[
{ ID:103, name:'Kitty Cat', children:[
{ ID:104, name:'Aristocat' } ]},
{ ID:105, name:'Kitty Bat' } ]},
{ ID:106, name:'Catwoman', children:[
{ ID:107, name:'Catalina' } ]} ]},
{ ID:108, name:'Catweazle' }
]}
))
it ('should generate correct queries for expands', ()=>{
let q = SELECT.from (Cats, c => { c.ID, c.name, c.children (c => c.name) })
expect (q) .to.eql ({
SELECT: {
from: { ref:[ "Categories" ] },
columns: [
{ ref: [ "ID" ] },
{ ref: [ "name" ] },
{ ref: [ "children" ], expand: [ {ref:['name']} ] },
]
}
})
/* temp skip for release
if (q.forSQL) expect (q.forSQL()) .to.eql ({
SELECT: {
from: { ref:[ "Categories" ], as: "Categories" },
columns: [
{ ref: [ "Categories", "ID" ] },
{ ref: [ "Categories", "name" ] },
{ as: "children", SELECT: { expand: true,
one: false,
columns: [{ ref: [ "children", "name" ]}],
from: { ref:["Categories"], as: "children" },
where: [
{ref:[ "Categories", "ID" ]}, "=", {ref:[ "children", "parent_ID" ]}
],
}},
],
}
})
if (q.toSql) expect (q.toSql()) .to.eql (
`SELECT json_insert('{}',` +
`'$."ID"',ID,` +
`'$."name"',name,` +
`'$."children"',children->'$'` +
`) as _json_ FROM (` +
`SELECT Categories.ID,Categories.name,(` +
`SELECT jsonb_group_array(jsonb_insert('{}','$."name"',name)) as _json_ FROM (` +
`SELECT children.name FROM Categories as children WHERE Categories.ID = children.parent_ID` +
`)` +
`) as children FROM Categories as Categories` +
`)`
)
*/
})
it ('supports nested reads', ()=> expect (
SELECT.one.from (Cats, c=>{
c.ID, c.name.as('parent'), c.children (c=>{
c.name.as('child')
})
}) .where ({name:'Cat'})
) .to.eventually.eql (
{ ID:101, parent:'Cat', children:[
{ child:'Kitty' },
{ child:'Catwoman' },
]}
))
it ('supports deeply nested reads', ()=> expect (
SELECT.one.from (Cats, c=>{
c.ID, c.name, c.children (
c => { c.name },
{levels:3}
)
}) .where ({name:'Cat'})
) .to.eventually.eql (
{ ID:101, name:'Cat', children:[
{ name:'Kitty', children:[
{ name:'Kitty Cat', children:[
{ name:'Aristocat' }, ]}, // level 3
{ name:'Kitty Bat', children:[] }, ]},
{ name:'Catwoman', children:[
{ name:'Catalina', children:[] } ]},
]}
))
it ('supports cascaded deletes', async()=>{
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
expect (affectedRows) .to.be.greaterThan (0)
await expect (SELECT`ID,name`.from(Cats) ).to.eventually.eql ([
{ ID:100, name:'Some Cats...' },
{ ID:101, name:'Cat' },
{ ID:108, name:'Catweazle' }
])
})
})

View File

@@ -1,4 +1,4 @@
using { sap.capire.bookshop as my } from '../db/schema';
using { sap.capire.bookshop as my } from '../../db/schema';
service TestService {
entity Genres as projection on my.Genres;
}

View File

@@ -0,0 +1,5 @@
{
"devDependencies": {
"@cap-js/sqlite": "*"
}
}

View File

@@ -0,0 +1,12 @@
using { CatalogService, sap.capire.bookshop as my } from '@capire/bookshop';
using from '@capire/common';
extend service CatalogService with {
@cds.localized:false
entity BooksSans as projection on my.Books {
*, //> non-localized defaults, e.g. title
key ID,
texts.title as localized_title,
texts.locale
};
}

View File

@@ -0,0 +1,79 @@
const cds = require('@sap/cds')
const { GET, expect } = cds.test (__dirname)
cds.User.default = cds.User.Privileged // hard core monkey patch
describe('cap/samples - Localized Data', () => {
it('serves localized $metadata documents', async () => {
const { data } = await GET(`/browse/$metadata?sap-language=de`, { headers: { 'accept-language': 'de' }})
expect(data).to.contain('<Annotation Term="Common.Label" String="Währung"/>')
})
it('supports accept-language header', async () => {
const { data } = await GET(`/browse/Books?$select=title,author`, {
headers: { 'Accept-Language': 'de' },
})
expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', author: 'Emily Brontë' },
{ title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ title: 'The Raven', author: 'Edgar Allen Poe' },
{ title: 'Eleonora', author: 'Edgar Allen Poe' },
{ title: 'Catweazle', author: 'Richard Carpenter' },
])
})
it('supports queries with $expand', async () => {
const { data } = await GET(`/browse/Books?&$select=title,author&$expand=currency`, {
headers: { 'Accept-Language': 'de' },
})
expect(data.value).to.containSubset([
{ title: 'Sturmhöhe', author: 'Emily Brontë', currency: { name: 'Pfund' } },
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } },
{ title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
{ title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Yen' } },
])
})
it('supports queries with nested $expand', async () => {
const { data } = await GET(`/admin/Authors`, {
params: {
$filter: `startswith(name,'E')`,
$expand: `books(
$select=title;
$expand=currency(
$select=name,symbol
)
)`.replace(/\s/g, ''),
$select: `name`,
},
headers: { 'Accept-Language': 'de' },
})
expect(data.value).to.containSubset([
{
name: 'Emily Brontë',
books: [{ title: 'Sturmhöhe', currency: { name: 'Pfund', symbol: '£' } }],
},
{
name: 'Edgar Allen Poe',
books: [
{ title: 'The Raven', currency: { name: 'US-Dollar', symbol: '$' } },
{ title: 'Eleonora', currency: { name: 'US-Dollar', symbol: '$' } },
],
},
])
})
it('supports @cds.localized:false', async ()=>{
const { data } = await GET(`/browse/BooksSans?&$select=title,localized_title&$expand=currency&$filter=locale eq 'de' or locale eq null`, {
headers: { 'Accept-Language': 'de' },
})
expect(data.value).to.containSubset([
{ title: 'Wuthering Heights', localized_title: 'Sturmhöhe', currency: { name: 'British Pound' } },
{ title: 'Jane Eyre', currency: { name: 'British Pound' } },
{ title: 'The Raven', currency: { name: 'US Dollar' } },
{ title: 'Eleonora', currency: { name: 'US Dollar' } },
{ title: 'Catweazle', currency: { name: 'Yen' } },
])
})
})

View File

@@ -0,0 +1,74 @@
const cds = require('@sap/cds')
const { expect } = cds.test.in(__dirname,'..')
describe('cap/samples - Messaging', ()=>{
const _model = '@capire/reviews'
const Reviews = 'sap.capire.reviews.Reviews'
beforeAll(()=>{
cds.User.default = cds.User.Privileged // hard core monkey patch
})
it ('should bootstrap sqlite in-memory db', async()=>{
const db = await cds.deploy (_model) .to ('sqlite::memory:')
await db.delete(Reviews)
expect (db.model) .not.undefined
})
let srv
it ('should serve ReviewsService', async()=>{
srv = await cds.serve('ReviewsService') .from (_model)
expect (srv.name) .to.match (/ReviewsService/)
})
let N=0, received=[], M=0
it ('should add messaging event handlers', ()=>{
srv.on('reviewed', (msg)=> received.push(msg))
})
it ('should add more messaging event handlers', ()=>{
srv.on('reviewed', ()=> ++M)
})
it ('should add review', async ()=>{
const review = { subject: "201", title: "Captivating", rating: ++N }
cds._debug = 1
const response = await srv.create ('Reviews') .entries (review)
expect (response) .to.containSubset (review)
})
it ('should add more reviews', ()=> Promise.all ([
// REVISIT: mass operation should trigger one message per entry
// srv.create('Reviews').entries(
// { ID: 111 + (++N), subject: "201", title: "Captivating", rating: N },
// { ID: 111 + (++N), subject: "201", title: "Captivating", rating: N },
// { ID: 111 + (++N), subject: "201", title: "Captivating", rating: N },
// { ID: 111 + (++N), subject: "201", title: "Captivating", rating: N },
// ),
srv.create ('Reviews') .entries (
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
srv.create ('Reviews') .entries (
{ ID: String(111 + (++N)), subject: "201", title: "Captivating", rating: N }
),
]))
it ('should have received all messages', async()=> {
await new Promise((done)=>setImmediate(done))
expect(M).equals(N)
expect(received.length).equals(N)
expect(received.map(m=>m.data)).to.deep.equal([
{ count: 1, subject: '201', rating: 1 },
{ count: 2, subject: '201', rating: 1.5 },
{ count: 3, subject: '201', rating: 2 },
{ count: 4, subject: '201', rating: 2.5 },
{ count: 5, subject: '201', rating: 3 },
])
})
})

101
bookshop/test/odata.test.js Normal file
View File

@@ -0,0 +1,101 @@
const cds = require('@sap/cds')
const { GET, expect, axios } = cds.test ('@capire/bookshop')
axios.defaults.auth = { username: 'alice', password: 'admin' }
describe('cap/samples - Bookshop APIs', () => {
it('serves $metadata documents in v4', async () => {
const { headers, status, data } = await GET `/browse/$metadata`
expect(status).to.equal(200)
expect(headers).to.contain({
// 'content-type': 'application/xml', //> fails with 'application/xml;charset=utf-8', which is set by express
'odata-version': '4.0',
})
expect(headers['content-type']).to.match(/application\/xml/)
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">')
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
})
it('serves ListOfBooks?$expand=genre,currency', async () => {
const Mystery = { name: 'Mystery' }
const Romance = { name: 'Romance' }
const USD = { code: 'USD', name: 'US Dollar', descr: null, symbol: '$' }
const { data } = await GET `/browse/ListOfBooks ${{
params: { $search: 'Po', $select: `title,author`, $expand:`genre,currency` },
}}`
expect(data.value).to.containSubset([
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe', genre:Mystery, currency:USD },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe', genre:Romance, currency:USD },
])
})
describe('query options...', () => {
it('supports $search in multiple fields', async () => {
const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` },
}}`
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
])
})
it('supports $select', async () => {
const { data } = await GET(`/browse/Books`, {
params: { $select: `ID,title` },
})
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights' },
{ ID: 207, title: 'Jane Eyre' },
{ ID: 251, title: 'The Raven' },
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
})
it('supports $expand', async () => {
const { data } = await GET(`/admin/Authors`, {
params: {
$select: `name`,
$expand: `books($select=title)`,
},
})
expect(data.value).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('supports $value requests', async () => {
const { data } = await GET`/admin/Books/201/stock/$value`
expect(data).to.equal(12)
})
it('supports $top/$skip paging', async () => {
const { data: p1 } = await GET`/browse/Books?$select=title&$top=3`
expect(p1.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights' },
{ ID: 207, title: 'Jane Eyre' },
{ ID: 251, title: 'The Raven' },
])
const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
expect(p2.value).to.containSubset([
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
})
})
it('serves user info', async () => {
const { data: alice } = await GET `/user/me`
expect(alice).to.containSubset({ id: 'alice' })
const { data: joe } = await GET (`/user/me`, {auth: { username: 'joe' }})
expect(joe).to.containSubset({ id: 'joe' })
})
})

View File

@@ -0,0 +1,126 @@
const cds = require('@sap/cds/lib')
const { GET, expect, axios } = cds.test(__dirname)
// Fetch API disallows GET|HEAD requests with body
if (axios.constructor.name === 'Naxios') it = it.skip
describe ('GET w/ query in body', () => {
it ('serves CQN query objects in body', async () => {
const {data:books} = await GET ('/hcql/admin', {
headers: { 'Content-Type': 'application/json' },
data: cds.ql `SELECT from Books`
})
expect(books).to.be.an('array').of.length(5)
})
it ('serves plain CQL strings in body', async () => {
const {data:books} = await GET ('/hcql/admin', {
headers: { 'Content-Type': 'text/plain' },
data: `SELECT from Books`
})
expect(books).to.be.an('array').of.length(5)
})
it ('serves complex and deep queries', async () => {
const {data:books} = await GET ('/hcql/admin', {
headers: { 'Content-Type': 'text/plain' },
data: `SELECT from Authors {
name,
books [order by title] {
title,
genre.name as genre
}
}`
})
expect(books).to.deep.equal([
{
name: "Emily Brontë",
books: [
{ title: "Wuthering Heights", genre: 'Drama' }
]
},
{
name: "Charlotte Brontë",
books: [
{ title: "Jane Eyre", genre: 'Drama' }
]
},
{
name: "Edgar Allen Poe",
books: [
{ title: "Eleonora", genre: 'Romance' },
{ title: "The Raven", genre: 'Mystery' },
]
},
{
name: "Richard Carpenter",
books: [
{ title: "Catweazle", genre: 'Fantasy' }
]
}
])
})
})
describe ('Sluggified variants', () => {
test ('GET /Books', async () => {
const {data:books} = await GET ('/hcql/admin/Books')
expect(books).to.be.an('array').of.length(5)
expect(books.length).to.eql(5) //.of.length(5)
})
test ('GET /Books/201', async () => {
const {data:book} = await GET ('/hcql/admin/Books/201')
expect(book).to.be.an('object')
expect(book).to.have.property ('title', "Wuthering Heights")
})
test ('GET /Books { title, author.name as author }' , async () => {
const {data:books} = await GET ('/hcql/admin/Books { title, author.name as author } order by ID')
expect(books).to.deep.equal ([
{ title: "Wuthering Heights", author: "Emily Brontë" },
{ title: "Jane Eyre", author: "Charlotte Brontë" },
{ title: "The Raven", author: "Edgar Allen Poe" },
{ title: "Eleonora", author: "Edgar Allen Poe" },
{ title: "Catweazle", author: "Richard Carpenter" }
])
})
test ('GET /Books/201 w/ CQL tail in URL' , async () => {
const {data:book} = await GET ('/hcql/admin/Books/201 { title, author.name as author } order by ID')
expect(book).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
})
it ('GET /Books/201 w/ CQL fragment in body' , async () => {
const {data:book} = await GET ('/hcql/admin/Books/201', {
headers: { 'Content-Type': 'text/plain' },
data: `{ title, author.name as author }`
})
expect(book).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
})
it ('GET /Books/201 w/ CQN fragment in body' , async () => {
const {data:book} = await GET ('/hcql/admin/Books/201', {
data: cds.ql `SELECT title, author.name as author` .SELECT
})
expect(book).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
})
it ('GET /Books/201 w/ tail in URL plus CQL/CQN fragments in body' , async () => {
const {data:[b1]} = await GET ('/hcql/admin/Books where ID=201', {
data: cds.ql `SELECT title, author.name as author` .SELECT
})
expect(b1).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
const {data:[b2]} = await GET ('/hcql/admin/Books where ID=201', {
headers: { 'Content-Type': 'text/plain' },
data: `{ title, author.name as author }`
})
expect(b2).to.deep.equal ({ title: "Wuthering Heights", author: "Emily Brontë" })
})
})

View File

@@ -0,0 +1,225 @@
@server = http://localhost:4004
GET {{server}}/odata/v4/admin/Authors?
&$select=ID,name
&$expand=books($select=ID,title)
&$count=true
###
#
# The basic variant expects a CQN object passed as an application/json body
# to a POST request. This is also the fastest one, as it doesn't need CQL parsing.
# Note: $count is returned in X-Total-Count response header
#
GET {{server}}/hcql/admin
Content-Type: application/json
# Accept-Language: de
{ "SELECT": {
"from": { "ref": [ "Authors" ] },
"columns": [
{ "ref": [ "name" ] },
{ "ref": [ "books" ], "expand": [
{ "ref": [ "ID" ] },
{ "ref": [ "title" ] }
]}
],
"count": true
}}
###
POST {{server}}/hcql/browse/submitOrder?book=201&quantity=2
Authorization: Basic alice:
###
POST {{server}}/hcql/browse/submitOrder
Authorization: Basic alice:
Content-Type: application/json
{
"book": 201,
"quantity": 2
}
###
GET {{server}}/hcql/browse/submitOrder?book=201&quantity=2
Authorization: Basic alice:
###
#
# Alternatively you can pass a CQL string as plain/text body
#
GET {{server}}/hcql/admin
Content-Type: text/plain
# X-Total-Count: true
SELECT from Authors { name, books { title }}
# SELECT from Books { title, currency }
###
#
# In addition we offer convenience slug routes...
# .e.g. /srv/entity routes
#
GET {{server}}/hcql/admin/Books
###
GET {{server}}/hcql/admin/Books/201
###
GET {{server}}/hcql/admin/Books { ID, title, author.name as author }
###
GET {{server}}/hcql/admin/Books order by stock desc
Content-Type: text/plain
{ title, stock }
###
GET {{server}}/hcql/admin/Books/201 { ID, title, author.name }
###
GET {{server}}/hcql/admin/Books/201 { ID, title, author{name} }
###
POST {{server}}/hcql/admin/Books?title=The Black Cat&author_ID=101
###
POST {{server}}/hcql/admin/Books?title=The Black Cat
Content-Type: application/json
{
"author_ID": 101
}
###
POST {{server}}/hcql/admin/Books
Content-Type: application/json
{
"title": "The Black Cat",
"author": { "ID": 101 }
}
###
PUT {{server}}/hcql/admin/Books/275?title=Catastrophe
###
PATCH {{server}}/hcql/admin/Books/275
Content-Type: application/json
{
"title": "Catastrophe"
}
###
GET {{server}}/hcql/admin/Authors { name, books { ID, title }}
###
GET {{server}}/hcql/admin/Books { ID, title, author.name as author } order by ID desc
###
// ------------------------------------
POST {{server}}/hcql/admin
Content-Type: application/json
{"SELECT": { "from": { "ref": ["Books"] }}}
###
POST {{server}}/hcql/admin
Content-Type: text/plain
SELECT from Authors {
name as author,
books {
title,
stock,
price,
currency { * }
}
}
where name like '%Bro%'
order by name asc
###
#
# Simple REST-style URLs as supported as well
#
GET {{server}}/hcql/admin/Books
###
GET {{server}}/hcql/admin/Books/201
###
#
# REST-style URLs can be combined with trailing CQL in the path, in plain
# text body, or with projections sent as application/json array
#
GET {{server}}/hcql/admin/Books order by stock desc
###
GET {{server}}/hcql/admin/Books { title as book, stock } order by stock desc
###
GET {{server}}/hcql/admin/Authors
Content-Type: text/plain
Accept-Language: fr
{
ID, name as author,
books {
title,
stock,
currency { * }
}
}
where name like '%Bro%'
order by name asc
###
GET {{server}}/hcql/admin/Books/201 { title, stock }
###
GET {{server}}/hcql/admin/Books order by stock desc
Content-Type: text/plain
{ title, stock }
###
#
# CQL adaptor also provides access to the underlying CSN schema
#
GET {{server}}/hcql/admin/$csn
###
#
# CQL adaptor also supports INSERTs, UPDATEs, DELETEs ...
#
POST {{server}}/hcql/admin
Content-Type: application/jsonin wonderland
{ "INSERT": {
"into": "Books",
"entries": [{
"title": "The Black Cat",
"author": { "ID": 150 }
}]
}}
###

View File

@@ -0,0 +1,26 @@
@server = http://localhost:4004
GET {{server}}/odata/v2/admin/Authors
Authorization: Basic alice:
###
GET {{server}}/odata/v2/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
Authorization: Basic alice:
###
GET {{server}}/odata/v4/admin/Authors
Authorization: Basic alice:
###
GET {{server}}/odata/v4/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
Authorization: Basic alice:
###
GET {{server}}/rest/admin/Authors
Authorization: Basic alice:
###
GET {{server}}/rest/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
Authorization: Basic alice:
###

View File

@@ -0,0 +1,9 @@
@server = http://localhost:4004
GET {{server}}/rest/admin/Authors
Authorization: Basic alice:
###
GET {{server}}/rest/admin/Authors?$select=ID,name&$expand=books($select=ID,title)
Authorization: Basic alice:
###

View File

@@ -0,0 +1,4 @@
using { CatalogService, AdminService } from '@capire/bookstore';
annotate CatalogService with @hcql @odata @path:'browse' @requires:[];
annotate AdminService with @hcql @odata @path:'admin';