diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml index 0373022..335894f 100644 --- a/.github/workflows/check-changelog.yml +++ b/.github/workflows/check-changelog.yml @@ -1,14 +1,14 @@ -# name: Check Changelog -# on: -# pull_request: -# types: [assigned, opened, synchronize, reopened, labeled, unlabeled] -# branches: -# - main -# jobs: -# Check-Changelog: -# name: Check Changelog Action -# runs-on: ubuntu-latest -# steps: -# - uses: tarides/changelog-check-action@v3 -# with: -# changelog: CHANGELOG.md +name: Check Changelog +on: + pull_request: + types: [assigned, opened, synchronize, reopened, labeled, unlabeled] + branches: + - main +jobs: + Check-Changelog: + name: Check Changelog Action + runs-on: ubuntu-latest + steps: + - uses: tarides/changelog-check-action@v3 + with: + changelog: CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bccb93..9f1d793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,8 @@ - The format is based on [Keep a Changelog](https://keepachangelog.com/). - This project adheres to [Semantic Versioning](https://semver.org/). -## Version 1.0.0 - TBD +## Version 0.1.0 - 2026-03-27 ### Added -### Changed - -### Fixed - -### Removed +- Initial release diff --git a/README.md b/README.md index 37b095d..489260d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/process)](https://api.reuse.software/info/github.com/cap-js/process) +CAP Plugin to interact with SAP Build Process Automation to manage processes. + ## Table of Contents - [Setup](#setup) diff --git a/eslint.config.mjs b/eslint.config.mjs index b969ea3..d63c26c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,7 +3,7 @@ import tseslint from 'typescript-eslint'; export default [ { - ignores: ['dist/**', 'gen/**', 'node_modules/**', '@cds-models/**'], + ignores: ['dist/**', 'gen/**', 'node_modules/**', '@cds-models/**', 'tests/sample/**'], }, ...cds, ...tseslint.configs.recommended, diff --git a/package.json b/package.json index dee5a4a..c9c2d65 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@cap-js/process", "version": "0.1.0", - "description": "", + "description": "CAP Plugin to interact with SAP Build Process Automation to manage processes.", "main": "cds-plugin.js", "files": [ "cds-plugin.js", @@ -36,8 +36,10 @@ "import:process:importSimple": "cd tests/bookshop && cds import --from process --name eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs --force" }, "keywords": [], - "author": "", - "license": "ISC", + "author": "SAP SE (https://www.sap.com)", + "repository": "cap-js/process", + "homepage": "https://cap.cloud.sap", + "license": "Apache-2.0", "type": "commonjs", "peerDependencies": { "@sap/cds": ">=9" @@ -49,7 +51,8 @@ "#cds-models/*": "./@cds-models/*/index.js" }, "workspaces": [ - "tests/bookshop" + "tests/bookshop", + "tests/sample/status-management" ], "dependencies": { "@sap-cloud-sdk/connectivity": "^4.5.1", diff --git a/tests/sample/status-management/LICENSE b/tests/sample/status-management/LICENSE new file mode 100644 index 0000000..1653b64 --- /dev/null +++ b/tests/sample/status-management/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tests/sample/status-management/README.md b/tests/sample/status-management/README.md new file mode 100644 index 0000000..57416fe --- /dev/null +++ b/tests/sample/status-management/README.md @@ -0,0 +1,240 @@ +# Status Management Bookshop + +A CAP sample application demonstrating how to integrate SAP Build Process Automation (SBPA) workflows into a bookshop scenario using the `@cap-js/process` plugin. The project showcases two distinct integration patterns -- **declarative** and **programmatic** -- for managing process lifecycles. + +## Overview + +The application consists of two SAP Fiori Elements apps, each backed by its own CAP service: + +| App | Service | Path | Purpose | +| ------------------ | ---------------- | -------------- | ---------------------------------------------------------------------------- | +| **Manage Books** | `BooksService` | `/api/books` | CRUD for books with an approval workflow triggered when the price exceeds 50 | +| **Manage Authors** | `AuthorsService` | `/api/authors` | CRUD for authors with a verification workflow triggered on every new author | + +A third read-only service (`CatalogService` at `/browse`) exposes books for public browsing. + +## Processes + +
+ +Book approval process + + +``` + ┌────────────────────────────────────────┐ + │ Process Start │ + └───────────────────┬────────────────────┘ + │ + ┌───────────────────▼────────────────────┐ + │ Set status to 'Manager Approval │ + │ Pending' │ + └───────────────────┬────────────────────┘ + │ + ┌───────────────────▼────────────────────┐ + │ Manager Approval │ + └──────────────┬──────────────┬──────────┘ + │ │ + [Approve] [Reject] + │ │ + ┌──────────────▼────────┐ ┌──▼──────────────────────────┐ + │ status = 'Manager │ │ status = 'Manager rejected │ + │ Approved, Author │ │ request' │ + │ Approval Pending' │ │ isApproved = false │ + └──────────────┬────────┘ └──┬──────────────────────────┘ + │ │ + ┌──────────────▼────────┐ │ + │ Author Approval │ │ + └──────┬───────────┬────┘ │ + │ │ │ + [Approve] [Reject] │ + │ │ │ + ┌─────────────────▼───┐ ┌────▼──────────────────────────┐ + │ status = 'Manager │ │ status = 'Manager approved, │ + │ and Author │ │ but Author rejected request' │ + │ Approved' │ │ isApproved = false │ + │ isApproved = true │ └────┬──────────────────────────┘ + └─────────────────┬───┘ │ │ + └─────┬─────┘ │ + │ │ + └────────┬───────┘ + │ + ┌──────────▼──────────┐ + │ End │ + └─────────────────────┘ +``` + +
+ +
+ + +Author verification process + + +``` + ┌─────────────────────────────────────────┐ + │ Process Start │ + └──────────────────┬──────────────────────┘ + │ + ┌──────────────────▼──────────────────────┐ + │ status = "Verification Pending" │ + └──────────────────┬──────────────────────┘ + │ + ┌──────────────────▼──────────────────────┐ + │ Author Verification Approval │ + └──────────┬──────────────────────┬───────┘ + │ │ + [Verify] [Reject] + │ │ + ┌───────────────▼──────────┐ ┌────────▼────────────────────┐ + │ status = "Author │ │ status = "Author was not │ + │ verified" │ │ verified" │ + │ isVerified = true │ │ isVerified = false │ + └───────────────┬──────────┘ └────────┬────────────────────┘ + │ │ + └──────────┬───────────┘ + │ + ┌─────────────────────▼─────────────────────┐ + │ End │ + └───────────────────────────────────────────┘ +``` + +
+ +## Project Structure + +``` + +db/ +schema.cds # Domain model: Books, Authors, Genres +data/ # CSV seed data +srv/ +books-service.cds # BooksService definition +books-service.js # Books handler: approval status enrichment +books-constraints.cds # Input validation for Books and Genres +authors-service.cds # AuthorsService definition +authors-service.js # Authors handler: verification lifecycle + status enrichment +authors-constraints.cds # Input validation for Authors +books-process.cds # Declarative BPM annotations for Books +cat-service.cds # CatalogService (read-only browse) +cat-service.js # CatalogService handler +external/ # Generated process service definitions (do not edit) +app/ +services.cds # Imports annotations from both apps +books/ # Fiori Elements app for Manage Books +authors/ # Fiori Elements app for Manage Authors + +``` + +## How the `@cap-js/process` Plugin Is Used + +The [`@cap-js/process`](https://github.com/cap-js/process) plugin provides a CAP-native way to interact with SAP Build Process Automation. It generates typed service definitions from SBPA process definitions and offers both declarative CDS annotations and a programmatic API for managing process instances. + +### Pattern 1: Declarative Process Integration (Books) + +The book approval process is managed entirely through CDS annotations in `srv/books-process.cds`, with no JavaScript needed for start/cancel: + +```cds +annotate BooksService.Books with @( + bpm.process.businessKey: (ID), + bpm.process.start : { + id: 'eu12...bookApprovalProcess', + on: 'CREATE', + inputs: [ + { path: $self.ID, as: 'entityid' }, + { path: $self.title, as: 'booktitle' }, + { path: $self.descr, as: 'description' }, + $self.author.name, + $self.author.dateOfBirth, + $self.price, + ], + if: (price > 50) + }, + bpm.process.cancel : { + on: 'UPDATE', + if: (price <= 50) + } +); +``` + +- **`@bpm.process.businessKey`** -- Correlates process instances back to entities using the book's ID. +- **`@bpm.process.start`** -- Automatically starts the approval process on `CREATE` when the price exceeds 50. Entity fields are mapped to process inputs, with support for renaming (`as`) and navigation paths (`$self.author.name`). +- **`@bpm.process.cancel`** -- Automatically cancels the running process on `UPDATE` when the price drops to 50 or below. + +### Pattern 2: Programmatic Process Integration (Authors) + +The author verification process is managed entirely in JavaScript (`srv/authors-service.js`), giving full control over the lifecycle: + +```js +const authorProcess = await cds.connect.to(AUTHOR_PROCESS); + +// Start verification on author creation +this.after('CREATE', Authors, async (author, req) => { + await authorProcess.start({ + entityid: author.ID, + authorname: author.name, + dateofbirth: author.dateOfBirth ?? '', + placeofbirth: author.placeOfBirth ?? '', + }); +}); + +// Cancel verification on author deletion +this.after('DELETE', Authors, async (author, req) => { + if (!author.ID) return; + + const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); + if (instances.length > 0) { + await authorProcess.cancel({ businessKey: author.ID, cascade: true }); + } +}); +``` + +### Process Status Enrichment (Both Apps) + +Both services use the same pattern to display live process status in the UI. Virtual fields (`processStatus`, `isApproved`, `processCriticality` for Books; `verificationStatus`, `isVerified`, `verificationCriticality` for Authors) are declared in the CDS projections and populated in `after('READ')` handlers: + +1. **Look up** the process instance via `getInstancesByBusinessKey(businessKey, statusFilters)` +2. **Based on status:** + - `RUNNING` -- Fetch current step via `getAttributes(instanceId)` + - `COMPLETED` -- Fetch final result via `getOutputs(instanceId)` + - `CANCELED` -- Show cancellation message +3. **Set criticality** for Fiori UI coloring (0 = neutral, 1 = negative/red, 2 = warning/yellow, 3 = positive/green) + +### Declarative vs Programmatic: When to Use Which + +| Aspect | Declarative (Books) | Programmatic (Authors) | +| -------------------- | ------------------------------------------------- | -------------------------------------------------------------- | +| Process start | CDS annotation `@bpm.process.start` | `processService.start(inputs)` in JS | +| Process cancel | CDS annotation `@bpm.process.cancel` | `processService.cancel(options)` in JS | +| Conditional triggers | CDS expression: `if: (price > 50)` | Custom JS logic | +| Input mapping | Declarative `inputs` array with `path`/`as` | Manual JS object construction | +| Best for | Standard workflows with simple trigger conditions | Complex logic, custom error handling, multi-step orchestration | + +## Getting Started + +### Prerequisites + +- Node.js >= 18 +- SAP CDS CLI (`npm i -g @sap/cds-dk`) + +### Install and Run + +```sh +npm install +cds watch +``` + +Open http://localhost:4004 in your browser. From there: + +- **Manage Books**: http://localhost:4004/books/index.html +- **Manage Authors**: http://localhost:4004/authors/index.html + +### Hybrid Testing with SBPA + +To test against a real SAP Build Process Automation instance, configure the binding in `.cdsrc-private.json` and run: + +```sh +cds watch --profile hybrid +``` + +For more information how to setup the plugin, please refer to the [plugins documentation](https://github.com/cap-js/process). diff --git a/tests/sample/status-management/app/authors/.appGenInfo.json b/tests/sample/status-management/app/authors/.appGenInfo.json new file mode 100644 index 0000000..0b197f5 --- /dev/null +++ b/tests/sample/status-management/app/authors/.appGenInfo.json @@ -0,0 +1,33 @@ +{ + "generationParameters": { + "generationDate": "Tue Mar 24 2026 16:30:55 GMT+0100 (Central European Standard Time)", + "generatorPlatform": "Visual Studio Code", + "serviceType": "Local CAP", + "metadataFilename": "", + "serviceUrl": "http://localhost:4004/admin/", + "appName": "authors", + "appTitle": "Manage Authors", + "appDescription": "An SAP Fiori application.", + "appNamespace": "", + "ui5Theme": "sap_horizon", + "ui5Version": "1.146.0", + "enableCodeAssist": false, + "enableEslint": false, + "enableTypeScript": false, + "showMockDataInfo": false, + "generatorVersion": "1.20.0", + "template": "List Report Page V4", + "generatorName": "SAP Fiori Application Generator", + "entityRelatedConfig": [ + { + "type": "Main Entity", + "value": "Authors" + }, + { + "type": "Navigation Entity", + "value": "None" + } + ], + "launchText": "To launch the generated app, start your CAP project: and navigate to the following location in your browser:\n\nhttp://localhost:4004/authors/index.html" + } +} diff --git a/tests/sample/status-management/app/authors/README.md b/tests/sample/status-management/app/authors/README.md new file mode 100644 index 0000000..c67f0fc --- /dev/null +++ b/tests/sample/status-management/app/authors/README.md @@ -0,0 +1,35 @@ +## Application Details + +| | +| -------------------------------------------------------------------------------------------------- | +| **Generation Date and Time**
Tue Mar 24 2026 16:30:55 GMT+0100 (Central European Standard Time) | +| **App Generator**
SAP Fiori Application Generator | +| **App Generator Version**
1.20.0 | +| **Generation Platform**
Visual Studio Code | +| **Template Used**
List Report Page V4 | +| **Service Type**
Local CAP | +| **Service URL**
http://localhost:4004/admin/ | +| **Module Name**
authors | +| **Application Title**
Manage Authors | +| **Namespace**
| +| **UI5 Theme**
sap_horizon | +| **UI5 Version**
1.146.0 | +| **Enable Code Assist Libraries**
False | +| **Enable TypeScript**
False | +| **Add Eslint configuration**
False | +| **Main Entity**
Authors | +| **Navigation Entity**
None | + +## authors + +An SAP Fiori application. + +### Starting the generated app + +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: + +http://localhost:4004/authors/index.html + +#### Pre-requisites: + +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) diff --git a/tests/sample/status-management/app/authors/annotations.cds b/tests/sample/status-management/app/authors/annotations.cds new file mode 100644 index 0000000..044a5e2 --- /dev/null +++ b/tests/sample/status-management/app/authors/annotations.cds @@ -0,0 +1,80 @@ +using AuthorsService as service from '../../srv/authors-service'; +annotate service.Authors with @( + UI.FieldGroup #GeneratedGroup : { + $Type : 'UI.FieldGroupType', + Data : [ + { + $Type : 'UI.DataField', + Label : 'Name', + Value : name, + }, + { + $Type : 'UI.DataField', + Label : 'Date of Birth', + Value : dateOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'Date of Death', + Value : dateOfDeath, + }, + { + $Type : 'UI.DataField', + Label : 'Place of Birth', + Value : placeOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'Place of Death', + Value : placeOfDeath, + }, + { + $Type : 'UI.DataField', + Label : 'Verification Status', + Value : verificationStatus, + Criticality : verificationCriticality, + }, + { + $Type : 'UI.DataField', + Label : 'Verified', + Value : isVerified, + }, + ], + }, + UI.Facets : [ + { + $Type : 'UI.ReferenceFacet', + ID : 'GeneratedFacet1', + Label : 'General Information', + Target : '@UI.FieldGroup#GeneratedGroup', + }, + ], + UI.LineItem : [ + { + $Type : 'UI.DataField', + Label : 'Name', + Value : name, + }, + { + $Type : 'UI.DataField', + Label : 'Date of Birth', + Value : dateOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'Place of Birth', + Value : placeOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'Verification Status', + Value : verificationStatus, + Criticality : verificationCriticality, + }, + { + $Type : 'UI.DataField', + Label : 'Verified', + Value : isVerified, + }, + ], +); diff --git a/tests/sample/status-management/app/authors/package.json b/tests/sample/status-management/app/authors/package.json new file mode 100644 index 0000000..38c9414 --- /dev/null +++ b/tests/sample/status-management/app/authors/package.json @@ -0,0 +1,19 @@ +{ + "name": "authors", + "version": "0.0.1", + "description": "An SAP Fiori application.", + "keywords": [ + "ui5", + "openui5", + "sapui5" + ], + "main": "webapp/index.html", + "dependencies": {}, + "devDependencies": { + "@ui5/cli": "^4.0.33", + "@sap/ux-ui5-tooling": "1" + }, + "scripts": { + "deploy-config": "npx -p @sap/ux-ui5-tooling fiori add deploy-config cf" + } +} diff --git a/tests/sample/status-management/app/authors/ui5.yaml b/tests/sample/status-management/app/authors/ui5.yaml new file mode 100644 index 0000000..2f5e482 --- /dev/null +++ b/tests/sample/status-management/app/authors/ui5.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: '4.0' +metadata: + name: authors +type: application +server: + customMiddleware: + - name: fiori-tools-proxy + afterMiddleware: compression + configuration: + ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + ui5: + path: + - /resources + - /test-resources + url: https://sapui5.hana.ondemand.com + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 + - name: fiori-tools-preview + afterMiddleware: fiori-tools-appreload + configuration: + flp: + theme: sap_horizon diff --git a/tests/sample/status-management/app/authors/webapp/Component.js b/tests/sample/status-management/app/authors/webapp/Component.js new file mode 100644 index 0000000..f920404 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/Component.js @@ -0,0 +1,9 @@ +sap.ui.define(['sap/fe/core/AppComponent'], function (Component) { + 'use strict'; + + return Component.extend('authors.Component', { + metadata: { + manifest: 'json', + }, + }); +}); diff --git a/tests/sample/status-management/app/authors/webapp/i18n/i18n.properties b/tests/sample/status-management/app/authors/webapp/i18n/i18n.properties new file mode 100644 index 0000000..baffc2b --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/i18n/i18n.properties @@ -0,0 +1,9 @@ +# This is the resource bundle for authors + +#Texts for manifest.json + +#XTIT: Application name +appTitle=Manage Authors + +#YDES: Application description +appDescription=An SAP Fiori application. \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/index.html b/tests/sample/status-management/app/authors/webapp/index.html new file mode 100644 index 0000000..b3aab9d --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/index.html @@ -0,0 +1,39 @@ + + + + + + + Manage Authors + + + + +
+ + diff --git a/tests/sample/status-management/app/authors/webapp/manifest.json b/tests/sample/status-management/app/authors/webapp/manifest.json new file mode 100644 index 0000000..1c2ba9d --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/manifest.json @@ -0,0 +1,154 @@ +{ + "_version": "1.76.0", + "sap.app": { + "id": "authors", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "0.0.1" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "resources": "resources.json", + "sourceTemplate": { + "id": "@sap/generator-fiori:lrop", + "version": "1.20.0", + "toolsId": "da4ba4be-cfef-4cfc-bd01-4c50a2f9f514" + }, + "crossNavigation": { + "inbounds": { + "authors-manage": { + "semanticObject": "Authors", + "action": "manage", + "title": "{{appTitle}}", + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + } + } + } + }, + "dataSources": { + "mainService": { + "uri": "/api/authors/", + "type": "OData", + "settings": { + "annotations": [], + "odataVersion": "4.0" + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "icons": { + "icon": "", + "favIcon": "", + "phone": "", + "phone@2": "", + "tablet": "", + "tablet@2": "" + }, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "flexEnabled": true, + "dependencies": { + "minUI5Version": "1.146.0", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.fe.templates": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "authors.i18n.i18n" + } + }, + "": { + "dataSource": "mainService", + "preload": true, + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true + } + }, + "@i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + } + }, + "resources": { + "css": [] + }, + "routing": { + "config": {}, + "routes": [ + { + "pattern": ":?query:", + "name": "AuthorsList", + "target": "AuthorsList" + }, + { + "pattern": "Authors({key}):?query:", + "name": "AuthorsObjectPage", + "target": "AuthorsObjectPage" + } + ], + "targets": { + "AuthorsList": { + "type": "Component", + "id": "AuthorsList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Authors", + "variantManagement": "Page", + "navigation": { + "Authors": { + "detail": { + "route": "AuthorsObjectPage" + } + } + }, + "controlConfiguration": { + "@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "type": "ResponsiveTable" + } + } + } + } + } + }, + "AuthorsObjectPage": { + "type": "Component", + "id": "AuthorsObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "editableHeaderContent": false, + "contextPath": "/Authors" + } + } + } + } + } + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js b/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js new file mode 100644 index 0000000..5730f8c --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js @@ -0,0 +1,31 @@ +sap.ui.define(['sap/ui/test/opaQunit', './pages/JourneyRunner'], function (opaTest, runner) { + 'use strict'; + + function journey() { + QUnit.module('First journey'); + + opaTest('Start application', function (Given, When, Then) { + Given.iStartMyApp(); + + Then.onTheAuthorsList.iSeeThisPage(); + }); + + opaTest('Navigate to ObjectPage', function (Given, When, Then) { + // Note: this test will fail if the ListReport page doesn't show any data + + When.onTheAuthorsList.onFilterBar().iExecuteSearch(); + + Then.onTheAuthorsList.onTable().iCheckRows(); + + When.onTheAuthorsList.onTable().iPressRow(0); + Then.onTheAuthorsObjectPage.iSeeThisPage(); + }); + + opaTest('Teardown', function (Given, When, Then) { + // Cleanup + Given.iTearDownMyApp(); + }); + } + + runner.run([journey]); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html new file mode 100644 index 0000000..c7f7083 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html @@ -0,0 +1,27 @@ + + + + Integration tests + + + + + + + + + +
+
+ + diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js new file mode 100644 index 0000000..82796af --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js @@ -0,0 +1,31 @@ +sap.ui.loader.config({ + shim: { + 'sap/ui/qunit/qunit-junit': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/qunit/qunit-coverage': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/thirdparty/sinon-qunit': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon'], + }, + 'sap/ui/qunit/sinon-qunit-bridge': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon-4'], + }, + }, +}); + +window.QUnit = Object.assign({}, window.QUnit, { config: { autostart: false } }); + +sap.ui.require( + [ + 'sap/ui/thirdparty/qunit-2', + 'sap/ui/qunit/qunit-junit', + 'sap/ui/qunit/qunit-coverage', + 'authors/test/integration/FirstJourney', + ], + function (QUnit) { + 'use strict'; + QUnit.start(); + }, +); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js new file mode 100644 index 0000000..303d179 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ListReport'], function (ListReport) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; + + return new ListReport( + { + appId: 'authors', + componentId: 'AuthorsList', + contextPath: '/Authors', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js new file mode 100644 index 0000000..335fe07 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ObjectPage'], function (ObjectPage) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; + + return new ObjectPage( + { + appId: 'authors', + componentId: 'AuthorsObjectPage', + contextPath: '/Authors', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js new file mode 100644 index 0000000..27e50b3 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js @@ -0,0 +1,21 @@ +sap.ui.define( + [ + 'sap/fe/test/JourneyRunner', + 'authors/test/integration/pages/AuthorsList', + 'authors/test/integration/pages/AuthorsObjectPage', + ], + function (JourneyRunner, AuthorsList, AuthorsObjectPage) { + 'use strict'; + + var runner = new JourneyRunner({ + launchUrl: sap.ui.require.toUrl('authors') + '/test/flp.html#app-preview', + pages: { + onTheAuthorsList: AuthorsList, + onTheAuthorsObjectPage: AuthorsObjectPage, + }, + async: true, + }); + + return runner; + }, +); diff --git a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html new file mode 100644 index 0000000..edfb63e --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html @@ -0,0 +1,9 @@ + + + + QUnit test suite + + + + + diff --git a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js new file mode 100644 index 0000000..416193d --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js @@ -0,0 +1,10 @@ +window.suite = function () { + 'use strict'; + + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), + sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); + oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); + + return oSuite; +}; diff --git a/tests/sample/status-management/app/books/.appGenInfo.json b/tests/sample/status-management/app/books/.appGenInfo.json new file mode 100644 index 0000000..da19684 --- /dev/null +++ b/tests/sample/status-management/app/books/.appGenInfo.json @@ -0,0 +1,33 @@ +{ + "generationParameters": { + "generationDate": "Tue Mar 24 2026 16:28:38 GMT+0100 (Central European Standard Time)", + "generatorPlatform": "Visual Studio Code", + "serviceType": "Local CAP", + "metadataFilename": "", + "serviceUrl": "http://localhost:4004/admin/", + "appName": "books", + "appTitle": "Manage Books", + "appDescription": "An SAP Fiori application.", + "appNamespace": "", + "ui5Theme": "sap_horizon", + "ui5Version": "1.146.0", + "enableCodeAssist": false, + "enableEslint": false, + "enableTypeScript": false, + "showMockDataInfo": false, + "generatorVersion": "1.20.0", + "template": "List Report Page V4", + "generatorName": "SAP Fiori Application Generator", + "entityRelatedConfig": [ + { + "type": "Main Entity", + "value": "Books" + }, + { + "type": "Navigation Entity", + "value": "None" + } + ], + "launchText": "To launch the generated app, start your CAP project: and navigate to the following location in your browser:\n\nhttp://localhost:4004/books/webapp/index.html" + } +} diff --git a/tests/sample/status-management/app/books/README.md b/tests/sample/status-management/app/books/README.md new file mode 100644 index 0000000..9df4b76 --- /dev/null +++ b/tests/sample/status-management/app/books/README.md @@ -0,0 +1,35 @@ +## Application Details + +| | +| -------------------------------------------------------------------------------------------------- | +| **Generation Date and Time**
Tue Mar 24 2026 16:28:38 GMT+0100 (Central European Standard Time) | +| **App Generator**
SAP Fiori Application Generator | +| **App Generator Version**
1.20.0 | +| **Generation Platform**
Visual Studio Code | +| **Template Used**
List Report Page V4 | +| **Service Type**
Local CAP | +| **Service URL**
http://localhost:4004/admin/ | +| **Module Name**
books | +| **Application Title**
Manage Books | +| **Namespace**
| +| **UI5 Theme**
sap_horizon | +| **UI5 Version**
1.146.0 | +| **Enable Code Assist Libraries**
False | +| **Enable TypeScript**
False | +| **Add Eslint configuration**
False | +| **Main Entity**
Books | +| **Navigation Entity**
None | + +## books + +An SAP Fiori application. + +### Starting the generated app + +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: + +http://localhost:4004/books/webapp/index.html + +#### Pre-requisites: + +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) diff --git a/tests/sample/status-management/app/books/annotations.cds b/tests/sample/status-management/app/books/annotations.cds new file mode 100644 index 0000000..d69a2eb --- /dev/null +++ b/tests/sample/status-management/app/books/annotations.cds @@ -0,0 +1,131 @@ +using BooksService as service from '../../srv/books-service'; +annotate service.Books with @( + UI.FieldGroup #GeneratedGroup : { + $Type : 'UI.FieldGroupType', + Data : [ + { + $Type : 'UI.DataField', + Label : 'Author', + Value : author_ID, + }, + { + $Type : 'UI.DataField', + Label : 'Title', + Value : title, + }, + { + $Type : 'UI.DataField', + Label : 'Description', + Value : descr, + }, + { + $Type : 'UI.DataField', + Label : 'Stock', + Value : stock, + }, + { + $Type : 'UI.DataField', + Label : 'Price', + Value : price, + }, + { + $Type : 'UI.DataField', + Label : 'Currency', + Value : currency_code, + }, + { + $Type : 'UI.DataField', + Label : 'Genre', + Value : genre_ID, + }, + { + $Type : 'UI.DataField', + Label : 'Approval Status', + Value : processStatus, + Criticality : processCriticality, + }, + { + $Type : 'UI.DataField', + Label : 'Approved', + Value : isApproved, + }, + ], + }, + UI.Facets : [ + { + $Type : 'UI.ReferenceFacet', + ID : 'GeneratedFacet1', + Label : 'General Information', + Target : '@UI.FieldGroup#GeneratedGroup', + }, + ], + UI.LineItem : [ + { + $Type : 'UI.DataField', + Label : 'Author', + Value : author_ID, + }, + { + $Type : 'UI.DataField', + Label : 'Title', + Value : title, + }, + { + $Type : 'UI.DataField', + Label : 'Description', + Value : descr, + }, + { + $Type : 'UI.DataField', + Label : 'Price', + Value : price, + }, + { + $Type : 'UI.DataField', + Label : 'Approval Status', + Value : processStatus, + Criticality : processCriticality, + }, + { + $Type : 'UI.DataField', + Label : 'Approved', + Value : isApproved, + }, + ], +); + +annotate service.Books with { + author @Common.Text : author.name @Common.TextArrangement : #TextOnly; + genre @Common.Text : genre.name @Common.TextArrangement : #TextOnly; + currency @Common.Text : currency.name @Common.TextArrangement : #TextOnly; +}; + +annotate service.Books with { + author @Common.ValueList : { + $Type : 'Common.ValueListType', + CollectionPath : 'Authors', + Parameters : [ + { + $Type : 'Common.ValueListParameterInOut', + LocalDataProperty : author_ID, + ValueListProperty : 'ID', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'name', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'dateOfBirth', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'dateOfDeath', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'placeOfBirth', + }, + ], + } +}; diff --git a/tests/sample/status-management/app/books/package.json b/tests/sample/status-management/app/books/package.json new file mode 100644 index 0000000..545d54d --- /dev/null +++ b/tests/sample/status-management/app/books/package.json @@ -0,0 +1,19 @@ +{ + "name": "books", + "version": "0.0.1", + "description": "An SAP Fiori application.", + "keywords": [ + "ui5", + "openui5", + "sapui5" + ], + "main": "webapp/index.html", + "dependencies": {}, + "devDependencies": { + "@ui5/cli": "^4.0.33", + "@sap/ux-ui5-tooling": "1" + }, + "scripts": { + "deploy-config": "npx -p @sap/ux-ui5-tooling fiori add deploy-config cf" + } +} diff --git a/tests/sample/status-management/app/books/ui5.yaml b/tests/sample/status-management/app/books/ui5.yaml new file mode 100644 index 0000000..5481e8f --- /dev/null +++ b/tests/sample/status-management/app/books/ui5.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: '4.0' +metadata: + name: books +type: application +server: + customMiddleware: + - name: fiori-tools-proxy + afterMiddleware: compression + configuration: + ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + ui5: + path: + - /resources + - /test-resources + url: https://sapui5.hana.ondemand.com + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 + - name: fiori-tools-preview + afterMiddleware: fiori-tools-appreload + configuration: + flp: + theme: sap_horizon diff --git a/tests/sample/status-management/app/books/webapp/Component.js b/tests/sample/status-management/app/books/webapp/Component.js new file mode 100644 index 0000000..917c6ce --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/Component.js @@ -0,0 +1,9 @@ +sap.ui.define(['sap/fe/core/AppComponent'], function (Component) { + 'use strict'; + + return Component.extend('books.Component', { + metadata: { + manifest: 'json', + }, + }); +}); diff --git a/tests/sample/status-management/app/books/webapp/i18n/i18n.properties b/tests/sample/status-management/app/books/webapp/i18n/i18n.properties new file mode 100644 index 0000000..68fbdc1 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/i18n/i18n.properties @@ -0,0 +1,9 @@ +# This is the resource bundle for books + +#Texts for manifest.json + +#XTIT: Application name +appTitle=Manage Books + +#YDES: Application description +appDescription=An SAP Fiori application. \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/index.html b/tests/sample/status-management/app/books/webapp/index.html new file mode 100644 index 0000000..6f14444 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/index.html @@ -0,0 +1,39 @@ + + + + + + + Manage Books + + + + +
+ + diff --git a/tests/sample/status-management/app/books/webapp/manifest.json b/tests/sample/status-management/app/books/webapp/manifest.json new file mode 100644 index 0000000..a7135ee --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/manifest.json @@ -0,0 +1,154 @@ +{ + "_version": "1.76.0", + "sap.app": { + "id": "books", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "0.0.1" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "resources": "resources.json", + "sourceTemplate": { + "id": "@sap/generator-fiori:lrop", + "version": "1.20.0", + "toolsId": "ea000289-e433-433f-9d67-da5f23449471" + }, + "crossNavigation": { + "inbounds": { + "books-manage": { + "semanticObject": "Books", + "action": "manage", + "title": "{{appTitle}}", + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + } + } + } + }, + "dataSources": { + "mainService": { + "uri": "/api/books/", + "type": "OData", + "settings": { + "annotations": [], + "odataVersion": "4.0" + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "icons": { + "icon": "", + "favIcon": "", + "phone": "", + "phone@2": "", + "tablet": "", + "tablet@2": "" + }, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "flexEnabled": true, + "dependencies": { + "minUI5Version": "1.146.0", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.fe.templates": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "books.i18n.i18n" + } + }, + "": { + "dataSource": "mainService", + "preload": true, + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true + } + }, + "@i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + } + }, + "resources": { + "css": [] + }, + "routing": { + "config": {}, + "routes": [ + { + "pattern": ":?query:", + "name": "BooksList", + "target": "BooksList" + }, + { + "pattern": "Books({key}):?query:", + "name": "BooksObjectPage", + "target": "BooksObjectPage" + } + ], + "targets": { + "BooksList": { + "type": "Component", + "id": "BooksList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Books", + "variantManagement": "Page", + "navigation": { + "Books": { + "detail": { + "route": "BooksObjectPage" + } + } + }, + "controlConfiguration": { + "@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "type": "ResponsiveTable" + } + } + } + } + } + }, + "BooksObjectPage": { + "type": "Component", + "id": "BooksObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "editableHeaderContent": false, + "contextPath": "/Books" + } + } + } + } + } + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js b/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js new file mode 100644 index 0000000..57b99d4 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js @@ -0,0 +1,31 @@ +sap.ui.define(['sap/ui/test/opaQunit', './pages/JourneyRunner'], function (opaTest, runner) { + 'use strict'; + + function journey() { + QUnit.module('First journey'); + + opaTest('Start application', function (Given, When, Then) { + Given.iStartMyApp(); + + Then.onTheBooksList.iSeeThisPage(); + }); + + opaTest('Navigate to ObjectPage', function (Given, When, Then) { + // Note: this test will fail if the ListReport page doesn't show any data + + When.onTheBooksList.onFilterBar().iExecuteSearch(); + + Then.onTheBooksList.onTable().iCheckRows(); + + When.onTheBooksList.onTable().iPressRow(0); + Then.onTheBooksObjectPage.iSeeThisPage(); + }); + + opaTest('Teardown', function (Given, When, Then) { + // Cleanup + Given.iTearDownMyApp(); + }); + } + + runner.run([journey]); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html new file mode 100644 index 0000000..a8f9cd9 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html @@ -0,0 +1,27 @@ + + + + Integration tests + + + + + + + + + +
+
+ + diff --git a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js new file mode 100644 index 0000000..675fb9f --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js @@ -0,0 +1,31 @@ +sap.ui.loader.config({ + shim: { + 'sap/ui/qunit/qunit-junit': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/qunit/qunit-coverage': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/thirdparty/sinon-qunit': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon'], + }, + 'sap/ui/qunit/sinon-qunit-bridge': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon-4'], + }, + }, +}); + +window.QUnit = Object.assign({}, window.QUnit, { config: { autostart: false } }); + +sap.ui.require( + [ + 'sap/ui/thirdparty/qunit-2', + 'sap/ui/qunit/qunit-junit', + 'sap/ui/qunit/qunit-coverage', + 'books/test/integration/FirstJourney', + ], + function (QUnit) { + 'use strict'; + QUnit.start(); + }, +); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js new file mode 100644 index 0000000..d9f9c7c --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ListReport'], function (ListReport) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; + + return new ListReport( + { + appId: 'books', + componentId: 'BooksList', + contextPath: '/Books', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js new file mode 100644 index 0000000..eee0453 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ObjectPage'], function (ObjectPage) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; + + return new ObjectPage( + { + appId: 'books', + componentId: 'BooksObjectPage', + contextPath: '/Books', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js new file mode 100644 index 0000000..8ff464d --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js @@ -0,0 +1,21 @@ +sap.ui.define( + [ + 'sap/fe/test/JourneyRunner', + 'books/test/integration/pages/BooksList', + 'books/test/integration/pages/BooksObjectPage', + ], + function (JourneyRunner, BooksList, BooksObjectPage) { + 'use strict'; + + var runner = new JourneyRunner({ + launchUrl: sap.ui.require.toUrl('books') + '/test/flp.html#app-preview', + pages: { + onTheBooksList: BooksList, + onTheBooksObjectPage: BooksObjectPage, + }, + async: true, + }); + + return runner; + }, +); diff --git a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html new file mode 100644 index 0000000..edfb63e --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html @@ -0,0 +1,9 @@ + + + + QUnit test suite + + + + + diff --git a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js new file mode 100644 index 0000000..416193d --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js @@ -0,0 +1,10 @@ +window.suite = function () { + 'use strict'; + + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), + sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); + oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); + + return oSuite; +}; diff --git a/tests/sample/status-management/app/services.cds b/tests/sample/status-management/app/services.cds new file mode 100644 index 0000000..f0d0841 --- /dev/null +++ b/tests/sample/status-management/app/services.cds @@ -0,0 +1,4 @@ + +using from './books/annotations'; + +using from './authors/annotations'; \ No newline at end of file diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv new file mode 100644 index 0000000..63723e7 --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv @@ -0,0 +1,5 @@ +ID,name,dateOfBirth,placeOfBirth,dateOfDeath,placeOfDeath +a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire" +b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire" +c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f,Edgar Allan Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland" +d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a,Richard Carpenter,1929-08-14,"King's Lynn, Norfolk",2012-02-26,"Hertfordshire, England" diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv new file mode 100644 index 0000000..c96b0a1 --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv @@ -0,0 +1,6 @@ +ID,title,descr,author_ID,stock,price,currency_code,genre_ID +e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b,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.",a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d,12,11.11,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c,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.",b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e,11,12.34,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +a7b8c9d0-e1f2-4a3b-4c5d-6e7f8a9b0c1d,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.",c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f,333,13.13,USD,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e,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.",c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f,555,14,USD,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +c9d0e1f2-a3b4-4c5d-6e7f-8a9b0c1d2e3f,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.",d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a,22,3,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv new file mode 100644 index 0000000..af8f78e --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv @@ -0,0 +1,5 @@ +ID,locale,title,descr +e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b,de,Sturmhöhe,"Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts." +e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b,fr,Les Hauts de Hurlevent,"Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d'Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal." +f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c,de,Jane Eyre,"Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte" +b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e,de,Eleonora,"„Eleonora" ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit." diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv new file mode 100644 index 0000000..8e22e9b --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv @@ -0,0 +1,43 @@ +ID,parent_ID,name +10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Fiction +11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Drama +12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Poetry +13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fantasy +131aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fairy Tale +132aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Epic Fantasy +133aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,High Fantasy +134aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Gothic +14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Science Fiction +141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian and Dystopian +1411aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian +1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Dystopian +14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cyberpunk +141211aa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Steampunk +142aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Space Opera +143aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Time Travel +144aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tech Noir +15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romance +151aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Contemporary Romance +152aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Historical Romance +153aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romantic Suspense +16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Mystery +161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Crime +1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Thriller +16111aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Police Procedural +16112aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Legal Thriller +16113aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Medical Thriller +16114aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Spy Thriller +1612aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Detective +1613aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Suspense +162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Noir +1621aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Nordic Noir +1622aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tart Noir +163aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cozy Mystery +17aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Adventure +18aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Short Story +19aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Graphic Novel +20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Non-Fiction +21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Biography +22aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Autobiography +23aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Essay +24aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Speech diff --git a/tests/sample/status-management/db/init.js b/tests/sample/status-management/db/init.js new file mode 100644 index 0000000..fe3fdc8 --- /dev/null +++ b/tests/sample/status-management/db/init.js @@ -0,0 +1,21 @@ +const cds = require('@sap/cds'); + +/** + * In order to keep basic bookshop sample as simple as possible, we don't add + * reuse dependencies. This db/init.js ensures we still have a minimum set of + * currencies, if not obtained through @capire/common. + */ + +// NOTE: We use cds.on('served') to delay the UPSERTs after the db init +// to run after all INSERTs from .csv files happened. +module.exports = cds.on('served', () => + UPSERT.into('sap.common.Currencies') + .columns(['code', 'symbol', 'name']) + .rows( + ['EUR', '€', 'Euro'], + ['USD', '$', 'US Dollar'], + ['GBP', '£', 'British Pound'], + ['ILS', '₪', 'Shekel'], + ['JPY', '¥', 'Yen'], + ), +); diff --git a/tests/sample/status-management/db/schema.cds b/tests/sample/status-management/db/schema.cds new file mode 100644 index 0000000..ad69f36 --- /dev/null +++ b/tests/sample/status-management/db/schema.cds @@ -0,0 +1,30 @@ +using { Currency, cuid, managed, sap } from '@sap/cds/common'; +namespace sap.capire.bookshop; + +entity Books : cuid, managed { + author : Association to Authors @mandatory; + title : localized String @mandatory; + descr : localized String(2000); + genre : Association to Genres; + stock : Integer; + price : Price; + currency : Currency; +} + +entity Authors : cuid, managed { + name : String @mandatory; + dateOfBirth : Date; + dateOfDeath : Date; + placeOfBirth : String; + placeOfDeath : String; + books : Association to many Books on books.author = $self; +} + +/** Hierarchically organized Code List for Genres */ +entity Genres : cuid, sap.common.CodeList { + parent : Association to Genres; + children : Composition of many Genres on children.parent = $self; +} + +type Price : Decimal(9,2); + diff --git a/tests/sample/status-management/index.cds b/tests/sample/status-management/index.cds new file mode 100644 index 0000000..f229305 --- /dev/null +++ b/tests/sample/status-management/index.cds @@ -0,0 +1,6 @@ + +namespace sap.capire.statusmanagement; //> important for reflection +using from './db/schema'; +using from './srv/cat-service'; +using from './srv/books-service'; +using from './srv/authors-service'; diff --git a/tests/sample/status-management/package.json b/tests/sample/status-management/package.json new file mode 100644 index 0000000..ee4c67d --- /dev/null +++ b/tests/sample/status-management/package.json @@ -0,0 +1,49 @@ +{ + "name": "@capire/bookshop", + "version": "2.1.9", + "description": "Our primer sample for getting started in a nutshell.", + "repository": "https://github.com/capire/bookshop", + "files": [ + "app", + "srv", + "db", + "index.cds" + ], + "devDependencies": { + "@cap-js/sqlite": "^2.1.1", + "cds-plugin-ui5": "^0.13.0" + }, + "dependencies": { + "@sap/cds": "^9.6.2", + "@cap-js/process": "file:../../../", + "@sap-cloud-sdk/connectivity": "^4", + "@sap-cloud-sdk/http-client": "^4", + "@sap-cloud-sdk/resilience": "^4" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch", + "watch-books": "cds watch --open books/index.html?sap-ui-xx-viewCache=false --livereload false", + "watch-authors": "cds watch --open authors/index.html?sap-ui-xx-viewCache=false --livereload false" + }, + "license": "Apache-2.0", + "workspaces": [ + "app/*" + ], + "sapux": [ + "app/books", + "app/authors" + ], + "cds": { + "requires": { + "eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService": { + "kind": "process-service", + "model": "srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess" + }, + "eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService": { + "kind": "process-service", + "model": "srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess" + } + } + } +} diff --git a/tests/sample/status-management/srv/authors-constraints.cds b/tests/sample/status-management/srv/authors-constraints.cds new file mode 100644 index 0000000..f32b730 --- /dev/null +++ b/tests/sample/status-management/srv/authors-constraints.cds @@ -0,0 +1,19 @@ +using { AuthorsService } from './authors-service.cds'; + +// Add constraints for Authors +annotate AuthorsService.Authors with { + + name @mandatory; + + dateOfBirth @mandatory@assert: (case + when dateOfBirth > dateOfDeath then 'Date of birth cannot be after date of death' + end); + + dateOfDeath @assert: (case + when dateOfDeath < dateOfBirth then 'Date of death cannot be before date of birth' + end); +} + +// Require 'admin' role to access AuthorsService +// (disabled for getting-started guide) +// annotate AuthorsService with @requires:'admin'; diff --git a/tests/sample/status-management/srv/authors-service.cds b/tests/sample/status-management/srv/authors-service.cds new file mode 100644 index 0000000..28fe4a1 --- /dev/null +++ b/tests/sample/status-management/srv/authors-service.cds @@ -0,0 +1,13 @@ +using { sap.capire.bookshop as my } from '../db/schema'; + +service AuthorsService @(path:'/api/authors', protocol: 'odata-v4') { + @odata.draft.enabled + entity Authors as projection on my.Authors { + *, + @UI.Hidden: (not $draft.IsActiveEntity) virtual verificationStatus: String, + @UI.Hidden: (not $draft.IsActiveEntity) virtual isVerified: Boolean default false, + @UI.Hidden: (not $draft.IsActiveEntity) virtual verificationCriticality: Integer default 0 + }; +} + + diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js new file mode 100644 index 0000000..d765b94 --- /dev/null +++ b/tests/sample/status-management/srv/authors-service.js @@ -0,0 +1,102 @@ +const cds = require('@sap/cds'); + +const AUTHOR_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService'; + +const VERIFICATION_PENDING = 'Verification Pending'; +const VERIFICATION_CANCELED = 'Cancelled'; +const VERIFICATION_SUCCESSFUL = 'Author verified'; + +module.exports = class AuthorsService extends cds.ApplicationService { + async init() { + const { Authors } = this.entities; + const authorProcess = await cds.connect.to(AUTHOR_PROCESS); + // Track authors created at runtime so we can distinguish them from seed data + const createdAuthorIDs = new Set(); + // --------------------------------------------------------------- + // Authors: Programmatic process lifecycle (start, cancel, status) + // --------------------------------------------------------------- + + // Start verification process when a new author is created + this.after('CREATE', Authors, async (author, req) => { + createdAuthorIDs.add(author.ID); + await authorProcess.start({ + entityid: author.ID, + authorname: author.name, + dateofbirth: author.dateOfBirth ?? '', + placeofbirth: author.placeOfBirth ?? '', + }); + }); + + // Cancel verification process when an unverified author is deleted + this.after('DELETE', Authors, async (author, req) => { + if (!author.ID) return; + + const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); + if (instances.length > 0) { + await authorProcess.cancel({ businessKey: author.ID, cascade: true }); + } + }); + + // Enrich authors with verification process status + this.after('READ', Authors, async (results, _req) => { + await Promise.all( + results.map(async (author) => { + if (!author.ID) { + author.verificationStatus = VERIFICATION_PENDING; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + return; + } + + const instances = await authorProcess.getInstancesByBusinessKey(author.ID, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0]?.id && instances[0]?.status) { + const { id, status } = instances[0]; + + switch (status) { + case 'RUNNING': + { + const attributes = await authorProcess.getAttributes(id); + author.verificationStatus = attributes[0]?.value ?? VERIFICATION_PENDING; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } + break; + case 'COMPLETED': + { + const outputs = await authorProcess.getOutputs(id); + author.verificationStatus = outputs.verificationstatus; + author.isVerified = outputs.isverified; + author.verificationCriticality = outputs.isverified ? 3 : 1; // Green or Red + } + break; + case 'CANCELED': + { + author.verificationStatus = VERIFICATION_CANCELED; + author.isVerified = false; + author.verificationCriticality = 0; // Neutral + } + break; + } + } else if (createdAuthorIDs.has(author.ID)) { + // Just created but process instance not yet visible (race condition) + author.verificationStatus = VERIFICATION_PENDING; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } else { + // Pre-existing seed data author with no process — treat as verified + author.verificationStatus = VERIFICATION_SUCCESSFUL; + author.isVerified = true; + author.verificationCriticality = 3; // Positive (green) + } + }), + ); + }); + + return super.init(); + } +}; diff --git a/tests/sample/status-management/srv/books-constraints.cds b/tests/sample/status-management/srv/books-constraints.cds new file mode 100644 index 0000000..c06c53e --- /dev/null +++ b/tests/sample/status-management/srv/books-constraints.cds @@ -0,0 +1,28 @@ +using { BooksService } from './books-service.cds'; + +// Add constraints for input validation on Books +annotate BooksService.Books with { + + title @mandatory; + descr @mandatory; + price @mandatory; + author @mandatory @assert: (case + when not exists author then 'Specified Author does not exist' + end); + + genre @mandatory @assert: (case + when not exists genre then 'Specified Genre does not exist' + end); + + stock @assert.range: [(0),_]; // positive numbers only +} + +// Add constraints for Genres +annotate BooksService.Genres with { + + name @mandatory; + + parent @assert: (case + when parent == ID then 'A genre cannot be its own parent' + end); +} diff --git a/tests/sample/status-management/srv/books-process.cds b/tests/sample/status-management/srv/books-process.cds new file mode 100644 index 0000000..9e2adb1 --- /dev/null +++ b/tests/sample/status-management/srv/books-process.cds @@ -0,0 +1,23 @@ +using { BooksService } from './books-service.cds'; + +annotate BooksService.Books with @( + + bpm.process.businessKey: (ID), + bpm.process.start : { + id: 'eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess', + on: 'CREATE', + inputs: [ + { path: $self.ID, as: 'entityid' }, + { path: $self.title, as: 'booktitle'}, + { path: $self.descr, as: 'description'}, + $self.author.name, + $self.author.dateOfBirth, + $self.price, + ], + if: (price > 50) + }, + bpm.process.cancel : { + on: 'UPDATE', + if: (price <= 50) + } +); \ No newline at end of file diff --git a/tests/sample/status-management/srv/books-service.cds b/tests/sample/status-management/srv/books-service.cds new file mode 100644 index 0000000..818623c --- /dev/null +++ b/tests/sample/status-management/srv/books-service.cds @@ -0,0 +1,15 @@ +using { sap.capire.bookshop as my } from '../db/schema'; + +service BooksService @(path:'/api/books', protocol: 'odata-v4') { + @odata.draft.enabled + entity Books as projection on my.Books { + *, + @UI.Hidden: (not $draft.IsActiveEntity) virtual processStatus: String, + @UI.Hidden: (not $draft.IsActiveEntity) virtual isApproved: Boolean default false, + @UI.Hidden: (not $draft.IsActiveEntity) virtual processCriticality: Integer default 0 + }; + entity Genres as projection on my.Genres; + @readonly entity Authors as projection on my.Authors; +} + + diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js new file mode 100644 index 0000000..4f6dd9f --- /dev/null +++ b/tests/sample/status-management/srv/books-service.js @@ -0,0 +1,79 @@ +const cds = require('@sap/cds'); + +const BOOK_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService'; + +const NO_APPROVAL_REQUIRED = 'No Approval Required'; +const PENDING_APPROVAL = 'Pending Approval'; +const APPROVAL_CANCELLED = 'Approval process cancelled as price has been reduced'; + +module.exports = class BooksService extends cds.ApplicationService { + async init() { + const { Books } = this.entities; + const bookProcess = await cds.connect.to(BOOK_PROCESS); + // --------------------------------------------------------------- + // Books: Enrich with approval process status (declarative start) + // --------------------------------------------------------------- + this.after('READ', Books, async (results, _req) => { + await Promise.all( + results.map(async (book) => { + const bookID = book.ID; + if (!bookID) { + // Draft entries + book.processStatus = NO_APPROVAL_REQUIRED; + book.isApproved = true; + book.processCriticality = 0; // Neutral + return; + } + + const instances = await bookProcess.getInstancesByBusinessKey(bookID, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0]?.id && instances[0]?.status) { + const { id, status } = instances[0]; + + switch (status) { + case 'RUNNING': + { + let attributes = await bookProcess.getAttributes(id); + book.processStatus = attributes[0]?.value ?? PENDING_APPROVAL; + book.isApproved = false; + book.processCriticality = 2; + } + break; + case 'COMPLETED': + { + const outputs = await bookProcess.getOutputs(id); + const { finalstatus, isapproved } = outputs; + book.processStatus = finalstatus; + book.isApproved = isapproved; + book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) + } + break; + case 'CANCELED': + { + book.processStatus = APPROVAL_CANCELLED; + book.isApproved = true; + book.processCriticality = 3; // Positive (green) + } + break; + } + } else if (book.price > 50) { + // Process was likely just triggered but hasn't registered in SBPA yet + book.processStatus = PENDING_APPROVAL; + book.isApproved = false; + book.processCriticality = 2; // Warning (yellow) + } else { + book.processStatus = NO_APPROVAL_REQUIRED; + book.isApproved = true; + book.processCriticality = 3; // Positive (green) + } + }), + ); + }); + + return super.init(); + } +}; diff --git a/tests/sample/status-management/srv/cat-service.cds b/tests/sample/status-management/srv/cat-service.cds new file mode 100644 index 0000000..38c92b9 --- /dev/null +++ b/tests/sample/status-management/srv/cat-service.cds @@ -0,0 +1,23 @@ +using { sap.capire.bookshop as my } from '../db/schema'; + +service CatalogService @(path:'browse') { + + /** For displaying lists of Books */ + @readonly entity ListOfBooks as projection on Books { + *, currency.symbol as currency, + } + excluding { descr }; + + /** For display in details pages */ + @readonly entity Books as projection on my.Books { + *, // all fields with the following denormalizations: + author.name as author, + genre.name as genre, + } excluding { createdBy, modifiedBy }; + + @requires: 'authenticated-user' + action submitOrder ( book: Books:ID, quantity: Integer ); +} + +// Serve via OData, HCQL and REST +annotate CatalogService with @odata @hcql @rest; diff --git a/tests/sample/status-management/srv/cat-service.js b/tests/sample/status-management/srv/cat-service.js new file mode 100644 index 0000000..a8c3119 --- /dev/null +++ b/tests/sample/status-management/srv/cat-service.js @@ -0,0 +1,29 @@ +const cds = require('@sap/cds'); + +class CatalogService extends cds.ApplicationService { + init() { + const { Books } = cds.entities('sap.capire.bookshop'); + const { ListOfBooks } = this.entities; + + // Add some discount for overstocked books + this.after('each', ListOfBooks, (book) => { + if (book.stock > 111) book.title += ` -- 11% discount!`; + }); + + // Reduce stock of ordered books if available stock suffices + this.on('submitOrder', async (req) => { + let { book: id, quantity } = req.data; + if (quantity < 1) return req.error(400, `quantity has to be 1 or more`); + let succeeded = await UPDATE(Books, id).with`stock = stock - ${quantity}` + .where`stock >= ${quantity}`; + if (succeeded) return; + else if (!this.exists(Books, id)) req.error(404, `Book #${id} doesn't exist`); + else req.error(409, `${quantity} exceeds stock for book #${id}`); + }); + + // Delegate requests to the underlying generic service + return super.init(); + } +} + +module.exports = CatalogService; diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds new file mode 100644 index 0000000..dec633f --- /dev/null +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds @@ -0,0 +1,72 @@ +/* checksum : e4e85b6560cc4b66e72dcd48b9b3ee30 */ +namespace eu12.cdsmunich.sampleapplicationproject; + +/** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ +@protocol : 'none' +@bpm.process : 'eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess' +service AuthorVerificationProcessService { + type ProcessInputs { + authorname : String not null; + dateofbirth : String; + placeofbirth : String; + entityid : String; + }; + + type ProcessOutputs { + isverified : Boolean not null; + verificationstatus : String not null; + }; + + type ProcessAttribute { + id : String not null; + label : String not null; + value : String; + type : String not null; + }; + + type ProcessAttributes : many ProcessAttribute; + + type ProcessInstance { + definitionId : String; + definitionVersion : String; + id : String; + status : String; + startedAt : String; + startedBy : String; + }; + + type ProcessInstances : many ProcessInstance; + + action start( + inputs : ProcessInputs not null + ); + + function getAttributes( + processInstanceId : String not null + ) returns ProcessAttributes; + + function getOutputs( + processInstanceId : String not null + ) returns ProcessOutputs; + + function getInstancesByBusinessKey( + businessKey : String not null, + status : many String + ) returns ProcessInstances; + + action suspend( + businessKey : String not null, + cascade : Boolean + ); + + action resume( + businessKey : String not null, + cascade : Boolean + ); + + action cancel( + businessKey : String not null, + cascade : Boolean + ); +}; + diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds new file mode 100644 index 0000000..1c78ad8 --- /dev/null +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds @@ -0,0 +1,78 @@ +/* checksum : 814a8d2cd20e74f6fa5a984733c3fe07 */ +namespace eu12.cdsmunich.sampleapplicationproject; + +/** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ +@protocol : 'none' +@bpm.process : 'eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess' +service BookApprovalProcessService { + type Author { + name : String; + dateOfBirth : String; + }; + + type ProcessInputs { + booktitle : String; + author : Author; + description : String; + price : DecimalFloat; + entityid : String; + }; + + type ProcessOutputs { + isapproved : Boolean not null; + finalstatus : String not null; + }; + + type ProcessAttribute { + id : String not null; + label : String not null; + value : String; + type : String not null; + }; + + type ProcessAttributes : many ProcessAttribute; + + type ProcessInstance { + definitionId : String; + definitionVersion : String; + id : String; + status : String; + startedAt : String; + startedBy : String; + }; + + type ProcessInstances : many ProcessInstance; + + action start( + inputs : ProcessInputs + ); + + function getAttributes( + processInstanceId : String not null + ) returns ProcessAttributes; + + function getOutputs( + processInstanceId : String not null + ) returns ProcessOutputs; + + function getInstancesByBusinessKey( + businessKey : String not null, + status : many String + ) returns ProcessInstances; + + action suspend( + businessKey : String not null, + cascade : Boolean + ); + + action resume( + businessKey : String not null, + cascade : Boolean + ); + + action cancel( + businessKey : String not null, + cascade : Boolean + ); +}; + diff --git a/tests/sample/status-management/test/requests-hcql.http b/tests/sample/status-management/test/requests-hcql.http new file mode 100644 index 0000000..732a1e0 --- /dev/null +++ b/tests/sample/status-management/test/requests-hcql.http @@ -0,0 +1,55 @@ +@cats = http://localhost:4004/hcql/browse +@admin = http://localhost:4004/hcql/admin + + +### CatalogService.read Books +GET {{cats}}/Books { ID, title, author } where author like '%Bro%' + + +### Same with body fragment +GET {{cats}}/Books +Content-Type: text/plain + +{ ID, title, author } +where author like '%Bro%' + + +### Same with POST +POST {{cats}} +Content-Type: text/plain + +SELECT from Books [where author like '%Bro%'] { + ID, title, author +} + + +### AdminService.read Authors +POST {{admin}} +Authorization: Basic alice: +Content-Type: text/plain + +SELECT from Authors { + ID, name, books { + ID, title, + genre.name as genre + } +} + + +### Same with body as CQN +POST {{admin}} +Authorization: Basic alice: +Content-Type: application/json + +{ "SELECT": { + "from": { "ref": [ "Authors" ] }, + "columns": [ + { "ref": [ "ID" ] }, + { "ref": [ "name" ] }, + { "ref": [ "books" ], "expand": [ + { "ref": [ "ID" ] }, + { "ref": [ "title" ] }, + { "ref": [ "genre", "name" ], "as": "genre" } + ]} + ] +}} diff --git a/tests/sample/status-management/test/requests.http b/tests/sample/status-management/test/requests.http new file mode 100644 index 0000000..9cb0d78 --- /dev/null +++ b/tests/sample/status-management/test/requests.http @@ -0,0 +1,30 @@ + +### CatalogService.read Books +GET http://localhost:4004/browse/Books? +&$select=ID,title,author +&$filter=contains(author,'Bro') + + +### AdmingService.read Authors +GET http://localhost:4004/admin/Authors? +&$select=ID,name +&$expand=books($select=ID,title;$expand=genre($select=name)) +Authorization: Basic alice: + + +### Same with inofficial genre/name syntax +GET http://localhost:4004/admin/Authors? +&$select=ID,name +&$expand=books($select=ID,title,genre/name) +Authorization: Basic alice: + + +### CatalogService.submitOrder() +POST http://localhost:4004/browse/submitOrder +Content-Type: application/json +Authorization: Basic bob: + +{ + "book": 201, + "quantity": 3 +} diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json new file mode 100644 index 0000000..2ca36be --- /dev/null +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json @@ -0,0 +1,117 @@ +{ + "uid": "7b477c46-74fa-4c87-8414-09acb470663f", + "name": "Author Verification Process", + "description": "", + "type": "bpi.process", + "createdAt": "2026-03-26T08:54:22.179844Z", + "updatedAt": "2026-03-26T11:59:23.987727Z", + "header": { + "inputs": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "authorname": { + "type": "string", + "title": "authorName", + "description": "" + }, + "dateofbirth": { + "type": "string", + "title": "dateOfBirth", + "description": "" + }, + "placeofbirth": { + "type": "string", + "title": "placeOfBirth", + "description": "" + }, + "entityid": { + "type": "string", + "title": "entityID", + "description": "" + } + }, + "required": ["authorname"] + }, + "outputs": { + "title": "outputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "isverified": { + "type": "boolean", + "title": "isverified", + "description": "" + }, + "verificationstatus": { + "type": "string", + "title": "verificationStatus", + "description": "" + } + }, + "required": ["isverified", "verificationstatus"] + }, + "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", + "type": "object", + "properties": {}, + "required": [] + } + }, + "dependencies": [ + { + "artifactUid": "f31afeb5-9107-423c-932c-69ce21e93880", + "type": "content" + } + ], + "identifier": "authorVerificationProcess", + "valid": true, + "projectId": "eu12.cdsmunich.sampleapplicationproject", + "dataTypes": [] +} diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json new file mode 100644 index 0000000..0a7eb2a --- /dev/null +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json @@ -0,0 +1,159 @@ +{ + "uid": "76a6d601-ef75-43aa-8fed-346fb0fc2727", + "name": "Book Approval Process", + "description": "", + "type": "bpi.process", + "createdAt": "2026-03-25T11:46:56.091829Z", + "updatedAt": "2026-03-26T12:00:00.525166Z", + "header": { + "inputs": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "booktitle": { + "type": "string", + "title": "bookTitle", + "description": "" + }, + "author": { + "$ref": "$.cfc75dd1-c8d4-407b-914a-3592082e1004", + "refName": "Author", + "title": "author", + "description": "" + }, + "description": { + "type": "string", + "title": "description", + "description": "" + }, + "price": { + "type": "number", + "title": "price", + "description": "" + }, + "entityid": { + "type": "string", + "title": "entityID", + "description": "" + } + }, + "required": [] + }, + "outputs": { + "title": "outputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "isapproved": { + "type": "boolean", + "title": "isApproved", + "description": "" + }, + "finalstatus": { + "type": "string", + "title": "finalStatus", + "description": "" + } + }, + "required": ["isapproved", "finalstatus"] + }, + "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", + "type": "object", + "properties": {}, + "required": [] + }, + "requiresBusinessUser": false + }, + "dependencies": [ + { + "artifactUid": "3e51af12-9a42-4a95-9a3a-d12ef176ea42", + "type": "content" + }, + { + "artifactUid": "5227d63c-f620-476c-893a-517bd14c302e", + "type": "content" + }, + { + "artifactUid": "cfc75dd1-c8d4-407b-914a-3592082e1004", + "type": "both" + } + ], + "identifier": "bookApprovalProcess", + "valid": true, + "projectId": "eu12.cdsmunich.sampleapplicationproject", + "dataTypes": [ + { + "uid": "cfc75dd1-c8d4-407b-914a-3592082e1004", + "name": "Author", + "description": "", + "type": "datatype", + "createdAt": "2026-03-25T11:54:07.324680Z", + "updatedAt": "2026-03-25T11:54:32.177064Z", + "header": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Author", + "properties": { + "name": { + "type": "string", + "uid": "35918fe0-f647-4e16-918c-c5dfaa1d5c9b" + }, + "dateOfBirth": { + "type": "string", + "uid": "ac1a6ddf-073d-4365-8a8d-f97fe4e49915" + } + }, + "version": 1 + }, + "identifier": "author", + "valid": true + } + ] +}